How to Add Simple Permissions into Your Simple App. Also, Thoughtbot Rules!

Last week, in a few hours, I whipped together flightcontrolled.com for Flight Control, a super fun iPhone game. The site allows users to upload screenshots of their high scores. I thought I would provide a few details here as some may find it interesting.

It is a pretty straightforward and simple site, but it did need a few permissions. I wanted users to be able to update their own profile, scores and photos, but not anyone else’s. On top of that, I, as an admin, should be able to update anything on the site. I’m sure there is a better way, but this is what I did and it is working just fine.

Add admin to users

I added an admin boolean to the users table. You may or may not know this, but Active Record adds handy boolean methods for all your columns. For example, if the user model has an email column and an admin column, you can do the following.

user = User.new
user.email? # => false
user.email = 'foobar@foobar.com'
user.email? # => true

user.admin? # => false
user.admin = true
user.admin? # => true

Simple permissions module

Next up, I created a module called permissions, that looks something like this:

module Permissions
  def changeable_by?(other_user)
    return false if other_user.nil?
    user == other_user || other_user.admin?
  end
end

I put this in app/concerns/ and added that directory to the load path, but it will work just fine in lib/.

Mixin the permission module

Then in the user, score and photo models, I just include that permission module.

class Score < ActiveRecord::Base
  include Permissions
end

class Photo < ActiveRecord::Base
  include Permissions
end

class User < ActiveRecord::Base
  include Permissions
end

Add checks in controllers/views

Now, in the view I can check if a user has permission before showing the edit and delete links.

<%- if score.changeable_by?(current_user) -%>
  <li class="actions">
    <%= link_to 'Edit', edit_score_url(score) %>
    <%= link_to 'Delete', score, :method => :delete %>
  </li>
<%- end -%>

And in the controller, I can do the same.

class ScoresController < ApplicationController
  before_filter :authorize, :only => [:edit, :update, :destroy]

  private
    def authorize
      unless @score.changeable_by?(current_user)
        render :text => 'Unauthorized', :status => :unauthorized
      end
    end
end

Macro for model tests

I didn’t forget about testing either. I created a quick macro for shoulda like this (also uses factory girl and matchy):

class ActiveSupport::TestCase
  def self.should_have_permissions(factory)
    should "know who has permission to change it" do
      object     = Factory(factory)
      admin      = Factory(:admin)
      other_user = Factory(:user)
      object.changeable_by?(other_user).should be(false)
      object.changeable_by?(object.user).should be(true)
      object.changeable_by?(admin).should be(true)
      object.changeable_by?(nil).should be(false)
    end
  end
end

Which I can then call from my various model tests:

class ScoreTest < ActiveSupport::TestCase
  should_have_permissions :score
end

Looking at it now, I probably could just infer the score factory as I’m in the ScoreTest, but for whatever reason, I didn’t go that far.

A sprinkle of controller tests

I also did something like the following to test the controllers:

class ScoresControllerTest < ActionController::TestCase  
  context "A regular user" do
    setup do
      @user = Factory(:email_confirmed_user)
      sign_in_as @user
    end
    
    context "on GET to :edit" do
      context "for own score" do
        setup do
          @score = Factory(:score, :user => @user)
          get :edit, :id => @score.id
        end

        should_respond_with :success
      end

      context "for another user's score" do
        setup do
          @score = Factory(:score)
          get :edit, :id => @score.id
        end
        
        should_respond_with :unauthorized
      end
    end
  end
  
  context "An admin user" do
    setup do
      @admin = Factory(:admin)
      sign_in_as @admin
    end
    
    context "on GET to :edit" do
      context "for own score" do
        setup do
          @score = Factory(:score, :user => @admin)
          get :edit, :id => @score.id
        end

        should_respond_with :success
      end

      context "for another user's score" do
        setup do
          @score = Factory(:score)
          get :edit, :id => @score.id
        end

        should_respond_with :success
      end
    end
  end
end

Summary of Tools

