I followed Michael Hartl's tutorial up to a point, then followed Ryan Bates' Railcast for adding reset password (#274). Perhaps "tried to follow" is more accurate since I can't get the reset password to work despite many hours of effort/searching/tial&error.
Had to deviate from Ryan's call: #user.update_attributes(params[:user]) to: #user.update_attributes(user_params) as I was getting a ForbiddenAttributesError.
I also had to comment-out the #validates :password, length: { minimum: 6 } in the User class as it failed on submission (even though the submitted password was greater than 6 in length).
The code doesn't currently generate any errors, and the redirect_to root_url, :notice => "Password has been reset!" works. However, the password is not updated in the db.
Any insight is very gratefully received.
app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
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 }
has_secure_password
#validates :password, length: { minimum: 6 }
before_create :create_remember_token
def send_password_reset
generate_token(:password_reset_token)
self.password_reset_sent_at = Time.zone.now
save!
UserMailer.password_reset(self).deliver
end
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64(40)
end while User.exists?(column => self[column])
end
def create_remember_token
self.remember_token = User.digest(User.new_remember_token)
end
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update]
before_action :correct_user, only: [:edit, :update]
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params) if #user.save
sign_in #user
flash[:success] = "Welcome!"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation, :password_reset_token)
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
user.send_password_reset if user
redirect_to root_url, :notice => "Email sent with password reset instructions."
end
def edit
#user = User.find_by_password_reset_token!(params[:id])
end
def update
#user = User.find_by_password_reset_token!(params[:id])
if #user.password_reset_sent_at < 2.hours.ago
redirect_to new_password_reset_path, :alert => "Password reset has expired."
elsif #user.update_attributes(user_params)
redirect_to root_url, :notice => "Password has been reset!"
else
render :edit
end
end
private
def user_params
params.require(:user).permit(:id, :name, :email, :created_at, :updated_at, :password_digest, :remember_token, :admin, :auth_token, :password_reset_token, :password_reset_sent_at)
end
end
app/views/password_resets/edit.html.erb
<%= form_for #user, :url => password_reset_path(params[:id]) do |f| %>
<% if #user.errors.any? %>
<div class="error_messages">
<h2>Form is invalid</h2>
<ul>
<% for message in #user.errors.full_messages %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</div>
<div class="actions"><%= f.submit "Update Password" %></div>
<% end %>
app/views/password_resets/new.html.erb
<h1>PasswordReset#new</h1>
<%= form_tag password_resets_path, :method => :post do %>
<div class="field">
<%= label_tag :email %>
<%= text_field_tag :email, params[:email] %>
</div>
<div class="actions"><%= submit_tag "Reset Password" %></div>
<% end %>
There is no model for password_resets.
In app/views/password_resets/edit.html.erb, your form describes :password and :password_confirmation. However, in your PasswordResetsController, you update using user_params, which describe :id, :name, :email, :created_at, :updated_at, :password_digest, :remember_token, :admin, :auth_token, :password_reset_token, :password_reset_sent_at. Note the lack of the attributes you are trying to set. In the console, you will probably see a message indicating that the desired attributes have been discarded. Many of the attributes that you have added to user_params do not make sense in any context, as they would never be set by the user in any circumstance, like :id, :created_at, :updated_at, :password_digest, :remember_token, :admin, :auth_token, :password_reset_token, :password_reset_sent_at. In fact, may of these are blatant security risks (do you want the user to decide if they are an admin?). You will probably have better luck if you change the user_params method in PasswordResetsController to look like:
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
Make sense?
Related
I'm a beginner and have a serious problem with making two steps signing up.
I keep getting that error:
First argument in form cannot contain nil or be empty
<%= form_for(#Profile) do |f| %>
Users can sign up with their user information which are on the user table(DB). And all is fine with them. But I really want them to have profiles. So they can put their bio for example.
I tried to create MVC structure for the profiles based on users' MVC but it doesn't work.
I have been trying to find the answer for days. Tried hundreds of variations but nothing worked out. Please help!
views/users/new.html.erb (This is working)( I just wanted to create profile for the users and give them the ability to fill out their information as the second steps of the signing up)
<div class="container">
<% provide(:title, 'Sign up') %>
<h1 class="center">Sign up</h1>
<div class="row">
<div class="col s12 m10 l8 offset-m1 offset-l2">
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
<%= f.label :genre %>
<%= f.text_field :genre %>
<%= f.label :middle_name %>
<%= f.text_field :middle_name %>
<%= f.label :last_name %>
<%= f.text_field :last_name %>
<%= f.label :preferred_name %>
<%= f.text_field :preferred_name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<div class="row center">
<%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
models/user.rb
class User < ActiveRecord::Base
has_one :profile
has_many :microposts, dependent: :destroy
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :first_name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
def feed
Micropost.from_users_followed_by(self)
end
def following?(other_user)
relationships.find_by(followed_id: other_user.id)
end
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
end
def unfollow!(other_user)
relationships.find_by(followed_id: other_user.id).destroy
end
private
def create_remember_token
self.remember_token = User.digest(User.new_remember_token)
end
end
controllers/users_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user,
only: [:index, :edit, :update, :destroy, :following, :followers]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the my app"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "just got destroyed."
redirect_to users_url
end
def following
#title = "Connections"
#user = User.find(params[:id])
#users = #user.followed_users.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "known by"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
def user_params
params.require(:user).permit(:first_name, :middle_name, :last_name, :preferred_name, :email, :password,
:password_confirmation)
end
# Before filters
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
second signup page
views/users/signup2.html.erb
<div class="container">
<% provide(:title, 'Sign up2') %>
<h1 class="center">Sign up2</h1>
<div class="row">
<div class="col s12 m10 l8 offset-m1 offset-l2">
<%= form_for(#Profile) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :primary_instrument %>
<%= f.text_field :primary_instrument %>
<%= f.label :bio %>
<%= f.text_field :bio %>
<div class="row center">
<%= f.submit "Create my account2", class: "btn btn-large btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
controllers/profiles_controller.rb
class ProfilesController < ApplicationController
before_filter :get_user
def get_user
#profile = User.find(params[:user_id])
end
# generate new-profile form
def new
#user.profile = Profile.new
#profile = #user.profile
end
# process new-profile-form post
def create
#user.profile = Profile.new(params[:profile])
#profile = #user.profile
respond_to do |format|
if #profile.save
flash[:notice] = 'Profile was successfully created.'
format.html { redirect_to(#profile) }
format.xml { render :xml => #profile, :status => :created, :location => #profile }
...
end
end
end
# generate edit-profile form
def edit
#profile = Profile.find(params[:id])
end
# generate edit-profile-form post
def update
#profile = #user.profile
respond_to do |format|
if #profile.update_attributes(params[:profile])
flash[:notice] = 'Profile was successfully updated.'
# format.html { redirect_to(#profile) }
format.html { redirect_to(user_profile(#user)) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #profile.errors, :status => :unprocessable_entity }
end
end
end
Routes.rb
root to: 'static_pages#home'
match '/signup', to: 'users#new', via: 'get'
match '/signup2', to: 'profiles#new', via: 'get'
match '/signin', to: 'sessions#new', via: 'get'
match '/signout', to: 'sessions#destroy', via: 'delete'
match '/help', to: 'static_pages#help', via: 'get'
match '/about', to: 'static_pages#about', via: 'get'
match '/contact', to: 'static_pages#contact', via: 'get'
end
Your #Profile variable is capitalized in the markup, while the variable set in your controller is downcased. Rewrite the variable in the markup like so:
<%= form_for(#profile) do |f| %>
Hope it helps!
Apart from #Zoran's answer you can use the following gem which would make your life easy with multi step forms.
Wicked gem
The problem I'm facing most likely has something to do with strong parameters. The thing is when I try to edit some user information and update it, an error appears which is not related with this form. That kind of error's supposed to pop up only when signing up or logging in.
For example, here is my database. And then I click 'Edit'.
After editing some information and submitting it, the error pops up.
controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to [#user, #task], notice: "Thank you for signing up!"
else
render "new"
end
end
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to {|format| format.js }
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
end
views/user/edit.html
<h1>Editing user</h1>
<%= form_for :user, url: #user, method: :patch do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#user.errors.count, "error") %> prohibited
this task from being saved:
</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :first_name %><br>
<%= f.text_field :first_name %>
</p>
<p>
<%= f.label :last_name %><br>
<%= f.text_field :last_name %>
</p>
<p>
<%= f.submit %>
</p>
<%= link_to 'Back to List', users_path %>
<% end %>
models/user.rb
class User < ActiveRecord::Base
has_secure_password
EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :first_name, presence: true,
length: {maximum: 20}
validates :last_name, presence: true,
length: {maximum: 40}
validates :email, presence: true,
format: {with: EMAIL_REGEX},
uniqueness: {case_sensitive: false}
validates :password,
length: {within: 6..40}
has_many :tasks
end
What do I do? Can anybody help?
The problem is not with the strong params. It is due to validation you have on password.
This
validates :password,length: {within: 6..40}
should be
validates :password,length: {within: 6..40}, on: :create
Yes, the problem is in strong params. You should never permit password and password_confirmation
def user_params
params.require(:user).permit(:first_name, :last_name, :email)
end
Just remove password and password_confirmation from user_params. You wouldn't want to mass assign those values.
And this would be a very bad practice to store a plain password in database. You should store encrypted or hashed password in database; it would enhance the security and integrity of your application.
To get more information about storing an encrypted or hashed password, please take a look at this and this links.
I am using rails 4 and I've read that attr_accessible is beter not to use in this version. I have created that code:
class UsersController < ApplicationController
def new
#user = User.new(user_params)
end
private
## Strong Parameters
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
But it gives me out this error:
ActionController::ParameterMissing in UsersController#new
param is missing or the value is empty: user
I am trying to display that html.erb:
<%= form_for :user do |f| %>
<%= f.text_field :name %>
<%= f.text_field :email %>
<%= f.text_field :password %>
<%= f.text_field :password_confirmation %>
<%= f.label :submit %>
<% end %>
Any solutions?
Typically the new action would be #user = User.new as there are no user_params getting posted back from the view.
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user)
else
render :action => 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
Basically while instantiating an object, you don't need to call user_params .
I've followed Hartl's rails tutorials to the of Chap 9 and now i'm building my own idea so i can get deep into rails.
Current issue - I've added a Bio filed to user profiles in edit and the text box appear on the Users profile but i can't figure out how to save the text added to the Bio text box. I've generated a migration called "add_bio_to_user_profile" (haven't raked yet) but i'm struggling to figure out what to add to the User.rb model. Does controller come into this to?
Migration "add_bio_to_user_profile"
class AddBioToUserProfile < ActiveRecord::Migration
def change
add_column :user_profiles, :, :string
end
end
Model/User.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
#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 }
has_secure_password
validates :password, length: { minimum: 6 }
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
end
controllers/user_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
end
def new
#user = User.find(params[:id])
end
def new
#user = User.new
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
redirect_to users_url
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
show.html.erb
<% provide(:title, #user.name) %>
<div class="row">
<aside class="span4">
<section>
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
</section>
<div>
<textarea rows="3"></textarea>
</div>
</aside>
</div>
edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :bio %>
<textarea rows="3"></textarea>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirm Password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Save changes", class: "btn btn-large btn-primary" %>
<% end %>
<%= gravatar_for #user %>
change
</div>
</div>
Formally, you dont have to do anything. Rails magic starts here. You run the migration, restart server if you are running production env, change the name of textbox to :bio in edit html.erb
<%=f.text_field :bio%>
When this form is submitted, it automatically gets saved in the table. Additionally, you can add validation on this field in model.
In edit.html.erb, make this change
<%= f.label :bio %>
<%=f.text_field :bio%>
and in show.html.erb
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
Bio: <%= #user.bio %>
One change in controller.rb
def user_params
params.require(:user).permit(:name, :email, :bio, :password,
:password_confirmation)
end
You want to add bio field to users table I guess.
Here is the migration.
class AddBioToUserProfile < ActiveRecord::Migration
def change
add_column :users, :bio, :string
end
end
While you are going for a migration, only model comes to play. and in this,
users is your table name and bio is the new column and string is the data-type.
I am trying to have a nested form on my users/new page, where it accepts user-attributes and also company-attributes. When you submit the form:
Here's what my error message reads:
ActiveModel::MassAssignmentSecurity::Error in UsersController#create
Can't mass-assign protected attributes: companies
app/controllers/users_controller.rb:12:in `create'
Here's the code for my form:
<%= form_for #user do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.fields_for :companies do |c| %>
<%= c.label :name, "Company Name"%>
<%= c.text_field :name %>
<% end %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
<br>
<% if current_page?(signup_path) %>
<%= f.submit "Sign Up", class: "btn btn-large btn-primary" %> Or, <%= link_to "Login", login_path %>
<% else %>
<%= f.submit "Update User", class: "btn btn-large btn-primary" %>
<% end %>
<% end %>
Users Controller:
class UsersController < ApplicationController
def index
#user = User.all
end
def new
#user = User.new
end
def create
#user = User.create(params[:user])
if #user.save
session[:user_id] = #user.id #once user account has been created, a session is not automatically created. This fixes that by setting their session id. This could be put into Controller action to clean up duplication.
flash[:success] = "Your account has been created!"
redirect_to tasks_path
else
render 'new'
end
end
def show
#user = User.find(params[:id])
#tasks = #user.tasks
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = #user.name.possessive + " profile has been updated"
redirect_to #user
else
render 'edit'
end
#if #task.update_attributes params[:task]
#redirect_to users_path
#flash[:success] = "User was successfully updated."
#end
end
def destroy
#user = User.find(params[:id])
unless current_user == #user
#user.destroy
flash[:success] = "The User has been deleted."
end
redirect_to users_path
flash[:error] = "Error. You can't delete yourself!"
end
end
Company Controller
class CompaniesController < ApplicationController
def index
#companies = Company.all
end
def new
#company = Company.new
end
def edit
#company = Company.find(params[:id])
end
def create
#company = Company.create(params[:company])
#if #company.save
#session[:user_id] = #user.id #once user account has been created, a session is not automatically created. This fixes that by setting their session id. This could be put into Controller action to clean up duplication.
#flash[:success] = "Your account has been created!"
#redirect_to tasks_path
#else
#render 'new'
#end
end
def show
#comnpany = Company.find(params[:id])
end
end
User model
class User < ActiveRecord::Base
has_secure_password
attr_accessible :name, :email, :password, :password_confirmation
has_many :tasks, dependent: :destroy
belongs_to :company
accepts_nested_attributes_for :company
validates :name, presence: true, length: { maximum: 50 }
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 }
#below not needed anymore, due to has_secure_password
#validates :password_confirmation, presence: true
end
Company Model
class Company < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :users
end
First for debugging put the bang on the create in the two controllers like so: create! Your log may spit out more.
Then, if that sucked, try it the old fashioned way of Building the two Objects and assigning each one with the params.
I assume also that this is it for attributes, no after saves that should have more to the schema then you are showing.
Lastly, you are missing
def new
#user = User.new
#company = #user.companies.build
end
Print out the params too, just in case it says something wonky but adding this line: as #Beerlington said should work too :company_attributes, maybe companies_attributes... spit balling here.
Add attr_accessible :companies to your user model attr list