Подтвердить что ты не робот

Подклассы модели rails → множественное наследование или полиморфизм таблицы

Вот моя настройка, а затем объяснение того, что я пытаюсь выполнить.

class Layer < ActiveRecord::Base
  has_and_belongs_to_many :components
end

class Component < ActiveRecord::Base
  has_and_belongs_to_many :layers 
end

class ImageComponent < Component
  # I want this table to inherit from the Component table
  # I should be able to add image-specific fields to this table
end

class VideoComponent < Component
  # I want this table to inherit from the Component table
  # I should be able to add video-specific fields to this table
end

Что я хочу сделать:

layer.components << ImageComponent.create
layer.components << VideoComponent.create

На практике я понимаю, что ImageComponent и VideoComponent действительно должны наследовать от ActiveRecord::Base. Есть ли способ хорошо реализовать подклассы модели в Rails?

Прямо сейчас у меня установлена ​​моя модель Component polymorphic, для которой ImageComponent и VideoComponent каждый has_one :component, as: :componentable. Это добавляет слой раздражения и уродства в мой код:

image_component = ImageComponent.create
component = Component.create
component.componentable = image_component
layer.components << component

Я предполагаю, что простой способ объяснить это заключается в том, что я хочу реализовать отношения habtm между слоями и компонентами. У меня есть несколько типов Компонентов (например, ImageComponent, VideoComponent), каждый из которых имеет одну и ту же базовую структуру, но различные связанные с ними поля. Какие-либо предложения о том, как это можно сделать? Я чувствую, что у меня что-то не хватает, потому что мой код кажется хакерским.

4b9b3361

Ответ 1

"Официальным" способом достижения этого в Rails является использование Single Table Inheritance. Поддержка STI встроена в ActiveRecord: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance

Если вы хотите использовать Multi Table Inheritance, вам придется реализовать его самостоятельно...

Ответ 2

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

Сохраните тип (изображение/видео) в качестве ресурса для компонента и у него есть контроллер для компонента, а не для всех типов()

пусть структура модели будет как

Component < ActiveRecord::Base
  accepts_nested_attributes_for :resource
  belongs_to :resource, :polymorphic => true, :dependent => :destroy

  def resource_attributes=(params = {})
    self.resource = spec_type.constantize.new unless self.resource
    self.resource.attributes = params.select{|k| self.resource.attribute_names.include?(k) || self.resource.class::ACCESSOR.include?(k.to_sym)}
  end

#component will be either image or video and not both

Image < ActiveRecord::Base
  has_one :component, as :resource 

Video < ActiveRecord::Base
  has_one :component, as :resource

и один контроллер как ComponentsController для CRUD компонента. Поскольку компонент принимает атрибуты ресурса (т.е. Изображение/видео), вы можете сохранить компонент, а также ресурс и добавить обычные проверки для каждого ресурса.

основной вид добавления компонента может быть как

= form_for(@component, :url => components_path, :method => :post) do |f|
  = fields of Component
  = f.fields_for :resource, build_resource('image') do |image|
    = fields of resource Image
  = f.fields_for :resource, build_resource('video') do |video|
    = fields of resource Video

поля для Image/Video могут быть добавлены с помощью вспомогательного метода

module ComponentsHelper
  def build_resource(klass)
    klass  = "{klass.capitalize}"
    object = eval("#{klass}.new")
    if @component.resource.class.name == klass
      object = @component.resource
    end
    return object
  end
end

так как компонент может иметь только один связанный ресурс (изображение/видео), вам нужно выбрать тип ресурса в представлении (в моем случае это был раскрывающийся список) и в зависимости от выбранного ресурса отображают его поля и скрывают/удалить все поля других ресурсов (если выбрано изображение, удалите поля видео с помощью javascript). Когда форма отправляется, метод из модели Component фильтрует все пары ключ-значение для предполагаемого ресурса и создает компонент и связанный с ним ресурс.

Также

1) сохраняйте имена полей для каждой уникальной причины ресурса при отправке формы, представляются поля скрытого ресурса (нежелательные ресурсы), которые перезаписывают предполагаемые поля ресурсов.

2) приведенная выше структура модели дает проблему только для ресурса attr_accessor (они недоступны на консоли rails). его можно решить как

ACCESSOR = ['accessor1', 'accessor2'] #needed accessors
has_one :component, :as => :resource
attr_accessor *ACCESSOR

Смотрите как реализовать функциональность jobpost с 3 фиксированными категориями

Надеюсь, это поможет.

Ответ 3

С STI вы делитесь одной и той же таблицей с несколькими классами моделей, поэтому, если вы хотите, чтобы подклассы моделей имели уникальные поля (столбцы базы данных), они должны быть представлены в этой общей таблице. Из комментариев в вашем примере видно, что это то, что вы хотите.

Однако вы можете сделать трюк, который включает в себя наличие столбца строки в таблице, который каждая модель может использовать для хранения пользовательских сериализованных данных. Чтобы сделать это, должно быть хорошо, что эти элементы данных не индексируются, потому что вы не сможете легко найти их в SQL. Скажем, вы называете это поле aux. Поместите это в родительскую модель:

  require 'ostruct'
  serialize :aux, OpenStruct

Теперь скажем, что вы хотите, чтобы поля под названием manager и experience находились в подклассовой модели, но ни одна из других моделей STI не нуждалась в этом поле, и вам не нужно искать на основе этих атрибутов. Таким образом, вы можете сделать это в подклассовой модели:

# gets the value
def manager
  return self.aux.manager
end

# sets the value
def manager=(value)
  self.aux.manager = value
end

 # gets the value
def experience
  return self.aux.experience
end

# sets the value
def experience=(value)
  self.aux.experience = value
end

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