I should call flightcontrolled, the thoughtbot project as I used several of their awesome tools. I used clearance for authentication, shoulda and factory girl for testing, and paperclip for file uploads. This was the first project that I used factory girl on and I really like it. Again, I didn’t get the fuss until I used it, and then I was like “Oooooh! Sweet!”.

One of the cool things about paperclip is you can pass straight up convert options to imagemagick. Flight Control is a game that is played horizontally, so I knew all screenshots would need to be rotated 270 degress. I just added the following convert options (along with strip) to the paperclip call:

has_attached_file :image, 
  :styles => {:thumb => '100>', :full => '480>'},
  :default_style => :full,
  :convert_options => {:all => '-rotate 270 -strip'}

Conclusion

You don’t need some fancy plugin or a lot of code to add some basic permissions into your application. A simple module can go a long way. Also, start using Thoughtbot’s projects. I’m really impressed with the developer tools they have created thus far.

12 Comments

  1. Hey John, great article, and thanks for checking out our projects.

    We’re using your twitter gem in a recent project, and it’s been really easy to figure out (we had search integrated in probably ~30 mins).

  2. @Matt Glad the love is reciprocal. ;)

  3. Jon Stenqvist Jon Stenqvist

    Apr 20, 2009

    Hi John,

    Thanks for a great article, I will have modules in /app/concerns from now on.

  4. @Jon – Yeah it just depends on what you are doing. I don’t have a hard and fast rule, just go on gut.

  5. Good stuff. I’m curious about app/concerns—what do you usually put in here? Why not just put it in lib? Sometimes I put modules in app/models if is a module that is only intended to be mixed into models.

    I agree that thoughtbot’s tools are great—I use and love shoulda and factory girl. That said, one issue I’ve run into with factory girl is test speed. Creating all your data locally through your models is great, but it’s definitely slower than slamming the data in the database using fixtures, and then relying on transaction rollback between each test to put it back into the known state, so you can re-use your fixture data. This is fine with a small test suite, but as your test suite grows, this becomes more and more of an issue. I decided to do something about it and created factory_data_preloader to address it.

  6. @Myron Someone smarter than me started calling it that and I liked it I guess. I’ve also seen it in a few projects I’ve worked on and liked it. No grandiose reason in particular.

  7. Nice work, but what you’ve done can be more powerfully expressed using the acl9 gem:

    http://github.com/be9/acl9/tree/master

    Works the same way too (mostly).

  8. @Jaryl – The whole point of this article is simplicity. acl9 may be powerful, but I would definitely not refer to it as simple. It would take me longer to understand it than it took me to implement the code I showed above.

  9. We’ve just recently started using Clearance on a new Rails project and have found it to be much better than some of the other options. The minor issues we have had with it seem to have been fixed by the new Engine version of the plugin – check it out if you haven’t already.

    When we don’t need email confirmation or password reset functionality, we’ve been using the simplest_auth plugin that we (Viget) built. It abides by its label and only handles authentication, so it’s a good alternative to the more full-featured solutions.

  10. @Patrick Just saw the engine version of it. Looks handy.

  11. @John – When I first looked at acl9, it went right over my head. I was shopping for a per-object ACL then, so this seemed like the only option, so I went ahead to learn it.

    Turns out, using it is much simpler than reading the docs, which are confusing at best. Just my 2 cents.

  12. Hi John, great article. It’s funny, because I did this exact same thing in one of my projects, but instead used “modifiable_by?”. I even have a draft in my blog explaining this. I just thought it was funny.

    One of the things I did different was have 2 methods: accessible_by? and modifiable_by?. In most cases they were one in the same, but in others it would separate the difference between accessing the data in the public and accessing it in the admin. I also had these methods in the class level to determine complete model access.

    The one thing I loved about this approach is that it’s pure ruby, no static permission lists or anything like that. It’s plain and simple ruby, so I could use variable conditions in my rules, such as time.

    Anyways, hopefully this helps someone.

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a web developer and programmer who has fallen deeply in love with Ruby. More about John.

Syndication

Feed IconRailsTips Articles - An assortment of howto's and thoughts on Ruby and Rails.

Feed IconRails Quick Tips - Ruby and Rails related links.