I have some modules to be included in my controller classes. These modules define before_filter:
module BasicFeatures
def filter_method
...
end
def self.included(base)
base.before_filter(:filter_method)
...
end
end
module AdvancedFeatures
include BasicFeatures
...
end
And the classes:
class BasicController < ApplicationController
include BasicFeatures
end
class AdvancedController < ApplicationController
include AdvancedFeatures
end
When BasicFeatures module is included in AdvancedFeatures module, there are no before_filter methods in it.
The AdvancedController didn't get the before_filter call.
I need both my controllers to get the before_filter without any code duplication. I don't know if I am using the best approach so, I'm open to any suggestion.
This is why ActiveSupport::Concern was created.
module BasicFeatures
extend ActiveSupport::Concern
included do
before_filter :require_user
end
def this_is_an_instance_method
'foo'
end
module ClassMethods
def this_is_a_class_method
'bar'
end
end
end
class SomeClass
include BasicFeatures
end
SomeClass.new.this_is_an_instance_method #=> 'foo'
You can also nest them — that is, create concerns that include concerns — and everything will work as expected. And here are the docs.
You can try this. Instead of including the module in AdvancedFeatures, You can include the BasicFeatures module on the class including AdvancedFeatures
module BasicFeatures
def filter_method
#code....
end
#some others basic methods...
def self.included(base)
base.before_filter(:filter_method)
#some other class method calls
end
end
module AdvancedFeatures
def self.included klass
klass.class_eval do
include BasicFeatures
end
end
#some advanced methods
end
Related
Currently in my application I have one helper.rb (Helper module is defined in this file) which is included in my controller.rb file like this:
class Controller
before_action :authenticate_user!
include Helper
Problem is that I need to define one more module e.g. Helper2 and I don't know how to include them using if condition and I don't know if even this solution is possible.
example what I want to do:
class Controller
before_action :authenticate_user!
if variable = 1
include Helper
else
include Helper2
end
Thx for answers!
YAGNI.
There are better ways.
The easist way to make the behavior customizable is to just have a set of methods that can be overridden by classes that consume the module:
module Greeter
def initialize(name)
#name = name
end
def salution
"Hello"
end
def hello
"#{salution}!, my name is #{#name}"
end
end
class Person
include Greeter
end
puts Person.new('Bob').hello # Hello!, my name is Bob
class Dog
include Greeter
def salution
"Woof"
end
end
puts Dog.new('Laika').hello # Woof!, my name is Laika
For more complex tasks there is the "macro method" pattern you'll see all over in Ruby:
module Configurable
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
#options ||= {}
end
end
module ClassMethods
def configure(**kwargs)
#options.merge!(kwargs)
end
def options
#options
end
end
end
class Foo
include Configurable
configure(bar: :baz)
end
puts Foo.options.inspect
# {:bar=>:baz}
This is simply a class method that defines class variables / class instance variables, defines methods or whatever you need to be done. For example these very simplefied API clients:
class Client
include HTTParty
format :json
def answers
self.class.get('/answers')
end
end
class StackoverflowClient < Client
base_uri 'https://stackoverflow.com'
end
class SoftwareEngineeringClient < Client
base_uri 'https://softwareengineering.stackexchange.com'
end
Is it okay to call a private method of a parent class's subclass from a module which is included in the parent class especially when it concerns ApplicationController, Controllers and lib modules in Rails?
Consider if required to change the controller name the method name to reflect the model name(to Article) change.
I feel this is really bad coding and wanted to know what community thinks about this
Example from a Rails Application:
/lib/some_module.rb
module SomeModule
include SomeModuleResource
def filtering_method
calling_method
end
def calling_method
fetch_object
end
end
/lib/some_module_resource.rb
module SomeModuleResource
def fetch_object
note
end
end
/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include SomeModule
before_action :filtering_method
end
/app/controllers/notes_controller.rb
class NotesController < ApplicationController
def show
end
private
def note
#note ||= Note.find(param[:id]))
end
end
I'm of the opinion that this is not necessary bad, although when you expect a certain interface (methods, variables, etc.) from the class that includes the module I would add the following:
module SomeModuleResource
def fetch_object
note
end
private
def note
raise NotImplementedError
end
end
This way, when #note is called without implementing it (because you forgot it was needed or whatever) a NotImplementedError is raised.
Another option is to work around it and create a more general solution. For example, if all controllers behave the same way you described above you can do the following:
module SomeModuleResource
def fetch_object
note
end
private
def note
klass = params[:controller].classify.constantize
instance = klass.find(params[:id])
var_name = "##{klass.underscore}"
instance_variable_set(var_name, instance) unless instance_variable_get(var_name)
end
end
You could also create a class helper method like before_action so that you can pass your own implementation.
module SomeModule
include SomeModuleResource
def self.included(base)
base.extend(ClassMethods)
end
def filtering_method
calling_method
end
def calling_method
fetch_object
end
module ClassMethods
def custom_before_action(&block)
define_method(:note, &block)
private :note
before_action :filtering_method
end
end
end
Now you can use custom_before_filter { #note ||= Note.find(params[:id]) } in every controller (after including).
The above is just to present you with ideas. I'm sure you could find better solution to the problem, but this hopefully points you in the right direction.
See: Alternatives to abstract classes in Ruby?. Or search for abstract classes in Ruby and you'll find more on this subject.
I have some helpers and private methods in a controller, and I want to have the same helpers and private methods in another controller. So I moved that code to module and tried to include the module in the second controller. But I can't seem to do it, because it says undefined method helper method for the DashboardHelper. Is there anyway to accomplish what I am trying to do?
Here is the code
module DashboardHelper
def get_date(log)
end
def get_working_hours(log)
end
helper_method :get_date, :get_working_hours
private
def employee_params
end
def identify_employee
end
def check_is_arrived
end
def calculate_time_percentage
end
end
class AccountController < ApplicationController
include DashboardHelper
end
hello gates you have to include extend ActiveSupport::Concern in your concern .
This should not be in your helper folder instead pull it somewhere in you concern folder
the end file may look like
module DashboardHelper
extend ActiveSupport::Concern
module ClassMethods
def get_date(log)
end
def get_working_hours(log)
end
helper_method :get_date, :get_working_hours
private
def employee_params
end
def identify_employee
end
def check_is_arrived
end
def calculate_time_percentage
end
end
end
I have a gem that contains a method designed to be run as a before_filter in Rails:
before_filter :method_in_gem
It is up to the developer when they want to call this before_filter in their application (i.e I don't want to enforce it on them in any way)
How can I expose this method in a way that the controller is able to pick it up? I have my method in gem_name/lib/controllers.rb
If it's relevant, my gem is being created with bundler.
try the following
module ModuleName
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def meth(args)
before_filter :bf_method
include ModuleName::InstanceMethods
end
end
module InstanceMethods
def bf_method
# ...
end
end
end
then just include the Module in your controller
class ApplicationController < ActionController::Base
include ModuleName
end
I thought I'd come up with a slick way to extend ApplicationController in a Rails 3.x gem.
In my gem's lib/my_namespace/my_controller.rb, I had:
class MyNamespace::MyController < ApplicationController
before_filter :some_method
after_filter :another_method
def initialize
# getting classname of the subclass to use for lookup of the associated model, etc.
# and storing the model_class in an instance variable
# ...
end
# define :some_method, :another_method, etc.
# ...
private
attr_accessor :subclass_defined_during_initialize # etc.
# etc.
end
but when the Gem is loaded, app/controllers/application_controller.rb is not yet loaded, so it fails:
/path/to/rvm/gemset/gems/activesupport-3.2.6/lib/active_support/dependencies.rb:251:
in `require': cannot load such file -- my_gem_name/application_controller (LoadError)
As a workaround, I had defined ApplicationController in my gem's lib/gem_namespace/application_controller.rb as:
class ApplicationController < ActionController::Base
end
I assumed that even though I had defined it there, it would be redefined in my Rails 3 application's app/controllers/application_controller.rb, such that both controllers in the application that extended ApplicationController and controllers that extended MyNamespace::MyController would directly or indirectly extend the ApplicationController defined in app/controllers/application_controller.rb.
However, we noticed that after loading the gem, controllers that extend ApplicationController were unable to access methods defined in app/controllers/application_controller.rb. Also, the ApplicationHelper (app/helpers/application_helper.rb) module was no longer being loaded by other helper modules.
How can I extend ApplicationController within the controller in my gem for the purpose of defining a before_filter and after_filter to and use initialize to access the class's name to determine the associated model's class that it could then store and use within its methods?
Update 2012/10/22:
Here's what I came up with:
In lib/your_gem_name/railtie.rb:
module YourGemsModuleName
class Railtie < Rails::Railtie
initializer "your_gem_name.action_controller" do
ActiveSupport.on_load(:action_controller) do
puts "Extending #{self} with YourGemsModuleName::Controller"
# ActionController::Base gets a method that allows controllers to include the new behavior
include YourGemsModuleName::Controller # ActiveSupport::Concern
end
end
end
and in lib/your_gem_name/controller.rb:
module YourGemsModuleName
module Controller
extend ActiveSupport::Concern
# note: don't specify included or ClassMethods if unused
included do
# anything you would want to do in every controller, for example: add a class attribute
class_attribute :class_attribute_available_on_every_controller, instance_writer: false
end
module ClassMethods
# notice: no self.method_name here, because this is being extended because ActiveSupport::Concern was extended
def make_this_controller_fantastic
before_filter :some_instance_method_available_on_every_controller # to be available on every controller
after_filter :another_instance_method_available_on_every_controller # to be available on every controller
include FantasticStuff
end
end
# instance methods to go on every controller go here
def some_instance_method_available_on_every_controller
puts "a method available on every controller!"
end
def another_instance_method_available_on_every_controller
puts "another method available on every controller!"
end
module FantasticStuff
extend ActiveSupport::Concern
# note: don't specify included or ClassMethods if unused
included do
class_attribute :class_attribute_only_available_on_fantastic_controllers, instance_writer: false
end
module ClassMethods
# class methods available only if make_this_controller_fantastic is specified in the controller
def some_fanastic_class_method
put "a fantastic class method!"
end
end
# instance methods available only if make_this_controller_fantastic is specified in the controller
def some_fantastic_instance_method
puts "a fantastic instance method!"
end
def another_fantastic_instance_method
puts "another fantastic instance method!"
end
end
end
end
For this specific kind of functionality I would recommend creating a module in your gem and include that module in your Application Controller
class ApplicationController < ActionController::Base
include MyCoolModule
end
To add before filters, etc (add this to your module)
def self.included(base)
base.send(:before_filter, my_method)
end
Update: you may be able to just do base.before_filter :my_method which is cleaner.
Here is a Gist
that shows how to access the class of the subclass and store it in an instance variable and access it in the before and after filters. It uses the include method.
Truth is much much simpler and flexible.
Add to lib/engine.rb this: class Engine < Rails::Engine; end
And then simply use:
ActionController::Base.class_eval do
include SomethingFromMineGemModule
# or:
def hello_from_gem
'Hey people!'
end
end
I was able to reference ApplicationController with an initializer callback.
gem code that subclasses/references ApplicationController:
class GemApplicationController < ApplicationController
before_filter :method_to_call
def method_to_call
#your code here
end
end
gem code callback to create subclassed controller:
module GemName
def self.load_gem_application_controller
require "path/to/gem_application_controller"
end
end
rails_app/config/initializers/gem_name.rb
GemName.load_gem_application_controller
Then have controllers that use this functionality subclass GemApplicationController
class SpecialCaseController < GemApplicationController
# this will inherit from the gem's controller,
# which inherits from the rails_app ApplicationController
end