Rails shared controller actions - ruby-on-rails

I am having trouble building a controller concern. I would like the concern to extend the classes available actions.
Given I have the controller 'SamplesController'
class SamplesController < ApplicationController
include Searchable
perform_search_on(Sample, handle: [ClothingType, Company, Collection, Color])
end
I include the module 'Searchable'
module Searchable
extend ActiveSupport::Concern
module ClassMethods
def perform_search_on(klass, associations = {})
.............
end
def filter
respond_to do |format|
format.json { render 'api/search/filters.json' }
end
end
end
end
and, despite setting up a route i get the error 'The action 'filter' could not be found for SamplesController'.
I thought it might be to do with wether I include, or extend the module. I tried using extend but that also gave the same error.
I still need to be able to feed the module some configuration options on a per controller basis. Is it possible to achieve what I am trying to do here?
Any help appreciated, thanks

You should pass actions to the included block and perform_search_on to the class_methods block.
module Searchable
extend ActiveSupport::Concern
class_methods do
def perform_search_on(klass, associations = {})
.............
end
end
included do
def filter
respond_to do |format|
format.json { render 'api/search/filters.json' }
end
end
end
end
When your Searchable module include a method perform_search_on and the filter action.

Try removing the methods from the module ClassMethods. That is making them instance methods.

Related

Is it okay to call a private method of a parent class's subclass from a module which is included in the parent class in rails?

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.

How can I redirect from a Module?

