Mixin application.rb problem

Hello all.

I’m trying to implement my own role-based access scheme, and I’ve hit a
bit of a snag. It may be that my idea is just plain wrong, and if so,
I’d appreciate gentle instruction as to how to make it more sound.

My intent is to do something like

class MyController < ApplicationController
permit_access(:admin)
permit_access(:normal_user, :only => [:this, :that])

before_filter :require_access

def this
end

def that
end

def the_other
end

private

def require_access
    redirect_to error_path unless verify_access(@current_user)
end

end

The point of this endeavor is to more easily allow multiple roles to the
same action. To that end, I created a module called RoleAccess that
defines a permit_access method and a verify_access method. The idea is
that I’d have a hash of controllers, actions, and roles that would be
populated by permit_access and inspected by verify_access. The problem
is that permit_access is a class method and verify_access is an instance
method, so my module looks like

module RoleAccess
module ClassMethods
def permit_access
end
end

module InstanceMethods
    def verify_access
    end
end

end

and I can’t figure out how to have a hash shared between
ClassMethods::permit_access and InstanceMethods::verify_access. First,
can this be done? If so, will someone please point me to some sample
code that illustrates how to do it. I’ve been looking through various
pieces of Rails source and scouring the internet, which has brought me
to the point where I am, but I can’t quite get over the hump.

Peace,
Phillip

On Sep 5, 2:40 am, Phillip K. [email protected]
wrote:

and I can’t figure out how to have a hash shared between
ClassMethods::permit_access and InstanceMethods::verify_access. First,
can this be done? If so, will someone please point me to some sample
code that illustrates how to do it. I’ve been looking through various
pieces of Rails source and scouring the internet, which has brought me
to the point where I am, but I can’t quite get over the hump.

Use your favourite class variable pattern and then do
self.class.permitted_actions or whatever you call it.

Fred

On 5 Sep 2008, at 15:28, Phillip K. wrote:

I should probably just write “Hello Fred”. Heh heh.

In application.rb, I have

class ApplicationController < ActionController::Base
extend RoleAccess
init_role_access
At this point, no methods have yet been defined so it’s normal that
action_methods is empty.

You also want to be careful with @@ - since you first define
@@access_map on ApplicationController, all controllers will share it
(I wrote up some stuff on that not too long ago at
http://www.spacevatican.org/2008/8/19/fun-with-class-variables)

one minor thing you could do would be to use the self.included hook on
modules. This is called when the module is included so you could do just

include RoleAccess and do your init_role_access stuff from that hook

Fred

Use your favourite class variable pattern and then do
self.class.permitted_actions or whatever you call it.

Fred

Hi Fred.

Funny enough, when I initially wrote “Hello all”, I mused to myself that
I should probably just write “Hello Fred”. Heh heh.

Thanks for the instruction. You are a quite a help to a lot of people. I
was watching the Ruby Hero awards the other day, and was thinking that
you deserve one of those as well. Cheers to you!

Now, if I could pick your (or anyone else’s for that matter) brain a bit
more, I’m having another problem with my access scheme. The original
post was just generalities, so I’ll use specific code this time.

In application.rb, I have

class ApplicationController < ActionController::Base
extend RoleAccess
init_role_access

protected

def require_access
redirect_to person_path unless verify_access(@current_person)
end
end

and in an actual controller, this

class SportsController < ApplicationController
permit_access(:system_administrator)
permit_access(:oranization_administrator, [:index, :show, :update])
permit_access(:sport_administrator, [:show, :update])

before_filter :require_logged_in
before_filter :require_access


end

and finally, lib/role_access.rb looks like

module RoleAccess
def init_role_access
include InstanceMethods
extend ClassMethods
@@access_map ||= {}
end

def self.access_map
@@access_map
end

module ClassMethods
def permit_access(*args)
access_map = RoleAccess::access_map

  #debugger
  controller = self.controller_name.to_sym

  #********************************************
  # Problem with next line, see comment below
  all_actions = self.action_methods.collect { |action| action.to_sym 

}
#********************************************

  options = args.last.is_a?(Hash) ? args.pop.symbolize_keys : {}
  by = args.first.is_a?(Array) ? args.first : [args.first]

  return if by.size.zero?

  if options.has_key?(:only)
    only = options[:only] || []
    to = all_actions.select { |a| only.include?(a) }
  elsif options.has_key?(:except)
    except = options[:except] || []
    to = all_actions.delete_if { |a| except.include?(a) }
  end

  to ||= all_actions

  return if to.size.zero?

  access_map[controller] = {} unless access_map.has_key?(controller)

  to.each do |action|
    access_map[controller][action] = [] unless 

access_map[controller].has_key?(action)

    by.each do |role|
      access_map[controller][action] << role unless
        access_map[controller][action].include?(role)
    end
  end
end # permit_access

end # module ClassMethods

module InstanceMethods
protected

def verify_access(current_person)
  #debugger
  access_map = RoleAccess::access_map

  controller = controller_name.to_sym
  action = action_name.to_sym

  # if the controller is not explicitly listed, allow access
  return true unless access_map.has_key?(controller)

  # if the action is not explicitly listed, allow access
  return true unless access_map[controller].has_key?(action)

  # if we get here, the controller and action are both in 

access_map…check to see
# if the user can access the action
roles = access_map[:controller][:action]
roles.each do |role|
role_method = “#{role}?”
return true if current_person.respond_to?(role_method) &&
current_person.send(role_method)
end

  return false
end # verify_access(current_person)

end # module InstanceMethods
end

My intent, if it’s not clear, is to grant access to all the actions to
system administrators, and access to selected actions to organization
and sport administrators. It appears that sharing @@access_map is now
working, but the problem I am now having relates to the line indicated
above:

all_actions = self.action_methods.collect { |action| action.to_sym }

If I comment out all of the lines related to loading RoleAccess (as if
it never existed) and go into script/console,
SportController.action_methods returns

SportsController.action_methods
=> #<Set: {“edit”, “delete”, “show”, “new”, “update”, “destroy”,
“index”, “create”}>

