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

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

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

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

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

Frederick Cheung 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 Fun with class variables - Space Vatican)

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

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

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

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

Have a look at Jay Fields' Thoughts: Ruby extend and include   or Ruby Pattern: Extend through Include - Dan Manges's Blog

Fred

Frederick Cheung 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 Jay Fields' Thoughts: Ruby extend and include   or Ruby Pattern: Extend through Include - Dan Manges's Blog

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