Выбор записей на основе условий для нескольких ассоциаций, которые используют одну таблицу - PullRequest
0 голосов
/ 31 октября 2018

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

Суть в том, что эти две ассоциации используют одну и ту же таблицу.

Вот основная модель:

class Resource < ActiveRecord::Base
  belongs_to :primary_item, class_name: 'Item', foreign_key: 'primary_item_Id'
  belongs_to :secondary_item, class_name: 'Item', foreign_key: 'secondary_item_Id'
  ...

И модель Item:

class Item < ActiveRecord::Base
  has_many :res_primary, class_name: 'Resource', foreign_key: 'primary_item_Id'
  has_many :res_secondary, class_name: 'Resource', foreign_key: 'secondary_item_Id'
  ...

Предположим, что Item имеет строковое поле с именем name. Это просто название предмета. Я хочу быть в состоянии найти все ресурсы, которые имеют основной и / или дополнительный элемент, который похож на данное имя. Вот как это выглядит (я думаю), если я просто фильтрую основной элемент:

@resources.joins(:primary_item)
          .where("#{Item.table_name}.name like #{primary_search_string}")

primary_search_string - это просто строка, которую я хочу найти в name. Это отлично работает. Аналогичный поиск работает для вторичных предметов.

Теперь предположим, что я бы хотел, чтобы пользователь мог искать ресурсы, которые имеют либо заданное имя первичного элемента, либо заданный вторичный элемент по имени, либо оба (каждый со своим собственным именем). Я бы имел primary_search_string и secondary_search_string с независимыми значениями. Одним из них может быть nil, что означает, что я не хочу сужать поиск, основанный на этой строке. Я хочу фильтровать ресурсы по одной или обеим строкам в зависимости от того, являются ли они nil. Вот то, что я закончил с тем, что работает, но кажется неловким:

@resources = <some query that obtains an active record relation of resources>

if primary_search_string then
  @resources = @resources.joins(:primary_item)
                         .where("#{Item.table_name}.name like #{primary_search_string}")
  if secondary_search_string then
    @resources = @resources.joins(:secondary_item)
                           .where("secondary_items_#{Item.table_name}.name like #{secondary_search_string}")
  end
elsif secondary_search_string then
  @resources = @resources.joins(:secondary_item)
                        .where("#{Item.table_name}.name like #{secondary_search_string}")
end

Обратите внимание, что если я присоединяюсь только к одной таблице, имя таблицы известно как Item.table_name. Однако, если мне нужно объединить обе таблицы, Rails должен различить второй экземпляр таблицы, указав еще имя с именем ассоциации: secondary_items_#{Item.table_name}.

Мой вопрос заключается в том, есть ли несколько более простой способ справиться с этим, который не требует ссылки на имена таблиц ассоциаций в предложениях where? Так как я ссылаюсь на имена таблиц ассоциаций, и имя таблицы может отличаться в зависимости от того, присоединяюсь ли я к одному из них или к обоим, я в итоге проверяю строку поиска на nil, что является способом определить, присоединяюсь ли я к таблице Item более одного раза. В этом конкретном примере программы я могу проверить это и жить с этим, будучи неловким. Но что, если я не знаю, был ли ранее @resources присоединен к основным элементам, и я хотел фильтровать на основе дополнительных элементов? В этом случае я бы не знал, какое имя таблицы ассоциаций будет использоваться в предложении where, поскольку я не знаю, было ли оно уже join или нет.

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

1 Ответ

0 голосов
/ 31 октября 2018

Если у вас есть две отдельные ассоциации, но обе они относятся к одному и тому же типу дочернего объекта (например, Resource), тогда вместо объединений / включений вы можете просто сосредоточиться на поиске записей Resource, которые соответствуют либо primary_item_id или secondary_item_id родительской Item нужной записи.

Rails 3/4 изначально не поддерживает запросы OR, но один из способов перебора это найти идентификаторы Resources, которые принадлежат Item в качестве первичной или вторичной ассоциации. (Это очевидно неэффективно, потому что вы делаете несколько запросов к базе данных.)

ids = Resource.where(primary_item_id: @item.id).map(&:id)
ids << Resource.where(secondard_item_id: @item.id).map(&:id)
@special_resources = Resource.where(id: ids, name: 'Some Special Name')

Rails 5 поддерживает запросы 'или', поэтому все гораздо проще:

resources = Resource.where(primary_item_id: @item.id).or(secondary_item_id: @item.id)
@special_resources = resources.where(name: 'Some Special Name')
...