Username can't be blank - ruby-on-rails

So I enter a username, email, and password (+password confirmation) and it once I hit submit, it says: "Username can't be blank" and I clearly entered a username.
Here is my user.rb
class User < ActiveRecord::Base
validates :user_name, presence: true, length: { minimum: 4, maximum: 16
}
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
This is my registrations_controller:
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:email, :user_name, :password, :password_confirmation)
end
def account_update_params
params.require(:user).permit(:email, :user_name, :password, :password_confirmation, :current_password)
end
end
And the following is my new.html.erb
<h2>Sign up</h2>
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :user_name %>
<%= f.input :email, required: true, autofocus: true %>
<%= f.input :password, required: true, hint: ("#{#minimum_password_length} characters minimum" if #minimum_password_length) %>
<%= f.input :password_confirmation, required: true %>
</div>
<div class="form-actions">
<%= f.button :submit, "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
add_username_to_users.rb (migration)
class AddUsernameToUsers < ActiveRecord::Migration
def change
add_column :users, :username, :string
add_index :users, :username, unique: true
end
end
If there's any other needed to be posted, to find more about the error, please comment. Also, note that I have devise gem (properly installed) and I'm using a cloud 9 editor. Thanks in advance!

When reviewing your code on c9.io there is definitely an issue with your migrations file where you have this:
class CreateAddUserNameToUsers < ActiveRecord::Migration
def change
create_table :add_user_name_to_users do |t|
t.string :user_name
t.timestamps null: false
end
end
end
That actually created a table called add_user_name_to_users, which you can find in your schema.rb file. So when you are creating your User with the sign up form, there is no user_name to validate on the User's table since it does not exist. You should remove your migration file 20171231162016_create_add_user_name_to_users.rb since it is not what you are looking to do or drop the table (here are some docs for changing your migrations).
Instead make sure you follow this devise documentation on adding a user_name as a sign up/ sign in parameter here.
You also have a models table that has the same fields as your users table (also assuming that this was put here by accident. Make sure you double check your migrations as it looks like that was one of the results of the error you are seeing.

Related

Rails, Active Record, Devise and cloudinary, simple form, heroku... photo does not upload to my cloudinary

Ok so, you get a gist of my problem in the title. I created an app in rails which requires users to create a profile and upload a picture in order to access some features.
Anyway, everything works except the picture upload part.
I am new to all this and I get a bit confused with Devise in particular. I am looking for the user controller, which is where I should find the create action for the users right? But I have no idea where to find it.
That being said, I am not even sure the problem comes from the controller. Could the source of the problem be simple form? No idea. I have been searching and trying for quite a while now, and I thought it was time to ask the community :)
I am going to share everything I can think of which concerns the problem. Let me know if you need me to show anything else.
1/ User model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :user_instruments, dependent: :destroy
has_many :instruments, through: :user_instruments
has_many :messages, dependent: :destroy
has_one_attached :photo, dependent: :destroy
has_many :reviews_written, class_name: "Review", foreign_key: :writer_id
has_many :reviews_received, class_name: "Review", foreign_key: :receiver_id
has_many :jam_sessions, dependent: :destroy
def profile_picture
if photo.attached?
photo.key
else
"avatar-unknown.png"
end
end
def full_name
"#{first_name} #{last_name}"
end
end
2/ Application controller:
class ApplicationController < ActionController::Base
before_action :authenticate_user!, except: [:home, :index, :show]
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
# For additional fields in app/views/devise/registrations/new.html.erb
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, :bio])
# For additional in app/views/devise/registrations/edit.html.erb
devise_parameter_sanitizer.permit(:account_update, keys: [:username])
end
def default_url_options
{ host: ENV["DOMAIN"] || "localhost:3000" }
end
end
3/ The new view where the simple form is located:
<div class="container h-100">
<div class="row justify-content-center align-items-center mt-3">
<div class="col col-5">
<h2>Sign up</h2>
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :email,
required: true,
autofocus: true,
input_html: { autocomplete: "email" }%>
<%= f.input :password,
required: true,
hint: ("#{#minimum_password_length} characters minimum" if #minimum_password_length),
input_html: { autocomplete: "new-password" } %>
<%= f.input :password_confirmation,
required: true,
input_html: { autocomplete: "new-password" } %>
<%= f.input :first_name,
required: true %>
<%= f.input :last_name,
required: true %>
<%= f.input :bio,
required: true %>
<%= f.input :photo, as: :file %>
<%= cl_image_upload_tag(:image_id) %>
</div>
<div class="d-flex justify-content-end">
<%= f.button :submit, "Sign up", class: 'big-button block' %>
</div>
<% end %>
<div class="mt-3 mb-3">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
</div>
As you can see there are 2 different lines to prompt the user to upload a picture. They both seem to work on the user's point of view, but actually never upload any picture to my cloudinary.
4/ I have this line in both my development.rb and my production.rb file:
config.active_storage.service = :cloudinary
5/ I added this line in config/storage.yml
cloudinary:
service: Cloudinary
6/ And I did the necessary steps in my terminal to configure cloudinary to my heroku:
heroku config:set CLOUDINARY_URL=cloudinary://166....
Am I missing something?
Anyone's help would be very much appreciated!
Olivier
You forgot to add it as a strong parameter in the registrations. Since this is handled by device, you have to add it to the method in your application controller like so:
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, :bio, :photo])
end
Have you put your personal API key in the .env of your app?
CLOUDINARY_URL=cloudinary://API Key:API Secret
something like this:
CLOUDINARY_URL=cloudinary://298522693261255:Qa1ZfO4syfbOC-***********************8
After check in your terminal
heroku config
you must see
CLOUDINARY_URL: cloudinary://....with_your_informations_...

