Trying to add a nested custom attribute, Profile (a Mongoid document), to my devise User class. When the Devise registration form is submitted, it should create both a User and a corresponding Profile object as well.
I'd like the end-result to look something like this in my MongoDB:
User:
{
# Devise fields:
"email": "my@email.com",
...
# Custom field
"profile" : "<object_id>"
}
Profile:
{
"first_name": "Dave",
....
}
Unfortunately, I am receiving this in my console whenever I submit my registration. It successfully creates a User but fails to create an associated Profile.
Started POST "/" for 127.0.0.1 at 2013-04-20 23:37:10 -0400
Processing by Users::RegistrationsController#create as HTML
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"awN2GU8EYEfisU0",
"user"=>
{"profile_attributes"=>
{"first_name"=>"Dave",
"birthday(2i)"=>"4",
"birthday(3i)"=>"21",
"birthday(1i)"=>"1933",
"occupation_title"=>"Software Developer"},
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"email"=>"my@email.com"}}
Unpermitted parameters: profile_attributes
I have setup:
- Rails 4.0.0beta1, Ruby 2.0.0-p0
- Devise ('rails4' branch), Mongoid (from git)
- A custom Devise registrations controller to add a definition for strong parameters.
models/user.rb:
class User
include Mongoid::Document
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:token_authenticatable, :confirmable, :lockable, :timeoutable
field :email, type: String, default: ''
...
has_one :profile
accepts_nested_attributes_for :profile
end
models/profile.rb:
class Profile
include Mongoid::Document
include Mongoid::Timestamps
# Attributes
# ----------
field :slug, type: String, default: '' # Acts as user-'friendlier' slug
field :birthday, type: DateTime, default: DateTime.now
field :first_name, type: String, default: ''
field :occupation_title, type: String, default: ''
belongs_to :user
embeds_many :photos
has_one :occupation_industry, :as => :industry
end
controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
def resource_params
params.require(:user).permit(:email, :password, :password_confirmation, :profile_attributes)
end
private :resource_params
end
routes.rb
devise_for :users,
:path => '',
:path_names => {
:sign_in => 'login',
:sign_out => 'logout',
:sign_up => 'register'
},
:controllers => {
:registrations => "users/registrations",
:passwords => "users/passwords"
}
I have already looked at these related posts, they didn't seem to help:
- Rails 4 Nested Attributes Unpermitted Parameters
- https://gist.github.com/kazpsp/3350730
EDIT:
Looks like Devise does actually support strong parameters in its 'rails4' branch (which is supposed to be merged into master in a few days.) Looking through the code, it appears you can override a params function for each action on devise controllers. For creating new users, its sign_up_params
instead of resource_params
in my example.
Despite changing this name to the proper one, it still didn't work... only whitelisting all parameters using this bang seemed to work:
def sign_up_params
params.require(:user).permit!
end
Obviously, this kind of defeats the purpose of strong parameters... so now the question is how do I permit my nested attributes profile_attributes
(as seen in my original question)?
I had the exact same issue and overriding sign_up_params did work for me
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, :other, :etc)
end
of course, the difference is in that mine are just scalar values, while you're trying to mass assign a relation... I guess that's where you should look for.
By the way, the documentations is still inexistint in this topic (too new), and code commnents suggest to override devise_parameter_sanitizer, which isn't necessary.
I found a different method that allows all the devise overriding logic and code to reside in the application controller. This allows any and all custom params to be passed through for each devise action (sign in, sign up, update). I also add a parameter sanitizer for devise_invitable and handle that logic here (invite, accept_invitation). I've got custom params like avatar, avatar_cache, etc:
#application_controller.rb
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
# There are just three actions in Devise that allows any set of parameters to be passed down to the model,
# therefore requiring sanitization. Their names and the permited parameters by default are:
# sign_in (Devise::SessionsController#new) - Permits only the authentication keys (like email)
# sign_up (Devise::RegistrationsController#create) - Permits authentication keys plus password and password_confirmation
# account_update (Devise::RegistrationsController#update) - Permits authentication keys plus password, password_confirmation
# and current_password. More at https://github.com/plataformatec/devise#strong-parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:username,:validate_username, :password,:password_confirmation, :invitation_token)
end
devise_parameter_sanitizer.for(:invite) do |u|
u.permit(:name,:comments)
end
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:username,:password,:password_confirmation)
end
devise_parameter_sanitizer.for(:sign_in) do |u|
u.permit(:username,:email,:password,:password_confirmation,:phone, :validate_username, :avatar_cache, :remove_avatar, :current_password,:remember_me)
end
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:username,:email,:password,:password_confirmation,:phone, :validate_username,:avatar, :avatar_cache, :remove_avatar, :current_password)
end
end
Find and read more at https://github.com/plataformatec/devise#strong-parameters
I had the same issue when login, it says: Unpermitted parameters: password, remember_me
.
and because i have any controller that inheriting Devise::SessionsController, so i use my own parameter sanitizer.
here is what i do:
Create a file in '#{Rails.root}/lib' fold, my is hzsapa_parameter_sanitizer.rb
and required in config/application.rb, then override devise_parameter_sanitizer
method in application_controller.rb
lib/hzsapa_parameter_sanitizer.rb
class HzsapaParameterSanitizer < Devise::ParameterSanitizer
def sign_in
default_params.permit(auth_keys + [:password, :remember_me])
end
end
You can override those method depends on your issue:
def sign_in
default_params.permit(auth_keys)
end
def sign_up
default_params.permit(auth_keys + [:password, :password_confirmation])
end
def account_update
default_params.permit(auth_keys + [:password, :password_confirmation, :current_password])
end
config/application.rb
require "hzsapa_parameter_sanitizer"
app/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def devise_parameter_sanitizer
@devise_parameter_sanitizer ||= if defined?(ActionController::StrongParameters)
HzsapaParameterSanitizer.new(resource_class, resource_name, params)
else
Devise::BaseSanitizer.new(resource_class, resource_name, params)
end
end
end
Edit: i just found the solution in devise README, you can follow it here
I used your code and it worked for me!
Here is what I did
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token, :only => :create #, :if => Proc.new { |c| c.request.format == 'application/json' }
respond_to :json, :html, :xml
def create
user = User.new(devise_registrations_permitted_parameters)
if user.save
render :json=> user.as_json(:auth_token=>user.authentication_token, :email=>user.email,:name => user.name), :status=>201
return
else
warden.custom_failure!
render :json=> user.errors, :status=>422
end
end
protected
def devise_registrations_permitted_parameters
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end