Расширенный поиск со многими ко многим в Rails 4

#ruby-on-rails #ruby-on-rails-4 #activerecord #rails-activerecord #ruby-on-rails-4.2

#ruby-on-rails #ruby-on-rails-4 #activerecord #rails-activerecord #ruby-on-rails-4.2

Вопрос:

У меня есть список объектов недвижимости, каждая из которых имеет некоторые особенности. Затем у меня есть 2 таблицы:

  • real_estates, для хранения всех объектов недвижимости, добавляемых пользователями.
  • re_home_features, для хранения всех функций по умолчанию, добавленных администратором, таких как бассейн, шкаф, офис, сад и множество функций, которые может иметь недвижимость

Один и тот же real_estate может иметь много функций, и одна и та же функция может иметь много объектов недвижимости. Я создал эти модели:

real_estate.rb

 class RealEstate < ActiveRecord::Base
    has_many :real_estate_home_features
    has_many :re_home_features, as: :home_features, through: :real_estate_home_features, dependent: :destroy
end
 

re_home_features.rb

 class ReHomeFeature < ActiveRecord::Base
    has_many :real_estate_home_features
    has_many :real_estates, through: :real_estate_home_features
end
 

real_estate_home_feature.rb

 class RealEstateHomeFeature < ActiveRecord::Base
    belongs_to :real_estate
    belongs_to :re_home_feature
end
 

При этом отношение многие ко многим работает нормально.

У меня есть поиск по недвижимости с некоторыми параметрами, например:

  • Количество жилых комнат
  • Количество ванных комнат
  • Цена продажи (от минимальной до максимальной)
  • Общая площадь (от минимальной до максимальной)
  • Код недвижимости
  • И множество других параметров

Мой поиск такой:

real_estates_controller.rb

 def search
    r = real_estates
    r = r.where(code: params['code']) if params['code'].present?
    r = r.where(city_name: params['city']) if params['city'].present?
    r = r.where(garage: params['garage']) if params['garage'].present?
    r = r.where(restrooms: params['restrooms']) if params['restrooms'].present?

    r = r.paginate(:page => params[:page], :per_page => 10)

    r
end
 

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

Но теперь поиск немного сложнее. Я должен искать объекты недвижимости с определенными функциями. Пример: мне нужна вся недвижимость, в которой есть 4 туалета, 2 машины в гараже И бассейн.

В моем примере поиск в недвижимости с 4 туалетами и 2 автомобилями вернул мне 50 объектов недвижимости, но только 15 из этих объектов недвижимости имеют пул.

Как я могу отфильтровать эти 50 объектов недвижимости, чтобы показывать только записи, связанные с «функцией пула»?

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

I appreciate any help!

environment

 rails -v: Rails 4.2.1
ruby -v: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
so: Ubuntu 16.04.5 LTS
localhost database: MySQL (development)
server database: Postgres (production)
 

EDIT 1

Based on @jvillian answer, I will change my controller, adding a JOIN in the query.

 if params['home_features'].present? amp;amp; params['home_features'].any?
    r = r.joins(:re_home_features).where(re_home_features: {id: params['home_features']}).distinct(:id)
end
 

In my tests, I had:

 params['home_features'] = [1 , 2, 3]
 

У меня есть 1 объект недвижимости, который имеет эти 3 функции. Но, с точки зрения, я получил один и тот же сайт, показанный 3 раза. Если я добавил distinct, недвижимость отображается только один раз. НО … если я изменю параметры на:

 params['home_features'] = [1 , 2, 3, 500]
 

У меня нет недвижимости с этими 4 функциями, но результаты те же.
Без distinct недвижимость отображается 3 раза. С помощью dinstict недвижимость отображается один раз. Ожидаемый результат — нулевые результаты, потому что мне нужны объекты недвижимости со всеми выбранными функциями.

Но я думаю, что мы почти у цели! Я предоставлю некоторую информацию о моих моделях:

