Module: Mimi::Core::InheritableProperty::ClassMethods

Defined in:
lib/mimi/core/inheritable_property.rb

Instance Method Summary collapse

Instance Method Details

#inheritable_property(name, opts = {}) ⇒ Object

Declares an inheritable property.

The inheritable property is a special class variable, that is accessible in descendant classes, but each of the descendant classes can alter only its local value.

Once a property is declared, a class method with the name of the property becomes available.

class A
  include Mimi::Core::InheritableProperty

  inheritable_property :var1, default: 1
end

class B < A
end

A.var1 # => 1
B.var1 # => 1

A class method with the name of the property accepts one optional argument -- a new value for the property. If the argument is omitted, current inherited value is returned.

If the argument is present, it sets a new property value for this class and its subclasses, but not for the parent class.

class A
  include Mimi::Core::InheritableProperty

  inheritable_property :var1, default: 1
end

class B < A
  var1 123 # sets new value for B.var1
end

class C < B
end

A.var1 # => 1, the default value, unchanged
B.var1 # => 123
C.var1 # => 123

Working with Hash values

An inherited_property can be declared having the type :hash, then the inherited values are deep-merged in subclasses.

class A
  include Mimi::Core::InheritableProperty

  inheritable_property :var1, default: { a: 1 }
end

class B < A
  var1 b: 2
end

class C < B
end

A.var1 # => { a: 1 }
B.var1 # => { a: 1, b: 2 }
C.var1 # => { a: 1, b: 2 }

Proc as default value

A Proc can be specified instead of literal default value, in which case it will be evaluated when the inherited property value is queried. The passed block will be evaluated in the context of the current class.

class A
  include Mimi::Core::InheritableProperty

  inheritable_property :var1, default: -> { self.name }
end

class B < A
end

class C < B
end

A.var1 # => "A"
B.var1 # => "B"

Parameters:

  • name (Symbol)

    a name for the new inheritable property

  • opts (Hash, nil) (defaults to: {})

    optional parameters for the inheritable property

Options Hash (opts):

  • :default (Object, Proc)

    specifies the literal or Proc as the default value for the property

  • :type (:hash, nil)

    instructs that the property is a Hash or a simple value (inherited values are deep-merged for Hash'es)



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/mimi/core/inheritable_property.rb', line 127

def inheritable_property(name, opts = {})
  unless name.is_a?(Symbol)
    raise ArgumentError, 'Symbol is expected as inheritable_property name'
  end
  var_name = :"@#{name}"
  ip_get_name = :"inheritable_property_get_#{name}"
  ip_set_name = :"inheritable_property_set_#{name}"
  opts_default = opts[:default] && opts[:default].dup

  define_singleton_method(ip_get_name) do |caller|
    if opts[:type] == :hash
      # combine this class' value with a superclass' value
      self_value = instance_variable_get(var_name) || {}
      super_value =
        (superclass.respond_to?(ip_get_name) && superclass.send(ip_get_name, caller)) ||
        (opts_default.is_a?(Proc) ? caller.instance_exec(&opts_default) : opts_default)
      super_value.deep_merge(self_value)
    else
      instance_variable_get(var_name) ||
        (superclass.respond_to?(ip_get_name) && superclass.send(ip_get_name, caller)) ||
        (opts_default.is_a?(Proc) ? caller.instance_exec(&opts_default) : opts_default)
    end
  end

  define_singleton_method(ip_set_name) do |arg|
    if opts[:type] == :hash && !arg.is_a?(Hash) && !arg.nil?
      raise ArgumentError, "Hash or nil is expected as a value for property #{self}.#{name}"
    end
    instance_variable_set(var_name, arg)
  end

  define_singleton_method(name) do |*args|
    if args.empty?
      send(:"inheritable_property_get_#{name}", self)
    else
      send(:"inheritable_property_set_#{name}", args.first)
    end
  end

  define_singleton_method("#{name}!".to_sym) do
    send(name) || (raise "Property #{self}.#{name} is not set")
  end
end