See morw...

Sunday, November 14, 2010

19 Rails Tricks Most Rails Coders Don’t Know


When looking at my own Rails code and that of the community as a whole, I often see places where certain Rails techniques could have been used, but weren't. As much for my own memory as yours, I thought I'd list down some Rails tricks and tips that can make your application or code more efficient:
Benchmark logic in your controller actions - It's really easy. Use the benchmark class method available on all your models like this:
User.benchmark("adding and deleting 1000 users") do
 1000.times do
  User.create(:name => 'something')
  x = User.find_by_name('something')
  x.destroy
 end
end
Of course, your code would be a lot better ;-) The regular SQL logs are not shown when within the benchmark sections. Only the benchmark results are shown.
Nested-Set

acts_as_nested_set
- Almost everyone is familiar with acts_as_tree, but acts_as_nested_set snuck into Rails quietly. It's much like acts_as_tree, but with the added benefit that you can select all of the children (and their own descendants) of a node with a single query. A list of the instance methods is available.
Easier collections with to_proc - Sick of writing things like post.collect { |p| p.title } or post.select { |p| p.activated? }.collect{ |p| p.title} ? A little Ruby hackery that allows you to convert symbols into proc references makes it easy. You can write post.collect(&:title) or post.select(&:activated?).collect(&:title) instead! Learn a lot more about this.
Convert arrays to sentences in views - If you were collecting a bunch of names to be shown in a view, you might end up with an array like ['Peter', 'Fred', 'Chris'], and joining these with commas and inserting 'and' before the final one is a common pain. Not so, if you use the array method to_sentence as provided in Rails. names.to_sentence would return Peter, Fred, and Chris.
Send files back to the user - Usually, static files can be retrieved by using the direct URL and circumventing your Rails application. In some situations, however, it can be useful to hide the true location of files, particularly if you're sending something of value (e-books, for example). It may be essential to only send files to logged in users too. send_file makes it possible. It sends files in 4096 byte chunks, so even large files can be sent without slowing the system down.
Iterating through page elements with RJS - Changing page elements with RJS is easy, but what if you don't know exactly which elements you want to change, and would instead prefer to address them with CSS queries? You can with RJS's select method. For example: page.select('#items li').each { |item| item.hide } . Powerful stuff!
Check for existence - When doing a Model.find(id), an exception can be returned if the item with an id of 'id' doesn't exist. If you want to avoid this, use Model.exists?(id) first to get a true or false for whether that item exists or not.
Number helpers for common number tasks - All of these number helpers aren't commonly used but provide great shortcuts: number_to_currency(1234567.948) # => $1,234,567.95 or human_size(1234567890) # => 1.1GB or number_with_delimiter(999999999) # => 999,999,999. There are others.
Testing different route configurations easily - with_routing is a test helper that allows you to temporarily override the default 'routes' in routes.rb for test purposes. Demonstration:
with_routing do |set|
  set.draw { set.connect ':controller/:id/:action' }
  assert_equal(
     ['/content/10/show', {}],
     set.generate(:controller => 'content', :id => 10, :action => 'show')
  )
end
You can learn a little more here.
Get lots of info about requests - Checking request.post? and request.xhr? are popular ways to look for POST and AJAX requests, but some of the other request methods are lesser used. For example: request.subdomains can return an array of subdomains that you could use as part of your authentication scheme, request.request_uri returns the full local request URL, request.host returns the full hostname, request.method returns the HTTP method as a lowercase symbol, and request.ssl? returns true if it's an HTTPS / SSL request.
Improving session performance even more than with ActiveRecord - By default, Rails stores sessions on the local file system. Many users change this to using ActiveRecordStore to store sessions in the database. An even faster alternative is to use Memcached to store sessions, but that takes a lot to set up (and isn't available unless you run your own servers, etc). But you can get faster than ActiveRecordStore by using Stefan Kaes' SQLSessionStore. It circumvents the inefficiencies of ActiveRecordStore using his own direct SQL technique to store sessions.
Caching unchanging data at application startup - If you have data that doesn't change between application restarts, cache it in a constant somewhere. For example, you might have a YAML or XML file in /config that stores application configuration data, and you could load it into a constant in environment.rb, making lookups quick and easy application-wide.
Check your views are rendering valid HTML / XHTML - It's not for everyone, but if your output validates as correct HTML / XHTML, it's a sign your views are going to render properly. Scott Raymond has developed a assert_valid_markup test helper that you can use from your functional tests.
Cleaner HTML output testing - Combine why's Hpricot HTML parser and a special test extension, and you can have powerful tests like so: assert_equal "My title", tag('title') or assert element('body').should_contain('something'). This might be ideal for developing tests to test user built templates. In any case, it's nicer than assert_tag!
Run long-running tasks separately in the background - BackgrounDRb is a small framework, by Ezra Zygmuntowicz, that runs as a daemon in the background that can accept tasks your Rails application sends to it, and whose execution is totally separate to your Rails app. It's extremely powerful, and useful for many tasks such as sending hundreds of e-mails, fetching URLs, and other things you don't want to slow down the request times for your main app. One great demo is to develop a task that increments a variable by 1 and sleeps for 1 second. You can then make a Rails method that queries the variable, and see the distinct separation. Learn more.
Make ids in URLs more user friendly - Override the to_param method on your model and return something like "#{id}-#{title.gsub(/[^a-z0-9]+/i, '-')}" to get URLs like so: http://yoursite.com/posts/show/123-post-title-goes-here .. Much nicer for users, and you don't need to change anything with Post.find(params[:id]) as the non numeric characters will be stripped automagically! Get a full explanation here.
Separate out slices of functionality into Engines - Everyone's heard of Rails' plugins, but pitifully few are using Rails Engines! Rails Engines are like plugins on steroids. They can contain their own models, controllers, and views, and integrate with any applications you run them under. This allows you to split out common fragments of functionality (login, user management, content management, etc.) into separate 'engines' to use in your different projects within minutes. No more writing dull login code! Rails Engines is a big deal, but it should be a far bigger deal.
Calculations - Do you want to get maximums, minimums, averages, or sums for data in your tables? ActiveRecord's Calculations make these all possible. Person.average('age'), Person.maximum(:age, :group => 'last_name'), and Order.sum('total') all become a reality. Most can be customized pretty deeply with extra options, so go read about them if they're not already part of your code.
XML or YAML output of your data - It's not necessarily to create a Builder .rxml template for all XML output. ActiveRecord has a to_xml method that will output the object or result set in XML format. It works with simple objects, to complete tables (like User.find(:all).to_xml). Using includes works too, as with Post.find(:all, :include => [:comments]).to_xml. YAML is also supported, by using to_yaml instead.

1 comment:

Wildcard SSL said...

I just finished up reading your blog the first time so I thought I should comment to let you know your stuff is great and you have another follower! Keep the posts coming!