Devise - Insert data into HAS_ONE related model during sign up - ruby-on-rails

I have a User model, that has_one Profile.
Profile is the place where all the user stuff is saved (name, phone, address, state, etc).
During sign up I need to let user fill in those fields.
Tried to do nested fields but it doesn't really work and I don't really understand why.
Does anyone have similar code examples? Can't find anything in Internet.
Candidate has_one :profile
Profile belongs_to :user
Registration form:
= simple_form_for(:candidate,
as: Candidate,
url: candidate_registration_path) do |f|
= f.simple_fields_for :profile do |profile|
= profile.input :first_name
= profile.input :last_name
= f.input :email
= f.input :password
= f.input :password_confirmation
= f.submit 'Start Building', class: 'btn btn-primary'
Didn't do anything with controllers except this:
def configure_devise_params
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:email, :password, :password_confirmation,
profile_attributes: [:first_name, :last_name])
end
end

When you say it doesn't work do you mean that it doesn't save? Or it doesn't show the fields?
In the latter case, you would have to build the blank profile in the registrations controller before the action is hit. So basically override the devise controller and do something like this:
class RegistrationsController < Devise::RegistrationsController
def new
user = build_resource({})
user.build_profile if user.profile.blank?
respond_with self.resource
end
end
routes.rb
devise_for :candidates, :controllers => {:registrations => "registrations"}
Candidate.rb
has_one :profile
accepts_nested_attributes_for :profile
and make sure the code you have written above for strong parameters is in your application_controller.
This is also assuming your devise model is called "Candidate"

Check the params in your log. Try creating a user in console using those params. Do you have accepts_nested_attributes_for :profile on your user model?

Related

Two models with one controller using devise, problems with validations and error messages

