We’ve been playing around with the Sphinx full-text search engine and Ultrasphinx, the Ruby on Rails configurator and client to the Sphinx full text search engine. Sadly, it was giving us a warning about spell checking:

ultrasphinx: spelling support not available (raspell configuration raised "uninitialized constant Ultrasphinx::Spell::Aspell")

This is pretty easy to get rid of, though. You just need to install the aspell, spell checking library and raspell, the Ruby interface to aspell. The raspell README has instructions for installing aspell and raspell on both Mac and Ubuntu.

After we did this, we got one additional error:

ultrasphinx: spelling support not available (raspell configuration raised "No word lists can be found for the language "ap".")

Follow the instructions for setting up the custom wordlist needed by Ultrasphinx and you should be good to go.

Good luck and happy searching!

Error after upgrading SliceHost

September 23rd, 2008

If you happen to see this error after upgrading your SliceHost account, be calm.

ActionView::TemplateError (Define INLINEDIR or HOME in your environment and try again)

The simple solution is to add an environment declaration to your environment/production.rb file:

ENV['INLINEDIR'] = '/path/to/.ruby_inline'

Once that was set, we were in business again… whew!

References

Here’s a relevant reference that took a while to find:

Quick Tip: Rails 2.1 Time Zones

September 13th, 2008

My last quick tip involved setting your time zone in Ubuntu Hardy, so now, how do you set your time zone in a Ruby on Rails application? Rails 2.1 makes it much easier to manage time zone settings than it was previously.

Add the following to your environment configuration file:

config/environment.rb

config.time_zone = 'Arizona'

Replace Arizona with your own time zone. You can find a list of valid values by running any of the following rake tasks:

rake time:zones:all
rake time:zones:local
rake time:zones:us

Your data will still be stored in UTC time, but it will be converted into the specified time zone when it is type cast on retrieval.

Update: If you set both your server’s time zone and your applications time zone then you may see some incorrect times. I believe this is because you’ll be storing local times and the Rails app will be trying to convert them to local – a double conversion. So be careful.

References

Nginx 405 Not Allowed Error

September 5th, 2008

405 Not Allowed

So, I got this error today and it took me a few minutes to track down why. Seems like the kind of thing that might be interesting to people. I’m using nginx on a project and this error was being thrown by nginx, not by Rails. What could be the cause?

Nginx configuration and REST

Do you have something in your nginx config file that looks like this?

if (-f $request_filename) {
  break;
}

Basically, render an existing, static file directly, aka. bypass Rails. This is great, we don’t incur the overhead of hitting Rails for static files, including cached files, right? Wrong.

You see, with REST, your URL will be the same for a POST to create as it will for a GET to index. For example, viewing the users index and creating a user:

GET  /users <= user's index
POST /users <= create a user

See the problem?

That’s right, if you cache the user’s index using a technique like caches_page then you have a static file (e.g., users.html) that matches regardless of the HTTP method. Therefore, it will bypass rails and attempt to serve the static file on a POST request, resulting in a 405 error. Whew!

Caching with nginx

So what do you do? I don’t know. But this is what I did and I hope it helps. Add the following to your nginx config prior to the serving of static files.

if ($request_method != GET) {
  proxy_pass http://foobar;
  break;
}

Uh, where foobar is the name you specified in the upstream declaration in the nginx config. This should pass any non-GET request to Rails immediately since we don’t want to statically serve files for anything other than GET.

References

Quick Tip: Form Partials

September 1st, 2008

Partials are a great way to keep your view code separated logically. Prior to Rails 2.1 if you wanted to reuse a form partial in, for example, a new and edit view, then you needed to pass the form into the partial somehow.

Given the following form partial:

views/users/_form.html.erb

<div>
  <%= form.label :name -%>
  <%= form.text_field :name -%>
</div>

Pre-Rails 2.1

You might consider passing the form in as a local, like so.

views/users/new.html.erb or views/users/edit.html.erb

<% form_for @user do |form| -%>
  <%= render :partial => 'form', :locals => { :form => form } -%>
<% end -%>

Rails 2.1

There’s now a shortcut for this common method of rendering a form partial.

views/users/new.html.erb or views/users/edit.html.erb

<% form_for @user do |form| -%>
  <%= render :partial => form -%>
<% end -%>

Nice! Cleans things up a bit.

Quick Tip: named_scope

August 31st, 2008

Have you ever found yourself writing queries like this?

User.find(:all, :conditions => ['state = ? AND created_at > ? AND created_at <= ?', 'active', start_date, end_date], :limit => 5)

I suppose you could refactor this into a custom finder that did the heavy lifting for you…

User.find_all_active_in_date_range(start_date, end_date)

But what if you need that same query where the state is ‘pending’? Create another custom finder? Modify it so it takes another parameter for state? Wouldn’t it be nice if you could create some kind of reusable snippet that could be chained together to create a custom finder? Well, look no further. As of Rails 2.1, such a thing exists and it’s called named_scope.

Let’s refactor that code using named_scope.

app/models/user.rb

