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

Рельсы: от многих до многих полиморфных отношений

См. комментарии для обновлений.

Я изо всех сил пытался получить четкий и прямой ответ на этот вопрос, я надеюсь, что на этот раз я это получу!: D Мне определенно многое предстоит узнать с Rails, однако я понимаю проблему, с которой я столкнулся, и буду очень благодарен за дополнительную помощь.

  • У меня есть модель под названием "Задача".
  • У меня есть абстрактная модель под названием "Цель".
  • Я хотел бы связать несколько экземпляров подклассов Target to Task.
  • Я не использую однонаправленное наследование таблицы.
  • Я хотел бы запросить полиморфное отношение для возврата смешанного набора результатов подклассов Target.
  • Я хотел бы запросить отдельные экземпляры подклассов Target для получения задач, с которыми они связаны.

Итак, я считаю, что полиморфное отношение многих-многих отношений между Задачами и подклассами Целей упорядочено. Более подробно, я смогу сделать что-то подобное на консоли (и, конечно же, в другом месте):

task = Task.find(1)
task.targets
[...array of all the subclasses of Target here...]

Но! Предполагая, что существуют модели "Store", "Software", "Office", "Vehicle", которые являются подклассами "Target", было бы неплохо также пересечь отношения в другом направлении:

store = Store.find(1)
store.tasks
[...array of all the Tasks this Store is related to...]
software = Software.find(18)
software.tasks
[...array of all the Tasks this Software is related to...]

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

  • Используя мой пример, люди, похоже, хотят определить Store, Software, Office, Vehicle in Task, которые мы сразу можем сказать, не являются полиморфными отношениями, так как возвращает только один тип модели.
  • Как и в предыдущем пункте, люди все еще хотят определить Store, Software, Office и Vehicle in Task в одной форме или форме. Важным здесь является то, что отношения слепы к подклассу. Мои полиморфы первоначально будут взаимодействовать только с Целями, а не как их индивидуальные подклассы. Определение каждого подкласса в задаче снова начинает утихать с целью полиморфного отношения.
  • Я вижу, что модель для таблицы соединений может быть в порядке, что кажется мне несколько правильным, за исключением того, что она добавляет некоторую сложность, которую я предполагал, что Rails захочет покончить с этим. Я прошу неопытность на этом.

Кажется, это небольшая дыра в функциональности рельсов или в знаниях коллективного сообщества. Так что надеюсь, что stackoverflow может указать мой поиск ответа!

Спасибо всем, кто помогает!

4b9b3361

Ответ 1

Вы можете комбинировать полиморфизм и has_many :through, чтобы получить гибкое отображение:

class Assignment < ActiveRecord::Base
  belongs_to :task
  belongs_to :target, :polymorphic => true
end

class Task < ActiveRecord::Base
  has_many :targets, :through => :assignment
end

class Store < ActiveRecord::Base
  has_many :tasks, :through => :assignment, :as => :target
end

class Vehicle < ActiveRecord::Base
  has_many :tasks, :through => :assignment, :as => :target
end

... И так далее.

Ответ 2

Хотя ответ, предложенный SFEley, велик, есть некоторые недостатки:

  • Работа с задачами цели (Store/Vehicle) работает, но обратная связь не выполняется. Это в основном потому, что вы не можете пройти через: через ассоциацию с полиморфным типом данных, потому что SQL не может определить, в какой таблице он находится.
  • Каждой модели с помощью: через ассоциацию нужна прямая связь с промежуточной таблицей
  • Связь: через Assignment должна быть во множественном числе
  • Оператор: as не работает вместе с: через, вам нужно сначала указать его с помощью прямой связи, необходимой с промежуточной таблицей.

Учитывая это, моим самым простым решением было бы следующее:

class Assignment < ActiveRecord::Base
  belongs_to :task
  belongs_to :target, :polymorphic => true
end

class Task < ActiveRecord::Base
  has_many :assignments
  # acts as the the 'has_many targets' needed
  def targets
    assignments.map {|x| x.target}
  end
end

class Store < ActiveRecord::Base
  has_many :assignments, as: :target
  has_many :tasks, :through => :assignment
end

class Vehicle < ActiveRecord::Base
  has_many :assignments, as: :target
  has_many :tasks, :through => :assignment, :as => :target
end

Литература: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through

Ответ 3

Решение has_many_polymorphs, о котором вы говорите, не так уж плохо.

class Task < ActiveRecord::Base
  has_many_polymorphs :targets, :from => [:store, :software, :office, :vehicle]
end

Кажется, нужно делать все, что вы хотите.

Он предоставляет следующие методы:

Задача:

t = Task.first
t.targets   # Mixed collection of all targets associated with task t
t.stores    # Collection of stores associated with task t
t.softwares # same but for software
t.offices   # same but for office
t.vehicles  # same but for vehicles