I have two models in my app, one 'user' model with devise, one 'employee' model made by scaffolding.
I need a way to have the employee table populated as soon as a new user registers, both tables share some params, some are exclusive. The employee belongs to the user model, each user has one employee.
The view I use is the devise user registration form with nested attributes to allow for the employee params. Creation is handled by the user controller.
Problems that occur:
undefined method 'email' for empty password field until the first user is created
cannot make the error message for the exclusive 'name' parameter of the employee model show up in the same place as the error messages for the user model
employee-model:
class Employee < ApplicationRecord
audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
user model
class User < ApplicationRecord
audited
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable
has_one :employee, inverse_of: :user
accepts_nested_attributes_for :employee
validates_presence_of :password
validates :password, format: { with: /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}, if: -> {password.present?}
validates_presence_of :password_confirmation, if: -> {password.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}
validates_confirmation_of :password, if: -> {password.present? and password_confirmation.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}
validates_presence_of :email
validates :email, format: { with: /\A([^#[0-9]\s]+\.)+([^#[0-9]\s]+)#((thisapp+\.)+com)\z/i}, uniqueness: true, if: -> {email.present?}
end
user controller
class Users::RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
super
end
def new_employee
#employee = Employee.new
end
# POST /resource
def create
#employee = Employee.new(employee_params)
super
#employee.email = User.last.email
#employee.user_id = User.last.id
#employee.created_by_id = User.last.id
#employee.save
end
user registration view
.card style='text-align:center;'
.card-body
h2.card-title style='text-align:center;' = t('.sign_up')
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= render "users/shared/error_messages", resource: resource
br
.field
= f.label :email, t('users.email_field')
br
= f.text_field :email, autofocus: true, autocomplete: "email"
.field
= f.label :password, t('users.password_field')
/- if #minimum_password_length
em
= t('devise.shared.minimum_password_length', count: #minimum_password_length)
br
= f.password_field :password, autocomplete: "new-password"
.field
= f.label :password_confirmation, t('users.password_confirmation_field')
br
= f.password_field :password_confirmation, autocomplete: "new-password"
= fields_for :employee do |e|
= e.label :name
= e.text_field :name
So, when I use #employee.email = User.last.email in the controller I get a 'unknown method 'email' error when not filling out the password field, unless I have a preexisting user, then I get my custom error messages for not filling out the email. I guess it is because I am looking for a last user who does not exist at this point. Could seed a user, but that seems hackish. Tried using #employee.email = User.last(params[:email]) which leads to the email being saved as some hash value, but at least I get my error messages. Is there a way to convert that hash to the real email address again?
The other issue is the validation of the name field. Validation is asked for in the employee model, and user model accepts nested attributes, but that does not seem to be enough.
I did
if params[:employee][:name].blank?
flash[:notice] = t('.noname')
which works insofar as that the form cannot be submitted without some value in the name field, but messes up my error messages. Shows a flash message where all other errors (no email/password/pw confirmation) are handled by the devise's shared error messages as non-flash messages:
- resource.errors.full_messages.each do |message|
li
= message
So having the blank name as a flash message would look inconsistent, and the spot for the flash message is already reserved.
Flash message is on top, 'following errors prevent..', actual error messages are below 'Registrieren', and that is also where the error message for blank name would need to be.
Any ideas on how to approach this or maybe a better solution than handling this stuff in the user controller?
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
super
end
def new_employee
#employee = Employee.new
end
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, employee_attributes: %i[name])
end
This signup param will be used while creating a user from devise registration controller. Since we used nested attributes, passing the arguments along with the parent object will handle employee creation(https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)
For assigning email for employee
The one way is to merge the email from the user params.
def sign_up_params
sign_up_param = params.require(:user).permit(:email, :password, :password_confirmation,employee_attributes: %i[name])
sign_up_param[:employee_attributes].merge!(email: params[:user][:email])
sign_up_param
end
Or maybe you could assign the email of the employee from the user before the validation.
class Employee < ApplicationRecord
audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
# Callbacks
before_validation :set_email
# Methods
# Set email of the employee from the user.
def set_email
self.email = self.user.email
end

Devise, create a user that belongs to company

I want to create a user with Devise, that have a belongs_to association.
What is missing? What needs to create a user that belongs to a company from a select list?
my form have this:
.siimple-form-field
= f.label :name, class: "siimple-label"
br
= f.text_field :name, autofocus: true, autocomplete: "email", class: "siimple-input siimple-input--fluid siimple--height-50"
.siimple-form-field
label.siimple-label
| Select an option:
= f.fields_for :company_attributes do |b|
= b.select :company, Company.all.collect{|p| [p.name, p]}, {}, class: "siimple-select siimple-select--fluid"
the form shows ok, so when i try to submit, it says:
1 error prohibited this user from being saved:
Company must exist
Also my RegistrationsController have:
def create
super
end
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :bank])
end
Solved!
First them all, the registration controller was not overriden in the routes files, just got this:
devise_for :users, controllers: {
sessions: 'users/sessions',
# something missing no?
}
Fixed it with
devise_for :users, controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations'
}
Then, just do common things like add something like this to the controller:
def create
params[:user][:company] = Company.find(params[:user][:company_id])
super
end
And add the missing permission to configure_sign_up_params
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :company_id, :company])
end
I think your problem here is that the company parameters are not being permitted (which means the company gets set to nil, hence the Company must exist error).
You have to override Devise's resource_params method to add your company_id attribute. Try putting this in your Registrations controller:
def resource_params
params.require(:user).permit(:user_attribute_foo, :user_attribute_bar, :company_id)
end
I think your column name is company_id and you are using company in the form. Try by changing it and permit this parameter.

Rails5. Nested attributes are not going to database

