Rails 4.0 with Devise. Nested attributes Unpermited parameters - ruby-on-rails

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

Related

Rails 5 - Devise - User has_one association - nested form fields are not shown at sign up form

As I searched, this is a common issue, but none of the answers I found work for my case.
I have set up a User model with devise and it has two related models, it has one Contact Detail and many Addresses. The nested form works well with addresses, but my contact detail fields are not shown.
My User model is the following:
validates_presence_of :contact_detail, :addresses
# Include default devise modules. Others available are:
# :lockable, :timeoutable, :trackable and :omniauthable
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
has_one :contact_detail, dependent: :destroy
has_many :addresses, dependent: :destroy
accepts_nested_attributes_for :addresses,
allow_destroy: true
accepts_nested_attributes_for :contact_detail,
allow_destroy: true
The contact details model only has belongs_to :user
I made the changes mentioned at devise gem at my application controller:
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [addresses_attributes: [:street_name, :street_number, :city, :country, :postal_code, :name],
contact_detail_attributes: [:first_name, :last_name, :description, :telephone, :mobile ]])
end
end
and my app/views/devise/registrations/new.html.erb file looks like this:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
...
<div>
<% f.fields_for :contact_detail do |contact_detail|%>
<div class="field">
<%= contact_detail.label :first_name %>
<%= contact_detail.text_field :first_name %>
</div>
<div class="field">
<%= contact_detail.label :last_name %>
<%= contact_detail.text_field :last_name %>
</div>
<div class="field">
<%= contact_detail.label :description %>
<%= contact_detail.text_area :description %>
</div>
<div class="field">
<%= contact_detail.label :telephone %>
<%= contact_detail.number_field :telephone %>
</div>
<div class="field">
<%= contact_detail.label :mobile %>
<%= contact_detail.number_field :mobile %>
</div>
<% end %>
</div>
...
But my contact detail fields are not shown. Any ideas?
You have to "seed" the relation in order for the inputs for an association to appear. fields_for works like a loop. If the association is empty or nil the block runs 0 times.
Normally you would do this in the new action of your controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_contact_detail
end
end
In Devise the new action is Devise::RegistrationsController#new which you can customize by subclassing the controller:
class MyRegistrationsController < Devise::RegistrationsController
def new
super do |user|
user.build_contact_detail
end
end
end
super do |user| ... end uses the fact that all the Devise controller actions take a block and yield the resource. This makes it really easy to customize them without copy-pasting the entire method.
You then have to alter the routes so that your custom controller is used:
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'my_registrations'
}
end

Understanding MVC for Rails 4 with image upload form

I am learning rails and I am trying to understand how the MVC model works with rails 4. I am practicing this by creating a form that will allow the user to upload an image with a name to the database. I am using CarrierWave to handle image storage in the database. This is what I have so far. As I am new to Rails 4, I'm not sure how all these parts connect together.
Here are my models for User and IncomePicture:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:rememberable, :validatable
has_many :expense_pictures
has_many :income_pictures
end
class IncomePicture < ActiveRecord::Base
belongs_to :user
mount_uploader :image, ImageUploader
has_one :income_text
end
Controllers:
class UserController < ApplicationController
def create
User.create(user_params)
end
private
def user_params
# required input for params
# permit - returns a version of the params hash with ony the permitted attributes
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
class IncomePicturesController < ApplicationController
def create
# create IncomePicture object with params
#income_picture = IncomePicture.new(IncomePicture_params)
#
if #income_picture.save
flash[:notice] = "Income picture successfully uploaded"
redirect_to
end
private
def IncomePicture_params
params.require(:income_picture).permit(:image, :name)
end
end
view for form:
<%= form_for #income_picture, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<p>
<%= f.file_field :image %>
</p>
<p><%= f.submit %></p>
<% end %>
I'm not sure how to create a form that will store the upload to the logged in user. Currently only the user login portion works.
I am getting this error when I try to run rails s
First argument in form cannot contain nil or be empty
on the line
--> <%= form_for #income_picture, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
As the error says, the first argument of form_for cannot be nil or empty which means that #income_picture, the first argument, is most probably nil. So you have to ask why this variable is nil and where should I define it.
I'm assuming that the form is under app/views/income_pictures/new.html.erb which means that the most probable action corresponding to that view is the new action under IncomePicturesController.
Add a new action in the IncomePicturesController and define #income_picture
class IncomePicturesController < ApplicationController
def new
#income_picture = IncomePicture.new
end
...
end

Error with nested attributes for a users model with devise on Rails 4?

