Question

I am using the clearance gem, which is basically an authentication by e-mail.

Now I want to keep the log in via email, but want to add a 'name' field into the registration form for the user.

User.rb

class User < ActiveRecord::Base
  include Clearance::User

  validates_presence_of :name
  validates_uniqueness_of :name
end

users_controller.rb

class UsersController < Clearance::UsersController
  def create
    @user = user_from_params

    if @user.save(permit_params)
      sign_in @user
      render :json => {:success => true}
    else
      render :json => {:success => false}
    end
  end

  private

  def user_from_params
    user_params = params[:user] || Hash.new
    email = user_params.delete(:email)
    password = user_params.delete(:password)

    Clearance.configuration.user_model.new(user_params).tap do |user|
      user.email = email
      user.password = password
    end
  end

  def permit_params
    params.require(:user).permit(:name, :email, :encrypted_password, :password, :confirmation_token, :remember_token)
  end
end

As you can see I added the permission to .save and still the app throws me the following error upon request:

ActiveModel::ForbiddenAttributesError in Clearance::UsersController#create

If I remove the :name text field from my form everything works, but I want additional fields for my form.

Was it helpful?

Solution

You need to put your users_controller into folders like this:

app/controllers/clearance/users_controller.rb

Then in users_controller.rb, instead of:

class UsersController < Clearance::UsersController

you need to put:

class Clearance::UsersController < ApplicationController

This doesn't override the Clearance User Controller. Instead, now you are writing it. This is the controller that your sign-up form will go to. Thus, you still need to include the base code from Clearance's gem and edit Clearance::UsersController#user_from_params

def user_from_params
  user_params = params[:user] || Hash.new
  email = user_params.delete(:email)
  password = user_params.delete(:password)
  name = user_params.delete(:name)

  Clearance.configuration.user_model.new(user_params).tap do |user|
    user.email = email
    user.password = password
    user.name = name
  end
end

and update Clearance::UsersController#permit_params

def permit_params
  params.require(:user).permit(:name, :email, :password)
end

OTHER TIPS

UPDATE: I've changed my previous answer since it was missing the point.

The code you've provided is fine. The reason you're getting ActiveModel::ForbiddenAttributesError is because you're not overriding the Clearance Users Controller.

You need to tell your app to use your overriding controller instead of the controller inside Clearance's engine, by adding the following to your config/routes.rb.

resources :users,
  controller: 'users',
  only: 'create'

IMO this is better than writing a new Clearance::UsersController in your application code above.

You actually don't need to reimplement create method, just in case if you're creating an API (what looks like).

The steps are:

  • config/routes.rb

    Rails.application.routes.draw do
      resources :users, controller: 'users', only: Clearance.configuration.user_actions
    end
    
  • app/controllers/users_controller.rb

    class UsersController < Clearance::UsersController
    
      private
    
      def user_from_params
        email = user_params.delete(:email)
        password = user_params.delete(:password)
        name = user_params.delete(:name)
    
        Clearance.configuration.user_model.new(user_params).tap do |user|
          user.email = email
          user.password = password
          user.name = name
        end
      end
    
    end
    

After that, add the name field into your form and it's done.

I tried both ways and came to the realization that you don't need the permit params. You only need to do 4 things:

  • Add a name column in the migration
  • Create app/controllers/users_contoller.rb to override gem controller
  • Create the new form to accept name app/views/users/new.html.erb
  • Modify your routes

Starting from the generated migration rails generate clearance:install (before running rake db:migrate)

  1. add a name:string column with an index such so that your migration looks like: by adding this column, it is included in the creation of the user params.

    def change
      change_table :users do |t|
        t.timestamps null: false
        t.string :email, null: false
        t.string :name, null: false, limit: 50
        t.string :encrypted_password, limit: 128, null: false
        t.string :confirmation_token, limit: 128
        t.string :remember_token, limit: 128, null: false
      end
    
      add_index :users, :name
      add_index :users, :email
      add_index :users, :remember_token
    end
    

Then you only need to create 2 files:

app/controllers/users_controller.rb

class UsersController < Clearance::UsersController

  def create
    @user = user_from_params

    if @user.save
      sign_in @user
      redirect_to '/'
    else
      render template: 'users/new'
    end 
  end 

  private

  def user_from_params
    user_params = params[:user] || Hash.new
    name = user_params.delete(:name)
    email = user_params.delete(:email)
    password = user_params.delete(:password)

    Clearance.configuration.user_model.new(user_params).tap do |user|
      user.name = name
      user.email = email
      user.password = password
    end
  end
end

and

app/views/users/new.html.erb

<div id='clearance' class='sign-up'>
  <h2><%= t('.title') %></h2>

  <%= form_for @user do |form| %>
  <div class='text-field'>
    <%= form.label :name %>
    <%= form.text_field :name, :type => 'name' %>
  </div>

  <div class='text-field'>
    <%= form.label :email %>
    <%= form.text_field :email, :type => 'email' %>
  </div>

  <div class='password-field'>
    <%= form.label :password %>
    <%= form.password_field :password %>
  </div>

  <div class='submit-field'>
    <%= form.submit %>
  </div>

  <div class='other-links'>
    <%= link_to t('.sign_in'), sign_in_path %>
  </div>
  <% end %>
</div>

Then your routes should include this

resources :users,
    controller: 'users',
    only: 'create'
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top