Rails STI class 自动初始化
Rails STI class auto initialize
我正在尝试制作一个 STI 基础模型,它会自动更改为继承的 class,就像这样:
#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://whosebug.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
如果我是你,我会添加一个 class 方法来 returns 正确的实例。
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://whosebug.com/rss')
s.get_content #=> Got content from RSS
并且s
将是Source::RSS
的实例。
注意:在阅读您的评论后变为:其 code 使用 klass.new
。而 new
是一个 class 方法。初始化后,你的对象就完成了,它是一个Source::Base
,没有办法改变它。
有(至少)三种方法可以做到这一点:
1。使用 Factory method
@Alejandro Babio 的回答是这种模式的一个很好的例子。它的缺点很少,但您必须记住始终使用工厂方法。如果第三方代码正在创建您的对象,这可能具有挑战性。
2。覆盖 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
这是 "magic",可以带来所有超酷和超混乱的行李。
3。将 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
这与工厂方法非常相似,但它允许您在创建对象后进行转换,如果其他对象正在创建对象,这会很有帮助。
另一种选择是使用 polymorphic association,您的 classes 可能如下所示:
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 内容的正确 class,例如return RSS if /(rss)$/ ~= self.url
.
使用这种方法,您可以请求 Source.all includes: :content
,当您为每个 Source
实例加载 content
时,Rails' 多态性会将其实例化为正确的类型。
我正在尝试制作一个 STI 基础模型,它会自动更改为继承的 class,就像这样:
#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://whosebug.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
如果我是你,我会添加一个 class 方法来 returns 正确的实例。
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://whosebug.com/rss')
s.get_content #=> Got content from RSS
并且s
将是Source::RSS
的实例。
注意:在阅读您的评论后变为:其 code 使用 klass.new
。而 new
是一个 class 方法。初始化后,你的对象就完成了,它是一个Source::Base
,没有办法改变它。
有(至少)三种方法可以做到这一点:
1。使用 Factory method
@Alejandro Babio 的回答是这种模式的一个很好的例子。它的缺点很少,但您必须记住始终使用工厂方法。如果第三方代码正在创建您的对象,这可能具有挑战性。
2。覆盖 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
这是 "magic",可以带来所有超酷和超混乱的行李。
3。将 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
这与工厂方法非常相似,但它允许您在创建对象后进行转换,如果其他对象正在创建对象,这会很有帮助。
另一种选择是使用 polymorphic association,您的 classes 可能如下所示:
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 内容的正确 class,例如return RSS if /(rss)$/ ~= self.url
.
使用这种方法,您可以请求 Source.all includes: :content
,当您为每个 Source
实例加载 content
时,Rails' 多态性会将其实例化为正确的类型。