I'm trying to create a new record for a model that belongs to my main model on the main model's update page, but it is not being saved to the database. Basically the Company model acts as the main user model, and it has the ability to create new board members for itself on its edit registration page that devise generates. Here is what I have.
1) my company model which has many boardmembers
class Company < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :boardmembers
accepts_nested_attributes_for :boardmembers
end
2) my boardmembers model
class Boardmember < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :company
end
3) the companies controller
class Companies::RegistrationsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, only: [ :new, :create, :cancel ]
prepend_before_filter :authenticate_scope!, only: [:edit, :update, :destroy]
def create
build_resource(sign_up_params)
if resource.save
redirect_to edit_company_registration_path
else
clean_up_passwords resource
respond_with resource
end
end
def update
# For Rails 4
account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
# For Rails 3
# account_update_params = params[:user]
# required for settings form to submit when password is left blank
if account_update_params[:password].blank?
account_update_params.delete("password")
account_update_params.delete("password_confirmation")
end
#company = Company.find(current_company.id)
# saves the companies boardmembers
if #company.update_attributes(account_update_params)
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #company, :bypass => true
redirect_to company_home_path
else
render 'edit'
end
end
end
3) and my application controller where i configure params
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
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:email, :name, :address, :jurisdiction_of_incorporation, :entity_type, :password, :password_confirmation, boardmembers_attributes: [:company_id, :id, :email, :name, :address, :secondary_email, :primary_phone, :password])
end
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:email, :name, :password, :password_confirmation)
end
end
end
My forms look something like this: edit.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<!-- here is where all the company update fields go -->
...
<!-- here are the fields for creating boardmembers
<%= f.fields_for :boardmembers, resource.boardmembers.build do |boardmember| %>
<%= boardmember.text_field :name %>
<%= boardmember.text_field :address %>
<%= boardmember.text_field :primary_phone %>
<%= boardmember.text_field :email %>
<%= boardmember.text_field :secondary_email %>
<%= boardmember.password_field :password %>
<% end %>
<%= f.submit "Update Your Account" %>
<% end %>
However the company gets its records updated but the new boardmember is not created at all. I even tried rendering a json file of the params when i click the button and it ends up looking like
{"utf8":"✓","_method":"put","authenticity_token":"mK4yd8t4m7N5rdfmHG8XKc/c+vNUdO8vryk5kYm7juw=","company": {"email":"pizzahut#email.com","name":"Pizza Comp","password":"","password_confirmation":"","entity_type":"","jurisdiction_of_incorporation":"","address":"","boardmembers_attributes":{"0":{"name":"","address":"","primary_phone":"","email":"","secondary_email":"","password":""}}},"commit":"Update Your Account","action":"update","controller":"companies/registrations"}
All of the params for the boardmembers are empty even when I fill them in. I've tried every tutorial and answer online and none of them seem to work in this case. What else could it be? Any ideas? The new record is never created. Please help.
Try this
<%= form_for([resource,resource.with_boardmembers], as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<!-- here is where all the company update fields go -->
...
<!-- here are the fields for creating boardmembers
<%= f.fields_for :boardmembers do |boardmember| %>
<%= boardmember.text_field :name %>
<%= boardmember.text_field :address %>
<%= boardmember.text_field :primary_phone %>
<%= boardmember.text_field :email %>
<%= boardmember.text_field :secondary_email %>
<%= boardmember.password_field :password %>
<% end %>
In your User model add
def with_boardmembers
self.boardmembers.build
self
end

Rails 4, Devise, Nested attributes not saving to database

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.

Rails Devise registration with an additional model

I've searched for about an hour now and found an immense amount of questions describing how to add fields to the Devise user model. However I couldn't find any that explain in a clear way how to add one or multiple models to the registration process.
At registration I want the user to fill out an e-mailaddress, password and in addition my client model, company model and address model (so I have all the information the webapplication needs to run properly).
My models are like this
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me, :client
belongs_to :client
end
client.rb
class Client < ActiveRecord::Base
attr_accessible :bankaccount, :email, :logo, :mobile, :phone, :website
has_many :users
has_one :company
has_one :address
accepts_nested_attributes_for :company, :address
end
I think the only way to do this is to create my own RegistrationsController so I can do #client = Client.new and then do this in my view:
<%= f.simple_fields_for #client do |ff| %>
<%= f.simple_fields_for :company do |fff| %>
<% field_set_tag t(:company) do %>
<%= ff.input :name %>
<% end %>
<% end %>
<%= f.simple_fields_for :address do |fff| %>
//address inputs
<% end %>
<% end %>
<fieldset>
<legend><%= t(:other) %></legend>
// other inputs
</fieldset>
<% end %>
The reason I need it to work this way is because I have multiple users who can represent the same client (and thus need access to the same data). My client owns all the data in the application and therefor needs to be created before the application can be used.
Okay, it took me about 8 hours but I finally figured out how to make it work (if someone has a better/cleaner way of doing this, please let me know).
First I've created my own Devise::RegistrationsController to properly build the resource:
class Users::RegistrationsController < Devise::RegistrationsController
def new
resource = build_resource({})
resource.build_client
resource.client.build_company
resource.client.build_address
respond_with resource
end
end
After that I just needed to adjust config/routes.rb to make it work:
devise_for :users, :controllers => { :registrations => "users/registrations" } do
get '/users/sign_up', :to => 'users/registrations#new'
end
Also I had an error in my devise/registrations/new.html.erb. It should have been f.simple_fields_for :client instead of f.simple_fields_for #client.
Now it properly creates all the objects for the nested attributes and automatically saves it when saving the resource.

Resources