8

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.

Andy Harvey
  • 12,333
  • 17
  • 93
  • 185

1 Answers1

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.

Grant Hutchins
  • 4,275
  • 1
  • 27
  • 32
  • 1
    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. – Andy Harvey May 11 '12 at 12:07
  • when you created the delegation, did you mean `delegate :manufacturer_name, ...` ? – AlexBrand Nov 14 '12 at 22:58
  • 2
    According to the [docs for has_many](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html), `: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 record – Grant Hutchins Dec 01 '12 at 17:51
  • 1
    @GrantHutchins great comment, please create an answer. I'm curious if anyone knows any ill-effects of doing such an approach. – karns Feb 03 '21 at 12:58