class User < ActiveRecord::Base
  named_scope :active, :conditions => { :state => 'active' }
  named_scope :between, lambda { |starts, ends| { :conditions => ['created_at > ? AND created_at <= ?', starts, ends] } }
end

The lambda is simply so we can accept parameters into the named scope call. Named scopes are easily chainable, so to get the desired query we can call:

User.active.between(start_date, end_date)

Plus, if we want to add another condition, we just created another reusable named_scope and add it to the end.

app/models/user.rb

class User < ActiveRecord::Base
  named_scope :active, :conditions => { :state => 'active' }
  named_scope :between, lambda { |starts, ends| { :conditions => ['created_at > ? AND created_at <= ?', starts, ends] } }
  named_scope :limit, lambda { |num| { :limit => num } }
end

And we have our original query. This is much more readable and maintainable than the previous code we were looking at. The named scopes can also be reused when another query comes up with those conditions.

User.active.between(start_date, end_date).limit(5)

There’s so much more you can do with named_scope, so check it out.

References

Quick Tip: Route Associations

August 26th, 2008

Are you used to writing your routes like this?

map.resources :notes do |notes|
    notes.resource  :author
    notes.resources :comments
    notes.resources :attachments
  end

Don’t fret, there may be hope for you yet. For these simple routes you can use the has_one or has_many route association options.

  • has_one – use it for a singleton resource
  • has_many – use it for plural resources

Refactored routes

map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]

Give it a try!

Keep in mind I said simple. If you’re doing something more complex they might not be the best choice.

Additional Resources

It’s really easy to create a many-to-many relationship that can be assigned through checkboxes. Check it out!

Let’s say you have Users and Groups. A User can belong to a Group and a Group can have many Users – we call this a Membership, like so (migrations omitted for brevity):

app/models/user.rb

class User < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :groups, :through => :memberships
end

app/models/group.rb

class Group < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :users, :through => :memberships
end

app/models/membership.rb

class Membership < ActiveRecord::Base
  belongs_to :group
  belongs_to :user
end

We can now assign groups to members in a relatively easy manner with no extra work needed in the models. Behold!

app/views/users/edit.html.erb

<h1>User <%= @user.id -%></h1>

<h2>Group Memberships</h2>
<% form_for @user do -%>
  <% Group.all.each do |group| -%>
    <div>
      <%= check_box_tag :group_ids, group.id, @user.groups.include?(group), :name => 'user[group_ids][]' -%>
      <%= label_tag :group_ids, group.id -%>
    </div>
  <% end -%>
  <%= submit_tag -%>
<% end -%>

Errr… something like that. Anyway, the important thing to note is the use of group_ids. The values will get submitted as group_ids, a member of the User. Where did that come from? We don’t have an attribute or method on the model for it, so where’d it come from? Well, seems that it is auto-generated for you to allow something like I just showed.

When this form is submitted, any checked Groups will be associated through Memberships to the User by way of the magic *_ids= method. Should work the other way too with user_ids checkboxes on a group. No extra code needed. Awesome, right?

Bonus: If you uncheck all the checkboxes, then nothing gets posted, doh! So make sure to merge a default value with your parameters like this to ensure the *_ids= method gets called:

app/controllers/users_controller.rb

@user.attributes = {'group_ids' => []}.merge(params[:user] || {})

Super Bonus: When you’re defaulting the group_ids in the controller make sure to use the key as a string, not a symbol. Or if you do use a symbol then make it a Hash with_indifferent_access.

Super Monkey Ball: A monkey encased in a ball who collects bananas.

If you have changed the SSH port number on your server, then you need to let Capistrano know how to connect. Luckily, it’s pretty easy.

Add the following to your deployment file, replacing 8888 with your port number.

config/deploy.rb

ssh_options[:port] = 8888

This will apply that port number to connections made by Capistrano. If you need to specify the port for each server (app, web, db) then tack it on to the end of their declarations.

role :app, "65.74.169.199:8030" 
role :web, "65.74.169.199:8031" 
role :db,  "65.74.169.199:8032", :primary => true

I haven’t verified that one, but it supposedly works. Happy deployments!

Rails Class Collisions

August 23rd, 2008

While trying to use Integrum’s oh-so-nifty missing spec finder on a recent project, I discovered that it was not working. Hmmm… seems that somewhere along the way Rails added detection for existing constants and won’t allow you to re-generate something. Instead it calls raise_class_collision, which raises a UsageError. Damn them.

Anyway, to work around this for now, you can add the following somewhere (e.g., an initializer):

require 'rails_generator/base'
require 'rails_generator/commands'

module Rails
  module Generator
    module Commands
      class Create
        def raise_class_collision(class_name)
          # Do Nothing
        end
      end
    end
  end
end

This should get around that inconvenience. Plus, you won’t be exposed to nefarious suggestions like I was:

$ rake spec:sync
  The name 'User' is either already used in your application or reserved by Ruby on Rails.
  Please choose an alternative and run this generator again.

  Suggestions:  

exploiter
drug user
substance abuser

Thanks Rails, you’re so helpful! I just don’t understand how it could have known I was building a Facebook clone for junkies…

Other Resources