I'm building a website with user authentication. And I just noticed that if I create a user with an existing email, it just doesn't work which is normal, but I'd like to give feedback to the user. So for that I need to determine if any user has already that email.
I've tried some things like:
if User.email.include? params[:user][:email]
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
Those are the columns for User:
2.1.0 :014 > User.column_names
=> ["id", "name", "email", "created_at", "updated_at", "password_digest", "remember_token", "admin", "team_id", "teamLeader"]
And the result I get is a big fat error:
undefined method `email' for #<Class:0x00000102b9a908>
So if anybody sees what I'm doing wrong, or knows another way to do it, that would be great.
Cheers
Try this:
if User.exists?(:email => params[:user][:email])
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
...
else
# more code here..
end
Also, you can add validations when you're creating the object:
class User
validates_uniqueness_of :email
More on different validations here: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
I believe this way of doing the validation is wrong, you should validate the uniqueness of the email in the User model itself like below
validates :email, uniqueness: true #User model
This way the validation would be on the the User model. The problem with the condition you are using is that it is accessing an instance method specific to objects as a class method. So User.email means that there is a method called email that has the same logic for all the instances of the user class or more formally a class method which you don't have here. The email is an attribute specific to each user an instance attribute/variable (Each user has a different email).
You can see/show the validation errors present on the model using #user.errors.full_messages where #user is the instance you are trying to register/save.
This is how I would normally do it if this action is for registering users i.e. creating new users.
class User < ActiveRecord::Base
#attribute accessors and accessible
validates :email, uniqueness: true
end
class UsersController < ApplicationController
def create
#user = User.new params[:user]
if #user.save
#code for redirect or rendering the page you want
else
render 'new'
end
end
end
#new.html.erb
<%= form_for #user do |f| %>
<% if #user.errors.any? %>
<div>
<ul>
<% #job.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
#form fields
<% end %>
This way you display all the error messages to the user at the top of the registration form.
Related
I am having an issue understanding how to use Rails' delegated types when it comes to validations failing on the delegatee.
Having the following code
inbox.rb
class Inbox < ApplicationRecord
delegate :name, to: :inboxable
delegated_type :inboxable, types: %w[ Mailbox Messagebox ], dependent: :destroy
end
class Mailbox < ApplicationRecord
include Inboxable
belongs_to :inbox_domain
validates :alias, presence: true, uniqueness: true
def name
"#{self.alias}##{self.domain.name}"
end
end
messagees_controller.rb
def create
#mailbox = Inbox.create inboxable: Mailbox.new(mailbox_params)
if #mailbox.save
redirect_to #mailbox.inboxable, notice: "<b>#{#mailbox.name}</b> was created."
else
render :new
end
end
private
def mailbox_params
params.require(:mailbox).permit(:alias, :inbox_domain_id)
end
When i want to create a mailbox where the alias is already taken, the following error is thrown because Mailbox.new fails the validation
ActiveRecord::NotNullViolation (PG::NotNullViolation: ERROR: null value in column "inboxable_id" violates not-null constraint
DETAIL: Failing row contains (13, 2021-09-26 20:48:53.970799, 2021-09-26 20:48:53.970799, Mailbox, null, f).
):
Tried solution
What is the correct way to handle this scenario? I have tried to check explicitly Mailbox.new first, like this:
mailbox = Mailbox.new(mailbox_params)
if mailbox.valid?
#inbox = Inbox.create inboxable: mailbox
......
While it technically works, it's a mess once you also have to validate attributes on Inbox itself
Use validates_associated to trigger the validations on the associated record:
class Inbox < ApplicationRecord
delegate :name, to: :inboxable
delegated_type :inboxable, types: %w[ Mailbox Messagebox ], dependent: :destroy
validates_associated :inboxable
end
This will add an error ("Inboxable is invalid") to the errors object on this model and prevent saving if the associated mailbox is not valid.
What you want in your controller is:
def create
# .create both instanciates and saves the record - not what you want here
#mailbox = Inbox.new(inboxable: Mailbox.new(mailbox_params))
if #mailbox.save
redirect_to #mailbox.inboxable, notice: "<b>#{#mailbox.name}</b> was created."
else
render :new
end
end
If you want to display the errors for the associated item you need to access and loop through the errors object on it:
# app/views/shared/_errors.html.erb
<ul>
<% object.errors.each do |attribute, message| %>
<li><%= message %>
<% end %>
</ul>
<%= form_with(model: #inbox) do |form| %>
<% if form.object.invalid? %>
<%= render(partial: 'shared/errors', object: form.object) %>
<% if form.object.inboxable.invalid? %>
<%= render(partial: 'shared/errors', object: form.object.inboxable) %>
<% end %>
<% end %>
# ...
<% end %>
I’m using Rails 4.2.7. I would like to throw a validation error if a user doesn’t enter their date of birth field in the proper format, so I have
def update
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
end
if #user.update_attributes(user_params)
and I have this in my view
<%= f.text_field :dob, :value => (f.object.dob.strftime('%m/%d/%Y') if f.object.dob), :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
<% if #user.errors[:dob] %><%= #user.errors[:dob] %><% end %>
However, even if someone enters a date like “01-01/1985”, the above doesn’t return a validation error to the view. What do I need to do to get the validation error to be returned properly?
Edit: Per one of the answers given, I tried
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
puts "Setting error."
#user.errors.add(:dob, 'The birth date is not in the right format.')
end
if #user.update_attributes(user_params)
last_page_visited = session[:last_page_visited]
if !last_page_visited.nil?
session.delete(:last_page_visited)
else
flash[:success] = "Profile updated"
end
redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
else
render 'edit'
end
And even though I can see the "rescue" branch called, I'm not directed to my "render 'edit'" block.
Triggering an exception doesn't add anything to the errors list. If you just want to tweak this code slightly, you should be able to call errors.add inside the rescue block. Something like #user.errors.add(:dob, 'some message here').
Keep in mind that this will only validate the date of birth when using this controller method. If you want to validate the date of birth whenever the user is saved, you'll want to explicitly add the validation to the model. You can write your own custom validation class or method, and there are also some gems that add date validation.
Calling update_attributes clears out the errors that you set in the rescue. You should check for errors, and if none, then continue on, something like this:
#user = current_user
begin
#user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
rescue ArgumentError => ex
puts "Setting error."
#user.errors.add(:dob, 'The birth date is not in the right format.')
end
if !#user.errors.any? && #user.update_attributes(user_params)
last_page_visited = session[:last_page_visited]
if !last_page_visited.nil?
session.delete(:last_page_visited)
else
flash[:success] = "Profile updated"
end
redirect_to !last_page_visited.nil? ? last_page_visited : url_for(:controller => 'races', :action => 'index') and return
end
render 'edit'
Since you redirect_to ... and return you can close out the conditional and, if you make it this far, simply render the edit page.
You may also want to add a simple validation to your user model:
validates :dob, presence: true
This will always fail if the dob can't be set for some other, unforseen, reason.
To get the user entered string to populate the field on re-load, you could add an accessor to the user model for :dob_string
attr_accessor :dob_string
def dob_string
dob.to_s
#dob_string || dob.strftime('%m/%d/%Y')
end
def dob_string=(dob_s)
#dob_string = dob_s
date = Date.strptime(dob_s, '%m/%d/%Y')
self.dob = date
rescue ArgumentError
puts "DOB format error"
errors.add(:dob, 'The birth date is not in the correct format')
end
Then change the form to set the :dob_string
<%= form_for #user do |f| %>
<%= f.text_field :dob_string, :value => f.object.dob_string , :size => "20", :class => 'textField', placeholder: 'MM/DD/YYYY' %>
<% if #user.errors[:dob] %><%= #user.errors[:dob] %><% end %>
<%= f.submit %>
<% end %>
And update the controller to set the dob_string:
def update
#user = User.first
begin
##user.dob = Date.strptime(params[:user][:dob], '%m/%d/%Y')
#user.dob_string = user_params[:dob_string]
end
if ! #user.errors.any? && #user.update_attributes(user_params)
redirect_to url_for(:controller => 'users', :action => 'show') and return
end
render 'edit'
end
def user_params
params.require(:user).permit(:name, :dob_string)
end
I would add a validation rule in the model. Like:
validates_format_of :my_date, with: /\A\d{2}\/\d{2}\/\d{4}\z/, message: 'Invalid format'
Try adding validation rule in model.
validate :validate_date
def validate_date
begin
self.dob = Date.parse(self.dob)
rescue
errors.add(:dob, 'Date does not exists. Please insert valid date')
end
end
and in your controller update your code
...
#user.update_attributes(user_params)
if #user.save
....
I think this is a case where Active Model shines. I like to use it to implement form objects without extra dependencies. I don't know the exact details of your situation but below I pasted a small demo that you should be able to adapt to your case.
The biggest benefit is that you don't pollute your controllers or models with methods to support profile updates. They can be extracted into a separate model which simplifies things.
Step 1: Store dob in users
Your users table should have a column dob of type date. For example:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name, null: false
t.date :dob, null: false
end
end
end
Don't put anything fancy in your model:
class User < ActiveRecord::Base
end
Step 2: Add Profile
Put the following in app/models/profile.rb. See comments for explanations.:
class Profile
# This is an ActiveModel model.
include ActiveModel::Model
# Define accessors for fields you want to use in your HTML form.
attr_accessor :dob_string
# Use the validatiors API to define the validators you want.
validates :dob_string, presence: true
validate :dob_format
# We store the format in a constant to keep the code DRY.
DOB_FORMAT = '%m/%d/%Y'
# We store the user this form pertains to and initialize the DOB string
# to the one based on the DOB of the user.
def initialize(user)
# We *require* the user to be persisted to the database.
fail unless user.persisted?
#user = user
#dob_string = user.dob.strftime(DOB_FORMAT)
end
# This method triggers validations and updates the user if validations are
# good.
def update(params)
# First, update the model fields based on the params.
#dob_string = params[:dob_string]
# Second, trigger validations and quit if they fail.
return nil if invalid?
# Third, update the model if validations are good.
#user.update!(dob: dob)
end
# #id and #persisted? are required to make form_for submit the form to
# #update instead of #create.
def id
#user.id
end
def persisted?
true
end
private
# Parse dob_string and store the result in #dob.
def dob
#dob ||= Date.strptime(dob_string, DOB_FORMAT)
end
# This is our custom validator that calls the method above to parse dob_string
# provided via the params to #update.
def dob_format
dob
rescue ArgumentError
errors[:dob] << "is not a valid date of the form mm/dd/yyyy"
end
end
Step 3: Use the form in the controller
Use Profile in ProfilesController:
class ProfilesController < ApplicationController
def edit
# Ensure #profile is set.
profile
end
def update
# Update the profile with data sent via params[:profile].
unless profile.update(params[:profile])
# If the update isn't successful display the edit form again.
render 'edit'
return
end
# If the update is successful redirect anywhere you want (I chose the
# profile form for demonstration purposes).
redirect_to edit_profile_path(profile)
end
private
def profile
#profile ||= Profile.new(user)
end
def user
#user ||= User.find(params[:id])
end
end
Step 4: Render the form with form_for
In app/views/profiles/edit.html.erb use form_for to display the form:
<%= form_for(#form) do |f| %>
<%= f.label :dob_string, 'Date of birth:' %>
<%= f.text_field :dob_string %>
<%= f.submit 'Update' %>
<% end %>
Step 5: Add routing
Keep in mind to add routing to config/routes.rb:
Rails.application.routes.draw do
resources :profiles
end
That's it!
I am trying to create a secure login that will require a username and password. I am struggling with this and can not figure out the problem. When I try to access my login page I receive an error for <%= form_tag login_url do %> in the login page. If anyone can help me make this work, you will be my hero.
Schema for table that holds username and password
create_table "users", force: true do |t|
t.string "name"
t.string "password_digest"
t.string "password"
t.string "password_confirmation"
t.datetime "created_at"
t.datetime "updated_at"
end
Controller from the table that holds username and password
class User < ActiveRecord::Base
has_secure_password
end
The login page (which I can not get to work)
<h2>The Maintenance Functions are restricted to authorized users. Please login below</h2>
<%= form_tag login_url do %>
<p>
<label for="name">Name:</label>
<%= text_field_tag :name, params[:name] %>
</p>
<p>
<label for="password">Password:</label>
<%= password_field_tag :password, params[:password] %>
</p>
<%= submit_tag "login" %>
<% end %>
The controller for the login page
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_name(params[:name])
if user and user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to secertin_secertout_path :notice => "Logged in successfully"
else
flash.alert = "Invalid userid/password combination"
render 'new'
end
end
def destroy
session[:user_id] = nil
redirect_to root_url_path :notice => "Logged Out"
end
end
You'll be best using Devise (or maybe Authlogic)
Reason being it's sometimes better to outsource something as important as user authentication. Why? Several reasons:
it's a vital part of your app
it needs to adapt with changes in HTTP security
it needs to work with other dependencies
If you roll your own authentication (which isn't that difficult), your major issue is you won't have the extensibility or reliability that one of these solutions provides. I would highly recommend using devise - it's doing exactly what you need anyway
Remove the password and password_confirmation columns from your table and your code should work. has_secure_password gives you the virtual attributes password and password_confirmation but it stores a hashed version of the password in your table in the password_digest column. Not storing the plain text version of the password in the database is part of what makes has_secure_password secure.
I followed railscast #250 Authentication from Scratch & got everthing wworking fine. Now I'm trying to only display edit & destroy links on my index page to admin user's.
I've set up mu User database with a admin boolean field & tried putting a simple if statement in the view of another model (hikingtrails) to only display certain links to admin users but I get this error when I try it out, undefined method 'admin?' for nil:NilClass
Database Schema
create_table "users", :force => true do |t|
t.string "email"
t.string "password_digest"
t.boolean "admin"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
User Model
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation#, :admin
validates :email, :uniqueness => true
has_secure_password
end
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery
# fetch the currently logged-in user record to see if the user is currently logged in
# putting this method in ApplicationController so that it’s available in all controllers
private
def current_user
# checks for a User based on the session’s user id that was stored when they logged in, and stores result in an instance variable
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
# to give access to this method from all the views, the helper_method makes it a helper method
helper_method :current_user
# basic authorization, user must be logged in!
def authorize
redirect_to login_url, alert: "You must be logged in to perform this action" if current_user.nil?
end
end
views/hikingtrails/index.html.erb
<% if current_user.admin? %>
<%= link_to t('.edit', :default => t("helpers.links.edit")),
edit_hikingtrail_path(hikingtrail), :class => 'btn btn-mini' %>
<%= link_to t('.destroy', :default => t("helpers.links.destroy")),
hikingtrail_path(hikingtrail),
:method => :delete,
:data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) },
:class => 'btn btn-mini btn-danger' %>
<% end %>
current_user will be nil if a user is not logged in according to your code. So you need to do this:
<% if current_user && current_user.admin? %>
or using the try method Rails adds to all objects.
<% if current_user.try(:admin?) %>
as Dogbert said, current_user will be nil if the user is not logged in.
I would suggest two other alternatives:
1) in the current_user method return a special type "guest" user instead of nil. Il will be useful in case you want to do something else with it later, for example in response to some user action.
As inspiration, look at how Ryan Bates explains the Ability class of his gem cancan: link.
The first thing he does is creating an unitilized (and not persisted in DB) user. An that Ability class will be instantiated each time Rails will parse an ERB template with that kind of user verification.
So, you could do:
def current_user
#current_user ||= ((User.find(session[:user_id]) if session[:user_id]) || User.new)
end
So, if (User.find(session[:user_id]) if session[:user_id]) returns nil, the #current_user will be set to an uninitialized User with no identity in DB.
2) define a new metod just to check if the user is an admin, for example:
# your unmodified current_user implementation
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def is_an_admin?
if current_user && current_user.admin?
end
So that you can use it in this way:
<% if is_an_admin? %>
<div>
<%= do stuff....%>
...It might be an extra method call, but it might also make your code more readable.
I know this is old, but if someone is googling the error as I did, there is actually no error in Rails Tutorial, but they forgot to highlight one thing they added.
Listing 9.54
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
Note that they added :destroy action here, not added before, which makes sure that the user is logged to perform destroy action and just then checks if he's an admin
before_action :admin_user, only: :destroy
Correction:
As of the time of this edit 12/14/2015, the rails tutorial now adds the :destroy action in Listing 9.53. If you miss that one as I did, you will get this error.
it looks like in your User model:
attr_accessible :email, :password, :password_confirmation#, :admin
admin is commented out, you need to delete the #.
I've created a simple Rails application where users can register to attend events. To create a pleasing user experience, an unidentified user can browse the events and then if they see one they'd like to event they can click 'register for the event'. Using Twitter Bootstrap, the application then presents the user with a modal (popup) asking them to register first, to attend the event.
The form on the modal is as follows:
simple_form_for #user do |f|
f.input :name, :placeholder => "Name", :label => false
f.input :email, :placeholder => "Email", :label => false
f.input :postcode, :placeholder => "Postcode", :label => false
hidden_field_tag :event_id, #event.id
f.button :submit, "Sign me up for this event"
end
So, you can see that basically I pass in an event_id value to the user controller's create action. To handle this the create action becomes:
def create
if params[:event_id].blank?
event_registration = false
else
event_id = params[:event_id].to_s
event_registration = true
end
if event_registration == true
# The user is being created as part of signing up to an event
#user_check = User.find_by_email(params[:user][:email])
unless #user_check.nil?
# The user already exists, but the visitor forgot
#user = #user_check
else
# The user is a new sign up
#user = User.new(params[:user])
end
# Now create the attendance for the user
#event = Event.find(event_id)
#attendance = #event.attendances.new
#attendance.attendee = #user
#attendance.save
redirect_target = event_attendance_thank_path(#event, #attendance)
else
# The user is being created cleanly
#user = User.new(params[:user])
end
if #user.save
redirect_to root_path, notice: "Thanks for signing up, check your email"
else
# We should destroy the failing attendance?
redirect_to root_path, alert: "Something's up with the signup. Have you already registered with this email address?"
end
end
To me, handling this level of complexity in the controller feels messy, and I'm wondering what better ways I might go about this?
Any help or pointers to refactor this would be greatly appreciated.
If it is possible for users that are already signed and logged in to mark their attendance for events, you should use the same piece of code in both places. Just create helper that will mark users' attendance and call it from both UsersController#create and Events#attend (or whatever it is called) actions.
Now -- if you have heard of "fat model, skinny controller" pattern, you are probably starting to notice it is a place you could use it.
Just create User model method to attend some event, that will accept id, or event itself as an argument. This way you could simply write in your controller:
User.create(...).tap do |user|
user.attend(params[:event_id]) if params[:event_id]
end