class NRSER::Props::Metadata
@todo document NRSER::Props::ClassMetadata class.
Constants
- VARIABLE_NAME
Constants
¶ ↑
Public Class Methods
has_metadata?(klass)
click to toggle source
metadata_for(klass)
click to toggle source
# File lib/nrser/props/metadata.rb, line 55 def self.metadata_for klass klass.instance_variable_get VARIABLE_NAME end
new(klass)
click to toggle source
Instantiate a new `NRSER::Props::ClassMetadata`.
# File lib/nrser/props/metadata.rb, line 68 def initialize klass @klass = klass @props = {} @invariants = Set.new @storage = nil end
Public Instance Methods
default_storage()
click to toggle source
# File lib/nrser/props/metadata.rb, line 350 def default_storage if superclass_has_metadata? superclass_metadata.storage else @storage = NRSER::Props::Storage::InstanceVariable.new end end
each_primary_prop_value_from(values, &block)
click to toggle source
Check primary prop values and fill in defaults, yielding `(Prop, VALUE)` to the `&block`.
Used when initializing instances.
@param [#each_pair | each_index] values
Collection of prop values iterable by key/value pairs or by indexed entries.
@param [Proc<(NRSER::Props::Prop
, VALUE)>] block
Block that will receive primary prop and value pairs.
@raise [TypeError]
If a value is does not satisfy it's {NRSER::Props::Prop#type}.
@raise [ArgumentError]
If `values` doesn't respond to `#each_pair` or `#each_index`.
@raise [NameError]
If a value is not provided for a primary prop and a default can not be created.
@raise [TSort::Cyclic]
If any of the primary prop's {NRSER::Props::Prop#deps} for dependency cycles.
# File lib/nrser/props/metadata.rb, line 215 def each_primary_prop_value_from values, &block primary_props = props only_primary: true props_by_names_and_aliases = {} primary_props.each_value do |prop| [prop.name, *prop.aliases].each do |sym| if props_by_names_and_aliases.key? sym other_prop = props_by_names_and_aliases[sym] prop_sym_is = sym == prop.name ? 'name' : 'alias' other_prop_sym_is = sym == other_prop.name ? 'name' : 'alias' raise NRSER::ConflictError.new \ "Prop", prop.to_desc, prop_sym_is, sym.inspect, "conflicts with", other_prop.to_desc, "of", other_prop_sym_is end props_by_names_and_aliases[sym] = prop end end # 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 # if props_by_names_and_aliases.key? name values_by_name[ props_by_names_and_aliases[name].name ] = value end } 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 # Way to noisy, even for trace # logger.trace "Ready to start loading values", # values: values, # values_by_name: values_by_name, # props_by_names_and_aliases: props_by_names_and_aliases.map { |k, v| # [ k, v.to_desc ] # }.to_h # 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
freeze()
click to toggle source
Calls superclass method
# File lib/nrser/props/metadata.rb, line 372 def freeze super() if superclass_has_metadata? && !superclass_metadata.frozen? superclass_metadata.freeze end end
get_prop(name)
click to toggle source
# File lib/nrser/props/metadata.rb, line 89 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? superclass_metadata.get_prop name else nil end end
Also aliased as: []
invariant(type)
click to toggle source
# File lib/nrser/props/metadata.rb, line 345 def invariant type @invariants.add type end
invariants(only_own: false)
click to toggle source
# File lib/nrser/props/metadata.rb, line 334 def invariants only_own: false result = if !only_own && superclass_has_metadata? superclass_metadata.invariants only_own: false else Set.new end result + @invariants end
prop(name, **opts)
click to toggle source
Define a property.
@param [Symbol] name
The name of the property.
@param [Hash{ Symbol
=> Object
}] opts
Constructor options for {NRSER::Props::Prop}.
@return [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?
# File lib/nrser/props/metadata.rb, line 153 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)
click to toggle source
@todo
Cache / optimize
# File lib/nrser/props/metadata.rb, line 107 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)
click to toggle source
Get a map of property names to property instances.
@param [Boolean] only_own
Don't include super-class properties.
@param [Boolean] only_primary
Don't include properties that have a {NRSER::Props::Prop#source}.
@return [Hash{ Symbol
=> NRSER::Props::Prop
}]
Hash mapping property name to property instance.
# File lib/nrser/props/metadata.rb, line 123 def props only_own: false, only_primary: false result = if !only_own && superclass_has_metadata? superclass_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)
click to toggle source
# File lib/nrser/props/metadata.rb, line 359 def storage value = nil if value.nil? if @storage.nil? default_storage else @storage end else @storage = value end end
superclass_has_metadata?()
click to toggle source
superclass_metadata()
click to toggle source
# File lib/nrser/props/metadata.rb, line 84 def superclass_metadata @klass.superclass.metadata end