How can I access a controller's instance variable inside of a model?
In this example, I want to access the instance variable #user inside of the model.
Post Controller
class PostController < ApplicationController
def destroy
#user = User.find(params[:user_id])
post = Post.find(params[:id])
end
end
Post Model
class Post < ActiveRecord::Base
belongs_to :user
before_destroy :check_if_owned_by_user
if self.user != #user
return false
end
end
end
you cannot access #user in the post model. the callbacks do not accept parameters as they triggered automatically. you have to explicitly call the method from the controller. like this.
class PostController < ApplicationController
def destroy
#user = User.find(params[:user_id])
post = Post.find(params[:id])
if post.check_if_owned_by_user(#user)
#delete it
end
end
end
class Post < ActiveRecord::Base
belongs_to :user
def check_if_owned_by_user(user)
self.user == user
end
end
Checking whether the current user is allowed to delete an object is an authorization concern, and should be handled within your authorization method(eg. cancan).
If you must handle it manually, consider using a service object to achieve the same. Callbacks are messy things once they accumulate.
class DestroyPost
def initialize post, user
#post = post
#user = user
end
def call
return false unless #post.user = #user
#post.destroy
end
end
You'd call DestroyPost.new(#post, #user).call in the controller instead of #post.destroy
Related
I have created a user controller with login and logout.
After login user should be able to give some comment in text box input and it should be saved in db.
How to associate the comment to the user. My users controller is
class UsersController < ApplicationController
def new
end
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to '/url'
else
redirect_to '/signup'
end
end
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
my urls controller is
class UrlsController < ApplicationController
def new
end
def create
url = Url.new(url_params)
url.save
redirect_to #url
end
def url_params
params.require(:url).permit(:url)
end
end
I am getting error in url_params. How it should be for a text field?
For example you need to create Comment model
class Comment < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
has_many :comments
end
and create CommentsController
class CommentsController < ApplicationController
before_action :set_user
def create
#user.comments.create(comment_params)
redirect_to root_url
end
private
def set_user
#user = User.find(session[:user_id])
end
def comment_params
params.require(:comment).permit(:text)
end
end
How to create Policies for API-Controller's using Pundit gem?
Api controller path: /app/controllers/api/posts_controller.rb
#posts_controller.rb
class Api::PostsController < ApplicationController
def create
......
end
def update
......
end
def delete
......
end
end
I have Controller for the same and the corresponding Model
controller path: /controllers/posts_controller.rb
#posts_controller.rb
class PostsController < ApplicationController
def create
......
end
def update
......
end
def delete
......
end
end
I have created the policies for posts controller. How to create the same for API's Controller
Pundit is resource-based, not controller-based. When you call authorize and pass it a resource, Pundit cares about the action name and the resource type, but it does not care about the controller name.
Regardless of whether you call from the Api::PostsController:
# /app/controllers/api/posts_controller.rb
class Api::PostsController < ApplicationController
def create
#post = Post.find(params[:post_id])
authorize #post
end
end
or from your original PostsController:
# /app/controllers/posts_controller.rb
class PostsController < ApplicationController
def create
#post = Post.find(params[:post_id])
authorize #post
end
end
So long as #post is a member of the Post class, you can call authorize #post from the controller of a parent or child or a completely unrelated controller, it doesn't matter. In all cases Pundit will go and look for a method called create? within app/policies/post_policy:
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
#user = user
#post = post
end
def create?
user.present?
end
end
More specifically, if I have two options: do a check in the controller or override an association method in the model, which one should I prefer?
Edit:
class Book < ApplicationRecord
belongs_to :author
def author
super || build_author
end
end
Is the code above ok or should I prefer another solution like the one below?
class BooksController < ApplicationController
def update
set_author
if #book.update_attributes(params[:book])
#redirect
else
#render show page - unprocessable entity
end
end
def set_author
a = Author.where(fields)
#book.author = a || #book.build_author
end
end
I am creating a webiste where people can debate with each other. It has 4 main models - post, for_the_motion, against_the_motion, and user( added in the respective order). I ran a migration and made a association between for model and against model.
For each view in "for" model I want to show which user added that particular motion. But I am getting an error
undefined method `image_url' for nil:NilClass
Stuck from long time on this. This is how the models look
user.rb
class User < ApplicationRecord
has_many :posts
has_many :fors
has_many :againsts
class << self
def from_omniauth(auth_hash)
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.name = auth_hash['info']['name']
user.image_url = auth_hash['info']['image']
user.url = auth_hash['info']['urls'][user.provider.capitalize]
user.save!
user
end
end
end
for.rb
class For < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user,optional: true
end
post.rb
class Post < ApplicationRecord
has_many :fors, dependent: :destroy
has_many :againsts, dependent: :destroy
belongs_to :user, optional: true
end
against.rb
class Against < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user, optional:true
end
CONTROLLERS
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def land
end
def show
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
redirect_to #post
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title)
end
end
fors_controller.rb
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
#for.user = current_user
redirect_to post_path(#post)
end
private
def fors_params
params.require(:for).permit(:content)
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
begin
#user = User.from_omniauth(request.env['omniauth.auth'])
session[:user_id] = #user.id
# flash[:success] = "Welcome, #{#user.name}!"
rescue
# flash[:warning] = "There was an error while trying to authenticate you..."
end
redirect_to root_path
def destroy
if current_user
session.delete(:user_id)
# flash[:success] = 'See you!'
end
redirect_to root_path
end
end
end
This is where I am getting the error
<h1><%=#post.title%></h1>
<div class="fort">
<h3>For the motion</h3>
<%#post.fors.each do |f|%>
<p><%=f.content%></p>
<p><%=f.user.image_url%></p>/*This is where errors arise*/
<%end%>
<%= render "fors/form"%>
</div>
<div class="against">
<h3>Against the motion</h3>
<%#post.againsts.each do |f|%>
<p><%=f.content%></p>
<p><%= #post.user.name%></p>
<%end%>
<%= render "againsts/form"%>
</div>
Here is the github link for any other required information
https://github.com/sarfrazbaig/DebatingSociety2
Seems like you missed saving the .user on fors_controller.rb:
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
# .create above already will save a new For record in DB
# therefore your #for.user assignation will be only assigned in memory, but not yet in DB
#for.user = current_user
# you'll need to save it again afterwards:
#for.save
redirect_to post_path(#post)
end
# ...
end
Suggestion:
use .new instead of .create to not-yet-save into the DB, and only call save when everything that you need to assign is already assigned.
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.new(fors_params)
#for.user = current_user
#for.save
redirect_to post_path(#post)
end
# ...
end
Take note that you would still encounter that error even if you already updated your code with the above; this is because currently your For records in the DB all are missing the .user value. You'll have to manually assign and save the .user accordingly for each For record, and probably best that you'd write a...
class For < ApplicationRecord
validates :user, presence: true
end
... validation so that this error will be prevented in the future.
One of the #post.fors is lacking a user, which is permitted by the belongs_to :user, optional: true in your For model.
You can restrict your query to showing only fors that have an associated user:
#post.fors.joins(:users) or you can use the safe navigation operator to return nil when attempting to read the image_url for a non-existent user - f.user&.image_url
I have 2 controller, 1 for user and 1 for admin.
controllers/articles_controller.rb
class ArticlesController < ActionController::Base
...
def show
#article = Article.find(parmas[:id])
authorize #article
end
...
end
controllers/admin/articles_controller.rb
class Admin::ArticlesController < AdminController
...
def show
#article = Article.find(parmas[:id])
authorize #article
end
...
end
And i have 2 file policy
policies/article_policy.rb
class ArticlePolicy
extend ActiveSupport::Autoload
autoload :Admin
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def show?
# allow show for every user.
true
end
end
And one file policies/admin/article_policy.rb
class Admin::ArticlePolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def show?
# only show if use have role manager
user.manager?
end
end
but when i use a account user to show articles at /admin/articles/1/. It show normaly, Should is "Access denied".
How to fix this? (I use gem pundit 1.10).
Use the authorize method to pass the namespace as a parameter.
class ArticlesController < ActionController::Base
...
def show
#article = Article.find(parmas[:id])
authorize [:admin, #article]
end
...
end