Archive for the 'Tech' category

Snippet of The Day: How to Sort By a Grandparent’s Attribute in Rails

Apr 28 2013 Published by thepanakawan under Tech

Problem:

Let say you have a model named Child which belongs to a parent model named Parent. This Parent model also belongs to another model named Grandparent. How to easily sort Child model’s instances by Grandparent’s attribute?

Solution:

1
Child.joins(:parent => :grandparent).order('grandparents.name')

[source]

No responses yet

Rails Nested Form with Has Many Through Association (Part 2)

Jun 07 2011 Published by thepanakawan under Tech

What about editing?

I create this post to answer Pierre’s question in the comment section in my previous tutorial here.

The edit part is actually not much different from the new part. Therefore, I wanted to just edit that previous post. But somehow, there is something wrong with my blog, I could not update that post. Well, I tried this and that and nothing worked while my lunch time is almost over now. I figured out, I’d better write new post even though it would only contain small addition. Let’s take a look.

First, we need to modify our edit.html.erb file to render different form:

1
2
3
4
5
6
<h1>Editing calibration</h1>

<%= render 'edit_form' %>

<%= link_to 'Show', @calibration %> |
<%= link_to 'Back', calibrations_path %>

Then, we need to create _edit_form.html.erb file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<%= form_for @calibration, :url => calibration_path(@calibration) do |f| %>
  <% if @calibration.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@calibration.errors.count, "error") %> prohibited this calibration from being saved:</h2>

      <ul>
      <% @calibration.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :tester %><br />
    <%= f.text_field :tester %>
  </div>
  <div class="field">
  <table border="1">
    <tr>
      <td>Step Name</td>
      <td>Result</td>
      <td>Note</td>
    </tr>
      <%= f.fields_for :calibration_results do |builder|  %>
        <%= render 'calibration_result_fields', :f => builder, :calibration => @calibration, :calibration_step => builder.object.calibration_step %>
      <% end %>
  </table>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Please note that the main different thing is that in this file we add the exact path to form_for and omit the part where we iterate the @calibration_steps instance to render our fields_for code.

While the controller is not changed at all. But to make sure you know what my edit method looks like, here it is:

1
2
3
  def edit
    @calibration = Calibration.find(params[:id])
  end

I guess, there we have the editing part.

3 responses so far

Rails Nested Form with Has Many Through Association

Jan 20 2011 Published by thepanakawan under Tech

I tend to avoid using nested form in my rails code. If needed, I always use form_tag rather than fields_for. But in my recent project, I give a try on using nested form. It turns out that Rails’ way to build a nested form is… how to say this delicately… beautiful. So, I think, I would like to post an article on this.

As usual, I learn how to use nested form from Ryan Bates’ Railscasts and then modify it to meet my need. In this post, I particularly focus on how to use it with three models which share a has many through association among them, just like what I did in the actual project. And for your reference, you can also check the Railscasts’ episodes here and here.

The Project

In my actual project, I had to do a calibration on something (well, I can’t name what, I can’t violate the NDA). The calibration involved several steps that can be dynamically managed. And then, for every step, I had to mark whether the calibrated thing pass the step or not and I had to enable the tester to write some notes about the step conducted. In short, I translated this scheme into a three models wtih has many through association. The models are: Calibration, CalibrationStep, and CalibrationResult. Calibration has many CalibrationStep through CalibrationResult.

1. Generation

Just like any other development, let Rails kindly does our needed auto-generated codes. Of course,  we start from generating the simple app we’re going to use through this whole tutorial. I’d like to name it “calibrator”. Somehow it sounds stupid, but let it be.

1
rails new calibrator

For both Calibration and CalibrationStep, we will generate the full MVC. Here it goes the Calibration:

1
rails generate scaffold Calibration name:string tester:string

And here goes the CalibrationStep

1
rails generate scaffold CalibrationStep step_name:string step_number:integer

While for the CalibrationResult, we will only generate the model:

1
rails generate model CalibrationResult calibration_id:integer calibration_step_id:integer result:boolean note:string

And then we end this step by migrating our database.

1
rake db:migrate

2. Setting up the association

Now, let’s code. I start from the Calibration model, here is what we code:

1
2
3
4
5
class Calibration < ActiveRecord::Base
  has_many :calibration_steps, :through => :calibration_results
  has_many :calibration_results
  accepts_nested_attributes_for :calibration_results
end

You should note that it’s just ordinary association that we put in our Calibration model with a slight addition the accepts_nested_attributes_for for our nested form later on.

And then, here is our CalibrationStep model, nothing special really:

1
2
3
4
class CalibrationStep < ActiveRecord::Base
  has_many :calibrations, :through => :calibration_results
  has_many :calibration_results
