4

According to the accepted answer of this question, you should be able to use the return value of a instance method as the against parameter...

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

  def manufacturer_name
   manufacturer.name
  end
end

However, when I try to do this in a clean app (to rule out conflicting code in the real app) with the above model, db, etc created I get an undefined local variable or method error - what am I missing?

Community
  • 1
  • 1
BrightBlue
  • 417
  • 4
  • 11
  • Can we get the actual error message? I presume it is saying manufacturer is undefined because to call a model it needs to be capitalized and even then I don't think it will necessarily work depending on your name and manufature model. – toolz Jan 28 '14 at 02:33
  • The actual error message is `NameError: undefined local variable or method 'manufacturer_name' for #`. A `manufacturer` method is automatically defined by rails when `belongs_to :manufacturer` is added to the model and manufacturer_name is calling that method, not the Manufacturer class. – BrightBlue Jan 28 '14 at 02:37
  • you need a model instance to call on so define the method `def self.manufacturer_name Manufacturer.name #I'm pretty sure this needs to be capitalized end` in your controller `@foo = Car.manufacturer_name` – toolz Jan 28 '14 at 03:09
  • `Manufacturer.name` would call the `name` method on the `Manufacturer` *class* and not an *instance* thereof, which will only return "Manufacturer" - ie. the name of the class, when dealing with an instance of the above class the original code is correct, it does not require a capital. `def self.manufacturer_name` is defining a class method, not an instance method which would invalidate it's purpose (which is to return instance specific data). – BrightBlue Jan 28 '14 at 05:42

1 Answers1

3

Ok, the short story is that the code provided in the linked post does not and cannot work as it would appear as though you cannot reference a method (instance or otherwise) in this way - which makes sense: the class needs to compile in order for the method to become available, but can't because it requires said method in order for compiling to be possible.

To achieve what I was originally attempting (without overriding pg_search) I took the following steps:

Firstly, the against declaration should use a symbol in place of the original method call.

multisearchable :against => [:name, :manufacturer_name]

This done, when an object is saved the PgSearch::Document will be created/updated using the manufacturer_name method. However, this will break if you attempt to rebuild the search index with the following error:

ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR:  column cars.manufacturer_name does not exist
  LINE 5: ...sce("cars".name::text, '') || ' ' || coalesce("cars"...
                                                           ^
: INSERT INTO "pg_search_documents" (searchable_type, searchable_id, content, created_at, updated_at)
  SELECT 'Car' AS searchable_type,
         "cars".id AS searchable_id,
         (
           coalesce("cars".name::text, '') || ' ' || coalesce("cars".manufacturer_name::text, '')
         ) AS content,
         '2014-01-29 14:08:00.190767' AS created_at,
         '2014-01-29 14:08:00.190767' AS updated_at
  FROM "cars"

To fix this, override the rebuild_pg_search_documents class method which is added by the Multisearch module, to build a dynamic SQL insert query rather than reply on the existing pure SQL INSERT INTO table_name (columns) SELECT etc query:

def self.rebuild_pg_search_documents
  cars = Car.all

  query_str = 'INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at) VALUES'

  cars.each do |car|
    query_str += "('Car', #{car.id}, '#{car.name} #{car.manufacturer_name}', NOW(), NOW())"

    query_str += ', ' unless car == cars.last
  end

  query_str += ';'

  connection.execute query_str
end

Hopefully this will help someone else.

BrightBlue
  • 417
  • 4
  • 11
  • 2
    I ran into the same problem. You know what's really weird? It works out of the box if you do this: ```multisearchable :against => [:name, :manufacturer_name], if: proc{|p| true}```. It seems to recognize the method only if there's a condition? – Hesham Jan 30 '14 at 16:08
  • You're right and if you look at the queries when running `PgSearch::Multisearch.rebuild` Car you can see that it's running the update once per instance (as it does on save), rather than a single query to insert all records at once (bypassing the `self.rebuild_pg_search_documents` method). I can only assume that this is because it has to work with an instance of the object in order to fulfil the condition anyway. – BrightBlue Feb 04 '14 at 02:38
  • Also of interest is that is you do define your own `self.rebuild_pg_search_documents`, that is used - regardless of the proc. Good to know if performance is an issue when rebuilding. – BrightBlue Feb 04 '14 at 02:43
  • I opened an issue here: https://github.com/Casecommons/pg_search/issues/157. Hopefully it'll be fixed soon. – Hesham Feb 07 '14 at 18:18