Question

I try to change my rails-3.2-model to rails 4, but I dont understand it.
Maybe you can help me to change it.
3.2:

class User < ActiveRecord::Base
  attr_accessible :email, :username, :password, :password_confirmation
  attr_accessor :password
  before_save :encrypt_password

  validates_confirmation_of :password
  validates_presence_of :password, :on => :create
  validates_presence_of :email, :on => :create
  validates_presence_of :username, :on => :create
  validates_uniqueness_of :email
  validates_uniqueness_of :username

  def self.authenticate(email, password)
    user = find_by_email(email)
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil
    end
  end

  def encrypt_password
    if password.present?
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end
end

4.0.4:

class User < ActiveRecord::Base

  validates :name, presence: true, uniqueness: {case_sensitive: true}, length: {minimum: 3, too_short: "must have at least %{count} characters"}
  validates :email, presence: true, uniqueness: {case_sensitive: true}
  validates :password_hash

end

I tried to get rid off the attr_accessible and attr_accessor, but I don't know how.
attr_accessor :password and attr_accessible [...] :password_confirmation are not stored in the database, so how can I use it in my view?

EDIT:
View:

<p>Sign Up</p>
    <%= form_for @user, :as => :user, :url => auth_sign_up_path, :html => {:class => 'navbar-form', :role => 'login'} do |user_form_builder| %>
        <p>
          <%= user_form_builder.label 'name:' %><br/>
          <%= user_form_builder.text_field :name %>
          <%= show_field_error(@user, :name) %>
        </p>
        <p>
          <%= user_form_builder.label 'email:' %><br/>
          <%= user_form_builder.text_field :email %>
          <%= show_field_error(@user, :email) %>
        </p>
        <p>
          <%= user_form_builder.label 'password:' %><br/>
          <%= user_form_builder.password_field :password %>
          <%= show_field_error(@user, :password) %>
        </p>
        <p>
          <%= user_form_builder.label 'password confirmation:' %><br/>
          <%= user_form_builder.password_field :password_confirmation %>
          <%= show_field_error(@user, :password_confirmation) %>
        </p>
        <p>
          <%= user_form_builder.submit 'Sign Up' %>
          <%= user_form_builder.submit 'Clear Form', :type => 'reset' %>
        </p>
    <% end %>

Controller:

def sign_up
    @user = User.new
  end

  def register
    @user = User.new(user_params)

    if @user.valid?
      @user.save
      session[:user_id] = @user.id
      flash[:notice] = 'Welcome.'
      redirect_to :root
    else
      render :action => "sign_up"
    end
  end

private
  def user_params
    params.require(:user).permit(:email, :name, :password, :password_confirmation)
  end

Model:

require 'bcrypt'
class User < ActiveRecord::Base

  attr_accessor :name, :email, :password, :password_confirmation
  before_save :encrypt_password
  after_save :clear_password

  validates :name, presence: true, uniqueness: {case_sensitive: true}, length: {minimum: 3, too_short: "must have at least %{count} characters"}
  validates :email, presence: true, uniqueness: {case_sensitive: true}
  validates :password, presence: true, length: {minimum: 8, too_short: "must have at least %{count} characters"}, :confirmation => true #password_confirmation attr

  def initialize(attributes = {})
    super # must allow the active record to initialize!
    attributes.each do |name, value|
      send("#{name}=", value)
    end
  end

  def self.authenticate_by_email(email, password)
    user = find_by_email(email)
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil
    end
  end

  def self.authenticate_by_name(name, password)
    user = find_by_username(name)
    if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
      user
    else
      nil
    end
  end

  def encrypt_password
    if password.present?
      self.password_salt = BCrypt::Engine.generate_salt
      self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
    end
  end

  def clear_password
    self.password = nil
  end

end

Migration:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :password_hash
      t.string :password_salt
      t.string :cal_owner, :array => true, :default => '{}'
      t.string :cal_joined, :array => true, :default => '{}'

      t.timestamps
    end
  end
end

Routes:

Calendar::Application.routes.draw do

  # You can have the root of your site routed with "root"
  root 'welcome#index'

  get "auth/sign_up" => "auth#sign_up"
  get "auth/sign_in" => "auth#sign_in"
  get "auth/sign_out" => "auth#sign_out"
  get "auth/settings"
  get "auth/pwd_reset"
  get "welcome/index"

  post "auth/sign_in" => "auth#login"
  post "auth/sign_up" => "auth#register"
end

Database

I used a tutorial, but I didnt know why the author add this:

def initialize(attributes = {})
        super # must allow the active record to initialize!
        attributes.each do |name, value|
          send("#{name}=", value)
        end
      end

The author wrote:

For each key-value pair (hash) we assign the value to the attribute by calling the "send" function (all method calls in Ruby are actually messages.)

Important:

We don't actually need to do this for the User class because the constructor provided by Rails will allow us to do a "mass assign" from a hash as long as the fields that we are assigning have been designated as "attr_accessible", which they have. However, there are cases when one wants to initialize several fields (such as ID's in a many-to-many table) that are not intended to be accessible to a view but instead are designated with an "attr_accessor" instead. The above function is a simple way of providing safe mass assign capability for internal constructors.

Was it helpful?

Solution

From what I now understand, your user table has effectively 4 columns in addition to the ID, created at etc.:

  • name
  • email
  • password_hash
  • password_salt

On your view you have fields:

  • name
  • email
  • password
  • password_confirmation

Your controller looks correct. The user_params method is whitelisting the 4 fields from the view and letting them be passed through to the User model for creating a new user.

In your model you need to make 2 changes.

Firstly you should remove name and email from the attr_accessor line:

attr_accessor :password, :password_confirmation

You only need password and password_confirmation. The reason for this is since name and email are columns on the database, Rails will automatically give you the getter and setter methods for these attributes. attr_accessor then saves you having to explicitly write getter and setter methods for password and password_confirmation and lets them be populated automatically on creating a new user with the values that come through from the view.

Secondly you should remove the initialize method. User inherits from ActiveRecord::Base and will be able to build new user records quite happily without the need for a constructor.

The author of the tutorial included the constructor for doing mass-assignment. However in Rails 4 this has been changed to use strong parameters so that it is now the responsibility of the controller to state which parameters can be passed to the model. You do this correctly in your user_params method in the controller.

I hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top