posts by date

05/2008
Rails plugin: EasySearch

Have you ever written a plugin or a piece of code that was more fun to write than it was actually worth? I seem to do that a lot—I enjoy experimenting with Ruby. Recently, I needed to search ActiveRecord models individually, and was in the mood to write a simple DSL. It’s now in plugin form, called EasySearch, and can be found on GitHub.

It doesn’t support joins or shared indexes or anything else you might come to expect from a search plugin. I’m posting about it not because I think you should use it, but because I think it’s a pretty cool API for searching.

So, install the plugin and do something like this:

class Search
  include RPH::EasySearch
end

(I originally had a nice acts_as_easy_search class method that hooked up the behavior, but I didn’t feel right about mixing that into Object. I wanted this plugin to work without the need of an ActiveRecord superclass. Plus, AR would then be expecting a corresponding table in the database, which is another unnecessary need. So, manual inclusion is the answer until I come to terms with a better alternative. Continuing…)

The Search class now has the means to easily search any ActiveRecord model within an application. Let’s say I want to find all of the users who mention ‘ruby’. Let’s reword that: search users with the term ‘ruby’.

$> Search.users.with('ruby')
$> # => [<#User ...>, <#User ...>, ...]

Makes sense, huh?

I have a confession to make: I lied about only having to include the module. Before anything will actually work, I’ll need to tell EasySearch about the tables I want to easily search.

EasySearch Configuration

For any generic search plugin to work, there needs to be a way to specify which database columns the search terms should be matched against. Here’s an example of how it works:

# in Rails 2.0+ you could put this in 
# config/initializers/easy_search_setup.rb
# (otherwise just use config/environment.rb)

RPH::EasySearch::Setup.config do
  setup_tables do
    users    :first_name, :last_name, :email, :bio
    projects :title, :description
    groups   :name
  end
end

There, that’s it (for real this time).

If you notice, it looks like there are “users”, “projects”, and “groups” methods that are called from within the block. And actually, that’s correct. The only difference is those methods don’t exist until they’re called, and even then they don’t “exist” (which is kind of weird, I know, but incredibly awesome nonetheless). Don’t worry about how it works (read through the code if you’re interested), just follow the pattern (use the table names instead of the model names).

After this one-time configuration is done, I can do things like:

$> Search.users.with('ryan')
$> # => [<#User ...>]
$> Search.projects.with('design')
$> # => [<#Project ...>]
$> Search.groups.with('friends')
$> # => [<#Group ...>]

If you try to search a model that hasn’t been configured, EasySearch will let you know. After all, how else would it know which columns to look in? It’s not that magical.

And getting a hold of the current table configuration is easy:

$> RPH::EasySearch::Setup.table_settings
$> # => {"users" => [:first_name, :last_name, :email, :bio], "projects" => ...}

How it Works

Really, all EasySearch is doing is building a WHERE clause and using conventions to pass that on to the appropriate finder. So Search.users is really doing User.find with a custom conditions clause.

Let’s say I want to search my User model for “ryan heath ruby”.

Search.users.with('ryan heath ruby')

This would not only compare “ryan heath ruby” with each of the specified columns (you know, from the configuration), but it’d compare “ryan”, “heath”, and “ruby” individually against each of the specified columns, providing more accurate results.

This, however, is an incredible performance limitation and is why this plugin is not meant for those gigantic applications with tons and tons of database records. It builds a potentially large WHERE clause using LIKE, which is quite slow in the computer world. For those large applications, it’s far better to use a full text solution. But again, this is all (more-or-less) for fun with little profit. I had a need to search individual models separately in a small application, so there you go. I can always go back and rework the back-end to operate differently/more efficient.

Oh, and since a rather large WHERE clause is being constructed, checking each term individually, I want to ignore meaningless words to avoid the extra overhead. For example, let’s say I tweak my search to be:

Search.users.with('ryan heath is a ruby')

EasySearch would still only search “ryan”, “heath”, and “ruby”, ignoring the “is” and “a” (because they’re dull). You can easily see what keywords are considered “dull” by doing:

