module Eco::API::Common::ClassHelpers

Public Instance Methods

class_resolver(name, klass) click to toggle source

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

# 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) click to toggle source

Finds all child classes of the current class. @param parent_class [Class] the parent class we want to find children of. @param direct [Boolean] it will only include direct child classes. @param scope [nil, Array] to only look for descendants among the ones in `scope`. @return [Arrary<Class>] the child classes in hierarchy order.

# 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) click to toggle source

@param parent_class [Class] the parent class we want to find children of. @param direct [Boolean] it will only include direct child classes. @return [Boolean] `true` if the current class has child classes, and `false` otherwise.

# 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) click to toggle source

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

# 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) click to toggle source

Keeps track on class instance variables that should be inherited by child classes. @note

- subclasses will inherit the value as is at that moment
- any change afterwards will be only on the specific class (in line with class instance variables)
- adapted from https://stackoverflow.com/a/10729812/4352306

TODO: this separates the logic of the method to the instance var. Think if would be possible to join them somehow.

# 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) click to toggle source

This callback method is called whenever a subclass of the current class is created. @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.
# File lib/eco/api/common/class_helpers.rb, line 131
def inherited(subclass)
  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) click to toggle source

Helper to create an instance variable `name` @param [String, Symbol] the name of the variable @reutrn [String] the name of the created instance variable

# 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) { |klass| ... } click to toggle source

If the class for `name` exists, it returns it. Otherwise it generates it. @param name [String, Symbol] the name of the new class @param inherits [Class] the parent class to inherit from @param parent_space [String] parent namespace of the generated class, if not given: `self` @yield [child_class] configure the new class @yieldparam child_class [Class] the new class @return [Class] the new generated class

# 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) click to toggle source

With given a `klass` name it resolves to an actual `Class` @return [Class] the class that was being searched by name `klass`.

# 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) click to toggle source

Helper to normalize `key` into a correct `ruby` **constant name** @param key [String, Symbol] to be normalized @return [String] a correct constant name

# 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