1

私は次のモデルをセットアップしています

# 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 column の単純なアクセサ メソッドであるため、明らかに attribute を見つけることができませんdata

composed_ofこのフィールドをDBに追加するだけで、おそらく機能することはわかっていますが、ステートメントを作成する必要はありません。expires_atただし、すべてのタスクに列があるわけではないため、このルートには進みません。

どうすればこのエラーを克服できますか? それとも私は何かを逃しましたか?

4

1 に答える 1

3

現在、compose_of は、データベースにあると想定される属性に直接書き込むため、このシナリオをサポートしていません。私は(Rails 4.0.2バージョンに基づいて)行う微調整されたcompose_ofバージョンを書きました

これを初期化フォルダーに入れる:

#/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
于 2014-04-12T15:06:40.383 に答える