I have 2 types of Users: Normal users and pros.
Pros are Users, but have extra fields in a separate table called :pros.
So , I did a separate registration form for :pros, in which I included :users fields, and added a fields_for with the new :pro fields.
I also added those new parameters to application_controller, so that devise permits them.
When submiting the registration form, the user is created, but I get the following error in my logs:
Started POST "/users" for 127.0.0.1 at 2014-11-13 00:53:43 +0100
Processing by RegistrationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"zUVLJFHhShoHvUVneGNmCf46E4KPWaINeTw4o7iCa7w=", "user"=>{"name"=>"asdasd", "email"=>"asdasd#sss.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "pros"=>{"type"=>"Marca de Decoración", "web"=>"asadasd", "telephone"=>"765876", "about"=>"sadasd"}, "tos_agreement"=>"1"}, "commit"=>"Registrarme y aplicar para PRO"}
Unpermitted parameters: pros
My view is:
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= f.label :name, "Nombre de usuario" %>
<%= f.text_field :name, :autofocus => true %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
<%= f.fields_for :pro do |pro| %>
<%= pro.select :type,["Marca de Decoración","Tienda de Decoración","Blogger"] %>
<%= pro.text_field :web, placeholder: "http://www.miweb.com" %>
<%= f.label :telephone, "Teléfono" %>
<%= pro.text_field :telephone, placeholder: "Teléfono", label: "Teléfono de contacto" %>
<%= pro.text_field :about%>
<% end %>
Users Controller new action
def pro_new
render "devise/registrations/new-pro-registration"
#user = User.create
end
My model relations:
User.rb
has_one :pro
accepts_nested_attributes_for :pro, allow_destroy: true
Pro.rb
belongs_to :user
My application controller:
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :tos_agreement, :avatar, :avatar_cache, :email, :password, :password_confirmation, pros_attributes: [:pro_name, :pro_image, :is_active, :web, :user_id, :about, :facebook, :twitter, :linkedin, :telephone]) }
end
I completely agree with #smallbutton.com
You need to change pro_attributes instead of pros_attributes. you can use params.require(:user).permit! if you want to accept all the params.
You have association like user has_one :pro.
Then you have passed array in permitted parameters but it is has_one relation.
It should be like this.
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:name, :tos_agreement, :avatar, :avatar_cache, :email, :password, :password_confirmation, pro_attributes: (:pro_name, :pro_image, :is_active, :web, :user_id, :about, :facebook, :twitter, :linkedin, :telephone)) }
end
It will works :)
Try to change your controller to this so that a new instance of pro is build and your form helper should produce pro_attributes. I assume this is the new action so you dont call #create but #new on your user. Maybe you need to change pros_attributes to pro_attributes in the permitted_parameters method because its a has_one relation.
def pro_new
#user = User.new
#user.build_pro
render "devise/registrations/new-pro-registration"
end
Hope this will do the trick.
I could give you a quick answer, but I think your design needs some work. You'd be far better of (especially in the long run) with the following format using single table inheritance and delegations
User #(base class)
has_one :userInfo
delegate :list_of_pro_fields, to: :userInfo
BasicUser < User #(sub class)
Pro < User #(sub class)
validates presence: true, [:list_of_pro_fields]
UserInfo #(base class)
belongs_to :user
While you could put the relationship between Pro and ProInfo I'd keep it on the base class if a user is able to lose (and regain) their pro status. This will prevent orphan records in UserInfo of ex-Pros.
This is also going to result in far better data segregation and response times as many fields you wont need a lot of the time are being moved to a separate table which can be joined as required.
Related
I am using devise and I built another table for the user information (username, firstname, lastname, address, etc.). What I want to do is when the user clicks on his profile settings (user model -> edit.html.erb view) to show the input fields for the attributes in the user_info table.
class User < ActiveRecord::Base
has_one :user_info
and
class UserInfo < ActiveRecord::Base
belongs_to :user
my form seems like that:
<%= form_for(#user) do |f| %>
<%= f.label :username %>
<%= f.text_field :username, :placeholder => "Enter username",
<%= f.label :email %>
<%= f.text_field :email, :placeholder => "Enter email" %>
and the controller
def edit
#user = User.find(params[:id])
#user_info = current_user.user_info
end
Of course this doesn't work because username is not an attribute of the user but the user_info table. If I decide to print it, #user.user_info.username works, but what If I want to show and save specific nested form's fields ? Most SO replies mention how to render a partial with the nested form's attributes, but I want to be able to print one or two of them on demand, where I want.
You can use the same table User, and override Devise.
Migrate your informations to your User table
rails g migration add_username_to_users username:string
rake db:migrate
Generate views for Devise
rails g devise:views
Add parameters to Devise
In your application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:username,
:email,
:password,
:password_confirmation,
:remember_me) }
end
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:username,
:email,
:password,
:password_confirmation,
:current_password) }
end
end
Modify Devise's views
Add your fields in /views/registrations/edit.html.erb and /views/registrations/new.html.erb
<%= f.label :username %>
<%= f.input :username %>
I'm relatively new to Rails (using Rails 4), and am having a problem with validation for my user model. Even when the form is fully filled in with both the passwords, when I submit the code two errors print out:
{:password=>["can't be blank"], :password_confirmation=>["doesn't match Password"]}
I would like the user to be saved into the database, but these validation errors are preventing that from happening. What I would like to know is what I need to change in order to get rid of these errors.
I am printing out the params object and it looks like this (the authenticity token is omitted here):
params: {"utf8"=>"✓","authenticity_token"=>"[omitted]",
"user"=>{"username"=>"testuser1", "password"=>"test",
"password_confirmation"=>"test", "email_attributes"=>{"email"=>"d#d.com"},
"first_name"=>"test", "last_name"=>"user", "gender"=>"male", "city"=>"la",
"state"=>"ca", "country"=>"usa", "dob"=>"1980-11-20"},
"commit"=>"Create Account", "action"=>"create", "controller"=>"users"}
So it appears that the password and password_confirmation attributes are getting passed correctly. I am wondering if this may have to do with the virtual attribute password I have defined in the user model, but if that is the case I am still not quite sure how to solve this problem. Any help would be greatly appreciated. Let me know if I need to elaborate further.
Here is relevant code for reference:
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_email
end
def create
if #user = User.create(user_params)
logger.debug "#{#user.errors.messages}"
logger.debug "params: #{params}"
redirect_to :action => "new"
else
logger.debug "#{#user.errors.messages}"
logger.flush
redirect_to :action => "new"
end
end
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :first_name, :last_name, :gender, :dob, :city, :state, :country, :admin_level, email_attributes: [:email])
end
end
Model:
class User < ActiveRecord::Base
has_one :email
validates_presence_of :username, :email, :password
validates_confirmation_of :password, :on => :create
accepts_nested_attributes_for :email
def password_valid?(candidatePass)
candidatePassAndSalt = "#{candidatePass}#{self.salt}"
candidatePasswordDigest = Digest::SHA1.hexdigest(candidatePassAndSalt)
if (candidatePasswordDigest == self.password_digest)
return true
else
return false
end
end
def password
end
def password=(text)
self.salt = Random.new.rand
passAndSalt = "#{text}#{self.salt}"
self.password_digest = Digest::SHA1.hexdigest(passAndSalt)
end
end
View:
<%= form_for #user, url: {action: "create"}, html: {class: "user-creation-form"} do |f| %>
<%= f.text_field :username %>username<br/>
<%= f.password_field :password %>pw<br/>
<%= f.password_field :password_confirmation %>pwcopy<br/>
<%= f.fields_for :email do |email_form| %>
<%= email_form.text_field :email %>email<br />
<% end %>
<%= f.text_field :first_name %>first<br/>
<%= f.text_field :last_name %>last<br/>
<%= f.radio_button :gender, "male" %>
<%= f.label :gender_male, "M" %>
<%= f.radio_button :gender, "female" %>
<%= f.label :gender_female, "F" %><br />
<%= f.text_field :city %>city<br/>
<%= f.text_field :state %>state<br/>
<%= f.text_field :country %>country<br/>
<%= f.date_field :dob %>dob<br/>
<%= f.submit "Create Account" %><br/>
<% end %>
The issue is your empty getter:
def password
end
It always return nil.
2 small additions to the previous answer, which should resolve your issue by the way.
1) If you're using Rails >3 (I assume you are by looking at your user_params method in the controller) you don't have to specify all those password fields and validations.
ActiveRecord automatically includes this ActiveModel method :
has_secure_password
More details at : http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password
2) If the uncrypted password/password_confirmation are shown in your log files your app is insecure. Add this to your config/application.rb :
config.filter_parameters = [:password, :password_confirmation]
This should not be needed if you are using has_secure_password in your User model.
I have a Developer model that :has_one User model. This allows for authentication and stuff across different user types.
When I create a new Developer with incorrect User data, it renders the list of validation errors. When I update a Developer with incorrect User data, it just re-renders the edit form (as it should) but doesn't show the validation errors.
My validation error display code sits in my fields partial for the form so that shouldn't make a difference.
I feel like the issue is in the way I'm trying to update my models.
def update
#developer = Developer.find(params[:id])
if #developer.user.update_attributes(params[:user]) && #developer.update_attributes(params[:developer])
flash[:success] = "Profile Updated"
sign_in #developer.user
redirect_to #developer
else
render 'edit'
end
end
and my User validations aren't anything fancy:
validates :name, presence: true, length: {maximum: 30}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: {minimum: 6}
validates :password_confirmation, presence: true
I've read at least 10 different similar-sounding posts but I haven't found anything that's helped. Any help would be great.
Edit
When I submit my update form, the following params come through
Parameters: {"utf8"=>"✓", "authenticity_token"=>"70xmNVxxES7lK2bSIIul/i5GaiJhB9+B5bV/bUVFlTs=", "user"=>{"name"=>"foo", "email"=>"foo#example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "developer"=>{"skype_name"=>""}, "commit"=>"Save changes", "id"=>"11"}
It still doesn't do the User validations. If I do the following via the console, it works though (i.e. it saves when the params are good and fails when the params are bad):
Developer.last.update_attributes(:user_attributes => {:name => "test updated", :email => "test#example.com", :password => "123456", :password_confirmation => "123456"})
So the only thing that seems different to me is the :user_attributes rather than just :user that my form is giving me. How do I change that?
Edit 2
Relevant part of my _fields partial for the form:
<%= render 'shared/error_messages' %>
<%= fields_for :user do |user| %>
<%= user.label :name %>
<%= user.text_field :name %>
<%= user.label :email %>
<%= user.text_field :email %>
<%= user.label :password %>
<%= user.password_field :password %>
<%= user.label :password_confirmation, "Confirm Password" %>
<%= user.password_field :password_confirmation %>
<% end %>
and my Developer#edit action:
def edit
#developer = Developer.find(params[:id])
end
No need to save user and developer separately, you can manage to save the user through developer model like this,
<%= form_for(#developer) do |f| %>
... developer's attribute ...
<%= f.fields_for :user do |ff| %>
... user's attribute ...
in controller, only
#developer = Developer.find(params[:id])
if #developer.update_attributes(params[:developer])
....
In developer model, you just need to add,
accepts_nested_attributes_for :user
and
attr_accessible :user_attribute
now form_for will automatically display the validation errors of user's model as well.
see this link for more details http://rubysource.com/complex-rails-forms-with-nested-attributes/
I have a User Model and an Instructor Model. There is a one-to-one relationship between user and instructor. And some users will be instructors and some will not. As such I have a registration form that uses a fields_for method to write to both.
How can I write to the instructor table only on the condition that they say they are an instructor, such as through a checkbox. And when they do write I want to maintain my validations of the table along with the rest of the form
Ideally this would work best if I can do this through the model, but I'm open to all suggestions.
Instructor Model
class Instructor < ActiveRecord::Base
belongs_to :user
validates_presence_of :school_url, :etc...
attr_accessible :school_url, :etc...
end
User Model
class User < ActiveRecord::Base
has_one :instructor, :dependent => :destroy
validates_uniqueness_of :email
validates :email, :confirmation => true
accepts_nested_attributes_for :instructor
attr_accessible :email, :password, :instructor_attributes, :etc
end
Form in HAML
- resource.build_instructor
- form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= hidden_field_tag :destination, { :value => destination}
.field
= f.label :firstname, "First Name"
= f.text_field :firstname
.field
= f.label :lastname, "Last Name"
= f.text_field :lastname
.field
= f.label :email, "E-Mail"
= f.email_field :email
.field
= f.label :email_confirmation, "Confirm E-Mail"
= f.email_field :email_confirmation
.field
= f.label :password
= f.password_field :password
.field
= f.label :password_confirmation, "Confirm Password"
= f.password_field :password_confirmation
#instructor-box
%p
%span.bold Are you an instructor?
= check_box_tag :instructor_check
%span Yes, I am an instructor
= f.fields_for :instructor do |i|
= render "/users/registrations/instructor", :form => i
I fixed the problem. It seems all too obvious now. In order for the fields_for to be cancelled out all I have to do is delete the instructor_attributes that are being created by the form in the controller. For example:
Create
# params[:user] => {:email => "justin#example.edu", ..., :instructor_attributes => { :school_url => "www.example.edu", ...}
# params[:instructor_check] => "0"
Given these parameters being passed, I can easily delete the attributes that are to be saved and the rails no longer tries to create a new record for instructor that's to be associated with user. This is literally the code I used. Not the most elegant, but it works.
params[:user].delete :instructor_attributes if params[:instructor_check] = "0"
This recognizes that no instructor profile is being created for the user and thus does not write to the table. Before it was sending back blank attributes and failing on the validations.
I've spent about 5 straight hours at this and keep ending up back at square one...time to ask for time help!
I am using Rails 3.2, devise and simple_form, I am trying to build a form that will allow a user to register (email, password) & allow them to create a simple listing object - all on the one page. However none of my nested attributes for the user are appearing on the markup when the /listings/new page loads & I cannot figure out why.
Here is what I have:
Listing controller:
def new
#listing = Listing.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #listing }
end
end
Listing model:
class Listing < ActiveRecord::Base
has_one :address
belongs_to :category
belongs_to :user
accepts_nested_attributes_for :address
accepts_nested_attributes_for :user
end
User model:
class User < ActiveRecord::Base
has_many :listings
devise :database_authenticatable, :registerable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
New Listings Form:
<%= simple_form_for(#listing) do |f| %>
<%= f.label :listing_type %>
<%= f.collection_select :listing_type, [["Creative","Creative"]], :first, :last%>
<%= f.simple_fields_for :user do |u| %>
<%= u.label :email %>
<%= u.input_field :email %>
<%= u.label_for :password %>
<%= u.input_field :password %>
<%= u.label_for :password_confirmation %>
<%= u.input_field :password_confirmation %>
<% end %>
<% end %>
My head is melted looking at this, any help is much appreciated!
Railscasts' Nested Model Form would be a good tutorial for you.
Also, what it sounds like you'd want to do is call Users#new, not Listings#new. Usually you make a form for the thing (User) which has_many of something else (Listings). So you want to make a form for a new User, not a new listing. If you take this route, then in Users#new in your controller, do something like
#user = User.new
#user.listings.build
....
If you want to keep it how it is, you might be able to do
#listing.user.build
But I'm not sure if that'll work since you're doing it in the opposite direction as I described above.
You need a new User object.
Change
<%= f.simple_fields_for :user do |u| %>
to
<%= f.simple_fields_for :user, User.new do |u| %>
It should be work.