Class: NRSER::Props::Metadata

Inherits:
Object
  • Object
show all
Defined in:
lib/nrser/props/metadata.rb

Overview

TODO:

document NRSER::Props::ClassMetadata class.

Constant Summary collapse

VARIABLE_NAME =

Constants

:@__NRSER_metadata__

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass) ⇒ Metadata

Instantiate a new ‘NRSER::Props::ClassMetadata`.



70
71
72
73
74
75
# File 'lib/nrser/props/metadata.rb', line 70

def initialize klass
  @klass = klass
  @props = {}
  @invariants = Set.new
  @storage = nil
end

Class Method Details

.has_metadata?(klass) ⇒ return_type

TODO:

Document has_metadata? method.

Returns @todo Document return value.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



52
53
54
# File 'lib/nrser/props/metadata.rb', line 52

def self.has_metadata? klass
  klass.instance_variable_defined? VARIABLE_NAME
end

.metadata_for(klass) ⇒ Object



57
58
59
# File 'lib/nrser/props/metadata.rb', line 57

def self. klass
  klass.instance_variable_get VARIABLE_NAME
end

Instance Method Details

#default_storageObject



326
327
328
329
330
331
332
# File 'lib/nrser/props/metadata.rb', line 326

def default_storage
  if superclass_has_metadata?
    .storage
  else
    @storage = NRSER::Props::Storage::InstanceVariable.new
  end
end

#each_primary_prop_value_from(values, &block) ⇒ Object

Check primary prop values and fill in defaults, yielding ‘(Prop, VALUE)` to the `&block`.

Used when initializing instances.

