2013-07-10 2 views
0

У меня есть следующие классы, которые имеют много общего кода с небольшими вариациями.Извлечение общего кода ActiveRecord для аналогичных кланов

class ThirdPartyComponent < ActiveRecord::Base 
    belongs_to :prev_version, :class_name => 'ThirdPartyComponent', :foreign_key => 'prev_version_id' 
    has_one :next_version, :class_name => 'ThirdPartyComponent', :foreign_key => 'prev_version_id' 

    attr_accessible :name, :version, :installer, :install_script 

    mount_uploader :installer, ComponentFileUploader 
    mount_uploader :install_script, ComponentFileUploader 

    validates :name, :presence => true 
    validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ } 
    validates :installer, :presence => true 
    validates :install_script, :presence => true 
    validate :increased_version 

    def increased_version 
    # Check to ensure that version number is greater than the previous version number for the same component set 
    unless prev_version.nil? 
     version > prev_version.version 
    end 
    end 

    def all_previous_versions 
    prev_versions = all_versions 
    prev_versions.shift 
    prev_versions 
    end 

    def all_versions 
    current_version = self 
    all_versions = [current_version] 
    while !current_version.prev_version.nil? 
     all_versions << current_version.prev_version 
     current_version = current_version.prev_version 
    end 
    all_versions 
    end 
end 

class RegistryComponent < ActiveRecord::Base 

    belongs_to :prev_version, :class_name => 'RegistryComponent', :foreign_key => 'prev_version_id' 
    has_one :next_version, :class_name => 'RegistryComponent', :foreign_key => 'prev_version_id' 

    attr_accessible :name, :version, :registry_file 

    mount_uploader :registry_file, ComponentFileUploader 

    validates :name, :presence => true 
    validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ } 
    validates :registry_file, :presence => true 
    validate :increased_version 

    def increased_version 
    # Check to ensure that version number is greater than the previous version number for the same component set 
    unless prev_version.nil? 
     version > prev_version.version 
    end 
    end 

    def all_previous_versions 
    prev_versions = all_versions 
    prev_versions.shift 
    prev_versions 
    end 

    def all_versions 
    current_version = self 
    all_versions = [current_version] 
    while !current_version.prev_version.nil? 
     all_versions << current_version.prev_version 
     current_version = current_version.prev_version 
    end 
    all_versions 
    end 
end 

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

Я хочу извлечь общий код из этих классов в один файл (включая вызовы метода ActiveRecord, такие как проверки и т. Д.), А затем просто ссылаться на них в конкретных классах.

До сих пор я пытался,

  1. наследование - Я создал базовый класс, унаследованный от ActiveRecord, а затем каждый класс, унаследованный от базового класса. Результат: Rails жаловался, что не удалось найти таблицу базы данных, имя которой соответствует базовому классу.
  2. наследство - я считал создание базового класса в качестве бестабличных моделей вместо (см http://railscasts.com/episodes/219-active-model), но потом я понял, что конкретные классы также будут отсутствовать полную функциональность ActiveRecord
  3. композиция - Я попытался определением общего кода в модуле а затем использовать включить или расширить в конкретных классах для доступа к нему, как показано ниже.

    module ComponentBase 
        belongs_to :prev_version, :class_name => self.class.name, :foreign_key => 'prev_version_id' 
        has_one :next_version, :class_name => self.class.name, :foreign_key => 'prev_version_id' 
    
        attr_accessible :name, :version 
    
        validates :name, :presence => true 
        validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ } 
        validate :increased_version 
    
        def increased_version 
        # Check to ensure that version number is greater than the previous version number for the same component set 
        unless prev_version.nil? 
         version > prev_version.version 
        end 
        end 
    
        def all_previous_versions 
        prev_versions = all_versions 
        prev_versions.shift 
        prev_versions 
        end 
    
        def all_versions 
        current_version = self 
        all_versions = [current_version] 
        while !current_version.prev_version.nil? 
         all_versions << current_version.prev_version 
         current_version = current_version.prev_version 
        end 
        all_versions 
        end 
    end 
    
    class RegistryComponent < ActiveRecord::Base 
        include ComponentBase 
    
        attr_accessible :registry_file 
    
        mount_uploader :registry_file, ComponentFileUploader 
    
        validates :registry_file, :presence => true 
    end 
    

    Это привело к ошибке, что метод belongs_to не определен для ComponentBase. Это выглядит наиболее перспективным решением, но есть ли способ выполнить методы класса ActiveRecord в контексте класса, который включает их? В качестве альтернативы, есть ли лучший способ для меня достичь тех же целей?

