Rails:使用 ActiveModel 的自定义验证
Rails: Custom validations using ActiveModel
我目前正在尝试使自定义验证与日期输入一起工作,但不幸的是,它似乎不起作用。
应用程序中有两个页面,索引页面和搜索页面。在索引页内有一个输入日期的文本字段。我正在使用 Chronic gem 将文本解析为日期。如果日期无效,Chronic returns nil。如果有效,它会重定向到搜索页面并显示日期。
到目前为止我写的代码似乎不能正常工作,但我想要实现的是..
1) 验证 Chronic 不会 return nil
2) 验证该日期是否大于今天的日期
请注意,我没有为此使用数据库,我只是希望能够在没有 ActiveRecord 的情况下验证输入的日期。如果有人能帮我解决这个问题,我将不胜感激。
views/main/index.html.erb
<%= form_tag({controller: "main", action: "search"}, method: "get") do %>
<%= label_tag(:q, "Enter Date:") %>
<%= text_field_tag(:q) %>
<%= submit_tag "Explore", name: nil %>
<% end %>
views/main/search.html.erb
<%= @show_date %>
main_controller.rb
def search
passed_info = Main.new
if passed_info.valid_date?
@show_date = passed_info
else
flash[:error] = "Please enter correct date!"
render :index => 'new'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :q
validates_presence_of :q
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
def valid_date?
require 'chronic'
if Chronic.parse(q).nil? || Chronic.parse(q) < Time.today
errors.add(:q, "is missing or invalid")
end
end
end
编辑:
这就是问题所在...
localhost:3000
然后重定向到..
localhost:3000/main/search?utf8=%E2%9C%93&q=invalid+date+test
没有验证,没有日期,什么都没有..
问题
对 return 值要更加小心。当您尝试使用 if valid_date?
保护您的控制器时,您正在做的是检查 valid_date?
return 是否为假。如果解析失败,return 值是 errors.add
的输出,而后者又是 Array#<< 的输出。相关地,输出不是 nil
或 false
,因此它的计算结果为真,因此 if
子句通过并且您继续前进。
可能的解决方案
您可能想让 Rails Validation Framework 为您做更多的工作。不要将 valid_date?
视为控制器调用的 public 方法,而是调用 ActiveModel::Validations
添加的 valid?
方法。 valid?
将 return 一个布尔值,基于是否所有模型验证都通过。因此,您可以像 Rails 方式一样,在您的控制器中调用 if model_instance.valid?
。
这让您只需在模型中编写验证器方法来表达您尝试编写的逻辑。现在,您在一个方法中拥有所有日期验证逻辑,并带有一条错误消息。相反,您可以放置两个方法,它们会添加更具描述性的单个错误方法。
class YourClass
include ActiveModel::Validations
validate :date_is_valid
validate :date_not_before_today
private
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:q, "Date is invalid")
end
end
def date_not_before_today
if Chronic.parse(q) < Date.today
errors.add(:q, "Date cannot be before today")
end
end
end
正如 ABMagil 的正确建议,我想 post 我的答案的完整解决方案。事实上,这个答案可以真正适用于任何想要使用 ActiveModel 使用验证的人,无论是否有 Chronic gem 或涉及日期。可以说它可以作为一个有效的模板。
坦率地说,我的大部分错误都来自当时对我实际尝试实现的目标的理解非常差。大多数代码需要进行重大重构,请参阅下面我必须进行的更新。我试图尽可能完整地记录代码。
解法:
views/main/index.html.erb
<%= form_for @search, url: { action: "search" },
html: { method: :get } do |f| %>
# Displays error messages if there are any.
<% if @search.errors.any? %>
The form contains <%= pluralize(@search.errors.count, "error") %>.<br />
<% @search.errors.each do |attr, msg| %>
<%= msg %><br />
<% end %>
<% end %>
<%= f.label :q, "Enter Date:" %>
<%= f.text_field :q %>
<%= f.submit "Explore", :class => 'submit' %>
<% end %>
views/main/search.html.erb - 同前
<%= @show_date %>
main_controller.rb
def index
# Initializes a string from the form to a variable.
@search = Search.new
end
def search
# Retrieves the input from the form.
@search = Search.new(params[:search])
# Checks for validity,
# If valid, converts a valid string into a date.
# Redirects to search.html.erb
# If not valid, renders a new index.html.erb template.
if @search.valid?
@show_date = (Chronic.parse(params[:search][:q])).to_date
else
render :action => 'index'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
# Accepts the passed attribute from the form.
attr_accessor :q
# If form is submitted blank, then output the prompting message.
validates_presence_of :q, :message => "This text field can't be blank!"
# Two custom validations that check if passed string converts to a valid date.
validate :date_is_valid
validate :date_not_before_today
# Initializes the attributes from the form.
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
# Checks for persistence, i.e. if it's a new record and it wasn't destroyed.
# Otherwise returns false.
def persisted?
false
end
# ABMagil's code used for custom date validations
private
require 'chronic'
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:base, "Date is invalid")
end
end
def date_not_before_today
if !Chronic.parse(q).nil?
if Chronic.parse(q) < Date.today
errors.add(:base, "Date cannot be before today")
end
end
end
end
结果:
我目前正在尝试使自定义验证与日期输入一起工作,但不幸的是,它似乎不起作用。
应用程序中有两个页面,索引页面和搜索页面。在索引页内有一个输入日期的文本字段。我正在使用 Chronic gem 将文本解析为日期。如果日期无效,Chronic returns nil。如果有效,它会重定向到搜索页面并显示日期。
到目前为止我写的代码似乎不能正常工作,但我想要实现的是..
1) 验证 Chronic 不会 return nil
2) 验证该日期是否大于今天的日期
请注意,我没有为此使用数据库,我只是希望能够在没有 ActiveRecord 的情况下验证输入的日期。如果有人能帮我解决这个问题,我将不胜感激。
views/main/index.html.erb
<%= form_tag({controller: "main", action: "search"}, method: "get") do %>
<%= label_tag(:q, "Enter Date:") %>
<%= text_field_tag(:q) %>
<%= submit_tag "Explore", name: nil %>
<% end %>
views/main/search.html.erb
<%= @show_date %>
main_controller.rb
def search
passed_info = Main.new
if passed_info.valid_date?
@show_date = passed_info
else
flash[:error] = "Please enter correct date!"
render :index => 'new'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :q
validates_presence_of :q
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
def valid_date?
require 'chronic'
if Chronic.parse(q).nil? || Chronic.parse(q) < Time.today
errors.add(:q, "is missing or invalid")
end
end
end
编辑:
这就是问题所在...
localhost:3000
然后重定向到..
localhost:3000/main/search?utf8=%E2%9C%93&q=invalid+date+test
没有验证,没有日期,什么都没有..
问题
对 return 值要更加小心。当您尝试使用 if valid_date?
保护您的控制器时,您正在做的是检查 valid_date?
return 是否为假。如果解析失败,return 值是 errors.add
的输出,而后者又是 Array#<< 的输出。相关地,输出不是 nil
或 false
,因此它的计算结果为真,因此 if
子句通过并且您继续前进。
可能的解决方案
您可能想让 Rails Validation Framework 为您做更多的工作。不要将 valid_date?
视为控制器调用的 public 方法,而是调用 ActiveModel::Validations
添加的 valid?
方法。 valid?
将 return 一个布尔值,基于是否所有模型验证都通过。因此,您可以像 Rails 方式一样,在您的控制器中调用 if model_instance.valid?
。
这让您只需在模型中编写验证器方法来表达您尝试编写的逻辑。现在,您在一个方法中拥有所有日期验证逻辑,并带有一条错误消息。相反,您可以放置两个方法,它们会添加更具描述性的单个错误方法。
class YourClass
include ActiveModel::Validations
validate :date_is_valid
validate :date_not_before_today
private
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:q, "Date is invalid")
end
end
def date_not_before_today
if Chronic.parse(q) < Date.today
errors.add(:q, "Date cannot be before today")
end
end
end
正如 ABMagil 的正确建议,我想 post 我的答案的完整解决方案。事实上,这个答案可以真正适用于任何想要使用 ActiveModel 使用验证的人,无论是否有 Chronic gem 或涉及日期。可以说它可以作为一个有效的模板。
坦率地说,我的大部分错误都来自当时对我实际尝试实现的目标的理解非常差。大多数代码需要进行重大重构,请参阅下面我必须进行的更新。我试图尽可能完整地记录代码。
解法:
views/main/index.html.erb
<%= form_for @search, url: { action: "search" },
html: { method: :get } do |f| %>
# Displays error messages if there are any.
<% if @search.errors.any? %>
The form contains <%= pluralize(@search.errors.count, "error") %>.<br />
<% @search.errors.each do |attr, msg| %>
<%= msg %><br />
<% end %>
<% end %>
<%= f.label :q, "Enter Date:" %>
<%= f.text_field :q %>
<%= f.submit "Explore", :class => 'submit' %>
<% end %>
views/main/search.html.erb - 同前
<%= @show_date %>
main_controller.rb
def index
# Initializes a string from the form to a variable.
@search = Search.new
end
def search
# Retrieves the input from the form.
@search = Search.new(params[:search])
# Checks for validity,
# If valid, converts a valid string into a date.
# Redirects to search.html.erb
# If not valid, renders a new index.html.erb template.
if @search.valid?
@show_date = (Chronic.parse(params[:search][:q])).to_date
else
render :action => 'index'
end
end
models/main.rb
class Main
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
# Accepts the passed attribute from the form.
attr_accessor :q
# If form is submitted blank, then output the prompting message.
validates_presence_of :q, :message => "This text field can't be blank!"
# Two custom validations that check if passed string converts to a valid date.
validate :date_is_valid
validate :date_not_before_today
# Initializes the attributes from the form.
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
# Checks for persistence, i.e. if it's a new record and it wasn't destroyed.
# Otherwise returns false.
def persisted?
false
end
# ABMagil's code used for custom date validations
private
require 'chronic'
def date_is_valid
if Chronic.parse(q).nil?
errors.add(:base, "Date is invalid")
end
end
def date_not_before_today
if !Chronic.parse(q).nil?
if Chronic.parse(q) < Date.today
errors.add(:base, "Date cannot be before today")
end
end
end
end