Dynamic include

Hello,

I try to include some modules to a class instance, but I have problems with the scope, here's what I did:

person.roles.each do |role|
     class << person
         include role.class.const_get('Functions')
     end if role.class.const_defined?('Functions')
end unless person.nil?

As you can see role is not defined when I include it, does anybody know how I can add some singleton methods to person?

Regards
Florian

Florian Aßmann wrote:

As you can see role is not defined when I include it, does anybody know
how I can add some singleton methods to person?

Maybe the module Functions is the culprit? Are the methods module
functions ("def self.some_method"), or mixin functions ("def
some_method") in the module?

David Vallner

Mixin:

class ManagerRole < Role
     module Functions
         def hire( person )
             self.company << person
         end
     end
end

Florian

···

Am 01.10.2006 um 20:53 schrieb David Vallner:

Florian Aßmann wrote:

As you can see role is not defined when I include it, does anybody know
how I can add some singleton methods to person?

Maybe the module Functions is the culprit? Are the methods module
functions ("def self.some_method"), or mixin functions ("def
some_method") in the module?

David Vallner

To be complete here if the Exception:

NameError: undefined local variable or method `role' for #<Class:#<Person:0x2869aa0>>
     /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1129:in `method_missing'
     /Users/boof/Documents/Workspaces/rails/comany/config/../app/models/person.rb:63:in `auth'
     /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in `each'
     /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in `send'
     /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in `method_missing'
     /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/has_and_belongs_to_many_association.rb:81:in `method_missing'
     /Users/boof/Documents/Workspaces/rails/comany/config/../app/models/person.rb:60:in `auth'
     test/unit/person_test.rb:37:in `test_4_change_password'

···

Am 01.10.2006 um 20:53 schrieb David Vallner:

Florian Aßmann wrote:

As you can see role is not defined when I include it, does anybody know
how I can add some singleton methods to person?

Maybe the module Functions is the culprit? Are the methods module
functions ("def self.some_method"), or mixin functions ("def
some_method") in the module?

David Vallner

Florian Aßmann wrote:

To be complete here if the Exception:

NameError: undefined local variable or method `role' for
#<Class:#<Person:0x2869aa0>>

I smell a Ruby bug / gotcha.

Apparently, in metaclass scope, the surrounding scope just isn't visible.

Try a workaround:

person.roles.each do |role|
    if role.class.const_defined?('Functions')
        class << person
      self
        end.class_eval do
            include role.class.const_get('Functions')
        end
    end
end unless person.nil?

David Vallner

PS: Statement modifiers after whole blocks? Ick.

Hi --

Florian Aßmann wrote:

To be complete here if the Exception:

NameError: undefined local variable or method `role' for
#<Class:#<Person:0x2869aa0>>

I smell a Ruby bug / gotcha.

Apparently, in metaclass scope, the surrounding scope just isn't visible.

The class keyword always creates a new scope, whether you give it a
constant argument or a "<< obj"-style argument.

Try a workaround:

person.roles.each do |role|
   if role.class.const_defined?('Functions')
       class << person
      self
       end.class_eval do
           include role.class.const_get('Functions')
       end
   end
end unless person.nil?

You could also use extend, and avoid having to do the thing that won't
be easy until RCR 231 is accepted :slight_smile:

   person.roles.each do |role|
     if role.class.const_defined?('Functions')
       person.extend role.class.const_get('Functions')
     end
   end

David

···

On Mon, 2 Oct 2006, David Vallner wrote:

--
                   David A. Black | dblack@wobblini.net
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] Ruby for Rails | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org

Ok, I just tested it, but now even a person with a CustomerRole and no ManagerRole role has the methods once they are included, thanks anyway. :smiley:
dblack, extend works excactly the way I want it to, thank you too :slight_smile:

Result...

if person = self.send( dyn_finder, *find_args )
     m_name = 'Functions'
     functions = person.roles.inject() do |f,r|
         r.class.const_defined?(m_name) and
         f << r.class.const_get(m_name)
     end and person.extend *functions
end

···

Am 01.10.2006 um 21:14 schrieb David Vallner:

Florian Aßmann wrote:

To be complete here if the Exception:

NameError: undefined local variable or method `role' for
#<Class:#<Person:0x2869aa0>>

I smell a Ruby bug / gotcha.

Apparently, in metaclass scope, the surrounding scope just isn't visible.

Try a workaround:

person.roles.each do |role|
    if role.class.const_defined?('Functions')
        class << person
      self
        end.class_eval do
            include role.class.const_get('Functions')
        end
    end
end unless person.nil?

David Vallner

PS: Statement modifiers after whole blocks? Ick.

Could've sworn extend was private or protected... On second thought,
that would make the method pretty much useless.

*facewall*

There goes the attempt at remembering the little I can about the messier
meta stuff.

David Vallner

···

On Mon, 2006-10-02 at 04:37 +0900, dblack@wobblini.net wrote:

You could also use extend, and avoid having to do the thing that won't
be easy until RCR 231 is accepted :slight_smile:

Hi --

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Florian Aßmann wrote:

To be complete here if the Exception:

NameError: undefined local variable or method `role' for
#<Class:#<Person:0x2869aa0>>

I smell a Ruby bug / gotcha.

Apparently, in metaclass scope, the surrounding scope just isn't visible.

Try a workaround:

person.roles.each do |role|
   if role.class.const_defined?('Functions')
       class << person
      self
       end.class_eval do
           include role.class.const_get('Functions')
       end
   end
end unless person.nil?

David Vallner

PS: Statement modifiers after whole blocks? Ick.

Ok, I just tested it, but now even a person with a CustomerRole and no ManagerRole role has the methods once they are included, thanks anyway. :smiley:
dblack, extend works excactly the way I want it to, thank you too :slight_smile:

The two techniques should give you the same results. If you have a
case where they don't, I'd be interested in seeing it.

Result...

if person = self.send( dyn_finder, *find_args )
  m_name = 'Functions'
  functions = person.roles.inject() do |f,r|
      r.class.const_defined?(m_name) and
      f << r.class.const_get(m_name)
  end and person.extend *functions
end

That call to inject will always return an array, so the "and" will
always flip to the right and execute person.extend *functions -- even
if functions is empty, in which case you'll get an error. You'd
probably want to drop the "and" and just do a conditional test:

   person.extend *functions unless functions.empty?

David

···

On Mon, 2 Oct 2006, Florian Aßmann wrote:

Am 01.10.2006 um 21:14 schrieb David Vallner:

--
                   David A. Black | dblack@wobblini.net
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] Ruby for Rails | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org

After a Test/Unit failed because of an Exception I did: <code>p functions</code> in my source.
It shows me that at one time <var>functions</var> was False, so I added the && operator.

Regards
Florian