Rails Best Practices, Tips and Tricks

Posted by kev Tue, 07 Feb 2006 01:32:00 GMT

Last week I accepted a job with Mingle.com. The work is exciting (all Rails) and the team is excellent.

Because Rails is a young framework, I thought it would be helpful to write up what I consider best practices when coding with it both for my new coworkers and the web at large. Here’s my current draft. Feel free to critique and comment. I’m very open to suggestions.

Testing

This is absolutely essential. Rails makes writing unit and functional tests incredibly easy and testing should be employed at all times. Positive and negative testing should be employed: the first to verify that the application does what it is supposed to when the proper variables are passed to the correct action and then second to verify that when incorrect variables are passed the prefered behavior occurs.

Unit Testing

As a general rule, unit testing should test any validation in models as well as any added methods in those models.

For example, if I have a User model which validates_presence_of first_name and last_name fields as well as a method fullname which combines the two, I might have test cases test_validates_names and test_fullname which would test that the model worked as it was intended to.

Functional Testing

Functional testing is used to test controllers. As a rule, there should be atleast one testcase for each action and positive and negative testing should be employed. This means that if I have an action create in my PostsController then there should be test_create and test_bad_create methods defined in my functional tests which do positive and negative testing. This does not mean there should be only two tests for each action. If there are exceptional cases beyond a simple good and bad, proper testing should cover those cases.

Helpful Reading

Migrations

Migrations mean never having to say you’re sorry because you nuked the database. They allow for database agnostic schemas which means you can develop locally on SQLite and deploy on MySQL without a problem. They’re cleaner (and easier) than writing your own schemas custom and should be used whenever possible.

NEVER EVER EVER modify schema.rb. It is a reflection of the database. Migrations should be used to move this forward. If you don’t use migrations and instead modify schema.rb, things will break and people will be unhappy.

Note: You should always run svn update before generating a migration so you don’t have prefix collisions (two number 4s for example).

Helpful Reading

SQLite

SQLite is an SQL engine which runs in a single file rather than a server. It is (in general) as fast or faster than MySQL and is excellent for development and testing. In fact, it is possible to run a database completely out of memory which speeds up tests significantly. With the advent of migrations, it makes sense to use SQLite for testing as there is no extra work to deploy on MySQL.

DRY: Don’t Repeat Yourself

The main idea of DRY is that if code which is repeated is extracted to a helper or function, you only have one place to look for (and edit) your code if and when something goes wrong. If you find that similar code is used in several places, you may want to look into extracting that code to a helper function or partial.

Helpful Reading

Naming Conventions

Don’t use abbreviations, especially in database column names. It should be immediately obvious what the column is for (or atleast what the name means) when looking at the name. These are not the days of C, we don’t need to conserve space with our variable names. Additionally, Rails error handling automatically knows how to “humanize” column names, so when you use a well described name you get to work less on outputting errors.

If names are too long, try to think of another word or phrase that means the same thing, but is immediately obvious.

Source Control (Subversion)

Some sort of source control should be used at all times. This allows for easy roll back if something goes wrong as well as the ability to refer back to old code if needed. Rails has certain files which should not be included in a source control repository, so please refer to the wiki page when preparing the initial import.

Helpful Reading

Authentication Systems

Several authentication modules have been written for Rails, but some are better at some things and some are better than others.

acts_as_authenticated

This should be the prefered authentication plugin. It is easily installed via script/plugin, is easily extendible and can handle all issues of the other authentication modules. Additionally, the testing code is excellent which makes it easy to modify.

login_generator

login_generator is the original login generator gem written by Tobias Luetke. The downside is that there is a bug which scrambles the password if you save an already existing user. It should not be used for this reason. It is replaced and superceded by acts_as_authenticated.

SaltedHashLoginGenerator

This was a first attempt at created a salted login generator which could reset passwords and do activation. It has since been extracted into login_engine. It is in general bloated and hard to modify. It should be avoided.

login_engine

login_engine is the extraction of SaltedHashLoginGenerator, and as such, has the disadvantages that SaltedHash has. Additionally, it uses the Rails Engines system which is designed for drop in use. In general, applications need customization. If you need more than simple modification of an authentication system, this should be avoided.

On Scaffolding

Scaffolding can be a time saver or a crutch. When using scaffolding, make sure to understand exactly what the code is doing and why. Once that happens you can generally write the code faster without using scaffolding as there are almost always changes to each section that scaffolding provides.