end

And the last, our CalibrationResult model:

1
2
3
4
class CalibrationResult < ActiveRecord::Base
  belongs_to :calibration
  belongs_to :calibration_step
end

3. Modifying the controllers

The only controller we need to worry about is our CalibrationsController class. We should modify its show and new method. Here is our modification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CalibrationsController < ApplicationController
  # some lines are deleted...
  def show
    @calibration = Calibration.find(params[:id])
    @calibration_steps = CalibrationStep.find(:all, :order => 'step_number ASC')

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @calibration }
    end
  end

  def new
    @calibration = Calibration.new
    @calibration_steps = CalibrationStep.find(:all, :order => 'step_number ASC')
    calibration_result = @calibration.calibration_results.build()

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @calibration }
    end
  end
  # some lines are deleted....
end

You should notice that we added a variable called calibration_result with value generated by a method called build. This build method is the key ingridients of our nested form. This method, according to Rails documentation,

returns a new object of the collection type that has been instantiated with attributes and linked to this object through the join table, but has not yet been saved.

For detailed description about this, you can look it up here with keyword collection.build().

We can leave the other controllers as they are.

4. Modifying the views

There are at least three views that we need to tweak. The first one is our app/views/calibrations/_form.html.erb file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%= form_for(@calibration) do |f| %>
  # some lines are deleted....
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :tester %><br />
    <%= f.text_field :tester %>
  </div>
  <div class="field">
  <table border="1">
    <tr>
      <td>Step Name</td>
      <td>Result</td>
      <td>Note</td>
    </tr>
    <% @calibration_steps.each do |calibration_step| %>
      <%= f.fields_for :calibration_results do |builder|  %>
        <%= render 'calibration_result_fields', :f => builder, :calibration => @calibration, :calibration_step => calibration_step %>
      <% end %>
    <% end %>
  </table>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Then, we need to add the partial view we described in our form earlier by adding this app/views/calibrations/_calibration_result_fields.html.erb file:

1
2
3
4
5
6
7
8
9
10
11
<tr>
  <td><%=h calibration_step.step_name %></td>
  <td>
    <%= f.radio_button :result, true %>Pass
    <%= f.radio_button :result, false %>Fail
  </td>
  <td>
    <%= f.text_field :note %>
    <%= f.hidden_field :calibration_step_id, :value => calibration_step.id %>
  </td>
</tr>

And the last, we need to show the result. For this tutorial, I will just put it in my Calibration’s view that is in app/views/calibrations/show.html.erb file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @calibration.name %>
</p>

<p>
  <b>Tester:</b>
  <%= @calibration.tester %>
</p>

<table border="1">
  <tr>
    <td>Step Name</td>
    <td>Result</td>
    <td>Note</td>
  </tr>
  <% @calibration_steps.each do |calibration_step| %>
    <tr>
      <td><%= calibration_step.step_name %></td>
      <% calibration_result = CalibrationResult.find(
        :first, :conditions => {:calibration_step_id => calibration_step.id, :calibration_id => @calibration.id}) %>
      <% if !calibration_result.nil? %>
        <td>
          <% if !calibration_result.result.nil? %>
            <%= calibration_result.result==true ? "Pass" : "Fail" %>
          <% else %>
            Not yet tested
          <% end %>
        </td>
        <td><%=h calibration_result.note %></td>
      <% else %>
        <td>Not yet tested</td>
        <td>Not yet tested</td>
      <% end %>
    </tr>
  <% end %>
</table>


<%= link_to 'Edit', edit_calibration_path(@calibration) %> |
<%= link_to 'Back', calibrations_path %>

5. That’s it

That’s it. Now, you can try to add your calibration steps and then try to use the nested form in your calibration’s view. Here is a screenshot from my own trial:

the look of the nested form

8 responses so far

Rails 3 Authentication and Authorization with Devise and CanCan (Part 2)

Dec 16 2010 Published by thepanakawan under Tech

This is the second part of two posts tutorial on how to use Devise and CanCan with Rails 3. You can find the first part of this tutorial here.

Where were we?

In the previous part, we have used Devise library to create our User model in CRUD style. With what we have coded in the previous part, you should now be able to manage users and authenticate them. Now it’s time to add the roles and define what features they may access.

1. Adding the Roles

This step is pretty traditional, we can just scaffold all the MVC part for our roles. In this article, I only need one attribute that is the name of the role.

1
rails generate scaffold Role name:string

2. Making HABTM relationship between roles and users

We have to do several tasks in this step. The first thing is to manually create the migration file for the habtm table. We do it by first generating the migration file like this:

1
rails generate migration UsersHaveAndBelongToManyRoles

