Modules, self class, help!

(J-Van) #1

module JoeAccessors
  class << self
    def joe_accessor_method *args
      args.each do |arg|
        eval <<-src
          def #{ arg }
            @#{ arg }
          end
          def #{ arg }= value
            @#{ arg } = value
          end
        src
      end
    end
  end
end

class Joe
  include JoeAccessors
  joe_accessor_method :arg1, :arg2
end

test.rb:21: undefined method `joe_accessor_method' for Joe:Class (NoMethodError)

*bangs head against desk*

(Brian Mitchell) #2

module JoeAccessors
  class << self
    def joe_accessor_method *args
      args.each do |arg|
        eval <<-src
          def #{ arg }
            @#{ arg }
          end
          def #{ arg }= value
            @#{ arg } = value
          end
        src
      end
    end
  end
end

class Joe
  include JoeAccessors
  joe_accessor_method :arg1, :arg2
end

module JoeAccessors
  def joe_accessor_method *args
    args.each do |arg|
      eval <<-src
        class << self
          def #{ arg }
            @#{ arg }
          end
          def #{ arg }= value
            @#{ arg } = value
          end
        end
      src
    end
  end
end

class Joe
  extend JoeAccessors
  joe_accessor_method :arg1, :arg2
end

This seems to be where the problem is. The module method does not
become a class method by include. The other thing to note is that I
moved the class << self so it targets your final object (instance of
Class in this case). Otherwise, you will find that it will not find
you method.

However, I am not sure what else you are trying to accomplish by this
code... as a guess, the following works for me:

Joe.arg1 = 42
Joe.arg1 #=> 42

> test.rb:21: undefined method `joe_accessor_method' for Joe:Class (NoMethodError)

*bangs head against desk*

Always wear a helmet when doing any meta-programming. :slight_smile:

Brian.

···

On 8/15/05, Joe Van Dyk <joevandyk@gmail.com> wrote:

(Ara.T.Howard) #3

you're close - the issue is that including a module in another class/module
only inserts the __instance__ methods of the included module into the
class/module doing the reciever. so, altough you've defined a class level
method correctly it doesn't make it into your new class. there are a couple of
ways around that - you can do things like:

     module M
       def foo; 42; end
     end
     class C
       extend M
     end
     p C::foo #=> 42

but that only works if ALL the methods in M should be class methods - though
this is often the case where one is defining accessor type stuff in a module.
another approach is do define class level methods in the other class on the
fly and let the instance methods take care of themselves:

     module M
       def self::included other
         class << other
           def foo; 42; end
         end
       end
       def bar; 'forty-two'; end
     end
     class C
       include M
     end
     p C::foo #=> 42
     c = C::new
     p c.bar #=> 'forty-two'

