Sunday, September 9, 2012

Free Full-Text Search with Heroku, Part 2

   Last time, we looked at putting a Ruby on Rails app on Heroku, using the pg_search gem to access PostgreSQL's built-in full-text search by making pg_search_scopes, and how to work around the bogon that you must always feed them something to search on.

   The second bogon, subject of their very first issue filed on Github, is that you can't chain two or more of them in the same statement.  For instance, if my Job model has:

  pg_search_scope :has_description, against: :description
  pg_search_scope :has_title, against: :title

then I can't ask for:

   Job.has_description("Ruby").has_title("developer")

   What happens if I do?  It barfs and gives a cryptic error message about pg_search_rank being ambiguous.  Long story short, that means it's calculating pg_search_rank in two different contexts (one for description, and one for title), and then trying to refer to pg_search_rank, without saying which context to get it from.

   Looking at the generated SQL, we see that it's trying to use it in an ORDER BY clause, specifically:

    ORDER BY pg_search_rank DESC, "jobs"."id" ASC

Luckily, that might not be the order we want.  Sure, sometimes you do want it that way... but in my particular application (a job board), there are other perfectly relevant orders to default to, like posting date, or distance from the center of a search (if the user did a distance-limited search), or various other columns I haven't mentioned before.  So let's try to replace it, and do:

    Job.has_description("Ruby").has_title("developer").
        order(:posted_at)

   Close but no cigar.  Now we have:

    ORDER BY pg_search_rank DESC, "jobs"."id" ASC,
    posted_at DESC

   It seems that order only adds to the ordering, so it can't help us get rid of referring to pg_search_rank.

   BUT... ActiveRelation also provides the lesser-known reorder!  So if we do:

    Job.has_description("Ruby").has_title("developer").
        reorder(:posted_at)

our ORDER BY clause is now simply:

    ORDER BY posted_at DESC

   Long story short, this works.  Later, maybe I'll look into how to actually use the pg_search_rank....

Free Full-Text Search with Heroku, Part 1

   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.

Tuesday, March 20, 2012

TDD or TDDD, or should we call it T3D?

   I was commenting on Test Driven Development in one of the Ruby groups on LinkedIn today, and realized it might make a decent blog post.  So here we go:


   The "purpose" of TDD is somewhat controversial, and also depends just how you do it.  The traditional approach is "think of what you want it to do, think how you would test that, write the test, verify that the test fails (because you haven't written the code to make it pass), then write JUST enough code to make the test pass, preferably the simplest thing that could possibly work".  Those who care strongly about clean code (most who use TDD do anyway) would then add "clean up your code, and your test, to get rid of any 'smells'" (which is one of our terms for things that aren't necessarily wrong, but at least indicate that there might be something worrisome going on).

   BUT....

   Nowadays a lot of people are actually using TDD to mean Test Driven Design, not just Development.  Rather than having an overall design in mind and approaching each feature with the bias towards that design, just really do the Simplest Possible Thing, and let that influence your design.  (In more ways than just making it more modular and testable, as it always has.)  Some would say that since it applies to both design (the overall pattern, perhaps a level below architecture) and development, it should be TDDD, or as our defense contractors would probably then call it, T3D.  ;-)

Wednesday, February 29, 2012

Rubymoticons

   A few days ago, I was demonstrating injection of a symbol, to perform a series of subtractions in an array, and noticed a familiar pattern of characters.  That inspired a bit more investigation.  So now I present some basic Rubymoticons:
[].inject(  :-)
{}[         :-]
->() {      :-}
   These are perfectly valid Ruby code, and evaluate to nil, nil, and a lambda.

   Of course, the nose is easily substitutable, and noseless ones can use any mouth they please... but what about changing the eyes, or the mouth on full-size ones, and additional adornments?  Let's see how creative you can get, especially in as few characters as possible on either side.  Put some new Rubymoticons in the comments.

Wednesday, February 8, 2012

