【问题标题】:How do I display a validation error properly if my date format is not correct in Rails?如果我的日期格式在 Rails 中不正确,如何正确显示验证错误?
【发布时间】:2017-05-11 01:51:01
【问题描述】:

我使用的是 Rails 4.2.7。如果用户没有以正确的格式输入他们的出生日期字段,我想抛出一个验证错误,所以我有

  def update
    @user = current_user
    begin
      @user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
    rescue ArgumentError => ex
    end
    if @user.update_attributes(user_params)

我有这个想法

      <%= f.text_field :dob, :value => (f.object.dob.strftime('%m/%d/%Y') if f.object.dob), :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
      <% if @user.errors[:dob] %><%= @user.errors[:dob] %><% end %>

但是,即使有人输入“01-01/1985”之类的日期,上述内容也不会向视图返回验证错误。我需要做什么才能正确返回验证错误?

编辑:根据给出的答案之一,我尝试了

@user = current_user
begin
  @user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
  puts "Setting error." 
  @user.errors.add(:dob, 'The birth date is not in the right format.')
end
if @user.update_attributes(user_params)
  last_page_visited = session[:last_page_visited]
  if !last_page_visited.nil?
    session.delete(:last_page_visited)
  else
    flash[:success] = "Profile updated"
  end
  redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
else
  render 'edit'
end

即使我可以看到调用的“救援”分支,我也没有被定向到我的“渲染'编辑'”块。

