Authorization settings using pundit gem rails - ruby-on-rails

I'm new at rails so bear with me pls. My problem is so specific. I'm creating a User blog, where they could put any posts. So Users has a blogs, and blogs has posts. So when user create a blog, all posts in his blog should be written by him. Other users can't write not on their blogs.
post_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :authorize_user!, only: [:edit, :update, :destroy]
expose :blog
expose :post
def show
end
def new
end
def edit
end
def create
post.user = current_user
post.save
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def update
post.update(post_params)
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
def destroy
post.destroy
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end
private
def authorize_user!
authorize(post, :authorized?)
end
def post_params
params.require(:post).permit(:title, :content, :user_id, :blog_id)
end
end
Here i'm using pundit to authorize user, when they update or destroy posts (users can update or destroy only their own posts) and it works perfectly.
views/posts/new
.row
.columns
h2 = title("New post")
.row
.medium-5.columns
= simple_form_for post do |f|
= f.error_notification
.form-inputs
= f.input :title
= f.input :content
= f.hidden_field :blog_id, value: blog.id
.form-actions
= f.button :submit
Here i'm using the hidden form to set the blog_id which I take from params. Http link looks like http://localhost:3000/posts/new?blog_id=6. The problem is that each user can copy this link to create the post( and they are not the blog owners).
post_policy.rb
class PostPolicy < ApplicationPolicy
def authorized?
record.user == user
end
end
How should I check the blog's owner before post creating? Maybe I have a wrong way to create posts like this(using hidden form).
Link to create new post
= link_to 'New Post', new_post_path(blog_id: blog.id)

