I am using Devise in my rails 4 app. I have a member model (for devise) and a user model that contains all the profile info for each member. A member accepts nested attributes for user. See the form below for a new registration as well as the custom routes in my routes.rb file.
My problem is this - everything works fine except validation of the user attributes. If I leave first name and last name blank, then it validates (but crashes like this:
But all the other validations for Member (the devise model) work - they end up on the /members page with the error message displayed as it should. I am unsure as to what is going on - shouldn't devise show the error messages for the invalid nested attributes?
class Members::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
build_resource({})
self.resource.user = User.new
respond_with self.resource
end
# POST /resource
def create
super
resource.user.ip_address = request.remote_ip
resource.user.email = resource.email
resource.user.save!
end
private
def sign_up_params
allow = [:provider, :uid, :email, :password, :password_confirmation, user_attributes: [:member_id, :email, :first_name, :last_name, :institution, :city, :country, :job_title, :about, :tag_list, :picture, :ip_address]]
params.require(resource_name).permit(allow)
end
end
Member.rb
class Member < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :trackable
has_one :user, dependent: :destroy, autosave: true
accepts_nested_attributes_for :user
end
User.rb
class User < ActiveRecord::Base
belongs_to :member
validates :first_name, presence: true
validates :last_name, presence: true
end
In my routes.rb
devise_for :members, controllers: {registrations: 'members/registrations',
omniauth_callbacks: 'members/omniauth_callbacks',
sessions: 'members/sessions' }
In my members/registrations/new.html.erb
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), html: { role: "form" }) do |f| %>
<%= f.fields_for(:user) do |user_fields| %>
<%= user_fields.text_field :first_name, autofocus: true, class: 'form-control', :placeholder => "First name" %>
<%= user_fields.text_field :last_name, class: 'form-control', :placeholder => "Last name" %>
<% end %>
<%= f.email_field :email, autofocus: true, class: "form-control", :placeholder => "Email" %>
<%= f.password_field :password, class: "form-control", :placeholder => "Password" %>
<%= f.submit t('.sign_up', :default => "Sign up"), class: "btn btn-danger" %>
<% end %>
Here is the log for this
Started POST "/members" for ::1 at 2015-07-16 15:18:54 +0100
Processing by Members::RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"sIxK1PTvrxNHUBdDXAtKFLLG27FvqypQ3mUvo398tHOMomi1S3I2o3vBM GwrF6PVN1NFBR5vnr8ezKP6XnMzqw==", "member"=>{"user_attributes"=>{"first_name"=>"", "last_name"=>""}, "email"=>"slfwalsh#gmail.com", "password"=>"[FILTERED]"}, "commit"=>"Sign up"}
(0.2ms) begin transaction
Member Exists (0.3ms) SELECT 1 AS one FROM "members" WHERE "members"."email" = 'slfwalsh#gmail.com' LIMIT 1
(0.1ms) rollback transaction
Rendered devise/shared/_links.erb (9.9ms)
Rendered members/registrations/new.html.erb within layouts/static_home_page (19.8ms)
Rendered layouts/_static_login_header.html.erb (4.1ms)
(0.1ms) begin transaction
(0.1ms) rollback transaction
Completed 422 Unprocessable Entity in 1499ms
The reason why you are seeing that page is because save! triggers validation errors if the validation for any of the attributes is failed and those errors are displayed in the page. On the other hand save doesn't display the validation errors but just cancels the transaction from being saved if any of the validations are failed. Changing your create method like below should solve your problem.
def create
super
resource.user.ip_address = request.remote_ip
resource.user.email = resource.email
resource.user.save
end
Related
What I'm trying to accomplish:
When a user registers with my app they are taken to a new account creation page. This is where they enter their desired subdomain. from this form I also want to create the owner (a user class).
The problem:
As it sits right now, when i fill out the generated form (below)
<%= form_for #account do |f| %>
<%= fields_for :owner do |o| %>
<p>
<%= o.label :f_name %>
<%= o.text_field :f_name %>
</p>
<p>
<%= o.label :m_name %>
<%= o.text_field :m_name %>
</p>
<p>
<%= o.label :l_name %>
<%= o.text_field :l_name %>
</p>
<p>
<%= o.label :email %>
<%= o.email_field :email %>
</p>
<p>
<%= o.label :password %>
<%= o.password_field :password %>
</p>
<p>
<%= o.label :password_confirmation %>
<%= o.password_field :password_confirmation %>
</p>
<% end %>
<p>
<%= f.label :subdomain %>
<%= f.text_field :subdomain %>
</p>
<%= f.submit %>
<% end %>
and try to submit the form, I get the following rails server output:
Started POST "/accounts" for 127.0.0.1 at 2018-04-08 21:52:57 -0600
Processing by AccountsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"4yUhk6N40udNBMoBJz/sFzbjC/RUtU7FVyHe9NlhtBkmpGEMZE0+xMcD7E6GLOjgp02hbkrbuMNLQ5gBjh+kvA==", "owner"=>{"f_name"=>"xxxxx", "m_name"=>"xxxxx", "l_name"=>"xxxxx", "email"=>"xxxxx#xxxxxnltd.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "account"=>{"subdomain"=>"testinga"}, "commit"=>"Create Account"}
(0.2ms) BEGIN
Account Exists (0.6ms) SELECT 1 AS one FROM "accounts" WHERE LOWER("accounts"."subdomain") = LOWER($1) LIMIT $2 [["subdomain", "testinga"], ["LIMIT", 1]]
(0.1ms) ROLLBACK
Rendering accounts/new.html.erb within layouts/application
Rendered accounts/new.html.erb within layouts/application (2.4ms)
Completed 200 OK in 49ms (Views: 21.5ms | ActiveRecord: 8.3ms)
Now when I read the output I cant seem to find why this is rolling back and not saving. I do see it telling me an account already exists whit that subdomain, however this is a CLEAN database and there are no accounts saved in it! When I run byebug just before the #account.save in the accounts controller (below) there are no error messages or details I can find.
My AccountController: (I've left the byebug in the controller, perhaps im putting it in the wrong place?)
class AccountsController < ApplicationController
def index
end
def show
end
def new
#account = Account.new
#account.build_owner
end
def create
#account = Account.new(account_params)
byebug
if #account.save
redirect_to root_path, notice: 'Account creates successfully.'
else
render action: 'new'
end
end
def edit
end
def update
end
def destroy
end
private
def account_params
params.require(:account).permit(:subdomain, :owner_id, :plan_id, :account_verified, :account_status, owner_attributes: [:id, :email, :password, :password_confirmation, :f_name, :m_name, :l_name, :office_country_code, :office_phone_number, :mobile_country_code, :mobile_phone_number])
end
end
My Account model
class Account < ApplicationRecord
RESTRICTED_SUBDOMAINS = %w(www admin loadlead)
belongs_to :owner, class_name: 'User'
has_many :users
validates :owner, presence: true
validates :subdomain, presence: true,
#uniqueness: { case_sensitive: false },
format: { with: /\A[\w\-]+\Z/i, message: 'contains invalid characters'},
exclusion: { in: RESTRICTED_SUBDOMAINS, message: 'restricted name'}
before_validation :downcase_subdomain
accepts_nested_attributes_for :owner
protected
def downcase_subdomain
self.subdomain = subdomain.try(:downcase)
end
end
My User model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable
belongs_to :account
end
Any assistance here would be greatly appreciated! I have no idea where I'm going wrong with this? Thanks in advance.
Try to call fields_for on f builder instead:
<%= form_for #account do |f| %>
<%= f.fields_for :owner do |o| %>
<p>
<%= o.label :f_name %>
<%= o.text_field :f_name %>
</p>
# ....
<% end %>
# ....
<%= f.submit %>
<% end %>
And you can remove :owner_id, this attribute value will be set automatically by Rails when we're using :accepts_nested_attributes_for.
You are calling #account.save which does not raise an exception. It returns true if everything is fine, or returns false when the validation fails (if #account.valid? returns false).
If there are any validation errors, you can check them by calling:
pry(main)> #account.valid?
pry(main)> false
pry(main)> #account.errors
That should help you debug the issue.
Stuck on nested forms..
Order model:
class Order < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
User mode:
class User < ActiveRecord::Base
has_many :orders, dependent: :destroy
accepts_nested_attributes_for :orders
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Form view:
=form_for #order do |order|
=order.fields_for :user, #order.user do |user|
.row
.col-md-3
.form-group
=user.label :first_name, "Name"
=user.text_field :first_name, :class => "form-control"
.col-md-3
.form-group
=user.label :last_name, "Last name"
=user.text_field :last_name, :class => "form-control"
.col-md-3
=user.label :email, "Email"
=user.text_field :email, :class => "form-control"
.col-md-3
=user.label :telephone, "Phone"
=user.text_field :telephone, :class => "form-control"
.row
.col-md-4.margin-top-15
=order.submit 'Send', :class => 'btn btn-success'
OrdersController:
class OrdersController < ApplicationController
def new
#order = Order.new
if user_signed_in?
user = current_user
else
user = User.new
end
end
def create
#order = Order.new order_attributes
#order.save
end
private
def order_attributes
params.require(:order).permit(:user_id, user_attributes: [:id, :user_id, :user, :first_name, :last_name, :email, :telephone, :password, :password_confirmation])
end
end
So this is what I am trying to do:
User model has devise. I want to create order and assign to it user_id. On submit it tells me "Unpermitted parameter: user". Order model creates its column, but nothing goes to user model.
What am I doing wrong?
Change:
params.require(:order).permit(:user_id, user_attributes: [:id,...
to:
params.require(:order).permit(:user_id, user: [:id,...
Remove :user from :user_attributes.
And I don't think :user_id is necessary.
I am using devise for user authentication in a rails 4 app.
After the user registers, I am redirecting the user to a page which has some additional fields they can choose to populate. I have the form appearing correctly, but it is not saving the nested attribute to the database.
I have a model called "seeker_skill" which has this relation to user:
user has_many seeker_skills
seeker_skills belongs to user
user.rb
class User < ActiveRecord::Base
has_many :seeker_skills, dependent: :destroy
accepts_nested_attributes_for :seeker_skills, :reject_if => lambda { |a| a[:skill].blank? }, :allow_destroy => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
users_controller.rb
class UsersController< ApplicationController
def job_seeker_additional_fields
#user = current_user
#user.seeker_skills.build
#seeker_skill = current_user.seeker_skills.build
end
end
seeker_skill.rb
class SeekerSkill < ActiveRecord::Base
belongs_to :user
validates :skill, presence: true
end
seeker_skills_controller.rb
class SeekerSkillsController < ApplicationController
def create
#seeker_skill = current_user.seeker_skills.build(seeker_skill_params)
if #seeker_skill.save
redirect_to root_url
else
flash[:error] = "Invalid Input"
redirect_to myskills_path
end
end
def destroy
end
def new
#seeker_skill = current_user.seeker_skills.build
#user = current_user
end
private
def seeker_skill_params
params.require(:seeker_skill).permit(:skill)
end
end
I believe I have the permitted parameters set up correctly in the application controller.
application_controller.rb
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :role, :email,
:company, :password, :password_confirmation, :remember_me,
seeker_skills_attributes: [:id, :skill, :user_id, :_destroy]) }
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:login,
:username, :role, :email, :company, :password, :remember_me) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :bio,
:min_salary, :location, :radius, :role, :email, :company, :password, :password_confirmation,
:current_password, seeker_skills_attributes: [:id, :skill, :user_id, :_destroy]) }
end
end
Finally there is the form in the view: Eventually I will add option to add multiple skills at once.
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= f.fields_for(#seeker_skill) do |f| %>
<%= f.text_field :skill, placeholder: "Add skill" %>
<% end %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
What am I missing? I have set this up with a custom user authentication system but never with devise.
Change your nested fields call to:
<%= f.fields_for(:seeker_skill) do |f| %>
with a symbol, not the object. When created with object, it names the field from the object class name, so in result you got params[:user][:seeker_skill], which are then filtered by strong params. While run with symbol, it tries to execute method with given name, treats it as an object and if the form object defines <name>_attributes sets the subobject name to <name>_attributes.
I have a form_for that uses AJAX to update custom fields I added to the devise user model. However, upon submit (with valid input), I get a POST 422 (Unprocessable Entity) error. This occurs because of the validations in my user model below:
class User < ActiveRecord::Base
validates_presence_of :annual_income, :current_savings, :retirement_savings, :if => :enable_strict_validation
validates_numericality_of :annual_income, :current_savings, :retirement_savings, :if => :enable_strict_validation
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Here is the form:
<div class="main_form">
<h1>Income & Savings First...</h1>
<%= form_for #user, url: { action: :persist_data }, method: :post, remote: true, html: { id: "insurance_form" } do |f| %>
<%= f.label "Total Annual Income" %>
<%= f.text_field :annual_income, autofocus: true, placeholder: "Enter $" ,value: "", class: "format_me" %><br/>
<%= f.label "Savings" %>
<%= f.text_field :current_savings, placeholder: "Enter $", value: "", class: "format_me" %><br/>
<%= f.label "Saved for Retirement?" %>
<%= f.text_field :retirement_savings, placeholder: "Enter $", value: "", class: "format_me" %><br/>
<%= f.submit "Calculate", class: "btn btn-primary" %>
<% end %>
</div>
The call is made to the HomeController below:
class HomeController < ApplicationController
before_filter :authenticate_user!
def index
#user = current_user
end
# TODO: refactor as update in a custom devise controller for user
def persist_data
user_params.each {|key, val| val.gsub!("$", "") }
# Make sure user model validations are run
current_user.enable_strict_validation = true
current_user.save!
current_user.update_attributes( user_params )
render json: {status: "OK"}
end
private
def user_params
params.require(:user).permit(:annual_income, :current_savings, :retirement_savings, :recommended_insurance)
end
end
And lastly, here is the server log with the error:
Started POST "/home/persist_data" for 127.0.0.1 at 2014-03-12 12:11:34 -0400
Processing by HomeController#persist_data as JS
Parameters: {"utf8"=>"✓", "user"=>{"annual_income"=>"$4.00", "current_savings"=>"$4.00", "retirement_savings"=>"$4.00"}, "commit"=>"Calculate"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 ORDER BY "users"."id" ASC LIMIT 1
(0.1ms) begin transaction
(0.1ms) rollback transaction
Completed 422 Unprocessable Entity in 35ms
ActiveRecord::RecordInvalid (Validation failed: Annual income can't be blank, Annual income is not a number, Current savings can't be blank, Current savings is not a number, Retirement savings can't be blank, Retirement savings is not a number):
app/controllers/home_controller.rb:13:in `persist_data'
Thanks for the help.
Update persist_data as below:
def persist_data
## Set the user_params value
user_params = user_params.each {|key, val| val.gsub!("$", "") }
# Make sure user model validations are run
current_user.enable_strict_validation = true
## Remove below line, as you are updating in the very next line with user_params
## current_user.save!
current_user.update_attributes( user_params )
render json: {status: "OK"}
end
I am working on a web-app using Devise and Rails 4. I have a User model which I have extended with 2 extra form fields such that when a user signs up he can also submit his first/last names. (based on http://blog.12spokes.com/web-design-development/adding-custom-fields-to-your-devise-user-model-in-rails-4/). I now want to add a Institution model. This model has_many :users, and a user belongs_to :institution. I want to be able to register the institution's name on the same form I register the user. I know I need a nested_attribute in my Institution model, since this is the parent, which I will show in a bit. When I try to sign up the user I get in the console: Unpermited parameters: Institutions.
My hint is that I cannot update my parent class(Institution) based upon my child class (User). Might there be a solution to this? Or has anyone experienced something similar?
class Institutions < ActiveRecord::Base
has_many :users,
accepts_nested_attributes_for :users
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :institution
end
registrations/new.html.erb Here I have the nested form
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
.
.
<%= f.fields_for :institutions do |i| %>
<p><%= i.label :name %><br />
<%= i.text_field :institutions_attr %></p>
<% end %>
Based on the tutorial I have linked earlier, I have created a new User::ParameterSanitizer which inherits from the Devise::ParameterSanitizer and overridden the sign_up method as follows:
lib/user_sanitizer.rb
private
def sign_up
default_params.permit(:first_name, :last_name ,:email, :password, :password_confirmation, :current_password, institutions_attributes: [:id, :name])
end
Finally, my application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
protected
def devise_parameter_sanitizer
if resource_class == User
User::ParameterSanitizer.new(User, :user, params)
else
super
end
end
end
Thank you for reading!
Console params output:
{"utf8"=>"✓",
"authenticity_token"=>"JKuN6K5l0iwFsj/25B7GKDj7WEHR4DO3oaVyGxGJKvU=",
"user"=>{"email"=>"abc#foo.com",
"first_name"=>"abc",
"last_name"=>"xyz",
"institutions"=>{"name"=>"Government"},
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"commit"=>"Sign up"}
EDIT
As suggested, I have added
params.require(resource_name).permit( :email, :first_name, :last_name, institution: [:name], :password, :password_confirmation ) and I get an *error syntax error, unexpected ',', expecting => ...nstitution: [:name], :password, :password_confirmation )*
BUT, if I re-edit to
params.require(resource_name).permit( :email, :first_name, :last_name, :password, :password_confirmation, institution: [:name] )
I get NO syntax error but I get Unpermited parameters: Institutions in the Request.
My belief is that this happens because User is a child of Institution. I have, however, been unable to find a work-around this.
config/routes.rb
Create your own registration controller like so ... (see Devise documentation for the details of overriding controllers here ...) ... which is more elegant way as opposed to doing it via the ApplicationController
devise_for :users, controllers: {registrations: 'users/registrations'}
app/controllers/users/registrations_controller.rb
Override the new method to create a Profile associated with the User model as below ... run the configure_permitted_parameters method before to sanitize the parameters (note how to add nested parameters)
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
# GET /users/sign_up
def new
# Override Devise default behaviour and create a profile as well
build_resource({})
resource.build_profile
respond_with self.resource
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u|
u.permit(:email, :password, :password_confirmation, :profile_attributes => :fullname)
}
end
end
db/migrate/xxxxxxxxxxxxxx_create_profiles.rb
This is the migration that generates the Profile model (note the reference to User) ... this example profile only keeps fullname as an extension of the User but feel free to add as you wish!
class CreateProfiles < ActiveRecord::Migration
def change
create_table :profiles do |t|
t.references :user
t.string :fullname
t.timestamps
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
# Associations
has_one :profile, dependent: :destroy, autosave: true
# Allow saving of attributes on associated records through the parent,
# :autosave option is automatically enabled on every association
accepts_nested_attributes_for :profile
# Devise
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
app/models/profile.rb
class Profile < ActiveRecord::Base
# Associations
belongs_to :user
# Validations
validates :fullname, presence: true
end
app/views/devise/registrations/new.html
<% resource.build_profile if resource.profile.nil? %>
<%= form_for(resource, :as => resource_name,
:url => registration_path(resource_name)) do |f| %>
<ul>
<%= devise_error_messages! %>
<li class="fullname">
<%= f.fields_for :profile do |profile_fields| %>
<%= profile_fields.label :fullname %>
<%= profile_fields.text_field :fullname %>
<% end %>
</li>
<li class="email">
<%= f.label :email %>
<%= f.email_field :email, :autofocus => true %>
</li>
<li class="password">
<%= f.label :password %>
<%= f.password_field :password %>
</li>
<li class="password">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</li>
<li>
<%= f.submit %>
</li>
<li>
<p><%= render "devise/shared/links" %></p>
</li>
</ul>
<% end %>
You must create your own registration controller to do so, here is how:
routes.rb
devise_for :users, controllers: {registrations: 'registrations'}
Controller
You must replace :your_fields by the fields you want to allow (sorry if I leave that to you, but that makes my answer more general, therefore usable for anyone that would pass by)
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
allow = [:email, :your_fields, :password, :password_confirmation]
params.require(resource_name).permit(allow)
end
end
Additional info (nested attributes + some testing)
Also note that if you are using association and accepts_nested_attributes_for you will have params structured like this
model: {field, field, field, associated_model: {field, field}}
And off course you must use the same structure in your sign_up_params method. If you need to understand this, you can change the content of sign_up_params method like this:
def sign_up_params
params.require(resource_name).permit!
end
That will allow any param, then post your form (it should pass this time) and look into your rails console to see the structure of params, finally you can set-up sign_up_params method correctly
Check this for more info http://www.railsexperiments.com/using-strong-parameters-with-nested-forms/
In your case you should use:
params.require(resource_name).permit( :email, :first_name, :last_name, institutions: [:name], :password, :password_confirmation )
Using rails 5.1 and devise 4.4.1 following is the shortest and works pretty good:
app/models/user.rb
after_initialize do
build_profile if new_record? && profile.blank?
end
app/controllers/application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [{ profile_attributes: :name }])
end
The key here is that you can do following without making separate controller:
permit nested attributes
build relation for form builder