Rails 3 - how to organize / split up bloated controllers? - ruby-on-rails

I've been working on a CMS app to sharpen up my skills and the controllers are getting quite bloated with the definitions. I know it's possible to store stuff in lib/whatever.rb and then use require and include, but that doesn't quite work with controllers - at least, in my case, where I have before_filters.
Without the definitions right in the controller, before_filters refuse to work.
Do all the defs HAVE to go in the controller or is there a way to take them out? (They are specific to that controller so they can't go in application controller.

You can do a lot of things with mixin modules that will add behavior to an existing controller, or you can try and come up with a class hierarchy that will allow the controllers to inherit the required methods from their parent class.
In most applications I sub-class ApplicationController at least once in order to enforce some standards in certain contexts. For instance, all controllers relating to a Project would inherit from ProjectController::Base:
class ProjectController::Base < ApplicationController
before_filter :must_be_logged_in
before_filter :load_project
protected
def load_project
#project = Project.find(params[:project_id] || params[:id])
rescue ActiveRecord::RecordNotFound
render(:template => 'not_found')
end
def must_be_logged_in
# ...
end
end

The augmentation-plugin (it's rather a snippet) could be a solution for you.
What it does (add some methods to Object/Module)
class ::Object
def self.augment(*mods)
include *mods
mods.each {|mod| class_eval &mod.augmentation }
end
end
class ::Module
def augmentation(&block)
#augmentation ||= block
end
end
What it allows you to do
# app/controllers/your_controller.rb
class YourController
augment YourController::Stuff
...
end
# app/controllers/your_controller/stuff.rb
module YourController::Stuff
augmentation do
before_filter :something
def something
...
end
end
end
You need to make sure that subfolders of folders in /app are included in Rails' autoload paths.

Related

Encapsulating controller logic in rails

I have 2 controllers in rails with different authentications schemes,
but they do almost the same.
What is the best way in rails to encapsulate
the logic of a controller in another class or helper?
Sample:
def ControllerA < BasicAuthController
def create
blablacode
end
end
def ControllerB < TokenAuthController
def create
blablacode
end
end
Whats the proper way to do this? create a model with the code?
Create a helper? other?
The simplest thing is to make a module and then include it into the other controllers:
module ControllerMixin
def create
blablacode
end
end
The remaining question, though, is where to put this code such that it is works with Rails autoloader, since it needs to be loaded before the controllers. One way to do it would be to write the module to a file in the lib/ directory, then add that to the autoload paths (see auto-loading-lib-files-in-rails-4
Why don't you enable both schemes for a single controller? Especially if the only difference is Authentication. You could have two app/controllers/concerns to encapsulate both authentication methods and include Auth1 and include Auth2 for a single controller who is only responsible for whatever resource it manages.
Otherwise, services are the best approach to encapsulate controller logic.
Create a folder called services in your app folder and write PORO classes here. Say you have a few places in your app where you want to pay for stuff via make Stripe.
# app/services/stripe_service.rb
module StripeService
def customer(args)
...
end
def pay(amount, customer)
...
end
def reverse(stripe_txn_id)
...
end
end
# controller
StripeService.customer(data)
=> <#Stripe::Customer>
Or if you only need to do one thing.
# app/services/some_thing.rb
module SomeThing
def call
# do stuff
end
end
# controller
SomeThing.call
=> # w/e
If you need an object with multiple reponsibilities you could create a class instead.
class ReportingService
def initialize(args)
...
end
def query
...
end
def data
...
end
def to_json
...
end
end
https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services
I do it something like this:
#app/services/my_app/services/authentication.rb
class MyApp::Services::Authentication
class < self
def call(params={})
new(params).call
end
end # Class Methods
#==============================================================================================
# Instance Methods
#==============================================================================================
def initialize(params)
#params = params
end
def call
... do a lot of clever stuff
... end by returning true or false
end
private
def params() #params end
end
Then:
class FooController < ApplicationController
before_action :authenticate
def authenticate
redirect_to 'some_path' unless MyApp::Services::Authenticate.call(with: 'some_params')
end
end
Short answer, i choose to create a Helper.
From all the suggestions in the answers
Create a Module:
Seems correct but it didnt feel right to have logic outside
the app directory. This wasnt an external module or library but
something very related to the logic of my app.
Integrate diferents authentications in one controller:
Was a good suggestion but i have to change all the logic of my app.
Create a Helpers:
It seems to me the better solution, i had the code on a helper, and
is inside the app directory, very near from the other logic.

Create an "includable" Rails controller action within helper

Is it possible to make an includable controller action within a Rails Helper through an included block? I'm thinking something like this:
module XablauHelper
included do
def my_shared_action
true
end
end
end
Already tried doing it through class.eval block and through using like a class method i.e. self.my_shared_action but no success, I have already found a solution that is making a parent controller with the desired shared actions and inheriting from it, but for the sake of modular design I would like to make it a more "global" approach, so I could gemify my solution and reuse code, any suggestions that doesn't use inheritance?
Adding controller actions in a helper is probably the wrong choice, as these methods are intended for your views.
Consider using controller concerns instead, and including them where required. For example:
# in app/controllers/concerns/useful_functions_concern.rb
module UsefulFunctionsConcern
extend ActiveSupport::Concern
included do
rescue_from SomeException, with: :handle_access_denied
end
def useful_method
# ...
end
protected
def handle_access_denied
# ...
end
end
# in your controller
class XyzController < ApplicationController
include UsefulFunctionsConcern
def index
useful_method
end
end
Where common controller actions can be shared and the controllers have something in common e.g. they are all API controllers, also consider using inheritance to achieve this. For example:
# parent controller
class ApiController < ApplicationController
def my_shared_action
end
end
class SpecificApiController < ApiController
end

How to use reusable controller methods in Rails?

I'm relatively new to Rails and am working on creating a simple user authentication system to get to grips with how Rails works.
I'm currently at the point where I'd like to create some methods that I can use in my controllers like so:
is_logged? # => true
and
current_user_id # => 6
These would be used to interact with sessions, mainly so I'm not repeating myself in the controller.
Where would I define these functions and how would I include them in a controller?
Thanks a lot in advance for any help.
Method 1
You can define these method in helper files, inside app/helpers/my_module.rb. You can create a module there, put all the methods inside of it, and then include the modules in your control to use these method.
module MyMoule
def is_logged?
...
end
end
Then in you class include the module
class MyClassController < ApplicationController
include MyModule
def my_method
#Use it like this
logged_in = MyModule.is_logged?
end
end
Method 2
If you using session related stuff you can always put them inside application_controller.rb. And since all your controller will inherit ApplicationController the methods will be available to you.
class ApplicationController < ActionController::Base
def is_logged?
...
end
end
In your other controller you can use them directly.
class MyClassController < ApplicationController
def my_method
logged_in = is_logged?
end
end

Access application controller variable before class name

I want to access application_controller variable before class name of another controller
class TodosController < eval("#{subdomain_name.humanize}Controller")
I want to customize my ToDoController to inherit different controllers for different subdomains.
Just make the implementations of the inherited methods depend on the subdomain.
One way of avoiding such methods becoming and if-else-case-jungle is to use separate objects to model the subdomain-specific "policies". Here is one possible solution for implementing a subdomain-specific before filter
class TodosController < ApplicationController
before_filter :require_foobar_is_allowed
# ...
end
Define the filter in application controller (or a concern included to TodosController)
class ApplicationController
# ...
protected
def require_foobar_is_allowed
unless subdomain_policy.foobar_allowed_for?(current_user)
render nothing: true, status: 403
end
end
def subdomain_policy
"#{subdomain_name}Policy".constantize.new
end
end
And then define the implementation in domain-specific policy objects (plain-old Ruby objects), can be placed to e.g., app/models/
class FoobarSubdomainPolicy
def foobar_allowed_for?(user)
# in the foobar subdomain, foobar is allowed for everyone
true
end
end
class BazSubdomainPolicy
def foobar_allowed_for?(user)
# in the baz subdomain, foobar is only allowed for admins
user.admin?
end
end
Depending on what the "inherited" functionality actually looks like, there might also exist ready-made solutions in gems such as Devise.

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