$> RPH::EasySearch::Setup.dull_keywords
$> # => ['the', 'a', ...]

Now I know what you’re thinking, “Who gets to decide what keywords are dull keywords?” The programmer, of course! By default, EasySearch has a predefined list of keywords that it thinks are meaningless. But you can add to that list quite easily. Using the same Setup.config block:

RPH::EasySearch::Setup.config do
  setup_tables do
    # ...
  end

  strip_keywords do
    ['other', 'words', 'you', 'do', 'not', 'want', 'to', 'search']
  end
end

That will append those keywords to the default list (and will only count a dull keyword once, so don’t worry about duplication). If I wanted to ignore the default list completely (instead of appending to it), I’d just have to pass true to the strip_keywords method, like so:

strip_keywords(true) do
  ['other', 'words', 'you', 'do', 'not', 'want', 'to', 'search']
end

So that’s about it. It’s a fun plugin to play with, and like I said, it was more fun to write than it’s probably worth. But honestly, I think this sort of experimentation is nothing but good for programmer experience. And besides, every now and then, an “experiment” turns into something awesome (see Ambition for a prime example).

Ruby conditions

Often the conditions of an if statement are dependent on more than one thing, and sometimes it’s of the form, “if A is true and B is false”.

Avoiding the details, let’s say I wanted to do something like this:

user.cache! if user.needs_cached? && !API.local?

Essentially that’s saying “cache the user if the user needs cached (A is true) and the API is NOT a local request (B is false). The && ! is fine and all, and it’s actually quite readable to most programmers, but were you aware that you could do this?

user.cache! if user.needs_cached? unless API.local?

Maybe I’m flying solo on this one, but I wasn’t aware that you could use if and unless to chain conditions together. And I’m sure this is one of those Ruby moments where I just need to take a step back and find the right perspective. Sometimes things aren’t always what they seem (like how 1+3 is actually 1.+(3)).

Railscasts Contest: 5 Rails Tips

There’s a contest on Railscasts that has me feeling compelled to participate. So I am. To enter, the contest requires 5 tips concerning Rails, all in one post. So, this post will more than likely be one of the longest I’ve written in a while, but hopefully it will serve as a good resource/reference for other Rails developers. Without further ado, here are my five tips:

Note: I haven’t wrapped my View code examples in the ERB <%= -%> tags, as it currently makes the syntax highlighting terribly hard to read. I just wanted to make new comers aware until I can get this issue fixed.

Tip 1: Navigation Helper Plugin

Nearly every web application has some sort of navigation. Albeit tabs, menus, plain links, whatever, there needs to be a way to click through the application. And more often than not, keeping track of the current section/tab is a desired feature. Well, I wrote a Rails plugin called navigation_helper to assist with these needs.

Basic Usage

Once installed, here’s how the plugin works:

# somewhere in your view (typically your layout)
navigation [:home, :about, :contact]

# HTML output:
#   <ul class="nav_bar">
#     <li class="current"><a href="/home">Home</a></li>
#     <li><a href="/about">About</a></li>
#     <li><a href="/contact">Contact</a></li>
#   </ul>

You pass an array of symbols representing the sections that the links will navigate to. The link text will be a capitalized version of the symbol passed in. So :home becomes ‘Home’; :contact_me becomes ‘Contact Me’; and so on.

An important thing to be aware of: the helper will be expecting a named route mapped to the section passed in the array. For instance, for the :home section, the navigation helper will use home_path to build the link. And for :about, it will look for about_path. And so on. You need to make sure those routes are defined in your routes.rb file (it’s good practice to use named routes, this plugin just enforces that practice).

And as you can see, the markup is very extensible (in terms of CSS) with the use of an unordered list. The list item that holds the current section will get a CSS class of “current”. And the unordered list itself gets a CSS class rather than an ID, so that it can be used multiple times on the same page and not break your strict XHTML markup :-)

Adding Subtitles

