1

I was reading another SO question Enums in Ruby and it had the following code snippet:

class Enum

  private

  def self.enum_attr(name, num)
    name = name.to_s

    define_method(name + '?') do
      @attrs & num != 0
    end

    define_method(name + '=') do |set|
      if set
        @attrs |= num
      else
        @attrs &= ~num
      end
    end
  end

  public

  def initialize(attrs = 0)
    @attrs = attrs
  end

  def to_i
    @attrs
  end
end

As I understand this, this is defining a class method named enum_attr, is that correct? What I'm unsure of is what it means to have the define_method statements inside of the enum_attr method.

Then later on that post it shows the class being extended as follows

class FileAttributes < Enum
  enum_attr :readonly,       0x0001
  enum_attr :hidden,         0x0002
end

I don't quite understand what this second part does - can someone explain?

Community
  • 1
  • 1
Jeff Storey
  • 56,312
  • 72
  • 233
  • 406

2 Answers2

2

In Enum, a method, enum_attr, is defined on the class's singleton, and is available to all subclasses. This method is in scope in the class definition body, and in FileAttributes it is being called with the arguments :readonly, 0x0001 and then :hidden, 0x0002.

When enum_attr is called (let's look at just the first call, enum_attr :readonly, 0x0001), it defines two methods: readonly? & readonly=(set). The result of this call to enum_attr is functionally equivalent to writing out the following in FileAttributes:

def readonly?
  @attrs & 0x0001 != 0
end

def readonly=(set)
  if set
    @attrs |= 0x0001
  else
    @attrs &= ~0x0001
  end
end

Since the block passed to define_method is a closure, the variable num from the scope in which the block is passed is still in scope when you call the method that is defined. In other words, the num variable passed in to enum_attr is still available within the generated methods readonly? & readonly= when they are called later from a different scope.

define_method must be used because the name of the method is being dynamically generated (i.e., we do not know the name of the method ahead of time).

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • Thanks for the explanation. So enum_attr :readonly, 0x0001 is just a method call with two parameters? If so, is that code just in the body of FileAttributes? Does it not need to be in a method block or in initialize? – Jeff Storey Jun 24 '12 at 23:54
  • If you're coming from a background in, say, Java, you might think so, but Ruby doesn't work that way. You can run any code you like in a `class` block. Inside a `class` block, `self` will be the class which is being defined, so any method calls without a receiver will go to the class object (in this case `FileAttributes`). – Alex D Jun 24 '12 at 23:56
  • Ah, I see, thanks. How can FileAttributes actually call enum_attr though if it is listed as private? – Jeff Storey Jun 24 '12 at 23:58
  • `private` methods can never be called with a receiver, so you can only call them on `self`. In this case, `self` is `FileAttributes`, just as it is inside a call to a class method on `FileAttributes`. – Alex D Jun 24 '12 at 23:59
  • 1
    @JeffStorey `private` in Ruby is not the same as `private` is some other languages. Private methods in Ruby can be accessed by subclasses. – Andrew Marshall Jun 25 '12 at 00:01
  • I reread that section of pickaxe, thanks. As I now seem to understand it, subclasses can call their own instance's private methods, whereas with protected methods, a subclass can call any instance's (of that type) protected methods. – Jeff Storey Jun 25 '12 at 00:12
1

You are looking at a class method which generates true/false attributes which are stored using a bitfield. Do you have a background in C (or perhaps Java)? If so, you may be familiar with bitfields and general "bit twiddling". Most Internet resources on these topics will be related to C; but you can still use them in Ruby and other languages.

In this case, you don't gain anything from storing your boolean attributes in individual bits, and I would advise you not to actually use this code. You would be better off using a different instance variable for each attribute, with true or false values.

Alex D
  • 29,755
  • 7
  • 80
  • 126
  • thanks. I am familiar with bid twiddling. I wasn't actually trying to use this code, but I'm learning ruby and I'm trying to understand the code more than anything. – Jeff Storey Jun 24 '12 at 23:55
  • OK. There are some very important concepts for you to take from this code: that classes in Ruby are also objects, and you can call methods on them; that there is nothing "special" about class and method definitions in Ruby (they are just code which is executed like any other code); also, if you're not familiar with closures, this is a good introduction for you. To understand Ruby, you *need* to "get" closures, so make sure you understand what is going on there. – Alex D Jun 25 '12 at 00:01
  • Yep, I have a groovy/Java background, so I use closured quite frequently in groovy. I'm finding a lot of the code concepts in ruby easy to understand because I've done a lot of groovy work. – Jeff Storey Jun 25 '12 at 00:03