How to add macro for controller? - ruby-on-rails

I want to add macro to controller like active record :has_many
class ApplicationController < ActionController::Base
macro_to_define_methods :parameters => :here
end
How can I achieve this?
Thank You.

module ControllerExtensions
def do_something(options)
# do something with options here, e.g. define_method
end
end
ActionController::Base.extend(ControllerExtensions)
class ApplicationController < ActionController::Base
do_something :parameters => :here
end

Related

Use the same `before_action` filter in multiple controllers

In a Rails app, I have a before_action filter that sends a webhook message. Since I have a few controllers that I want the before_action to act on, is there a good way to make it a module and prepend it?
My current logic is:
# first_controller.rb:
before_action :do_something
def do_something
#same logic
end
# second_controller.rb:
before_action :do_something
def do_something
#same logic
end
# third_controller.rb:
before_action :do_something
def do_something
#same logic
end
If your controllers inherit from ApplicationController, You can do the following:
class ApplicationController < ActionController::Base
def do_something
#same logic
end
end
class FirstController < ApplicationController
before_action :do_something
end
class SecondController < ApplicationController
before_action :do_something
end
class ThirdController < ApplicationController
before_action :do_something
end
Or you can make your own parent controller, eg. DoSomethingController
class DoSomethingController < ApplicationController
before_action do_something
def do_something
#same logic
end
end
class FirstController < DoSomethingController
end
class SecondController < DoSomethingController
end
class ThirdController < DoSomethingController
end
Or you can use #code_aks's answer https://stackoverflow.com/a/59846330/8554172 to make a module and include it.
Yes, it is good to use DRY here. If your controllers do have same parent class you can place that method in there. If not it is good practice to move this method to the module and include it with reusing.
You can try below code write the methods in a controller concern. For example:
# app/controllers/concerns/example_concern.rb
module ExampleConcern
extend ActiveSupport::Concern
protected
def before_filter_1
puts "from first before_filter_method"
end
def before_filter_2
puts "from second before_filter_method"
end
end
Now in the controller, include the module in the concern and call the methods using before_action as required. For example:
# app/controllers/examples_controller.rb
class ExamplesController < ApplicationController
include ExampleConcern
before_action :before_filter_1, only: [:action_a, :action_b, :action_c]
before_action :before_filter_2, only: [:action_d, :action_e]
def action_a
end
def action_b
end
def action_c
end
def action_d
end
def action_e
end
end
Hope this will help you. :)

Add functions to the callback's condition list in the module

