How to configure a pg_search multisearch on associated models in Rails?
Asked Answered
D

1

9

I'm adding pg_search into a Rails app. I'm not completely understanding the configuration, and would appreciate a gentle nudge in the right direction.

First, I already have a multi model site more or less set up and running on my app. But I want to extend it to also search on associated models.

For example, I have Manufacturer, Car, Model classes. Currently if I search for "Ford", only the manufacturer is returned. I'd also like to return all the associated Cars (which belong to Manufacturer) and Models (which belong to Car).

I can see how to do this as a scoped search

class Car
  pg_search_scope :manufactured_by, :associated_against => {
    :manufacturer => [:name]
  }
end

But if I try to do this on a multisearch it doesn't work

class Car
  include PgSearch
  multisearchable :against => [:name], 
    :associated_against => {
        :manufacturer => [:name]
      }
end

It doesn't generate an error, it simply doesn't pick up the associated records.

I have a feeling I'm missing something fundamental in my understanding of how this all fits together. I'd really appreciate if someone could help me understand this, or point me towards a good source of info. I've been through the info on github and the related Railscast, but I'm still missing something.

Division answered 9/5, 2012 at 9:4 Comment(0)
E
15

It is impossible to search associated records with multisearch, due to how polymorphic associations work in Rails and SQL.

I will add an error that explains the situation so that in the future it won't be as confusing.

Sorry for the confusion.

What you could do instead is define a method on Car that returns the text you wish to search against.

class Car < ActiveRecord::Base
  include PgSearch
  multisearchable :against => [:name, manufacturer_name]
  belongs_to :manufacturer

  def manufacturer_name
    manufacturer.name
  end
end

Or to be even more succinct, you could delegate:

class Car < ActiveRecord::Base
  include PgSearch
  multisearchable :against => [:name, manufacturer_name]
  belongs_to :manufacturer
  delegate :name, :to => :manufacturer, :prefix => true
end

But you have to make sure the pg_search_documents table gets updated if you ever make a name change to a Manufacturer instance, so you should add :touch => true to its association:

class Manufacturer < ActiveRecord::Base
  has_many :cars, :touch => true
end

This way it will call the Active Record callbacks on all the Car records when the Manufacturer is updated, which will trigger the pg_search callback to update the searchable text stored in the corresponding pg_search_documents entry.

Emerald answered 11/5, 2012 at 0:14 Comment(4)
hi @nertzy, thanks for your answer. It makes much more sense now! Can :touch ba called on a :has_many relationship? Or does it only work "up the chain"? I'm having trouble getting this fully functional. Appreciate any ideas you may have.Division
when you created the delegation, did you mean delegate :manufacturer_name, ... ?Dissatisfactory
According to the docs for has_many, :touch is not an option. You would need to write your own solution. Also I think you generally don't want to go the other way because you would be calling touch on a lot more records and it could start to run away, especially if those records also touch other ones after their saves. Instead, you could set up an after_save hook that checks if particular columns have changed, iterates through the has_many with find_each, and calls update_pg_search_document on each recordEmerald
@GrantHutchins great comment, please create an answer. I'm curious if anyone knows any ill-effects of doing such an approach.Venturesome

© 2022 - 2024 — McMap. All rights reserved.