rock_n_rolla
rock_n_rolla

Reputation: 357

Cannot set new attribute on User model (using Devise & Rails 5)

I've added a new attribute/column (locale) to my User model (Devise). I will use this to save the User's chosen language to show the app in. This is selected by the user on the default Devise sign up form.

I have added the devise_parameter_sanitizer method to my application controller to permit the new parameter but everytime I add a new user, the locale column is always blank in the database.

If I follow the logs, I can see that the parameter is passed through to the create action, but for some reason it's lost somewhere in the middle.

Would love some guidance on what I'm doing wrong here.

Log when creating a new user

    Started POST "/users" for 127.0.0.1 at 2019-01-19 12:09:25 +0100
    Processing by Devise::RegistrationsController#create as HTML
    Parameters: {"utf8"=>"✓", "authenticity_token"=>"O3FEXmGBOnipEOLXkxNpRCKmpYVa5gc8oM2wauJbNt1iechdMQslDYZRSOgNEWeqsVFxcOQnxR2tbM2zNfAayw==", "user"=>{"email"=>"testytesty@testytesty1.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "locale"=>"sv", "commit"=>"Sign up"}
    (0.1ms)  begin transaction
    ↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
    User Exists (0.5ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "testytesty@testytesty1.com"], ["LIMIT", 1]]
    ↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
    User Create (1.2ms)  INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["email", "testytesty@testytesty1.com"], ["encrypted_password", "$2a$11$EYTMfLLydcUvPjBjKcKxFe67OsTvwJao/SvP8PFrkctC95SkDQJDq"], ["created_at", "2019-01-19 11:09:25.326356"], ["updated_at", "2019-01-19 11:09:25.326356"]]

Schema

create_table "users", force: :cascade do |t|
  t.string "email", default: "", null: false
  t.string "encrypted_password", default: "", null: false
  t.string "reset_password_token"
  t.datetime "reset_password_sent_at"
  t.datetime "remember_created_at"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.string "locale"
  t.index ["email"], name: "index_users_on_email", unique: true
  t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
 end

Application controller

class ApplicationController < ActionController::Base
    protect_from_forgery with: :exception, unless: -> { request.format.json? }
    before_action :configure_permitted_parameters, if: :devise_controller?
    before_action :authenticate_user!
    before_action :belongs_to_user
    before_action :set_locale

    def after_sign_in_path_for(resource_or_scope)
        # Set a default current company scope for a use after signing in
        session[:current_company] = current_user.companies.first.id unless current_user.companies.empty?
        puts session[:current_company]
        companies_path
    end

    def set_locale
        I18n.locale = current_user.try(:locale) || I18n.default_locale
    end

    def belongs_to_user
        unless params[:controller] == 'devise/sessions' || params[:access_token].present? # TODO this is a potential security gap - patch this up so that it can't just any old token, it must be a valid one (look in the captables controller)
        @current_user_companies = current_user.companies unless !user_signed_in?

        if !params[:company_id].present?
            @company = Company.find(params[:id]) unless !params[:id].present?
        else
            @company = Company.find(params[:company_id]) unless !params[:company_id].present?
        end

        unless !@company
            if !@current_user_companies.include?(Company.find(@company.id))
            redirect_to companies_path, alert: "Access denied: You do not belong to this company"
            end
        end
        end
    end

    protected

    def configure_permitted_parameters
        # Permit some additional params on the user sign up that aren't the default email, password etc
        devise_parameter_sanitizer.permit(:sign_up, keys: [:locale])
    end

end

My sign up form

    <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
    <%= devise_error_messages! %>

    <div class="field">
        <%= f.label :email %><br />
        <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
    </div>

    <div class="field">
        <%= f.label :password %>
        <% if @minimum_password_length %>
        <em>(<%= @minimum_password_length %> characters minimum)</em>
        <% end %><br />
        <%= f.password_field :password, autocomplete: "new-password" %>
    </div>

    <div class="field">
        <%= f.label :password_confirmation %><br />
        <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
    </div>

    <div class="field">
        <%= f.label :locale %><br />
        <%= select_tag(:locale, options_for_select([["English", "en"], ["Swedish", "sv"]])) %>
    </div>

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

Update: Full stack trace