Omniauth Twitter sign up takes over existing Devise user record (if any with same username) instead of stopping the sign up with username exists error

I'm using Devise (form) and Omniauth Twitter. My sign up form fields are:
username (unique, required)
email (unique, required)
full name
password
Both sign up works fine when there is no existing record with the same username. User can sign up either via Twitter or email form.
The problem is:
If user tries to sign up via form using an existing username, it doesnt allow. It is OKAY.
But when user tries to sign up via Twitter and if twitter nickname (username in my website) already exists, it doesnt block the sign up, it transfers the existing account to the new sign up Twitter, it updates existing user details with the ones from Twitter profile (API) . How can I stop it?
Thank you!
omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
designer = Designer.from_omniauth(request.env['omniauth.auth'])
if designer.persisted?
sign_in_and_redirect designer, notice: "Signed in!"
else
session["devise.designer_attributes"] = designer.attributes
redirect_to new_designer_registration_url
end
end
alias_method :twitter, :all
end
designer.rb
class Designer < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, omniauth_providers: [:twitter]
validates_presence_of :email
validates_uniqueness_of :email
validates_presence_of :username
validates_uniqueness_of :username
validates_presence_of :password, if: :password_required? # recommended
validates_confirmation_of :password, if: :password_required? # recommended
validates_length_of :password, within: password_length, allow_blank: true # recommended
extend FriendlyId
friendly_id :username, use: [:slugged, :history]
has_many :posts
mount_uploader :avatar, AvatarUploader
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |designer|
designer.provider = auth.provider
designer.uid = auth.uid
designer.slug = auth.info.nickname
designer.username = auth.info.nickname
designer.twitter_username = auth.info.nickname
designer.email = auth.info.email
designer.password = Devise.friendly_token[0, 20]
designer.fullname = auth.info.name
end
end
def self.new_with_session(params, session)
if session["devise.designer_attributes"]
new(session["devise.designer_attributes"]) do |designer|
designer.attributes = params
designer.valid?
end
else
super
end
end
def password_required?
super && provider.blank?
end
def update_with_password(params, *options)
if encrypted_password.blank?
update_attributes(params, *options)
else
super
end
end
end
controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:designer).permit(:username, :fullname, :email, :password, :password_confirmation)
end
def account_update_params
params.require(:designer).permit(:username, :fullname, :email, :location, :website, :twitter, :bio, :password, :password_confirmation, :current_password)
end
protected
def after_sign_up_path_for(resource)
edit_designer_path(current_designer) if current_designer
end
end
devise/registration/new.html.erb
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :fullname, required: true, placeholder: "Fullname", label: false, input_html: { maxlength: 120 } %>
<%= f.input :username, unique: true, required: true, placeholder: "Username", label: false, input_html: { maxlength: 120 } %>
<%= f.input :email, required: true, placeholder: "Email", label: false, input_html: { maxlength: 120 } %>
<% if f.object.password_required? %>
<%= f.input :password, required: true, placeholder: "Password (minimum 6 chars)", label: false, input_html: { maxlength: 120 } %>
<% end %>
</div>
<div class="form-actions tl pl1">
<%= f.button :submit, "Sign up" %>
</div>
<% end %>
From this I redirect to profile editing for additional profile information which is designer/edit.html.erb
I fixed it with adding the codes below to designer.rb 🤦‍♂️
def should_generate_new_friendly_id?
slug.blank? || username_changed?
end

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

