Rails模型属性有时是虚拟的,有时是真实的

时间:2013-09-25 20:12:09

标签: ruby-on-rails ruby activerecord ruby-on-rails-3.2

我有一个非常复杂的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_accessor添加到MyObject模型中:

attr_accessor :apples, :bananas, :oranges

适用于virtual MyObject版本的attr_accessor。但现在,当试图显示对象的真实版本时,我的所有苹果,香蕉和橙子都是零!我假设这是因为method_missing getter和setter覆盖了select返回的内容。

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

P.S。我尝试了多种使用define_method和{{1}}的方式,但无法获得任何成功的方法。

3 个答案:

答案 0 :(得分: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
                         })

虚拟属性现在应该以{{1​​}}的形式提供,除非调用@my_object.apples,否则不会覆盖实际对象的任何属性方法。

答案 1 :(得分:1)

我建议调查Null Object Pattern;我认为这些方面的想法对你的情况有帮助。

一般的想法是,不是在任何地方进行零检查,而是创建一个可以代替具有一些合理默认值的原始对象的对象。

我假设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的实例,可能会将其保存在数据库中。

答案 2 :(得分:0)

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

class MyObject < ActiveRecord::Base

  def apples
    read_attribute(:apples) || 1
  end

  # etc ....
end