Rails and before_action with different conditionals - ruby

I have these before_actions set in my Controller:
before_action :require_user, unless: :public?, only: [:show]
before_action :require_user, only: [:index, :edit, :update]
Basically I am trying execute the filter required_front_user in the show action only if the public? is false.
for the rest of actions I want the filter to be executed always.
Looks like the first before_action set up is ignored and totally override by the second set up.
Is it possible to combine both combinations using before_action statements or do I have to implement this logic in the filter it self?
Update
This also doesn't work:
before_action :require_user, if: :public?, only: [:index, :edit, :update]
before_action :require_user, unless: :public?, only: [:show, :index, :edit, :update]
I thought if public? returns true the first set up will be loaded, and if false the second set up will be loaded. It happens that only the second set up is loaded and if public? == true the before_action is never triggered
Update 2
This is what I found it works:
before_action :require_user_when_public, if: :public?, only: [:index, :edit, :update]
before_action :require_user_when_no_public, unless: :public?, only: [:show, :index, :edit, :update]
protected
def require_user_when_public
require_user
end
def require_user_when_no_public
require_user
end
Which is very ugly :/

before_action :require_user, only: require_user_before_actions
private
def require_user_before_actions
actions = [:index, :edit, :update]
actions << :show unless public?
actions
end

The most clean way I have found is:
before_action :require_user, only: [:index, :edit, :update]
before_action :require_user_when_no_public, unless: :public?, only: [:show]
protected
def require_user_when_no_public
require_user
end

I've only tested this a tiny bit so not super sure it'll work, but maybe something like:
before_action :require_user, if: ->(c) {
[:index, :edit, :update, !public? && :show].include?(c.action_name.to_sym)
}
As a possibly dumb / broken (that seems to work in a basic test for me as far as I can tell) alternative, possibly something like:
class <<self
alias_method :old_before_action, :before_action
end
def self.before_action(*callbacks)
options = callbacks.extract_options!
if options[:only] && options[:only].is_a?(Proc)
only_proc = options.delete(:only)
if_proc = ->(c) { Array(only_proc.(c)).reject(&:blank?).map(&:to_s).to_set.include? c.action_name }
options[:if] = Array(options[:if]).unshift(if_proc)
end
old_before_action(*callbacks, options)
end
before_action :require_user, only: ->(c) { [:index, :edit, :update, !public? && :show] }

Related

Trying to get reviews for a specfic game