I have:
a controller with before_filter callback defined (UPDATE : controller is in rails application and I can't modify it)
a module with some new functions, which is added to the controller (UPDATE : module is in my plugin)
I want this callback to execute before those functions too.
class MyController < ApplicationController
before_filter :prepare, :only => :show
def show
end
def edit
end
private
def prepare
end
end
module MyModule
def self.included(base)
base.send(:include, InstanceMethods)
base.before_filter :prepare, :only => [:a, :b]
end
module InstanceMethods
def a
...
end
def b
...
end
end
end
If I do it this way, the list will be redefined, and "prepare" wouldn't be called before "show".
The options I see:
def self.included(base)
base.send(:include, InstanceMethods)
base.before_filter :prepare, :only => [:a, :b, :show]
end
or
def a
prepare
...
end
def b
prepare
...
end
or
module MyModule
def self.included(base)
base.send(:include, InstanceMethods)
base.before_filter :prep, :only => [:a, :b]
end
module InstanceMethods
def prep
prepare
end
...
end
end
are SO ugly and un-DRY.
Is there any natural way to do it?
Perhaps you should
before_filter :prepare, :except => [:foo, :bar, :etc]
Which I think will pick up :a and :b.
BTW, your controller seems like it might be pretty fat. Perhaps, also, an opportunity for refactoring?
Perhaps you are looking for something like this:
module MyModule
def self.included(base)
base.send(:include, InstanceMethods)
base.extend ClassMethods
end
module ClassMethods
def prepare(*actions)
# optional - add defaults
actions = ([:a, :b] + (actions || [])).uniq
# self here is the singelton class object
self.instance_eval do
before_filter :prep, only: actions
end
end
end
module InstanceMethods
def prep
prepare
end
...
end
end
We create a prepare class method which takes a splat and then evaluates a before_filter with the splat of actions merged with defaults.
class ThingsController < ApplicationController
include MyModule
prepare :show
end

Share some before_filters between controllers

I have two parent controllers, one for an API, one for normal html pages.
class ApplicationController < ActionController::Base
...
end
class ApiController < ActionController::Metal
include AbstractController::Callbacks
...
end
I want to share some before_filter on both controllers. I tried something like this:
class ApplicationController < ActionController::Base
include MyFilters
end
class ApiController < ActionController::Metal
include MyFilters
end
module MyFilters
before_filter :filter1
before_filter :filter2
def filter1
end
def filter2
end
...
end
And also this:
module MyFilters
def self.included(klass)
klass.before_filter :filter1
klass.before_filter :filter2
end
def filter1
end
def filter2
end
...
end
But in both cases I receive:
undefined method `before_filter' for MyFilters:Module
What is the correct way to implement this?
You could use a concern for that:
# app/controllers/concerns/my_filters.rb
module MyFilters
extend ActiveSupport::Concern
included do
before_filter :filter1
before_filter :filter2
end
def filter1
end
def filter1
end
end
Use them through a standard include in your controllers:
class ApplicationController < ActionController::Base
include MyFilters
end
class ApiController < ActionController::Metal
include MyFilters
end

load_and_authorize_resource with collection and custom model

I have controller with many custom actions:
class FooController < ApplicationController
def fizz
end
def buzz
end
...
end
And I want to load and authorize Bar collection (without repeating #bars = Bar.all) into these two methods.
Something like that:
load_and_authorize_resource :bar, :collection, :only => [:fizz, :buzz, ...]
But this simply doesn't work, #bars variable is nil in the all actions. Please, help me to understand what is going wrong and how can I make it work. Thanks!
The following worked for me (source):
load_and_authorize_resource :bar, :parent => false
You could use a before_filter in your controller:
before_filter :load_and_authorize_resource, :only => [:fizz, :buzz]
private
def load_and_authorize_resource
#bars = Bar.all
end

before_filter :require_owner

I have a number of resources (Trips, Schedules, etc) with actions that should be limited to just the resource's owner.
How do you implement code with a #require_owner method defined in ApplicationController to achieve this? Ideally, the code will look up the inheritance chain for the owner so the before_filter will work on a :comment that belongs_to :trip that belongs_to :user.
class TripsController < ApplicationController
belongs_to :member
before_filter :require_owner
...
end
I don't fully follow the description (would a comment really be owned by the trip owner?), but expanding slightly on jonnii's answer, here is an example that restricts the trip controller:
class ApplicationController < ActionController::Base
...
protected
# relies on the presence of an instance variable named after the controller
def require_owner
object = instance_variable_get("##{self.controller_name.singularize}")
unless current_user && object.is_owned_by?(current_user)
resond_to do |format|
format.html { render :text => "Not Allowed", :status => :forbidden }
end
end
end
end
class TripsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
# only require these filters for actions that act on single resources
before_filter :get_trip, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_trip
#trip = Trip.find(params[:id])
end
end
Assuming the model looks like this:
class Trip < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
...
def is_owned_by?(agent)
self.owner == agent
# or, if you can safely assume the agent is always a User, you can
# avoid the additional user query:
# self.owner_id == agent.id
end
end
The login_required method (provided by or relying on an auth plugin like restful_authentication or authlogic) makes sure that the user is logged in and provides the user with a current_user method, get_trip sets the trip instance variable which is then checked in require_owner.
This same pattern can be adapted to just about any other resource, provided the model has implemented the is_owned_by? method. If you are trying to check it when the resource is a comment, then you'd be in the CommentsController:
class CommentsController < ApplicationController
before_filter :login_required # using restful_authentication, for example
before_filter :get_comment, :only => [:show, :edit, :update, :destroy]
before_filter :require_owner, :only => [:show, :edit, :update, :destroy]
...
protected
def get_comment
#comment = Comment.find(params[:id])
end
end
with a Comment model that looks like:
class Comment < ActiveRecord::Base
belongs_to :trip
# either
# delegate :is_owned_by?, :to => :trip
# or the long way:
def is_owned_by?(agent)
self.trip.is_owned_by?(agent)
end
end
Make sure to check the logs as you are doing this since association-dependent checks can balloon into a lot of queries if you aren't careful.
There's a few different ways to do this. You should definitely check out the acl9 plugin (https://github.com/be9/acl9/wiki/tutorial:-securing-a-controller).
If you decide you want to do this yourself, I'd suggest doing something like:
class Trip < ...
def owned_by?(user)
self.user == user
end
end
class Comment < ...
delegate :owned_by?, :to => :trip
end
# in your comment controller, for example
before_filter :find_comment
before_filter :require_owner
def require_owner
redirect_unless_owner_of(#commemt)
end
# in your application controller
def redirect_unless_owner_of(model)
redirect_to root_url unless model.owned_by?(current_user)
end
Forgive me if there are any syntax errors =) I hope this helps!
Acl9 is a authorization plugin. I'd give you the link, but I don't have cut and paste on my iPhone. If no one else provides the link by the time I get to a computer, I'll get it for you. Or you can google. Whichever. :)
I have only just started using it, but it has an extremely simple interface. You just have to create a roles table and a roles_user. Let me know how it goes if you decide to use it.
Or just use inherited resources:
InheritedResources also introduces another method called begin_of_association_chain. It’s mostly used when you want to create resources based on the #current_user and you have urls like “account/projects”. In such cases you have to do #current_user.projects.find or #current_user.projects.build in your actions.
You can deal with it just by doing:
class ProjectsController < InheritedResources::Base
protected
def begin_of_association_chain
#current_user
end
end

Resources