[Skip To Content]


# DBMS-specific queries with Rails

Have you ever wanted to run queries which are specific to the type of database you’re connected to in Rails? Well, here’s one way of doing it :

  1. class Random < ActiveRecord::Base
  2. def do_stuff
  3. case connection.class
  4. when ActiveRecord::ConnectionAdapters::MysqlAdapter
  5. find( :all, :order => "RAND()" )
  6. when ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
  7. find( :all, :order => "RANDOM()" )
  8. else
  9. raise "Merrrrh."
  10. end
  11. end
  12. end

Is there a better way? Answers on a postcard …

Update : This broke in either Rails 2.0 or 2.1. Rails’ new behaviour is to only load the connection adapters you’re actually using, so you’ll need to do something like this instead :

  1. if ActiveRecord::ConnectionAdapters.constants.include? MysqlAdapter
  2. ...
  3. else ...
  4. ...
  5. end

# ActiveRecord Gets Some Love

If you’ve ever tried to extend ActiveRecord in a way that impacts its handling of columns you probably just gave up and didn’t bother.

Hopefully the first in a series of patches to sort this out, we have Changeset 7315 – AR no longer repeatedly parses the actual column data when you use it, so you should see some performance benefit especially if what you’re doing is date-heavy.

Further requests, if anyone is listening :

  • When you update a model, only issue an UPDATE which updates the columns that have changed. This would stop breakage when a table has a column AR doesn’t support (As well as possibly giving some performance benefit.)
  • Make it easy to add support for column types that AR doesn’t support out of the box.

UPDATE: Seems like someone is working on exactly this – Read here.

Railscasts

Three times a week Ryan Bates will host a new Railscasts episode featuring tips and tricks with Ruby on Rails. These screencasts are short and focus on one technique so you can quickly move on to applying it to your own project.

Fantastic stuff.

# extract_options!

The (old) “standard” way of extracting an optional options hash passed as the last parameter of a method call :

  1. def add_food( *args )
  2. options = args.last.is_a?( Hash ) ? args.pop : {}
  3. puts args.inspect
  4. puts options.inspect
  5. end
  6. add_food( "cheese", :edible => true, :drinkable => false )
  7. => "cheese"
  8. => {:edible=>true, :drinkable=>false }

This is something I’m seemingly doing more and more, and let’s face it – It’s ugly.

However, as of changeset 7217 (and the original ticket) of Rails, a method has been added to Array encapulate this. You can now do :

  1. require 'active_support'
  2. def add_food( *args )
  3. options = args.extract_options!
  4. puts args.inspect
  5. puts options.inspect
  6. end
  7. add_food( "cheese", :edible => true, :drinkable => false )
  8. => "cheese"
  9. => {:edible=>true, :drinkable=>false}

Such a simple addition, it’s hard to believe there wasn’t already a method to do this.

Update: And yes, I realise my examples above are crap – Hopefully you can see what’s happening though.

# migration generation

The Hobo Migration Generator isn’t quite dont_fucking_repeat_yourself, but it’s not at all shabby. It allows you to maintain your database structure within your model and generate migrations based on the changes you make to those definitions. It also picks up the addition and removal of associations in the models, and does the right thing there too.

Shame it’s not a separate plugin, and doesn’t try to deal with testing. I’ll definitely be following its progress though.

Thanks to Shalev for pointing me at Hobo.

# dont_fucking_repeat_yourself

  1. table :user, options => "TYPE=InnoDB" do |t|
  2. # Adds has_many :tasks to the model, and adds an index on the target column
  3. # in the associated table (tasks.user_id, in this case.)
  4. t.has_many :tasks
  5. # Create a VARCHAR(50) NOT NULL column, validates_length_of :login,
  6. # :in => 5..50, validates_presence_of :login, validates_uniqueness_of
  7. # :login and a unique index for the column.
  8. t.string :login, :limit => 5..50, :null => false, :unique => true
  9. # Create a VARCHAR(40) NOT NULL column ready for a SHA1 hash of the
  10. # :password field. Also adds validates_confirmation_of :password, and an
  11. # accessor for :password_confirmation. (:confirmation isn't limited to
  12. # passwords, although it's the main use of it.)
  13. t.hash :password, :limit => 4..16, :null => false, :confirmation => true
  14. # Create a DATETIME NOT NULL column for created_at
  15. t.created_at
  16. end
  17. table :task, :options => "TYPE=InnoDB" do |t|
  18. # Adds belongs_to :user, :allow_nil => true to the model. Also adds an
  19. # user_id INTEGER column, and creates an index for it.
  20. t.belongs_to :user, :allow_nil => true
  21. # Adds a VARCHAR(128) NOT NULL column, validates_length_of :title, :in
  22. # => 1..128, validates_presence_of :title, and an index for the column.
  23. t.string :title, :limit => 1..128, :null => false, :index => true
  24. # Adds a DATETIME column, no validation.
  25. t.datetime :due
  26. # Adds a BOOLEAN NOT NULL DEFAULT FALSE column. Doesn't add a
  27. # validates_presence_of validation.
  28. t.boolean :done, :default => false, :null => false
  29. # Create DATETIME NOT NULL columns for created_at and updated_at
  30. t.timestamps
  31. # Create a unique index for the two columns due and done.
  32. t.index :due, :done, :unique => true
  33. # Create an index for the two columns user_id and title.
  34. t.index :user_id, :title
  35. end

