Правильный способ сделать это с ActiveRecord? - PullRequest
0 голосов
/ 02 февраля 2012

Скажем, у меня есть два класса,

Изображение и кредит

class Image < ActiveRecord::Base
  belongs_to :credit
  accepts_nested_attributes_for :credit
end

class Credit < ActiveRecord::Base
  #has a field called name
  has_many :images
end

Я хочу связать кредит при создании изображения, действуя немного как тег.По сути, я хочу поведение, подобное Credit.find_or_create_by_name, но в клиентском коде, использующем Credit, было бы намного чище, если бы это был просто Create.Кажется, я не могу придумать, как это сделать в модели.

Ответы [ 2 ]

0 голосов
/ 03 февраля 2012

Это, вероятно, больше проблем, чем стоит, и опасно, потому что включает в себя переопределение initialize метода класса Credit, но я думаю, что это может сработать.Я бы посоветовал вам попробовать решение, которое я предложил ранее, и отбросить эти драгоценные камни или изменить их, чтобы они могли использовать ваш метод.При этом здесь ничего не говорится:

Сначала вам нужно найти способ вызова метода для инициализатора Credit.Давайте используем класс , который я нашел в сети и который называется CallChain, но мы изменим его для наших целей.Возможно, вы захотите поместить это в папку lib.

class CallChain
  require 'active_support'

  def self.caller_class
    caller_file.split('/').last.chomp('.rb').classify.constantize
  end

  def self.caller_file(depth=1)
    parse_caller(caller(depth+1).first).first
  end

  private

  #Stolen from ActionMailer, where this was used but was not made reusable
  def self.parse_caller(at)
    if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
      file   = Regexp.last_match[1]
      line   = Regexp.last_match[2].to_i
      method = Regexp.last_match[3]
      [file, line, method]
    end
  end
end

Теперь нам нужно перезаписать инициализатор классов Credit, потому что когда вы делаете вызов Credit.new или Credit.create из другогокласс (в данном случае ваш Image класс), он вызывает инициализатор из этого класса.Вам также необходимо убедиться, что при вызове Credit.create или Credit.new, который вы вводите :caller_class_id => self.id в аргумент атрибутов, поскольку мы не можем получить его из инициализатора.

class Credit < ActiveRecord::Base 
  #has a field called name
  has_many :images
  attr_accessor :caller_class_id

  def initialize(args = {})
    super
    # only screw around with this stuff if the caller_class_id has been set
    if caller_class_id
      caller_class = CallChain.caller_class
      self.send(caller_class.to_param.tableize) << caller_class.find(caller_class_id)
    end
  end
end

Теперь, когда у нас есть эта настройка, мы можем сделать простой метод в нашем классе Image, который создаст новый Credit и правильно настроит ассоциацию следующим образом:

class Image < ActiveRecord::Base
  belongs_to :credit
  accepts_nested_attributes_for :credit

  # for building
  def build_credit
    Credit.new(:attr1 => 'val1', etc.., :caller_class_id => self.id)
  end

  # for creating
  # if you wanted to have this happen automatically you could make the method get called by an 'after_create' callback on this class.
  def create_credit
    Credit.create(:attr1 => 'val1', etc.., :caller_class_id => self.id)
  end
end

Опять же, я бы действительно не хотелНе рекомендую, но я хотел посмотреть, возможно ли это.Попробуйте, если вы не против переопределить метод initialize для Credit, я считаю, что это решение, которое соответствует всем вашим критериям.

0 голосов
/ 02 февраля 2012

Попробуйте это:

class Image < ActiveRecord::Base
  belongs_to :credit

  attr_accessor :credit_name
  after_create { Credit.associate_object(self) }
end

class Credit < ActiveRecord::Base
  #has a field called name
  has_many :images

  def self.associate_object(object, association='images')
    credit = self.find_or_create_by_name(object.credit_name)
    credit.send(association) << object
    credit.save
  end

конец

Тогда, когда вы создаете изображение, вы можете сделать что-то вроде

Image.create(:attr1 => 'value1', :attr2 => 'value2', ..., :credit_name => 'some_name')

И оно возьмет имя, которое вы ввели в значение :credit_name, и использует его в обратном вызове after_create.

Обратите внимание, что если вы решили позже связать другой объект с кредитом (скажем, класс с именем Text), вы все равно можете использовать этот метод следующим образом:

class Text < ActiveRecord::Base
  belongs_to :credit

  attr_accessor :credit_name
  before_create { Credit.associate_object(self, 'texts') }
end

Хотя в этот момент вы, вероятно, захотите подумать о создании суперкласса для всех классов, которые принадлежат к кредиту, и просто наличии суперкласса для обработки ассоциации. Вы также можете посмотреть на полиморфные отношения .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...