2

I started working through the tutorial at railstutorial.org to get exposure to the framework. My controllers aren't monstrous yet, but I can see that Single Responsibility Principal(SRP) isn't being applied throughout the tutorial, as it extends beyond the scope of the tutorial.

I've got this relatively simple controller here. I can already see different concerns (e.g. authentication and authorization) leaking into this controller, which contains too many actions to begin with. This assigns too many actions to one controller. I stumbled upon rails focused controllers which solves one of these issues and looks quite interesting.

Is this a common solution? Or are better solutions available?

In the .net world, we tend to use aspect oriented programming(AOP) to achieve a cleaner Separation of Concerns (SoC). However, recently a few guys have written a new front controller framework called Fubu Behaviours. It captures the idea of a request pipeline nicely. Something that makes more and more sense to me.

In order to handle a request we tend to go through a few steps before (and sometimes after) the action is executed. In some cases, conditionally ending the request. It seems natural to use something like behaviors, a pipeline or a russian dolls pattern. So that each link in the chain is responsible for either continuation or cessation. Inheritance doesn't seem to be the best solution.

Is there anything similar to that in rails? Would it make sense in rails?

Recommended readings will be just as well welcome!

Ryan Gates
  • 4,352
  • 4
  • 46
  • 84
nieve
  • 3,015
  • 2
  • 18
  • 24
  • The authorization should happen before you end up "locked" in a controller. And same is partially true for authentication: you would have to verify use credentials inside model layer, but recovering user's identity from token should too happen outside the MVC triad. Unfortunately the only example, that I can give, is in [php](http://stackoverflow.com/a/9685039/727208), and I am not sure, if it is applicable to particular framework. RoR's relation to MVC is kinda vague .. – tereško Oct 02 '12 at 21:51

3 Answers3

2

I agree with you that having those authorization functions like is_admin and correct_user are a bit of a code smell. I would remove them and handle it a bit better with a gem I often use called CanCan.

It allows you to move all authorization rules out of your controllers into a manifest (i.e. the Ability model), only requiring your controllers to initiate the authorization check through authorize_resource call in your controller. Then you can handle simple redirects in your ApplicationController:

class ApplicationController < ActionController::Base
  rescue_from CanCan::AccessDenied do |exception|
    if current_user
      redirect_to signin_url, :alert => exception.message
    else
      redirect_to root_path, :alert => exception.message
    end
  end
end

Other than that, I would move all the @user = User.find(params[:id]) calls into a before_filter, and clean up your indenting and your action ordering (should be index,new, create, show, edit, update, destroy) and I think your controller would be nice and skinny.

dnatoli
  • 6,752
  • 7
  • 51
  • 95
  • This is probably the closest I'll get. I presume there are gems for every situation I may come across. Still, this means it's all done by inheritance: I'd probably have an AuthorizationController, AuthenticationController and so on... – nieve Oct 03 '12 at 13:25
  • 1
    NOOOOO! creating extra controllers to handle Authorization, etc, is massive bad practice. Controllers in REST should be around resources, not around functionality! The ApplicationController is there for the sole purpose of configuration, so use it to configure your authorization redirect. For more on ROA see http://en.wikipedia.org/wiki/Resource-oriented_architecture – dnatoli Oct 03 '12 at 23:41
  • Hang on, so then you might potentially end up with all sorts of weird logic in your ApplicationController- if for example there's some specific rules like on where to redirect different types of users on authorization exceptions for example. not to say that there could be all sorts of different concerns crammed into the ApplicationController eventually. – nieve Oct 04 '12 at 10:07
1

To be honest, I can't really tell if authorization/authentication should be a model's or a controller's job and people will give you various answers on this. It's like a grey area.

So I prefer to go with the Rails conventions there, as they have been proven trustworthy for all these years. Your controllers seem fine to me, I wouldn't call them fat. You could ofcourse move these private methods into a helper.

Agis
  • 29,320
  • 2
  • 67
  • 77
  • 1
    moving methods would not change the fact that controller is verifying user's authorization. – tereško Oct 02 '12 at 21:45
  • Hey Agis- thanks for the reply. What would happen with more complex controllers where more concerns & responsibilities are being taken care of by the controller (validation,authorization,authentication, captcha or anything else a request might need in order to allow the rendering of a certain view). So what tereško said was bang on the money (and with less words). I was really hoping to see that there are other ways of doing this in rails, closer to the fubu way. But again, maybe that's just me looking for something that shouldn't even be there..? – nieve Oct 02 '12 at 21:47
  • To be honest, I still haven't reached that stage where something in my controllers would bother me. I'm sure there are solutions, like focused controllers you talked about, or abstracting functionality like this to gems (ex. devise gem for authentication). But unfortunately I can't provide you certain approaches on how to do this. :) – Agis Oct 02 '12 at 21:51
  • people will disagree but I feel that authentication should happen at BOTH the controller level and the model level. In some circumstances, there will be duplication but there will also be peace of mind. You run into situations where controllers have too much conditional logic and a user needs to be able to access the resource – timpone Oct 02 '12 at 22:03
0

For any sort of authorization logic, I would actually remove that from the controller layer, and move that to a 'policy' layer.

using this gem: https://github.com/NullVoxPopuli/skinny_controllers it gives you two additional layers

  • operations
    • where business logic goes
  • policies
    • where authorization goes

An example from the Readme:

module EventOperations
  class Read < SkinnyControllers::Operation::Base
    def run
      # the business logic here is to only check if we will allow this model
      # to be returned
      model if allowed?
    end
  end
end



class EventPolicy < SkinnyControllers::Policy::Base
  # allowed? from the operation delegates to this method
  # here, you could do whatever logic you need to check if the operation
  # is allowed
  def read?(o = object)
    o.is_accessible_to?(user)
  end
end
NullVoxPopuli
  • 51,415
  • 69
  • 184
  • 335