【问题讨论】:

    标签: ruby-on-rails validation ruby-on-rails-4 format date


    【解决方案1】:

    我会在模型中添加一个验证规则。喜欢:

    validates_format_of :my_date, with: /\A\d{2}\/\d{2}\/\d{4}\z/, message: 'Invalid format'
    

    【讨论】:

    • 同意在模型中添加验证规则。当 OP 似乎想要使用斜杠时,此特定规则使用连字符作为分隔符。如果这对 OP 的用例很重要,它还将允许月份和日期字段中的某些数字通常不被视为有效日期。
    • 如果有人输入“99/88/0010”,这会验证日期吗?如果是这样,那是不正确的,因为我列出的(“99/88/0010”)不是有效日期。
    【解决方案2】:

    触发异常不会向errors 列表添加任何内容。如果您只想稍微调整此代码,您应该可以在 rescue 块内调用 errors.add。类似@user.errors.add(:dob, 'some message here')

    请记住,这只会在使用此控制器方法时验证出生日期。如果您想在保存用户时验证出生日期,您需要显式地将验证添加到模型中。你可以自己写custom validation class or method,也有一些gem添加了日期验证。

    【讨论】:

    • 我尝试了 yoru 建议(请参阅我的编辑),但即使我确实看到在我的控制器中调用了“救援”分支,执行并没有下拉我返回原始视图的部分,而是转到“配置文件更新成功”,好像所有数据都输入正常。
    • 您编辑中显示的代码的问题是调用update_attributes 只会检查模型中明确定义的验证,而begin/rescue 块与那。如果您没有更新除dob 之外的任何其他字段,您可以检查@user.errors 中是否有任何内容,然后检查save 而不是使用update_attributes。我倾向于在模型中添加验证,然后使用update_attributes。不幸的是,在验证运行时,Rails 已经将提交的字符串解析为日期。
    • 我不明白您在此处给出的解决方案是什么。您能否编辑您的答案以包含我可以剪切和粘贴的代码?
    • 我在该评论中的第一个建议(检查@user.errors)基本上是@Doug 在stackoverflow.com/a/41369750/7151673 中建议的。对于模型验证(以及一般的此问题),重要的是要了解,如果您的 dob 字段是数据库中的日期字段,那么 ActiveRecord 将尝试自行解析日期字符串。在验证运行时,字符串已经被类型转换为日期(如果不可能,则设置为 nil),因此此时无法验证格式。
    • 我认为有一些方法可以解决这个问题,但我需要更多地研究它。如果没有其他人得到有效的东西,我稍后会编辑。
    【解决方案3】:

    调用update_attributes 会清除您在rescue 中设置的错误。您应该检查错误,如果没有,则继续,如下所示:

    @user = current_user
    begin
      @user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
    rescue ArgumentError => ex
      puts "Setting error." 
      @user.errors.add(:dob, 'The birth date is not in the right format.')
    end
    if !@user.errors.any? && @user.update_attributes(user_params)
      last_page_visited = session[:last_page_visited]
      if !last_page_visited.nil?
        session.delete(:last_page_visited)
      else
        flash[:success] = "Profile updated"
      end
      redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
    end
    
    render 'edit'
    

    既然你redirect_to ... and return,你可以关闭条件,如果你做到这一点,只需渲染编辑页面。

    您可能还想为您的用户模型添加一个简单的验证:

    validates :dob, presence: true
    

    如果由于其他不可预见的原因无法设置 dob,这将始终失败。

    要让用户输入的字符串在重新加载时填充该字段,您可以为用户模型添加一个访问器:dob_string

    attr_accessor :dob_string
    
    def dob_string
      dob.to_s
      @dob_string || dob.strftime('%m/%d/%Y')
    end
    
    def dob_string=(dob_s)
      @dob_string = dob_s
      date = Date.strptime(dob_s, '%m/%d/%Y')
      self.dob = date
    rescue ArgumentError
      puts "DOB format error"
      errors.add(:dob, 'The birth date is not in the correct format')
    end
    

    然后改变表格设置:dob_string

    <%= form_for @user do |f| %>
      <%= f.text_field :dob_string, :value => f.object.dob_string , :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
      <% if @user.errors[:dob] %><%= @user.errors[:dob] %><% end %>
      <%= f.submit %>
    <% end %>
    

    并更新控制器以设置 dob_string:

    def update
      @user = User.first
      begin
        #@user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
        @user.dob_string = user_params[:dob_string]
      end
      if ! @user.errors.any? && @user.update_attributes(user_params)
        redirect_to url_for(:controller => 'users', :action => 'show') and return
      end
      render 'edit'
    end
    
    def user_params
      params.require(:user).permit(:name, :dob_string)
    end
    

    【讨论】:

    • 谢谢。这确实将我引导到正确的页面,但显示日期的文本字段(我的问题中以“
    • @Dave -- 在rescue 块中添加@user.dob = params[:user][:dob] 可能会有所帮助。这会将值原样分配给@user 模型。 :)
    • 遗憾的是,“@user.dob = params[:user][:dob]”这行什么也没做。虽然“params[:user][:dob]”中有数据我可以在调试中看到,在将其分配给“@user.dob”后,查询“@user.dob”不会显示任何数据。我想知道这是否与我的基础 PostGres 列具有类型 date 的事实有关。
    • 我已经用一个技巧编辑了我的答案,以便在表单中重新填充字符串。我为 dob 的字符串版本添加了一个访问器,因为正如您所怀疑的那样,日期字段无法存储格式错误的日期字符串以返回给用户。
    • @Doug,感谢您提供深思熟虑的答案。我注意到一个问题。执行此行后,“if !@user.errors.any?&& @user.update_attributes(user_params)”、“@user.dob”设置为空,即使我成功将其设置为“ @user.dob_string = user_params[:dob_string]" 上面一行。
    【解决方案4】:

    尝试在模型中添加验证规则。

      validate :validate_date
    
      def validate_date
        begin
          self.dob = Date.parse(self.dob)
        rescue
          errors.add(:dob, 'Date does not exists. Please insert valid date')
        end
      end
    

    并在您的控制器中更新您的代码

    ...
    @user.update_attributes(user_params)
    if @user.save
    ....
    

    【讨论】:

    • 这有几个问题——其中一个问题是它会验证日期并不是真正的日期,比如“2/29/2017”。
    • 我已经更改了代码,请看一下。这可能会解决您的问题。
    • 酷。这确实正确验证了正确和不正确的日期,但是当我返回到我的视图时,文本字段(我的问题中的“f.text_field :dob”)不包含任何内容。理想情况下,它应该包含用户输入的错误值,以便用户更正它。
    • 你的控制器好像有逻辑错误,我已经更新了代码,看看。
    • @user.update_attributes 确实调用“保存”,因此执行“如果 @user.update_attributes”等同于您所拥有的,但即使尝试您所拥有的,无效的日期值也不会显示在文本中字段。
    【解决方案5】:

    我认为这是 Active Model 大放异彩的一个案例。我喜欢用它来实现没有额外依赖的表单对象。我不知道您的具体情况,但我在下面粘贴了一个小演示,您应该能够适应您的情况。

    最大的好处是您不会使用支持配置文件更新的方法污染您的控制器或模型。它们可以被提取到一个单独的模型中,从而简化事情。

    第 1 步:将 dob 存储在 users

    您的users 表应该有一个dob 类型为date 的列。例如:

    class CreateUsers < ActiveRecord::Migration
      def change
        create_table :users do |t|
          t.string :name, null: false
          t.date :dob, null: false
        end
      end
    end
    

    不要在模型中添加任何花哨的东西:

    class User < ActiveRecord::Base
    end
    

    第二步:添加Profile

    将以下内容放入app/models/profile.rb。解释见 cmets。:

    class Profile
      # This is an ActiveModel model.
      include ActiveModel::Model
    
      # Define accessors for fields you want to use in your HTML form.
      attr_accessor :dob_string
    
      # Use the validatiors API to define the validators you want.
      validates :dob_string, presence: true
      validate :dob_format
    
      # We store the format in a constant to keep the code DRY.
      DOB_FORMAT = '%m/%d/%Y'
    
      # We store the user this form pertains to and initialize the DOB string
      # to the one based on the DOB of the user.
      def initialize(user)
        # We *require* the user to be persisted to the database.
        fail unless user.persisted?
    
        @user = user
        @dob_string = user.dob.strftime(DOB_FORMAT)
      end
    
      # This method triggers validations and updates the user if validations are
      # good.
      def update(params)
        # First, update the model fields based on the params.
        @dob_string = params[:dob_string]
    
        # Second, trigger validations and quit if they fail.
        return nil if invalid?
    
        # Third, update the model if validations are good.
        @user.update!(dob: dob)
      end
    
      # #id and #persisted? are required to make form_for submit the form to
      # #update instead of #create.
      def id
        @user.id
      end
    
      def persisted?
        true
      end
    
      private
    
      # Parse dob_string and store the result in @dob.
      def dob
        @dob ||= Date.strptime(dob_string, DOB_FORMAT)
      end
    
      # This is our custom validator that calls the method above to parse dob_string
      # provided via the params to #update.
      def dob_format
        dob
      rescue ArgumentError
        errors[:dob] << "is not a valid date of the form mm/dd/yyyy"
      end
    end
    

    第 3 步:使用控制器中的表单

    ProfilesController 中使用Profile

    class ProfilesController < ApplicationController
      def edit
        # Ensure @profile is set.
        profile
      end
    
      def update
        # Update the profile with data sent via params[:profile].
        unless profile.update(params[:profile])
          # If the update isn't successful display the edit form again.
          render 'edit'
          return
        end
    
        # If the update is successful redirect anywhere you want (I chose the
        # profile form for demonstration purposes).
        redirect_to edit_profile_path(profile)
      end
    
      private
    
      def profile
        @profile ||= Profile.new(user)
      end
    
      def user
        @user ||= User.find(params[:id])
      end
    end
    

    第 4 步:使用form_for 呈现表单

    app/views/profiles/edit.html.erb 中使用form_for 显示表单:

    <%= form_for(@form) do |f| %>
      <%= f.label :dob_string, 'Date of birth:' %>
      <%= f.text_field :dob_string %>
      <%= f.submit 'Update' %>
    <% end %>
    

    第 5 步:添加路由

    记得添加路由到config/routes.rb:

    Rails.application.routes.draw do
      resources :profiles
    end
    

    就是这样!

    【讨论】:

      猜你喜欢
      • 2021-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多