class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
belongs_to :user
end
<%= form_for #user do |f| %>
.... // some filed here everything fine
<%= f.fields_for :address do |a| %>
<%= a.text_field :city %> // this field is not appear
<% end %>
<% end %>
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.valid?
#user.save
else
redirect_to root_path
end
end
private
def user_params
params.require(:user).permit(:id, :name, :email, :password, :password_confirmation, :status, :image, :address_attributes => [:id, :city, :street, :home_number, :post_code, :country])
end
end
So like you can see above I have two classes and one form, when I am trying display fields for Address class I can not do it in that way. I took this example from https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
I was trying different combination like for example using User.new and Address.new in form definition it not working as well, I was able display all fields in that situation but I wasn't able to save Address data to table, because of "unpermited address".
Can someone explain what I am doing wrong? Or at least give me please some hints.
[SOLVED]
I should learn how to read documentations properly. Excalty like #Srack said I needed just use build_address method. I checked documentation rails api again and on the end of page there was examples says to create User class like this:
class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
def address
super || build_address
end
end
and that solved my issue.
Thank you.
You'll have to make sure there's an address instantiated for the user in the new view. You could do something like:
def new
#user = User.new
#user.build_address
end
You should then see the address fields on the form.
The nested_fields_for show the fields for a record that's been initialised and belong to the parent. I think the latter is why your previous attempts haven't worked.
FYI build_address is an method generated by the belongs_to association: http://guides.rubyonrails.org/association_basics.html#methods-added-by-belongs-to

How can I display nested records on activeadmin?

So far I have two models, user and profile
Class User < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile
Class Profile < ActiveRecord::Base
belongs_to :user
On my active admin model
form do |f|
f.inputs "User" do
f.input :email
#code to get the profile data
end
f.action :submit
end
So I want to get the profile data on the user form I have tryed couple of things but I wasn't able to get them.
Something like
f.inputs 'Profile', :for => [:profile, f.object.profile || Profile.new] do |profile_form|
profile_form.input ...
...
end
inside the f.inputs "User" block should work. Just set up the attributes of Profile inside this block using profile_form.

devise and multiple "user" models

