Context
So a bit of context, as I'm working on Triplancer.net which is a Multi-Tenant Project developed by Ionite I needed to scope results returning for any Searchkick Query by the Current Tenant Id not to return results from other tenants.
You can do that simply by using a where query something like:
where: { tenant_id: current_tenant_id }
I did not want, however, to have to write this on every query I write, I wanted a way to override search method for models that are tenant-scoped, and add that tiny query by default to whatever Searchkick queries are made on that model.
Luckily, overriding stuff is fairly easy in Ruby, I just needed to look for where exactly should I inject my code.
SearchKick search() method
Looking at the Searchkick codebase for the source code of the method searchkick
- that used to initialize searching inside a model - here: https://github.com/ankane/searchkick/blob/master/lib/searchkick/model.rb
You can find that, on line 43, Searchkick inserts a method called searchkick_search
to the model eigenclass and conveniently to us alias this method with Searchkick.search_method_name
which you can change but is set by default to search
here: https://github.com/ankane/searchkick/blob/d10ffd4fd97003b638cb93821dcab3c86a2abba5/lib/searchkick.rb#L42
That makes our job very easy, all we need to do is follow the same convention and define a method, let's call it scope_searchkick
, that has the same signature and currys the searchkick_search method with the scoping query.
def self.scope_searchkick
class_eval do
class << self
def search(term = '*', **options, &block)
options[:where] = {} unless options.key?(:where)
options_with_tenant_scope = options[:where].merge(
{ tenant_id: current_tenant_id }
)
searchkick_search(term, options_with_tenant_scope, &block)
end
end
end
end
Now, all we need to do is call this method after the searchkick
method in our models.
I've defined this method in the ApplicationRecord class so that it'd be available in every model to call but you can define it anywhere you see suitable.
One last edit: Let's make it defensive and define it in a way that if we change Searchkick.search_method_name
our patch would still work.
How to do that?, we can simply use define_method
instead of def
and pass the Searchkick.search_method_name
now our scope_searchkick
method is:
def self.scope_searchkick
class_eval do
class << self
define_method Searchkick.search_method_name do |term= '*', **options, &block|
options[:where] = {} unless options.key?(:where)
options_with_tenant_scope = options[:where].merge(
{ tenant_id: current_tenant_id }
)
searchkick_search(term, options_with_tenant_scope, &block)
end
end
end
end
You've got to love the Ruby Metaprogramming.
Top comments (1)
Great Work!