Module: Eco::API::Common::ClassHelpers

Included in:
ClassAutoLoader, ClassHierarchy, Loaders::Base, Loaders::Config, UseCases::BaseCase
Defined in:
lib/eco/api/common/class_helpers.rb

Instance Method Summary collapse

Instance Method Details

#class_resolver(name, klass) ⇒ Object

Creates a class and instance object methods with name name to resolve klass name



7
8
9
10
# File 'lib/eco/api/common/class_helpers.rb', line 7

def class_resolver(name, klass)
  define_singleton_method(name) { resolve_class(klass) }
  define_method(name) { self.class.resolve_class(klass) }
end

#descendants(parent_class: self, direct: false, scope: nil) ⇒ Arrary<Class>

Finds all child classes of the current class.

Parameters:

  • parent_class (Class) (defaults to: self)

    the parent class we want to find children of.

  • direct (Boolean) (defaults to: false)

    it will only include direct child classes.

  • scope (nil, Array) (defaults to: nil)

    to only look for descendants among the ones in scope.

Returns:

  • (Arrary<Class>)

    the child classes in hierarchy order.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/eco/api/common/class_helpers.rb', line 79

def descendants(parent_class: self, direct: false, scope: nil)
  scope ||= ObjectSpace.each_object(::Class)
  return [] if scope.empty?
  scope.select do |klass|
    klass < parent_class
  end.sort do |k1, k2|
    next -1 if k2 < k1
    next  1 if k1 < k2
    0
  end.tap do |siblings|
    if direct
      siblings.reject! do |si|
        siblings.any? {|s| si < s}
      end
    end
  end
end

#descendants?(parent_class: self, direct: false) ⇒ Boolean

Returns true if the current class has child classes, and false otherwise.

Parameters:

  • parent_class (Class) (defaults to: self)

    the parent class we want to find children of.

  • direct (Boolean) (defaults to: false)

    it will only include direct child classes.

Returns:

  • (Boolean)

    true if the current class has child classes, and false otherwise.



100
101
102
# File 'lib/eco/api/common/class_helpers.rb', line 100

def descendants?(parent_class: self, direct: false)
  descendants(parent_class: parent_class, direct: direct).length > 0
end

#inheritable_attrs(*attrs) ⇒ Object

Builds the attr_reader and attr_writer of attrs and registers the associated instance variable as inheritable.



116
117
118
119
120
121
122
123
# File 'lib/eco/api/common/class_helpers.rb', line 116

def inheritable_attrs(*attrs)
  attrs.each do |attr|
    class_eval %(
      class << self; attr_accessor :#{attr} end
    )
  end
  inheritable_class_vars(*attrs)
end

#inheritable_class_vars(*vars) ⇒ Object

Note:

Keeps track on class instance variables that should be inherited by child classes. TODO: this separates the logic of the method to the instance var. Think if would be possible to join them somehow.



110
111
112
113
# File 'lib/eco/api/common/class_helpers.rb', line 110

def inheritable_class_vars(*vars)
  @inheritable_class_vars ||= [:inheritable_class_vars]
  @inheritable_class_vars += vars
end

#inherited(subclass) ⇒ Object

Note:
  • values of the instance variables are copied as they are (no dups or clones)
  • the above means: avoid methods that change the state of the mutable object on it
  • mutating methods would reflect the changes on other classes as well
  • therefore, freeze will be called on the values that are inherited.

This callback method is called whenever a subclass of the current class is created.



131
132
133
134
135
136
137
138
# File 'lib/eco/api/common/class_helpers.rb', line 131

def inherited(subclass)
  super
  inheritable_class_vars.each do |var|
    instance_var = instance_variable_name(var)
    value        = instance_variable_get(instance_var)
    subclass.instance_variable_set(instance_var, value.freeze)
  end
end

#instance_variable_name(name) ⇒ Object

Helper to create an instance variable name

Parameters:

  • the (String, Symbol)

    name of the variable



45
46
47
48
49
# File 'lib/eco/api/common/class_helpers.rb', line 45

def instance_variable_name(name)
  str = name.to_s
  str = "@#{str}" unless str.start_with?("@")
  str
end

#new_class(name, inherits:, parent_space: nil) {|child_class| ... } ⇒ Class

If the class for name exists, it returns it. Otherwise it generates it.

Parameters:

  • name (String, Symbol)

    the name of the new class

  • inherits (Class)

    the parent class to inherit from

  • parent_space (String) (defaults to: nil)

    parent namespace of the generated class, if not given: self

Yields:

  • (child_class)

    configure the new class

Yield Parameters:

  • child_class (Class)

    the new class

Returns:

  • (Class)

    the new generated class



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/eco/api/common/class_helpers.rb', line 58

def new_class(name, inherits:, parent_space: nil)
  name            = name.to_sym.freeze
  class_name      = to_constant(name)
  parent_space    = parent_space ? resolve_class(parent_space) : self
  full_class_name = "#{parent_space}::#{class_name}"

  unless target_class = resolve_class(full_class_name, exception: false)
    target_class = Class.new(inherits)
    parent_space.const_set class_name, target_class
  end

  target_class.tap do |klass|
    yield(klass) if block_given?
  end
end

#resolve_class(klass, exception: true) ⇒ Class

With given a klass name it resolves to an actual Class

Returns:

  • (Class)

    the class that was being searched by name klass.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/eco/api/common/class_helpers.rb', line 14

def resolve_class(klass, exception: true)
  @resolved ||= {}
  @resolved[klass] ||=
    case klass
      when Class
        klass
      when String
        begin
          Kernel.const_get(klass)
        rescue NameError => e
          raise if exception
        end
      when Symbol
        resolve_class(self.send(klass))
      else
        raise "Unknown class: #{klass}" if exception
    end
end

#to_constant(key) ⇒ String

Helper to normalize key into a correct ruby constant name

Parameters:

  • key (String, Symbol)

    to be normalized

Returns:

  • (String)

    a correct constant name



36
37
38
39
40
# File 'lib/eco/api/common/class_helpers.rb', line 36

def to_constant(key)
  str_name = key.to_s.strip.split(/[\-\_ ]/i).compact.map do |str|
    str.slice(0).upcase + str.slice(1..-1).downcase
  end.join("")
end