Started POST "/users" for 127.0.0.1 at 2019-01-19 12:37:40 +0100
Processing by Devise::RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"pAL2lKhnw4VEFx+umYkGDp+d87hrCRZ/xR0JaYof5X4J8Y4Acj8SOzB85mm6heRtNMXHpIPaI9l8+oDCeuLH5A==", "user"=>{"email"=>"jameselliotpember@gmail.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "locale"=>"sv", "commit"=>"Sign up"}
(0.1ms)  begin transaction
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "jameselliotpember@gmail.com"], ["LIMIT", 1]]
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
User Create (0.4ms)  INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["email", "jameselliotpember@gmail.com"], ["encrypted_password", "$2a$11$N5A/.cfWXnAa/3upKDYlwOxLCt4zc8WPe2Jce.A60A3.SFR4jgoxe"], ["created_at", "2019-01-19 11:37:40.625528"], ["updated_at", "2019-01-19 11:37:40.625528"]]
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
(1.8ms)  commit transaction
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
(0.1ms)  begin transaction
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
LoginActivity Create (0.6ms)  INSERT INTO "login_activities" ("scope", "strategy", "identity", "success", "user_type", "user_id", "context", "ip", "user_agent", "referrer", "created_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["scope", "user"], ["strategy", "database_authenticatable"], ["identity", "jameselliotpember@gmail.com"], ["success", 1], ["user_type", "User"], ["user_id", 1], ["context", "devise/registrations#create"], ["ip", "127.0.0.1"], ["user_agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"], ["referrer", "http://localhost:3000/users/sign_up"], ["created_at", "2019-01-19 11:37:40.630791"]]
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
(1.0ms)  commit transaction
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
[ActiveJob] Enqueued AuthTrail::GeocodeJob (Job ID: 3c096920-93b2-45aa-ba0d-3c9ff5a1466f) to Async(default) with arguments: #<GlobalID:0x00007f9698b7c510 @uri=#<URI::GID gid://calmcap/LoginActivity/2>>
LoginActivity Load (0.4ms)  SELECT  "login_activities".* FROM "login_activities" WHERE "login_activities"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
[ActiveJob] [AuthTrail::GeocodeJob] [3c096920-93b2-45aa-ba0d-3c9ff5a1466f] Performing AuthTrail::GeocodeJob (Job ID: 3c096920-93b2-45aa-ba0d-3c9ff5a1466f) from Async(default) with arguments: #<GlobalID:0x00007f9698b4bbe0 @uri=#<URI::GID gid://calmcap/LoginActivity/2>>
[ActiveJob] [AuthTrail::GeocodeJob] [3c096920-93b2-45aa-ba0d-3c9ff5a1466f]    (0.1ms)  begin transaction
[ActiveJob] [AuthTrail::GeocodeJob] [3c096920-93b2-45aa-ba0d-3c9ff5a1466f]   ↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
[ActiveJob] [AuthTrail::GeocodeJob] [3c096920-93b2-45aa-ba0d-3c9ff5a1466f]    (0.0ms)  commit transaction
[ActiveJob] [AuthTrail::GeocodeJob] [3c096920-93b2-45aa-ba0d-3c9ff5a1466f]   ↳ /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
[ActiveJob] [AuthTrail::GeocodeJob] [3c096920-93b2-45aa-ba0d-3c9ff5a1466f] Performed AuthTrail::GeocodeJob (Job ID: 3c096920-93b2-45aa-ba0d-3c9ff5a1466f) from Async(default) in 2.8ms
Company Exists (0.1ms)  SELECT  1 AS one FROM "companies" WHERE "companies"."user_id" = ? LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:10

Redirected to http://localhost:3000/companies
Completed 302 Found in 175ms (ActiveRecord: 4.5ms)

Upvotes: 1

Views: 335

Answers (2)

Giridharan
Giridharan

Reputation: 568

Try This!

change

 <%= select_tag(:locale, options_for_select([["English", "en"], ["Swedish", "sv"]])) %>

TO

 <%= f.select :locale, options_for_select([["English","en"],["Swedish","sv"]]), {include_blank: "locale"} %>

Upvotes: 2

Sergey Mell
Sergey Mell

Reputation: 8050

If you check your logs you will see that your locale param does not come inside user scope. I've reformatted this line:

    Parameters: {
      "utf8"=>"✓",
      "authenticity_token"=>"...", 
      "user"=>{
         "email"=>"testytesty@testytesty1.com", 
         "password"=>"[FILTERED]", 
         "password_confirmation"=>"[FILTERED]"
      }, 
      "locale"=>"sv", 
      "commit"=>"Sign up"
}

So, you should add it inside user group of params.

As I understand this had happened because you didn't bind it to the form. You should use f.select instead of select_tag in your form.

Upvotes: 2

Related Questions