I hope, it will work for you
application_controller.rb
class ApplicationController
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
before_action :authenticate_admin_user!
helper_method :current_user
def pundit_user
current_admin_user
end
def current_user
#current_user ||= User.find(current_admin_user.id)
end
end
posts_controller.rb
class PostsController < ApplicationController
before_action :set_blog
def new
authorize(Post)
end
def edit
#post = #blog.posts.find(params[:id])
authorize(#post)
end
def index
#posts = policy_scope(#blog.posts)
end
private
def set_blog
#blog = current_user.blogs.find(params[:blog_id])
end
end
post_policy.rb
class PostPolicy < ApplicationPolicy
def show?
true
end
def index?
true
end
def new?
create?
end
def create?
true
end
def edit?
update?
end
def update?
scope_include_object?
end
def destroy?
scope_include_object?
end
class Scope < Scope
def resolve
scope.joins(:blog).where(blogs: { admin_user_id: user.id })
end
end
def scope_include_object?
scope.where(id: record.id).exists?
end
end
routes.rb
Rails.application.routes.draw do
devise_for :admin_users
resources :blogs do
resources :posts
end
end

Related

Rails: How can I change Boolean values in a model?

I have a model called "Clients". The Clients model belongs to the Users model (Users model is associated with devise). The Client can do payments to me manually (cash only). When the client does this payment, I give them access to more pages in the website. To do this, I was thinking I add a migration to the Client model like:
rails generate migration add_paid_to_clients paid:boolean
Then when the clients pay the money, I can just go to their profile and click on an option (maybe a checkbox) saying the client has paid. Only the admin(me) can see this part. The way I would implement that is, in the user profile view:
<% if current_user.user_type == :client%>
userinformation....blablabla
<% elsif current_user.user_type == :admin %>
userinformation....blablabla
AND the checkbox I talked about.
<% end %>
The user_type is a predefined function to figure out if the current_user is admin or just a client. I have done this part.
So in the private pages(the pages you can get access to after paying), I can have some logic like If current_user.paid? THEN show them this page.
How can I implement the checkbox part? After the migration(if there is a better way,please let me know),how can I just "flip the switch" to let them get more access?
My Clients contoller:
class ClientController < ApplicationController
before_action :find_client, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#client = current_user.build_client
end
def create
#client = current_user.build_client(client_params)
if #client.save
redirect_to clients_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#client.destroy
redirect_to root_path
end
private
def client_params
params.require(:client).permit(:name, :company, :position, :number, :email, :client_img)
end
def find_client
#client = Client.find(params[:id])
end
end
This is not real cash, this is a project for school.
Something like this?
In the form for the admin:
<%= form_for #user do |f| %>
<%= check_box_tag 'paid', 'yes', true %>
<%= f.submit "Update" %>
<% end %>
Then in the controller:
class ClientController < ApplicationController
before_action :find_client, only: [:show, :edit, :update, :destroy]
def index
end
def show
end
def new
#client = current_user.build_client
end
def create
#client = current_user.build_client(client_params)
if #client.save
redirect_to clients_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#client.destroy
redirect_to root_path
end
private
def client_params
if current_user.user_type == :admin
# This is different because only admin can update paid
# To make a string a boolean
params[:client][:paid] = params[:client][:paid] == 'yes'
params.require(:client).permit(:paid, :name, :company, :position, :number, :email, :client_img)
else
params.require(:client).permit(:name, :company, :position, :number, :email, :client_img)
end
end
def find_client
#client = Client.find(params[:id])
end
end
you can do this more properly using the protected attributes, described here
After that you want to make a PaidController
class PaidController < ApplicationController
before_filter :authorize_paid
def authorize_paid
return head(:forbidden) unless current_user.paid?
end
end
Then make sure that all the protected pages inherit from the PaidController

Activerecord reputation system - nested routes

I am trying to follow this video railscast #364 but I am having a lot of trouble with my nested routes. When I use this code:
<%= link_to "up", vote_movie_review_path(#movie, #reviews, type: "up"), method: "post" %>
I get this error when I select up vote:
ActiveRecord::RecordNotFound in ReviewsController#vote
Couldn't find Review with 'id'=# <Review::ActiveRecord_Relation:0x007f0358c1e550>
This is my route:
vote_movie_review POST /movies/:movie_id/reviews/:id/vote(.:format) genre_linkers#vote
I created another model that was not nested using this code:
<%= link_to "up", vote_movie_path(movie, type: "up"), method: "post" %>
and that one worked. So I am thinking it has to be something wrong with my path or how I am calling the objects. I have spent almost all day working on this, I really need help.
review_controller.rb
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :set_movie
before_action :authenticate_user!
respond_to :html
def index
#reviews = Review.all
respond_with(#reviews)
end
def show
end
def vote
value = params[:type] == "up" ? 1 : -1
#review = Review.find(params[:id])
#review.add_evaluation(:vote, value, current_user)
redirect_to :back, notice: "thanks for the vote"
end
def new
#review = Review.new
respond_with(#review)
end
def edit
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
#review.movie_id = #movie.id
if #review.save
redirect_to #movie
else
render 'new'
end
end
def update
#review.update(review_params)
respond_with(#review)
end
def destroy
#review.destroy
respond_with(#review)
end
private
def set_review
#review = Review.find(params[:id])
end
def set_movie
#movie = Movie.find(params[:movie_id])
end
def review_params
params.require(:review).permit(:genre, :description, :vote)
end
end
routes.rb
Rails.application.routes.draw do
devise_for :users
resources :movies do
resources :reviews do
member { post :vote }
end
end
root 'movies#index'
end
and the model
review.rb
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :movie
has_reputation :votes, source: :user, aggregated_by: :sum
end
The culprit is this part of your link_to: vote_movie_review_path(#movie, #reviews, type: "up"). #reviews is an ActiveRecord::Relation and not a Review record, hence no record with an ID can be found.

Create a post as a user and assign it to a group

In my rails application I have made three models, User, Group and Post.
I am trying to make it so that #post.group is equal to #group.id
The post is posted from a render inside of the group#show controller and then redirects itself to the post#create controller. How do I set the #post.group to #group.id?
GroupsController
class GroupsController < ApplicationController
before_action :set_group, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#groups = Group.all
respond_with(#groups)
end
def show
#group_id = #group.id
respond_with(#group)
end
def new
#group = Group.new
respond_with(#group)
end
def edit
end
def create
#group = Group.new(group_params)
#group.save
respond_with(#group)
end
def update
#group.update(group_params)
respond_with(#group)
end
def destroy
#group.destroy
respond_with(#group)
end
private
def set_group
#group = Group.find(params[:id])
end
def group_params
params.require(:group).permit(:name, :description, :motto, :usercount, :group, :id, :groupid)
end
end
PostsController
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#posts = Post.all
respond_with(#posts)
end
def show
respond_with(#post)
end
def new
#post = Post.new
respond_with(#post)
end
def edit
end
def create
#post.group = #group_id
#post = Post.new(post_params)
#post.save
respond_with(#post)
end
def update
#post.update(post_params)
respond_with(#post)
end
def destroy
#post.destroy
respond_with(#post)
end
private
def set_post
#post = Post.find(params[:id])
end
def set_group
#group = Group.find(params[:id])
end
def post_params
params.require(:post).permit(:body, :group, :id)
end
def group_params
params.require(:group).permit(:id, :groupid, :group)
end
end
PostModel
class Post < ActiveRecord::Base
belongs_to :group
end
GroupsModel
class Group < ActiveRecord::Base
has_many :posts
end
The current code that I have returns the error undefined methodgroup=' for nil:NilClass`
Any help is appreciated!
You should start from creating Post instance, because you don't do this. I would do it like this:
def create
#post = #group.posts.build(post_params)
if #post.save
# handle success
else
# handle error
end
end
What's more, you should set before_action :set_group in your PostsController.

rails 4. how to create actions using relationships between three models?

I would like to create relationships between three models: user, post and comment.
User have many posts and comments
Post have only one user and many comments
Comment have one user and one post
so i create next migrations:
class Users < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
class Posts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :content
t.integer :user_id
t.timestamps
end
end
end
class Comments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :title
t.string :content
t.integer :user_id
t.integer :post_id
t.timestamps
end
end
end
=============================================
models are next:
user.rb
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
post.rb
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
===============================================
My users_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update, :destroy]
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.find(params[:id])
#posts = #user.posts.paginate(page: params[:page])
#comments = #user.comments.paginate(page: params[:page])
end
def new
#user = User.new(params[:user])
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
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted."
redirect_to users_url
end
def create
#user = User.new(user_params)
if #user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
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
now i want to create some actions for next tasks:
For posts_controller.rb
1.1 create a post by user
1.2 delete a post by user
1.3 show user post with all comments
1.4 show all user posts
class PostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#post = user.post.build(post_params)
#post = post.save
end
def destroy
#post.destroy
end
def show_user_post_with_all_comments
???
end
def show_all_user_posts
???
end
private
def post_params
params.require(:post).permit(:title, :content)
end
def correct_user
#post = current_user.posts.find_by(id: params[:id])
redirect_to root_url if #post.nil?
end
end
For comments_controller.rb
2.1 create a comment by user in post
2.2 delete a comment by user in post
2.3 show all user comments
2.4 find and show a post by user comment
class CommentsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#comment = user.comment.build(comment_params)
#comment = comment.save
end
def destroy
#comment.destroy
end
def show_comment
???
end
def show_all_user_comments
???
end
def find_and_show_post_by_user_comment
???
end
private
def comment_params
params.require(:comment).permit(:content)
end
def correct_user
#comment = current_user.comments.find_by(id: params[:id])
redirect_to root_url if #comment.nil?
end
end
Pls check for correct my migrations and models and help me with creating of actions with "???" in bodies
Thank you much for your answers.
PostsController
def show_user_post_with_all_comments
#post = Post.find(params[:id]).eager_load(:comments)
end
def show_all_user_posts
#posts = current_user.posts
end
CommentsController
def show_comment
#comment = Comment.find(params[:id])
end
def show_all_user_comments
#comments = current_user.comments
end
def find_and_show_post_by_user_comment
#comment = Comment.find(params[:id]).eager_load(:post)
#post = #comment.post
end
What I've done in the past in a similar situation would be to put all this work in the UsersController and add a few new actions to it:
class UsersController < ApplicationController
...
def new_post
#user = User.find(params[:id])
end
def create_post
#user = User.find(params[:id])
if #user.update_attributes user_post_params
redirect_to somewhere_path
else
render 'new_post'
end
end
def show_post
#post = Post.find(params[:id])
# Not sure how you are implementing sessions, but say you have current_user defined
# for sessions, then your view could have a delete link conditional on
# #post.user_id == current_user.id
#comments = #post.comments
end
def show_all_posts
#user = User.find(params[:id])
#posts = #user.posts
end
def new_comment
#user = current_user
#post = Post.find(params[:id])
end
def create_comment
#user = current_user
#post = Post.find(params[:id])
#comment = Comment.new(comment_params)
if #post.update_attributes comment_params
#user.comments << #comment
if #user.save
redirect_to somewhere
else
render 'new_comment'
end
else
render 'new_comment'
end
end
def show_comments
#user = User.find(params[:id])
#comments = #user.comments
end
...
private
def user_post_params
params.require(:user).permit(:id, posts_attributes: [:title, :content])
end
def comment_params
params.require(:post).permit(:id, comments_attributes: [:content, :user_id])
end
In show_post.html.erb:
<% if #post.user_id == current_user.id %>
<%= link_to 'delete', post, method: :delete, data: { confirm: "you sure?" }
<% end %>
in your routes.rb:
get '/user/:id/new_post' => 'users#new_post', as: :user_new_post
put '/user/:id/create_post' => 'test_takers#create_post', as: :user_create_post
...and similar lines for the other actions.
Hopefully this can get you started...

how to define the admin_user to delete posts? Ruby on rails app

I am trying to get admins to be able to delete postings by users.
This is the code I'm using inside post.html.erb
<% if current_user.admin? %>
<%= link_to "delete", post, method: :delete %>
<% end %>
This is what I have inside the controller
class PostsController < ApplicationController
before_filter :signed_in_user
before_filter :admin_user, only: :destroy
before_filter :correct_user, only: :destroy
def destroy
#post.destroy
redirect_to root_path
end
private
def correct_user
#post = current_user.posts.find_by_id(params[:id])
redirect_to root_path if #post.nil?
end
I'm able to get it working for correct_user, but not for admin. I get this error message
undefined local variable or method `admin_user' for #<PostsController:0x5fda230>
I tried to def admin_user as user.id ==1
private
def admin_user?
current_user && current_user.id == 1
end
But I'm experiencing the same error message.
before_filter :admin_user, only: :destroy
class ApplicationController
def admin_user?
if current_user && current_user.id == 1
return true
else
redirect_to root_url, :error => "Admin only"
return false
end
end
end
Since admin_user? is called from any controller so put it in application controller
class ApplicationController < ActionController::Base
def admin_user?
unless current_user && current_user.admin?
redirect_to root_url
return false
end
end
end
class PostsController < ApplicationController
before_filter :signed_in_user
# before_filter :admin_user?, only: :destroy
before_filter :load_post, :only: :destroy
def destroy
#post.destroy
redirect_to root_url
end
private
def load_post
#post = current_user.admin? ? Post.find(params[:id]) : current_user.posts.find(params[:id])
end
end
UPD:
As I understood from comments you want users to be able to destroy their own posts and admin to be able to destroy any posts. In this case you don't have to check is user admin in before_filter, e.g. remove this line:
before_filter :admin_user?, only: :destroy

Resources