I have been stuck with this issue for quite some time now and not sure what I am doing wrong.
I am using Rails 4.2.5.1, Pundit 1.1.0 and Devise.
I have a blog post which displays the following:
- title
- author username
- image
- sanitized excerpt (on index page)
- body (on show page)
The index page displays correctly (with exception of the author username which does not show, because it doesn't recognize the username param). However, when I try to view an individual post via the show page, I get the following error:
undefined method `image' for nil:NilClass
If I remove that line of code for displaying the image, I get an error for the title with the same undefined method error.
I have followed the example at SitePoint-source/Authorization_with_Pundit almost exactly for policies and the controllers (only minor modifications)
Everything was working perfectly before adding Pundit for creating authorization between admins, editors, and users.
Here is my current code:
Application Controller
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
before_filter :configure_permitted_parameters, if: :devise_controller?
private
def user_not_authorized
flash[:alert] = "Access denied. You are not authorized to view that page."
redirect_to (request.referrer || root_path)
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me) }
devise_parameter_sanitizer.permit(:sign_in) { |u| u.permit(:username, :email, :password, :remember_me) }
devise_parameter_sanitizer.permit(:account_update) {|u| u.permit(:username, :email, :password, :password_confirmation, :current_password)}
end
end
Post Controller
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
after_action :verify_authorized, only: [:destroy]
after_action :verify_policy_scoped, only: [:user_posts]
def index
@meta_title = "Blog"
@meta_description = "description here"
@posts = Post.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 4)
end
def show
end
def new
@meta_title = "Add New Blog"
@meta_description ="Add a new blog to your profile."
@post = Post.new
end
def edit
@meta_title = "Edit Blog"
@meta_description ="Edit an existing blog from your profile."
end
def create
@post = Post.new
@post.update_attributes(permitted_attributes(@post))
if @post.save
redirect_to @post, notice: 'Post was successfully created.'
else
render :new
end
end
def update
@post = Post.find(params[:id])
if @post.update_attributes(permitted_attributes(@post))
redirect_to @post, notice: 'Post was successfully updated.'
else
render :edit
end
end
def destroy
if @post.present?
authorize @post
@post.destroy
else
skip_authorization
end
redirect_to posts_url, notice: 'Post was successfully deleted.'
end
def user_posts
@posts = policy_scope(Post)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find_by(id: params[:id])
end
# Only allow the white list through.
def post_params
params.require(:post).permit(policy(@post).permitted_attributes)
end
end
Application Policy
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
@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
Post Policy
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(user: user)
end
end
def permitted_attributes
if user.admin? || user.editor?
[:title, :body, :image, :permalink, :description, :tag_list, :username]
else
[:title, :body, :image, :username]
end
end
def new?
user.admin? || user.editor?
end
def index?
true
end
def create?
user.admin? || user.editor?
end
def update?
user.admin? || user.editor? || record.user == user
end
def destroy?
user.admin? || record.user == user
end
end
Post.rb
class Post < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
belongs_to :user
# This method associates the attribute ":image" with a file attachment
has_attached_file :image, styles: {
thumb: '100x100>',
square: '200x200#',
medium: '300x300>',
}
extend FriendlyId
friendly_id :permalink, use: [:slugged, :history, :finders]
validates :permalink, presence: true, uniqueness: true
validates :title, presence: true, length: { minimum: 5}
validates :description, presence: true, uniqueness: true, length: {maximum: 160}
validates :body, presence: true
validates :image, presence: true
# Validate the attached image is image/jpg, image/png, etc
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
def should_generate_new_friendly_id?
permalink_changed?
end
end
Post#show
<% provide(:title, "@post.title") %>
<% provide(:description, "@post.description") %>
<div class="row">
<div class="col-md-offset-1 col-md-10">
<div class="panel panel-default">
<div class="panel-heading center">
<%= image_tag @post.image.url, :style => "width: 100%; height: auto;" %>
</div>
<div class="panel-body">
<h2 class="title center"><%= @post.title %></h2>
<p class="posted"><i class="ion-android-time"></i> <%= @post.created_at.strftime("%B %d, %Y") %> </p>
<p class="posted"><i class="ion-person"></i> Author: <%= link_to @post.username, about_path(:anchor => "coaches") %></p>
<hr>
<div class="postBody" id="summernote">
<%= @post.body.html_safe %>
</div>
</div>
<div class="panel-footer center">
<%= link_to 'Back', posts_path %> |
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Delete', @post, method: :delete, data: { confirm: 'Are you sure you want to delete this post?' } %>
<%= render 'disqus' %>
</div>
<div class="panel-footer center">
<%= link_to 'Back', posts_path %>
</div>
</div>
</div>
</div>
Post#index
<div class="container">
<div class="row">
<div class="col-md-9">
<% @posts.each do |post| %>
<div class="post-wrapper">
<h3 class="title center"><%= link_to post.title, post %></h3>
<p class="posted"><i class="ion-android-time"></i> <%= post.created_at.strftime("%B %d, %Y") %></p>
<p class="posted"><i class="ion-person"></i> Author: <%= link_to post.user(:username), about_path(:anchor => "coaches") %></p><br>
<div class="post-image center"><%= link_to image_tag(post.image.url, :style => "width: 100%; height: auto;"), post %></div><br>
<%= sanitize(post.body[0,300]) %>...<br>
<div class="center">
<%= link_to 'View Blog', post, class: "btn btn-primary" %>
<% if policy(post).update? %>
<%= link_to 'Edit', edit_post_path(post) %> |
<% end %>
<% if policy(post).destroy? %>
<%= link_to 'Delete', post, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
</div>
<br>
</div>
<% end %>
<div class="center">
<%= will_paginate @posts, renderer: BootstrapPagination::Rails %>
</div>
</div>
</div>
</div>
I also have a few other issues that hopefully will resolve on their own once this issue is fixed:
- Deleted posts get a flash message saying they delete, but they are still there
- Edit post gets the same image error message
- Non-signed in users are denied access to view posts and I want them to be able to view all posts regardless of if signed in or not. This is the same issue, but the solution is not working for me and I don't get any type of rails error message: Pundit policy_scope error. Maybe this has something to do with the initialize in the App Policy?
These other issues can be solved later or if you see an error I'd be grateful for help.
Right now my main issue trying to solve the undefined method "image" for nil:NilClass
error