для программного обеспечения, магазина, офиса, транспортного средства:

s = Software.first    # works for any of the subtargets.
s.tasks               # lists tasks associated with s

Если я правильно соблюдаю комментарии, единственная оставшаяся проблема заключается в том, что вы не хотите изменять приложение /models/task.rb каждый раз, когда вы создаете новый тип Subtarget. Кажется, что путь Rails требует, чтобы вы изменили два файла, чтобы создать двунаправленную связь. Для has_many_polymorphs требуется только изменить файл Задачи. Мне кажется победой. Или, по крайней мере, это было бы, если бы вам вообще не пришлось редактировать новый файл модели.

Есть несколько способов обойти это, но они кажутся слишком много работы, чтобы не менять один файл каждый раз. Но если вы - это мертвый набор против модификации Task, чтобы добавить к полиморфным отношениям, вот мое предложение:

Сохраняйте список подзадач, я собираюсь предложить в lib/subtargets отформатированную по одной записи в строке, которая по существу является table_name.underscore. (Заглавные буквы имеют подчеркивание с префиксом, а затем все делается строчным)

store
software
office
vehicle

Создайте config/initializers/subtargets.rb и заполните его этим:

SubtargetList = File.open("#{RAILS_ROOT}/lib/subtargets").read.split.reject(&:match(/#/)).map(&:to_sym)

Затем вам захочется либо создать пользовательский генератор, либо новую команду rake. Чтобы создать новый подзадач и добавить имя модели в файл списка подзаголовков, определенный выше. Вы, вероятно, в конечном итоге сделаете что-то голые кости, которые меняют и передают аргументы стандартным генераторам.

Извините, мне сейчас не нравится проходить через это, но вот some ресурсы

Наконец, замените список в объявлении has_many_polymorphs с помощью SubtargetList

class Task < ActiveRecord::Base
  has_many_polymorphs :targets, :from => SubtargetList
end

С этого момента вы можете добавить новый подзадач с

$ script/generate subtarget_model home

И это автоматически обновит ваш полиморфный список, как только вы перезагрузите консоль или перезапустите производственный сервер.

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

Ответ 4

Использование STI:

class Task < ActiveRecord::Base
end

class StoreTask < Task
  belongs_to :store, :foreign_key => "target_id"
end

class VehicleTask < Task
  belongs_to :vehicle, :foreign_key => "target_id"
end

class Store < ActiveRecord::Base
  has_many :tasks, :class_name => "StoreTask", :foreign_key => "target_id"
end

class Vehicle < ActiveRecord::Base
  has_many :tasks, :class_name => "VehicleTask", :foreign_key => "target_id"
end

В вашей базе данных вам понадобятся: Task type:string и Task target_id:integer

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

См. также STI и полиморфная модель вместе

Ура!

Ответ 5

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

Я думаю, что создание модели ActiveRecord для таблицы соединений - это правильный способ подойти к проблеме. Нормальная связь has_and_belongs_to_many предполагает соединение между двумя указанными таблицами, тогда как в вашем случае это похоже на то, что вы хотите присоединиться между tasks и любым из stores, softwares, offices или vehicles ( кстати, есть ли причина не использовать STI здесь? Похоже, что это поможет уменьшить сложность, ограничив количество таблиц, которые у вас есть). Поэтому в вашем случае таблица соединения также должна знать имя подкласса Target. Что-то вроде

create_table :targets_tasks do |t|
  t.integer :target_id
  t.string :target_type
  t.integer :task_id
end

Затем в вашем классе Task подклассы Target и TargetsTask вы можете настроить ассоциации has_many, используя ключевое слово :through, как описано в ActiveRecord:: Ассоциации:: ClassMethods rdoc pages.

Но тем не менее это только поможет вам в этом, потому что :through не знает, как использовать поле target_type в качестве подкласса Target. Для этого вы могли бы написать некоторые пользовательские фрагменты SQL для выбора/поиска, также зарегистрированные в ActiveRecord:: Ассоциации:: методы класса.

Надеюсь, это заставит вас двигаться в правильном направлении. Если вы найдете полное решение, я бы хотел его увидеть!

Ответ 6

Я согласен с другими, что я хотел бы найти решение, которое использует смесь ИППП, и делегирование было бы намного проще реализовать.

В основе вашей проблемы лежит запись всех подклассов Target. ActiveRecord выбирает базу данных через модель STI.

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

Ответ 7

Вы преследовали этот подход грубой силы:

class Task 
  has_many :stores
  has_many :softwares
  has_many :offices
  has_many :vehicles

  def targets
    stores + softwares + offices + vehicles
  end
  ...

Возможно, это не так элегантно, но, честно говоря, это не так много, и нет ничего по своей сути неэффективно в коде.