Ruby 于 Rails:使用多参数日期处理无效日期
Ruby on Rails: Handling invalid dates with multiparameter dates
我已经在我的 rails 应用程序中添加了一个表格,要求约会。我不想使用 (IMO) 笨重的 date_select
帮助程序或日期弹出解决方案,而是使用单独的日期、月份和年份输入字段(如 GDS service manual 中指定)。我在这里为 simple_form 编写了自定义输入:
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
它在前端完美运行,但是,如果用户输入无效日期(例如 99/99/9999
),Rails 会引发 ActiveRecord::MultiparameterAssignmentErrors
错误。有没有一种干净的方法来处理这个问题,而不是引发错误我可以将验证错误应用于数据库对象并向用户显示无效日期错误?
您可以在before_filter
回调中解析日期或引入params对象,将无效日期替换为可触发AR验证的日期。
我决定自己尝试一下,并将以下内容添加到我的基本模型中 class(我使用的是 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
这似乎适用于标准属性和嵌套属性,这意味着它自动适用于所有日期列,而无需我执行任何操作。
我已经在我的 rails 应用程序中添加了一个表格,要求约会。我不想使用 (IMO) 笨重的 date_select
帮助程序或日期弹出解决方案,而是使用单独的日期、月份和年份输入字段(如 GDS service manual 中指定)。我在这里为 simple_form 编写了自定义输入:
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
它在前端完美运行,但是,如果用户输入无效日期(例如 99/99/9999
),Rails 会引发 ActiveRecord::MultiparameterAssignmentErrors
错误。有没有一种干净的方法来处理这个问题,而不是引发错误我可以将验证错误应用于数据库对象并向用户显示无效日期错误?
您可以在before_filter
回调中解析日期或引入params对象,将无效日期替换为可触发AR验证的日期。
我决定自己尝试一下,并将以下内容添加到我的基本模型中 class(我使用的是 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
这似乎适用于标准属性和嵌套属性,这意味着它自动适用于所有日期列,而无需我执行任何操作。