Sie mögen mich! Sie wirklich mögen mich!

   Andy Newton, over at In Search of the Tempestuous Sea, has just nominated me for this award:

   According to the terms of the award, I must now:
  1. Paste the image on my blog.  (See above.)

  2. Link back to the blogger who gave me the award.  (See further above.)

  3. Pick my five favorite blogs with less than 200 followers, and leave a comment on their blog to let them know they have received the award.

       Hmmmm, let me think.  Dang, this is harder than I thought!  Practically all the blogs I read have at least 200 followers, or are from big enough names that they probably do.  Most of the rest don't say, so I'll ass-u-me not, just to make it easy.  And nominating Andy's other blog, Bus Error, where he expounds on technical topics, would be too obvious.  So here we go, in no particular order:

    • Joe Klemmer's My Unknown Blog (which maybe now will become known), mostly on political news and cutting-edge science.

    • Lesley Slepner's Talking with our Aging Relatives, which has proven useful as a source of information... and as a place to vent.

    • Naresh Jain's Managed Chaos, mainly about agile software development.  This might have too many readers, but it doesn't say, so I'm counting it.  He's probably a big name in India, seeing as his last several posts have been about organizing the Agile India conference.

    • Bryan Liles' Smarticus, explained by its subtitle: "code – video – mac – lifehack" (the code being mainly Ruby these days).

    • Taryn somebody's (she doesn't reveal her last name) Ruby-Coloured Glasses.  They have a serious U surplus in England, doncha know.  Britain has to make use of those leftover U's from Wales somehow....

    • And as an extra special bonus, no I'm not going to officially nominate my other blogs, but I'd appreciate if you'd check them out.  See the sidebar.

  4. Hope that the five blogs chosen will keep spreading the love and pass it on to five more blogs.
   So there you have it.  Or rather, them.  Go check 'em out, including my nominator, even though they're not all on technical topics like I explore here.

Wednesday, January 18, 2012

Stop SOPA and PIPA!

   This post is black in protest of SOPA and PIPA, the House and Senate bills that will strangle freedom and innovation on the Internet, while doing next to nothing about "piracy", the alleged reason for the bills.  Take action!

* Go sign Google's petition against them, at https://www.google.com/landing/takeaction/, and any others you can find.

* Contact your Senator and Representatives. If you don't know who they are, see http://en.wikipedia.org/wiki/Main_Page, enter your zip code, and follow the links.

* Spread the word, on Facebook, Twitter, LinkedIn, Google Plus, and any other services you use.

* Put a message like this on your web site and blog.

   Do it now!

-Dave

Thursday, December 8, 2011

Ruby Geekery: Defining method_missing and respond_to at the same time

   Not all the code (or coding comments) I'd like to share with the world, is on my own blog.  Recently I made a comment I'd like to direct y'all to, over on Avdi Grimm's wondrous blog, Virtuous Code.  Long story short:
  • Ruby has this interesting mechanism called method_missing, whereby you can call a method that doesn't really exist, but method_missing will be called instead, recognize the name, and know what to do, often by pulling apart the name and acting on pieces.  (Yes, that's how a lot of the magic in Ruby on Rails works.)
  • There's also respond_to?, which you can use to ask if an object responds to a given message, which is "OO-geek talk" for whether it has a given method.   (Yes, "method" is basic OO terminology, but I mean hardcore OO-geek talk, the kind used by people who actually use languages like Smalltalk!)
  • The problem is, adding a method by having method_missing recognize it and perform the action, does not also add it to the things respond_to? knows about!  You could also manually add it to respond_to? at the same time, but that's not very DRY.  As some of us pointed out, that's exactly the kind of drudgery we expect Ruby to save us from.  (Sometimes you can have method_missing really add the method, in which case respond_to? should see it just fine, but that's another story.)

  • So Avdi wrote a blog post about what he came up with to do it automagically.

  • As I started reading his post, I envisioned a way to do it.  When I got to how he did it, it was a lot different.  His is suitable for dynamic situations where mine wasn't.

  • Later I came up with another way that is also dynamic, though some rough spots remain to be worked out.

  • Later still, I intend to come up with some further improvements.

  • Your feedback is solicited, whether there or (preferably) here.
   See: http://avdi.org/devblog/2011/12/07/defining-method_missing-and-respond_to-at-the-same-time/.