On 2011-01-21 10:25 PM, David Kahn wrote:


On Mon, Jan 17, 2011 at 1:43 PM, David Chelimsky <dchelim...@gmail.com <mailto:dchelim...@gmail.com>> wrote:

    On Jan 17, 2011, at 10:16 AM, David Kahn wrote:

    On Mon, Jan 17, 2011 at 9:48 AM, Ants Pants
    <antsmailingl...@gmail.com <mailto:antsmailingl...@gmail.com>> wrote:

        Hello all,

        From what I've seen, this type of question doesn't really
        seem to get an answer on this list as most of the replies
        relate to failures of RSpec. If this is the case, where is
        the best place to go to get advice about best practices etc?

        I have a question about best practice. In some of my
        controllers only an admin user can perform edit, update, show
        etc. So I have a before filter in those controllers;
        ApplicationController#authorise_is_admin

        The ApplicationController#authorise_is_admin throws an
        AccessDenied exception and that is caught
        in ApplicationController#access_denied

        My question is, in the spec for the calling controller, let's
        say ProductGroups, what should I spec?

        I have a context "user is admin" and that's easy to spec, but
        the context "user is not admin" is where I'm stuck as no
        actions are performed in that controller but I would just
        like to cover that failure somehow.

    Interesting question. I had the same dilemma and decided that it
    took too much effort and test code to test this at the controller
    level. What I do (and this may or may not work for you depending
    on your apps security needs), is to have an authorize method in
    the User model. It returns success or failure based on the
    controller and action passed. The model looks something like this:

      def authorize(controller_name, action_name)
        if self.role
          current_role = self.role.name <http://self.role.name/>
        else
          # guest user is empty user
          current_role = 'guest'
        end


        case controller_name
        when 'activations'
          if current_role != 'guest'
            return set_autorize_failure_value("You are already logged
    in to the system. If you are activating a new user please log out
    first and try again.")
          end
          return authorize_success_message

        when 'feedback_supports'
          if current_role == 'guest' || current_role == 'sysadmin'
            return set_autorize_failure_value(LOGIN_NOTICE)
          end
          return authorize_success_message
    ...

    end

    Then in the spec it is real easy:

      describe "user authorization - guest role" do
        it "is authorized to access certain pages only" do
          user = User.new
          user.authorize('activations', 'create')[:success].should ==
    true
          user.authorize('home', 'index')[:success].should == false

        ....

        end
      end

    This might not be everyone's cup of tea and I am sure I can
    refactor and make this less verbose, but what I like is having
    the 'dna' of all my access rights app wide in one place.

    Definitely agree with the idea of keeping decisions in one place.
    I don't really like the idea of 'controllers' living inside a
    model, but change 'controller_name' to 'resource_collection_name'
    and that solves that problem.

    I would still want to specify that the controller asks the user
    for authorization. WDYT?


If I am following the thread right, I should clarify that I call 'authorize' from the application controller -- should have included this in my first response. It is from there that the User model is invoked:

class ApplicationController < ActionController::Base

    private

    def authorize
      if current_user
return_value = current_user.authorize(controller_name, action_name)
      else
        return_value = User.new.authorize(controller_name, action_name)
      end

      if !return_value[:success]
        flash[:notice] = return_value[:failure_message]
        if current_user
          redirect_to home_path
        else
          redirect_to login_path
        end
      end
    end

end


I would not venture to say what I have is the best but just what came up from refactoring in search of simplifying my code and tests. I also do not like the idea of anything dependent like a session or params or something else from a controller getting coupled to a model, but in this case it feels to me that the model is just a switchboard in decision-making, which for me I would consider business logic.



Keeping the "cup of tea" idea in mind, I can understand how you could arrive at this point. I did something similar a couple of years ago. I was passing something into the model that could come only from a controller, which was fine and dandy until I needed to use the model method in some way that proved difficult to get that information. It may have been a rake task, I can't remember the exact details. But from that point, I started thinking about my models as separate from the particular application I was working on at the moment. When creating methods and passing in arguments, I started thinking about what would happen if I used this model outside of the Rails application. Would I still be able to pass that information in easily and would it make sense?

The other thought that I had with this particular approach (passing controller name and action into the model) is that it seems the model specs would feel sort of fake. In order to unit test the authorize method, you have to test with real values. I may be overlooking something (just got up, so it's very possible), but that doesn't seem like it's testing behavior so much as how the method handles specific values. If you add new controllers and/or actions, you must then go add them to your unit test to make sure the authorize method responds correctly. That doesn't seem like the kind of test that I'd feel comfortable trusting.

Now that I think about it, I actually had this same situation come up a couple of years ago. Before I started splitting role-based functionality into separate namespaced controllers, I was trying to control access to various actions via before_filters. Using the same concept of controller/action combination, I created a hash of those pairs and which roles could access them. The role names then turned into model methods that returned true/false. Something like:

access_rules = {
  'products' => {
    'show' => [:clerk, :manager],
    'new' => [:manager],
    'edit' => [:manager]
  },
  'stores' => {
    'show' => [:clerk, :manager],
    'new' => [:manager],
    'edit' => [:manager]
  }
}

roles = access_rules[controller_name][action_name]

return true if roles.any? { |role| current_user.send("#{role}?") }
return false

Something like that anyway. I even went so far as to write a controller plugin so I could simplify it to

allow_access(:clerk, :only => [:show])
allow_access(:manager, :only => [:show, :new, :edit])
verify_access

This is obviously a contrived example, but it should suffice to get the point across. Bringing this back to RSpec, specs make more sense this way as you can check to make sure your role methods on the user model return true or false based on a database setting and you can easily mock/stub those methods in the controller specs to make sure that you don't get to actions that you shouldn't.

By the way, I prefer English breakfast tea and Indian spiced chai. :)

Peace.
Phillip

_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to