I need a bit of help. I am trying to:
Send am email with a link.
Have the link set the user.email_activation_token = true
Then redirect to the home page.
Currently I have a link that goes to the email but it gives me this error.
(I'm using HAML btw.)
Edit: There is no view currently.
Unknown action
The action 'show' could not be found for UsersController
user_mailer/registration_confirmation
Confirm your email address please!
= accept_invitation_users_url({:token=>#user.email_activation_token})
users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
UserMailer.registration_confirmation(#user).deliver
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
def accept_invitation
#user = User.find_by_email_activation_token!(params[:token])
#user.email_activation_token = true
redirect_to root_url, :notice => "Email has been verified."
end
end
end
email_activations_controller.rb
class EmailActivationsController < ApplicationController
def edit
#user = User.find_by_email_activation_token!(params[:id])
#user.email_activation_token = true
save!
redirect_to root_url, :notice => "Email has been verified."
end
def new
#user = User.find_by_email_activation_token!(params[:id])
end
def edit
end
end
user.rb (user model)
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
before_save { |user| user.email = email.downcase }
before_create { generate_token(:auth_token) }
# before_create { generate_token(:email_activation_token) }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
VALID_PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*[0-9]).{6,}$/
validates_confirmation_of :password
validates :password, :on => :create, presence: true, format: { with: VALID_PASSWORD_REGEX }
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
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 encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
end
routes.rb
LootApp::Application.routes.draw do
get "password_resets/new"
get "sessions/new"
resources :users
resources :sessions
resources :password_resets
resources :email_activations
resources :users do
collection do
get :accept_invitation
end
end
# get "users/new"
get "static_pages/home"
get "static_pages/help"
root to: 'static_pages#home'
match "sign_up", to: "users#new"
match '/help', to: 'static_pages#help'
match '/log_in', to: 'sessions#new'
match '/log_out', to: 'sessions#destroy'
end
Why are you passing, activation token instead of id in the accept_invitation_users_url? When you have this code:
resources :users do
member do
get :accept_invitation
end
end
in the routes, we are assuming that, accept_invitation is a Restful route and will give id in its url. See http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
So, I think you should be doing something like this:
routes.rb
# resources :users ### Remove this as it is reduntant
resources :users do
member do
get :accept_invitation
end
end
user_mailer/registration_confirmation
Confirm your email address please!
= accept_invitation_users_url(#user)
users_controller.rb
def accept_invitation
#user = User.find(params[:id])
#user.email_activation_token = true
redirect_to root_url, :notice => "Email has been verified."
end
After reading comments
Yes, as #Frederick Cheung said in the comments, passing id instead of token would defeat the point of sending the email confrmation and would make it easy for people to confirm an email address without actually receiving the email.
So, please refer to #PriteshJ's answer. Apparently, you just added an extra line resources :users, in routes.rb and I made a fuss out of it :). Just remove the line and you will be ok.
Try by changing the routes like this ..
resources :users do
collection do
get :accept_invitation
end
end
resources :users
resources :sessions
resources :password_resets
resources :email_activations
You have called resources :users twice.
Remove the first call
there are two routes for users in routes.rb
remove resources :users
the first one resources users overrides the next users resource
so the application does not know there is a collection route for accept_invitation and
treats it as a id for show actions as the url genreated for the action will be
/users/accept_invitation
which matches the signature of show /users/:id
removing the first resources :users should do the trick
also I don't see any use of email_activations_controller.rb
may be you can safely remove it
Related
I am using devise and devise_ldap for my rails authentication. I am trying to use the built in helper, current user to display the users email on the welcome page of my application.
This is the code that I have tried to use to:
<% if user_signed_in? %>
<div>Signed in as... <%= current_user.email %></div>
<% end %>
when I sign in to the application, I get the error;
undefined method `email' for nil:NilClass
Here is my routes.rb
Rails.application.routes.draw do
devise_for :users
resources :users
resources :systems do
member do
get :targets, :sources
end
root 'systems#index'
end
and my users controller:
class UsersController < ApplicationController
authorize_resource
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
def index
#users = User.all.order("display_name asc")
end
# GET /users/1
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /stories/1/edit
def edit
respond_to do |format|
format.html
format.js
end
end
# POST /stories
def create
#user = User.new(user_params)
respond_to do |format|
puts 'user controller'
if #user.save!
format.html { redirect_to user_path(#user), notice: 'User was successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /stories/1
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_path(#user), notice: 'User was successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /stories/1
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_path notice: 'User was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:display_name, :email, :username)
end
end
my users model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
before_create :rememberable_value
before_save :get_ldap_values
devise :ldap_authenticatable, :rememberable, :trackable, :validatable
def get_ldap_values
if self.username
self.email = Devise::LDAP::Adapter.get_ldap_param(self.username,"mail").first if Devise::LDAP::Adapter.get_ldap_param(self.username,"mail")
self.display_name = Devise::LDAP::Adapter.get_ldap_param(self.username,"displayName").first if Devise::LDAP::Adapter.get_ldap_param(self.username,"displayName")
end
end
# def role?(role)
# return !!self.roles.find_by_name(role.to_s.camelize)
# end
def email_required?
false
end
def email_changed?
false
end
def rememberable_value
self.remember_token ||= Devise.friendly_token
end
def name_to_display
if self.display_name
self.display_name
else
self.username
end
end
def password_required?
false
end
def password_match?
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
end
I am not sure what I am missing to be able to access the current users information after a successful sign in.
Update
Here is the new routes file:
Rails.application.routes.draw do
devise_scope :user do
get '/users/sign_out' => 'devise/sessions#destroy'
get "/users/sign_in" => "devise/sessions#new"
# delete "/logout" => "devise/sessions#destroy"
end
devise_for :users
authenticate(:user) do
resources :users
resources :reports
resources :change_logs, only: [:index, :show]
resources :systems do
member do
get :targets, :sources
end
resources :change_logs, module: :systems
resources :components do
resources :change_logs, module: :components
end
resources :c_relations
end
resources :integrations
get '/static_pages/home' # => 'static_pages#home', as: 'home'
root 'systems#index'
end
In routes.rb you should enclose the rows following
devise_for :users
in a block
authenticate(:user) do
resources :users
[...]
end
The problem was that I had overridden the devise mapping and current_user. I removed them and was able to access current user in my views.
Do you have before_action :authenticate_user! in your controller chain at all?
Current_user could be nil after signin is if you aren't asking devise to authenticate the action.
User signed up
User received confirmation email
User click on user_confirmation_url(#resource, :confirmation_token => #token)
User goes to /users/confirmation.39?confirmation_token=V-UwSF5qzCt8mBVAFuwK
It get this error
If i manually change the url to: users/confirmation/39?confirmation_token=V-UwSF5qzCt8mBVAFuwK
I get this error: The action 'confirmation' could not be found for UsersController
Routes.rb
App::Application.routes.draw do
get "pages/quickstart"
get "pages/configuration"
get "pages/invoices"
get "/reports" => "reports#index"
get "/reports/historical_data" => "reports#historical_data", as: :historical_data
#get "statements/document/month/:date" => "statements#month", :document => true, as: :monthly_statements_document
#get "statements/month/:date/" => "statements#month", as: :monthly_statements
resources :reminders
resources :reminder_users
resources :forecasts
resources :statements
resources :monthly_statements
resources :fuel_cards
resources :accounts_payables
resources :accounts_receivables
resources :customers
resources :invoices
resources :suppliers
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
devise_for :users, :controllers => { :registrations => "registrations"}
# You can have the root of your site routed with "root"
root 'pages#quickstart', as: :home
get "/home" => "home#welcome"
get "/registrations", to: redirect('/users/edit')
get "/user", to: redirect('/users/edit')
get "/user/change_password", as: :change_password
match ':controller(/:action(/:id))(.:format)', via: [:get, :post]
end
(I use this to allow user to edit their profile)
user_confirmation POST /users/confirmation(.:format) devise/confirmations#create
new_user_confirmation GET /users/confirmation/new(.:format) devise/confirmations#new
GET /users/confirmation(.:format) devise/confirmations#show
Model
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
UserController
class UsersController < ApplicationController
before_filter :authenticate_user!
def edit_password
#user = current_user
end
def update_password
#user = User.find(current_user.id)
if #user.update(user_params)
# Sign in the user by passing validation in case his password changed
sign_in #user, :bypass => true
redirect_to root_path
else
render "edit"
end
end
private
def user_params
# NOTE: Using `strong_parameters` gem
params.required(:user).permit(:password, :password_confirmation)
end
end
RegistrationController
class RegistrationsController < Devise::RegistrationsController
def update
#user = User.find(current_user.id)
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
else
# remove the virtual current_password attribute
# update_without_password doesn't know how to ignore it
params[:user].delete(:current_password)
#user.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
#redirect_to after_update_path_for(#user)
redirect_to edit_user_registration_path
else
render "edit"
end
end
private
# check if we need password to update user data
# ie if password or email was changed
# extend this as needed
def needs_password?(user, params)
user.email != params[:user][:email] ||
params[:user][:password].present?
end
end
devise (3.4.1)
The issue here is with the view generators.
Do you see, in your path, you are getting users/confirmation.39 for your confirmation route...that 39 is being interpreted as a format by your controller. Its not expecting a user id (#resource) if you have a look at rake routes
the answer here is to remove #resource from your url_helper in the email/view, and if needed, pass ID in as a parameter with the token
user_confirmation_url(confirmation_token: #token, id: #resource)
I'm trying to get a Rails mailer working but I get the following error message when I try to sign up a new user and send the activation email. I'm following chapter 10 of Hartl's latest (3rd ed.) railstutorial so in theory it should be working:
No route matches {:action=>"edit", :controller=>"account_activations", :email=>"joe#bloggmail.com", :format=>nil, :id=>nil} missing required keys: [:id]
app/views/user_mailer/account_activation.html.erb:9:in `_app_views_user_mailer_account_activation_html_erb__1296124105699284853_2236858900'
app/mailers/user_mailer.rb:6:in `account_activation'
app/models/user.rb:82:in `send_activation_email'
app/controllers/users_controller.rb:27:in `create'
The relevant bits of code are:
User model:
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token
extend FriendlyId
friendly_id :callsign, :use => :slugged
has_secure_password
before_save do
self.email.downcase!
self.callsign.downcase!
end
before_create do
:create_activation_digest
end
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(?:\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
VALID_CALLSIGN_REGEX = /\A[a-z\d\-.\_]+\z/i
validates :callsign, presence: true,
length: { maximum: 20 },
format: { with: VALID_CALLSIGN_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }
validates :slug, presence: true
# Returns the hash digest of the given string.
def self.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def self.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
private
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
Users controller:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :show, :edit, :update, :destroy]
before_action :non_logged_in_user, only: [:new, :create]
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.friendly.find(params[:id])
#page_name = "user_page"
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end # create
def destroy
#user = User.find(params[:id])
if ( current_user != #user )
#user.destroy
flash[:success] = "User deleted."
redirect_to users_url
else
redirect_to #user, notice: "Suicide is not permitted, admin chappie. Hard cheese."
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end # update
private
def user_params
params.require(:user).permit(:name, :email, :callsign, :password, :password_confirmation)
end
# Before filters
def non_logged_in_user
if logged_in?
redirect_to root_url, notice: "Nice try pal. You can't create a new user
if you're already signed 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
account_activation.html.erb:
<h1>MySite</h1>
<p>Hi <%= #user.name %>,</p>
<p>
Welcome! Click on the link below to activate your account:
</p>
<%= link_to "Activate", edit_account_activation_url(#user.activation_token,
email: #user.email) %>
user_mailer.rb:
class UserMailer < ActionMailer::Base
default from: "noreply#mysite.com"
def account_activation(user)
#user = user
mail to: user.email, subject: "Account activation"
end
def password_reset
#greeting = "Hi"
mail to: "to#example.org"
end
end
routes.rb:
Mysite::Application.routes.draw do
resources :users
resources :account_activations, only: [:edit]
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
end
EDIT:
account_activations_controller.rb:
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end
I'd suggest using this in your routes file instead of resources:
get 'account_activation', "account_activations#edit", :as => :edit_account_activation
...and deal with the secret token and email address params there.
Right now the problem is that you don't have a controller action to match the route you're using in the account_activation.html.erb
Found the error.
The problem was this bit of code in class User:
before_create do
:create_activation_digest
end
It should be:
before_create do
create_activation_digest
end
Or:
before_create :create_activation_digest
Schoolboy error. I apologise for wasting everyone's time.
(Therefore the code:
resources :account_activations, only: [:edit]
in routes.rb does indeed create the correct route)
I've completed the Michael Hartl Ruby on Rails Tutorial (for Rails 3) and I am wondering how you would go about displaying all the users that have been assigned the Admin attribute but on a separate page as it doesn't mention this anywhere.
users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate, :only => [:index, :edit, :update, :destroy]
before_filter :correct_user, :only => [:edit, :update]
before_filter :admin_user, :only => :destroy
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(:page => params[:page])
#title = #user.name
end
def new
#user = User.new
#title = "Sign up"
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to University Sports!"
redirect_to #user
else
#title = "Sign up"
render 'new'
end
end
def edit
#title = "Edit user"
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
def index
#users = User.paginate(:page => params[:page])
end
def admins
#users = User.admins
render "users/index"
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
redirect_to users_path
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.following.paginate(:page => params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(:page => params[:page])
render 'show_follow'
end
private
def authenticate
deny_access unless signed_in?
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
Routes.rb
FinalProject::Application.routes.draw do
get "club/new"
resources :users do
member do
get :following, :followers
end
end
resources :users do
collection do
get :admins
end
end
resources :sessions, :only => [:new, :create, :destroy]
resources :microposts, :only => [:create, :destroy]
resources :relationships, :only => [:create, :destroy]
get "sessions/new"
match '/signup', :to => 'users#new'
match '/signin', :to => 'sessions#new'
match '/signout', :to => 'sessions#destroy'
match '/sign_up', :to => 'pages#sign_up'
root :to => 'pages#home'
resources :users
match '/signup', :to => 'users#new'
end
user.rb
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
has_many :microposts, :dependent => :destroy
has_many :relationships, :foreign_key => "follower_id", :dependent => :destroy
has_many :following, :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
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 }
scope :admins, where(:admin => true)
# Automatically create the virtual attribute 'password_confirmation'.
validates :password, :presence => true, :confirmation => true, :length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
def following?(followed)
relationships.find_by_followed_id(followed)
end
def follow!(followed)
relationships.create!(:followed_id => followed.id)
end
def unfollow!(followed)
relationships.find_by_followed_id(followed).destroy
end
def feed
Micropost.from_users_followed_by(self)
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
First, you would need to create a route in your config/routes.rb file that would route to an action that would display this information. Something like this:
resources :users do
collection do
get :admins
end
end
That would route to the admins action inside the UsersController, and so that's what you'll need to define next. It would go like this:
def admins
#users = User.admins
render "users/index"
end
Because the list of admins shouldn't be too different from the users list, you can just assign all the admins to #users and then render the users/index template... if it exists. I'm probably assuming too much here, but it's one way of doing it.
Now, there's no admins method on your User class and so you'll need to define that. One way to do that is with a scope, like this:
scope :admins, where(:admin => true)
This will define the admins method on the User class, returning a scope of all users who are admins. Scopes are cool, you should look into what else they can do.
Alternatively, you can define a class method:
def self.admins
where(:admin => true)
end
You should do the following:
UsersController
def admins
#admins=User.where(:admin => true)
end
And add this to your routes file:
resources :users do
collection do
get :admins
end
end
And then just render the #admins instance variable in the admins.html.erb view (which youll have you create in the User views).
I'm modifying a micro post ruby on rails tutorial app and am running into the following problem that has me stumped:
A user has a permalink url, http://localhost:3000/users/exampleuser. Visitors can come to this url and answer a survey poll. The following code works if exampleuser (current_user) is logged in to their own account. However if you come as a Visitor, who is not required to log in, I receive what looks like a Null User ID error. All my attempts at trying to assign the correct user id have been unsuccessful even though it looks to me that the user id is not null anymore.
Here is the error I receive:
Started POST "/polls" for 127.0.0.1 at 2012-02-24 20:28:56 -0500
Processing by PollsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZE/KXWCnBfE8PAn1CyAM51rnQI6z2Ut1UvHavEqSkZY=", "poll"=>{"overall_grade"=>"strong", "relevance"=>"strong", "personalization"=>"strong", "design"=>"strong", "value_proposition"=>"strong", "responder_name"=>"test", "responder_email"=>"test#test.com", "comments"=>"test"}, "commit"=>"Submit Grade"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
Completed 500 Internal Server Error in 47ms
RuntimeError (Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id):
app/controllers/polls_controller.rb:5:in `create'
Using the console I can see that the first user (exampleuser) is ok.
ruby-1.9.2-p290 :001 > User.find(1)
User Load (13.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1
[["id", 1]] => #<User id: 1, name: "Example User", email: "example#railstutorial.org",
created_at: "2012-02-23 17:27:45", updated_at: "2012-02-23 17:27:45",
encrypted_password: "418b54481fffe05051621c500d69e44fd25573145c0b12e1860...", salt:
"57d9f6da0f6554e92c4180a469d5a1807c4a9dd46ce47c30b45...", admin: true, username:
"exampleuser", permalink: "exampleuser">
But this logic doesn't work in my Polls controller for some reason. Specifically I believe that the following lines in Polls Controller are the issue:
user_to_grade = User.find_by_id(#user.id)
#poll = user_to_grade.polls.build(params[:poll])
Any insights would be most appreciated.
John
Polls Controller
class PollsController < ApplicationController
def create
if current_user.blank?
user_to_grade = User.find_by_id(#user.id)
#poll = user_to_grade.polls.build(params[:poll])
else
#poll = current_user.polls.build(params[:poll])
end
if #poll.save
flash[:success] = "Pitch graded successfully!"
redirect_to root_path
else
render 'pages/home'
end
end
end
Polls Model
class Poll < ActiveRecord::Base
attr_accessible :overall_grade, :personalization, :relevance, :value_proposition, :design, :other, :responder_name, :responder_email, :comments, :next_steps
belongs_to :user
validates :user_id, :presence => true
validates :overall_grade, :presence => true
default_scope :order => 'polls.created_at DESC'
end
Users Model
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :username, :email, :password, :password_confirmation
has_many :polls, :dependent => :destroy
username_regex = /\A[\w\-]+\z/i
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :username, :presence => true,
:length => { :maximum => 50 },
:format => { :with => username_regex },
:uniqueness => { :case_sensitive => false }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password, :create_permalink
#Returns true if the user's password matches the submitted password
def has_password?(submitted_password)
#Compare stored to submitted encrypted versions
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
#handles 2 scenarios: invalid email and a successful email, password mismatch implicitly since returns nil at end of method
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
def to_param
permalink
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
def create_permalink
self.permalink = username.downcase
end
end
Users Controller
class UsersController < ApplicationController
before_filter :authenticate, :only => [:index, :edit, :update, :destroy]
before_filter :correct_user, :only => [:edit, :update]
before_filter :admin_user, :only => [:index, :destroy]
def show
#user = User.find_by_permalink(params[:id])
#polls = #user.polls.paginate(:page => params[:page])
#title = #user.name
#poll = Poll.new
end
def new
#user = User.new
#title = "Sign up"
end
def create
#user = User.new(params[:user])
if #user.save
#Handle a successful save.
sign_in #user
flash[:success] = "Signup Success welcome to Grademypitch!"
redirect_to #user
else
#title = "Sign up"
#user.password = ""
render 'new'
end
end
def edit
#title = "Edit user"
end
def update
#user = User.find_by_permalink(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
def index
#title = "All users"
#users = User.paginate(:page => params[:page])
end
def destroy
User.find_by_permalink(params[:id]).destroy
flash[:success] = "User destroyed."
redirect_to users_path
end
private
def authenticate
deny_access unless signed_in?
end
def correct_user
#user = User.find_by_permalink(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
Pages Controller
class PagesController < ApplicationController
def home
#title = "Home"
#poll = Poll.new
end
def contact
#title = "Contact"
end
def about
#title = "About"
end
def help
#title = "Help"
end
end
Routes
SampleApp::Application.routes.draw do
# removed when added session new/create/destroy.... get "sessions/new"
#get "users/new" , can remove now that resources :users added cause it automatically adds all routes for users!
resources :users
resources :sessions, :only => [:new, :create, :destroy]
resources :polls, :only => [:new, :create]
match '/signup', :to => 'users#new'
match '/signin', :to => 'sessions#new'
match '/signout', :to => 'sessions#destroy'
root :to => 'pages#home'
match '/contact', :to => 'pages#contact'
match '/about', :to => 'pages#about'
match '/help', :to => 'pages#help'
end
Yes, line 5 of your PollsController is the problem:
user_to_grade = User.find_by_id(#user.id)
And it doesn't look like you've defined #user anywhere in your PollsController.
Here is the relevant part:
if current_user.blank?
user_to_grade = User.find_by_id(#user.id)
#poll = user_to_grade.polls.build(params[:poll])
else
#poll = current_user.polls.build(params[:poll])
end
When the user is logged in, you're building a poll on the current user. When the user isn't logged in, you're first trying to find a user with the same id as #user (nil) which fails, and then trying to build a poll on that user.
What exactly is your desired behavior for users that aren't logged in? Should they be able to hit the create action at all?
So I figured this out.
Step 1 - I installed ruby-debug which helped me determine that the #user.id was nil when it reached the polls controller. So between the users controller and the polls controller the #user.id was being forgotten.
Step 2 - I researched that the session function as a possible solution so I modified the following areas to:
Users Controller
def show
#user = User.find_by_permalink(params[:id])
#polls = #user.polls.paginate(:page => params[:page])
#title = #user.name
#poll = Poll.new
if current_user.blank?
session[:remember_token] = #user.id
end
end
Here I added a session token to remember the user id from the user page with the permalink - i.e. users/exampleuser. So when I now goto the Polls controller it can call upon the session token and find out what user id we were coming from with the following code...
Polls Controller
def create
if current_user.blank?
user_to_grade = User.find_by_id(session[:remember_token])
#poll = user_to_grade.polls.build(params[:poll])
else
#poll = current_user.polls.build(params[:poll])
end
.
.
.
The key line is the User.find_by_id(session[:remember_token]). This now uses the user id from the user controller and is not nil anymore which solves the issue of how to remember the user id from controller to controller!