Picky Active Record 3 Tweet
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!)
The last post illustrated a way of writing an active record integration. Still missing is index persistence.
However, in this post I’d like to talk about wrapping the last solution up into a nicer bundle.
Beautifying the last solution
Why? It contains a few advanced Ruby concepts and statements. While I think everybody should know about class << self
and define_method
, it can get kind of hard to read compared to a more declarative style that Tire (Elastic Search), Thinking Sphinx (Sphinx), or Sunspot (Solr) offer.
However: While I like the declarative style in many cases, some libraries hide away too much important code. Many times even code that is hugely important, or does things to your model which you only find out about after reading the library source. After a crash. In production.
Goals
So what I’d like is
- have the important bits be visible and manipulable.
- hide away boilerplate code that makes code harder to read.
And maybe most important:
- use the standard Picky API
A quick reminder what the basic Picky API is:
data = Picky::Index.new :name do
category :name
end
things = Picky::Search.new data
things.search 'something'
Most other search engine adapters try to elegantify the original API. This is nice.
However, having control over both APIs, I believe that using the original (standard) Picky API creates a pressure on it to stay as elegant as possible and as useable as possible.
If we hide away the Picky API, pressure is only excerted on the ActiveRecord/Picky adapter. This also means that people who only use the Picky ActiveRecord API only come in contact with that one.
Why is this a problem? This is a problem when people want to transcend the AR API to use for example the separate and specific Picky server. If the APIs look and feel fundamentally different, users will not willingly make this jump. In fact, many people then start looking for search engine alternatives. This is a bad thing. Let me put this in bold, because it gets violated so many times:
The jump from the simple API to the harder API should not be noticeable.
The only way to do this is use a subset of the original API for the simpler one. However, since Picky is about giving you the power, we will not constrict you, but instead make the whole API accessible.
A first draft
I am not the biggest fan of the following pattern:
class Model < ActiveRecord::Base
include Picky::ActiveRecord
some_method_call_from_the included, module
end
I am not sure why since it’s perfectly ok Ruby. I believe it is because it usually consists of two lines, and only one really describes what is going on: “I am using this” and “I am using it like this”.
With this subgoal in mind, I started drafting the API. It turned out like this:
class Model < ActiveRecord::Base
extend(Picky::ActiveRecord.new(:models) do
Picky::Index.new :models do
category :name
category :surname
end
end)
end
Don’t judge me. It gets better.
Why do I use so many round parentheses, having declared them unnecessary not so long ago?
Turns out, extend
gobbles up my block. Try running the following code:
module A; end
class B
extend A do
# ...
end
end
I am unsure what happens here. Looking at the CRuby code didn’t help. Ideas?
I guess we can all agree that this API is neither good looking nor elegant. Let’s try again.
A better draft
So, teeth grinding, we return back to the standard solution of having a separate include and declarations. However, I’d like to be able to use the Picky API.
This is what I’ve come up with:
class Model < ActiveRecord::Base
include Picky::ActiveRecord
index = Picky::Index.new :models do
category :name
category :surname
end
search = Picky::Search.new index
updates_picky index
searches_picky search
end
Let’s look at the design in detail.
In detail
First of all, note that no saving of indexes in instance variables is done. You can do it, should you need it, but Picky is not saving anything like @__picky_index
for you. Instead, the index and the search are both passed into the a method in which they are captured in a closure.
Let’s look at the API code.
The line
include Picky::ActiveRecord
does two things: First, it includes two other modules, Picky::ActiveRecord::Indexing
and Picky::ActiveRecord::Searching
, that are concerned with indexing and searching, respectively. It is well imaginable that one doesn’t want realtime indexing, just searching, or vice versa.
index = Picky::Index.new :models do
category :name
category :surname
end
search = Picky::Search.new index
This is the standard Picky API. You create an index (definition) and pass it into the search.
The line
updates_picky index
tells this class to automatically update the given index as soon as the after_commit
method is called.
This method can also be called as follows:
updates_picky :models
or
updates_picky
The first one uses the index called :models
and the second one uses model_class.name.tableize
to find the model name.
Finally, the line
searches_picky search
installs a Model.search
method using the given search
.
Also of note
This API does not really care where anything is set up. This is well possible:
class Model < ActiveRecord::Base
include Picky::ActiveRecord
end
# In e.g. initializers/picky.rb
#
index = Picky::Index.new :models do
category :name
category :surname
end
search = Picky::Search.new index
Model.updates_picky index
Model.searches_picky search
for the case where you’d like your search code outside the model.
Also, you can call updates_picky
multiple times:
Model.updates_picky index
Model.updates_picky index2
Model.updates_picky index3
Any updates to the model will update each index.
Implementation
If you’re interested in the implementation, see the Picky::ActiveRecord module (code at the time of this writing).
Finally, you
Hope you like the API design series. The API is certainly turning out to be simple. Too simple? Who knows.
Opinions, ideas?
We still haven’t looked at index persistence. We save this for another blog post.
Next CocoaPods Search DesignShare
Previous Picky Active Record 2