【问题标题】:Rails date_select helper and validationRails date_select 帮助器和验证
【发布时间】:2011-03-18 17:53:45
【问题描述】:

我的 Rails 应用程序中有一个模型支持表单中的日期字段:

<%= f.date_select :birthday,
  {:start_year => Time.now.year,
  :end_year => 1900,
  :use_short_month => true,
  :order => [:month, :day, :year],
  :prompt => {:month => 'Month', :day => 'Day', :year => 'Year'}},
  {:class => 'year',
  :id => 'user_birthday'}
%>

正在模型代码中使用以下方法对其进行验证:

validates_presence_of :birthday, :message => 'is a required field'

不幸的是,如果用户输入了部分值(例如年份),表单仍会提交而不会出错。相反,一个时髦的日期值被写入数据库。如何将所有三个字段都设为必填?

我想为此编写一个自定义验证,但我不知道如何正确访问生日元素的各个部分。我该怎么做?

谢谢! 萌

【问题讨论】:

  • 我注意到你没有:include_blank =&gt; true。甚至可以留一个空白吗?如果您将年份设置为 2000 年,您会得到什么日期?此外,:start_year 应该早于 :end_year。
  • 嗨 mckeed,:prompt =&gt; {:month =&gt; 'Month', :day =&gt; 'Day', :year =&gt; 'Year'} 为月、日和年插入空白默认值。 :end_year 小于 :start_year 因为我想要按顺序排列的值。

标签: ruby-on-rails activerecord


【解决方案1】:

在遵循 Benoitr 的建议后,我想出了使用虚拟属性的类似方法。在视图方面,“fields_for”中有 3 个单独的选择(年、月、日)。数据被提交给控制器的质量分配(控制器中没有修改,请参阅asciicasts #16),然后传递给模型中的 getter/setter(即虚拟属性)。我使用 Rails 3.0.3 和 simpleForm 作为视图代码。

在视图中:

<%= f.label "Design Date", :class=>"float_left" %>
<%= f.input :design_month, :label => false, :collection => 1..12 %>
<%= f.input :design_day,   :label => false, :collection => 1..31 %>
<%= f.input :design_year,  :label => false, :collection => 1900..2020 %>

在模型中:

validate :design_date_validator 

def design_year
  design_date.year
end
def design_month
  design_date.month
end
def design_day
  design_date.day
end
def design_year=(year)
  if year.to_s.blank?
    @design_date_errors = true  
  else      
    self.design_date = Date.new(year.to_i,design_date.month,design_date.day)
  end
end  
def design_month=(month)
  if month.to_s.blank?
     @design_date_errors = true  
   else      
   self.design_date = Date.new(design_date.year,month.to_i,design_date.day)
  end
end  
def design_day=(day)
  if day.to_s.blank?
    @design_date_errors = true  
  else      
   self.design_date = Date.new(design_date.year,design_date.month,day.to_i)
  end
end  
#validator
def design_date_validator
  if @design_date_errors
    errors.add(:base, "Design Date Is invalid")
  end
end  

'design_date_attr'是设置数据库中design_date值的虚拟属性。 getter 传回一个类似于在表单中提交的哈希。设置器检查空白并创建一个新的日期对象并设置它并设置错误变量。自定义验证器 :design_date_validator 检查错误实例变量并设置错误变量。我使用 ':base' 是因为变量名不是人类可读的,并且使用 base 会从错误字符串中删除该值。

需要重构的一些东西可能是错误检查实例变量,但它似乎至少可以工作。如果有人知道更新 Date 对象的更好方法,我很想听听。

【讨论】:

    【解决方案2】:

    post 相关,这是我找到的最佳解决方案。 但是我应该添加 :day, :month, :year 作为 attr_accessible,我不明白为什么..(因为验证?请告诉我..)

    用户.rb

    MONTHS = ["January", 1], ["February", 2], ...
    DAYS = ["01", 1], ["02", 2], ["03", 3], ...
    START_YEAR = Time.now.year - 100
    END_YEAR = Time.now.year
    YEAR_RANGE = START_YEAR..END_YEAR
    
    attr_accessible :day, :month, :year
    attr_accessor :day, :month, :year
    
    before_save :prepare_birthday
    
    validate :validate_birthday
    
    private
    
    def prepare_birthday
      begin
        unless year.blank? # in order to avoid Year like 0000
          self.birthday = Date.new(self.year.to_i, self.month.to_i, self.day.to_i)
        end
          rescue ArgumentError
        false
      end
    end
    
    def validate_birthday
      errors.add(:birthday, "Birthday is invalid") unless prepare_birthday
    end
    

    用户注册表

    <%= f.select :month, options_for_select(User::MONTHS), :include_blank => "Month" %>
    <%= f.select :day, options_for_select(User::DAYS), :include_blank => "Day" %>
    <%= f.select :year, options_for_select(User::YEAR_RANGE), :include_blank =>"Year" %>
    

    【讨论】:

    • 将其添加到attr_accessible 的原因是允许对属性进行批量分配。否则在执行User.new(params[:user])@user.update_attributes(params[:user]) 时它们将被剥离。但是,我认为这不是必需的,除非您已经指定它(也就是说,当根本没有指定它时,散列中的所有属性都将发送到模型,但可能不适用于虚拟属性)
    【解决方案3】:

    我认为您必须在控制器本身中创建验证。

    日期部分正在传递给birthday(1i)birthday(2i)birthday(3i)。这里的问题是它们在传递属性时立即分配,因此在任何验证发生之前。

    您也可以覆盖attributes= 方法以在那里创建您自己的验证,但我不建议您这样做。

    请记住,如果您进行验证,最好也针对任何不正确的日期进行验证。 (例如 2 月 31 日,当通过时将产生 3 月 2 日而不是错误)。

    我认为这里的主要问题是 ActiveRecord 实际上是在创建 Date 之前用 1 替换空值,这也意味着如果访问者只过了一年,日期将在 1 日创建那年一月。我想这是一种预期的行为,允许只使用年/月/日选择之一并仍然创建一个有用的日期。

    【讨论】:

    • 感谢您的信息,实际上这是错误仪式吗?如何解决这个问题?
    【解决方案4】:

    您可以覆盖validate_on_create 方法,如下所示:

    class MyModel < ActiveRecord::Base
      def validate_on_create
        Date.parse(birthday)
      rescue
        errors.add_to_base("Wrong date format")
      end
    end
    

    【讨论】:

    • Date.parse 在这里不起作用,因为日期值不是无效的。这很时髦。如果您将年份留空,您会得到类似“0007-01-01”的信息。
    • 你是对的。而不是解析,你应该使用民用。像这样: Date.civil(birthday[:year].to_i,birthday[:month].to_i,birthday[day].to_i) 如果任何字段为空,则应该抛出 ArgumentError。
    • 不幸的是,这也不起作用。即使我正确输入了所有字段,它也会引发错误。我试图在模型类中编写自定义验证方法,但我无法使用birthday[:month] 甚至附加.to_i 访问字段值。关于如何做到这一点的任何想法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-29
    • 2010-12-14
    • 2023-03-22
    • 2011-08-21
    • 2011-02-23
    • 1970-01-01
    相关资源
    最近更新 更多