Rails Devise registration with an additional model - ruby-on-rails

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.

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

Rails 4 - Access attributes of nested model in form

I have Account model which have a has_many relationship with User model:
class Account < ActiveRecord::Base
has_many :users, -> { uniq }
accepts_nested_attributes_for :users
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :confirmable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :account
I added avatar attribute to User model using paperclip.
I want each user to have access to the common account settings, and inside it having the possibility to upload his/her own avatar.
I use simple_form so I tried this:
<%= simple_form_for current_account, :html => { :multipart => true } do |f| %>
<%# here come account settings %>
<%= f.input :time_zone, :label => t(".timezone"),
:
:
<%# here I need to access current user attributes %>
<%= f.simple_fields_for :user, current_account.users.first do |user_form| %>
<%= user_form.file_field :avatar, :error => false %>
<% end %>
<% end %>
First problem:
I need some logic to access current_user instead of current_account.users.first. Since there is a superadmin which can access all accounts, use current_user is not enough.
Second (and bigger) problem:
I added in my controller the avatar parameter to the whitelist:
def allowed_params
params.require(:account).permit(:time_zone, :logo, :description, user: [:avatar])
end
When I try to update my model:
if current_account.update(allowed_params)
I get this error:
unknown attribute: user
I also tried:
params.require(:account).permit(:language, :time_zone, :logo, :description, :user_attributes => [:avatar])
and:
params.require(:account).permit(:language, :time_zone, :logo, :description, :users_attributes => [:avatar])
(in plural)
but since I use ActionController::Parameters.action_on_unpermitted_parameters = :raise I get:
found unpermitted parameters: user
It must be something very easy, some help please?
Ok, got it!!
The problem is the one-to-many relationship and the way I tried to access a single instance of user. The correct way to do it is:
<% current_account.users.each_with_index do |user, index|%>
<%= f.simple_fields_for :users, user do |user_form| %>
<%= user_form.file_field :avatar, :error => false %>
<% end %>
<% end %>
As you can see, the iteration should be done over the relation, and only when having a
single instance "in hand" we can user the simple_fields_for.
Also, notice that the first parameter passed to simple_fields_for is :users and not :user, since this is a one-to-many relationship.

Couldn't find Photo with id=16

Not sure why this isn't working but I've been mis-guided I think....I'd rather not re-route but simply have the photo uploaded in the current landing_welcome page.. not be transfered to an update template.
error:
Couldn't find Photo with id=16 [WHERE "photos"."attachable_id" = $1 AND "photos"."attachable_type" = $2]
def update
#user = current_user.photos.find(params[:id])
#user.update_attributes!(person_params)
redirect_to #user
end
Users_controller.rb
class UsersController < ApplicationController
def update
#user = current_user.photos.find(params[:id])
#user.update_attributes!
redirect_to #user
end
def show
end
end
landing_welcome.html.erb
<div class="tab-pane" id="tab2">
<%= nested_form_for current_user, :html=>{:multipart => true } do |f| %>
<%= f.fields_for :photos do |p| %>
<p>
<%= image_tag(p.object.file.url) if p.object.file? %>
<%= p.label :file %><br />
<%= p.file_field :file %>
</p>
<%= p.link_to_remove "Remove this attachment" %>
<% end %>
<%= f.link_to_add "Add photos to your profile", :photos %>
<br /><br />
<p><%= f.submit %></p>
<% end %>
</div>
routes.rb
root to: "home#landing"
devise_for :users, :controllers => {:registrations => "users/registrations",
:sessions => "users/sessions",
:passwords => "users/passwords"}
get "welcome", to: "home#landing_welcome"
devise_scope :user do
# get "edit/edit_account", :to => "devise/registrations#edit_account", :as => "account_registration"
get 'edit/edit_account' => 'users/registrations#account_registration', as: :edit_account
end
patch '/users/:id', to: 'users#update', as: 'user'
photo.rb
class Photo < ActiveRecord::Base
attr_accessible :file
belongs_to :attachable, :polymorphic => true
mount_uploader :file, FileUploader
end
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email,
:password,
:password_confirmation,
:zip,
:gender,
:remember_me,
:first_name,
:last_name,
:birthday,
:current_password,
:occupation,
:address,
:interests,
:aboutme,
:profile_image,
:photos_attributes
has_many :photos, as: :attachable
accepts_nested_attributes_for :photos
mount_uploader :profile_image, ProfileImageUploader
validates :gender, :presence => true
def number_of_users
User.all.count
end
end
For lack of a better answer, I think your woes lie in the query generated by your app:
Couldn't find Photo with id=16 [WHERE "photos"."attachable_id" = $1 AND "photos"."attachable_type" = $2]
Two factors are present here:
Why is your attachable_id being called as $1?
Why is your attachable_type a number, not a string?
Polymorphic Association
Your query is trying to load a Photo with ID=16, however, your query is also trying to validate the model, to satisfy the polymorphic association. This is where the error is coming from
As you've not stated which route / page this error is showing, I can only speculate as to the cause of the problem:
#user = current_user.photos.find(params[:id])
This query is really bad for a number of reasons:
You're using the current_user object directly. I might be wrong here, but this is used by Devise / Warden to store relative information about the logged-in user, and is not a real ActiveRecord object (with relational data etc)
You're trying to .find on top of a relation (current_user.photos)
Although this might be incorrect, I would look at doing this for that query:
#photo = User.joins(:photos).find(current_user.id).where("photos.id = ?", params[:id])
Then you can perform the updates you require

