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}%")
}
private
pg_search_scope :_has_title, against: :title
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?
}
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).
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?
}
_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.
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.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.