Sometimes it’s a nice touch to add subtitles to your navigation (see my portfolio for example). This plugin has support for that. All you have to do is pass a string of the subtitle right after the section that the subtitle should be associated with. For example:

navigation [:home, 'Start Here', :about, 'Learn More', :contact, 'Get In Touch']

# HTML output:
#  <ul class="nav_bar">
#    <li class="current">
#      <a href="/home">Home</a>
#      <span>Start Here</span>
#    </li>
#    <li>
#      <a href="/about">About</a>
#      <span>Learn More</span>
#    </li>
#    <li>
#      <a href="/contact">Contact</a>
#      <span>Get In Touch</span>
#    </li>
#  </ul>

As you can see, the markup generated is clean and is ready for some CSS love. Now, what if you would rather those subtitles just be hover text on the link instead of full-blown span elements? No problem, just let the navigation helper know with the :hover_text => true option:

navigation [:home, 'Start Here', :about, 'Learn More', :contact, 'Get In Touch'], :hover_text => true

# HTML output:
#  <ul class="nav_bar">
#    <li class="current"><a href="/home" title="Start Here">Home</a></li>
#    <li><a href="/about" title="Learn More">About</a></li>
#    <li><a href="/contact" title="Get In Touch">Contact</a></li>
#  </ul>

The span elements will transform into title attributes on the links, so the text will only show up on hover.

Authorized Sections

Now, a definite need (for me at least) is to have certain tabs show up depending on some condition, such as a user being logged in. For instance, on this very site, the public sees ‘Portfolio’, ‘Words’, ‘Archives’, and ‘About’ tabs. But if I’m logged in, there’s also an ‘Admin’ tab, which allows me to access the area to post new entries, add categories, edit/delete comments, etc. But I only want that tab visible based on me being logged in. Well, I’ve added support for that, too. Just specify which sections should be “authorized” sections, like so:

navigation [:home, :about, :admin, :reports], :authorize => [:admin, :reports]

# HTML output:
#   <ul class="nav_bar">
#     <li class="current"><a href="/home">Home</a></li>
#     <li><a href="/about">About</a></li>
#     <li class="authorized_nav_link"><a href="/admin">Admin</a></li>
#     <li class="authorized_nav_link"><a href="/reports">Reports</a></li>
#   </ul>

By default, the link will be “authorized” by checking a logged_in? method. If you don’t have a logged_in? method, or need some other means of authorization (such as checking if the current_user has the admin role), then you can specify which method you’d like the navigation helper to check against by using the :with option:

navigation [:home, :about, :admin, :reports], :authorize => [:admin, :reports], :with => :administrator?

Now the plugin will only show the ‘Admin’ and ‘Reports’ tabs if the administrator? method exists and returns true. (Note: if the authorization method doesn’t exist, the helper won’t crash, it will simply not show the authorized tabs)

Also notice how the admin links get a special “authorized_nav_link” CSS class. That’s because sometimes you want your “admin” tabs to appear differently, and this gives you the power to do so. For instance, you may have your common tabs to the left, but your admin tabs floating to the right. And just to be clear, if an authorized link happens to be the current section, it will simply append the CSS classes (i.e. class=”authorized_nav_link current”).

I’ll admit, the “authorized_nav_link” isn’t the best CSS class name, but it’s best to keep defaults such as that awkward so they don’t interfere with your current CSS classes. However, you can pass another option to override that class, like so:

navigation [:home, :about, :contact, :admin], :authorize => [:admin], :authorize_css => 'custom_css'

There’s one more thing to mention about authorized links. Let’s say you’re using this for an admin menu, where all of the links are to be “authorized”. Rather than list out all of the authorized links again, you can pass an :all to the :authorize option, like so:

navigation [:users, :reports, :logs], :authorize => [:all]

Now all tabs will be authorized. Pretty simple, huh?

Setting Current Tab