Parameters:

  • values (#each_pair | #each_index)

    Collection of prop values iterable by key/value pairs or by indexed entries.

  • &block (Proc<(NRSER::Props::Prop, VALUE)>)

    Block that will receive primary prop and value pairs.

Raises:

  • (TypeError)

    If a value is does not satisfy it’s Prop#type.

  • (ArgumentError)

    If ‘values` doesn’t respond to ‘#each_pair` or `#each_index`.

  • (NameError)

    If a value is not provided for a primary prop and a default can not be created.

  • (TSort::Cyclic)

    If any of the primary prop’s Prop#deps for dependency cycles.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/nrser/props/metadata.rb', line 217

def each_primary_prop_value_from values, &block
  primary_props = props only_primary: true
  
  # Normalize values to a `Hash<Symbol, VALUE>` so everything can deal with
  # one form. Default values will be set here as they're resolved and made
  # available to subsequent {Prop#default} calls.
  values_by_name = {}
  
  if values.respond_to? :each_pair
    values.each_pair { |key, value|
      # Figure out the prop name {Symbol}
      name = case key
      when Symbol
        key
      when String
        key.to_sym
      else
        key.to_s.to_sym
      end
      
      # If the `name` corresponds to a primary prop set it in the values by
      # name
      # 
      # TODO  Should check that the name is not already set?
      # 
      values_by_name[name] = value if primary_props.key? name
    }
  elsif values.respond_to? :each_index
    indexed = []
    
    primary_props.each_value do |prop|
      indexed[prop.index] = prop unless prop.index.nil?
    end
    
    values.each_index { |index|
      prop = indexed[index]
      values_by_name[prop.name] = values[index] if prop
    }
  else
    raise ArgumentError.new binding.erb <<~END
      `source` argument must respond to `#each_pair` or `#each_index`
      
      Found:
      
          <%= source.pretty_inspect %>
      
    END
  end
  
  # Topological sort the primary props by their default dependencies.
  # 
  NRSER::Graph::TSorter.new(
    primary_props.each_value
  ) { |prop, &on_dep_prop|
    # 
    # This block is responsible for receiving a {Prop} and a callback
    # block and invoking that callback block on each of the prop's
    # dependencies (if any).
    # 
    prop.deps.each { |name|
      if primary_props.key? name
        on_dep_prop.call primary_props[name]
      else
        raise RuntimeError.new binding.erb <<~END
          Property <%= prop.full_name %> depends on prop `<%= name %>`,
          but no primary prop with that name could be found!
        END
      end
    }
  }.tsort_each do |prop|
    # {Prop} instances will now be yielded in an order that allows any
    # inter-dependencies to be resolved (as long as there weren't dependency
    # cycles, which {NRSER::Graph::TSorter} will raise if it finds)
    
    # If we have a value for the prop, just check that
    if values_by_name.key? prop.name
      prop.check! values_by_name[prop.name]
    else
      # Otherwise, get the default value, providing the values we already
      # know in case the default is a {Proc} that needs some of them.
      # 
      # We set that value in `values_by_name` so that subsequent
      # {Prop#default} calls can use it.
      # 
      values_by_name[prop.name] = prop.default **values_by_name
    end
    
    # Yield the {Prop} and it's value back to the `&block`
    block.call prop, values_by_name[prop.name]
  end # .tsort_each
end

#freezeObject



348
349
350
351
352
353
354
355
356
# File 'lib/nrser/props/metadata.rb', line 348

def freeze
  super()

  if  superclass_has_metadata? &&
      !.frozen?
    .freeze
  end
  
end

#get_prop(name) ⇒ Object Also known as: []



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/nrser/props/metadata.rb', line 91

def get_prop name
  name = name.to_s.to_sym unless name.is_a?( Symbol )
  
  return @props[name] if @props.key?( name )
  
  if superclass_has_metadata?
    .get_prop name
  else
    nil
  end
end

#invariant(type) ⇒ Object



321
322
323
# File 'lib/nrser/props/metadata.rb', line 321

def invariant type
  @invariants.add type
end

#invariants(only_own: false) ⇒ Object



310
311
312
313
314
315
316
317
318
# File 'lib/nrser/props/metadata.rb', line 310

def invariants only_own: false
  result = if !only_own && superclass_has_metadata?
    .invariants only_own: false
  else
    Set.new
  end
  
  result + @invariants
end

#prop(name, **opts) ⇒ NRSER::Props::Prop

Define a property.

Parameters:

Returns:

  • (NRSER::Props::Prop)

    The newly created prop, thought you probably don’t need it (it’s already all bound up on the class at this point), but why not?



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/nrser/props/metadata.rb', line 155

def prop name, **opts
  t.non_empty_sym.check! name
  
  if @props.key? name
    raise ArgumentError.new binding.erb <<~END
      Prop <%= name.inspect %> already set for <%= @klass %>:
      
          <%= @props[name].inspect %>
    END
  end
  
  prop = NRSER::Props::Prop.new @klass, name, **opts
  @props[name] = prop
  
  prop.names.each do |name|
    if prop.create_reader? name
      @klass.class_eval do
        define_method name do
          prop.get self
        end
      end
    end
    
    if prop.create_writer? name
      @klass.class_eval do
        define_method "#{ name }=" do |value|
          prop.set self, value
        end
      end
    end
  end
  
  prop
end

#prop_names(only_own: false, only_primary: false) ⇒ Object

TODO:

Cache / optimize



109
110
111
# File 'lib/nrser/props/metadata.rb', line 109

def prop_names only_own: false, only_primary: false
  Set.new props( only_own: only_own, only_primary: only_primary ).keys
end

#props(only_own: false, only_primary: false) ⇒ Hash{ Symbol => NRSER::Props::Prop }

Get a map of property names to property instances.

Parameters:

  • only_own: (Boolean) (defaults to: false)

    Don’t include super-class properties.

  • only_primary: (Boolean) (defaults to: false)

    Don’t include properties that have a Prop#source.

Returns:



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/nrser/props/metadata.rb', line 125

def props only_own: false, only_primary: false
  result = if !only_own && superclass_has_metadata?
    .props only_own: only_own,
                              only_primary: only_primary
  else
    {}
  end
  
  if only_primary
    @props.each {|name, prop| result[name] = prop if prop.primary? }
  else
    result.merge! @props
  end
  
  result
end

#storage(value = nil) ⇒ Object



335
336
337
338
339
340
341
342
343
344
345
# File 'lib/nrser/props/metadata.rb', line 335

def storage value = nil
  if value.nil?
    if @storage.nil?
      default_storage
    else
      @storage
    end
  else
    @storage = value
  end
end

#superclass_has_metadata?Boolean

Instance Methods

Returns:

  • (Boolean)


81
82
83
# File 'lib/nrser/props/metadata.rb', line 81

def superclass_has_metadata?
  @klass.superclass.respond_to? :metadata
end

#superclass_metadataObject



86
87
88
# File 'lib/nrser/props/metadata.rb', line 86

def 
   @klass.superclass.
end