таблица real_estates

 id | title      | description | code | city_name      | garage | ...
 7 | Your House | Awesome...  | 1234 | Rio de Janeiro | 4
 

таблица re_home_features

 id | name
 1 | Pool
 2 | Garden
 3 | Tenis court
 4 | Closet
 

таблица real_estate_home_features — ассоциация многие ко многим

 id | real_estate_id | re_home_feature_id
 1 |              7 |                  1
 2 |              7 |                  2
 3 |              7 |                  3
 

Если я запущу:

 r = r.joins(:re_home_features).where(re_home_features: {id: [1,2,3,500]}).distinct(:id)
 

Я получил этот запрос (консоль rails):

 SELECT DISTINCT `real_estates`.* FROM `real_estates` INNER JOIN `real_estate_home_features` ON `real_estate_home_features`.`real_estate_id` = `real_estates`.`id` INNER JOIN `re_home_features` ON `re_home_features`.`id` = `real_estate_home_features`.`re_home_feature_id` WHERE `re_home_features`.`id` IN (1, 2, 3, 500)
 

И он возвращает 1 результат. Идентификатор недвижимости 7. Если я удалю distinct, у меня будет 3 результата: одна и та же недвижимость, 3 раза.

Ожидаемый результат равен нулю. Если params['home_features'] = [1 , 2, 3] я ожидаю 1 результат.

РЕДАКТИРОВАТЬ 2

Этот метод объединения работает, но он возвращает запрос типа «ИЛИ». В моем случае мне нужен запрос объединения с «И». Запрос должен быть: «Вернуть все объекты недвижимости, которые имеют функции 1, 2 И 3».

Спасибо!

Ответ №1:

Я не уверен, что это «расширенный поиск». Поиск по объединенным моделям описан в руководстве в разделе 12.1.4 с указанием условий для объединенных таблиц. Это будет выглядеть примерно так:

 real_estates.joins(:re_home_features).where(re_home_features: {feature_name: 'pool'})
 

Естественно, это, вероятно, не совсем сработает, потому что вы мало рассказываете нам о том, как найти «функцию пула». Но это должно дать вам правильное направление.

Кстати, причина этого:

 params['home_features'] = [1 , 2, 3, 500]
 

возвращает записи, которые имеют any значение home_features is, потому что в нем используется ‘или’. Если вы хотите real_estates со всеми home_features , тогда вам нужны «и». Вы можете поискать в Google по этому поводу.

Я думаю, я бы попробовал что-то вроде:

 def search

    r = real_estates.joins(:re_home_features)

    %i(
      code
      city_name
      garage
      restrooms
    ).each do |attribute|
      r = r.where(attribute => params[attribute]) unless params[attribute].blank?
    end

    params[:home_features].each do |home_feature_id|
      r = r.where(re_home_features: {id: home_feature_id})
    end unless params[:home_features].blank?

    r = r.uniq
    r = r.paginate(:page => params[:page], :per_page => 10)
    r

end
 

ПРИМЕЧАНИЕ: я изменил params[:city] params[:city_name] , чтобы сделать этот первый each итератор более чистым. Вам нужно будет изменить свой вид, если вы хотите сделать это таким образом. Если вы не хотите делать это таким образом, вы можете вернуться к неитераторному подходу, который у вас уже есть.

Это не проверено, поэтому я не уверен, что оно будет работать точно так, как представлено.

Комментарии:

1. Привет @jvillian, спасибо за ваш ответ! Я попробую прямо сейчас!

2. Не работает, но это хороший способ. Я должен идти сейчас, но через 2 часа я буду тестировать больше!

3. ДА. Я догадался в своем ответе, что это не сработает, потому что вы недостаточно рассказываете нам о своей ReHomeFeature модели. Добавьте больше деталей в свой вопрос для лучшего ответа.

4. Привет, @jvillian! Ваш путь верен! Теперь я вижу результаты. Я обновлю свой вопрос с результатами и предоставлю дополнительную информацию о моих моделях.

5. Отлично! Не стесняйтесь голосовать / принимать для будущих поисковиков.