我有一个状态表,每个状态都有一个name属性。目前我可以这样做:
FooStatus.find_by_name("bar")
那没关系。但我想知道我能否做到:
FooStatus.bar
所以我采用这种方法:
class FooStatus < ActiveRecord::Base
def self.method_missing(meth, *args, &block)
if self.allowed_statuses.include?(meth.to_s.titleize)
self.where("name = ?", meth.to_s.titleize).first
else
super(meth, *args, &block)
end
end
def self.allowed_statuses
self.pluck(:name)
end
end
上面的代码有效,但它会导致以下奇怪的行为:
FooStatus.respond_to?(:bar) => false
FooStatus.bar => #<FooStatus name: 'bar'>
那不是很好,但是如果我尝试实现respond_to ?,我会收到递归问题
class FooStatus < ActiveRecord::Base
def self.method_missing(meth, *args, &block)
if self.allowed_statuses.include?(meth.to_s.titleize)
self.where("name = ?", meth.to_s.titleize).first
else
super(meth, *args, &block)
end
end
def self.allowed_statuses
self.pluck(:name)
end
def self.respond_to?(meth, include_private = false)
if self.allowed_statuses.include?(meth.to_s.titleize)
true
else
super(meth)
end
end
end
这让我:
FooStatus.bar => ThreadError: deadlock; recursive locking
有关让method_missing和respond_to一起工作的想法吗?
答案 0 :(得分:1)
我不知道我是否推荐你的方法......对我来说似乎太神奇了,我担心当你的状态名称为'destroy'或者你可能合法地想要的其他方法时会发生什么调用(或内部的Rails调用,你不知道)。
但是......我觉得你最好通过循环使用allowed_statuses并创建它们来扩展类并自动定义方法,而不是忽略方法。这将使respond_to?工作。你还可以检查以确保它还没有在其他地方定义......
答案 1 :(得分:1)
我同意Philip Hallstrom的建议。如果您在构建类时知道allowed_statuses,那么只需遍历列表并明确定义方法:
%w(foo bar baz).each do |status|
define_singleton_method(status) do
where("name = ?", status.titleize).first
end
end
...或者如果您需要代码中其他位置的状态列表:
ALLOWED_STATUSES = %w(foo bar baz).freeze
ALLOWED_STATUSES.each do |status|
define_singleton_method(status) do
where("name = ?", status.titleize).first
end
end
更清晰,更短,更不容易出现未来破损和奇怪的兔子洞与ActiveRecord的冲突,就像你所在的那样。
你可以使用method_missing和朋友做很酷的事情,但这不是第一种进行元编程的方法。在可能的情况下,明确通常会更好。
我也同意菲利普关于用内置方法创造冲突的关注。拥有一个硬编码的状态列表可以防止这种情况走得太远,但如果该列表可能会增长或发生变化,您可能会考虑FooStatus.named_bar
而不是FooStatus.bar
这样的约定。
答案 2 :(得分:0)
使用范围。
class FooStatus < ActiveRecord::Base
scope :bar, where(:name => "bar")
# etc
end
现在,您可以执行FooStatus.bar
,它将返回ActiveRelation对象。如果您希望此返回单个实例,则可以执行FooStatus.bar.first
或FooStatus.bar.all
,或者您可以将.first
或.all
放在范围的末尾在哪种情况下,它会返回与取景器相同的东西。
如果输入不是常数(不总是&#34; bar&#34;),您还可以使用lambda定义范围。 Section 13.1 of this guide has an example