Up to this point, you may be wondering how the helper knows which tab/section is the “current” tab/section. Well, it’s simple. By default the plugin uses the controller’s name to determine the current tab (or link or section) you’re on. But as we all know, that’s not always feasible. Just because you have a PublicController doesn’t mean you want your link to be “Public” in the navigation menu. To fix that issue, you can specify the current tab for any controller by doing:

class PublicController < ApplicationController
  current_tab :home
end

Now something like this will work as intended:

navigation [:home, :about, :contact]

The PublicController will be seen as :home to the navigation helper, and will choose the current tab accordingly. This is also very handy for namespaced controllers.

Install via Git

You can find this plugin on GitHub (and see the README for more/better examples), and can be installed by doing:

$> cd path/to/your/rails/project/vendor/plugins
$> git clone git://github.com/rpheath/navigation_helper.git

You can download a tarball if you don’t have Git installed. And for completeness, here’s the original announcement of the navigation_helper plugin.

Tip 2: Input CSS Plugin

This next tip is more for designers who work with Rails applications. As any designer would know, designing Forms can be somewhat of a pain, as there are many different types of INPUT tags (text boxes, password fields, submit buttons, file fields, etc). Obviously, you would not want to apply the same text box styling to a submit button, which means it requires extra work on every form to make sure you’re styling the correct INPUT tag.

Since I personally design and develop Rails applications, I’ve decided that it was time to attack this problem at its core. I wrote a plugin that will automatically add a CSS class based on the “type” attribute of any INPUT tag. And I’ve called it, input_css.

How it Works

Just install it. Then your standard form helpers will automatically have a class assigned to them. For example:

f.text_field :name
# => <input type="text" class="text" name="user[name]" value="" />

submit_tag 'Create', :class => 'button'
# => <input name="commit" type="submit" class="button submit" value="Create" />

f.check_box :is_preview
# => <input type="checkbox" class="checkbox" name="article[is_preview]" value="" />

So once the plugin is installed, the CSS to style your INPUT tags is simple:

input.text     { ... }
input.submit   { ... }
input.file     { ... }
input.checkbox { ... }
input.radio    { ... }
...

Install via Git

This plugin can be found on GitHub, and you can install it by doing:

$> cd path/to/your/rails/project/vendor/plugins
$> git clone git://github.com/rpheath/input_css.git

Once installed, you’re good to go. There’s no setup, customization, etc. It taps directly into the existing Rails tag helper, so all of your existing form elements will be updated! Now you have an easy and consistent way to style your forms.

Remember, if you don’t have Git installed, you can download a tarball. Just put the source in your vendor/plugins directory.

Tip 3: Acts As Lookup Plugin

Maybe I’m alone on this one, but do you find it a pain to deal with select (i.e. drop down) lists? If you’re like me, I don’t always remember the proper Rails syntax, or I want to add a default nil option to the list, etc. They always just feel awkward to me. Here’s a typical select field you might find in one of my forms. It’s simply a lookup to choose a category:

f.select :category_id, [['--', nil]] + Category.find(:all).collect { |m| [m.name, m.id] }

Yikes! That’s sort of nasty to have that right in your views. At least, to me it is. So I wrote a plugin that assists with this “lookup table” issue. I’ve so cleverly called it, acts_as_lookup.

How it Works

Once installed, you need to tell your model(s) that they should be acting as a lookup table. This is pretty straightforward, though:

class Category < ActiveRecord::Base
  acts_as_lookup :name
end

You have to pass it the field/column name you want to show up in your drop down list. So in the example above, I want to be able to choose from a list of category names, hence the :name parameter.

From there, you get two things: 1) a lookup_for helper (used in your views) and 2) an options_for_select class method for any model that is “acting as a lookup table”. Here’s how you use both:

Using options_for_select

This is a clean way to get rid of that collect nonsense I showed previously. So, redoing my above example using this plugin (and using options_for_select), we have:

f.select :category_id, Category.options_for_select

That’s much cleaner. And it automatically adds the nil ”- -” option as the default in your list. Arguably, though, there’s even a better way: the lookup_for helper.

Using lookup_for

