I have made the following validation method:
def if_admin(&block)
if #current_user.administrator?
yield
else
redirect_to '/go_away'
end
end
and i find my classes are increasingly looking like:
class Foo < ApplicationsController
def index
if_admin do
.....
end
end
def show
if_admin do
.....
end
end
def new
if_admin do
.....
end
end
def edit
if_admin do
.....
end
end
.......
end
I want to know if there is anything similar to before_action which would pass the method into the if_admin method, thus DRYing up the code?
Just like you wrote, there is before_action. You can use it like this:
class Foo < ApplicationsController
before_action :if_admin
# ...
private
def if_admin
redirect_to '/go/away' unless #current_user.administrator?
end
end
Related
Doing API for my first Rails project.
I have base class ApiController for all the APIs:
module Api
class ApiController < ::ApplicationController
respond_to :json
skip_before_action :verify_authenticity_token
def index
#collection = resource_class.all
render json: #collection.as_json(as_json_collection)
end
private
def resource_class
raise NotImplementedError
end
def as_json_collection
{}
end
end
end
And I have child class UsersController:
module Api
class UsersController < ApiController
private
def resource_class
User
end
def resource_params
params.require(:user).permit(:name, :email)
end
end
end
My routes:
namespace :api do
resources :users
end
Then I'm going to my_app/api/users I have error:
The action 'index' could not be found for Api::UsersController
But then I changing UsersController writing it's own index class, everything works fine and I'm having all my Users in JSON format.
I've alrady tried to comment all private marks in both classes, but that doesn't help.
I don't want to write an API for every entity in my project and I'd like to avoid this problem in future.
I got it to work with this:
module Api
class ApiController < ::ApplicationController
def index
respond_to do |format|
format.json { render json: '"abc"' }
end
end
end
end
module Api
class UsersController < ApiController
end
end
The URL was http://localhost:3000/api/users.json
So for you I suggest:
module Api
class ApiController < ::ApplicationController
def index
respond_to do |format|
format.json do
#collection = resource_class.all
render json: #collection.as_json(as_json_collection)
end
end
end
end
end
module Api
class UsersController < ApiController
def resource_class
User
end
def resource_params
params.require(:user).permit(:name, :email)
end
end
end
Its supposed to be like this:
class Api::ApiController < ApplicationController
and do not forget to remove extra end, end of the file!
#sample
- api(folder)
-- api_controller.rb (Api::ApiController < ApplicationController)
-- users_controller.rb (Api::UsersController < Api::ApiController)
application_controller.rb
You need to read this my friend:
rails routes
When you do this:
namespace :api do
resources :users
end
rails creates CRUD routes automatically which means that my_app/api/users will translate to: .../users#index.
Do this to see your routes created by rails:
rails routes and for specific word (e.g. user): rails routes | grep user
Seeing is believing ;)
I'm creating a controller in Rails, and I'm looking for ways to have different strong parameters for different controller methods
In update and new actions, I would want to require post
params.require(:post).permit(:body, :is_public, :title, :id)
But in post/index, i don't need to require these parameters.
How do you make different requirements strong parameters for different controller methods?
Your "strong parameters methods" are just Ruby methods. You can have however many you want.
class PostsController < ApplicationController
def create
#post = Post.new(create_params)
end
def update
#post = Post.find(params[:id])
if #post.update(update_params)
# ...
else
# ...
end
end
private
def base_params
params.require(:post)
end
# Don't take IDs from the user for assignment!
def update_params
base_params.permit(:body, :title)
end
def create_params
base_params.permit(:body, :title, :foo, :bar)
end
end
You can also name them whatever you want. Calling it [resource_name]_params is just a scaffolding convention.
Just do something like
class FooController < ApplicationController
def create
#post = Post.new(create_params)
if #post.save
blah
else
blah
end
end
def index
... something else
end
private
def create_params
params.require(:post).permit(:body, :is_public, :title, :id)
end
end
To have a cleaner code I want to split my controller in some concerns.
In my routes.rb how to redirect to concern without redefine the methods of concern index show destroy create ...
class SomeController
include SomeConcern
def index
end
end
module SomeConcern
def index
end
end
Sorry for my bad english.
Lets say we have a CarsController and AirplanesController that have the typical create and new actions.
class AirplanesController < ApplicationController
def new
#airplane = Airplane.new
end
def create
#airplane = Airplane.new(create_params)
if #airplane.save
redirect_to #airplane
else
render :new
end
end
# ...
end
class CarsController < ApplicationController
def new
#car = Car.new
end
def create
#car = Car.new(create_params)
if #car.save
redirect_to #car
else
render :new
end
end
# ...
end
To dry this up we can extract the shared code to a module:
module Createable
extend ActiveSupport::Concern
included do
attr_accessor :resource
alias_attribute :self.controller_name.to_sym, :resource
end
def new
#resource = resource_class.new
yield #resource if block_given?
end
def create
#resource = resource_class.new(create_params)
if #resource.save
yield #resource if block_given?
redirect_to #resource
else
render :new
end
end
private
def create_params
raise "not implemented controller!"
end
def resource_class
#resource_class ||= self.controller_name.classify.constantize
end
end
We can then apply it to the controller classes by:
class CarsController < ApplicationController
include Createable
def create_params
params.require(:car)
.permit(:model) # ...
end
end
class AirplanesController < ApplicationController
include Createable
def create_params
params.require(:airplane)
.permit(:model) # ...
end
end
But a very important point here is that you are not routing to the module. The module is providing methods to the controller class.
You have to always map to your controller. Concerns are modules where you can put shared logic (it makes sense only in case you need 2 absolutely similar methods in 2 different controllers).
I think, that such code should work:
class SomeController
include SomeConcern
end
module SomeConcern
def index
end
end
Isn't it?
But concerns mostly used to move out some private helper methods from controller, rather actions as we do in this code piece
I am referring to my own question Rails Nested Resources with Pundit Allowing Index and finally came up with a working solution but is there not any much better solution defining scope.where(?) or scope.select(?) in the property_policy? How to get all the properties that only belongs to one specific deal using the pundit resolve method?
What I finally did :
properties_controller.rb
class PropertiesController < ApplicationController
before_action :set_deal, except: [:index, :all]
before_action :set_property, only: [:show, :edit, :update, :destroy]
def all
#properties = Property.all
authorize #properties
end
def index
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(Deal)
end
def set_deal
#deal = Deal.find(params[:deal_id])
# pundit ######
authorize #deal
###############
end
(...)
end
property_policy.rb
class PropertyPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all if user.admin?
end
def all?
user_is_admin?
end
def user_is_admin?
user.try(:admin?)
end
(...)
end
What I'd like better:
properties_controller.rb
def index
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(Property) # => for # #properties = #deal.properties
authorize #deal
end
and in the property_policy.rb something like
def resolve
# scope.where(???) if user.admin? # only an admin user can see the #deal.properties
# or any other solution using scope
end
As a reminder 1 deal has many properties and 1 property belongs to one specific deal. My routes are nested deals/id/properties except for the full list of properties I have simple "/properties". Thanks a lot for helping.
** UPDATE **
I finally went for
properties_controller.rb
def index
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(#deal.properties)
authorize #properties, :index?
end
and in property_policy.rb
class PropertyPolicy < ApplicationPolicy
class Scope < Scope
def resolve
user.admin? ? scope.all : scope.none
end
end
def index?
user_is_admin?
end
def user_is_admin?
user.try(:admin?)
end
end
Not sure if it is the proper way
What you want to do is pass a scope to the policy - not just a class.
#properties = policy_scope(#deal.policies)
class PropertiesPolicy
class Scope < Scope
def resolve
user.admin? ? scope.all : scope.none
end
end
end
Another problem with your controller is that authorize #deal will call DealsPolicy#index? which is not what you want.
To authorize an index action you want to call authorize with the model class (and not an instance):
def index
authorize Property # calls PropertiesPolicy#index?
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(#deal.properties)
end
In that case you don't have to do anything special in your Scope#resolve method really. Just return scope since you can assume at that point that the user is an admin.
Say I have the following controller:
class FooController < ApplicationController
def show
end
def a
foo = Foo.find(params[:id])
foo.a
redirect_to foo_url(foo)
end
def b
foo = Foo.find(params[:id])
foo.b
redirect_to foo_url(foo)
end
def c
foo = Foo.find(params[:id])
foo.c
redirect_to foo_url(foo)
end
end
Is there anyway to get a after_filter to perform the shared redirect code?
Try this:
class FooController < ApplicationController
def show
end
[:a, :b, :c].each do |name|
define_method(name) do
foo = Foo.find(params[:id])
foo.send(:name)
redirect_to foo_url(foo)
end
end
end
An after_filter will not work in this situation.
I would use the following approach.
class FooController < ApplicationController
before_filter :get_foo, :only => [:a, :b, :c]
def show
end
def a
do_and_redirect(:a)
end
def b
do_and_redirect(:b)
end
def c
do_and_redirect(:c)
end
private
def get_foo
#foo = Foo.find(params[:id])
end
def do_and_redirect(method_name)
#foo.send(method_name)
redirect_to foo_url(#foo)
end
end
Here's your code refactored:
class FooController < ApplicationController
before_filter :get_foo, :except => [:show]
def show
end
def a
#foo.a
redirect_to #foo
end
def b
#foo.b
redirect_to #foo
end
def c
#foo.c
redirect_to #foo
end
private
def get_foo
#foo = Foo.find(params[:id])
end
end
I haven't tried it, but http://rails.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html#M000319 says you can use the after_filter.
Leave it where it is. after_filters won't work in this case.