1

我有一个非常复杂的 Rails (v 3.2) 模型,它正在对多个表进行手动选择。这对于获取显示模型所需的所有数据非常有用。但是,当我的数据不存在并且我必须创建一个虚拟对象时,这些列在我的模型中不存在。在我的一生中,我想不出一种方法来支持模型上的虚拟列和实际列。

它比这复杂得多,但这是我目前拥有的一般选择:

class MyObject < ActiveRecord::Base

  attr_accessible :apples, :bananas, :oranges

  def self.get(id)
    select("my_objects.*, table1.apples, table2.bananas, table3.oranges")
      .joins("left outer join table1 on something
              left outer join table2 on something
              left outer join table2 on something")
      .where(:my_object => id)
  end
end

这非常适合我需要它在id存在时显示的内容。但是,在某些情况下id不存在,我必须显示一个虚拟(或默认)对象。

我以为我可以简单地为那个虚拟对象做这样的事情:

@my_object = MyObject.new({:apples => 1,
                           :bananas => 50,
                           :oranges => 10})

但是,当然,在我这样做的视图中,@my_object.apples我得到一个错误,因为 MyObject 实际上没有这些列:

ActionView::Template::Error (unknown attribute: apples)

我采取的下一步是添加attr_accessorMyObject模型中:

attr_accessor :apples, :bananas, :oranges

这非常virtual适合MyObject. 但是现在,当尝试显示对象的真实版本时,我所有的苹果、香蕉和橙子都是零!我认为这是因为attr_accessorgetter 和 setter 覆盖了 select 返回的内容。

如何在此模型上同时支持虚拟和实际属性?

ps 我已经尝试了多种使用method_missingwith的方法define_method,但一直无法获得任何成功的东西。

4

3 回答 3

1

有趣的问题。

一种方法是为这样的虚拟属性定义一个setter方法

# MyObject.rb

def set_virtual_attribs=(hsh)
 hsh.map{|key,value|  self[key] = value}
end

然后你可以像这样创建一个虚拟对象

@my_object = MyObject.new(:set_virtual_attribs => {
                           :apples => 1,
                           :bananas => 50,
                           :oranges => 10
                         })

虚拟属性现在应该可用 as@my_object.apples并且这不会覆盖实际对象的任何属性方法,除非set_virtual_attribs被调用。

于 2013-09-25T22:17:21.660 回答
1

我建议研究Null Object Pattern;我认为沿着这些思路的想法将对您的情况有所帮助。

一般的想法是,您创建一个可以用一些合理的默认值代替原始对象的对象,而不是到处都有 nil 检查。

我假设您class MyObject定义之外的某些东西正在决定何时id不存在,因此您需要@my_object = MyObject.new({:apples => 1,...拨打电话。MyObject你可以有一个不同的类,而不是在那里创建一个实例:

class MyNilObject
  def apples
    1
  end

  def bananas
    50
  end

  def oranges
    10
  end
end

然后改为@my_object = MyNilObject.new. 如果要求您能够动态设置这些默认值,您可以拥有一个def initialize接受散列并分配属性并具有 attr_readers 的函数。

此类将是一个没有任何数据库持久性的普通旧 Ruby 对象。它需要实现您的视图在MyObject.

我不确定在呈现对象后在这种情况下您会做什么,因此您可能需要create在此对象上实现类似的方法,将其转换为实例MyObject并将其保存在数据库中,可能。

于 2013-09-25T23:27:02.193 回答
0

您可以覆盖模型中的访问器方法:

class MyObject < ActiveRecord::Base

  def apples
    read_attribute(:apples) || 1
  end

  # etc ....
end
于 2013-09-25T23:45:52.750 回答