The lookup_for helper automatically generates the above, but it’s a little cleaner:

lookup_for :product, :category_id

And if you’re using a FormBuilder (read: “f.” in front of your form helpers), it’s even better. You only have to give it the foreign key to the lookup:

f.lookup_for :category_id

Viola! The above example(s) will automagically generate something similar to (of course depending on the real data):

<select>
  <option value="" selected="selected">--</option>
  <option value="1">Appliances</option>
  <option value="2">Furniture</option>
</select>

One thing to note, though: the foreign key must follow the traditional Rails convention for foreign keys. Meaning, for a Category, you’d have category_id foreign key reference; for a Task, you’d have task_id foreign key reference; and so on. The reason is because the plugin uses that foreign key to get at the Model in which it represents. But since that’s pretty standard to most Rails developers, I don’t think it bears too much constraint :-)

Customization

Now, you may be asking “what if I don’t want ’- -’ as my default option?” or “what if I don’t want every category to show up?” or “what if I have a category order, other than alphabetical?”. Not a problem. This plugin supports that sort of customization.

Changing the Default Text

class Category < ActiveRecord::Base
  acts_as_lookup :name, :default_text => "- Choose Category -" 
end

And if you don’t want a default nil option at all, just set the :default_text option to :first (meaning, the first option of your data returned):

class Category < ActiveRecord::Base
  acts_as_lookup :name, :default_text => :first
end

Now there will be no default option in your drop down (in this example, it would be set to the first category of the collection instead).

Limiting the Options

# only show categories from within the last 4 months
class Category < ActiveRecord::Base
  acts_as_lookup :name, :conditions => ["created_at > ?", 4.months.ago]
end

Displaying in a Different Order

By default, the plugin will sort your options alphabetically, but you can change that, too:

class Category < ActiveRecord::Base
  acts_as_lookup :name, :order => 'category_order ASC'
end

And of course, you can combine the options and pass all three at once, I’ve just shown them separately to emphasize each point.

Install via Git

You can find this plugin on GitHub, and it can be installed by doing:

$> cd path/to/your/rails/project/vendor/plugins
$> git clone git://github.com/rpheath/acts_as_lookup.git

Remember, if you don’t have Git installed, you can download a tarball. Just put the source in your vendor/plugins directory.

Tip 4: Automatic Fading Flash Messages

This next tip is rather minor, but I think it adds a nice touch, and I seem to use it in all of my Rails applications. Basically, the idea is to show a flash message for a predetermined amount of time, then slowly fade the message out. (I personally think it’s tacky to leave it there, waiting on the user to navigate away from that area)

So, using a tip from a prior Railscast, we put this somewhere in our layout:

<% flash.each do |key, msg| -%>
  <%= content_tag :p, msg, :id => key -%>
<% end -%>

This will create a <p> tag with an id of the flash type. Meaning, flash[:notice] = '...' will give <p id="notice">...</p> and flash[:error] = '...' will give <p id="error">...</p> and so on. Now, what we want to do is fade this <p> element out. We’ll use JavaScript to do this, and we’ll want to put that JavaScript in public/javascripts/application.js of our main Rails project root.

Fading Messages using jQuery

$(document).ready(function() {
  setTimeout(hideFlashMessages, 10000);
});

function hideFlashMessages() {
  $('p#notice, p#warning, p#error').fadeOut(5000)
}

Fading Messages using Prototype/Scriptaculous

document.observe("dom:loaded", function() {
  setTimeout(hideFlashMessages, 10000);
});

function hideFlashMessages() {
  $$('p#notice, p#warning, p#error').each(function(e) { 
    if (e) Effect.Fade(e, { duration: 5.0 });
  });
}

(Note: the above examples were based on jQuery 1.2.3 and Prototype 1.6)

The above functions will allow the message to be shown for 10 seconds (10000 milliseconds) in full, then begin to fade the message out over a 5 second duration. Of course, just change the values to fit your needs/desires.

Tip 5: Interfacing with SOAP Web Services

