How can I stop meta_search from defining search on my models?

694 views Asked by At

I'm using active_admin and that's bringing in meta_search to my project. (Which I don't want to use for anything else).

It seems to be defining the search method on all my models, which then means when I include tire, I can't use it's search method.

There seems to be something strange with how it's defining the method also - method_defined? says that the search method is not defined, yet when I call it I get the meta_search one. Even if I define my own search method in the class, when I call Document.search I still get meta_search.

EDIT: I'd be interested in general ways of dealing with this sort of thing - I have solved this particular issue by using Model.tire.search (since tire is also accessible that way), but I still hate that a gem I'm not even using can force me to use a workaround in the rest of my project.

EDIT: I don't know of a good way of including code blocks in answers to answers, so I'll put this here.

# Meta_search loaded, tire is not
1.9.3p125 :001 > require "tire"   #=> true
1.9.3p125 :002 > Document.send(:include, Tire::Model::Search)
=> Document(...)
1.9.3p125 :003 > Document.search
  Document Load (2.1ms)  SELECT "documents".* FROM "documents" 
  # I get meta_search, as I should


# Tire loaded (and the include Tire::Model::Search is inside the class definition), meta_search is not loaded
1.9.3p125 :001 > Document.search
# I get tire, as I should
1.9.3p125 :002 > require "meta_search"   #=> true
1.9.3p125 :003 > Document.search
# I still get tire, all is well

# Tire loaded, meta_search is not loaded
1.9.3p125 :001 > require "meta_search"   #=> true
1.9.3p125 :002 > Document.search
  Document Load (1.8ms)  SELECT "documents".* FROM "documents" 
# I get meta_search, even though Document.search was already defined!

# Tire loaded, meta_search is not loaded, RAILS_ENV="production"
Loading production environment (Rails 3.2.2)
1.9.3p125 :001 > require "meta_search"
=> true 
1.9.3p125 :002 > Document.search
# I get tire!

My interpretation of this is that there is a bug with how meta_search detects if search is already defined when the class hasn't actually loaded. Hooray!

2

There are 2 answers

4
Viktor Trón On BEST ANSWER

the relevant 2 lines:

https://github.com/ernie/meta_search/blob/master/lib/meta_search.rb#L55

https://github.com/ernie/meta_search/blob/master/lib/meta_search/searches/active_record.rb#L46

I don't think it is a bug, it is just how things are.

in scenario 3 In development environment, you do not preload the model. When you require 'meta_search', it will define 'search' on ActiveRecord::Base. Then you say Document, which loads the model, will first inherit defined search method so when it includes Tire search module, it will leave search aliased to metasearch.

In production mode (scenario 4) as well as scenario 2, you preload the document model before metasearch so Tire will define search. Now requiring metasearch will only have effect on newly loaded classes.

You can see that the order in which gems are defined does not count. But you can undefine search method after gem require.

# application.rb
# ...
Bundler.require(:default, Rails.env) if defined?(Bundler)
# now move search out of the way
ActiveRecord::Base.instance_eval { undef :search }

so any time later we load a model class and include tire, search will correctly go to tire, both in development and production.

This is not ideal since the search method for non-tire models will not delegate to metasearch, in fact it will not be defined. So probably the second solution is best: here you overwrite the search method with one which checks for tire at runtime:

class ActiveRecord::Base
  def self.search(*args, &block)
    if respond_to?(:tire)
       tire.search(*args, &block)
    else
       metasearch(*args, &block)
    end
  end
end

does this help?

2
Michael Fairley On

Short, but unsatisfying answer: Move tire above active_admin in your Gemfile. Both meta_search and tire avoid defining the search method if it's already defined, so making sure tire's loaded first should do it.

Alternatively, after both tire and meta_search are loaded (e.g. in a Rails initializer), you could redefine ActiveRecord::Base.search to do tire.search:

method_defined? checks to see if the instance method is defined, whereas search in this case is a class method. The only real way to see if a class method is defined is Document.methods.include?(:search). Likewise, when you redefined search, did you make sure to make it a class method (e.g. def self.search)?

Unfortunately, the "my dependency's dependency is monkey-patching my code" problem is a major annoyance in Ruby, and is somewhat unavoidable. Library authors are given a lot of flexibility to do what they want, but they often abuse their power in order to "make things easier to use".