Suggested Reading (in General)

  • The Pragmatic Programmer by Dave Thomas and Andy Hunt

  • Refactoring by Martin Fowler

  • Agile Web Development with Rails by Dave Thomas and David Heinemeier Hannson

  • Programming Ruby by Dave Thomas, with Chad Fowler and Andy Hunt

Posted in , , ,  | 17 comments | 3 trackbacks

Comments

  1. Avatar Phil said about 4 hours later:

    I am always surprised that I don’t see more people suggesting use of SQLite in development environments—it just seems like a no-brainer to me.

    One thing that would help is to explain exactly which gems are necessary, since the first time it can be a little confusing to choose between sqlite, sqlite3, sqlite3-ruby and sqlite-ruby. (I think you’re supposed to choose the latest version of sqlite3-ruby, but I’m not 100% sure.)

  2. Avatar Luke Redpath said about 10 hours later:

    One of the mistakes I think Rails makes is its reference to “functional” tests when they are in reality, nothing more than unit tests for controllers/actions.

    A better choice for real acceptance tests that test the application, not the code, is Selenium.

    Now there is this: http://andthennothing.net/archives/2006/02/05/selenium-on-rails

    Using Selenium is easy.

  3. Avatar Luke Redpath said about 11 hours later:

    Another hint worth mentioning is refactor those tests as well as the production code.

    You mention having at least one testcase per controller action, but I think a better way of looking at things is to have one testcase per fixture, with individual test methods for testing specific things.

    For instance, to use an example by Dave Astels, you are writing a piece of software that is essentially a movie library. You have a movie container class called MovieList.

    The first testcase would be an empty movie list. So your first testcase would probably be something like:

    class EmptyMovieList < Test::Unit::TestCase def setup @list = MovieList.new end end

    def test_is_empty
      assert @list.empty?
    end
    1. other test methods

    You would then have another testcase, OneMovieList, then ManyMovieList (this is probably a suitable amount of testcases at this point). Another cool thing about this form is that your test cases and test methods can read like specifications…”EmptyMovieList…is empty”. Which leads me on to my next point…look into BDD instead of TDD, and rSpec:

    http://rspec.rubyforge.org/ http://blog.daveastels.com/?p=53

    So how do I translate the above into the Rails context? Lets say we have a blog controller with a post action. Two testcases immediately spring to mind. ValidPostTest and InvalidPostTest. I can’t see any need to be more specific than that…the controller should only care whether the post is valid or invalid and handle with an appropriate response. The specifics of why a post may or may not be invalid is down to the model and such finely grained tests (say, post without a title, post without a body etc.) form your model unit tests.

    Hope this helps!

  4. Avatar Luke Redpath said about 11 hours later:

    Um, that code again…;)

    http://pastebin.com/543128

  5. Avatar Mike Pence said about 13 hours later:

    Dude! Congratulations on the new gig! Sweet!

  6. Avatar Kevin Clark said about 16 hours later:

    Luke: Great comments. Selenium looks really exciting and I’m looking to play with it in the near future.

    Keep it up guys, lets hear some more of what you consider best practices. What am I missing?

  7. Avatar Dave Welton said about 20 hours later:

    One of the things that is driving rails uptake is that it just encapsulates a lot of good ways of doing things.

    It would be nice if there were one good way of doing authentication/logins – if there are several, new users like myself (who are, of coures, the least well placed to actually judge) are left wondering “which is best?”. Hopefully the Rails guys will pick one, make sure it really is the best by investing some time in it, and include it.

  8. Avatar Alex said 6 days later:

    In reference to Dave Welton’s comments:

    I think the problem is that “which is best” is very dependent on the specific requirements of the application.

    For example, some applications will simply require that a user be logged in before they can use the application, others will have different levels of access and others may require fine grain control over permissions to perform tasks and access specific records.

    The onyl way to fit every eventuality in to a “which is best” scenario is to have a solution that can handle every eventuality and yet be rediculously simple to implement for the more basic examples.

  9. Avatar Kevin Clark said 6 days later:

    Dave: I agree, it would be nice to have a “best” authentication plugin, but it still wouldn’t belong in trunk ;) It’s too specific.

    Alex: As for finding the which is best, I agree, it is entirely application and need specific. I suggest acts_as_authenticated because, in my experience, it is the easiest to modify to fill my needs.

  10. Avatar matt said 7 days later:

    I see people come to Rails from all sorts of other languages, all of them with their own format conventions. Ruby goes to great length to be readable, and as a result makes it easy to be non-uniform. Do you put () around every set of method attributes?

    Is there a good Ruby document around that tries to establish coding guidelines?

  11. Avatar Kevin Clark said 7 days later:

    Matt: The question of parenthesis really goes coder to coder. () aren’t needed, but the general rule is that if there can be confusing, use parenthesis.

    eg.
    
    puts "Hi" # Doesn't really need parenthesis
    
    # vs
    
    render :text => h("unsafe string")
    # probably get a parse error without parenthesis.. 
    # what should h be interpreted as without them?
    
  12. Avatar Rich said 25 days later:

    Has anyone else tried using the latest release of ModelSecurity?

    If so, have you tried using it together with acts_as_authenticated?

    The design and usage of ModelSecurity looks like it deserves mention as a best practice for Rails.

    Home

    http://perens.com/FreeSoftware/ModelSecurity/

    Tutorial

    http://perens.com/FreeSoftware/ModelSecurity/Tutorial.html

  13. Avatar Carter said 31 days later:

    re: authentication

    The argument that there are too many different needs for pluggin X for pluggin X to be included in the trunk could really apply to just about any pluggin.

    I don’t think it would be at all unreasonable to have a standard login / session authentication engine.

    By default it could be a standard hashed password database. It could have a lot more options under the hood (that is, if you need more than a “standard” authenticator, you should be working at more than a beginner level anyway) and this would do nothing to prevent other authentication engines from being used for even more specialized uses.

    Authentication is a big problem, and people are right to be wary of cookie-cutter solutions. But there should be an included and standard way of doing simple low-priority-security authentication for things like setting up a simple email-validated account for a public messageboard and other such “shake n’ bake” applications.

    The whole point of Rails seems to be to make simple applications easy to write well, and make big applications easy to structure well. A standard authenticator seems to me to be totally in line with this goal.

    Personally I’ve been using OpenID for my applications. I’m tired of having to register and reregister for every web application I use, so I’m hoping to see this standard catch on, also, an openID consumer is trivially easy to implement, and it takes a lot of hassle and responsibility out of my hands.

  14. Avatar Kevin Clark said 32 days later:

    Carter:

    “The argument that there are too many different needs for pluggin X for pluggin X to be included in the trunk could really apply to just about any pluggin.”

    I agree. This is why plugins aren’t generally included in the trunk. They are plugins.

    I don’t think it’s unreasonable to have a standard login engine. I specifically say in this document which one I think should be standard. This, however, doesn’t mean I think it should be included in the trunk. It has the same problems as pagination does in that it is too specific for use out of the box as you’ll need to modify models and pages and that gets into configuration—something that Rails avoids like the plague. Look at scaffolding. It is there, but you never go live with scaffolding, it is too specific. The whole point of plugins is so you can pick the one that works best for you and modify if you need. Login engines don’t belong in core. Period.

  15. Avatar Jordi Pradel said 35 days later:

    Great article!
    I would add Robert C. Martin’s “Agile Software Development” as a suggested reading. It has a great chapter on DRY.

  16. Avatar Joe said 64 days later:

    I just tried signing up an existing user with LoginGenerator and got a “Login has already been taken” validation error. Is that what you mean by “save an already existing user” scrambles the password?

  17. Avatar Josh said 65 days later:

    I’d be curious to hear your take on ActiveRBAC as an authentication system.

Trackbacks

Use the following link to trackback from your own site:
http://glu.ttono.us/articles/trackback/207

  1. From nano RAILS: development, testing and hosting tidbits
    Best practices
    glu.ttono.us has posted an excellent summary of his take on Rails best practices. And I think I need to do some reading, that the second time in as many days that someone mentions Refactoring by Martin Fowler, and this has me intrigued. My top fav...
  2. From menearailes.com
    Buenas prácticas con Rails.
    Buen artículo con algunos consejos para programar con Rails. En inglés.
  3. From Rails Tips
    Rails best practices
    Kevin Clark shares with us [some of the important things he’s learned while developing in Rails](http://glu.ttono.us/articles/2006/02/06/rails-best-practices-tips-and-tricks). > Because Rails is a young framework, I thought it would be helpf...

Comments are disabled