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.