So, you’re a Rails developer? CHECK! You have this great idea for a new application, the one which will surely get Yahoo and Google involved in a bidding war? CHECK! But you don’t actually start developing it because you are fed up having to run script/generate model model_name, editing the migration for the table, writing the tests for all the rules which are implied by the database (Such as maximum lengths for columns and column uniqueness), and then editing the model to pass those tests? CHECK!

To solve this problem, you can see the proposed syntax for my (tentatively titled) dont_fucking_repeat_yourself plugin. After creating a file much in the manner of that above, you simply run the (tentatively titled) i_am_bored_of_repeating_myself rake task and all your models, migrations and tests are generated.

It will also allow you to add new models during development, so no longer will you think “No, I won’t add that feature because it requires a new table and I can’t be arsed doing the create-model-write-migration-write-tests-edit-model dance.”

Obviously this only helps with the initial creation of models and doesn’t attempt to help you when you want to change your title column to hold more than 128 characters, but man, it should really speed up those early days of development.

If anyone has any comments let me know, a link to my email is at the bottom of this page. Hell, if anyone reads this at all, it’ll be good to hear from you.

(And if anyone is offended by the swearing, I’m sorry, but DHH started it.)

# dead_good_caching

Say you have some pages which are publicly accessible, but have some admin-only links on them when someone is logged into the site. Also, let’s pretend that you’re going to be Slashdotted any day now, and you want to cache the pages, and you quite fancy using Rails’ page caching because it’s nice and easy to use.

“But”, I hear you cry, “if you use caches_page in your controller, it’ll cache the page regardless! You’ll cache your admin links! Everyone will be able to see that you have ajaxy editing on your blog!”

NO! NOT ANY MORE! As of this afternoon, that’s not the case! Read on!

Stick this in lib/dead_good_caching.rb :

  1. module ActionController
  2. module Caching
  3. module Pages
  4. module ClassMethods
  5. alias_method :rubbish_caches_page, :caches_page
  6. def caches_page( *actions )
  7. return unless perform_caching
  8. if_proc = actions.last.is_a?( Hash ) ? actions.pop[ :if ] : nil
  9. actions.each do |action|
  10. class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' and ( not if_proc or ( if_proc and if_proc.call( c ) ) ) }"
  11. end
  12. end
  13. end
  14. end
  15. end
  16. end

Then, in your config/environment.rb file, somewhere near the bottom :

  1. require 'dead_good_caching'

Then, in your controllers, you can do something like :

  1. caches_page :list, :tag, :show, :if => Proc.new { |controller|
  2. not controller.logged_in?
  3. }

The above won’t cache your last, tag and show actions if you’re logged in! Good eh? If don’t want to do any checking, you can still use caches_page as God^WDHH intended.

“But”, I hear you cry louder, “if a non-logged in user visits the page, Rails will write it out to disk, and because you’ve configured Apache to not bother Rails if a file exists on disk, you, as an admin, will get served that cached page!”

Ah ha! I have to admit, it took a few minutes to figure out a workaround for this. But, assuming you have the ability to mod_rewrite on your server and that the cookie’s name which is set when you login is your_cool_cookie_name, you just need to add something like :

  1. RewriteRule ^/$ /index [QSA]
  2. RewriteCond %{HTTP_COOKIE} !(your_cool_cookie_name=(.*?))
  3. RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}.html -f
  4. RewriteRule ^(.*)$ $1.html [QSA,L]

to your vhost or .htaccess and you’ll be set. Unless I’ve messed up the rules, which I may have done.

As (almost) always, the above code is public domain.

(Update : My procrastination has led to someone else submitting a patch for Rails Core and getting it accepted. See here and here. The upshot is that in Rails 2.1, you won’t need the above hack.)

# migrations get better

  1. create_table :products do |t|
  2. t.integer :shop_id, :creator_id
  3. t.string :name, :value, :default => "Untitled"
  4. t.timestamps
  5. end

See the changeset.

# render++

  1. render :xml => post.to_xml, :status => :created, :location => post_url(post)

Render gets improved.

  1. render :xml => post, :status => :created, :location => post_url(post)

Render gets further improved.

# rake tutorial

A very good Rake tutorial. Rake really isn’t complicated, and this tutorial covers pretty much everything you need to know in a very readable way.

More about this site...

Last Week’s Top 5 Albums (More »)

  1. The LumineersThe Lumineers
  2. Young the GiantYoung the Giant
  3. xxThe xx
  4. Give UpThe Postal Service
  5. Tourist HistoryTwo Door Cinema Club