我正在尝试创建一个STI Base模型,该模型会自动更改为继承的类:
#models/source/base.rb
class Source::Base < ActiveRecord::Base
after_initialize :detect_type
private
def detect_type
if (/(rss)$/ ~= self.url)
self.type = 'Source::RSS'
end
end
end
#models/source/rss.rb
class Source::RSS < Source::Base
def get_content
puts 'Got content from RSS'
end
end
我想要这样的行为:
s = Source::Base.new(:url => 'http://stackoverflow.com/rss')
s.get_content #=> Got content from RSS
s2 = Source::Base.first # url is also ending rss
s2.get_content #=> Got content from RSS
答案 0 :(得分:3)
至少有三种方法可以做到这一点:
Source::Base.new
Ruby(包括其所有罪行)将允许您覆盖new
。
class Source::Base < ActiveRecord::Base
def self.new(attributes)
base = super
return base if base.type == base.real_type
base.becomes(base.real_type)
end
def real_type
# type detection logic
end
end
这是“神奇的”,带有所有超级酷且超级混乱的行李。
becomes
class Source::Base < ActiveRecord::Base
def become_real_type
return self if self.type == self.real_type
becomes(real_type)
end
def real_type
# type detection logic
end
end
thing = Source::Base.new(params).become_real_type
这与工厂方法非常相似,但它允许您在对象创建后进行转换,如果还有其他东西在创建对象,这可能会有所帮助。
答案 1 :(得分:2)
另一种选择是使用polymorphic association,您的类可能如下所示:
class Source < ActiveRecord::Base
belongs_to :content, polymorphic: true
end
class RSS < ActiveRecord::Base
has_one :source, as: :content
validates :source, :url, presence: true
end
创建实例时,您需要创建源,然后创建并分配具体的content
实例,因此:
s = Source.create
s.content = RSS.create url: exmaple.com
您可能希望accepts_nested_attributes_for
更简单。
您的detect_type
逻辑将位于控制器或service object中。它可以返回内容的正确类,例如return RSS if /(rss)$/ ~= self.url
。
使用这种方法,您可以要求Source.all includes: :content
,当您为每个content
实例加载Source
时,Rails的多态性会将其实例化为正确的类型。
答案 2 :(得分:1)
如果我是你,我会添加一个返回正确实例的类方法。
class Source::Base < ActiveRecord::Base
def self.new_by_url(params)
type = if (/(rss)$/ ~= params[:url])
'Source::RSS'
end
raise 'invalid type' unless type
type.constantize.new(params)
end
end
然后您将获得所需的行为:
s = Source::Base.new_by_url(:url => 'http://stackoverflow.com/rss')
s.get_content #=> Got content from RSS
s
将是Source::RSS
的实例。
注意:阅读完评论后:code使用klass.new
。 new
是一种类方法。初始化后,您的对象已完成且为Source::Base
,并且无法更改它。