and then we fill the file with this code:

1
2
3
4
5
6
7
8
9
10
11
class UsersHaveAndBelongsToManyRoles < ActiveRecord::Migration
  def self.up
    create_table :roles_users, :id => false do |t|
      t.references :role, :user
    end
  end

  def self.down
    drop_table :roles_users
  end
end

And do not forget to modify our model files. This is how our role.rb looks like after we modify it:

1
2
3
class Role < ActiveRecord::Base
  has_and_belongs_to_many :users
end

And this is how our user.rb file looks like. Please be noted that we also add :role_ids in the attr_accessible and we add a method to identify the role of the respective user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User < ActiveRecord::Base
  has_and_belongs_to_many :roles
 
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable, :lockable and :timeoutable
  devise :database_authenticatable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :username, :password, :password_confirmation, :remember_me, :role_ids

  def role?(role)
    return !!self.roles.find_by_name(role.to_s.camelize)
  end
end

In the views part, we have to modify at least the _form.html.erb from our users view so that we can assign the role to the user from web interface. This is how the file users/_form.html.erb looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% @user.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>
  <% if @current_method == "new" %>
    <div class="field">
      <%= f.label :password %><br />
      <%= f.password_field :password %>
    </div>
    <div class="field">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation %>
    </div>
  <% end %>
  <% for role in Role.find(:all) %>
    <div>
      <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
      <%= role.name %>
    </div>
  <% end %>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Of course you can also modify your index.html.erb and show.html.erb files to show the role of the listed user(s). But I think I leave it to you to do it yourself. After all these tasks, we can run rake task db:migrate.

1
rake db:migrate

In this state, we can now add users and assign them with the roles we desire. But of course each role has not yet identified with any permission by CanCan. That’s what we’re going to do in the next step.

3. Adding ability

To use CanCan, we have to make a class that defines the permissions that are given to each role. In CanCan, this is called as “ability” and to set it up, we have to create the ability.rb file as part of our models.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Ability
  include CanCan::Ability
 
  def initialize(user)
    user ||= User.new # guest user

    if user.role? :administrator
      can :manage, :all
    elsif user.role? :operator
      can :manage, Post
    else
      can :read, :all
    end
  end
end

CanCan use a very pleasant way to describe our roles and it’s corresponding allowed features. In our code above, we use the keyword “can” and then follow it with the level of authorization we want the respective role to have and at last we end it with the name of the model. The level of authorizations that I have tried so far is :create, :read, :update, :delete, and :manage. Maybe there is a way to define another ability, you can find about it in depth in the official Github page of CanCan library here.

4. Adding authorization

Finally, in every controller that needs authorization, we add this line:

1
load_and_authorize_resource

Final Words

I think that’s it for now. I know I only have covered the basic part of using Devise and CanCan but I hope this can be helpful for anyone that just starts to learn about both libraries. I may add a working source code to this tutorial later. Meanwhile, if you have any trouble following this tutorial, you can ask me in the comment section.

7 responses so far

Rails 3 Authentication and Authorization with Devise and CanCan (Part 1)

Nov 26 2010 Published by thepanakawan under Tech

Recently, I was involved in a new project that uses Rails 3. That was my first Rails 3 project, I’ve always used Rails 2 before. Therefore, I tried to get accustomed to how Rails 3 works and how it differs from Rails 2. One thing that I really had to change back then was what authentication and authorization plugin to use. In my previous projects, I always used Authlogic and Lockdown. When I started this particular project, it seemed that both Authlogic and Lockdown was not Rails 3 ready (but I heard now both are Rails 3 ready). So I decided to give a try to different plugins. That’s how I found Devise and CanCan.

Most part of this writing is heavily duplicated from tutorials from Devise’s official github page, Asciicast’s episode on CanCan by Ryan Bates, and Tony Amoyal’s tutorial. I took most of the codes from these resources and then slightly modified it to meet my own needs. In this post, I try to share my experience working with both Devise and CanCan. I’ll explain it in a step-by-step style, maybe some steps are just too obvious for expert readers, please bear with it.

The Project

I wanted to have a user management feature. In the project, we had several roles with predefined sets of features that each role can access. The system has an administrator roles. Users can’t freely register to the system, the admins add them to the system and then assign a role to them. In the actual system I worked with, users passwords are generated by the application and then sent to the respective user’s mail. To keep this post simple, I’ll make administrator set the password directly.

1. Adding needed gems

Add these lines to your Gemfile:

1
2
gem 'devise'
gem 'cancan'

then install the gems with this command from your command line:

1
bundle install

2. Adding Devise to your project

Run these commands from your command line:

1
2
rails generate devise:install
rails generate devise User

3. Modifying user model and migration file

