ActiveRecord 使用回调和 STI 的问题

发布于 2024-10-09 13:57:53 字数 1258 浏览 10 评论 0原文

嘿伙计们,以下是 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

那么实例变量不会共享,因为它正在创建一个新对象。 在不将回调移至父类并检查类的类型的情况下,是否有任何解决方案?

格瑞兹 马里奥

Hey folks, following problem with Rails and STI:

I have following classes:

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

And now the problem:
Without rewriting User.new (in module STI), the callback inside User::Standard gets never called, otherwise the account_id is always nil if I create users this way:

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

If I'm using a different approach for the module like:

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

Then instance variables are not shared, because it's creating a new object.
Is there any solution for this problem without moving the callbacks to the parent class and checking the type of class?

Greetz
Mario

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

甩你一脸翔 2024-10-16 13:57:53

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

app/models/user.rb:

class User < AC::Base
  belongs_to :account
end

app/models/users/standard.rb:

module Users
  class Standard < User
    before_save :some_callback
  end
end

app/models/users/other.rb:

module Users
  class Other < User
  end
end

看起来好像你正在合并类范围(类“所在的位置”) “相对于其他类、模块、方法等)具有类继承(由“类标准<用户”表示)。 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() 即可。

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

Maybe there's something I don't know, but I've never seen Rails STI classes defined in that manner. Normally it looks like...

app/models/user.rb:

class User < AC::Base
  belongs_to :account
end

app/models/users/standard.rb:

module Users
  class Standard < User
    before_save :some_callback
  end
end

app/models/users/other.rb:

module Users
  class Other < User
  end
end

It looks as though you are conflating class scope (where a class "lives" in relation to other classes, modules, methods, etc.) with class inheritance (denoted by "class Standard < User"). Rails STI relationships involve inheritance but do not care about scope. Perhaps you are trying to accomplish something very specific by nesting inherited classes and I am just missing it. But if not, it's possible it's causing some of your issues.

Now moving on to the callbacks specifically. The callback in Standard isn't getting called because the "account.users" relationship is using the User class, not the Standard class (but I think you already know that). There are several ways to deal with this (I will be using my class structure in the examples):

One:

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

This will force all account.users to use the Standard class. If you need the possibility of Other users, then...

Two:

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

Three:

Just call Users::Standard.create() and Users::Other.create() manually in your code.

I'm sure there are lots of other ways to accomplish this, but there are probably the simplest.

不疑不惑不回忆 2024-10-16 13:57:53

因此,在将实例变量移动到 @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 对象以及回调和验证正在运行。好的!但这只是一种解决方法,而不是真正的解决方案。 ;)

So I solved my problems after moving my instance variables to @attributes and using my second approach for the module 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

Now my instance variable (the password) is copied to the new User::Standard object and callbacks and validations are working. Nice! But it's a workaround, not really a fix. ;)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文