Rails: How do I resolve: Pundit::NotDefinedError in PostsController#show? - ruby-on-rails

whenever I run the below program and try to view my posts (in my show view) as any user, I am introduced to this error page:
Pundit::NotDefinedError in PostsController#show
unable to find policy of nil
Within that error page:
def show
#post = Post.find(params[:id])
authorize #posts # <- The error highlights this line
end
I'm not sure how to get around this dilemma as I'm just learning about Pundit Policy rules and am new to Rails and Ruby. Any help would be much appreciated. Below are my policy pages and related pages:
User.rb Model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :posts
def admin?
role == 'admin'
end
def moderator?
role == 'moderator'
end
def member?
role == 'member'
end
def guest?
role == 'guest'
end
end
Application Controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
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
Posts Controller
class PostsController < ApplicationController
# before_action :flash_attack
# protected
# def flash_attack
# flash[:notice] = "Create/Edit/Comment on a post!"
# end
def index
#posts = Post.all
authorize #posts
end
def show
#post = Post.find(params[:id])
authorize #posts
end
def new
#post = Post.new
authorize #post
end
def create
#post = current_user.posts.build(params.require(:post).permit(:title, :body))
authorize #post
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])
authorize #post
end
def update
#post = Post.find(params[:id])
authorize #post
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."
render :edit
end
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?
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
Posts Policy
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where(:published => true)
end
end
def index?
true
end
def show?
true
end
def update?
user.admin? or not post.published?
end
end
end
Index View
<h1>All Posts</h1>
<% if policy(Post.new).create? %>
<%= link_to "New Post", new_post_path, class: 'btn btn-success' %>
<% end %>
<% #posts.each do |post| %>
<div class="media">
<div class="media-body">
<h4 class="media-heading">
<%= link_to post.title, post %>
</h4>
<small>
submitted <%= time_ago_in_words(post.created_at) %> ago by <%= post.user.name unless post.user.nil? %><br>
<%= post.comments.count %> Comments
</small>
</div>
</div>
<% end %>
Show View
<h1> <%= #post.title %> </h1>
<% if policy(#post).edit? %>
<%= link_to "Edit", edit_post_path(#post), class: 'btn btn-success' %>
<% end %>
<p> <%= #post.body %> </p>
Thanks in advance everyone. Let me know if any more information would be great.

#posts is nil in show action, you should use #post as such:
authorize #post

I had this issue when working on a Rails 6 API only application with the Pundit gem.
I was running into the error below when I test my Pundit authorization for my controller actions:
Pundit::NotDefinedError - unable to find policy of nil
Here's how I solved:
The instance variables called by the authorize method in your controller must correspond to the instance variable of the controller action being called.
So for the index action it should be #posts:
authorize #posts
For the show action it should be #post:
authorize #post
and for the create action it should be #post
authorize #post
and so on.
That's all.
I hope this helps

Related

Rails Omniauth twitter gem - not authorizing user correctly

I'm building a Rails app which allows users to create and book onto events. I've integrated the twitter omniauth gem along with devise. It logs me in correctly and redirects back however when I click on the link to create an event or book an event the app redirects me back to the sign in page. I've set the site up so that only signed in users can do this but it doesn't appear to cover the omniauth integration.
I also have no way to sign-out from one user to another if I use Twitter to sign in. I want to add Facebook auth also but want to fix this first. What code (inc. validations) am I missing to cover these functions?
Here's the relevant code so far -
Events Controller -
class EventsController < ApplicationController
before_action :find_event, only: [:show, :edit, :update, :destroy,]
# the before_actions will take care of finding the correct event for us
# this ties in with the private method below
before_action :authenticate_user!, except: [:index, :show]
# this ensures only users who are signed in can alter an event
def index
if params[:category].blank?
#events = Event.all.order("created_at DESC")
else
#category_id = Category.find_by(name: params[:category]).id
#events = Event.where(category_id: #category_id).order("created_at DESC")
end
# The above code = If there's no category found then all the events are listed
# If there is then it will show the EVENTS under each category only
end
def show
end
def new
#event = current_user.events.build
# this now builds out from a user once devise gem is added
# after initially having an argument of Event.new
# this assigns events to users
end
# both update and create actions below use event_params as their argument with an if/else statement
def create
#event = current_user.events.build(event_params)
# as above this now assigns events to users
# rather than Event.new
if #event.save
redirect_to #event, notice: "Congratulations, you have successfully created a new event."
else
render 'new'
end
end
def edit
# edit form
# #edit = Edit.find(params[:id])
#event = current_user.events.find(params[:id])
end
def update
if #event.update(event_params)
redirect_to #event, notice: "Event was successfully updated!"
else
render 'edit'
end
end
def destroy
#event.destroy
redirect_to root_path
end
private
def event_params
params.require(:event).permit(:title, :location, :date, :time, :description, :number_of_spaces, :is_free, :price, :organised_by, :url, :image, :category_id)
# category_id added at the end to ensure this is assigned to each new event created
end
def find_event
#event = Event.find(params[:id])
end
end
Application controller -
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
devise_parameter_sanitizer.for(:account_update) << :name
end
# the application controller
# handles everything across the site
# make the current_user AND the logged_in? available to
# be used in the views as well as the controllers
helper_method :current_user
helper_method :logged_in?
helper_method :logged_out?
def current_user
# this is who I am signed in as
#current_user = User.find(session[:uid])
end
def logged_in?
# am i logged in?
# do i have a cookie called uid?
session[:uid].present?
end
def make_sure_logged_in
# If I'm not logged in, redirect me to the log in page
if not logged_in?
flash[:error] = "You must be signed in to see that page"
redirect_to new_session_path
end
end
def logged_out?
session[:uid] = nil
flash[:success] = "You've logged out"
redirect_to root_path
end
end
index.html.erb - events
<header>
<div class="category">
<%= link_to image_tag('MamaKnows.png'), root_path, id: "home" %>
<% Category.all.each do |category| %>
<li><%= link_to category.name, events_path(category: category.name) %></li>
<% end %>
<!-- The code loop above creates category links to the home page -->
</div>
<nav id="nav">
<% if logged_in? %>
<%= link_to 'Create Event', new_event_path %>
<%= link_to 'Account', user_path(current_user) %>
<%= link_to 'Sign out', destroy_user_session_path, :method => :delete %>
<% else %>
<%= link_to "Create an Event", new_user_session_path %>
<% end %>
</nav>
</header>
<% #events.each do |event| %>
<%= link_to (image_tag event.image.url), event %>
<h2><%= link_to event.title, event %></h2>
<h2><%= link_to event.date.strftime('%A, %d %b %Y'), event %></h2>
<% end %>
OmniauthCallback Controller
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def twitter
#details = request.env["omniauth.auth"]
#provider = #details["provider"]
#provider_id = #details["uid"]
#user = User.where(provider: #provider, provider_id: #provider_id).first
if #user.present?
#sign them in
else
# make a new user
#user = User.new
#user.provider = #provider
#user.provider_id = #provider_id
# because of has_secure_password - will this work?
#user.password = "AAAAAA!!"
#user.password_confirmation = "AAAAAA!!"
# let's save the key and secret
#user.key = #details["credentials"]["token"]
#user.secret = #details["credentials"]["secret"]
# lets fill in their details
#user.name = #details["info"]["name"]
if #provider == "twitter"? #user.save!(:validate => false) : #user.save!
# the above if statement allows for twitter to skip validation which requires an email
#user.email = #details["info"]["email"]
end
#user.save!
end
session[:uid] = #user.id
flash[:success] = "You've signed in"
redirect_to root_path
end
def password_required?
super && provider.blank?
end
end
Any assistance would be appreciated.

I get this error "undefined method `name' for nil:NilClass" on trying to access a user by id

I am a newbie to rails, and I have already tried all solutions available on stackoverflow before.I am trying to access a user by id using localhost:3000/users/1. My code for user_controller is:
class UsersController < ApplicationController
def new
#user=User.new
end
def index
#users = User.all
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
#title = "Sign up"
render 'new'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation,:email)
end
def show
#user = #users.find(params[:id])
#users=User.all
end
end
And my show.html.erb contains :
<% provide(:title, #users.name ) %>
<div class="row">
<aside class="span4">
<section>
<h1>
<%= gravatar_for #user %>
<%= #users.name %>
</h1>
</section>
</aside>
</div>
Take show action out of private. It is not a private method. That is why the variable, #users is nil. However, you don't need to even find #users, answer below shows that.
You are trying to call the method name (an attribute), on an array, #users. You need to call it on the object, #user.
Side note: there is also a predefined method, name, in Rails for an ActiveRecord::Relation (User.all). however, in the example you posted, your variable, #users was nil, so it wasn't getting this far. But, if your show action was not a private method, you would have experienced this:
$ User.all.name # or #users.name
# => 'User'
$ Post.all.name
# => 'Post'
$ User.first.name # or #user.name
# => 'John'
Solution
# show.html.erb
<%= #user.name %>
# users_controller
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user = User.new
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
#title = "Sign up"
render 'new'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation, :email)
end
end

ArgumentError in Topics#show Possible Devise error?

I'm a bit of a rails newb here and need some help figuring this out. I have an argument error that is thrown every time I try and create or edit a new "topic."
ArgumentError
Here is the code for the "show:"
<h1><%= #topic.title %></h1>
<%= link_to "Edit", edit_topic_path(#topic), class: 'btn btn-success' %>
<%= link_to "Delete Topic", #topic, method: :delete, class: 'btn btn-danger', data: {confirm: 'Are you sure you want to delete this topic?'} %>
<% if policy(Bookmark.new).create? %>
<%= link_to "New Bookmark", new_topic_bookmark_path(#topic), class: 'btn btn-success' %>
<% end %>
<% #topic.bookmarks.each do |bookmark| %>
<div class="media-body">
<div class="row">
<div class="col-md-2">
<div class="container">
<img src="http://icons.better-idea.org/icon?url=<%= bookmark.url %>&size=120">
<div class="media-heading">
<%= link_to bookmark.name, topic_bookmark_path(#topic, bookmark) %>
</div>
</div>
</div>
</div>
<div class="col-md-1">
<%= render partial: 'likes/like', locals: {bookmark: bookmark} %>
</div>
</div>
<% end %>
Here is the "topics" controller:
class TopicsController < ApplicationController
def index
#topics = Topic.all
end
def show
#topic = Topic.find(params[:id])
end
def new
#topic = Topic.new
end
def create
#topic = Topic.new(topic_params)
if #topic.save
flash[:notice]= "Topic was saved."
redirect_to #topic
else
flash.now[:alert]= "The topic could not be saved. Please try again"
render :new
end
end
def edit
#topic = Topic.find(params[:id])
end
def update
#topic = Topic.find(params[:id])
#topic.assign_attributes(topic_params)
if #topic.save
flash[:notice]= "The topic was saved sucessfully."
redirect_to #topic
else
flash.now[:alert]= "There was an error saving the topic. Please try again."
render :edit
end
end
def destroy
#topic = Topic.find(params[:id])
if #topic.destroy
flash[:notice]= "\"#{#topic.title}\" was deleted successfully."
redirect_to topics_path
else
flash.now[:alert] = "There was an error in deleting this topic."
render :show
end
end
def topic_params
params.require(:topic).permit(:title)
end
end
Update(1): New error after deleting "policy" New Error
Here is the "application policy that uses Pundit:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#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 )
end
def edit?
update?
end
def destroy?
user.present? && (record.user == user)
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
Problem is with Policy made with the Pundit gem. Can you check something called the BookmarkPolicy or something similar, or at least post that here. Did you forget to include Pundit in your controller ?
You're using the Pundit gem but I don't see the authorize method in your controller. From Pundit's documentation:
Supposing that you have an instance of class Post, Pundit now lets you
do this in your controller:
def update
#post = Post.find(params[:id])
authorize #post
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
The authorize method automatically infers that Post will have a
matching PostPolicy class, and instantiates this class, handing in the
current user and the given record.
The source code in the screenshot and the code you posted is not the same. Your code:
<% if policy(Bookmark.new).create? %>
Screenshot:
<% if (Bookmark.new).create? %>
Rails is correctly reporting that Bookmark.new does not have a create? method, because it's missing the policy() method call.
Is your file saved? Are you sure you're changing the correct file?

Rails closure_tree - how do I add Devise method current_user to Create action in PostsController?

I built nested posts using this closure_tree tutorial:
[Nested Comments with Rails - Sitepoint][1]
Then I installed Devise and began attributing posts to users in the PostsController and Post & User models. How can I add the current_user method to the Create action (the 'if' portion, not the 'else' portion) in my PostsController so I can track the creator of a post which has replies? I have tried several combinations including:
#post = current_user.parent.children.build(post params)
#post = current_user.posts.parent.children.build(post params)
They are all throwing errors.
Here is my code:
Post.rb
class Post < ActiveRecord::Base
belongs_to :user
acts_as_tree order: 'created_at DESC'
end
User.rb
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index]
def index
#post = Post.new
#posts = Post.hash_tree
end
def show
#post = Post.find(params[:id])
end
def new
#post = current_user.posts.build(parent_id: params[:parent_id])
end
def create
if params[:post][:parent_id].to_i > 0
parent = Post.find_by_id(params[:post].delete(:parent_id))
#post = parent.children.build(post_params)
else
#post = current_user.posts.build(post_params)
end
if #post.save
flash[:success] = 'Your post was successfully added!'
redirect_to posts_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
end
private
def post_params
params.require(:post).permit(:content)
end
end
_Post.html.erb partial
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="http://placehold.it/50x50" alt="...">
</a>
</div>
<div class="media-body">
<%= post.content %></br>
<%= link_to time_ago_in_words(post.created_at), post_path(post) %> ago by <%= post.user.name.capitalize %> | <% from_reply_form ||= nil %>
<% unless from_reply_form %>
<%= link_to 'reply', new_post_path(post.id) %>
<% end %>
</div>
</div>
Thanks!!!
`ThreadedSalesApp::Application.routes.draw do
devise_for :users
resources :users, only: [:show, :index]
resources :posts, only: [:index, :show, :create]
get '/posts/new/(:parent_id)', to: 'posts#new', as: :new_post
root 'posts#index'
end`
I'm having a bit of an issue understanding what is your post nested under? The tutorial you referenced shows how to nest comments. Comments could be nested under posts. I'm sure others will be more helpful than I am given my limited experience but from what I can see, your methods are wrong. Consider the following:
def index
#post = Post.new
#posts = Post.hash_tree
end
In your index, you ought to show an index (think of it as a list) of your objects, in this case it would be the list of Posts. You're initiating a new post which should happen in your 'new' method. In the tutorial you referenced, you'll notice that index is defined as:
def index
#comments = Comment.all
end
To give you an example, if we're nesting posts under Topics, your code could look something like this:
class Topics::PostsController < ApplicationController
def show
#post = Post.find(params[:id])
#topic = Topic.find(params[:topic_id])
end
def new
#topic = Topic.find(params[:topic_id])
#post = Post.new
end
def create
#topic = Topic.find(params[:topic_id])
#post = current_user.posts.build(post_params)
#post.topic = #topic
if #post.save
flash[:notice] = "Your post was successfully added!"
redirect_to [#topic, #post]
else
flash[:error] = "There was an error saving your post."
render :new
end
end
...
Basically, if your posts are nested under something else, you must find that object's ID first to nest it under that. The same could be done with comments (comments being nested under your posts). You would look for the post_id in your Comments Controller.
Figured it out. I needed to merge the user_id: with the post_params, and when calling the user in the post index view I needed to use post.user.name versus user.name.

Rails wicked gem

I am having problems related to the links given to login and logout.
I am not using devise gem
In my code I have given the following links
<% if current_user %>
<li><%= link_to 'Logout',{:controller=>'sessions', :action=> 'destroy'}%></li>
<% else %>
<li> <%= link_to 'Signup',{:controller =>'users', :action => 'new'} %> </li>
<li> <%= link_to 'Login,{:controller =>'sessions', :action => 'new'} %> </li>
<% end %>
I am using the wicked gem which also has the following steps:
include Wicked::Wizard
steps :business, :login, :payment
If a user enters the form_for values for new method in users_controller and submits it, the user goes to the next step but the link it shows above is "Logout" i.e the user is logged in before signup.
What to do?
Pls, any solution given is appreciated
users_controller.rb:
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.update_attributes(user_params )
session[:user_id]= #user.id
redirect_to user_steps_path
else
render :new
end
end
private
def user_params
params.require(:user).permit( :fname, :lname, :email, :mob, :gender, :country, :state, :suburb, :postal ,:add)
end
end
user_steps_controller.rb
include Wicked::Wizard
steps :business, :login, :payment
def show
#user = current_user
render_wizard
end
def update
#user = current_user
params[:user][:current_step] = step
session[:user_id]= #user.id
#user.update_attributes(user_params )
render_wizard #user
end
private
def redirect_to_finish_wizard(options = nil)
redirect_to root_url
end
def user_params
params.require(:user).permit( :current_step,:cmpyname, :abnacn, :cmpyadd, :cmpydet,:cash, :paypal,:bsb,:usrname,:password, :password_confirmation, :selcat, :protit, :prodes)
end
end
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
Just check in your views if the user is logged in to show your step form:
<% if user_signed_in?%>
instead of
<% if current_user%>
You need to sign out the user after creating it, you can do something like this
if resource.save
sign_out resource # resource = #user
You might need to override devise registrations controller for that if you are using devise!
EDIT:
In your create action you are setting session for newly created user, remove this line from your create action
session[:user_id]= #user.id
Hope this helps!
Instead of checking with current_user you should check <% if session[:user_id].present? %>
It may solve your problem

Resources