1

我有一个状态表,每个状态都有一个名称属性。目前我可以这样做:

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 一起工作的任何想法?

4

3 回答 3

1

我不知道我是否会推荐您的方法...对我来说似乎太神奇了,我担心当您的状态为“销毁”或您可能合法地想要调用的其他方法时会发生什么(或Rails 在内部调用你不知道的)。

但是......我认为您最好扩展类并通过循环遍历 allowed_statuses 并创建它们来自动定义方法,而不是解决方法丢失。这会使respond_to? 工作。您还可以检查以确保它尚未在其他地方定义...

于 2012-11-30T20:15:57.843 回答
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 和朋友做一些非常酷的事情,但这不是进行元编程时使用的第一种方法。如果可能,显式通常会更好。

我也同意 Philip 关于与内置方法产生冲突的担忧。拥有一个硬编码的状态列表可以防止它走得太远,但您可能会考虑一个约定,FooStatus.named_bar而不是FooStatus.bar该列表是否可能增长或更改。

于 2012-11-30T22:01:25.027 回答
0

使用范围。

class FooStatus < ActiveRecord::Base
  scope :bar, where(:name => "bar")

  # etc
end

现在,您可以执行FooStatus.bar这将返回一个 ActiveRelation 对象。如果您希望它返回单个实例,则可以执行FooStatus.bar.firstor if many FooStatus.bar.all,或者您可以将.firstor.all放在作用域的末尾,在这种情况下,它将返回与查找器相同的内容。

如果输入不是恒定的(不总是“bar”),您还可以使用 lambda 定义范围。 本指南的第 13.1 节有一个示例

于 2012-11-30T20:16:14.357 回答