ActiveRecord使用回调和STI的问题

时间:2010-12-23 12:57:13

标签: ruby-on-rails ruby activerecord ruby-on-rails-3 single-table-inheritance

嘿伙计们,关注Rails和STI的问题:

我有以下课程:

class Account < AC::Base
  has_many :users
end

class User < AC::Base
  extend STI
  belongs_to :account

  class Standard < User
    before_save :some_callback
  end

  class Other < User
  end
end

module STI
  def new(*args, &block)
    type = args.dup.extract_options!.with_indifferent_access.delete(:type)
    if type.blank? or (type = type.constantize) == self
      super(*args, &block)
    else
      type.new(*args, &block)
    end
  end
end

现在问题是: 如果不重写User.new(在模块STI中),User::Standard内的回调永远不会被调用,否则如果我以这种方式创建用户,则account_id始终为nil

account.users.create([{ :type => 'User::Standard', :firstname => ... }, { :type => 'User::Other', :firstname => ... }])

如果我对模块使用不同的方法,如:

module STI
  def new(*args, &block)
    type = args.dup.extract_options!.with_indifferent_access.delete(:type)
    if type.blank? or (type = type.constantize) == self
      super(*args, &block)
    else
      super(*args, &block).becomes(type)
    end
  end
end

然后不共享实例变量,因为它正在创建一个新对象。 如果没有将回调移动到父类并检查类的类型,是否有解决此问题的方法?

格尔茨 马里奥

2 个答案:

答案 0 :(得分:0)

也许有些事情我不知道,但我从未见过以这种方式定义的Rails STI类。通常它看起来像......

应用程序/模型/ user.rb:

class User < AC::Base
  belongs_to :account
end

应用程序/模型/用户/ standard.rb:

module Users
  class Standard < User
    before_save :some_callback
  end
end

应用程序/模型/用户/ other.rb:

module Users
  class Other < User
  end
end

看起来好像是在将类范围(类“生活”与其他类,模块,方法等相关)与类继承(由“class Standard&lt; User”表示)混淆在一起。 Rails STI关系涉及继承但不关心范围。也许你试图通过嵌套继承的类来完成一些非常具体的东西,我只是想念它。但如果没有,它可能会导致你的一些问题。

现在特别转向回调。标准中的回调没有被调用,因为“account.users”关系使用的是User类,而不是Standard类(但我想你已经知道了)。有几种方法可以解决这个问题(我将在示例中使用我的类结构):

一:

class Account
  has_many :users, :class_name => Users::Standard.name
end

这将强制所有account.users使用Standard类。如果您需要其他用户的可能性,那么......

二:

class Account
    has_many :users # Use this to look up any user
    has_many :standard_users, :class_name => Users::Standard.name # Use this to look up/create only Standards
    has_many :other_users, :class_name => Users::Other.name # Use this to look up/create only Others
end

三:

只需在代码中手动调用Users :: Standard.create()和Users :: Other.create()。

我确信还有很多其他方法可以实现这一目标,但可能最简单。

答案 1 :(得分:0)

所以我在将实例变量移动到@attributes并将第二种方法用于模块STI后解决了我的问题:

module STI
  def new(*args, &block)
    type = args.dup.extract_options!.with_indifferent_access.delete(:type)
    if type.blank? or (type = type.constantize) == self
      super(*args, &block)
    else
      super(*args, &block).becomes(type)
    end
  end
end

class User < AR:Base
  extend STI

  belongs_to :account

  validates :password, :presence => true, :length => 8..40
  validates :password_digest, :presence => true

  def password=(password)
    @attributes['password'] = password
    self.password_digest = BCrypt::Password.create(password)
  end

  def password
    @attributes['password']
  end

  class Standard < User
    after_save :some_callback
  end
end

现在我的实例变量(密码)被复制到新的User::Standard对象,并且回调和验证正在运行。太好了!但这是一种解决方法,而不是真正的解决方案。 ;)