是否有可能获得 Rails 与 return 子类而不是超类的关联?
Is it possible to get Rails associations to return subclasses instead of the superclass?
首先,继承方式可能不正确。如果是这样,请解释不同的方法。
这是一个设置示例。
除了使用完全相同的数据库外,我有许多未连接的应用程序。为了耗尽代码库,我有一个包含所有活动记录的引擎 类。我想在主要应用程序中保留特定于应用程序的范围和方法。
在我的 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
我不想 redefine/define 主要应用程序中的所有关联。
这种类型的问题将以不同的形式在我所有的主要应用程序中不断出现。这让我想到可能有一种方法可以重新定义关联。
有没有一种方法可以评估子类而不是超类的关联,就像 STI 模式一样让 Rails 助手识别不同的子类类?
意思是,我希望我的主应用 User#dogs 成为 return 主应用 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
MyEngine::User.dogs
returns [由关联实例代理] 一组 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
重要说明即使没有继承也不能查询user.dogs.with_spots
。您的意思可能是在 user.dogs.to_a
.
中获取 Dog
的数组
解决方案 解决您问题的最通用方法是使用Single Table Inheritance.
让我们看看它是如何工作的。
我假设已经有表 my_engine_users
和 my_engine_dogs
定义了所有必需的列(就 MyEngine
模块而言)。
类 MyEngine::User
和 MyEngine::Dog
保持不变。
类 User
和 Dog
只是扩展了各自的引擎-classes 而没有任何关于它们之间关系的额外声明。
我们所做的,我们告诉 Rails,这两个表可能被不同的 classes 使用(通过扩展)。通过向表中添加 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
列中显式写入 class 名称来实现的。
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">
我只想post跟进:
虽然@dimakura 是正确的,但它仍然无法满足我的要求。这是因为 Rails 不允许 STI 的链接作用域。最后,我删除了 STI 并使用了以下内容:
user = User.first
user.dogs.merge(Dog.with_spots)
首先,继承方式可能不正确。如果是这样,请解释不同的方法。
这是一个设置示例。
除了使用完全相同的数据库外,我有许多未连接的应用程序。为了耗尽代码库,我有一个包含所有活动记录的引擎 类。我想在主要应用程序中保留特定于应用程序的范围和方法。
在我的 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
我不想 redefine/define 主要应用程序中的所有关联。
这种类型的问题将以不同的形式在我所有的主要应用程序中不断出现。这让我想到可能有一种方法可以重新定义关联。
有没有一种方法可以评估子类而不是超类的关联,就像 STI 模式一样让 Rails 助手识别不同的子类类?
意思是,我希望我的主应用 User#dogs 成为 return 主应用 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
MyEngine::User.dogs
returns [由关联实例代理] 一组 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
重要说明即使没有继承也不能查询user.dogs.with_spots
。您的意思可能是在 user.dogs.to_a
.
Dog
的数组
解决方案 解决您问题的最通用方法是使用Single Table Inheritance.
让我们看看它是如何工作的。
我假设已经有表 my_engine_users
和 my_engine_dogs
定义了所有必需的列(就 MyEngine
模块而言)。
类 MyEngine::User
和 MyEngine::Dog
保持不变。
类 User
和 Dog
只是扩展了各自的引擎-classes 而没有任何关于它们之间关系的额外声明。
我们所做的,我们告诉 Rails,这两个表可能被不同的 classes 使用(通过扩展)。通过向表中添加 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
列中显式写入 class 名称来实现的。
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">
我只想post跟进:
虽然@dimakura 是正确的,但它仍然无法满足我的要求。这是因为 Rails 不允许 STI 的链接作用域。最后,我删除了 STI 并使用了以下内容:
user = User.first
user.dogs.merge(Dog.with_spots)