This tip might actually target a small niche of Rails developers, but it’s something that I couldn’t find too much about when “Googling” for some help. So I eventually just came up with my own solution. It deals with creating an interface for legacy .NET SOAP web services. Rather than go into the explanation, I’m going to provide an example skeleton class that you can tailor to your needs:

require 'digest/sha1'
require 'soap/wsdlDriver'

module WebServices
  class SOAPInterface
    attr_accessor :endpoint, :service

    def initialize(endpoint='www.yoursite.com', service='YourWebServices', options={})
      @endpoint = endpoint
      @service  = service
    end

    # --
    # your methods go here
    # -- 

    private
      def wsdl
        SOAP::WSDLDriverFactory.new("http://#{@endpoint}/services/#{@service}.asmx?WSDL")    
      end    
  end

  # pulls out the diffgram of SOAP::Mapping::Object's into an array
  class DataSetParser
    def initialize(soap_response, data_set)
      @response = soap_response
      @data_set = data_set
    end

    # allows for @obj['CourseListing'] to be @obj.course_listing
    def method_missing(name, *args)
      @response.send(:diffgram).send(@data_set.to_sym).send(:[], name.to_s.camelize)
    end
  end
end

We’ll talk about about what goes in “your methods go here” in just a second. I wanted to mention what the DataSetParser is all about. Firstly, it’s specific to the .NET framework, so if you’re wanting to use this for SOAP services coming from something other than .NET, you can wipe out the DataSetParser class entirely. Now, onto what it does.

.NET returns it’s data collections as a DataSet, which then gets wrapped in a DiffGram. I know, goofy. From the Microsoft site:

A DiffGram is an XML format that is used to identify current and original versions of data elements. The DataSet uses the DiffGram format to load and persist its contents, and to serialize its contents for transport across a network connection.

What does that mean to us? It means that it’s another level of tedious parsing that we need to deal with when getting a SOAP response. If your API is returning a DataSet, then you can pass the result to the DataSetParser class, and it will return the collection as an array of SOAP::Mapping::Object. It also will allow you to access the data via an underscored method based on the camelized name of the DataSet returned. If that’s confusing, see the comment above the method_missing definition in the DataSetParser class.

Now, here’s an example of how you might use this skeleton module. Here, we’ll be replacing the “your methods go here” comment.

# returns a user in the form of a SOAP object
def authenticate(username, password)
  soap = wsdl.create_rpc_driver
  response = soap.Authenticate(:username => username, :password => password)
  result = response.authenticateResult
  soap.reset_stream
  DataSetParser.new(result, 'user').user
rescue
  # do your graceful error catching/logging stuff
end

This would allow you to do something like:

$> api = WebServices::SOAPInterface.new('www.mysite.com', 'MyWebServices')
$> api.authenticate('login', 'password')
$> # => <#SOAP::Mapping::Object ... >

The Authenticate and authenticateResult is dependent on the WSDL of the SOAP service, but the rest is pretty close to what each of your methods might look like. For more info, you can visit an earlier post of mine which discusses the same issues, and provides a more Rails-esque example.

Again, I realize that this is for a rather niche group of Rails developers, but like I said, I had a hard time finding any information on this, so maybe someone else will find my solution to this problem useful.

Conclusion

That concludes my 5 tips related to Rails. Hopefully they’ve proven to be worthwhile. Let me know if you have any trouble implementing any of these tips, as I’d be glad to help you make use of them. Thanks for reading my first novel :-)

Hulu: Online Movies and TV Shows

I just came across an awesome site for streaming TV shows and movies. It’s called Hulu. Apparently it’s a site supported (or built?) by NBC/Fox, so the videos are all of incredible quality. I came across it while looking at a list of high-traffic Rails applications (and it’s one of them).

So if you’re looking for another way to be distracted, check out Hulu. Here’s one to get you started: 2008 Tostitos Fiesta Bowl.

2008 by Ryan Heath | Get In Touch

flickr

DesolateAlong the fenceBlurredDartingOne rose leftFarm entry