我有一个过滤器类,其中包括ActiveModel,由两个日期组成:
class MealFilter
include ActiveModel::Model
attribute :day_from, Date
attribute :day_to, Date
end
该模型呈现为如下所示的形式:
<%= form_for(@filter) do |f| %>
<div class="form-group form-group--date">
<%= f.label :day_from %>
<%= f.date_select :day_from %>
</div>
<div class="form-group form-group--date">
<%= f.label :day_to %>
<%= f.date_select :day_to %>
</div>
<% end %>
现在的问题是,当表单提交时,它会将此参数发送到控制器:
{"utf8"=>"✓", "meal_filter"=>{"day_from(1i)"=>"2016", "day_from(2i)"=>"1", "day_from(3i)"=>"29", "day_to(1i)"=>"2016", "day_to(2i)"=>"1", "day_to(3i)"=>"30"}, "commit"=>"Filter"}
我通过控制器参数提取值:
def meal_filter_params
if params.has_key? :meal_filter
params.require(:meal_filter).permit(:day_from, :day_to)
end
end
如果我现在使用 @filter = MealFilter.new(meal_filter_params)
将params[:meal_filter]
分配给我的MealFilter
类,我的日期字段没有正确更新。接缝是 1i、2i、3i 部分未正确分配给日期。
但是,如果使用ActiveRecord
类,则此方法可以正常工作。
我错过了一些包括吗?有谁知道,如果不是在ActiveModel::Model
,这个魔术映射在哪里实现?
应用程序从 Rails 3.2 升级到 5.0 时遇到了这个问题
我是如何整理的,如下:
在导轨 5+ 中
class MealFilter
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveRecord::AttributeAssignment
attribute :day_from, :date
end
或者
class MealFilter
include ActiveRecord::AttributeAssignment
attr_reader :day_from
def initialize(attributes = {})
self.attributes = attributes
end
def day_from=(value)
@day_from = ActiveRecord::Type::Date.new.cast(value)
end
end
在 Rails 4.2 中
class MealFilter
include ActiveRecord::AttributeAssignment
attr_accessor :day_from
def initialize(attributes = {})
self.attributes = attributes
end
def type_for_attribute(name)
case name
when "day_from" then ActiveRecord::Type::Date.new
end
end
end
然后你可以做:
attributes = { "day_from(3i)" => "1", "day_from(2i)" => "9", "day_from(1i)" => "2020" }
meal_filter = MealFilter.new(attributes)
meal_filter.day_from
# => Tue, 01 Sep 2020
好的,找到了一个解决方案。我需要的是多参数分配,它实际上是在ActiveRecord中实现的,而不是在ActiveModel中实现的。
据我所知,有一个开放的拉取请求(https://github.com/rails/rails/pull/8189)应该可以解决此问题。
但与此同时,一些聪明的家伙编写了一个可以包含在模型中的模块:https://gist.github.com/mhuggins/6c3d343fd800cf88f28e
您需要做的就是包含关注点并定义一个 class_for_attribute
方法,该方法返回属性应映射到的类 - 在我的情况下Date
.
您只需从date_select帮助程序访问 1i、2i 和 3i 参数,并将它们组合在一起以在before_validation回调中创建新的 Date:
class DateOfBirth
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Validations::Callbacks
attribute :date_of_birth, :date
attribute "date_of_birth(3i)", :string
attribute "date_of_birth(2i)", :string
attribute "date_of_birth(1i)", :string
before_validation :make_a_date
validates :date_of_birth, presence: { message: "You need to enter a valid date of birth" }
def make_a_date
year = self.send("date_of_birth(1i)").to_i
month = self.send("date_of_birth(2i)").to_i
day = self.send("date_of_birth(3i)").to_i
begin # catch invalid dates, e.g. 31 Feb
self.date_of_birth = Date.new(year, month, day)
rescue ArgumentError
return
end
end
end