Dynamically add class methods in Rails

I have a Product class which has a small number of records in the db, each of which has a name, eg “millionaire”.  Rather than keep typing Product.find_by_name(“millionaire”) i want to type Product.millionaire.

I could use method_missing for this but that means every time Product gets a method it doesn’t recognise it loads all the products out of the db – not very good.  Instead, let’s use define_method.  Normally define_method adds instance methods to a class but we can add them at the class level like so.  In the product class:

class << self
  Product.find(:all, :select => "name").collect(&:name).each do |name|
    define_method name.to_sym do
      self.find_by_name(name)
    end
  end
end

Now, when the classes are loaded, which happens only at restart in production mode, the methods are created.  No method_missing required and no db access besides the single access at class load.

Gem magic – copying gems from a server to your local repository

I recently upgraded to Ubuntu 9.10, wiping everything, formatting my partition and starting again.  There was the usual sunday full of software installing hassles.  But, i discovered one thing that made my life a ton easier, thanks to Marnen Laibow-Koser on http://ruby-forum.com

My situation (a common one) was that i had several apps deployed on servers, and i wanted my development environment to match that of the servers as closely as possible.  This means having the same gems as the servers, and, crucially, having the same versions of those gems.  Normally this would be a big pain in the ass, hunting around for places to get old out of date gems from.  Not so, however – i ssh onto my server and run

gem server & #the & is just to make it run in the background

This starts a gem server running on port 8808.

Now, back in my local machine, i can type

sudo gem install insert-gem-name-here –remote –source http://mywebsite.com:8808

and, hey presto, i get the version that’s on the server, installed locally.  Even better, you can pass it a list of all the gems you need at once, and go and drink a beer to celebrate the utter wonderfulness of rubygems.  Repeat for every app you’re working on, pointing it at it’s relevant server.  Huzzah!

Thanks Marnen!

BASH howto – find files with required text in an svn managed folder

This is just a bash (linux/mac terminal) one-liner to look for files with matching text in my rails app, which i do all the time.  It’s a lot faster and more useful than Nautilus’ file search, and is useful when you don’t use any IDEs.  Personally i do everything in the command line and gedit, nice and simple.

Here i’m looking for the string “ratings” in my views folder and my public/javascripts folder

This is broken down as follows:

get all files in these folders
| surround them with quotes (otherwise filenames with spaces would screw up the later steps)
| only consider non-svn files (i’m not interested in the files svn uses to manage things)
| look inside the files for the search string

And here it is:

find app/views public/javascripts -print | awk ‘{print “\””$0″\””}’ | grep -v \.svn | xargs grep -i “ratings”

Obviously this is a bit of a mouthful but once it’s been done it will go into your command memory, and you can get it back by pressing ctrl&r, and starting to type ‘find’.  You may need to press ctrl&r a few more times to get the right command from memory.

Note – wordpress or your browser may change some of the above double quotes to weird web quote characters.  I really fracking hate that.  Anyway, when you paste it over, change all the quotes to normal single or double quotes.

Distinguish between local production mode and server production mode in Rails

I just ran into a problem with production mode. Normally i never run this locally but i decided to use it to run my selenium test battery against, since it’s much faster than dev mode. In production mode on our server we pull our assets in from an amazon asset server, done with this line in production.rb

config.action_controller.asset_host = “http://assets.fakedomain.ir.com&#8221;

In my local version of production i don’t want to do this though, i just want to use what’s in my local public folder. I decided to have a look at ENV which i did by making apache explode :) prior to the above line –

raise “ENV = #{ENV.inspect}”

Quick restart of my local apache, reload the page, and BANG, i can see this:

ENV = {“APACHE_PID_FILE”=>”/var/run/apache2.pid”, “PATH”=>”/usr/local/bin:/usr/bin:/bin”, “LANG”=>”C”, “APACHE_RUN_GROUP”=>”www-data”, “APACHE_RUN_USER”=>”www-data”, “PWD”=>”/etc/apache2/sites-available”, “RAILS_ENV”=>”production”, “HOME”=>”/home/max”, “RAILS_ASSET_ID”=>””}

aha – “HOME”=>”/home/max” – perfect!

So, i changed the line (and removed the raise!) to

config.action_controller.asset_host = “http://assets.fakedomain.ir.charanga.com&#8221; unless ENV['HOME'] == “/home/max”

Bingo. Perhaps not the cleverest way of doing it, but it’s safe – for everyone else, including our server, ENV['HOME'] will be something else, or perhaps nil. Either way, they’ll use the remote assets site.

Stubbing methods (eg callbacks) in a Machinist blueprint

I had a problem recently where i was using the test object-generating plugin Machinist to make a blueprint for one of my model classes. The model has a before_create filter which references some other data in the database, which didn’t exist in my test version of the db. This before_create filter had nothing to do with the tests i was running and i wanted to just stub it out. I couldn’t work out how, so wrote a wrapper method that looked like this:

def make_music_service(attributes = {})
  music_service = MusicService.make_unsaved(attributes)
  music_service.stub!(:set_default_column_customisations).and_return(true)
  music_service.save
  music_service
end

That meant that whenever i wanted to make a MusicService in my tests i had to call this method instead.  Pretty clumsy, eh.  But – Machinist creator Pete Yandell came through with a great bit of info on the machinist mailing list – when you’re inside your blueprint, you can reference the object being made with ‘object’.  So, now i can put the stub inside the blueprint, like so:

MusicService.blueprint do
  name     { Faker::Name.name }
  address1 { Faker::Address.street_address }
  address2 { Faker::Address.street_address }
  address3 { Faker::Address.city }
  postcode { Faker::Address.uk_postcode }
  default_password { "password" }
  object.stub!(:set_default_column_customisations).and_return(true)
end

Thanks Pete!

Firebug & jQuery – making other people’s sites better

Was just on a website that had 80 checkboxes (all checked) for various options. I wanted to have only one checked. There’s no ‘Uncheck all’ button. Design Fail! But – open the firebug console and run

jQuery(“input[type='checkbox']“).attr(‘checked’,false)

Bing, all unchecked. I get a childlike sense of wonder and joy from that, still. If only more things in life could be improved with a little console activity.

jQuery(“#pub.crap”).attr(“ale-count”, 20)
:D

Gotcha with hand-rolled sql in rails associations – eg self.id and has_many

Sometimes in rails associations (eg has_many) you can’t quite do what you want with the existing options and have to do a bit of hand-rolled sql. And, often you want to refer to the object that the association will be called on. BUT – the following doesn’t work. (i’ve used a trivial example here, we wouldn’t normally hand-roll in this case)

has_many :foos, :finder_sql => "select * from foos where bar_id = #{self.id}"

The reason this doesn’t work is that the string has double quotes, which means that #{self.id} will be evaluated when the string is evaluated, which, because this is a class method, is at class load time! That means that self.id will come out as the id of the class – not what we want.

To get around this, use single quotes instead:

has_many :foos, :finder_sql => 'select * from foos where bar_id = #{self.id}'

With single quotes, the #{} block isn’t evaluated and sits there, waiting for the association to be called. When that happens, the finder_sql is combined, and the eval block evaluated, and at that point self IS the current instance, and the association will work. Bear in mind that, because we’re now using single quotes to wrap the string, any single quotes you might have in your sql will now need to be escaped:

has_many :foos, :finder_sql => 'select * from foos where flavour = \'chicken\' and bar_id =#{self.id}'