what you can't do, however, is to define a class method in a module and then
somehow 'get it back' to inject it into the including class at runtime (well
you could but it'd be very ugly). i've found this approach to be quite simple
and almost guaranteed to make sense to any developer that ever programmed an
object-oriented language instantly - even if they don't understand the
included method totally:

     harp:~ > cat b.rb
     module JoeAccessors

       module ClassMethods
         def joe_accessor_method *args
           args.each do |arg|
             module_eval <<-src
               def #{ arg }
                 @#{ arg }
               end
               def #{ arg }= value
                 @#{ arg } = value
               end
             src
           end
         end
       end

       module InstanceMethods
         # put em here
       end

       def self::included other
         other.extend ClassMethods
         other.module_eval{ include InstanceMethods }
       end
     end

     class Joe
       include JoeAccessors
       joe_accessor_method :arg1, :arg2
     end

     joe = Joe::new
     joe.arg1 = 42
     p joe.arg1

     harp:~ > ruby b.rb
     42

it's just a tiny bit clunky - but it makes it totally clear which methods are
which without any doccumentation or understanding of 'class << self' etc.

(note that you have to use 'module_eval' here.)

hth.

-a

···

On Tue, 16 Aug 2005, Joe Van Dyk wrote:

module JoeAccessors
class << self
   def joe_accessor_method *args
     args.each do |arg|
       eval <<-src
         def #{ arg }
           @#{ arg }
         end
         def #{ arg }= value
           @#{ arg } = value
         end
       src
     end
   end
end
end

class Joe
include JoeAccessors
joe_accessor_method :arg1, :arg2
end

test.rb:21: undefined method `joe_accessor_method' for Joe:Class (NoMethodError)

*bangs head against desk*

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
Your life dwells amoung the causes of death
Like a lamp standing in a strong breeze. --Nagarjuna

===============================================================================

(J-Van) #4

What's the rationale behind that?

···

On 8/16/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

On Tue, 16 Aug 2005, Joe Van Dyk wrote:

> module JoeAccessors
> class << self
> def joe_accessor_method *args
> args.each do |arg|
> eval <<-src
> def #{ arg }
> @#{ arg }
> end
> def #{ arg }= value
> @#{ arg } = value
> end
> src
> end
> end
> end
> end
>
> class Joe
> include JoeAccessors
> joe_accessor_method :arg1, :arg2
> end
>
>> test.rb:21: undefined method `joe_accessor_method' for Joe:Class (NoMethodError)
>
> *bangs head against desk*

you're close - the issue is that including a module in another class/module
only inserts the __instance__ methods of the included module into the
class/module doing the reciever. so, altough you've defined a class level
method correctly it doesn't make it into your new class.

(Pit) #5

Ara.T.Howard schrieb:

    module JoeAccessors
      module ClassMethods
        ...
      end
      module InstanceMethods
        ...
      end
      def self::included other
        other.extend ClassMethods
        other.module_eval{ include InstanceMethods }
      end
    end

(note that you have to use 'module_eval' here.)

Because Object#include is a private method, or are there other reasons?

Regards,
Pit

(7rans) #6

Ara.T.Howard wrote:

what you can't do, however, is to define a class method in a module and then
somehow 'get it back' to inject it into the including class at runtime (well
you could but it'd be very ugly). i've found this approach to be quite simple
and almost guaranteed to make sense to any developer that ever programmed an
object-oriented language instantly - even if they don't understand the
included method totally:

     harp:~ > cat b.rb
     module JoeAccessors

       module ClassMethods
         def joe_accessor_method *args
           args.each do |arg|
             module_eval <<-src
               def #{ arg }
                 @#{ arg }
               end
               def #{ arg }= value
                 @#{ arg } = value
               end
             src
           end
         end
       end

       module InstanceMethods
         # put em here
       end

You can just put the instance methods where they normally go. No
InstanceMethods module needed.

       def self::included other
         other.extend ClassMethods
         other.module_eval{ include InstanceMethods }
       end

Then cahnge the above to:

         extend ClassMethods

         def self::included other
           other.extend ClassMethods
         end

it's just a tiny bit clunky - but it makes it totally clear which methods are
which without any doccumentation or understanding of 'class << self' etc.

A little less clunky.

T.

(Ara.T.Howard) #7

i'm not sure - but if it were true to include class methods by default it'd be
easy to do this:

     harp:~ > cat a.rb
     module M
       module ClassMethods
         def new
           "can't create instances!"
         end
         def name
           "can't get the name"
         end
         def ancestors
           ['ha', 'ha']
         end
         def is_a?
           'nope'
         end
       end
       module InstanceMethods
       end
       def self::included other
         other.extend ClassMethods
         other.module_eval{ include InstanceMethods }
       end
     end

     class C
       include M
     end

     p C::new
     p C::name
     p C::ancestors
     p C::is_a?

     harp:~ > ruby a.rb
     "can't create instances!"
     "can't get the name"
     ["ha", "ha"]
     "nope"

if including modules added/supered class methods include would be almost
exactly like inheritence - some people think this is a good idea, some not. my
take is just that, in the normal case it's not what you want - but sometimes it
is.

cheers.

-a

···

On Wed, 17 Aug 2005, Joe Van Dyk wrote:

On 8/16/05, Ara.T.Howard <Ara.T.Howard@noaa.gov> wrote:

On Tue, 16 Aug 2005, Joe Van Dyk wrote:

module JoeAccessors
class << self
   def joe_accessor_method *args
     args.each do |arg|
       eval <<-src
         def #{ arg }
           @#{ arg }
         end
         def #{ arg }= value
           @#{ arg } = value
         end
       src
     end
   end
end
end

class Joe
include JoeAccessors
joe_accessor_method :arg1, :arg2
end

test.rb:21: undefined method `joe_accessor_method' for Joe:Class (NoMethodError)

*bangs head against desk*

you're close - the issue is that including a module in another class/module
only inserts the __instance__ methods of the included module into the
class/module doing the reciever. so, altough you've defined a class level
method correctly it doesn't make it into your new class.

What's the rationale behind that?

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
Your life dwells amoung the causes of death
Like a lamp standing in a strong breeze. --Nagarjuna

===============================================================================