Class: NRSER::Meta::Props::Prop
- Inherits:
-
Object
- Object
- NRSER::Meta::Props::Prop
- Defined in:
- lib/nrser/meta/props/prop.rb
Overview
‘Prop` instances hold the configuration for a property defined on propertied classes.
Props are immutable by design.
Instance Attribute Summary collapse
-
#defined_in ⇒ Class
readonly
The class the prop was defined in.
-
#name ⇒ Symbol
readonly
The name of the prop, which is used as it’s method name and key where applicable.
-
#source ⇒ Symbol | String
readonly
Optional name of instance variable (including the ‘@` prefix) or getter method (method that takes no arguments) that provides the property’s value.
-
#type ⇒ NRSER::Types::Type
readonly
The type of the valid values for the property.
Instance Method Summary collapse
- #default ⇒ Object
-
#full_name ⇒ String
Full name with class prop was defined in.
- #get(instance) ⇒ return_type
-
#has_default? ⇒ return_type
(also: #default?)
Test if this prop is configured to provide default values -.
-
#initialize(defined_in, name, type: t.any, default: nil, default_from: nil, source: nil, to_data: nil, from_data: nil) ⇒ Prop
constructor
Instantiate a new ‘Prop` instance.
- #instance_variable_source? ⇒ return_type
- #primary? ⇒ return_type
- #set(instance, value) ⇒ return_type
- #set_from_values_hash(instance, **values) ⇒ return_type
- #source? ⇒ return_type
-
#to_data(instance) ⇒ Object
Get the “data” value - a basic scalar or structure of hashes, arrays and scalars, suitable for JSON encoding, etc.
-
#to_s ⇒ String
A short string describing the instance.
- #value_from_data(data) ⇒ return_type
Constructor Details
#initialize(defined_in, name, type: t.any, default: nil, default_from: nil, source: nil, to_data: nil, from_data: nil) ⇒ Prop
Instantiate a new ‘Prop` instance.
You should not need to construct a ‘Prop` directly unless you are doing custom meta-programming - they should be constructed for you via the `.prop` “macro” defined at ClassMethods#prop that is extended in to classes including NRSER::Meta::Props.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/nrser/meta/props/prop.rb', line 86 def initialize defined_in, name, type: t.any, default: nil, default_from: nil, source: nil, to_data: nil, from_data: nil # Set these up first so {#to_s} works in case we need to raise errors. @defined_in = defined_in @name = name @type = t.make type @to_data = to_data @from_data = from_data # Source @source = source # TODO fix this: t.maybe( t.label ).check source # Detect if the source if source.nil? @instance_variable_source = false else # TODO Check that default and default_from are `nil`, make no sense here source_str = source.to_s @instance_variable_source = source_str[0] == '@' end # Defaults # Can't provide both default and default_from unless default.nil? || default_from.nil? raise NRSER::ConflictError.new binding.erb " Both `default:` and `default_from:` keyword args provided when \n constructing <%= self %>. At least one must be `nil`.\n \n default:\n <%= default.pretty_inspect %>\n \n default_from:\n <%= default_from.pretty_inspect %>\n \n ERB\n end\n \n if default_from.nil?\n # We are going to use `default`\n \n # Validate `default` value\n if default.nil?\n # If it's `nil` we still want to see if it's a valid value for the type\n # so we can report if this prop has a default value or not.\n # \n # However, we only need to do that if there is no `source`\n # \n @has_default = if source.nil?\n @type.test default\n else\n # NOTE This is up for debate... does a derived property have a \n # default? What does that even mean?\n true # false ?\n end\n \n else\n # Check that the default value is valid for the type, raising TypeError\n # if it isn't.\n @type.check( default ) { |type:, value:|\n binding.erb <<-ERB\n Default value is not valid for <%= self %>:\n \n <%= value.pretty_inspect %>\n \n ERB\n }\n \n # If we passed the check we know the value is valid\n @has_default = true\n \n # Set the default value to `default`, freezing it since it will be \n # set on instances without any attempt at duplication, which seems like\n # it *might be ok* since a lot of prop'd classes are being used\n # immutably.\n @default_value = default.freeze\n end\n \n else\n # `default_from` is not `nil`, so we're going to use that.\n \n # This means we \"have\" a default since we believe we can use it to make\n # one - the actual values will have to be validates at that point.\n @has_default = true\n \n # And set it.\n # \n # TODO validate it's something reasonable here?\n # \n @default_from = default_from\n end\n \nend\n" |
Instance Attribute Details
#defined_in ⇒ Class (readonly)
The class the prop was defined in.
46 47 48 |
# File 'lib/nrser/meta/props/prop.rb', line 46 def defined_in @defined_in end |
#name ⇒ Symbol (readonly)
The name of the prop, which is used as it’s method name and key where applicable.
54 55 56 |
# File 'lib/nrser/meta/props/prop.rb', line 54 def name @name end |
#source ⇒ Symbol | String (readonly)
Optional name of instance variable (including the ‘@` prefix) or getter method (method that takes no arguments) that provides the property’s value.
Props that have a source are considered derived, those that don’t are called primary.
73 74 75 |
# File 'lib/nrser/meta/props/prop.rb', line 73 def source @source end |
#type ⇒ NRSER::Types::Type (readonly)
The type of the valid values for the property.
61 62 63 |
# File 'lib/nrser/meta/props/prop.rb', line 61 def type @type end |
Instance Method Details
#default ⇒ Object
220 221 222 223 224 225 226 227 228 |
# File 'lib/nrser/meta/props/prop.rb', line 220 def default if default? @default else raise NameError.new NRSER.squish " Prop \#{ self } has no default value.\n END\n end\nend\n" |
#full_name ⇒ String
Full name with class prop was defined in.
199 200 201 |
# File 'lib/nrser/meta/props/prop.rb', line 199 def full_name "#{ defined_in.name }##{ name }" end |
#get(instance) ⇒ return_type
Document get method.
Returns @todo Document return value.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/nrser/meta/props/prop.rb', line 278 def get instance if source? if instance_variable_source? instance.instance_variable_get source else case source when String, Symbol instance.send source when Proc instance.instance_exec &source else raise TypeError.squished " Expected `Prop#source` to be a String, Symbol or Proc;\n found \#{ source.inspect }\n END\n end\n end\n else\n values(instance)[name]\n end\nend\n" |
#has_default? ⇒ return_type Also known as: default?
Test if this prop is configured to provide default values -
212 213 214 |
# File 'lib/nrser/meta/props/prop.rb', line 212 def has_default? @has_default end |
#instance_variable_source? ⇒ return_type
Document instance_variable_source? method.
Returns @todo Document return value.
252 253 254 |
# File 'lib/nrser/meta/props/prop.rb', line 252 def instance_variable_source? @instance_variable_source end |
#primary? ⇒ return_type
Document primary? method.
Returns @todo Document return value.
265 266 267 |
# File 'lib/nrser/meta/props/prop.rb', line 265 def primary? !source? end |
#set(instance, value) ⇒ return_type
Document set method.
Returns @todo Document return value.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/nrser/meta/props/prop.rb', line 309 def set instance, value type.check( value ) do binding.erb " Value of type <%= value.class.name %> for prop <%= self.full_name %> \n failed type check.\n \n Must satisfy type:\n \n <%= type %>\n \n Given value:\n \n <%= value.pretty_inspect %>\n \n END\n end\n \n values(instance)[name] = value\nend\n" |
#set_from_values_hash(instance, **values) ⇒ return_type
Document set_from_hash method.
Returns @todo Document return value.
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/nrser/meta/props/prop.rb', line 338 def set_from_values_hash instance, **values if values.key? name set instance, values[name] else if default? # set instance, if !default.nil? && default.respond_to?( :dup ) # default.dup # else # default # end set instance, default else raise TypeError.new binding.erb " Prop <#= full_name %> has no default value and no value was provided \n in values:\n \n <%= values.pretty_inspect %>\n \n ERB\n end\n end\nend\n" |
#source? ⇒ return_type
Document source? method.
Returns @todo Document return value.
239 240 241 |
# File 'lib/nrser/meta/props/prop.rb', line 239 def source? !@source.nil? end |
#to_data(instance) ⇒ Object
Get the “data” value - a basic scalar or structure of hashes, arrays and scalars, suitable for JSON encoding, etc. - for the property from an instance.
The process depends on the ‘to_data:` keyword provided at property declaration:
-
nil default
-
If the property value responds to ‘#to_data`, the result of invoking that method will be returned.
WARNING
This can cause infinite recursion if an instance has a property value that is also an instance of the same class (as as other more complicated scenarios that boil down to the same problem), but, really, what else would it do in this situation?
This problem can be avoided by by providing a ‘to_data:` keyword when declaring the property that dictates how to handle it’s value. In fact, that was the motivation for adding ‘to_data:`.
-
Otherwise, the value itself is returned, under the assumption that it is already suitable as data.
-
-
Symbol | String
-
The ‘to_data:` string or symbol is sent to the property value (the method with this name is called via Object#send).
-
-
Proc
-
The ‘to_data:` proc is called with the property value as the sole argument and the result is returned as the data.
-
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# File 'lib/nrser/meta/props/prop.rb', line 406 def to_data instance value = get instance case @to_data when nil if value.respond_to? :to_data value.to_data elsif type.respond_to? :to_data type.to_data value else value end when Symbol, String value.send @to_data when Proc @to_data.call value else raise TypeError.squished " Expected `@to_data` to be Symbol, String or Proc;\n found \#{ @to_data.inspect }\n END\n end\nend\n" |
#to_s ⇒ String
Returns a short string describing the instance.
492 493 494 |
# File 'lib/nrser/meta/props/prop.rb', line 492 def to_s "#<#{ self.class.name } #{ full_name }:#{ type }>" end |
#value_from_data(data) ⇒ return_type
Document value_from_data method.
Returns @todo Document return value.
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'lib/nrser/meta/props/prop.rb', line 439 def value_from_data data value = case @from_data when nil # This {Prop} does not have any custom `from_data` instructions, which # means we must rely on the {#type} to covert *data* to a *value*. # if type.has_from_data? type.from_data data else data end when Symbol, String # The custom `from_data` configuration specifies a string or symbol name, # which we interpret as a class method on the defining class and call # with the data to produce a value. @defined_in.send @to_data, data when Proc # The custom `from_data` configuration provides a procedure, which we # call with the data to produce the value. @from_data.call data else raise TypeError.new binding.erb " Expected `@from_data` to be Symbol, String or Proc;\n found <%= @from_data.class %>.\n \n Acceptable types:\n \n - Symbol or String\n - Name of class method on the class this property is defined in\n (<%= @defined_in %>) to call with data to convert it to a\n property value.\n \n - Proc\n - Procedure to call with data to convert it to a property value.\n \n Found `@from_data`:\n \n <%= @from_data.pretty_inspect %>\n \n (type <%= @from_data.class %>)\n \n ERB\n end\n \nend\n" |