setter方法无法看到父亲在after_initialize回调中构建的子对象

时间:2014-05-14 13:01:28

标签: ruby-on-rails associations

电子邮件有多个变种(用于ab测试目的)并且始终有一个

我想确保电子邮件始终具有基于初始化的主变体。

我还想委托attr访问者' subject'和'身体'到主变体。

我最初尝试使用

delegate :subject, :body, to: :master

但是rails抱怨主人没事。

所以我尝试手动滚动我自己的subject = setter方法并通过pry我发现虽然我的主人被设置在after_initialize回调中,但后来对subject =抱怨主人的调用是零。我不明白为什么。

  class Email < ActiveRecord::Base

    has_one :master, 
            -> { where is_master: true },
            class_name: 'Tinycourse::Variant',
            dependent: :destroy,
            inverse_of: :email


    def subject=(str)
      master.subject = str # Rails says master is nil here
    end

    #
    # Callbacks
    #-----------------------------------------

    after_initialize :ensure_master
    def ensure_master
      return unless new_record? 
      self.master ||= build_master
    end
  end


Email.new(:subject => 'yah') # undefined method `subject=' for nil:NilClass

1 个答案:

答案 0 :(得分:1)

当您的电子邮件实例初始化时,master为零,您需要在设置任何内容之前触发build_master

怎么样:

# app/models/email.rb
class Email < ActiveRecord::Base

  def subject=(str)
    master.subject = str # Rails says master is nil here
  end

  def master
    super || build_master
  end
end

不知道您的项目需要after_initialize回调的时间和原因,但如果您确定需要此功能,我会考虑使用自定义服务类来实现此目的

# app/models/email.rb
class Email < ActiveRecord::Base

  has_one :master, 
          -> { where is_master: true },
          class_name: 'Tinycourse::Variant',
          dependent: :destroy,
          inverse_of: :email

  def subject=(str)
    master.subject = str # this way Rails won't says master is nil here
  end
end

# app/lib/email_builder.rb
class EmailBuilder
  attr_reader :args

  def self.build(args={})
    new(args).build
  end

  def initialize(args)
    @args = args
  end

  def build
    email = Email.new  
    email.build_master
    email.attributes = args
    email
  end   
end

email = EmailBuilder.build subject: 'yah'
email.class # => Email

...以及持久记录的另一种变体

更新

甚至更好

# app/models/email.rb
class Email < ActiveRecord::Base

  has_one :master, 
          -> { where is_master: true },
          class_name: 'Tinycourse::Variant',
          dependent: :destroy,
          inverse_of: :email

end

# app/lib/email_builder.rb
class EmailBuilder
  attr_reader :args, :subject

  def self.build(args={})
    new(args).build
  end

  def initialize(args)
    @subject = args.fetch(:subject)
    @args = args
  end

  def build
    email = Email.new args
    email.build_master
    email.master.subject = subject 
    email
  end   
end

email = EmailBuilder.build subject: 'yah'
email.class # => Email