访问初始化程序中的关系,mongoid

时间:2015-11-30 23:28:54

标签: ruby-on-rails mongoid

我正在将项目从MongoMapper迁移到Mongoid。我有两个模型GraphPoint,其中引用了1-N关系,因此点belongs_to :graph和每个图has_many :points。我已经在Point模型中覆盖了初始化器,因为我需要从该点所属的图形实例中引用一些状态,以便正确初始化该点。

然而,似乎即使在初始化程序中调用super之后关系也未初始化。如何正确设置点的状态?

这是我的两个类和初始化程序的简化版本:

class Graph
  include Mongoid::Document

  has_many :points,  dependent: :delete

  field :type,       type: String
  field :timezone,   type: String

  # ...etc...
end

class Point
  include Mongoid::Document

  belongs_to :graph

  field :date,    type: Date
  field :value,   type: Float
  field :urtext,  type: String  # as originally entered by user
  field :nextday, type: Date

  attr_accessible :urtext

  validates :value, presence: true
  validates :date,  presence: true

  def initialize(params, options={})
    super(params, options)
    parse_urtext
  end

  def parse_urtext(ur)
    # for the sake of argument, imagine this is doing some critical calculation
    # that requires info derived from current state of the graph at creation
    # and which is related to parsing the urtext

    self.date, self.value = urtext.split(",")
    self.nextday = Time.use_zone(self.graph.timezone){ Time.zone.now.to_date + 1 }
  end
end

当我尝试创建与图表关联的新点时,我从初始化程序中得到了一个nil错误。

# in a console:
> g = Graph.first
> p = g.points.build( urtext: "2015-11-30,1" )
  NoMethodError: undefined method `timezone' for nil:NilClass
      from /rails_root/app/models/point.rb:34:in `parse_urtext'
      from /rails_root/app/models/point.rb:22:in `initialize'
      from /.../.rvm/gems/ruby-2.2.0/gems/mongoid-3.1.7/lib/mongoid/factory.rb:23:in `new'
      ...

任何人都可以解释为什么这不起作用?我可以在Graph模型上编写一个build_point方法,并始终以这种方式创建Points,但后来我失去了遵循Rails约定的好处。回调似乎并没有让我得到我想要的行为。 (验证是首先运行的,我想在验证之前完成初始化状态)。

Rails 3.2.22,Mongoid 3.1.7

1 个答案:

答案 0 :(得分:1)

你错了。使用O [RD] M时覆盖initialize几乎总是一个坏主意。

更常见的方法是使用标准生命周期钩子来解析事物。例如,您可以使用before_validation

class Point
  include Mongoid::Document

  belongs_to :graph

  field :date,    type: Date
  field :value,   type: Float
  field :urtext,  type: String
  field :nextday, type: Date

  before_validation :parse_urtext, :if => :urtext_changed?

  validates :value, presence: true
  validates :date,  presence: true

private

  def parse_urtext
    self.date, self.value = self.urtext.split(",")
    self.nextday = Time.use_zone(self.graph.timezone){ Time.zone.now.to_date + 1 }
  end

end

在某些情况下,您可以覆盖urtext mutator方法:

def urtext=(v)
  ...
end

但这不会在这里工作。说g.points.build( urtext: "2015-11-30,1" )会调用#urtext=,但不一定会调用#graph_id=,因此在self.graph.nil?调用期间,您可以(在这种情况下实际上)#urtext= g.points.build(...)

我倾向于使用before_validation挂钩进行此类操作,然后validates挂钩以确保before_validation调用做了正确的事情。