I am very very new to Rails and am struggling a little.
I have users that follow a basic sign up, edit process.
I have a situation where a user can be 'part registered', i.e a users email address in the db with a random password as they where put in there via an invite process by an existing user.
When an invited user who already technically exists in the db tries to register I get 'email already exists' since I do have uniqueness checks applied to the user object and a unique index on the table.
I did turn this off and using if statements within the create method to see if the user exists and checked other values did an update or a new and save. All of the if conditions were ignored and a new user was created.
What I want to do is use the same registration form that 'creates' a user to update an existing 'part registered' user with their entered password and name if they exist (email address check) and other db flags are set to true. if the user does exist but the conditions not met load a 'forgot password' page or if user is a new user create and save it.
Any assistance or advice on how to achieve the above would be brill.
Thanks
Vicky
Here is my set up:
User class
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :email, :name, :password, :password_confirmation, :tandc
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => {:with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
validates :tandc, :presence => true
before_save :encrypt_password
.
.
.
end
I have a UsersController with the following
def new
#user = User.new
#title = "Sign Up"
end
def create
if #user.save
sign_in #user
redirect_to #user
else
#title = "Sign up"
render 'new'
end
end
def update
if #user.update_attributes(params[:user])
redirect_to #user
else
#title = "Edit"
render 'edit'
end
end
.
.
.
I have views:
user > new.html.erb
<%= form_for(#user, :html => { :class => "validateUser", :name =>"edit_user"}) do |f| %>
.
.
.
form stuff here
.
.
.
<% end %>
and user > edit.html.erb
<%= form_for(#user, :html => { :class => "validateUser", :name =>"edit_user"}) do |f| %>
.
.
.
form stuff here
.
.
.
<% end %>
my routes for users are as follows
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
A quick, but not elegant solution could be:
def create
if user = User.find_by_email(params[:user][:email])
if user.part_registered?
user.update_attributes(params[:user])
user.save
sign_in user
redirect_to user
else
#user = User.new(params[:user])
if #user.save
# just to trigger the error messages ...
else
#title = "Sign up"
render 'new'
end
end
else
#user = User.new(params[:user])
if #user.save
sign_in #user
redirect_to #user
else
#title = "Sign up"
render 'new'
end
end
end
Sorry, i don't have time right now to optimize these conditions
First off, don't remove the unique index on the user's email. You need that to be unique if you are using it as the natural key of the model, aka signing in with it.
Ask yourself, what is the point of an invitation? I have a sneaking suspicion creating a new user is not really what you want the application to do... what if the user never takes up the inviter on the invitation?
Instead you could make an Invitation object with the user's email and the inviter (if that is the point of an invitation.) Then you could give a link that passes in the invitation as a parameter: <%= link_to new_user_url, email: invitation_email_here %> and put that link in, say, an email to the invited user.
In users/new.html.erb you could pass in the value to pre-fill the form with the email:
<%= f.email_field :email, value: params[:email] %>
Also, I would put your form in a partial to not repeat yourself.
The way form_for works, if #user is new, it will issue a POST request with the filled in values to the create action of the UsersController. If #user is an existing model, it will fill the form with the existing values, issue a PUT request to the update action of the UsersController.
Long story short the edit and new views can have the identical form. To simply display the same form, make a partial in the users/ veiws directory.
<!-- app/views/users/_form.html.erb -->
<%= form_for(#user, html: { class: "validateUser" }) do |f| %>
...
<% end %>
And then in the views where you would put the form, put <%= render 'users/form' %>
You can use something along the lines of User.find_or_initialize_by_email(:email => params[:user_email]) to get the User object.
Basically, find_or_initialize will attempt to get the User from the database, and if it fails to get anything it will create a new one that you can later save.
After you get your User object, you can perform whatever you want to its fields, and then save the result to the database (either updating the old entry or creating a new one).
(Note: if you want to check whether the User previously existed, use .persisted? on the object returned by find_or_initialize, this will return a boolean value telling you if it was already in the database.)
When someone registers with the site, you could send an account verification email before logging them in. The email includes a link with a system-generated token (e.g. 'User.token'). When they click the link, your verification_controller retrieves the user by their token and lets them set their password.
# In class User < ActiveRecord::Base
# Give the user a unique, random token
user.token = SecureRandom.urlsafe_base64(20)
# In class VerificationsController < ApplicationController
# Retrieve the user with their token
#user = User.find_by_token(params[:token])
Now let's consider your case. When an invited user tries to register before accepting an invite, you can still send an account verification email. Now they have two emails: 1) from the invite and 2) from their registration. It doesn't matter...they just have to click the link and set their password.
You will probably want to expire the token since it gives a way to access the account. A popular authentication framework, AuthLogic, has a password resets tutorial that is helpful. It shows a similar technique and might give you some ideas for your hand-built solution.
You could try in users_controller.rb
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #collection, notice: 'User was successfully updated.'}
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
the add link to your view
<%= link_to content_tag(:span, "Edit"), edit_user_path(#user), :class => 'edit' %>
in new.html.erb remove the form and add
<%= render 'form' %>
(this will render the partial that you will add form code to)
create _form.html.erb and add the form code to this
then create your edit.html.erb view and add code
<%= render 'form' %>
Related
[Update] I still dont know what to put in my else of my create conroller to put and error message.
I am trying to validate the format of users email.
This is the html the error is coming from
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this article
from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
I experimented with this from an old ruby video but it didn't seen to pan out.
This is my user.rb model
class User < ApplicationRecord
has_secure_password
validates :email, presence: true, uniqueness: true
validates :password, length: { minimum: 4 }
validates :email, format: { with: /(\A([a-z]*\s*)*\<*([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\>*\Z)/i }
end
I get these errors currently
No route matches {:action=>"show", :controller=>"users", :id=>nil},
missing required keys: [:id]
and
undefined method `errors' for nil:NilClass
whenever the password or email makes an error from the requirements.
my create action in my sessions controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.create(password: params[:password], email: params[:email], firstname: params[:firstname], lastname: params[:lastname])
if #user.save
redirect_to user_path(#user)
else
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
def show
#user = User.find(params[:id])
end
end
When there is no user with the requested email address, your #user variable will be nil in the controller (since this is the returned value of the find_by_email method if nothing is found)
In that case, you are explicitly rendering your new view which then attempts to render errors on the #user object. However, since the variable is nil instead of the User object you are apparently expecting there, nil.errors fails with the error you are seeing there.
To solve this, you should either check in your view if you have an actual #user object, or render a more suitable view from your controller.
3 issues here
def create
#user = User.create(password: params[:password], email: params[:email], firstname: params[:firstname], lastname: params[:lastname])
redirect_to user_path(#user)
end
That function tries to create a user and regardless of success it then goes on to redirect to the show action
1) You need to check if the User.create actually succeeds then take appropriate action
2) You have no route for the show action so you need to fix that but
3) None of your actions are specifying the format. What happens if someone is using an XML feed Are you sure you want to show the user, Just show a list of users, there won't be anything much to see
Several people including asked for the full stack trace which you seem to have ignored. This would be so much easier to track down the origination of your error had you done so, including the params sent to the create action
def create
if #user = User.create(password: params[:password], email: params[:email], firstname: params[:firstname], lastname: params[:lastname])
redirect_to user_path(#user)
else
format.html { render :action => "new" }
end
end
Now you have to track down why the create failed
You should also show the errors to the user by using a flash message
I have two partial views for two different sign up forms. On my home page , based on the link one clicks on, I'm rendering respective form.(views/application/index)
= link_to 'Mentor', new_user_path(user_role: true), :class =>'btn'
= link_to 'Mentee', new_user_path, :class =>'btn'
In views/users/new.html.haml , I'm checking the user role and redirecting to the respective form.
- if params[:user_role]
= render 'mentor'
- else
= render 'mentee'
In the user model I've added validation like this.
class User < ActiveRecord::Base
email_regex = /\A[\w+\-.]+#cisco.com/i
validates :cisco_email, :presence => true,
:format => { :with => email_regex,}
validates :work_city, :presence => true
end
So, when there is any invalid field I want to direct to the same form with a flash message. My controller looks like this.
class UsersController < ApplicationController
def index
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user]) # Not the final implementation!
if #user.save
flash[:success] = "Welcome to the CSG Mentoring Tool!"
redirect_to #user
else
flash[:notice] = "Error regsitering."
if params[:user][:user_role]
render :partial => 'users/mentor'
else
render :partial => 'users/mentee'
end
end
end
end
When an invalid field entry is there, it is redirecting to 'mentee' page no matter on which page the error is made. Also the entire css styling gets changed and flash is also not displayed
Why this is not working?
if params[:user][:user_role]
render :partial => 'users/mentor'
else
render :partial => 'users/mentee'
end
params[:user][:user_role] is nil.
You can check it using lots of way:
Above your if condition raise params[:user].inspect
Why its nil?
Reason of this is You are passing new_user_path(user_role: true) user_role true, but user_role is not true in mentor form.
params[:user_role] will not set user_role = true field in mentor form.
Set user_role
<%=f.hidden_field :user_role, value: params[:user_role] %>
If its supposed to be true for mentor always
<%=f.hidden_field :user_role, value: true %>
By default flash will make them available to the next request, but sometimes you may want to access those values in the same request.
Reference
This works with redirection
flash[:success] = "Welcome to the CSG Mentoring Tool!"
This will work with render
flash.now[:success] = "Welcome to the CSG Mentoring Tool!"
I have a form where I want to make it possible for users to change their password. I don't use Devise or anything like that. Before changing their passwords I want them to enter their current password, and I want to do it in an secure way. The form looks something like this:
--------------------
I Current password I
--------------------
--------------------
I Password I
--------------------
--------------------
I Confirm password I
--------------------
---------------
I Submit I
---------------
In my UsersController I have two methods that look like this:
def edit_password
end
def change_password
# Probably not correct, but I want to make sure that the current password is correct before the user can change password.
if #user.authenticate(params[:current_password])
respond_to do |format|
if #user.update_attributes(user_params)
format.html { redirect_to(#user, :notice => 'Your account was successfully updated') }
format.json { respond_with_bip(#user) }
else
format.html { render :action => "edit" }
format.json { respond_with_bip(#user) }
end
end
else
redirect_to edit_user_path(#user), notice: "Your current password was incorrect"
end
end
The form is displayed in my edit form action. My routes look like this:
resources :users do
get 'edit_password', to: "users#edit_password"
put 'change_password', to: "users#change_password"
end
My form looks like this:
= form_for #user, :url => user_change_password_path(#user) do |f|
.form-group
= f.label :current_password
= f.text_field :current_password
.form-group
= f.label :password
= f.password_field :password
.form-group
= f.label :password_confirmation
= f.password_field :password_confirmation
.form-actions
= f.submit "Update Account"
In my user model I have attr_accessor :current_password.
When I click submit I currently get this error:
No route matches [PATCH] "/users/1/change_password"
So, how can I get it to work, is my current approach secure, and, if not, what should I change?
To fix the routes issue, please, at your config/routes.rb change this:
put 'change_password', to: "users#change_password"
with this:
patch 'change_password', to: "users#change_password"
Because with explicit put the patch alias is not defined.
The way you are doing is pretty standard, authenticate with old password and then let change it.
Perhaps you want to remove the id from url and change only the current_user's password, but I don't think its an issue, because you are asking for the current password.
I have a table CLIENTS with id, name and email fields and I am sending them emails using ActionMailer with 3rd party SMTP.
Now I want the clients to have subscription option too so I added "subscription" column with default value as true.
Now how to generate a link which can be put in views mailer template so when the user clicks on it, the subscription value changes to false so in future the client dont' get any email ? Do note that these clients are not my rails app users so I can't uses what is been suggested here Rails 3.2 ActionMailer handle unsubscribe link in emails
I found this link how to generate link for unsubscribing from email too which looked helpful but I thought may be in 3 years, we might have got a better solution
Here is my Complete Code -
#client.rb
attr_accessible :name, :company, :email
belongs_to :user
has_many :email_ids
has_many :emails, :through => :email_ids
before_create :add_unsubscribe_hash
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
Here is Clients_controller.rb file
# clients_controller.rb
def new
#client = Client.new
respond_to do |format|
format.html
format.json { render json: #client }
format.js
end
end
def create
#client = current_user.clients.new(params[:client])
respond_to do |format|
if #client.save
#clients = current_user.clientss.all
format.html { redirect_to #client }
format.json { render json: #client }
format.js
else
#clients = current_user.clients.all
format.html { render action: "new" }
format.json { render json: #client.errors, status: :error }
format.js
end
end
end
def unsubscribe
#client = Client.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#client.update_attribute(:subscription, false)
end
The code is working fine for existing records and the unsubscription is working perfectly, I am only having problem in creating new clients.
I have used #client in unsubscribe method as I am using this object in client_mailer.rb template (using #client or just using client, both are working!)
EDIT 2 -
_form.html.erb
<%= simple_form_for(#client, :html => {class: 'form-horizontal'}) do |f| %>
<%= f.input :name, :label => "Full Name" %>
<%= f.input :company %>
<%= f.input :email %>
<%= f.button :submit, class: 'btn btn-success' %>
<% end %>
I have copied the full track stack at http://jsfiddle.net/icyborg7/dadGS/
Try associating each client with a unique, but obscure, identifier which can be used to look up (and unsubscribe) the user via the unsubscribe link contained within the email.
Start by adding another column to your clients table called unsubscribe_hash:
# from command line
rails g migration AddUnsubscribeHashToClients unsubscribe_hash:string
Then, associate a random hash with each client:
# app/models/client.rb
before_create :add_unsubscribe_hash
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
Create a controller action that will toggle the subscription boolean to true:
# app/controllers/clients_controller.rb
def unsubscribe
client = Client.find_by_unsubscribe_hash(params[:unsubscribe_hash])
client.update_attribute(:subscription, false)
end
Hook it up to a route:
# config/routes.rb
match 'clients/unsubscribe/:unsubscribe_hash' => 'clients#unsubscribe', :as => 'unsubscribe'
Then, when a client object is passed to ActionMailer, you'll have access to the unsubscribe_hash attribute, which you can pass to a link in the following manner:
# ActionMailer view
<%= link_to 'Unsubscribe Me!', unsubscribe_url(#user.unsubscribe_hash) %>
When the link is clicked, the unsubscribe action will be triggered. The client will be looked up via the passed in unsubscribe_hash and the subscription attribute will be turned to false.
UPDATE:
To add a value for the unsubscribe_hash attribute for existing clients:
# from Rails console
Client.all.each { |client| client.update_attribute(:unsubscribe_hash, SecureRandom.hex) }
In my application, only users with the administrator role may create new users. In the new user form, I have a select box for each of the available roles that may be assigned to new users.
I am hoping to use the after_create callback method to assign the role to the user. How can I access the selected value of the select box in the after_create method?
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User creation successful.'
format.html { redirect_to #user }
else
format.html { render :action => 'new' }
end
end
end
In the user model I have:
after_create :assign_roles
def assign_roles
self.has_role! 'owner', self
# self.has_role! params[:role]
end
I receive an error because the model doesn't know what role is.
You could use attr_accessor to create a virtual attribute and set that attribute as part of your create action.
The short answer is, no. You cannot pass any arguments to after_create.
However what your trying to do is a pretty common and there are other ways around it.
Most of them involve assigning the relationship before the object in question is created, and let ActiveRecord take care of everything.
The easiest way to accomplish that depends on the relationship between Roles and Users. If is a one to many (each user has one role) then have your users belong to a role and sent role_id through the form.
<%= f.collection_select :role_id, Role.all, :id, :name %>
If there is a many to many relationship between users and roles you achieve the same by assigning to #user.role_ids
<%= f.collection_select :role_ids, Role,all, :id, :name, {}, :multiple => :true %>
The controller in either case looks like
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User creation successful.'
format.html { redirect_to #user }
else
format.html { render :action => 'new' }
end
end
end