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 #.
Related
I want to use a subdomain to redirect users to their subdomain
example
https://usersubdomain.mydomain.com
My setup is as fellow,
Each user has an account which is created when a new user is registered
class User < ApplicationRecord
.........
has_one :account
after_initialize :set_account
def set_account
build_account unless account.present?
end
......
end
The account has subdomain attributes which I intend to use as the Url for the user
create_table "accounts", force: :cascade do |t|
t.string "subdomain"
t.bigint "user_id"
..........
On user creation, I used nested attributes to create the subdomain for the account. I am using devise
User Registration.
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.fields_for :account do |a| %>
<div class="field">
<%= a.label :subdomain %><br />
<%= a.text_field :subdomain%>
..........
Resources are scoped through account
class ApplicationController < ActionController::Base
before_action :set_account
def require_account!
redirect_to home_index_url(subdomain: nil) if #account.nil?
end
def set_account
#account = Account.find_by(subdomain: request.subdomain)
end
routes.rb
Rails.application.routes.draw do
devise_for :users
resources :posts
root to: 'posts#index'
get 'home/index'
end
With this setup. I need to manually enter the subdomain to get the page of the user. If not I am always redirected to the home index page as intended.
I use lvh.me on the localhost. So must enter subdomain.lvh.me:3000 on the address bar to get the user page. So how can the subdomain be added automatically when users sign in Thanks
All The controllers are scoped with
#resource = #accout.resoures
I am trying to build a multitenant app
I get the impression that since you know how to get the subdomain from the request and from the database what you are having problem is building the URL to where you want to send the user from the on right? if that is the case you might be looking for url_for handling the this in detail https://api.rubyonrails.org/v5.2.2/classes/ActionDispatch/Routing/UrlFor.html
for example
Rails.application.routes.url_helpers.url_for(controller: "admin/users", action: 'show', id: 1, host: 'somehost.org', port: '8080', subdomain: "yadah")
=> "http://yadah.somehost.org:8080/admin/users/1"
but also you can do users_url(subdomain: user.subdomain).
Dont forget you can use regular expression in you constraints too
constraints: {subdomain: /.+/}
or a lambda
constraints: lambda { |r| r.subdomain.present? && r.subdomain != 'www' }
user this with devise's after sign in path
class ApplicationController < ActionController::Base
protected
def after_sign_in_path_for(resource)
request.env['omniauth.origin'] || stored_location_for(resource) || root_path
end
end
https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in
Is it possible limit the number of emials sent for devise recoverable in a period of time for each user? How can I defend against malicious requests?
I would use grecaptcha to protect your form where you let the user rescue his account.
It's really easy and simple to use and include it in your rails app.
In your view:
<%= form_for #user do |f| %>
<%= recaptcha_tags %>
<% end %>
In your controller, create action:
def create
verify_recaptcha(model: #user) # => returns true or false
end
To limit: "emials sent for devise recoverable"
Example Gemfile:
gem 'simple_captcha2'
routes:
devise_for :users, :controllers => { ..., :passwords => 'passwords', ... }
app/controllers/passwords_controller.rb:
class PasswordsController < Devise::PasswordsController
prepend_before_action :require_no_authentication
#
# GET /resource/password/new
def create
if simple_captcha_valid?
super
else
....
end
end
end
app/views/devise/passwords/new.html.erb
into the form_for:
<%= show_simple_captcha %>
Having tried to access the 'answer1' attribute on my #results object via:
#results.answer1
...I looked online, and tried the solutions suggested here...
get attribute of ActiveRecord object by string
..but I can't seem to access the attributes of my passed ActiveRecord #results object.
Initially the user is directed to the /quizzes/new view, the QuizzesController#new action which looks like this:
def new
#user = current_user
#quiz_answer = current_user.quiz_answers.build
end
#quiz_answer is, therefore accessible to the view and passed into the form_for there. EDIT: Here is my form (a partial rendered as part of quizzes/new.html.erb):
<%= form_for(#quiz_answer) do |f| %>
<p>
<%= f.check_box(:answer1) %>
<%= f.check_box(:answer2) %>
<%= f.check_box(:answer3) %>
<%= f.check_box(:answer4) %>
<%= f.check_box(:answer5) %>
<%= f.check_box(:answer6) %>
<%= f.check_box(:answer7) %>
<%= f.check_box(:answer8) %>
</p>
<p>
<%= f.submit("Get my results!") %>
</p>
<% end %>
When the user clicks submit on the form the QuizAnswers#create action is triggered which redirects to results_path (the index action in the ResultsController).
#results is therefore accessible to the view because of the index action in ResultsController:
def index
# in order to access all the results in our view...
#results = current_user.quiz_answers
end
In my results/index view, this
<p><%= #results %></p>
outputs the following to the page:
#<QuizAnswer::ActiveRecord_Associations_CollectionProxy:0x5191b30>
...so the object is not nil.
But when I try to access the 'answer1' attribute of #results, via:
<p><%= #results[answer1] %></p>
OR
<p><%= #results.read_attribute(answer1) %></p>
...I get the following error:
undefined local variable or method `answer1' for #<#<Class:0x72384d8>:0x71b6d10>
Finally, in my routes.rb, I define the following reources:
resources :quizzes
resources :results
resources :quiz_answers
resources :users do
resources :posts
end
But when I include 'resources :quiz_answers' as part of 'resources :user' (immediately below the 'resources :posts' line) I get the following error:
undefined method `quiz_answers_path' for #<#<Class:0x5310618>:0x5411b80>
...when I go to the quizzes/new page.
So my question is: If quiz_answers needs to be a resource included as part of the user resource, how do I pass current_user.quiz_answers to form_for? And if it DOESN'T need to be 'part of' the user resource, how do I access the attributes of quiz_answers?
Once again, if there's anything I'm presuming or doing wrong, please feel free to explain the 'Rails way' of doing it.
EDIT
I think I've been asked for the models, controllers and migration, so here you go:
users controller:
class UsersController < ApplicationController
def show
#user = current_user
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
#user.update_attributes(user_params)
if #user.save
redirect_to(#user)
else
render 'edit'
end
end
private
# Using a private method to encapsulate the permissible parameters is just a good pattern
# since you'll be able to reuse the same permit list between create and update. Also, you
# can specialize this method with per-user checking of permissible attributes.
def user_params
params.require(:user).permit(:name, :age, :email, :section)
end
end
quiz answers controller:
class QuizAnswersController < ApplicationController
def new
#user = current_user
#quiz_answer = current_user.quiz_answers.build
end
def create
redirect_to results_path
end
private
def post_params
params.require(:quiz_answer).permit(:body, :user_id)
end
end
results controller:
class ResultsController < ApplicationController
def index
# in order to access all the results in our view...
#results = current_user.quiz_answers
end
end
schema.rb (let me know if this is what you need, migration-wise):
ActiveRecord::Schema.define(version: 20141002130233) do
create_table "posts", force: true do |t|
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
add_index "posts", ["user_id"], name: "index_posts_on_user_id"
create_table "quiz_answers", force: true do |t|
t.integer "user_id"
t.string "answer1"
t.string "answer2"
t.string "answer3"
t.string "answer4"
t.string "answer5"
t.string "answer6"
t.string "answer7"
t.string "answer8"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "quiz_answers", ["user_id"], name: "index_quiz_answers_on_user_id"
# Could not dump table "users" because of following NoMethodError
# undefined method `[]' for nil:NilClass
end
And the QuizAnswer model:
class QuizAnswer < ActiveRecord::Base
belongs_to :user
end
The User model is pretty long, but it DOES include:
has_many :posts
has_many :quiz_answers
I hope that helps!
All of the comments are perfectly valid i.e. you are setting an CollectionProxy to the variable #results but your main issue is that the create action in your QuizAnswersController does nothing except redirect to results_path.
Although Rails will do a lot of work for you, you have to process the parameters which are submitted by your form in the create action.
There's quite a lot in issue with your code here so I'd suggest you read this part of the Rails Guide on Action Controller. You'll also need to adjust the post_params method in your QuizAnswersController as you only permit the body and user_id attributes to be mass-assigned meaning you won't be able to do anything with the answer1 etc. attributes unless you assign them manually.
Having said that, what do they do in the form? As far as I can see, they will just be checkboxes setting a true or false value?
Your QuizAnswersController needs to look something like this.
class QuizAnswersController < ApplicationController
...
def create
#quiz_answer = current_user.quiz_answers.new(post_params) #from private method
if #quiz_answer.save
redirect_to results_path
else
#error handle here
end
end
private
def post_params
params.require(:quiz_answer).permit(:answer1, :answer2) #add other attributes here
end
end
EDIT: You say you are permitting a body attribute on the QuizAnswer model but that's not one of its attributes according to your DB schema so I've updated the code.
Since #results is a relation that includes all quiz_answers that belong to the current user:
#results = current_user.quiz_answers
your results/index.erb view will contain something like:
<%- #results.each do |quiz_answer| %>
<%# ... some code shows answer data like %>
<h4>Quiz Answer</h4>
<p><%= quiz_answer.answer1 %></p>
<p><%= quiz_answer.answer2 %></p>
<p><%= quiz_answer.answer3 %></p>
<p><%= quiz_answer.answer4 %></p>
<p><%= quiz_answer.answer5 %></p>
<p><%= quiz_answer.answer6 %></p>
<p><%= quiz_answer.answer7 %></p>
<p><%= quiz_answer.answer8 %></p>
<%- end %>
If no quiz_answers are assigned to the current user, that form will be skipped in output. And probably you should add name field to quiz_answer table then you can add it to above form as:
<p>Name: <%= quiz_answer.name %></p>
To assign the newly created quiz_answer add to create action inside the QuizAnswersController assignment to the current user, like follows:
class QuizAnswersController < ApplicationController
def create
quiz_answer = QuizAnswer.create(params.require(:quiz_answer).permit(:answer1)
current_user.quiz_answers << quiz_answer
redirect_to results_path
# require 'pry'
# binding.pry
end
end
After the quiz_answer.save! please make sure that the current_user.quiz_answers isn't empty by checking its #size. You can debug your it by using pry gem. Just add it to Gemfile, then insert to required place as a breakpoint, and uncomment the two lines with require, and binding.
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.
I'm trying to get the controller's "destroy" to work correctly and I'm wondering what the correct set up should be.
The error that I'm getting is
ActiveRecord::RecordNotFound in AuthenticationsController#destroy
Couldn't find Authentication without an ID
My controller looks like
class AuthenticationsController < InheritedResources::Base
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
redirect_to(:back)
end
database table
create_table "authentications", :force => true do |t|
t.integer "user_id"
t.string "provider"
t.string "uid"
t.string "secret"
t.string "token"
end
I have tried other parameters such as :user_id
How can I get users to destroy their tokens? (with the option to re-authenticate later)
You're not passing id to controller
try
<%= link_to "Disconnect Your Authentication", {:controller=>'authentications', :action=>'destroy', :id=>current_user.authentication_id} %>
or use path helper with #autentication argument as option.
(You will need to edit your routes file)
If you're wanting to destroy all authentications for a user, you could certainly change your controller's destroy method to be:
def destroy
current_user.authentications.destroy_all
end
A more conventional approach would be to destroy a particular authentication. In that case the link_to method needs a path that includes an id parameter (which will end up as your params[:id] value in the controller). You can imagine a view snippet like the following that displays all a user's authentications, each with a destroy link:
<ul>
<% current_user.authentications.each do |a| %>
<li>
<%= a.provider %>
-
<%= link_to 'Disconnect Your Authentication', authentication_path(a), :method => :delete %>
</li>
<% end %>
</ul>
This assumes current_user is a helper and that your routes are set up on your authentication model. The authentication_path helper uses the a authentication instance to generate a path, complete with an id parameter.