I want to create a Current_User method but I don't want to use a gem or similar. How would I do that in Rails 4.1.2
Questions_Controller where i want the current_user method.
class QuestionsController < ApplicationController
before_filter :auth, only: [:create, :your_questions, :edit, :update]
# def index
# #question = Question.new
# #questions = Question.unsolved(params)
# end
#questions = current_user.your_questions(params[:page])
def your_questions(page)
questions.paginate(page: page, order: 'created_at DESC', per_page: 3)
end
def self.unsolved(params)
order('created_at DESC').where(solved: false).paginate(page: params[:page],per_page: 3)
end
def create
#question = current_user.questions.build(params[:question])
if #question.save
flash[:success] = 'Your question has been posted!'
redirect_to #question
else
#questions = Question.unsolved(params)
render 'index'
end
end
def new
#question = Question.new
end
def show
# raise FOO
puts params
#question = Question.find(params[:id])
#answer = Answer.new
end
def your_questions
#questions = current_user.your_questions(params[:page])
# current_user.your_questions(params[:id])
end
def edit
#question = current_user.questions.find(params[:id])
end
def update
#question = current_user.questions.find(params[:id])
if #question.update_attributes(params[:question])
flash[:success] = 'Your question has been updated!'
redirect_to #question
else
render 'edit'
end
end
def search
#questions = Question.search(params)
end
end
My user model
class User < ActiveRecord::Base
has_many :questions
has_many :answers
# attr_accessible :username, :password, :password_confirmation
has_secure_password
# validates :username, presence: true, uniqueness: { case_sensitive: false },
# length: { in: 4..12 },
# format: { with: /A[a-z][a-z0-9]*z/, message: 'can only contain lowercase letters and numbers' }
validates :password, length: { in: 4..8 }
validates :password_confirmation, length: { in: 4..8 }
def your_questions(page)
questions.paginate(page: page, order: 'created_at DESC', per_page: 3)
end
end
My application controller
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
# protect_from_forgery
helper_method [:current_user, :logged_in?]
protected
private
def login(user)
session[:user_id] = user.id
end
def current_user
current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in?
!current_user.nil?
end
def auth
redirect_to login_url, alert: 'You must login to access that page' unless logged_in?
end
end
If there are more files you want me to add to the question please comment im a novice ruby on rails developer :)
class ApplicationController < ActionController::Base
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
I think your problem is not in the controller but in you helpers.
It is calling methods which are looking for a local variable current_user and there isn't one. You need to instantize these like this:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method [:current_user, :logged_in?]
private
def login(user)
session[:user_id] = user.id
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in?
!current_user.nil?
end
def auth
redirect_to login_url, alert: 'You must login to access that page' unless logged_in?
end
end
Like #RafalG. stated see the # in front of current_user. This will create an instance variable to track around instead of referencing a local variable that is missing.
Also note your current current_user method will always run the User.find because the local variable current_user will always be nil inside this scope, thus why you need to make it part of the instance.
UPDATE
I will leave the above for edification because you should still create an instance. I think this is the real issue
class QuestionsController < ApplicationController
before_filter :auth, only: [:create, :your_questions, :edit, :update]
# def index
# #question = Question.new
# #questions = Question.unsolved(params)
# end
#vvvv This Line is out of a scope and will raise errors vvv#
#questions = current_user.your_questions(params[:page])
def your_questions(page)
questions.paginate(page: page, order: 'created_at DESC', per_page: 3)
end
....
end
If you want to do this you would declare it in a before_filter call back because right now Rails has no idea how to handle this statement appropriately and outside of a method it will not have access to any of your helpers.
Related
I'm using the Pundit gem for the user authorizations in my Rails project. The Edit function works as I expected, just user admin and whoever created the review is able to update it. However, I can't delete them with the pundit set up.
Here's my Reviews Controller:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
def index
#reviews = Review.all
end
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
if #review.save
redirect_to review_path(#review), :alert => "Awesome! Here's your small review!"
else
error_messages(#review)
render 'new'
end
end
def show
end
def edit
authorize #review
end
def update
if #review.update(review_params)
redirect_to review_path(#review), :alert => "That's a good update!"
else
error_messages(#review)
render 'edit'
end
end
def destroy
authorize #review
#review.destroy
redirect_to reviews_path, :alert => "We'll miss that review."
end
private
def set_review
#review = Review.find_by(id: params[:id])
end
def review_params
params.require(:review).permit(:title, :content)
end
end
The Application Policy looks like this:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def create?
new?
end
def update?
false
end
def edit?
update?
end
def destroy?
destroy?
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
And here's my customized Review Policy file:
class ReviewPolicy < ApplicationPolicy
def edit?
user.admin? || record.user == user
end
def destroy?
user.admin? || record.user == user
end
end
In case you're wondering, I created the user_not_authorized helper method. My Application Controller looks like this.
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
protect_from_forgery with: :exception
helper_method :current_user
add_flash_types :info, :error, :warning
def logged_in?
!!current_user
end
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
def error_messages(obj)
obj.errors.messages.each do |k,v|
flash[:alert] = "#{k.to_s} #{v.first.to_s}"
end
end
private
def user_not_authorized
flash[:alert] = "Ooops sorry. You don't have access to this."
redirect_to (request.referrer || root_path)
end
end
Hope my message is clear enough and would appreciate any help. Remember, '''user.admin? || record.user == user''' works for editing but the user admins and the review creators can't delete the reviews when applying the same methods.
Please let me know if any question.
I'm still trying to wrap my head around Pundit policies. I think I'm close but I've wasted too much time trying to figure this out. My Posts policy works great, but trying to authorize comments, I am getting undefined errors...
comments_controller.rb
class CommentsController < ApplicationController
before_action :find_comment, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:comment))
#comment.user_id = current_user.id if current_user
authorize #comment
#comment.save
if #comment.save
redirect_to post_path(#post)
else
render 'new'
end
end
def edit
authorize #comment
end
def update
authorize #comment
if #comment.update(params[:comment].permit(:comment))
redirect_to post_path(#post)
else
render 'edit'
end
end
def destroy
authorize #comment
#comment.destroy
redirect_to post_path(#post)
end
private
def find_comment
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
end
end
comment_policy.rb
class CommentPolicy < ApplicationPolicy
def owned
comment.user_id == user.id
end
def create?
comment.user_id = user.id
new?
end
def new?
true
end
def update?
edit?
end
def edit?
owned
end
def destroy?
owned
end
end
The formatting and indenting is a bit off... thats not how I code I swear
class ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#user = user
#post = post
end
def index?
false
end
def show?
scope.where(:id => post.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, post.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
You initialized your resource in ApplicationPolicy as #post. And since your CommentPolicy inherits from ApplicationPolicy and uses its initialization, it only has access to #post. Best option is to keep it as record:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#user = user
#record = record
end
## code omitted
end
class CommentPolicy < ApplicationPolicy
def owned
record.user_id == user.id
end
## code omitted
end
Basically you can call it anything you want but record makes more sense as it will be used in different policy subclasses.
I have 2 models:
User model and Profile Model.
My association is as follows:
Class User < ActiveRecord::Base
has_one :profile
end
Class Profile < ActiveRecord::Base
belongs_to :user
validates user_id, presence: true
end
My Controllers is as follows:
USER:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
if logged_in?
#micropost = current_user.microposts.build
#profile = current_user.build_profile
#new_comment = Comment.build_from(#user, current_user.id, ' ')
end
#microposts = #user.microposts.paginate(page: params[:page])
#profile_player = #user.profile
end
end
PROFILE:
class ProfilesController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: [:destroy]
def create
#profile = current_user.build_profile(profile_params)
if #profile.save
flash[:success] = 'Profile Has Been Published'
# redirect_to request.referrer || root_url
redirect_to users_url
else
render :'pages/home'
end
end
def update
#profile.update(profile_params)
redirect_to user_url
end
def destroy
end
private
def profile_params
params.require(:profile).permit(:name, :age, :nationality, :country, :city, :height, :weight,
:dominant_hand, :play_position, :highschool, :college, :team,
:awards, :highlights)
end
def correct_user
#profile = current_user.profile.find_by(id: params[:id])
redirect_to root_url if #profile.nil?
end
end
Now, what I'm trying to do is render the profile view partial in the user show page (following Michael Hartl's tutorial):
hence I'm rendering the view via the instance variable I created in Users Controller show action for profile:
def show
##username = params[:id] ==============> this is intended to show the user name instead
# of the user id in the address bar
#user = User.find(params[:id])
if logged_in?
#micropost = current_user.microposts.build
#profile = current_user.build_profile
#new_comment = Comment.build_from(#user, current_user.id, ' ')
end
#microposts = #user.microposts.paginate(page: params[:page])
#profile_player = #user.profile
end
so, in my user show page:
I'm rendering the profile view like this:
Now here is the error I run into, my profile saves correctly when the form is submitted, however, when I return to the User show page (I'm rendering the profile form in the user home page) to view the profile, I get the error:
'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.
<div class="current-user-persona">
<%= render #profile_player %> =====> highlighted section for error
</div>
I'm not sure what I'm doing wrong here, can you help me?
been staring at this for days.
Running into something I don't understand with Pundit,
Using Rails 4.2.5.1, Pundit 1.1.0 with Devise for authentication.
I'm trying to use a policy scope for the BlogController#Index action.
If user is admin, display all posts (drafts, published)
If user is standard, display posts marked published only
If no user / user not logged in, display posts marked published only
Getting an error:
undefined method `admin?' for nil:NilClass
Live shell reveals:
>> user
=> nil
# ApplicationController
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def user_not_authorized
flash[:error] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
end
end
# BlogController
# == Schema Information
#
# Table name: blogs
#
# id :integer not null, primary key
# title :string default(""), not null
# body :text default(""), not null
# published :boolean default("false"), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class BlogsController < ApplicationController
before_action :set_blog, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
after_action :verify_authorized, except: [:index, :show]
after_action :verify_policy_scoped, only: [:index]
def index
#blogs = policy_scope(Blog)
authorize #blog
end
def show
end
def new
#blog = Blog.new
authorize #blog
end
def edit
authorize #blog
end
def create
#blog = Blog.new(blog_params)
#blog.user = current_user if user_signed_in?
authorize #blog
if #blog.save
redirect_to #blog, notice: "Blog post created."
else
render :new
end
end
def update
authorize #blog
if #blog.update(blog_params)
redirect_to #blog, notice: "Blog updated."
else
render :edit
end
end
def destroy
authorize #blog
#blog.destroy
redirect_to blogs_url, notice: "Blog post deleted."
end
private
def set_blog
#blog = Blog.friendly.find(params[:id])
end
def blog_params
params.require(:blog).permit(*policy(#blog|| Blog).permitted_attributes)
end
end
# Application Policy
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
# Blog Policy
class BlogPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where(published: true)
end
end
end
def new?
user.admin?
end
def index?
true
end
def update?
user.admin?
end
def create?
user.admin?
end
def destroy?
user.admin?
end
def permitted_attributes
if user.admin?
[:title, :body]
end
end
end
In the Pundit BlogPolicy scope I've created:
class Scope < Scope
def resolve
if user.admin?
scope.order('id DESC')
else
scope.where('published: true')
end
end
end
If I log in as an admin user it works fine.
I'm able to view all blog posts.
If I log in as a standard user it works.
Standard user sees blog posts that are marked published.
If I'm not logged in where user is nil I get an error:
NoMethodError at /blog
undefined method `admin?' for nil:NilClass
I can add another clause elsif user.nil? before user.admin? or a case when statement but I thought if the user is not an admin it should just display what is in the else block?
# This seems wrong?
class Scope < Scope
def resolve
if user.nil?
scope.where('published: true')
elsif user.admin?
scope.all
else
scope.where('published: true')
end
end
end
Any pointers much appreciated
You can use try:
if user.try(:admin?)
# do something
end
http://api.rubyonrails.org/v4.2.5/classes/Object.html#method-i-try
This happens because there is no user when you are not logged in. (Probably to user variable nil value is assigned somewhere, so you are trying to call admin? method on nil object)
If you use ruby 2.3.0 or newer you had better use safe navigation
if user&.admin?
scope.order('id DESC')
else
scope.where('published: true')
end
If you user other ruby version
if user.try(:admin?)
scope.order(id: :desc)
else
scope.where(published: true)
end
I'm trying to add authorization to my rails app and want to redirect a non-user to root_url when they try to access posts/new, using rescue_from. However, there is no redirect to root or error message being displayed and I'm not sure why.
This is my application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
rescue_from Pundit::NotAuthorizedError do |exception|
redirect_to root_url, alert: exception.message
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
end
end
This is application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
user.present?
end
def new?
create?
end
def update?
user.present? && (record.user == user || user.admin?)
end
def edit?
update?
end
def destroy?
update?
end
def scope
record.class
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
This is post_policy.rb
class PostPolicy < ApplicationPolicy
def new
#post = Post.new
authorize #post
end
end
This is posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def show
#ApplicationController::Find
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = current_user.posts.build(params.require(:post).permit(:title, :body))
if #post.save
flash[:notice] = "Post was saved."
redirect_to #post
else
flash[:error] = "There was an error saving the post. Please try again."
render :new
end
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
if #post.update_attributes(params.require(:post).permit(:title, :body))
flash[:notice] = "Post was updated."
redirect_to #post
else
flash[:error] = "There was an error saving the post. Please try again."
end
end
end
The issue was that I defined another new method in the post_policy.rb file, overwriting the new method in application_policy.rb. I also didn't include authorize #post in the new method in the posts_controller.rb file.