Im attempting to see reviews for a specific game that ive created. Right now my reviews/index show for all games, not just the specific game im looking for. Im assuming its just something im missing in my params but not sure...
these my are controllers...
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except:[:index, :show]
before_action :correct_user, only: [:edit, :update, :destroy]
def index
#reviews = Review.all
end
def show
#review = Review.find(params[:id])
end
...
class GamesController < ApplicationController
before_action :set_game, only: [:show, :edit, :update, :destroy]
def index
#games = Game.all
end
def show
end
my routes ...
reviews#index
POST /games/:game_id/reviews(.:format) reviews#create
new_game_review GET /games/:game_id/reviews/new(.:format) reviews#new
edit_game_review GET /games/:game_id/reviews/:id/edit(.:format) reviews#edit
game_review GET /games/:game_id/reviews/:id(.:format) reviews#show
PATCH /games/:game_id/reviews/:id(.:format) reviews#update
PUT /games/:game_id/reviews/:id(.:format) reviews#update
DELETE /games/:game_id/reviews/:id(.:format) reviews#destroy
games GET /games(.:format) games#index
POST /games(.:format) games#create
new_game GET /games/new(.:format) games#new
edit_game GET /games/:id/edit(.:format) games#edit
game GET /games/:id(.:format) games#show
PATCH /games/:id(.:format) games#update
PUT /games/:id(.:format) games#update
DELETE /games/:id(.:format) games#destroy
root GET / games#index
this is how im attempting to do my link_to but its showing all the games reviews in my seeds not just a single game and all its reviews...
<%= link_to 'reviews', game_reviews_path(game_id: #game.id), #reviews %>
first RoR prj on my own, any help would be great!!
thanks!
The reason is not working is because you are not using the game_id you are passing to the ReviewsController. You need to query for Reviews belonging to the specific Game.
def index
#reviews = Review.where(game_id: params[:game_id]).all
end

Pundit with second devise model

I manage the authorization of users in my app with the pundit gem. Everything works fine for the user. Now I created a second devise model: Employers. I want to show a specific page to both logged in user as well as logged in employers. How do I do that?
Here is my policy for the model:
class CurriculumPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def create?
return true
end
def show?
record.user == user || user.admin
end
def update?
record.user == user || user.admin
end
def destroy?
record.user == user || user.admin
end
end
And here is my controller for the index page which I want to make accessible:
class CurriculumsController < ApplicationController
skip_before_action :authenticate_user!, only: [:new, :create, :index]
before_action :set_curriculum, only: [:show, :edit, :update, :destroy]
def index
# #curriculums = policy_scope(Curriculum).order(created_at: :desc)
if params[:query]
#curriculums = policy_scope(Curriculum).joins(:user)
.where('users.job_category ILIKE ?', "%#{params[:query]}%")
.where(
'job_category ILIKE :query', query: "%#{params[:query]}%"
)
else
#curriculums = policy_scope(Curriculum).order(created_at: :desc)
end
end
private
def set_curriculum
#curriculum = Curriculum.find(params[:id])
end
def curriculum_params
params.require(:curriculum).permit(:doc)
end
end
You can have workaround here like below for each actions
def show?
true if #user.class.table_name == "employees"
end

irb does not have access to my models (NameError: uninitialized constant)

I am receiving this error when trying to create a new 'Pin' in IRB. For example:
irb(main):001:0> #pin = Pin.first
NameError: uninitialized constant Pin
OR
irb(main):001:0> #pin = Pin.new
NameError: uninitialized constant Pin
I must of changed something as it was working before. Unfortunately, I cannot find the error
Here is my pins controller:
class PinsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :correct_user, only: [:edit, :update, :destroy]
before_action :set_pin, only: [:show, :edit, :update, :destroy]
def index
#pins = Pin.all
end
def show
#pin = Pin.find params[:id]
end
def new
#pin = Pin.new
end
def edit
end
def create
#pin = Pin.new(pin_params)
if #pin.save
redirect_to #pin, notice: 'Pin was successfully created.'
else
render action: 'new'
end
end
def update
if #pin.update(pin_params)
redirect_to #pin, notice: 'Pin was successfully updated.'
else
render action: 'edit'
end
end
def destroy
#pin.destroy
redirect_to pins_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_pin
#pin = Pin.find(params[:id])
end
def correct_user
#pin = current_user.pins.find(params[:id])
redirect_to pins_path, notice: "Not allowed!" if #pin.nil?
end
# Never trust parameters from the scary internet, only allow the white list through.
def pin_params
params.require(:pin).permit(:description)
end
end
Here is are my associations, pin.rb
class Pin < ApplicationRecord
belongs_to :user
end
And my associations for User.rb:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :pins
end
and my routes
Rails.application.routes.draw do
resources :pins
devise_for :users
root "pages#home"
get "about" => "pages#about"
end
irb does not automatically load your Rails environment, which is why it does not have access to your models (or helpers, or database, or anything else). However, the "rails console" is an irb session that does load all of your Rails classes, database connections, etc.
To start the rails console:
rails c
Which is shorthand for:
rails console
This starts the rails console for your development environment. You can make it connect to your test environment:
rails c RAILS_ENV=test
or to your production environment:
rails c RAILS_ENV=production

#project.impressionist_count - wrong number of arguments (given 2, expected 0..1)

I use rails 5
I want to count the number of views of the project
I installed gem 'impressionist'
Generate the impressions table migration
rails g impressionist
Run the migration
rake db:migrate
In view projects/show.html.erb
<%= #project.impressionist_count %>
Displayed error on line <%= #project.impressionist_count %> wrong number of arguments (given 2, expected 0..1) I am at a loss to identify the cause of the error.
Logs:
app/views/projects/show.html.erb:89:in `_app_views_projects_show_html_erb___149972252570834158_69970117978380'
Rendering /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb within rescues/layout
Rendering /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (8.9ms)
Rendering /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (5.1ms)
Rendering /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.7ms)
Rendered /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.rc1/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb within rescues/layout (121.7ms)
DEPRECATION WARNING: #original_exception is deprecated. Use #cause instead. (called from status_code_with_paginate at /usr/local/rvm/gems/ruby-2.3.0/gems/will_paginate-3.1.0/lib/will_paginate/railtie.rb:49)
In rails console:
pry(main)> project.impressionist_count
ArgumentError: wrong number of arguments (given 2, expected 0..1)
from /usr/local/rvm/gems/ruby-2.3.0/gems/activerecord-5.0.0.rc1/lib/active_record/associations/collection_proxy.rb:731:in `count'
Model Project
class Project < ActiveRecord::Base
..........................
is_impressionable
.............................
end
ProjectsController
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update]
before_action :authenticate_user!, only: [:new, :edit]
before_action :require_permission, only: [:edit]
before_action :set_freelancer, only: [:show]
before_action :set_response_current_user, only: :show, if: :exist_response?
after_action :verify_authorized, only: [:update]
impressionist :actions => [:show]
def show
#new_response = #project.responses.build
impressionist(#project)
end
...............................................
end
Your model should be like:
class Project < ActiveRecord::Base
..........................
include Impressionist::IsImpressionable
is_impressionable
.............................
end
In controller:
def show
#new_response = #project.responses.build
# impressionist(#project) //comment out this line
end

Do I have to specify actions in routes.rb?

While I'm reading Rails 4 in Action, I'm trying to implement my own application, so it doesn't look same as in the book.
The book's corresponding commit is Section 7.2.3: Only admins can create or delete projects
In my case, admins can only delete the item (item corresponds to the project in the book.).
My repo https://github.com/tenzan/shop and deployed http://ichiba-demo.herokuapp.com/
The rule I want to apply is:
A regular user (you can login with staff#example.com/password) can do everything except destroy action.
Admin (admin#example.com/password) only can destroy.
To realise that I have:
In admin/items_controller.rb:
class Admin::ItemsController < Admin::ApplicationController
def destroy
#item = Item.find(params[:id])
#item.destroy
flash[:notice] = 'Item has been deleted.'
redirect_to items_path
end
private
def item_params
params.require(:item).permit(:name, :quantity)
end
end
In controllers/items_controller.rb:
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update]
def index
#items = Item.all
end
def new
#item = Item.new
end
def create
#item = Item.new(item_params)
if #item.save
flash[:notice] = 'Item has been created.'
redirect_to #item
else
flash.now[:alert] = 'Item has not been created.'
render 'new'
end
end
def show
end
def edit
end
def update
if #item.update(item_params)
flash[:notice] = 'Item has been updated.'
redirect_to #item
else
flash.now[:alert] = 'Item has not been updated.'
render 'edit'
end
end
private
def set_item
#item = Item.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = 'The item could not be found.'
redirect_to items_path
end
def item_params
params.require(:item).permit(:name, :quantity)
end
end
In routes.rb:
Rails.application.routes.draw do
namespace :admin do
root 'application#index'
resources :items, only: :destroy
end
devise_for :users
root 'items#index'
resources :items, only: [:index, :show, :edit, :update, :new, :create] do
resources :comments
end
end
Questions:
Do I have to specify actions in the routes.rb, as I already have specified who can use what actions in their corresponding controllers? I didn't notice any change when I remove them from the routes.rb...
Am I violating DRY concept, when I specify actions in 2 places, i.e. in the routes.rb and controllers/items_controllers.rb ?
I'll be happy if you point out other places to improve to meet best practice.
PS: The subject maybe vague, please feel free to edit it.
Do I have to specify actions in the routes.rb, as I already have
specified who can use what actions in their corresponding controllers?
Yes. For instance, if you'd have only one action in items_controller.rb controller (let say show), and left
resources :items do # no specified actions
#...
end
in routes.rb it would generate all routes for items controller (for new, create, edit, destroy, update etc). But specifying actions in routes.rb you limit generated routes to only needed.
Am I violating DRY concept, when I specify actions in 2 places, i.e.
in the routes.rb and controllers/items_controllers.rb ?
No. Because you actually specify actions in controller, in routes.rb you only specify routes.
I'll be happy if you point out other places to improve to meet best
practice.
This line:
resources :items, only: [:index, :show, :edit, :update, :new, :create] # better to use %i() notation, eg only: %i(index show edit update new create)
could be written as:
resources :items, except: :destroy
Regarding your admin user - to allow only him to destroy, just check if current_user is admin. If you'll have more, than one action which is allowed to be performed by only admin, you can create before_action in controller:
before_action :check_admin?, only: %i(destroy another_action)
private
def check_admin?
# your logic to check, if user is admin
end
You can also be interested in going through Ruby style guide.
Even though you're not violating DRY directly, you're muddying up the REST architecture by moving a single entity's actions to different controllers. You don't need a specific controller or namespace for admins - you just need to assert that the user is an administrator before proceeding with the delete action.
Since you have already added the admin column to your devise model, you can move the delete action to ItemsController
def destroy
if current_user.try(:admin?)
#item = Item.find(params[:id])
#item.destroy
flash[:notice] = 'Item has been deleted.'
else
flash[:alert] = 'Only admins can delete items.'
end
redirect_to items_path
end
Your routes would be cleaner since your admin namespace would be used only for user moderation. The only route for items would be:
resources :items do
resources :comments
end

Resources