ответ

0

Проблема заключается в том, что для класса класса, который включает модуль, а не для самого модуля, нужен метод belongs_to.

Проверьте модуль № http://www.ruby-doc.org/core-2.0/Module.html#method-i-included, который позволит вам запускать код на модуле, в который вы включаете свой модуль. Remmeber, что модуль является предком класса, поэтому это работает для классов и модулей.

В этом случае вы хотите запустить belongs_to на классе, что модуль быть включен в, так что-то вроде следующего должен выступать в качестве примера можно отрабатывать:

module ComponentBase 
    def self.included(mod) 
     mod.class_eval do 
      belongs_to :prev_version, :class_name => self.class.name, :foreign_key => 'prev_version_id' 
     end 
    end 
end 
+0

Посмотрите мой комментарий к ответу ниже – richard

0

я наткнулся следующий ответ на Extending a Ruby class with a standalone piece of code и с небольшим количеством экспериментов заставил его работать. Таким образом, окончательный код, который я закончил с был,

module ComponentBase 

    def self.included(base) 
    base.class_eval do 
     belongs_to :prev_version, :class_name => base, :foreign_key => 'prev_version_id' 
     has_one :next_version, :class_name => base, :foreign_key => 'prev_version_id' 

     attr_accessible :name, :version 

     validates :name, :presence => true 
     validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ } 
     validate :increased_version 
    end 
    end 

    def increased_version 
    # Check to ensure that version number is greater than the previous version number for the same component set 
    unless prev_version.nil? 
     version > prev_version.version 
    end 
    end 

    def all_previous_versions 
    prev_versions = all_versions 
    prev_versions.shift 
    prev_versions 
    end 

    def all_versions 
    current_version = self 
    all_versions = [current_version] 
    while !current_version.prev_version.nil? 
     all_versions << current_version.prev_version 
     current_version = current_version.prev_version 
    end 
    all_versions 
    end 
end 

class RegistryComponent < ActiveRecord::Base 
    include ComponentBase 

    attr_accessible :registry_file 

    mount_uploader :registry_file, ComponentFileUploader 

    validates :registry_file, :presence => true 
end 

Решения было использовать included обратный вызов, который вызывается каждый раз, когда модуль включается где-то в другом месте. Затем вызовите class_eval на базовом модуле для запуска методов в контексте класса (т. Е. Как методы класса). Самая сложная часть - получить имя класса в этом контексте, но оказалось, что я могу использовать base (не совсем уверен, почему это так, но это работает).

+0

база - это класс, в который вы включаете модуль. – Puhlze

+0

@Puhlze Я видел ваш ответ, прежде чем удалять его, и я думаю, что его немного легче понять, чем моя, хотя вам нужно изменить 'self.class.name' на' base' в обоих решениях. – richard

+0

Я верну его и исправлю, чтобы включить метод class_eval, поэтому ясно, что обсуждается в комментариях. «base» в вашем ответе - это параметр, передаваемый в этот метод. – Puhlze

1

Ваш первый вариант был на самом деле лучшим вариантом. Rails использует Single Table Inheritance, что означает, что данные для всех ваших подклассов хранятся в одной таблице, поэтому вы получили ошибку, которую вы сделали.

Что нужно сделать, это создать новую модель под названием Component и добавить к ней все поля, которые являются общими для всех ваших компонентов, а также одно дополнительное поле с именем type, которое должно быть строковым.

Ваша модель Component будет иметь все общие поля, логику и валидации.

class Component < ActiveRecord::Base 
    ... 
end 

Затем у вас есть каждый из ваших подклассов классов компонентов componenttent.

class ThirdPartyComponent < Component 
    ... 
end 
Смежные вопросы