December 17th, 2007
Rails 2.0.2 was tagged not too long ago and will hit the gem servers soon I expect. Being a minor point release there aren’t many changes, but here’re the ones I feel are worth mentioning:
ActionPack changes
Configurable asset hosts: You can now pass a proc to ActionController::Base.asset_host. This allows you to get by the hard-coded assumption that you have 4 asset hosts numbered 0-3 when using the ‘%d’ wildcard (introduced in Rails 2.0.1).
I contributed this patch so I’m gonna be lazy and copy and paste most of the documentation I wrote for patch.
The example proc below generates http://assets1.example.com and http://assets2.example.com randomly.
ActionController::Base.asset_host = Proc.new { |source| "http://assets#{rand(2) + 1}.example.com" }
image_tag("rails.png")
=> <img src="http://assets2.example.com/images/rails.png" alt="Rails" />
stylesheet_include_tag("application")
=> <link href="http://assets1.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
The proc also takes a single source parameter which is the path of the source asset. This can be used to generate a particular asset host depending on the asset path. This is useful if you have, say, images hosted elsewhere, or you want an SEO-friendly URL for your images:
ActionController::Base.asset_host = Proc.new { |source|
if source.starts_with?('/images')
"http://images.example.com"
else
"http://assets.example.com"
end
}
image_tag("rails.png")
=> <img src="http://images.example.com/images/rails.png" alt="Rails" />
stylesheet_include_tag("application")
=> <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
Asset cache directories are automatically created: In Rails 2.0.1, if you tried to cache your assets (javascripts and stylesheets) in a subdirectory (example below), you either need to check it into your source control or have your deployment tool (Capistrano, etc.) create it for you. Josh Peek contributed a patch that created the subdirectory instead so you can do something like:
javascript_include_tag(:all, :cache => "cache/money")
and not have to bother about creating the ‘cache’ subdirectory.
Railties changes
Default database is now SQLite3: The default database is now SQLite3 instead of MySQL. Running ‘rails takeover_the_world_app’ will now come with a database.yml that’s setup for SQLite3. To get the old behavior (where it’s preconfigured for MySQL), run rails -d mysql takeover_the_world_app instead. This is mostly to make this “just work” on Mac OS X Leopard, where SQLite3 is already installed with the OS.
Template loading is faster: DHH has turned on ActionView::Base.cache_template_loading by default in the production.rb environment config file that basically means that Rails no longer does file system stat calls when loading templates. The downside is that you have to restart your Rails application to see templates changes in production mode, but it’s not really that big an issue since no one should be editing templates anyway ‘live’ in a production application.
New rake tasks for migrations: rake db:migrate:redo to undo your last migration and re-run it - very useful when developing migrations (when you screw up, that is). rake db:migrate:reset drops the database, re-creates it, and then runs all migrations.
rake task for generating secret keys: rake secret to generate a secure key that you can use for cookie sessions. This is useful for updating Rails applications from 1.x to 2.x, which uses cookie-based sessions by default and requires a secret key.
config.action_controller.session = {
:session_key => '_your_app_session',
:secret => 'some super long string that you can generate with the rake secret task'
}
Personally I just use super long quotes from Family Guy.
That’s it! For the curious, expect Ruby 1.9 compatibility, improved caching, and big ActionPack and rendering refactoring changes in Rails 2.1.
December 16th, 2007
Doing GROUP BYs in ActiveRecord has always been tricky. There’s a lot of “magic” in ActiveRecord and room for untested edge cases. Thankfully, Rails also has a squadron of eagle-eyed contributors who are on hand to fix unforeseen errors.
For example, if you wanted to do this:
class Click < ActiveRecord::Base
belongs_to :site # site_id foreign key is a String, e.g. 'www.rubyonrails.org'
end
# Now, try counting the number of clicks, grouped by site.
Click.count(:all, :group => :site)
# => [[nil, 1], [nil, 3]]
This is a surprisingly common thing to do especially when you are producing reports or statistics of any kind and unfortunately decided to go with a non-(auto)numeric foreign key. As you may have noticed, the sites were returned as nil which is totally unhelpful. Thankfully, this has been fixed in Rails 2.0:
Click.count(:all, :group => :site)
# => [[nil, 1], ['www.site, 3]]
GROUP BY and :foreign_key don’t mix
This change is NOT in Rails 2.0.
If you wanted to do GROUP BYs in your ActiveRecord models (via the :group option), you had to take note of one particular gotcha. For example, with an ActiveRecord model like this:
class Anime < ActiveRecord::Base
belongs_to :japanese_studio, :class_name => 'Studio', :foreign_key => 'studio_id'
end
Note that we specified the :foreign_key option since it can’t be inferred from the association name of ‘japanese_studio’. If we wanted to counting the number of anime grouped by studio though, we’re in big trouble:
Anime.count(:all, :group => :japanese_studio)
# ActiveRecord fails and mumbles about an unknown "japanese_studio_id" key.
Basically what happened is Rails did not use the specified foreign key column of ’studio_id’, inferring it instead from the association name of ‘japanese_studio’. This is not yet fixed but there’s already a patch ready for it at the Rails issue tracker. In fact, I’m gonna verify it right after posting this.
About the contributor, Kamal Fariz
I thought it was time to cover Rails 2.0 contributions by someone closer to home (”home” being my home, Singapore) so I singled out Kamal Fariz (WorkingWithRails profile), a Rails developer working in Malaysia for a Rails shop. Kamal’s also a former Rails Hackfest winner (read the post-Rails Hackfest interview). Kamal is also an active member of the Malaysia Ruby Brigade (I guess it’s not called a “Ruby User Group” since “RUG” sounds rather unglamorous).
Wow look at the number of parentheses I used in the last paragraph. For no reason whatsoever here’s a lolcats picture:
December 16th, 2007
Today’s (un)feature covers the oft-abused and misunderstood with_scope method. The excellent err.the_blog had a post on how you should be using with_scope (yes, only use it in your ActiveRecord models, not in your controllers or filters). Yes that blog post may have been written a long time back but it’s still applicable to Rails 2.0.
With Rails 2.0, the with_scope method is now protected, meaning that you can no longer use it in your controllers (unless you use the #send(:with_scope), or #send!(:with_scope) in Ruby 1.9). I’m not even going to show you how you could use it in your controllers since you really shouldn’t (unless you know what you are doing).
Here’s what with_scope allows you to do:
class SiteOwner < ActiveRecord::Base
# Returns list of distinct active sites.
def active_sites(*args)
with_scope :find => { :select => 'DISTINCT(site)', :conditions => { :active => true } } do
find(*args).collect(&:site)
end
end
end
And even better example, stolen from the err.the_blog post mentioned above, is re-using scopes:
class Movie < ActiveRecord::Base
def self.find_playing(*args)
with_playing do
find(*args)
end
end
def self.find_playing_by_id
with_playing do
find_by_id(*args)
end
end
def self.with_playing
with_scope :find => { :conditions => [ state = ? AND visible = ?, NOW_PLAYING, true ] } do
yield
end
end
end
I tend to use that a lot combined with method_missing magic (also from the err.the_blog post) to allow me to do things like Movie.find_playing_by_name:
def self.method_missing(method, *args, &block)
if method.to_s =~ /^find_(all_)?playing_by/
with_playing do
super(method.to_s.sub('playing_', ''), *args, &block)
end
else
super(method, *args, &block)
end
end
If you prefer to get these for free, check out the the Rails scope-out plugin implements many of the ideas in the err.the_blog post.
About the contributor, Josh Peek
Josh Peek (WorkingWithRails profile) was the man behind the “protection” of with_scope. This is not Josh’s greatest contribution to Rails, of course - Josh has been a very productive and active Rails contributor since the very beginning. A winner of one of the monthly Rails Hackfest, you can read up on the ever-elusive (I say “elusive” because he doesn’t have a blog - it’s just patch after patch after patch) Josh Peek in this post-Rails Hackfest interview.
Josh is also the man behind the deprecation and “plugin-izing” of dynamic scaffolding and pagination (yes, those are gone from Rails 2.0) among a great number of other patches and test coverage improvements. Right now, Josh appears to be working on refactoring ActionView.
Outro
Apologies for the late-on-arrival Rails 2.0 a feature a day posts, but such is life. Feature #5 follows immediately after :)
December 12th, 2007
I don’t think I need to explain how concatenating your 5 stylesheets and 12 JavaScript files into single files each makes your webpages load faster. Unless you’re using multiple asset hosts (Rails 2 allows for multiple asset hosts), then it becomes tricky to say for sure which method is better, but I digress.
There’re a bunch of really useful plugins that help you bundle your JS and CSS files, most of them offering minifying/packing of JS and stripping of comments and whitespace in CSS as well. I was a big fan (still love it) of Asset Packager, but newer plugins like PackR and Bundle-fu work great too.
With Rails 2.0 though, unless you want the minifying/packing/comment-stripping features, you can do away with installing a packager plugin. Remember your trusty javascript_include_tag and stylesheet_link_tag? They now accept a :cache option:
# :cache => true creates a cached javascript named all.js.
<%= javascript_include_tag :all, :cache => true %>
# This becomes:
# <script type="text/javascript" src="/javascripts/all.js"></script>
# You can name your javascript too, by passing a name instead of true.
<%= javascript_include_tag 'jquery', 'swfobject', 'application', :cache => 'base' %>
# This becomes:
# <script type="text/javascript" src="/javascripts/base.js"></script>
# Same deal for stylesheets...
# :cache => true creates a stylesheet named all.css.
<%= stylesheet_link_tag :all, :cache => true %>
# This becomes:
# <link href="/stylesheets/all.css" media="screen" rel="Stylesheet" type="text/css" />
# You can name your stylesheet like for javascript_include_tag.
<%= stylesheet_link_tag 'yui/reset-fonts-grids', 'application', :cache => 'styles' %>
# This becomes:
# <link href="/stylesheets/styles.css" media="screen" rel="Stylesheet" type="text/css" />
Of course, this only takes place if caching is turned on (ActionController::Base.perform_caching), which is on by default in the production environment and off in the development environment.
For those who want to look at some code, check out the changeset.
About the contributor, DHH
Well, DHH hardly needs any mention. David has_one Wikipedia entry. Nice little touch on this new feature though, I love the convenience.
I promise, like I’d said before, to cover contributions by someone less “mainstream” in the next Rails 2 “feature a day” blog post.
December 12th, 2007
Google Chart API looks quite useful. I’ve used Gruff to generate graphs before but this looks way easier (and much lighter on the server without all that ImageMagick magic!) I’ll have to play with it someday - if only I didn’t have to mess with those URL params!
Well, Deepak Jois from singapore.rb (the local Ruby user group) recently announced a Ruby wrapper around the Google Chart API: gchartrb (rdocs). You can get it as a rubygem, just run:
sudo gem install gchartrb
Looking good so far and I dig the familiarity - Deepak says the API is Gruff-inspired and it tells. If you need a charting solution (for reporting or whatever), this is a gem to consider.
Ah yup, there’ll be no Rails 2 feature today - just submitted a couple of Ruby 1.9 compatibility patches for Rails and feeling a little bummed out now (dependency handling is not that pleasant in Rails). Rails’ Ruby 1.9 compatibility needs you!