如何在Ruby中为实例的属性定义方法?
假设我们有一个名为HtmlSnippet
的类,它扩展了ActiveRecord :: Base of Rails,并且有一个属性content
。而且,我想为它定义一个方法replace_url_to_anchor_tag!
,并按以下方式调用它;
html_snippet = HtmlSnippet.find(1)
html_snippet.content = "Link to http://stackoverflow.com"
html_snippet.content.replace_url_to_anchor_tag!
# => "Link to <a href='http://stackoverflow.com'>http://stackoverflow.com</a>"
# app/models/html_snippet.rb
class HtmlSnippet < ActiveRecord::Base
# I expected this bit to do what I want but not
class << @content
def replace_url_to_anchor_tag!
matching = self.match(/(https?:\/\/[\S]+)/)
"<a href='#{matching[0]}'/>#{matching[0]}</a>"
end
end
end
由于content
是String类的实例,因此重新定义String类是一个选项。但我不想这样做,因为它会覆盖String的所有实例的行为;
class HtmlSnippet < ActiveRecord::Base
class String
def replace_url_to_anchor_tag!
...
end
end
end
有什么建议吗?
答案 0 :(得分:0)
你的代码不工作的原因很简单 - 你正在使用@content,它在执行的上下文中是nil
(self
是类,而不是实例)。所以你基本上修改了nil的本征类。
所以你需要在设置时扩展@content的实例。有几种方法,有一种:
class HtmlSnippet < ActiveRecord::Base
# getter is overrided to extend behaviour of freshly loaded values
def content
value = read_attribute(:content)
decorate_it(value) unless value.respond_to?(:replace_url_to_anchor_tag)
value
end
def content=(value)
dup_value = value.dup
decorate_it(dup_value)
write_attribute(:content, dup_value)
end
private
def decorate_it(value)
class << value
def replace_url_to_anchor_tag
# ...
end
end
end
end
为了简单起见,我已经忽略了“零场景” - 你应该以不同的方式处理nil
值。但这很简单。
另一件事是你可能会问为什么我在setter中使用dup
。如果代码中没有dup
,则以下代码的行为可能是错误的(显然这取决于您的要求):
x = "something"
s = HtmlSnippet.find(1)
s.content = x
s.content.replace_url_to_anchor_tag # that's ok
x.content.replace_url_to_anchor_tag # that's not ok
Wihtout dup
您不仅要扩展x.content,还要扩展您已分配的原始字符串。