Virtual Attributes in Rails 5

Objective

To learn how to use virtual attributes to map the fields in the view to the fields in a table in Rails 5.

Steps

Step 1

You can list all the columns of the users table like this in the rails console:

> User.new.attributes
 => {"id"=>nil, "first_name"=>nil, "middle_initial"=>nil, "last_name"=>nil, "created_at"=>nil, "updated_at"=>nil, "password"=>nil} 

We can get the columns of the users table by listing the keys of this hash.

 > User.new.attributes.keys
 => ["id", "first_name", "middle_initial", "last_name", "created_at", "updated_at", "password"] 

Step 2

Define the routes for the users resource in routes.rb file.

Rails.application.routes.draw do  
  resources :users
end

Step 3

Create the form for a new user registration in app/views/users/new.html.erb:

<%= form_for(@user) do |f| %>
  <div class="field">
    <%= f.label :first_name %>
    <%= f.text_field :first_name %>
  </div>

  <div class="field">
    <%= f.label :last_name %>
    <%= f.text_field :last_name %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <%= f.password_field :password %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

This form collects the first_name, last_name and the password of the user.

Step 4

You can see the columns defined in the users table in schema.rb:

create_table "users", force: :cascade do |t|
  t.string   "first_name"
  t.string   "middle_initial"
  t.string   "last_name"
  t.datetime "created_at",     null: false
  t.datetime "updated_at",     null: false
  t.string   "password"
end

The fields in the view is stored in the corresponding column in the users table. There is a one-to-one mapping between the fields in the view and the fields in the table.

Step 5

Let's change the form to collect the full_name from the user:

<%= form_for(@user) do |f| %>
  <div class="field">
    <%= f.label :full_name %>
    <%= f.text_field :full_name %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <%= f.password_field :password %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

How can me map the full_name field in the view to the first_name and last_name columns in the database?

Step 6

Browse to http://localhost:3000/users/new. You will get the error:

undefined method `full_name' for #<User:0x007f89a0>

Step 7

Define the full_name setter in the user model.

def full_name=(name)
  split = name.split(' ', 2)
  self.first_name = split.first
  self.last_name = split.last
end

Step 8

Browse to http://localhost:3000/users/new. You still get the error:

undefined method `full_name' for #<User:0x007bc0de8>
Did you mean?  full_name=

Step 9

Define the full_name getter in the user model:

def full_name
  [first_name, last_name].join(' ')
end

Step 10

Fill out the registration form and submit. In the development.log file, you can see the data submitted to the server.

Started POST "/users" for ::1 at 2016-03-11 17:41:55 -0800
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"Vf1Zb/3Zt", "user"=>{"full_name"=>" Pluto Dog", "password"=>"[FILTERED]"}, "commit"=>"Create user"}
No template found for UsersController#create, rendering head :no_content
Completed 204 No Content in 2ms (ActiveRecord: 0.0ms)

The user's full_name and password is sent to the server.

Step 11

The users controller that implements index, new and create actions looks like this:

class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def new
    @user = User.new
  end

  def create
    User.create(params.require(:user).permit(:full_name, :password))

    redirect_to users_path
  end
end

Step 12

When you register, you can see the data sent to the server and the insert sql command in the development.log file.

Started POST "/users" for ::1 at 2016-03-11 17:44:18 -0800
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"8ymtPpm", "user"=>{"full_name"=>" Pluto Dog", "password"=>"[FILTERED]"}, "commit"=>"Create user"}
   (0.0ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "users" ("first_name", "last_name", "created_at", "updated_at", "password") VALUES (?, ?, ?, ?, ?)  [["first_name", "Pluto"], ["last_name", "Dog"], ["created_at", 2016-03-12 01:44:18 UTC], ["updated_at", 2016-03-12 01:44:18 UTC], ["password", "123456"]]
   (0.4ms)  commit transaction
Redirected to http://localhost:3000/users
Completed 302 Found in 10ms (ActiveRecord: 1.6ms)

After creating the user we are redirected to the users index page.

Started GET "/users" for ::1 at 2016-03-11 17:44:18 -0800
Processing by UsersController#index as HTML
  User Load (0.1ms)  SELECT "users".* FROM "users"
  Rendered users/index.html.erb within layouts/application (1.4ms)
Completed 200 OK in 24ms (Views: 21.6ms | ActiveRecord: 0.1ms)

Step 13

We can verify the new user record in the rails console:

> User.last
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
 => #<User id: 1, first_name: "Pluto", middle_initial: nil, last_name: "Dog", created_at: "2016-03-12 01:44:18", updated_at: "2016-03-12 01:44:18", password: "123456"> 

Summary

In this article, you learned about virtual attributes and how you can use it in Rails 5 app to map fields in the view to the fields in the database.


Related Articles


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.