1

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?

gipsh
  • 578
  • 1
  • 3
  • 20
  • hmmm the rails way is to define helper methods found under corresponding helper folder, anyway you could actually make a class or module that represents what you want to do, if you go the module route you could mix it in the controller – niceman Jun 06 '17 at 21:07
  • 3
    That's a broad question. It depends. Helper methods? Modules? Inheritance? Dependency injection? Abstract logic into a gem? ... Could you at least show *what* code you're trying to clean up? – Tom Lord Jun 06 '17 at 21:08

4 Answers4

0

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
jvillian
  • 19,953
  • 5
  • 31
  • 44
  • `app/services/my_app/services`??!! – Tom Lord Jun 06 '17 at 21:32
  • Yeah. I've seen some discussions about things like that. I don't mind the goofy directory structure so much. And I like (what I perceive) to be the clarity of the `MyApp::Services::Authentication` namespacing. (Possibly because I gemify a ton of stuff that I use across a bunch of applications and I like the belt-and-suspenders security of excessive namespacing.) But, I recognize some people are more fussy about their directory structures than I am. To each their own. – jvillian Jun 06 '17 at 21:35
0

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

fbelanger
  • 3,522
  • 1
  • 17
  • 32
  • Unless I'm mistaken, those methods are going to be instance methods. In which case you'll need to do something like `SomeThing.new.call`. Or maybe not? – jvillian Jun 06 '17 at 21:20
0

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

max pleaner
  • 26,189
  • 9
  • 66
  • 118
0

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.

gipsh
  • 578
  • 1
  • 3
  • 20