If you want to put up a
Ruby on Rails app for free, where's the natural place to put it?
Heroku, of course! :-)
BUT... what if your app idea involves full-text search? While
Heroku gives you lots of great tools for free, and they do have a number of full-text search tools, they don't give you
any level of full-text search tools
for free. (Other than a few things in public beta, or if you're lucky enough to get into it, private beta.) If you're a cheap-@$$ like me, but want something known to be reliable, this is a problem. :-(
BUT... they have
PostgreSQL, which has full-text search built-in! :-)
BUT... PostgreSQL's full-text search is a royal pain in the proverbial posterior to use (or at least so I'm told). :-(
BUT... there's the
pg_search gem, which makes it a lot easier. :-)
BUT... that has some serious bogons in the scopes it generates. I've run into two so far. One is the subject of
their very first issue filed on
Github. :-(
BUT... I've found workarounds, and that's what this post and the next one are about. :-)
First, they don't deal well with being handed a blank or nil. For instance, I am creating a job board, and each job has a title and a description. I tried putting in the Job model:
pg_search_scope :has_description, against: :description
pg_search_scope :has_title, against: :title
But what happens if your user doesn't care about one (or both) of these? The HTTP request will most likely not include a string to search on. Your searching code will thus most likely pass the scope an empty string, or a nil. In either case, it barfs and hands back a cryptic error message about something not including any lexemes. That basically means "hey, fool, I need something to search for!"
You may have seen a very similar situation with normal scopes:
scope :has_title, lambda { |t|
where("title LIKE ?", "%#{t}%")
}
(Yeah, I know, using LIKE for this is a bad idea for many reasons. This is just an example, OK? I wanted to make it a similar purpose. You can just pretend it's looking for an integer match instead.)
There is a pretty much standard solution to this, for normal scopes:
scope :has_title, lambda { |t|
where("title LIKE ?", "%#{t}%") if t.present?
}
That will search the title
only if
t is "present". (In
Ruby, with the assorted
Rails extensions loaded, that means it's not blank. Being blank means it's nil, or an empty string, or a string of only whitespace.)
So how do you tell a
pg_search_scope to fire only if the argument is present? Unfortunately, you
can't (at least as far as I've seen).
BUT... you can work around it by
wrapping it in a normal scope. Rename your
pg_search_scope to something else, like
pg_has_title or
_has_title. Optionally, make it
private. (Yes, I know,
Ruby's notion of
private isn't
really private, but at least marking it as such can keep it out of the way.) Then,
refer to it from a normal scope:
scope :has_title, lambda { |t|
_has_title(t) if t.present?
}
private
pg_search_scope :_has_title, against: :title
Now your
pg_search_scope won't even be called unless the argument is present.
In
Part Two, we'll see how to work around the bogon that prevents you from chaining two (or more)
pg_search_scopes in a single query.