rails extending devise registration form

I'm working on a system where it has 2 models, a user model, a school model.
I'm using devise registration for a regular user sign_up with 'role' as an additional field to indicate whether user is a regular user or a school_user.
For now there is a admin user who creates a new school record while user will register using devise/sign_up action. School doesn't have login information yet.
I can call schools/new action (as school signup link) to add a new school.
Instead I want to extend the devise registration for school which will sign up using 'new school signup link' as a new user (use email, password, role='school' for a user model) and other fields like name, address, etc. going into the regular schools table. This way school admin gets a login account as well.
How do I extend devise/registration form and create these 2 records?
I really appreciate few thoughts.
Override devise registrations route:
routes.rb :
devise_for :users, controllers: {registrations: 'registrations'}
Create controllers/registrations_controller.rb :
class RegistrationsController < Devise::RegistrationsController
after_filter :add_school
protected
def add_school
if resource.persisted? # user is created successfuly
# resource holds all your form data.
resource.schools.build do |school|
school.name = resource.school_name # form fields...
end
#school.save
end
end
end
To validate school fields add validations to your user.rb model
eg:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :name,
presence: true,
length: {in: 1..50}
validates :school_name,
presence: true,
length: {in: 1..50}
end
Just for reference:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :school_name %>
<br/>
<%= f.text_field :name, autofocus: true %></div>
<div><%= f.label :email %>
<br/>
<%= f.email_field :email %></div>
# ...
<div><%= f.label :school_name %>
<br/>
<%= f.text_field :school_name %></div>
<div><%= f.submit "Sign up" %></div>
<% end %>

Ruby on Rail: Add Polymorphic address to Devise user

ROR newbie here. :-)
I am using Devise for authentication and have added first_name and last_name to the User model created by devise. I have also created Address model:
create_table :addresses do |t|
t.string :line1
t.string :line2
t.string :city
t.string :state
t.integer :addressable_id
t.string :addressable_type
t.timestamps
end
add_index :addresses, [:addressable_type, :addressable_id], :unique => true
end
a partial at views/shared/_address.html.erb
user.rb
has_one :address, :as => :addressable
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :firstname, :lastname
attr_accessible :address_attributes
accepts_nested_attributes_for :address, :update_only => true
added the following lines at views/devise/registrations/new.html.erb and edit.html.erb
<% f.fields_for :address do |address|%>
<%= render :partial => 'shared/address', :locals => {:f => address}%>
<% end %>
<% end %>
<% f.fields_for :address do |address|%>
<%= render :partial => 'shared/address', :locals => {:f => address}%>
<% end %>
<% end %>
Now I want to have Devise create an Address when a new user sign up. And the user could fill in the address after sign in. Apparently something is missing or I am doing it wrong. The address fields are not showing at sign_up nor edit.
Please share your wisdom. Thanks in advance.
The general behavior of fields_for is that the associated object will not show in the form unless it already exists. E.g. if we were speaking outside the context of Devise, then to get the address to show up on the new user form you would have something like:
# users_controller.rb
def new
#user = User.new
#user.address = Address.new
end
and then the address would display on the form.
Given that this is a Devise-managed form and you do not have direct access to the necessary controller, I'm guessing the best thing to do is to use an ActiveRecord callback to instantiate an address if it doesn't exist already. E.g. something like
# user.rb
after_initialize :create_address
def create_address
self.address = Address.new if self.address.nil?
end

Resources