Rails: Как реализовать поиск через ассоциацию

Оригинальный вопрос

Мое приложение имеет дело с "Машинами". У машин есть спецификации, написано столько же "Specs". Каждая из этих спецификаций содержит ссылку на «Spec_Field» (такие поля, как «Вес», «Высота», «Емкость» и т. д.) и значение.

Class Machine
  has_many :specs
  has_many :spec_fields, :through => :specs

Class Spec
  belongs_to :machine
  belongs_to :spec_field

Что я хочу сделать, так это иметь функцию поиска в действии #show для каждой машины, где пользователь отмечает спецификации, которые он хочет сопоставить («Да, длина 50 — это то, что мне нужно, а емкость 500 идеальна, я ширина не важна»), и нажимает «Найти похожие», после чего будут найдены все машины с похожими характеристиками.

Я предполагаю, что мне понадобится модель для поиска, похожая на следующий скринкаст: http://railscasts.com/episodes/111-advanced-search-form-revised?autoplay=true

Затем в действии #show для машины мне нужно будет просмотреть спецификации этой машины и добавить их в экземпляр «Поиск». Значит ли это, что «Спецификация» также должна принадлежать «Поиску»?

Я просто не могу понять, как организовать все в моем случае. Любая помощь приветствуется!

Схема базы данных

Машины:

t.string   "title"
t.text     "description"
t.datetime "created_at",                      :null => false
t.datetime "updated_at",                      :null => false
t.string   "status"
t.integer  "category_id"
t.string   "manufacturer"
t.string   "model"
t.string   "serial"
t.string   "year"
t.string   "location"

Характеристики:

t.integer  "machine_id"
t.integer  "field_id"
t.text     "value"
t.float    "base_value"
t.integer  "unit_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false

Поля спецификации:

t.string  "label"
t.integer "base_units"

Решение, которое кажется рабочим

Я добавил область «Со спецификацией», которая использует SQL «IN» с «SELECT»

 def self.with_spec(field_id,value)
    min_value = value.to_f * 0.8
    max_value = value.to_f * 1.2
    where("machines.id IN (SELECT machine_id FROM specs WHERE field_id = ? AND value > ? AND value < ?)",field_id,min_value,max_value)
  end

person Niko Efimov    schedule 02.05.2012    source источник
comment
Если это не лишнее, можно попробовать полнотекстовый поиск, например sunspot.github.com.   -  person Mik    schedule 02.05.2012


Ответы (2)


Я бы построил то, что я называю «областями фильтрации»; они сократят результирующий набор, если вы передадите хэш с ключами с соответствующими именами. Если то, что передано, равно нулю или пусто, это условие игнорируется.

Затем я определяю метод поиска класса, который объединяет все поисковые фильтры следующим образом:

Class Spec
  belongs_to :machine
  belongs_to :spec_field

  # filter scopes 
  scope :with_machine,  lambda{|*term| where('machine_id = ?',term) if term.present? }
  scope :with_length,   lambda{|*term| where('length = ?',term)     if term.present? }
  scope :with_capacity, lambda{|*term| where('capacity = ?',term)   if term.present? }
  scope :with_width,    lambda{|*term| where('width = ?',term)      if term.present? }

  def self.filter_search(options)
    with_machine(  options[:machine]  ).
    with_length(   options[:length]   ).
    with_capacity( options[:capacity] ).
    with_width(    options[:width]    )
  end

end

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

<%= text_field_tag 'search[machine]', @machine.id %>
<%= text_field_tag 'search[length]' %>
<%= text_field_tag 'search[capacity]' %>
<%= text_field_tag 'search[width]' %>
<%= submit_tag %>

И вызовите его на контроллере:

@specs = Spec.filter_search(params[:search]).all

Альтернативой может быть установка полнотекстовой поисковой системы и построение метода поиска почти таким же образом.

person Unixmonkey    schedule 02.05.2012
comment
Это выглядит интересно, но модель Spec не имеет таких полей, как длина, емкость и т. д. Вместо этого она указывает на другую таблицу (Spec_Fields) через field_id. Это сделано для того, чтобы разрешить неограниченное количество полей спецификаций и позволить пользователям создавать свои собственные поля спецификаций. - person Niko Efimov; 03.05.2012
comment
@ef-n Было бы полезно, если бы вы разместили соответствующие таблицы из своего schema.rb. - person Unixmonkey; 07.05.2012

Я думаю, что gem meta_search может помочь вам и упростить ваш код.

person caulfield    schedule 02.05.2012