Adding Authorization Using Devise

This will cover how to use Devise as your user authentication system. In previous posts I’ve used AuthLogic which is a good solution, I just find Devise simpler to use and implement. This will extend from my article Rails 3 Blog Tutorial. I’d highly suggest going through that tutorial, or you can run these commands.

git clone git@github.com:baileylo/blog.git
git checkout -b blogCreated 0ba92371c2998caf362827987a82050708e9cd25

First add devise to your gemfile, gem “Devise”. Then run the follow commands:

$ bundle install
$ rails generate devise:install

This will install the Devise gem and set up the Devise modules. You will be prompted for 3 steps:

Some setup you must do manually if you haven't yet:

  1. Setup default url options for your specific environment. Here is an
     example of development environment:

       config.action_mailer.default_url_options = { :host => 'localhost:3000' }

     This is a required Rails configuration. In production it must be the
     actual host of your application

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root :to => "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>
view raw gistfile1.txt This Gist brought to you by GitHub.

Do step 1, step 2 and 3 should already be done. Once you have completed these steps run:

$ rails generate devise user

Open up your user model(app/models/users.rb). You will see a comment at the top listing possible devise modules. You can add and remove these as you wish. For this tutorial we’ll only use: database_authenticatable, registerable, recoverable, rememberable, trackable. Save the changes, and run:

$ rake db:migrate
Now if you run “rake routes” you will see a series of /users/ routes setup automatically by Devise. Open up your application template, app/views/layout/application.html.erb and add the following code:

<div class="user-auth-nav" style="float:right">
  <% if user_signed_in? %>
  <%= link_to('Edit registration', edit_user_registration_path) %> |
  <%= link_to('Logout', destroy_user_session_path) %>
  <% else %>
  <%= link_to('Login', new_user_session_path) %> |
  <%= link_to('Register', new_user_registration_path) %>
  <% end %>
</div>
view raw gistfile1.txt This Gist brought to you by GitHub.

diff

Restart your rails server, and then reload your page. You should see in the top right Login and Register links. Feel free to create an account and play around with Devise’s built in authentication and user validation.

Now we need to associate a specific author with a specific post. To do this will use another migration:

$ rails generate migrate addUserIdToPosts user_id:integer
$ rake db:migrate

Rails migration generator will automatically read the migration name and realize that we’re adding the UserId column To the Posts table. Now we need to make the relationships between the models in ruby. Open up the user model(app/models/user.rb) and add “has_many :posts” within the class definition. Now open the posts model (app/models/post.rb) and add “belongs_to :user” within the class definition.
diff

So far now we’ve created user authentication system and added user to a post. Open your posts controller (app/controllers/posts_controller.rb) and add the following line at the top of your class, but inside the class definition
before_filter :authenticate_user!, :o nly => [:edit, :update, :destroy, :create, :new]
This will apply Devise’s built in function authenticate_user! When the actions edit, update, create, new, and destroy are called. It will effectively require the user to be logged in to access these actions.

Prior to a post being saved, we’re going to want to set the “poster” to be the user who is currently signed in. Devise provides the current_user helper which allows you access to the logged in user’s User object. Change the “create” action in the posts controller to look like this.

@post = Post.new(params[:post])
@post.user = current_user

diff

This will assign the currently logged in user as the user for the post. Lets clean up the views a little big. Change your posts index.html.erb and show.html.erb files to look like this, app/views/posts/.
diff

You may be getting error that reads: “undefined method `email’ for nil:NilClass“. These blogs were posted prior to having added the user migration. You can add an if statement to skip the user data when a Post doesn’t have a related User Object. But lets make a migration, run the following command
$ rails g migration insertUserInAllPosts

Open up the generated migration, and make the following changes . We named the migration “insertUserInAllPosts”, the name is up to you. It is nice to have descriptive names for migrations; in the future you will waste less time figuring out what the migration does if it has a good name.
diff

You may have noticed that we have removed the links to edit posts for other users, but they can still edit posts if they go directly to the url. To fix this we’ll use another “before_filter”. Open up the posts controller, app/controllers/post_controller.rb, and make the following changes:
diff

Now you have a full authorized blog. Come back next week to see user customizations.

  • Pingback: Rails 3 Blog Tutorial | Infectious Learning

  • Illogikal

    Hi this tutorial has been very helpful! I was wondering if you could show how to add comments, as I have been trying and I keep getting argument errors. Thanks

  • http://www.logansbailey.com Logan Bailey

    @Illogikal I’ll be adding that next sunday

  • Illogikal

    Thanks! I’ve only started learned rails within the last month and a half and its so different stylistically from php that it’s a lot to wrap my head around.

    For example, in your application, why is it that you use:

    @post = Post.new(params[:post])
    @post.user = current_user

    and not

    @post.user_id = current_user.user_id

    is this just rails convention or is there somewhere specific that you define this? Or is it that if you define it like that it just assumes that it’s the id of the user since you nested the resources in the routes?

    Thanks again!

  • http://www.logansbailey.com Logan Bailey

    @post.user_id = current_user.id
    @post.user = current_user
    Will produce the same query, I feel that assigning objects is easier to read.

  • Oliver Reilly

    Great tutorial.

    Question though … I’ve restricted access on certain actions using before_filter :authenticate_user!, :except => [:index, :show, :new]. I want the user to create a new record and then when it tries to save, it asks the user to login. This works fine except the login then redirects to my application root page and the Record.save command never gets executed.

    How can I authenticate on the create action and after logging in successfully continue to save the record.

    Thanks
    Oliver

  • Nils

    The correct syntax to create the migration is:

    rails generate migration add_user_id_to_posts user_id:integer

    not

    rails generate migrate addUserIdToPosts user_id:integer

    Thanks for the walkthrough!