2012-02-02 2 views
0

Скажем, у меня есть два класса,Правильный способ сделать это с помощью ActiveRecord?

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

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. Кажется, я не могу найти способ испечь это в модели.

+0

«Кредит» - это как кредит на фильм (атрибуция) в отличие от кредита на счете (оплате), правильно? – jefflunt

+0

Да, извините. 2,5 года, работая в редакции, исказил мой жаргон. –

ответ

0

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

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 

Хотя в этот момент вы вероятно, хотел бы рассмотреть возможность создания SuperClass для всех классов, принадлежащих к кредиту, и просто наличие надстройки для сопоставления. Вы также можете посмотреть на polymorphic relationships.

+0

Это может сработать. Но я надеюсь вложить эту логику в «Кредит», а не «Изображение», поскольку, в конце концов, многие другие вещи будут иметь Credits. –

+0

Я не уверен, что вы имеете в виду. Я думал, что вы хотите, чтобы действие произошло, когда изображение было создано? Если вы хотите сделать это через кредит, вы всегда можете создавать свои образы, выполняя что-то вроде 'some_credit.images << Image.create (: attr1 => 'value1')', а затем 'some_credit.save' или что-то в этом роде? – Batkins

+0

Я хочу, чтобы это произошло, когда НИЧЕГО создало кредит. В конце концов, будет много вещей с кредитами, прилагаемыми к ним. –

0

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

Сначала вам нужно получить способ вызова метода для инициализатора кредита. Давайте используем класс I found on the web called 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 

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

Смежные вопросы