简体   繁体   中英

Ruby on Rails: Handling invalid dates with multiparameter dates

I've added a form to my rails app which asks for a date. Rather than use the (IMO) clunky date_select helper, or a date popup solution, I'd like to use seperate input fields for date, month and year (as specified in the GDS service manual ). I've written a custom input for simple_form here:

class TextDateInput < SimpleForm::Inputs::Base
  def input(wrapper_options)
    input_html_options[:pattern] = '[0-9]*'
    @value = @builder.object.send(attribute_name)
    @merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
    "#{date_field} #{month_field} #{year_field}".html_safe
  end

  def date_field
    @builder.label(attribute_name, class: 'grouped-date date') do
      output = template.content_tag(:span, 'Date')
      output += @builder.text_field(attribute_name,
                                    @merged_input_options.merge(
                                      name: "#{@builder.object_name}[#{attribute_name}(3i)]",
                                      maxlength: 2,
                                      value: @value&.day
                                    ))
      output
    end
  end

  def month_field
    @builder.label(attribute_name, class: 'grouped-date month') do
      output = template.content_tag(:span, 'Month')
      output += @builder.text_field(attribute_name,
                                    @merged_input_options.merge(
                                      name: "#{@builder.object_name}[#{attribute_name}(2i)]",
                                      maxlength: 2,
                                      value: @value&.month
                                    ))
      output
    end
  end

  def year_field
    @builder.label(attribute_name, class: 'grouped-date year') do
      output = template.content_tag(:span, 'Year')
      output += @builder.text_field(attribute_name,
                                    @merged_input_options.merge(
                                      name: "#{@builder.object_name}[#{attribute_name}(1i)]",
                                      maxlength: 4,
                                      value: @value&.year
                                    ))
      output
    end
  end
end

And it works perfectly in the frontend, however, if the user enters an invalid date (for example 99/99/9999 ), Rails raises an ActiveRecord::MultiparameterAssignmentErrors error. Is there a clean way to handle this so rather than raising an error I can apply a validation error to the database object and show an invalid date error to the user?

您可以在before_filter回调中解析日期或引入params对象,该对象将用触发AR验证的日期替换无效日期。

I decided to have a stab at this myself and added the following to my base model class (I'm using Rails 5):

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def update_attributes(attrs = {})
    super parse_dates(attrs)
  end

  def parse_dates(attrs)
    # First fetch any nested attributes
    attrs.clone.each do |k, v|
      next unless v.is_a?(Hash)
      # If this is a hash, it's a nested attribute, so check for dates
      attrs = parse_nested_dates(k, attrs)
    end
    # Now marshal the rest of the dates
    marshal_dates(self.class, attrs)
  end

  def parse_nested_dates(key, attrs)
    klass = Object.const_get key.split('_attributes').first.classify
    attrs[key] = marshal_dates(klass, attrs[key])
    attrs
  end

  def marshal_dates(klass, attrs)
    # Get all the columns in the class that have a date type
    date_columns = klass.columns_hash.select { |_k, value| value.type == :date }.keys
    date_columns.each { |c| attrs = parse_date(attrs, c) }
    attrs
  end

  def parse_date(attrs, property)
    # Gather up all the date parts
    keys = attrs.keys.select { |k| k =~ /#{property}/ }.sort
    return attrs if keys.empty?
    values = []
    keys.each do |k|
      values << attrs[k]
      attrs.delete(k)
    end
    # Set the date as a standard ISO8601 date
    attrs[property] = values.join('-')
    attrs
  end
end

This seems to work perfectly for both standard attributes and nested attributes, and means it automatically works for all date columns without me having to do anything.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM