1

多くの試行錯誤と既存の回答の検索の後、私が抱えている根本的な誤解があるようで、明確化および/または方向性が大好きです。

事前に注意してください:私は複数のテーブル継承を使用しており、そうするのには十分な理由があるので、STIに戻す必要はありません:)

私は基本モデルを持っています:

class Animal < ActiveRecord::Base
  def initialize(*args)
    if self.class == Animal
      raise "Animal cannot be instantiated directly"
    end
    super
  end
end

そしてサブクラス:

class Bunny < Animal
  has_one(:bunny_attr)

  def initialize(*args)
    attrs = args[0].extract!(:ear_length, :hop_style)

    super

    self.bunny_attr = BunnyAttr.create!

    bunny_attrs_accessors 

    attrs.each do |key, value|
      self.send("#{key}=", value)
    end

  def bunny_attrs_accessors
    attrs = [:ear_length, :hop_style]

    attrs.each do |att|
      define_singleton_method att do
        bunny_attr.send(att)
      end

      define_singleton_method "#{att}=" do |val|
        bunny_attr.send("#{att}=", val)
        bunny_attr.save!
      end
    end
  end
end

関連する一連のデータ

class BunnyAttr < ActiveRecord::Base
  belongs_to :bunny
end

次に、次のようなことをすると:

bunny = Bunny.create!(name: "Foofoo", color: white, ear_length: 10, hop_style: "normal")
bunny.ear_length
Bunny.first.ear_length

bunny.ear_length は「10」を返し、Bunny.first.ear_length は「#<Bunny:0x0..> に対して未定義のメソッド 'ear_length'」を返します。

それはなぜですか?また、2 番目の呼び出しで値を返すにはどうすればよいですか?

4

2 に答える 2

1

現在初期化中のコードをafter_initializeコールバックに移動してみてください。

after_initialize do
  # the code above...
end

ActiveRecord がデータベースからロードするとき、実際には initialize を呼び出しません。を呼び出すとBunny.first、ActiveRecord は最終的に次のメソッドを呼び出します。

def find_by_sql(sql, binds = [])
  result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
  column_types = {}

  if result_set.respond_to? :column_types
    column_types = result_set.column_types
  else
    ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
  end

  result_set.map { |record| instantiate(record, column_types) }
end

インスタンス化メソッドは次のようになります。

 def instantiate(record, column_types = {})
    klass = discriminate_class_for_record(record)
    column_types = klass.decorate_columns(column_types.dup)
    klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
  end

そしてinit_with...

def init_with(coder)
  @attributes   = self.class.initialize_attributes(coder['attributes'])
  @column_types_override = coder['column_types']
  @column_types = self.class.column_types

  init_internals

  @new_record = false

  run_callbacks :find
  run_callbacks :initialize

  self
end

init_internals@readonly、 などの内部変数を設定するだけな@new_recordので#initialize、データベースからレコードをロードするときに実際に呼び出されることはありません。run_callbacks :initializeまた、データベースからロードするときに実行されることに も気付くでしょう。

上記のコードは Rails 4.1.1 から抽出されたものですが、初期化プロセスの多くは、Rails の他の最近のバージョンでも同じであることに注意してください。

編集:私はこれについてもう少し考えていました.セッターメソッドを定義するコードを削除し、メソッドをに委譲する場合はそれらを呼び出すことができますBunnyAttr.

class Bunny < Animal
  has_one :bunny_attr
  delegate :ear_length, :hop_style, to: :bunny_attr, prefix: false, allow_nil: false
end

ear_lengthこれにより、およびのゲッターとセッターが自動的に作成され、hop_styleそれらのダーティ ステータスも追跡されるためbunny_attr、 save on を呼び出したときに保存できますbunny。false に設定allow_nilすると、 ActiveRecord がbunny_attrisの場合にエラーをスローしますnil

于 2014-07-01T02:08:25.447 に答える