Picky Active Record 2

ruby / picky / activerecord

This post talks about integrating Picky directly into Rails/ActiveRecord.

(By the way, greetings from Rails Camp X Adelaide – come up and say hi if you are here!)

In the last post we talked about a light active record integration. This has been implemented in the prototype and released in Picky 4.0.9.

By light integration we mean:

A quick example of 4.0.9 ActiveRecord integration

First, configure a Sinatra Picky server to be open for external indexing.

class YourSearch < Sinatra::Base
  extend Sinatra::IndexActions

  # Configure indexes etc. as usual
end

Then, configure your AR model:

class Model < ActiveRecord::Base
  # These are the default options.
  #
  extend Picky::Client::ActiveRecord.configure(host: 'localhost', port: 8080, path: '/')

  # The model definition as usual.
end

And that’s it already :)

Direct integration

While the above is very nice, you still need a separate server.

Usually I advocate keeping search separate from the app, because normally, search and app have different goals. For example, caching for either needs to work differently. Search maybe needs to be restarted independently etc.

But sometimes, you simply want a quick and simple search to directly run in the one server you have.

So instead of setting up a separate server, we would integrate Picky directly in the model.

How would we do this?

A first simple implementation

At this point I am incredibly glad to have designed Picky to work and run anywhere.

Since you already can stick it anywhere (a Sinatra server, a DRb server, a simple script, a PORO, …), you can relatively easily stick it into an active record model.

How, you ask? Let me show you the whole thing and then pick it apart.

class Model < ActiveRecord::Base
  
  class << self
    data = Picky::Index.new :models do
      category :name
      category :surname
    end
  
    define_method :replace do |model|
      data.replace model
    end
    
    define_method :remove do |id|
      data.remove id
    end
  
    models = Picky::Search.new data
    
    define_method :search do |*args|
      models.search *args
    end
  end
  
  after_commit do
    if destroyed?
      self.class.remove self.id
    else
      self.class.replace self
    end
  end
  
end

Got that? If not, here’s a step by step explanation:

We want the index and the search object to reside in the (singleton) class to define methods there, so we open it:

class << self

Then we define a Picky index (two searchable categories, name and surname) and two methods. One to replace (“insert or update”) indexed models and one to remove indexed models with a given id:

data = Picky::Index.new :models do
  category :name
  category :surname
end

define_method :replace do |model|
  data.replace model
end

define_method :remove do |id|
  data.remove id
end

Why am I using define_method instead of def? I want to capture the data (index) and the models (search) in the block for these methods to use them later on.

These two methods, since defined on the class’ singleton class, are used like that:

Model.replace model
and
Model.remove model_id

These are all the methods that have to do with curating the index.

Finally, we want the class to update the index as soon as it changes. We use AR 3.0+ after_commit callback for that:

after_commit do
  if destroyed?
    self.class.remove self
  else
    self.class.replace self
  end
end

So if the object has been destroyed, we remove it from the index (using the “class methods” we defined earlier). If it hasn’t, we simply replace the data.

Interesting to note: On a replace, Picky simply calls the methods the categories name: name and surname. So not only can Picky index Active Record attributes, but any method it has.

First conclusion

You can already do this in the current Picky version 4.0.9.

However, this has a few disadvantages:

How do we do this? The dumping is relatively easy, but how do we get the data back into that index when restarting and loading the index? If you’re into trying to implement that have a go. If not, stay tuned! :)

Another question for you: Is sticking the method on the Model like

Model.replace model

actually a good idea? What if, say Thinking Sphinx, reloads your models? Is your model – being an AR model – not already doing enough? What about the single responsibility principle?

It’s already night here at Rails Camp X Adelaide, so good night. And good luck. Stay tuned!

Next Picky Active Record 3

Share


Previous

Comments?