доступ к отношению в инициализаторе, mongoid

Я переношу проект с MongoMapper на Mongoid. У меня есть две модели, Graph и Point со ссылочным отношением 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

Когда я пытаюсь создать новую точку, связанную с графиком, я получаю нулевую ошибку от инициализатора.

# 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'
      ...

Кто-нибудь может объяснить, почему это не работает? Я могу написать метод build_point для модели Graph и всегда создавать точки таким образом, но тогда я теряю преимущества следования соглашениям Rails. Обратные вызовы, похоже, тоже не дают мне желаемого поведения. (Проверки выполняются в первую очередь, и я хочу завершить инициализацию состояния перед проверками).

Рельсы 3.2.22, Монгоид 3.1.7


person Bee    schedule 30.11.2015    source источник


Ответы (1)


Вы идете об этом неправильно. Переопределение initialize при использовании O[RD]M почти всегда является плохой идеей.

Более распространенным подходом было бы использование стандартных хуков жизненного цикла для разбора вещей. Например, вы можете использовать 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:

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 делают правильные вещи.

person mu is too short    schedule 02.12.2015
comment
Я начал с попытки использовать обратные вызовы жизненного цикла, но, по сути, у меня возникло состояние гонки, потому что я поместил свой код в обратный вызов after_create, полагая, что к тому времени состояние графа/отношения, надеюсь, будет инициализировано. Я так и не добрался до обратных вызовов создания, потому что цель была недействительной, а цель была недействительной, потому что я так и не прошел весь путь создания... Мне нужно еще немного подумать, чтобы увидеть если я могу использовать хуки before_validation для этого. - person Bee; 03.12.2015