我设置了以下型号
# task.rb
class Task << AR
# everything all task objects have in common
end
# login_request.rb
class Tasks::LoginRequest < Task
store :data, accessors: [:email, :first_name, :last_name, :expires_at]
composed_of :valid_until, class_name: 'DateTime', mapping: %w(expires_at to_s), constructor: Proc.new { |date| (date && date.to_datetime) || DateTime.now }, converter: Proc.new { |value| value.to_s.to_datetime }
end
我在我的表单中使用datetime_select
帮助器:
# _form.html.haml
= f.datetime_select :valid_until
这很有效,但是当我使用提交的表单数据在控制器中调用update
时,我收到以下错误消息:
1 error(s) on assignment of multiparameter attributes [error on assignment [2014, 4, 2, 9, 48] to valid_until (can't write unknown attribute 'expires_at')]
所以,我猜测更新的方法试图直接操作attributes
哈希,但显然它找不到属性expires_at
,因为它是JSON列的一个简单的访问器方法{ {1}}。
我知道我可以简单地将此字段添加到数据库中,它可能会起作用 - 尽管不需要有data
语句。但是我不想走这条路,因为不是每个任务都有composed_of
列。
如何克服此错误?或者我错过了什么?
答案 0 :(得分:2)
目前compose_of不支持此方案,因为它直接写入假定在数据库中的属性。我写了一个调整后的compose_of版本(基于Rails 4.0.2版本)
将它放在初始化文件夹中:
#/initialize/support_store_in_composed_of.rb
module ActiveRecord
module Aggregations
extend ActiveSupport::Concern
def clear_aggregation_cache #:nodoc:
@aggregation_cache.clear if persisted?
end
module ClassMethods
def composed_of_with_store_support(part_id, options = {})
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter, :store)
name = part_id.id2name
class_name = options[:class_name] || name.camelize
mapping = options[:mapping] || [ name, name ]
mapping = [ mapping ] unless mapping.first.is_a?(Array)
allow_nil = options[:allow_nil] || false
constructor = options[:constructor] || :new
converter = options[:converter]
reader_method(name, class_name, mapping, allow_nil, constructor, options[:store])
writer_method(name, class_name, mapping, allow_nil, converter, options[:store])
create_reflection(:composed_of, part_id, nil, options, self)
end
private
def reader_method(name, class_name, mapping, allow_nil, constructor, store=nil)
define_method(name) do
if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
if store.present?
attrs = mapping.collect {|pair| send(pair.first)}
else
attrs = mapping.collect {|pair| read_attribute(pair.first)}
end
object = constructor.respond_to?(:call) ?
constructor.call(*attrs) :
class_name.constantize.send(constructor, *attrs)
@aggregation_cache[name] = object
end
@aggregation_cache[name]
end
end
def writer_method(name, class_name, mapping, allow_nil, converter, store=nil)
define_method("#{name}=") do |part|
klass = class_name.constantize
unless part.is_a?(klass) || converter.nil? || part.nil?
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
end
if part.nil? && allow_nil
mapping.each { |pair| self[pair.first] = nil }
@aggregation_cache[name] = nil
else
if store.present?
mapping.each { |pair| send("#{pair.first}=", part.send(pair.last)) }
else
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
end
@aggregation_cache[name] = part.freeze
end
end
end
end
end
end
像这样使用它可以解决你的问题。
class Task < ActiveRecord::Base
store :data, accessors: [:email, :first_name, :last_name, :expires_at]
composed_of_with_store_support :valid_until, class_name: 'DateTime', mapping: %w(expires_at to_s),
constructor: Proc.new { |date| (date && date.to_datetime) || DateTime.now },
converter: Proc.new { |value| value.to_s.to_datetime },
store: true
end