When trying to use RoleAccess and debugging the aforementioned line, I
see that self is SportsController, yet action_methods returns an empty
set.

/Users/phillip/dev/projects/sports/sports_dev/lib/role_access.rb:17
controller = self.controller_name.to_sym
(rdb:27) p self
SportsController
(rdb:27) p self.respond_to?(:action_methods)
true
(rdb:27) p self.action_methods
#<Set: {}>
(rdb:27) p SportsController.action_methods
#<Set: {}>
(rdb:27)

So the long and short of it is when I don’t pass any actions in the call

permit_access(:system_administrator)

I want to grab all of the actions dynamically, but the fact that
self.action_methods is empty results in the system administrator being
granted access to nothing.

Ideas?

Peace,
Phillip

Frederick C. wrote:

At this point, no methods have yet been defined so it’s normal that
action_methods is empty.

Well, hm. So they aren’t. Going to have to think about that one. I guess
I need to look at the filter code some more to see how Rails gets around
that.

You also want to be careful with @@ - since you first define
@@access_map on ApplicationController, all controllers will share it
(I wrote up some stuff on that not too long ago at
http://www.spacevatican.org/2008/8/19/fun-with-class-variables)

I thought of that, which is why I start by adding a key for the
controller. This sort of thing is pretty new to me, so I might be
missing something fundamental. If I just make it @access_map, will each
controller have its own? I’ll read your article and see if I understand
better afterward.

one minor thing you could do would be to use the self.included hook on
modules. This is called when the module is included so you could do just

include RoleAccess and do your init_role_access stuff from that hook

I tried do this different ways, but didn’t have any success. Part of my
problem might be that I’m a bit confused by extend versus include. Based
on what I’ve been reading, I thought that include was used to add
methods to classes whereas extend added methods to instances (objects).
The current form of

extend RoleAccess

is actually the reverse of what I thought I needed to have, so my grip
on that one is apparently pretty weak. I’ll experiment with them and see
if can’t get it cleaner.

Fred

Thanks, Fred. Your insight and patience are very much appreciated.

Phillip

On 5 Sep 2008, at 16:46, Phillip K. wrote:

around
that.

Personally I’d do it when the filter runs, not ahead of time.

missing something fundamental. If I just make it @access_map, will

include RoleAccess and do your init_role_access stuff from that hook

extend RoleAccess

Have a look at
http://blog.jayfields.com/2006/05/ruby-extend-and-include.html
or http://www.dcmanges.com/blog/27

Fred

Frederick C. wrote:

Personally I’d do it when the filter runs, not ahead of time.

Thanks for saying that. It didn’t register at first what you were
talking about, but it finally sank in and I followed that route. Wow, it
really simplified things. I decided to take ApplicationController out of
the picture completely and include RoleAccess in the specific
controllers in which I need greater flexibility. Coupling that with your
suggestion made it all cleaner. Now I can just include RoleAccess in
SportsController and not worry about whether it’s a class method or an
instance method. Since I am calling permit_access and verify_access from
within require_access, they are both instance methods. And that also
solves the problem of action_methods not being populated. The only
difference is now I have to call

self.class.action_methods

but that’s okay. The only downside, and it’s purely aesthetic, is not
having the statements about access control at the top of the controller.
When I come back to this later, or if someone else ever has to maintain
this code (which might happen), it won’t be as obvious what’s going on.
I suppose I could resort to that legacy construct we used to hear so
much about. Oh what was it? A ‘comment’, I think.

Have a look at
http://blog.jayfields.com/2006/05/ruby-extend-and-include.html
or http://www.dcmanges.com/blog/27

Thanks again. Those resources helped. I’m going to have to put those in
Evernote so I don’t forget about them.

So my module is much cleaner now, but I’m still confused by the access
to the instance variable. I now have

module RoleAccess
@access_map ||= {}

def self.access_map
@access_map
end # access_map

private

def permit_access(*args)
access_map = RoleAccess::access_map

end

def verify_access(current_person = nil)
access_map = RoleAccess::access_map

end
end

I tried various ways to access @access_map without having to use that
ugly RoleAccess::access_map, but nothing worked. I tried to apply what
you wrote in the blog post you mentioned earlier, but I believe there
are some differences between classes and modules in this regard. This
approach does work, but I’d like it to be a bit cleaner.

Again, thanks for the help and patience. I really enjoy Ruby/Rails, but
it’s taking me a bit of effort to wrap my head around a lot of it.

Fred

Phillip