I'm using rails 3.2 and devise 2.0 and I'm quite new to Rails.
Requirements
I'd like to achieve the following:
have 2 or more "user" models, eg. Member, Customer, Admin
all models share some required fields (eg. email and password)
each model may have some unique fields (eg. company for Customer only)
some fields may be shared but not have the same validation (eg. name is required for Customer but optional for Member)
all fields must be filled during the registration process, so the forms are different
the login form should be unique
Possible solutions
I googled and searched StackOverflow for quite a long time, but nothing seems right to me (I'm a Java guy, sorry :) and now I'm quite confused. Two solutions came up:
Single devise user
That's the most frequent answer. Just create the default devise User and create relations between Member-->User and Customer-->User.
My concern here is how can I achieve a customized registration process for each model? I tried different things but all ended as a mess!
Multiple devise users
This solves the custom registration process, and seems right to me, but the unique login form is a blocker. I found an answer on SO (Devise - login from two model) which suggests to override Devise::Models::Authenticatable.find_for_authentication(conditions).
That seems complicated (?) and since I'm new to rails, I'd like to know if that could work?
Thanks for your advice!
Welcome aboard Java guy =), I hope you'll enjoy the Rails world.
Simply, to solve your issue you have 2 solutions:
For each user create a table in the database and corresponding model.
Create a single table in the database and for each user type create a model. This is called single table inheritance (STI).
Which one to choose?
It depends on the common attributes of the roles. If they are almost common (for example all have a name, email, mobile, ...) and a few attributes are different, I highly recommend the STI solution.
How to do the STI?
1. Simply create the the devise user model and table using the command rails generate devise User
2. Add a column named type with string datatype to the user table in the database using a migration.
3. For each user type create a model (for example rails g model admin)
4. Make the Admin class inherits from user model
class Admin < User
end
That's it you are done =) ... Yupeee
To create an admin run the command Admin.create(...) where the dots is the admin attributes for example the email, name, ...
I think this question could help you too
I'm in similar shoes as you, after trying all sorts of approaches I went with a single User model, which would belong to polymorphic roles. This seems like the simplest way to achieve single-login.
The User model would contain the information specific to log-in only.
The Role model would store fields specific to each role, as well as other associations specific to the role.
New registrations would be customized for each user type (roles) via individual controllers, and then building nested attributes for the User.
class User < ActiveRecord::Base
#... devise code ...
belongs_to :role, :polymorphic => true
end
class Member < ActiveRecord::Base
attr_accessible :name, :tel, :city #etc etc....
attr_accessible :user_attributes #this is needed for nested attributes assignment
#model specific associations like
has_many :resumes
has_one :user, :as => :role, dependent: :destroy
accepts_nested_attributes_for :user
end
Routes -- just regular stuff for the Member model.
resources :members
#maybe make a new path for New signups, but for now its new_member_path
Controller -- you have to build_user for nested attributes
#controllers/members_controller.rb
def new
#member = Member.new
#member.build_user
end
def create
#... standard controller stuff
end
views/members/new.html.erb
<h2>Sign up for new members!</h2>
<%= simple_form_for #member do |f| %>
# user fields
<%= f.fields_for :user do |u| %>
<%= u.input :email, :required => true, :autofocus => true %>
<%= u.input :password, :required => true %>
<%= u.input :password_confirmation, :required => true %>
<% end %>
# member fields
<%= f.input :name %>
<%= f.input :tel %>
<%= f.input :city %>
<%= f.button :submit, "Sign up" %>
<% end %>
I would like to point out that there is NO NEED to reach for nested_form gem; since the requirement is that User can only belong_to one type of Role.
I found a way to go and I'm quite happy with it so far. I'll describe it here for others.
I went with the single "user" class. My problem was to achieve a customized registration process for each pseudo model.
model/user.rb:
class User < ActiveRecord::Base
devise :confirmable,
:database_authenticatable,
:lockable,
:recoverable,
:registerable,
:rememberable,
:timeoutable,
:trackable,
:validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :role
as_enum :role, [:administrator, :client, :member]
validates_as_enum :role
## Rails 4+ for the above two lines
# enum role: [:administrator, :client, :member]
end
Then I adapted http://railscasts.com/episodes/217-multistep-forms and http://pastie.org/1084054 to have two registration paths with an overridden controller:
config/routes.rb:
get 'users/sign_up' => 'users/registrations#new', :as => 'new_user_registration'
get 'clients/sign_up' => 'users/registrations#new_client', :as => 'new_client_registration'
post 'clients/sign_up' => 'users/registrations#create', :as => 'client_registration'
get 'members/sign_up' => 'users/registrations#new_member', :as => 'new_member_registration'
post 'members/sign_up' => 'users/registrations#create', :as => 'member_registration'
controllers/users/registrations_controller.rb:
I created a wizard class which knows the fields to validate at each step
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
session[:user] ||= { }
#user = build_resource(session[:user])
#wizard = ClientRegistrationWizard.new(current_step)
respond_with #user
end
# GET /clients/sign_up
def new_client
session[:user] ||= { }
session[:user]['role'] = :client
#user = build_resource(session[:user])
#wizard = ClientRegistrationWizard.new(current_step)
render 'new_client'
end
# GET /members/sign_up
def new_member
# same
end
# POST /clients/sign_up
# POST /members/sign_up
def create
session[:user].deep_merge!(params[:user]) if params[:user]
#user = build_resource(session[:user])
#wizard = ClientRegistrationWizard.new(current_step)
if params[:previous_button]
#wizard.previous
elsif #user.valid?(#wizard)
if #wizard.last_step?
#user.save if #user.valid?
else
#wizard.next
end
end
session[:registration_current_step] = #wizard.current_step
if #user.new_record?
clean_up_passwords #user
render 'new_client'
else
#session[:registration_current_step] = nil
session[:user_params] = nil
if #user.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_in(:user, #user)
respond_with #user, :location => after_sign_up_path_for(#user)
else
set_flash_message :notice, :"signed_up_but_#{#user.inactive_message}" if is_navigational_format?
expire_session_data_after_sign_in!
respond_with #user, :location => after_inactive_sign_up_path_for(#user)
end
end
end
private
def current_step
if params[:wizard] && params[:wizard][:current_step]
return params[:wizard][:current_step]
end
return session[:registration_current_step]
end
end
and my views are:
new.rb
new_client.rb including a partial according to the wizard step:
_new_client_1.rb
_new_client_2.rb
new_member.rb including a partial according to the wizard step:
_new_member_1.rb
_new_member_2.rb
So what's wrong? Just run rails g devise:views [model_name], customize each registration forms and in config/initializer/devise.rb just put config.scoped_views = true.

Resources