As said before, I needed the user management feature to be able to add, delete, and modify users through a CRUD mechanism. Therefore, there are several things we need to change in our user model and migration file. First, we delete the “registerable” attribute.  Second, we add a “username” attribute to the model and to the migration file. Here is how your user model should look like:

1
2
3
4
5
6
7
8
9
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable, :lockable and :timeoutable
  devise :database_authenticatable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :username, :password, :password_confirmation, :remember_me
end

and your migration file for user should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class DeviseCreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|
      t.database_authenticatable :null => false
      t.recoverable
      t.rememberable
      t.trackable
      t.string :username

      # t.confirmable
      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
      # t.token_authenticatable


      t.timestamps
    end

    add_index :users, :email,                :unique => true
    add_index :users, :reset_password_token, :unique => true
    # add_index :users, :confirmation_token,   :unique => true
    # add_index :users, :unlock_token,         :unique => true
  end

  def self.down
    drop_table :users
  end
end

4. Modifying the routes

When we installed Devise, it added it’s own routes to our routes.rb file. Since we want to have CRUD ability, we have to add another routes for our user resources. So this is what we do:

1
2
3
4
5
6
DeviseTest::Application.routes.draw do
  devise_for :users
  resources :users

  # add another lines as you need...
end

5. Creating the controller and views

In this step, we can just use a generic controller and views template that usually created when you use rails generator to do a scaffolding. Anyway, this is how my users_controller.rb looks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class UsersController < ApplicationController
  # GET /users
  # GET /users.xml
  def index
    @users = User.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @users }
    end
  end

  # GET /users/1
  # GET /users/1.xml
  def show
    @user = User.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @user }
    end
  end

  # GET /users/new
  # GET /users/new.xml
  def new
    @user = User.new
    @current_method = "new"

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @user }
    end
  end

  # GET /users/1/edit
  def edit
    @user = User.find(params[:id])
  end

  # POST /users
  # POST /users.xml
  def create
    @user = User.new(params[:user])

    respond_to do |format|
      if @user.save
        format.html { redirect_to(@user, :notice => 'User was successfully created.') }
        format.xml  { render :xml => @user, :status => :created, :location => @user }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
      end
    end
  end

  # PUT /users/1
  # PUT /users/1.xml
  def update
    @user = User.find(params[:id])

    respond_to do |format|
      if @user.update_attributes(params[:user])
        format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /users/1
  # DELETE /users/1.xml
  def destroy
    @user = User.find(params[:id])
    @user.destroy

    respond_to do |format|
      format.html { redirect_to(users_url) }
      format.xml  { head :ok }
    end
  end
end

Again, although this is a generated view, I’ll include it here anyway. Here’s our _form.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% @user.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>
  <% if @current_method == "new" %>
    <div class="field">
      <%= f.label :password %><br />
      <%= f.password_field :password %>
    </div>
    <div class="field">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation %>
    </div>
  <% end %>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Our edit.html.erb:

1
2
3
4
5
6
<h1>Editing user</h1>

<%= render 'form' %>

<%= link_to 'Show', @user %> |
<%= link_to 'Back', users_path %>

Our index.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<h1>Listing users</h1>

<table>
  <tr>
    <th>Email</th>
    <th>Username</th>
  </tr>

  <% @users.each do |user| %>
    <tr>
      <td><%= user.email %></td>
      <td><%= user.username %></td>
      <td><%= link_to 'Show', user %></td>
      <td><%= link_to 'Edit', edit_user_path(user) %></td>
      <td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
    </tr>
  <% end %>
</table>

<br />

<%= link_to 'New user', new_user_path %>

Our new.html.erb:

1
2
3
4
5
<h1>New user</h1>

<%= render 'form' %>

<%= link_to 'Back', users_path %>

And the last… our show.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<p id="notice"><%= notice %></p>

<p>
  <b>Email:</b>
  <%= @user.email %>
</p>

<p>
  <b>Username:</b>
  <%= @user.username %>
</p>


<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

6. Using the Devise

As noted by Peter in the comment section, I forget to put this step in my original post. In order Devise to really work, we have to put this line in every controller that can only be accessed by authenticated users.

1
before_filter :authenticate_user!

Next Step

Well, it’s a long post already… . I guess it’s better for us to take a break. So, this is the first part. We have not used CanCan yet, but now we have a working user management feature as we wanted it in the beginning. Hope this post helps you with your own project using Devise. I’ll write the second part where we will use CanCan as soon as possible.

9 responses so far

Hello world!

Nov 24 2010 Published by thepanakawan under Tech

Of course we can’t start anything without, “Hello world!”. It’s a taboo to skip this part, isn’t it?

No responses yet