Rails 4.0 with Devise. Nested attributes Unpermited parameters

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

Use both Account and User tables with Devise

I'm working with Rails 3.1.0 and Devise 1.4.8, and am new to both.
I want to allow multiple users for an account. The first user to sign up creates the account (probably for their company), then that user can add more users. A user is always linked to exactly one account.
I have Users and Accounts tables. Abbreviated models:
class User < ActiveRecord::Base
belongs_to :account
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
class Account < ActiveRecord::Base
has_many :users, :dependent => :destroy
attr_accessible :name, :account_type
end
The question is, when the first user signs up, how do I create both the Account and User?
Do I need to modify/override the Devise registrations_controller,
something like the answer here? I couldn't figure out how to
create the Account then pass it to Devise for creating the User.
account_id is already in the User model. Should I add account_name
and account_type to the User model, and create a new the Account
record if account_id is not passed in? In this case, I'm trying to hide Account creation from Devise, but not sure that will work since I still need to prompt for account_name and account_type on the registration page.
Finally got this to work using nested attributes. As discussed in the comments on Kenton's answer, that example is reversed. If you want multiple users per account, you have to create the Account first, then the User--even if you only create one user to start with. Then you write your own Accounts controller and view, bypassing the Devise view. The Devise functionality for sending confirmation emails etc. still seems to work if you just create a user directly, i.e. that functionality must be part of automagical stuff in the Devise model; it doesn't require using the Devise controller.
Excerpts from the relevant files:
Models in app/models
class Account < ActiveRecord::Base
has_many :users, :inverse_of => :account, :dependent => :destroy
accepts_nested_attributes_for :users
attr_accessible :name, :users_attributes
end
class User < ActiveRecord::Base
belongs_to :account, :inverse_of => :users
validates :account, :presence => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
spec/models/account_spec.rb RSpec model test
it "should create account AND user through accepts_nested_attributes_for" do
#AccountWithUser = { :name => "Test Account with User",
:users_attributes => [ { :email => "user#example.com",
:password => "testpass",
:password_confirmation => "testpass" } ] }
au = Account.create!(#AccountWithUser)
au.id.should_not be_nil
au.users[0].id.should_not be_nil
au.users[0].account.should == au
au.users[0].account_id.should == au.id
end
config/routes.rb
resources :accounts, :only => [:index, :new, :create, :destroy]
controllers/accounts_controller.rb
class AccountsController < ApplicationController
def new
#account = Account.new
#account.users.build # build a blank user or the child form won't display
end
def create
#account = Account.new(params[:account])
if #account.save
flash[:success] = "Account created"
redirect_to accounts_path
else
render 'new'
end
end
end
views/accounts/new.html.erb view
<h2>Create Account</h2>
<%= form_for(#account) do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :users do |user_form| %>
<div class="field"><%= user_form.label :email %><br />
<%= user_form.email_field :email %></div>
<div class="field"><%= user_form.label :password %><br />
<%= user_form.password_field :password %></div>
<div class="field"><%= user_form.label :password_confirmation %><br />
<%= user_form.password_field :password_confirmation %></div>
<% end %>
<div class="actions">
<%= f.submit "Create account" %>
</div>
<% end %>
Rails is quite picky about plural vs. singular. Since we say Account has_many Users:
it expects users_attributes (not user_attributes) in the model and tests
it expects an array of hashes for the test, even if there is only one element in the array, hence the [] around the {user attributes}.
it expects #account.users.build in the controller. I was not able to get the f.object.build_users syntax to work directly in the view.
Couldn't you use something like what's covered in these RailsCasts?:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
You could setup your models as described in those screencasts, using accepts_nested_attributes_for.
Then, your views/devise/registrations/new.html.erb form would be for :user like normal, and could include a nested form for :account.
So something like this within that default form:
<%= f.fields_for :account do |account_form| %>
<div>
<p>
<%= account_form.label :name, "Account Name", :class => "label" %>
<%= account_form.text_field :name, :class => "text_field" %>
<span class="description">(e.g., enter your account name here)</span>
</p>
</div>
<div>
<p>
<%= account_form.label :company, "Company Name", :class => "label" %>
<%= account_form.text_field :company, :class => "text_field" %>
</p>
</div>
<% end %>
This is sample code from an app I'm working on and I'm using the simple_form gem, so the helpers used in your app may be different, but you'll probably get the idea.
So when a user is created (when they register), they can also fill in the info that'll be used by the Account model to create their account once they hit the "Sign Up" button.
And you may want to set an attribute on that user like "admin" too...sounds like this initial user will be the "admin" user for the company, though other users may have access too.
Hope that helps.
The best solution would be to use a gem.
Easy way: milia gem
Subdomain way: apartment gem

Resources