Ruby Weekly is a weekly newsletter covering the latest Ruby and Rails news.

19 Rails Tricks Most Rails Coders Don’t Know

By Peter Cooper / July 8, 2006

New to Rails 3? Check out the Ruby on Rails 3 Tutorial book and screencast.

A book and screencast series showing you how to develop and deploy industrial-strength Rails apps in a direct, step by step way. The screencast series includes 12 lessons over more than 15 hours! Get the best "over the shoulder" experience of following what a top Rails 3 developer does when building an app today. Click here to learn more.

Please note that this post is over four years old - it's from 2006! As such, these tips were relevant to Rails 1.2 and this content is woefully out of date. We will be updating it for Rails 3 soon! For now, head to the Ruby Inside homepage to find something else to read or check out the Ruby on Rails 3 Tutorial book and screencast which will bring you up to speed with all things Rails 3.

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.

Technorati Tags: ,

Comments

  1. Oliver says:

    A really, really good post! Thank you.

  2. David says:

    Great article. Your blog is really useful and I always look forward to new entries. Keep up the great work!

  3. mike says:

    Awesome. Thanks so much. This article was really helpful.

  4. Ryan Allen says:

    Interesting post, I'm going to have to look into this acts_as_nested_set thing. I use acts_as_tree all the time (especially in conjucntion with acts_as_list) and a lot of the time I'm traversing up and down, gah! Expensive.

  5. Kevin Marsh says:

    Great article, especially liked .to_sentence, which I seemed to have forgotten about.

  6. Pingback: InfoHatter Blog!

  7. Brandon Keepers says:

    Re: Check for existence

    Using the dynamic find_by_id also avoids an exception being thrown, and doesn't produce 2 database queries like Model.exists?(id) does.

    Model.find_by_id(id) # returns object or nil

  8. Michael says:

    Excellent info!

    Please, for the love of all that's holy, drop the full text justification. When WYSIWYG first came out, it was kind of interesting, but we're a bit past that now. And truly, it looks like crap unless you can do full kerning and proper typography.

  9. John Koetsier says:

    Great tips ... my first reaction was: someone oughtta write a book: Rails cookbook or something like that with these and other tips.

  10. Jason Morrison says:

    Great article, and thanks for the tips!

  11. andrew taylor says:

    To work around the problem of Model.find(id) throwing an exception for invalid id's, I prefer to use the dynamic method Model.find_by_id(id). All of the "find_by" dynamic methods return nil instead of throwing exceptions. This is easiers than doing a combo of Model.exists and Model.find.

    Another thing I like is having a method like this for each model...

    def self.get(it)

    it = User.find(it) if it.is_a?(Integer)
    it = User.find_by_username(it) if it.is_a?(String)
    return it if it.is_a?(User)
    return nil

    end

    Sometime I have helper methods for Models, and in the quest fo have a general purpose interface, would like to be able to take either an id or a model instance or some key string as a parameter, this simple method addition makes it possible.

  12. Peter Cooper says:

    Michael: You talk, I listen. Full width justification is now off. It wasn't something I really noticed, and I don't have major opinions either way, but off is default, and defaults are good :)

  13. Peter Cooper says:

    John: Rails Recipes presents a bundle of 'recipes' for Rails, although most are not in this vein. I agree a collection of, say, 100-250 smaller tips and tricks might be a great read though rather than the 'bigger picture' things Rails Recipes looks at.

  14. Oleg Andreev says:

    Guys, I don't want to seem boring, but it's very sadly to read discussion of the new-wave-XXI-century-technology (it's great, really) with such words as "great", "awesome" and so on without even mentioning just one little silly fact: .to_sentence and NumberHelper should be based on locale settings (fr, es, en, de, ru, it, en-us, en-uk, fr-ca etc.)
    You know, ["a","b","c"].to_sentence == "a, b et c" (in french) == "a, b y c" (in spanish) == "a, b и c" (in russian; luckily smart WordPress uses utf-8) and so on.
    Numbers in russian are delimited with spaces, not commas; and OF COURSE currency may be different in the same language (what about english pounds?)

    And again, it's not very bad that such modules are so simple (i.e. stupid). The problem is, you don't understand it :(

  15. Jeremy Voorhis says:

    Oleg,

    I appreciate your concern, and would like to point you to the Globalize project. Globalize is a Rails plugin that provides internationalization services for Ruby on Rails applications. I'm not sure if we've tackled #to_sentence yet, but you're welcome to help us play catch-up!

  16. Paul O'Shannessy says:

    Oleg: I do agree with you about locale settings, which has been a major topic of contention with Rails. It seems to have been written with English in mind. A project called Globalize is attempting to do exactly what you are saying - determine how to output things based on locale settings.

    Keep in mind also, with number_to_currency you can override the default of using $ and can instead use the £ sign, and you can delimit the numbers differently. The same goes for a lot of the helpers - defaults are pretty easily overridden.

  17. ravi says:

    its really fine 19 tips,but currently i am getting problem like when i registred session in any method and not be able to fetch the value of that variable in any other method in same controller.
    session registration---------------
    session[:scheme_type]=nil
    session[:scheme_type] = @scheme_type
    value fetching----------------
    @scheme_type= session[:scheme_type]
    render_text @scheme_type

    is not working.is i am right on syntax?or any help please.

  18. Matt Thompson says:

    @ravi

    I can't be exactly sure as to why that wouldn't work, but if I may offer a few suggestions:

    First off, the initializing session[:scheme_type] to nil is unnecessary for most practical purposes, so try taking that out.

    One thing that does stick out is the fact that you initialize session[:scheme_type] to @scheme_type in assignment, but then assign everything vice-versa in the fetching. Try to figure out why you're pulling the session data from the controller in the first place, and post an explaination about what you want to ultimately do, if you want.

    One other nice tip is when testing any variable in a view, is very useful.

    @Paul

    Fancy seeing you here :)

  19. Tom Mornini says:

    Cleaner HTML output testing

    There's another plugin called RailsTidy (requires ruby-tidy) that is very nice for this as well, not requiring a net connection.

    It's much faster, and works everywhere. :-)

  20. Oleg Andreev says:

    2 Paul O'Shannessy: I can't agree with you about overriding defaults of number_to_currency ad hoc (in the usage place) because such things (sign, delimiters) should be stored in one place (e.g. something like locale.currency.uk, .us, .ru etc.). And it must be strictly prohibited to deviate from it in the actual code, 'cos it breaks whole locales concept!

  21. Ky Le says:

    I have 2 controller classes: Projects and Documents. I want the Documents.list to list only the documents of a particular project. So, how can I define sessions so that Document always need to know a project's id?
    I tried to add "beore_filter: required_project" in the Documents class, but don't know how to write such a method "required_project". Please help.
    Thank you in advance.

  22. Pingback: TechNovelty

  23. Paul O'Shannessy says:

    I know this is a bit late (I had forgetten I posted here and did a little bit of ego surfing), but...
    @Oleg: I do agree with you that something like that should be a locale setting, so perhaps a solution would be to create your own helper, that incorporates locale settings. Perhaps not an ideal solution, but it would work until something like Globalize really gets a strong hold in the community.
    I'm sure we'll see solutions in the near future.
    @Matt: Small world :)

  24. Pingback: Anonymous

Other Posts to Enjoy

Twitter Mentions