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

时间:2015-08-31 15:45:52

标签: ruby ruby-on-rails-4 activerecord associations metaprogramming

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

以下是设置示例。

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

在我的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

3 个答案:

答案 0 :(得分:1)

重要提示即使没有继承,也无法查询user.dogs.with_spots。你可能想要在Dog中获得user.dogs.to_a数组。

解决方案解决问题的最常用方法是使用Single Table Inheritance

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

我将假设已经为所有必需列定义了表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">

答案 1 :(得分:0)

MyEngine::User.dogs返回[由关联实例代理]一组MyEngine::Dog个实例,通常可能没有with_spots范围。

如果您知道所有MyEngine::Dog s“回复”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

答案 2 :(得分:0)

我只是想发布一个跟进:

虽然@dimakura是正确的,但它仍然无法正常工作。这是因为Rails不允许为STI链接范围。最后,我删除了STI并使用了以下内容:

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