Jisu's Blog

Breathe Deep!

NYC Big Apps 2014

Last week when I was trying to figure out when the rain would stop I decided to check weather.com and this was what I saw.

image

Revisiting Mass Assignments With Params

Considering there isn’t much information on params I wanted to write a blog post on it. However during the past week at the Flatiron School, we were forwarded two informative blog posts on params. You can check them out here:

http://codefol.io/posts/How-Does-Rack-Parse-Query-Params-With-parse-nested-query

http://surrealdetective.github.io/blog/2013/07/01/the-nested-ruby-params-hash-for-complex-html-forms-and-sinatra/

Rather than reinvent the wheel I decided to add to the material mentioned in the blog posts above. One of the key points we learned during lecture was to use proper naming conventions so that we could do a mass assignment with params. For example:

users_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class UsersController < ApplicationController

  get '/users/new' do
    erb :"users/new.html"
  end

  post '/users' do

      @user = User.new
      @user.name = params[:name]
      @user.email = params[:email]
      @user.password = params[:password]
      @user.save

      redirect "/users/#{@user.id}"
  end

  get '/users/:id' do
    @user = User.find(params[:id])
    erb :"users/show.html"
  end

end

Using mass assignment, lines 10-12 above can be replaced with @user=User.new(params[:user]) resulting in the following.

users_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class UsersController < ApplicationController

  get '/users/new' do
    erb :"users/new.html"
  end

  post '/users' do

      @user = User.new(params[:user])
      @user.save

      redirect "/users/#{@user.id}"
  end

  get '/users/:id' do
    @user = User.find(params[:id])
    erb :"users/show.html"
  end

end

There are only three attributes(name, email, password) but as attributes are added one can quickly see how mass assignment will be useful.

Using the same example, the trick to utilizing mass assignment is to get params to return a hash with the following structure:

users_controller.rb
1
2
3
4
5
6
7
params == {
            :user => {
              :name => "John Doe",
              :email => "john.doe@flatironschool.com",
              :password => "123456"
            }
          }

To get params to have that type of structure, one needs to properly name the form inputs for the name attribute.

new.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form action="/users" method="POST">
  <p>
    <label for="name">Name</label><br/>
    <input type="text" name="user[name]" id="name" />
  </p>
  <p>
    <label for="email">Email</label><br/>
    <input type="text" name="user[email]" id="email" />
  </p>
  <p>
    <label for="pwd">Password</label><br/>
    <input type="text" name="user[password]" id="pwd" />
  </p>
   <input type="submit" value="Submit" />
</form>

Notice within the <input> controls: name="user[name]", name="user[email]", name="user[password]". Using this naming convention we are able to get back the desired params hash because both user and [attribute] are keys to the input value.

Also because our naming convention will return a hash – the order of which the form labels are arranged does not affect the mass assignment. However, the last key value must be identical to the column name of the attribute assigned.

Based on the naming convention above our migration table should look like below

(Note: I mixed up the order of the columns to show that order doesn’t matter but the column names must match the last key of the params hash)

01_create_users.rb
1
2
3
4
5
6
7
8
9
10
class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :password  #order does not have to match the <form> inputs 
      t.string :email
      t.string :name
      t.timestamp
    end
  end
end

Column names must match the last key of the params hash for mass assignment.

  1. Line 6 :name matches the last key of the name= attribute <input type="text" name="user[name]"/>

  2. Line 5 :email matches the last key of the name= attribute <input type="text" name="user[email]"/>

  3. Line 4 :password matches the last key of the name= attribute <input type="text" name="user[password]"/>

If I named the attribute pwd <input type="text" name="user[pwd]"> while the table column is named :password I would get the following error:

image

Therefore the column names from the migration table must match the last key of the params hash.

My ‘Aha’ Moments From the Weekend

This past week was a bit hectic for me considering the pace picked up at the Flatiron School and on top I had to prepare for a meetup. I managed to make it through the week and after getting 12 hrs of much needed sleep on Saturday I went about reviewing the recorded lectures. By revisiting these lectures I had several ‘Aha’ moments and would like to share them. I apologize in advance if you already know this but for me…

image

1. Modules should have abstract values

Don’t rely on literal values! A major problem with relying on literal values is scope. In the example below on line 4 the @@all variable should be encapsulated in a method.

1
2
3
4
5
6
7
module Rankable
  module ClassMethods
    def top_5
      @@all.sort_by {|s|s.rank}[0..4]
    end
  end
end

The fix is to encapsulate @@all in a method resulting in the following code.

1
2
3
4
5
6
7
module Rankable
  module ClassMethods
    def top_5
      self.all.sort_by {|s|s.rank}[0..4]
    end
  end
end

2. Order Matters

When listing the files to load up it should be

1. Gems 
2. Concerns(Modules) 
3. Models(Classes)

The following code would break because the module line 3 is loaded after the model(Student Class)

1
2
3
require 'sqlite3'
require_relative '../lib/student' #class Student
require_relative '../lib/concerns/persistable' #module Persistable

The fix is to load it in the correct order.

1
2
3
require 'sqlite3'
require_relative '../lib/concerns/persistable' #module Persistable
require_relative '../lib/student' #class Student

3. Be Weary of Class Variables(@@)

Class variables can leak due to inheritance. For example in the following code when a new Dog instance is created the @@breeds will be overwritten to [:affenpoo].

1
2
3
4
5
6
7
class Dog
  @@breeds = [:poodle, :pug]
end

class Poodle < Dog
  @@breeds = [:affenpoo]
end

To avoid possible leaks, class CONSTANTS should be used. The following would fix the problem above and when a new Dog instance is created the BREEDS constant in the Dog class will remain [:poodle, :pug]. Likewise the BREEDS constant in the Poodle class will remain [:affenpoo].

1
2
3
4
5
6
7
class Dog
  BREEDS = [:poodle, :pug]
end

class Poodle < Dog
  BREEDS = [:affenpoo]
end

4. All Methods Must Have a Receiver

All methods must have a receiver and if not defined it is self.

1
2
3
4
5
6
class Movie
  attr_accessor :name
  def save
    DB.execute("INSERT INTO movies(name) VALUES (?)", name)
  end
end

In line 4 the last argument ‘name’ is not a variable. The way ruby determines this is by first looking for a variable called ‘name’. If not defined it then looks for method called name which is defined on line 2 “attr_accessor :name”.

5. Found Another Tool – The Tap Method

What the tap method does is yields x to the block and then returns x.

1
2
3
4
5
6
7
def self.new_from_db(row)
  s=self.new
  s.id=row[0]
  s.name=row[1]
  s.tagline=row[2]
  s
end

Using the tap method

1
2
3
4
5
6
7
def self.new_from_db(row)
  self.new.tap do |s|
    s.id=row[0]
    s.name=row[1]
    s.tagline=row[2]
  end
end