是否有可能让Rails关联返回子类而不是超类?



首先,继承方法可能不正确。如果是,请解释不同的方法。

下面是一个设置的例子。

我有许多应用程序,除了使用完全相同的数据库,没有连接。为了使代码库干涸,我有一个包含所有活动记录类的引擎。我希望将特定于应用程序的作用域和方法保留在主要应用程序中。

在我的Rails引擎中

class MyEngine::User < ActiveRecord::Base
  has_many :dogs
end
class MyEngine::Dog < ActiveRecord::Base
  belongs_to :user
end

在我的主应用程序中

class User < MyEngine::User
end
class Dog < MyEngine::Dog
  # EDITED TO SHOW EXAMPLE OF HOW SCOPE DOESN'T BELONG IN ENGINE
  DOGS_WITH_SPOTS_IDS = [ 1, 2, 3, 4 ]
  scope :with_spots, -> { where(id: DOGS_WITH_SPOTS_IDS) }
end

我的主应用程序

user = User.last
user.dogs
# => #<ActiveRecord::Associations::CollectionProxy[#<MyEngine::Dog spots: true>]>
user.dogs.with_spots
NoMethodError: undefined method 'with_spots' for #<MyEngine::Dog::ActiveRecord_Association_CollectionProxy:0x007fa4bf838e50>

class User < MyEngine::User
  has_many :dogs
end

我不希望在主应用程序中重新定义/定义所有的关联。

在我所有的主要应用程序中,这类问题将以不同的形式不断出现。这让我想到,也许有一种方法可以重新定义这些关联。

是否有一种方法来评估子类而不是超类上的关联,就像STI模式让Rails帮助器识别不同的子类?

意思是,我希望我的主应用user# dogs返回主应用Dog而不是MyEngine::Dog.

# may not be the exact code, just off the top of my head
instance_eval do
  def model_name
    self.class.name
  end
end

重要提示不能查询user.dogs.with_spots,即使没有继承。你的意思可能是在user.dogs.to_a中得到Dog的数组。

解决方案解决这个问题的最一般的方法是使用单表继承。

让我们看看它是如何工作的。

我将假设已经有表my_engine_usersmy_engine_dogs定义了所有所需的列(就像MyEngine模块所关注的那样)。

MyEngine::UserMyEngine::Dog类保持不变。

UserDog只是扩展了各自的引擎类,不需要额外声明它们之间的关系。

我们告诉Rails,这两个表可能被不同的类(通过扩展)使用。这很容易通过向表中添加type列来实现。

add_column :my_engine_users, :type, :string

 add_column :my_engine_dogs, :type, :string

现在你可以和你的用户和狗一起做所有美妙的事情了。

dimakura = User.create(username: 'dimakura')
dora = Dog.create(name: 'Dora', owner: dimakura)
fanta = Dog.create(name: 'Fanta', owner: dimakura)
dimakura.dogs.to_a # => array of Dogs, not MyEngine::Dogs

这个魔术是通过显式地在type列中写入类名来实现的。

p dimakura #=> #<User id: 1, username: "dimakura", type: "User">
p dora #=> #<Dog id: 1, owner_id: 1, name: "Dora", type: "Dog">
p fanta #=> #<Dog id: 2, owner_id: 1, name: "Fanta", type: "Dog">

MyEngine::User.dogs返回[由关联实例代理]一组MyEngine::Dog实例,这些实例通常可能没有with_spots作用域。

如果您知道所有MyEngine::Dog都对with_spots"响应",只需将范围定义移动到MyEngine::Dog。如果不是这样,在MyEngine::User.dogs上调用这个作用域就没有意义了。

UPD添加的示例可以解决:

class MyEngine::Dog < ActiveRecord::Base
  belongs_to :user
  scope :with_spots, -> { self.class.const_get(:SCOPE_PROC) }
end
class Dog < MyEngine::Dog
  DOGS_WITH_SPOTS_IDS = [ 1, 2, 3, 4 ]
  SCOPE_PROC = -> { where(name: DOGS_WITH_SPOTS_IDS }
end

我只是想发表一个后续:

虽然@dimakura是正确的,但它仍然不适合我想做的事情。这是因为Rails不允许链接STI的作用域。最后,我删除了STI并使用了以下代码:

user = User.first
user.dogs.merge(Dog.with_spots)

最新更新