Best Practice for Controllers

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.

Any advice?

Thanks in advance.

-ants

On Mon, Jan 17, 2011 at 9:48 AM, Ants P.
[email protected]wrote:

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
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.

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

end
  if current_role == 'guest' || current_role == 'sysadmin'
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?

That behaviour exists in ApplicationController. Since
ProductGroupsController inherits that behaviour from
ApplicationController,
the specs for ProductGroupsController should cover that behaviour.

To keep your code DRY, create a shared example group that can be used in
multiple controller specs. Eg:
http://pastie.org/1470751

When I read the David K.'s response, I thought it strange to have
controller code in the model and was going to query him on it so I’m
glad
that others think the same.

As for Nick’s response, I will look into it_behaves_like (I remember
seeing
that in the book with a pizza example) It sounds good to me.

Thanks for the replies.

-ants

this is the case, where is the best place to go to get advice about best
ProductGroups, what should I spec?
something like this:
when ‘activations’
end
user.authorize(‘activations’, ‘create’)[:success].should == true

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?

On Jan 18, 2:30 am, Ants P. [email protected] wrote:

One thing you might consider is to separate your admin functionality
from your regular user functionality. This isn’t for everyone, but it
might work out well for you. I used to do it the way you are currently
doing it, and it was okay until I ran into some situations in which a
regular user and an admin could edit a resource, but only the admin
could edit certain attributes. The logic in my controller started
putting on a bit of weight. My practice now is to separate appropriate
functionality into namespaced controllers. All features available for
users not logged in go in the default namespace, all features
requiring the user to be logged in (but not administrative) go in the /
user/ namespace, and all features that require administrative rights
go into the /admin/ namespace. Then, in each namespace, I create a
base controller that does the authorization/redirection and all other
controllers in the namespace inherit from it. Something like:

https://gist.github.com/784363

Doing it this way does create seeming duplication in the views, but
partials can be used very easily to deal with that. This method also
more or less demands a menu structure that completely separates the
functionality of each role, but in the projects that I’ve been working
on, that has been a benefit. To have all of the role-based
functionality separated in controllers and menus has provided clear
lines that are easy to follow. In short, it has worked out really
well. But, as always, your own mileage may vary.

Peace.
Phillip

On Mon, Jan 17, 2011 at 1:43 PM, David C.
[email protected]wrote:

I have a context “user is admin” and that’s easy to spec, but the context
def authorize(controller_name, action_name)
if current_role != ‘guest’
return authorize_success_message
user.authorize(‘home’, ‘index’)[:success].should == false

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.

On 18 January 2011 12:28, Phillip K. [email protected]
wrote:

admin user can perform edit, update, show etc. So I have a before
context
success

when 'feedback_supports'

describe “user authorization - guest role” do
This might not be everyone’s cup of tea and I am sure I can refactor

that in the book with a pizza example) It sounds good to me.
regular user and an admin could edit a resource, but only the admin
https://gist.github.com/784363
Peace.
Phillip


rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users

+1 for the many controllers for one model pattern, you can really
simplify
controllers with this approach. Having a separate view hierarchy for
each of
these controllers also makes for simpler views, with less logic. However
you
have be very disciplined with your view partials, or the number of files
can
get a bit confusing

All best

Andrew

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

    Hello all,

    like to cover that failure somehow.
      current_role = self.role.name <http://self.role.name/>
in to the system. If you are activating a new user please log out
I don't really like the idea of 'controllers' living inside a

invoked:
return_value = User.new.authorize(controller_name, action_name)
end

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. :slight_smile:

Peace.
Phillip

Yes, I like that response and I do do that to a limited fashion. Maybe I
should think about using it more widely.

-ants

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs