Class: Module

Inherits:
Object show all
Defined in:
lib/utilrb/module/include.rb,
lib/utilrb/module/ancestor_p.rb,
lib/utilrb/module/cached_enum.rb,
lib/utilrb/module/define_method.rb,
lib/utilrb/module/attr_predicate.rb,
lib/utilrb/module/attr_enumerable.rb,
lib/utilrb/module/define_or_reuse.rb,
lib/utilrb/module/const_defined_here_p.rb,
lib/utilrb/module/inherited_enumerable.rb

Instance Method Summary collapse

Instance Method Details

#__include_single_module(mod) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/utilrb/module/include.rb', line 22

def __include_single_module(mod)
	if mod.const_defined?(:ModuleExtension)
 if is_a?(Module)
		unless const_defined?(:ModuleExtension)
  const_set(:ModuleExtension, Module.new)
		end
		const_get(:ModuleExtension).class_eval do
  __instance_include__ mod.const_get(:ModuleExtension)
		end
		extend mod.const_get(:ModuleExtension)
 end
        # Do nothing on classes
	end
	if mod.const_defined?(:ClassExtension)
 if !is_a?(Class)
		unless const_defined?(:ClassExtension)
  const_set(:ClassExtension, Module.new)
		end
		const_get(:ClassExtension).class_eval do
  __instance_include__ mod.const_get(:ClassExtension)
		end
 else
		extend mod.const_get(:ClassExtension)
 end
	end

	__instance_include__ mod
end

#attr_enumerable(name, attr_name = name, enumerator = :each, &init_block) ⇒ Object

Support for attributes that are enumerables. This methods defines two methods:

obj.attr_name # => enumerable
obj.each_name(key = nil) { |value| ... } # => obj

The first one returns the enumerable object itself. The second one iterates on the values in attr_name. If key is not nil, then #attr_name is supposed to be a hash of enumerables, and key is used to select the enumerable to iterate on.

The following calls are equivalent

obj.attr_name.each { |value| ... }
obj.each_name { |value| ... }

And these two are equivalent:

obj.attr_name[key].each { |value| ... }
obj.each_name(key) { |value| ... }

enumerator is the name of the enumeration method we should use. init_block, if given, should return the value at which we should initialize #attr_name.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/utilrb/module/attr_enumerable.rb', line 25

def attr_enumerable(name, attr_name = name, enumerator = :each, &init_block)
	class_eval do
 attribute(attr_name, &init_block)
	end
    class_eval <<-EOF
        def each_#{name}(key = nil, &iterator)
            return unless #{attr_name}
            if key
                #{attr_name}[key].#{enumerator}(&iterator)
            else
                #{attr_name}.#{enumerator}(&iterator)
            end
		self
        end
    EOF
end

#attr_predicate(name, writable = false) ⇒ Object

Defines a name? predicate, and if writable is true a #name= method. Note that name can end with ‘?’, in which case the ending ‘?’ is removed.

The methods use the @name instance variable internally



7
8
9
10
11
12
13
14
15
16
# File 'lib/utilrb/module/attr_predicate.rb', line 7

def attr_predicate(name, writable = false)
	attr_name = name.to_s.gsub(/\?$/, '')
	attr_reader attr_name
	alias_method "#{attr_name}?", attr_name
	remove_method attr_name

	if writable
 class_eval "def #{attr_name}=(value); @#{attr_name} = !!value end"
	end
end

#cached_enum(enum_name, name, with_arg) ⇒ Object

Creates enum_#{name} method which returs an Enumerator object for the each_#{enum_name} method. This enumerator is created once.

If with_arg is true, it is supposed that the ‘each_’ method requires one argument, which is given in argument of the ‘enum’ method. In that case, an enumerator is created for each argument



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/utilrb/module/cached_enum.rb', line 21

def cached_enum(enum_name, name, with_arg)
    include CachedValuesSupport
	if with_arg
 class_eval <<-EOD
		def enum_#{name}(arg)
  @enum_#{name} ||= Hash.new
                cached_variables << :@enum_#{name}
  @enum_#{name}[arg] ||= enum_for(:each_#{enum_name}, arg)
		end
		EOD
	else
 class_eval <<-EOD
		def enum_#{name}
                cached_variables << :@enum_#{name}
  @enum_#{name} ||= enum_for(:each_#{enum_name})
		end
		EOD
	end
end

#const_defined_here?(name) ⇒ Boolean

Returns:

  • (Boolean)


4
5
6
# File 'lib/utilrb/module/const_defined_here_p.rb', line 4

def const_defined_here?(name)
    const_defined?(name, false)
end

#define_inherited_enumerable(name, attribute_name = name, options = Hash.new, &init) ⇒ Object

:nodoc:



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/utilrb/module/inherited_enumerable.rb', line 7

def define_inherited_enumerable(name, attribute_name = name, options = Hash.new, &init) # :nodoc:
    # Set up the attribute accessor
	attribute(attribute_name, &init)
	class_eval { private "#{attribute_name}=" }

	options[:enum_with] ||= :each

    class_eval <<-EOF
    def all_#{name}; each_#{name}.to_a end
    def self_#{name}; @#{attribute_name} end
    EOF

    if options[:map]
        class_eval <<-EOF
        def each_#{name}(key = nil, uniq = true)
     if !block_given?
         return enum_for(:each_#{name}, key, uniq)
		end

		if key
  for klass in ancestors
			if klass.instance_variable_defined?(:@#{attribute_name})
   if klass.#{attribute_name}.has_key?(key)
yield(klass.#{attribute_name}[key])
return self if uniq
   end
			end
  end
		elsif !uniq
  for klass in ancestors
			if klass.instance_variable_defined?(:@#{attribute_name})
   klass.#{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
			end
  end
		else
  seen = Set.new
  for klass in ancestors
			if klass.instance_variable_defined?(:@#{attribute_name})
   klass.#{attribute_name}.#{options[:enum_with]} do |el| 
unless seen.include?(el.first)
    seen << el.first
    yield(el)
end
   end
			end
  end

		end
            self
        end
        def find_#{name}(key)
            each_#{name}(key, true) do |value|
                return value
            end
            nil
        end
        def has_#{name}?(key)
		for klass in ancestors
  if klass.instance_variable_defined?(:@#{attribute_name})
			return true if klass.#{attribute_name}.has_key?(key)
  end
		end
		false
        end
        EOF
    else
        class_eval <<-EOF
        def each_#{name}
     if !block_given?
         return enum_for(:each_#{name})
		end

		for klass in ancestors
  if klass.instance_variable_defined?(:@#{attribute_name})
			klass.#{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
  end
		end
		self
        end
        EOF
    end
end

#define_method_with_block(name, &mdef) ⇒ Object

Emulate block-passing by converting the block into a Proc object and passing it to the given block as last argument dule)

For instance

define_method('my_method') do |a, &block|
end

Is written as define_method_with_block(‘my_method’) do |block, a| end

The block is given first to allow the following construct:

define_method_with_block(‘my_method’) do |block, *args| end

block is nil if no block is given during the method call



21
22
23
24
25
26
27
28
# File 'lib/utilrb/module/define_method.rb', line 21

def define_method_with_block(name, &mdef)
	class_eval <<-EOD
 def #{name}(*args, &block)
		dmwb_#{name}_user_definition(block, *args) 
 end
	EOD
	define_method("dmwb_#{name}_user_definition", &mdef)
end

#define_or_reuse(name, value = nil) ⇒ Object

:call-seq

define_or_reuse(name, value)   ->              value
define_or_reuse(name) { ... }  ->              value

Defines a new constant under a given module, or reuse the already-existing value if the constant is already defined.

In the first form, the method gets its value from its argument. In the second case, it calls the provided block



12
13
14
15
16
17
18
19
20
# File 'lib/utilrb/module/define_or_reuse.rb', line 12

def define_or_reuse(name, value = nil)
    if const_defined_here?(name)
        const_get(name)
    else
        module_eval do
            const_set(name, (value || yield))
        end
    end
end

#has_ancestor?(klass) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


3
4
5
# File 'lib/utilrb/module/ancestor_p.rb', line 3

def has_ancestor?(klass) # :nodoc:
    self <= klass
end

#include(*mods) ⇒ Object

Includes a module in this one, with support for class extensions

If a module defines a ClassExtension submodule, then

  • if it is included in a module, the target’s ClassExtension module includes the source ClassExtension (and if there is no ClassExtension in the target, it is created)

  • if it is included in a Class, the ClassExtension module extends the class.



16
17
18
19
20
# File 'lib/utilrb/module/include.rb', line 16

def include(*mods)
    mods.each do |mod|
        __include_single_module(mod)
    end
end

#inherited_enumerable(name, attribute_name = name, options = Hash.new, &init) ⇒ Object

Defines an attribute as being enumerable in the class instance and in the whole class inheritance hierarchy. More specifically, it defines a each_#{name}(&iterator) instance method and a each_#{name}(&iterator) class method which iterates (in order) on

  • the instance #name attribute

  • the singleton class #name attribute

  • the class #name attribute

  • the superclass #name attribute

  • the superclass’ superclass #name attribute

This method can be used on modules, in which case the module is used as if it was part of the inheritance hierarchy.

The name option defines the enumeration method name (value will define a each_value method). attribute_name defines the attribute name. init is a block called to initialize the attribute. Valid options in options are:

map

If true, the attribute should respond to []. In that case, the enumeration method is each_value(key = nil, uniq = false) If key is given, we iterate on the values given by attribute[key]. If uniq is true, the enumeration will yield at most one value for each key found (so, if both key and uniq are given, the enumeration yields at most one value). See the examples below

enum_with

the enumeration method of the enumerable, if it is not each

Example

Let’s define some classes and look at the ancestor chain

class A;  end
module M; end
class B < A; include M end
A.ancestors # => [A, Object, Kernel]
B.ancestors # => [B, M, A, Object, Kernel]

Attributes for which ‘map’ is not set

class A
  inherited_enumerable("value", "values") do
      Array.new
  end
end
module M
  inherited_enumerable("mod") do
      Array.new
  end
end

A.values << 1 # => [1]
B.values << 2 # => [2]
M.mod << 1 # => [1]
b = B.new 
class << b
    self.values << 3 # => [3]
    self.mod << 4 # => [4]
end
M.mod << 2 # => [1, 2]

A.enum_for(:each_value).to_a # => [1]
B.enum_for(:each_value).to_a # => [2, 1]
b.singleton_class.enum_for(:each_value).to_a # => [3, 2, 1]
b.singleton_class.enum_for(:each_mod).to_a # => [4, 1, 2]

Attributes for which ‘map’ is set

class A
  inherited_enumerable("mapped", "map", :map => true) do
      Hash.new { |h, k| h[k] = Array.new }
  end
end

A.map['name'] = 'A' # => "A"
A.map['universe'] = 42
B.map['name'] = 'B' # => "B"
B.map['half_of_it'] = 21

Let’s see what happens if we don’t specify the key option.

A.enum_for(:each_mapped).to_a # => [["name", "A"], ["universe", 42]]

If the uniq option is set (the default), we see only B’s value for ‘name’

B.enum_for(:each_mapped).to_a # => [["half_of_it", 21], ["name", "B"], ["universe", 42]]

If the uniq option is not set, we see both values for ‘name’. Note that since ‘map’ is a Hash, the order of keys in one class is not guaranteed. Nonetheless, we have the guarantee that values from B appear before those from A

B.enum_for(:each_mapped, nil, false).to_a # => [["half_of_it", 21], ["name", "B"], ["name", "A"], ["universe", 42]]

Now, let’s see how ‘key’ behaves

A.enum_for(:each_mapped, 'name').to_a # => ["A"]
B.enum_for(:each_mapped, 'name').to_a # => ["B"]
B.enum_for(:each_mapped, 'name', false).to_a # => ["B", "A"]


183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/utilrb/module/inherited_enumerable.rb', line 183

def inherited_enumerable(name, attribute_name = name, options = Hash.new, &init)
	singleton_class.class_eval { define_inherited_enumerable(name, attribute_name, options, &init) }

	if is_a?(Module) && !is_a?(Class)
 unless const_defined?(:ClassExtension)
		const_set(:ClassExtension, Module.new)
 end
 class_extension = const_get(:ClassExtension)
 class_extension.class_eval do
		define_inherited_enumerable(name, attribute_name, options, &init)
 end
	end
end