I tried TO Google Can I redirect_to in rails modules but couldn't come up with anything. Basically, I have a method that I am going to use across a couple of Controllers.
lib/route_module.rb
module RouteModule
def self.user_has_active_chocolate(c_id, u_id)
chocolate_id = c_id
user_id = u_id
unless UserChocolate.where(id: chocolate_id).empty?
if UserChocolate.where(id: chocolate_id).last.active?
true
else
false
# BREAKS OVER HERE...
redirect_to "/user/new-chocolate/#{user_id}"
end
else
false
redirect_to "/admin"
end
end
end
app/controllers/user_controllers.rb
include RouteModule
before_filter :thingz, only: [:display_user_chocolate]
# private
def thingz
RouteModule.user_has_active_chocolate(params["chocolate_id"], params["user_id"])
end
But... whenever I run this... It will break as soon as it hit's redirect_to.
undefined method `redirect_to' for RouteModule:Module
My other option is use ActiveSupport::Concerns but I just trouble converting this Module into a Concern.
When you include a module, it acts as a mixin. That said, you include and get all the methods of the module in the context of your class. The proper way would be:
module RouteModule
def user_has_active_chocolate(c_id, u_id) # NO self
...
end
end
And in the class:
include RouteModule
def thingz
# NO module method call (NO RouteModule)
user_has_active_chocolate(params["chocolate_id"], params["user_id"])
end

Register a different type of callback in ApplicationController

I need more fine grain control on the order of callbacks in my controllers. Currently Rails only let you use append|prepend_before|after_action, but this is just extremely bad if you want to add a module with its dedicated callbacks.
I am trying to understand how AbstractController::Callbacks work, and I'm trying to register a new type of callback, that would be executed at a specific moment, taking advantage of Rail's controllers syntax for adding a callback (only/except + list of actions, etc.).
You can think of it as a custom Access Control feature, but this question isn't about access control, please refrain answerbombing with gems like Cancan.
class ApplicationController
include xxx
include MyModuleWithCallbacks
include yyy
...
end
class MyController < ApplicationController
prepend_before_action :something_before_my_callbacks
my_callback_list :custom_callback, only: [:index, :show]
before_action :something_after_my_callbacks
# Goal : the order of above callbacks should NOT matter, my_callback does not depend on ActionController process_action callback list
end
module MyModuleWithCallbacks
extend ActiveSupport::Concern
extend AbstractController::Callbacks
included do
around_action :perform_if_condition
def perform_if_condition
run_callbacks :my_callback_list do
if my_callbacks_went_good?
yield # And run the controller's before_callbacks
else
# log, render error page, etc.
end
end
end
# This is the hard part register the callback, I tried
class_methods do
define_method :my_callback_list do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:my_callback_list, :before, name, options)
end
end
end
The current error is
undefined method `_my_callbacks_list_callbacks' for PublicController:Class
I am taking my inspiration from the source code of AbstractController::Callbacks but I'm not sure I understand what's going on there ^^"
I saw some upvotes so here is my current solution :
With the example of a very lightweight Access control method, the original name of my_callback was access_control
# controllers/concerns/access_control.rb
module AccessControl
extend ActiveSupport::Concern
class_methods do
define_method :my_callback do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:my_callback, :before, name, options)
end
end
end
included do
define_callbacks :my_callback
def perform_if_access_granted
run_callbacks :my_callback do
if #access_denied and not #access_authorized and not god_mode?
#request_authentication = true unless user_signed_in?
render(
file: File.join(Rails.root, 'app/views/errors/403.html'),
status: 403,
layout: 'error')
else
yield
end
end
end
Then in your other controllers that include the module (again with Access control example)
# controllers/your_controller.rb
class YourController < SeekerController
your_callback do
set_entity
allow_access_to_entity(#entity)
end

Is it possible to invoke a helper_method from another Helper?

I have two Helpers, ExamsHelper and ResultsHelper
exams_helper.rb
module ExamsHelper
def get_data
...
end
end
results_helper.rb
module ResultsHelper
def find_result
...
end
end
Is it possible to access the get_data method in ResultsHelper.
I know that if I am declaring it on the ApplicationHelper, I can access it. Is there any other solution for it?
You can always use include:
module ResultsHelper
include ExamsHelper
def find_result
get_data # works
end
end

where to put helper methods for controllers only?

I am looking to write certain methods for processing strings, and other tasks that take place in numerous of my controllers. I know its bad practice to include helpers in your controller, so I was just wondering, where is the best place to put application wide methods used in controllers?
I realize some of you will say to put them in models, but you have to realize that not all my controllers have an associated model. Any and all input would be appreciated.
I tend to put them into helpers. The fact that they are included in views
automatically haven't been a problem for me. You can also place them into
something like app/concerns/ or lib/
I don't like cluttering ApplicationController with private methods
because this often becomes a mess.
Example:
module AuthenticationHelper
def current_user
#current_user # ||= ...
end
def authenticate!
redirect_to new_session_url unless current_user.signed_in?
end
end
module MobileSubdomain
def self.included(controller)
controller.before_filter :set_mobile_format
end
def set_mobile_format
request.format = :mobile if request.subdomain == "m"
end
end
class ApplicationController
include AuthenticationHelper
include MobileSubdomain
end
If you need to use a method in the application scope then I would suggest that you keep those methods inside the application controller and in order to use them inside views.. declare those as helper methods.
For example,
class ApplicationController < ActionController::Base
helper_method :current_user, :some_method
def current_user
#user ||= User.find_by_id(session[:user_id])
end
def some_method
end
end
I would suggest to put them in lib folder. So for example I have:
lib/utils/string_utils
module StringUtils
def foo
...
end
end
class BarController < ActionController::Base
include StringUtils
end
This demonstrates good methodology called Fat model, Thin controller, in this case we are using Mixins instead of Models to separate logic but idea is same. You want your controllers as simple as possible.
It all depends on your needs. I will provide here 2 examples. Both of them are just a custom libraries, located under lib directory.
First example - "custom string processing"
# lib/filters.rb
module Filters
# Converts value to canonical view
def self.phone(value)
# remove all non-digits
clean_value = value.gsub(/\D/, '')
country_codes = configus.phone.country_codes
area_code = configus.phone.defaults.area_code
case clean_value.length
when 7
"#{area_code}#{clean_value}"
when 11
# remove country code only if phone starts with the allowed country code
if country_codes.include?(clean_value[0].to_i)
clean_value[1..-1]
else
clean_value
end
else clean_value
end
end
# usage
# app/api/phones_controller.rb
class Api::PhonesController < Api::ApplicationController
def exists
if params[:q]
clean_value = Filters.phone(params[:q])
...
end
end
end
Second example - helper for flash messages
# lib/flash_helper.rb
module FlashHelper
def flash_translate(key, options = {})
scope = [:flash, :controllers]
scope += params[:controller].split('/')
scope << params[:action]
t(key, {:scope => scope}.merge(options))
end
end
# app/application_controller.rb
class ApplicationController < ActionController::Base
include FlashHelper
end
# usage
# app/your_controller.rb
class YourController < ApplicationController
def create
#object = Object.new(params[:object])
if #object.save
flash[:success] = flash_translate(:success)
...
end
end
end
Note: do not forget to add lib dir to the autoload paths. In config/application.rb add/modify this line config.autoload_paths += %W(#{config.root}/lib).
So for me the answer is lib directory.
Starting from Rails 4 there is a dedicated folder for it app/controllers/concerns. So you can create a module there and then include it in a specific controller(s) or in ApplicationController if you need it available throughout all your controllers.
In case those methods are used in numerous controllers, I would define them in application_controller.rb. Every controller will inherits from it and will be capable to use any method defined there

Resources