portrayal 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +15 -0
 - data/README.md +61 -10
 - data/bin/bench +78 -0
 - data/bin/module +15 -0
 - data/lib/portrayal/default.rb +9 -10
 - data/lib/portrayal/schema.rb +64 -71
 - data/lib/portrayal/version.rb +1 -1
 - data/lib/portrayal.rb +11 -23
 - data/portrayal.gemspec +1 -0
 - metadata +18 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: acf906a0356260f40fbe25b94b12f72f8c696ee723dff3c2f7dc0f3817154fd0
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 0bb4b5a5d4982a59f6e66cd7fa2fe7eeec93b7880537c9e605c47f2d1b0df009
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 5d3543a802ba4dad5b8c746fa2e187b0debb95bd52354509bb29c2d88e6977d622aeafc8fca3db0a65811a9918c7044985c3355bea78799a0b7730b84da28e21
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 66f61afd2be1633c12739d49db088265d90279b622b4f4bf050bba2c4d7dc6444ce232e16be00485702ef9285fe2507758d8fdfee447414ac35bc44fe07a5e89
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -2,6 +2,21 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            ## Unreleased
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
      
 5 
     | 
    
         
            +
            ## 0.9.0 - 2023-05-06
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            None of these changes should break anything for you, only speed things up, unless you're doing something very weird.
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            * Rewrite internals to improve runtime performance. No longer depend on `portrayal.attributes`, instead generating ruby code that references keywords literally (much more efficient).
         
     | 
| 
      
 10 
     | 
    
         
            +
            * All attr_readers and other methods such as `eql?`, `==`, `freeze`, etc are now included as a module, rather than class_eval'ed into your class. This lets you use `super` when overriding them.
         
     | 
| 
      
 11 
     | 
    
         
            +
            * Class method `portrayal` now appears when you call `extend Portrayal`, and not after the first `keyword` declaration. (Instance methods are still added upon `keyword`.)
         
     | 
| 
      
 12 
     | 
    
         
            +
            * Remove `portrayal[]` shortcut that accessed `portrayal.schema` (use `portrayal.schema[]` directly instead).
         
     | 
| 
      
 13 
     | 
    
         
            +
            * Remove `portrayal.render_initialize`.
         
     | 
| 
      
 14 
     | 
    
         
            +
            * Add `portrayal.module`, which is the module included in your struct.
         
     | 
| 
      
 15 
     | 
    
         
            +
            * Add `portrayal.render_module_code`, which renders the code for the module.
         
     | 
| 
      
 16 
     | 
    
         
            +
            * Bring back class comparison to `==` (reverses a change in 0.3.0). Upon further research, it seems class comparison is always necessary.
         
     | 
| 
      
 17 
     | 
    
         
            +
            * Methods `==`, `eql?`, `hash`, `initialize_dup`, `initialize_clone`, and `freeze` now operate on @instance @variables, not reader method return values.
         
     | 
| 
      
 18 
     | 
    
         
            +
            * Methods `deconstruct` and `deconstruct_keys` now quietly exclude private/protected keywords.
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
       5 
20 
     | 
    
         
             
            ## 0.8.0 - 2023-01-27
         
     | 
| 
       6 
21 
     | 
    
         | 
| 
       7 
22 
     | 
    
         
             
            * Add pattern matching support (`#deconstruct` and `#deconstruct_keys`).
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            
         
     | 
| 
      
 1 
     | 
    
         
            +
            [](https://badge.fury.io/rb/portrayal) 
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            # Portrayal
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
         @@ -8,7 +8,7 @@ Inspired by: 
     | 
|
| 
       8 
8 
     | 
    
         
             
              - Piotr Solnica's [virtus](https://github.com/solnic/virtus)
         
     | 
| 
       9 
9 
     | 
    
         
             
              - Everything [Michel Martens](https://github.com/soveran)
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
            Portrayal is a minimalist gem (~ 
     | 
| 
      
 11 
     | 
    
         
            +
            Portrayal is a minimalist gem (~110 loc, no dependencies) for building struct-like classes. It provides a small yet powerful step up from plain ruby with its one and only `keyword` method.
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
            ```ruby
         
     | 
| 
       14 
14 
     | 
    
         
             
            class Person < MySuperClass
         
     | 
| 
         @@ -38,6 +38,7 @@ When you call `keyword`: 
     | 
|
| 
       38 
38 
     | 
    
         
             
            * It defines `#hash` for hash equality
         
     | 
| 
       39 
39 
     | 
    
         
             
            * It defines `#dup` and `#clone` that propagate to all keyword values
         
     | 
| 
       40 
40 
     | 
    
         
             
            * It defines `#freeze` that propagates to all keyword values
         
     | 
| 
      
 41 
     | 
    
         
            +
            * It defines `#deconstruct` and `#deconstruct_keys` for pattern matching
         
     | 
| 
       41 
42 
     | 
    
         
             
            * It creates a nested class when you supply a block
         
     | 
| 
       42 
43 
     | 
    
         
             
            * It inherits parent's superclass when creating a nested class
         
     | 
| 
       43 
44 
     | 
    
         | 
| 
         @@ -76,6 +77,14 @@ class Person < MySuperClass 
     | 
|
| 
       76 
77 
     | 
    
         
             
                super
         
     | 
| 
       77 
78 
     | 
    
         
             
              end
         
     | 
| 
       78 
79 
     | 
    
         | 
| 
      
 80 
     | 
    
         
            +
              def deconstruct
         
     | 
| 
      
 81 
     | 
    
         
            +
                [ name, age, favorite_fruit, address ]
         
     | 
| 
      
 82 
     | 
    
         
            +
              end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
              def deconstruct_keys(*)
         
     | 
| 
      
 85 
     | 
    
         
            +
                { name: name, age: age, favorite_fruit: favorite_fruit, address: address }
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
       79 
88 
     | 
    
         
             
              def initialize_dup(source)
         
     | 
| 
       80 
89 
     | 
    
         
             
                @name = source.name.dup
         
     | 
| 
       81 
90 
     | 
    
         
             
                @age = source.age.dup
         
     | 
| 
         @@ -123,6 +132,14 @@ class Person < MySuperClass 
     | 
|
| 
       123 
132 
     | 
    
         
             
                  super
         
     | 
| 
       124 
133 
     | 
    
         
             
                end
         
     | 
| 
       125 
134 
     | 
    
         | 
| 
      
 135 
     | 
    
         
            +
                def deconstruct
         
     | 
| 
      
 136 
     | 
    
         
            +
                  [ street, city ]
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                def deconstruct_keys(*)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  { street: street, city: city }
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
       126 
143 
     | 
    
         
             
                def initialize_dup(source)
         
     | 
| 
       127 
144 
     | 
    
         
             
                  @street = source.street.dup
         
     | 
| 
       128 
145 
     | 
    
         
             
                  @city = source.city.dup
         
     | 
| 
         @@ -336,9 +353,9 @@ else 
     | 
|
| 
       336 
353 
     | 
    
         
             
            end # => "matched"
         
     | 
| 
       337 
354 
     | 
    
         
             
            ```
         
     | 
| 
       338 
355 
     | 
    
         | 
| 
       339 
     | 
    
         
            -
            ###  
     | 
| 
      
 356 
     | 
    
         
            +
            ### Introspection
         
     | 
| 
       340 
357 
     | 
    
         | 
| 
       341 
     | 
    
         
            -
            Every class that  
     | 
| 
      
 358 
     | 
    
         
            +
            Every class that extends Portrayal receives a method called `portrayal`. This method is a schema of your object with some additional helpers.
         
     | 
| 
       342 
359 
     | 
    
         | 
| 
       343 
360 
     | 
    
         
             
            #### `portrayal.keywords`
         
     | 
| 
       344 
361 
     | 
    
         | 
| 
         @@ -348,7 +365,7 @@ Get all keyword names. 
     | 
|
| 
       348 
365 
     | 
    
         
             
            Address.portrayal.keywords # => [:street, :city, :postcode, :country]
         
     | 
| 
       349 
366 
     | 
    
         
             
            ```
         
     | 
| 
       350 
367 
     | 
    
         | 
| 
       351 
     | 
    
         
            -
            #### `portrayal.attributes`
         
     | 
| 
      
 368 
     | 
    
         
            +
            #### `portrayal.attributes(object)`
         
     | 
| 
       352 
369 
     | 
    
         | 
| 
       353 
370 
     | 
    
         
             
            Get all names + values as a hash.
         
     | 
| 
       354 
371 
     | 
    
         | 
| 
         @@ -371,12 +388,14 @@ Portrayal steps back from things like type enforcement, coercion, and writer met 
     | 
|
| 
       371 
388 
     | 
    
         | 
| 
       372 
389 
     | 
    
         
             
            #### Good Constructors
         
     | 
| 
       373 
390 
     | 
    
         | 
| 
       374 
     | 
    
         
            -
             
     | 
| 
      
 391 
     | 
    
         
            +
            Since a portrayal object is read-only (nothing stops you from adding writers, but I will personally frown upon you), you must set all its values in a constructor. This is a good thing, because it lets us study, coerce, and validate all the passed-in arguments in one convenient place. We're assured that once instantiated, the object is valid. And of course we can have multiple constructors if needed. They serve as adapters for different kinds of input.
         
     | 
| 
       375 
392 
     | 
    
         | 
| 
       376 
393 
     | 
    
         
             
            ```ruby
         
     | 
| 
       377 
394 
     | 
    
         
             
            class Address < ApplicationStruct
         
     | 
| 
       378 
395 
     | 
    
         
             
              class << self
         
     | 
| 
       379 
396 
     | 
    
         
             
                def from_form(params)
         
     | 
| 
      
 397 
     | 
    
         
            +
                  raise ArgumentError, 'invalid postcode' unless postcode =~ /\A\d+\z/
         
     | 
| 
      
 398 
     | 
    
         
            +
             
     | 
| 
       380 
399 
     | 
    
         
             
                  new \
         
     | 
| 
       381 
400 
     | 
    
         
             
                    street:   params[:street].to_s,
         
     | 
| 
       382 
401 
     | 
    
         
             
                    city:     params[:city].to_s,
         
     | 
| 
         @@ -400,16 +419,18 @@ class Address < ApplicationStruct 
     | 
|
| 
       400 
419 
     | 
    
         
             
            end
         
     | 
| 
       401 
420 
     | 
    
         
             
            ```
         
     | 
| 
       402 
421 
     | 
    
         | 
| 
       403 
     | 
    
         
            -
            Good constructors can  
     | 
| 
      
 422 
     | 
    
         
            +
            Good constructors can depend on one another to successively convert arguments into keywords. This is similar to how in functional languages one can use recursion and pattern matching.
         
     | 
| 
       404 
423 
     | 
    
         | 
| 
       405 
424 
     | 
    
         
             
            ```ruby
         
     | 
| 
       406 
425 
     | 
    
         
             
            class Email < ApplicationStruct
         
     | 
| 
       407 
426 
     | 
    
         
             
              class << self
         
     | 
| 
      
 427 
     | 
    
         
            +
                # Extract parts of an email from JSON, and kick it over to from_parts.
         
     | 
| 
       408 
428 
     | 
    
         
             
                def from_publishing_service_json(json)
         
     | 
| 
       409 
429 
     | 
    
         
             
                  subject, header, body, footer = *JSON.parse(json)
         
     | 
| 
       410 
430 
     | 
    
         
             
                  from_parts(subject: subject, header: header, body: body, footer: footer)
         
     | 
| 
       411 
431 
     | 
    
         
             
                end
         
     | 
| 
       412 
432 
     | 
    
         | 
| 
      
 433 
     | 
    
         
            +
                # Combine parts into the final keywords: subject and body.
         
     | 
| 
       413 
434 
     | 
    
         
             
                def from_parts(subject:, header:, body:, footer:)
         
     | 
| 
       414 
435 
     | 
    
         
             
                  new(subject: subject, body: "#{header}#{body}#{footer}")
         
     | 
| 
       415 
436 
     | 
    
         
             
                end
         
     | 
| 
         @@ -443,15 +464,45 @@ class Address < ApplicationStruct 
     | 
|
| 
       443 
464 
     | 
    
         
             
            end
         
     | 
| 
       444 
465 
     | 
    
         
             
            ```
         
     | 
| 
       445 
466 
     | 
    
         | 
| 
      
 467 
     | 
    
         
            +
            If a particular constructor doesn't belong on your object (i.e. a 3rd party module is responsible for parsing its own data and producing your object) — you don't need to have a special constructor. Remember that each portrayal object comes with `.new`, which accepts every keyword directly. Let the module do all the parsing on its side and call `.new` with final values.
         
     | 
| 
      
 468 
     | 
    
         
            +
             
     | 
| 
       446 
469 
     | 
    
         
             
            #### No Reinventing The Wheel
         
     | 
| 
       447 
470 
     | 
    
         | 
| 
       448 
     | 
    
         
            -
            Portrayal leans on Ruby  
     | 
| 
      
 471 
     | 
    
         
            +
            Portrayal leans on Ruby's built-in features as much as possible. For initialize and default values it generates standard ruby keyword arguments. You can see all the code portrayal generates for your objects by running `YourClass.portrayal.render_module_code`.
         
     | 
| 
       449 
472 
     | 
    
         | 
| 
       450 
473 
     | 
    
         
             
            ```irb
         
     | 
| 
       451 
     | 
    
         
            -
            Address.portrayal. 
     | 
| 
       452 
     | 
    
         
            -
             
     | 
| 
      
 474 
     | 
    
         
            +
            [1] pry(main)> puts Address.portrayal.render_module_code
         
     | 
| 
      
 475 
     | 
    
         
            +
            attr_accessor :street, :city, :postcode, :country
         
     | 
| 
      
 476 
     | 
    
         
            +
            protected :street=, :city=, :postcode=, :country=
         
     | 
| 
      
 477 
     | 
    
         
            +
            def initialize(street:, city:, postcode:, country: self.class.portrayal.schema[:country]); @street = street.is_a?(::Portrayal::Default) ? street.(self) : street; @city = city.is_a?(::Portrayal::Default) ? city.(self) : city; @postcode = postcode.is_a?(::Portrayal::Default) ? postcode.(self) : postcode; @country = country.is_a?(::Portrayal::Default) ? country.(self) : country end
         
     | 
| 
      
 478 
     | 
    
         
            +
            def hash; [self.class, {street: @street, city: @city, postcode: @postcode, country: @country}].hash end
         
     | 
| 
      
 479 
     | 
    
         
            +
            def ==(other); self.class == other.class && @street == other.instance_variable_get('@street') && @city == other.instance_variable_get('@city') && @postcode == other.instance_variable_get('@postcode') && @country == other.instance_variable_get('@country') end
         
     | 
| 
      
 480 
     | 
    
         
            +
            alias eql? ==
         
     | 
| 
      
 481 
     | 
    
         
            +
            def freeze; @street.freeze; @city.freeze; @postcode.freeze; @country.freeze; super end
         
     | 
| 
      
 482 
     | 
    
         
            +
            def initialize_dup(src); @street = src.instance_variable_get('@street').dup; @city = src.instance_variable_get('@city').dup; @postcode = src.instance_variable_get('@postcode').dup; @country = src.instance_variable_get('@country').dup; super end
         
     | 
| 
      
 483 
     | 
    
         
            +
            def initialize_clone(src); @street = src.instance_variable_get('@street').clone; @city = src.instance_variable_get('@city').clone; @postcode = src.instance_variable_get('@postcode').clone; @country = src.instance_variable_get('@country').clone; super end
         
     | 
| 
      
 484 
     | 
    
         
            +
            def deconstruct
         
     | 
| 
      
 485 
     | 
    
         
            +
              public_syms = [:street, :city, :postcode, :country].select { |s| self.class.public_method_defined?(s) }
         
     | 
| 
      
 486 
     | 
    
         
            +
              public_syms.map { |s| public_send(s) }
         
     | 
| 
      
 487 
     | 
    
         
            +
            end
         
     | 
| 
      
 488 
     | 
    
         
            +
            def deconstruct_keys(keys)
         
     | 
| 
      
 489 
     | 
    
         
            +
              filtered_keys = [:street, :city, :postcode, :country].select {|s| self.class.public_method_defined?(s) }
         
     | 
| 
      
 490 
     | 
    
         
            +
              filtered_keys &= keys if Array === keys
         
     | 
| 
      
 491 
     | 
    
         
            +
              Hash[filtered_keys.map { |k| [k, public_send(k)] }]
         
     | 
| 
      
 492 
     | 
    
         
            +
            end
         
     | 
| 
       453 
493 
     | 
    
         
             
            ```
         
     | 
| 
       454 
494 
     | 
    
         | 
| 
      
 495 
     | 
    
         
            +
            #### Implementation decisions
         
     | 
| 
      
 496 
     | 
    
         
            +
             
     | 
| 
      
 497 
     | 
    
         
            +
            Here are some key architectural decisions that took a lot of thinking. If you have good counter-arguments please make an issue, or contact me on [mastodon](https://ruby.social/@maxim) / [twitter](https://twitter.com/hakunin).
         
     | 
| 
      
 498 
     | 
    
         
            +
             
     | 
| 
      
 499 
     | 
    
         
            +
            1. **Why do methods `#==`, `#eql?`, `#hash` rely on @instance @variables instead of calling reader methods?**  
         
     | 
| 
      
 500 
     | 
    
         
            +
               Portrayal makes a careful assumption on what most people would expect from object equality: a comparison of type and runtime state (which is what instance variables are). Portrayal avoids comparing object structure and method return values, because it's too situational whether they should participate in equality or not. If you have such a situation, you're welcome to redefine `==` in your class.
         
     | 
| 
      
 501 
     | 
    
         
            +
            2. **Why do methods `clone` and `dup` copy @instance @variables instead of calling reader methods?**  
         
     | 
| 
      
 502 
     | 
    
         
            +
               As with the reason for `==`, when we clone an object, we want to clone its type and runtime state. Not the artifacts of its structure. It's too presumptious for a clone to assume that method outputs are authoritative. If objects are written deterministically, then by cloning their inner runtime state we should get the same reader method outputs anyway. If you are doing something else, you're welcome to redefine `initialize_clone`/`initialize_dup` in your class.
         
     | 
| 
      
 503 
     | 
    
         
            +
            3. **Why does pattern matching (`deconstruct`/`deconstruct_keys`) call reader methods rather than reading @instance @variables?**  
         
     | 
| 
      
 504 
     | 
    
         
            +
               Unlike equality or object replication, in case of pattern matching we're no longer trying to figure out object's identity, rather we are now an external caller working directly with the values that an object exposes. That's why portrayal lets pattern matching depend on reader methods that get to decide how to expose data outwardly, while making a conscious effort to exclude private and protected readers. You're welcome to override `deconstruct` and `deconstruct_keys` in your class if you'd like to do something different.
         
     | 
| 
      
 505 
     | 
    
         
            +
             
     | 
| 
       455 
506 
     | 
    
         
             
            ## Development
         
     | 
| 
       456 
507 
     | 
    
         | 
| 
       457 
508 
     | 
    
         
             
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         
     | 
    
        data/bin/bench
    ADDED
    
    | 
         @@ -0,0 +1,78 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'bundler/setup'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'portrayal'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'benchmark/ips'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            class Address1
         
     | 
| 
      
 8 
     | 
    
         
            +
              extend Portrayal
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              keyword :street
         
     | 
| 
      
 11 
     | 
    
         
            +
              keyword :city
         
     | 
| 
      
 12 
     | 
    
         
            +
              keyword :postal_code
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def ==(other)
         
     | 
| 
      
 15 
     | 
    
         
            +
                self.class.portrayal.attributes(self) ==
         
     | 
| 
      
 16 
     | 
    
         
            +
                  other.class.portrayal.attributes(other)
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            class Address2
         
     | 
| 
      
 21 
     | 
    
         
            +
              extend Portrayal
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              keyword :street
         
     | 
| 
      
 24 
     | 
    
         
            +
              keyword :city
         
     | 
| 
      
 25 
     | 
    
         
            +
              keyword :postal_code
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            Address2.portrayal.module.module_eval <<-RUBY
         
     | 
| 
      
 29 
     | 
    
         
            +
              def ==(o)
         
     | 
| 
      
 30 
     | 
    
         
            +
                street == o.street && city == o.city && postal_code == o.postal_code
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            RUBY
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            class Address3
         
     | 
| 
      
 35 
     | 
    
         
            +
              extend Portrayal
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              keyword :street
         
     | 
| 
      
 38 
     | 
    
         
            +
              keyword :city
         
     | 
| 
      
 39 
     | 
    
         
            +
              keyword :postal_code
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              def ==(o)
         
     | 
| 
      
 42 
     | 
    
         
            +
                { street: street, city: city, postal_code: postal_code } ==
         
     | 
| 
      
 43 
     | 
    
         
            +
                  { street: o.street, city: o.city, postal_code: o.postal_code }
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            a1 = Address1.new(street: 'street', city: 'city', postal_code: 123)
         
     | 
| 
      
 48 
     | 
    
         
            +
            a2 = Address2.new(street: 'street', city: 'city', postal_code: 123)
         
     | 
| 
      
 49 
     | 
    
         
            +
            a3 = Address3.new(street: 'street', city: 'city', postal_code: 123)
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            Benchmark.ips do |x|
         
     | 
| 
      
 53 
     | 
    
         
            +
              x.report("equality-via-hash-construction") {
         
     | 
| 
      
 54 
     | 
    
         
            +
                a1 == a1
         
     | 
| 
      
 55 
     | 
    
         
            +
              }
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              x.report("equality-via-boolean-expression") {
         
     | 
| 
      
 58 
     | 
    
         
            +
                a2 == a2
         
     | 
| 
      
 59 
     | 
    
         
            +
              }
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
              x.report("equality-via-hash-literal") {
         
     | 
| 
      
 62 
     | 
    
         
            +
                a3 == a3
         
     | 
| 
      
 63 
     | 
    
         
            +
              }
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
              x.compare!
         
     | 
| 
      
 66 
     | 
    
         
            +
            end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            Benchmark.ips do |x|
         
     | 
| 
      
 69 
     | 
    
         
            +
              x.report("hash of literal Hash") {
         
     | 
| 
      
 70 
     | 
    
         
            +
                { foo: 'foo', bar: 'bar', baz: 'baz' }.hash
         
     | 
| 
      
 71 
     | 
    
         
            +
              }
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              x.report("hash of literal Array") {
         
     | 
| 
      
 74 
     | 
    
         
            +
                [ [:foo, 'foo'], [:bar, 'bar'], [:baz, 'baz'] ].hash
         
     | 
| 
      
 75 
     | 
    
         
            +
              }
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
              x.compare!
         
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
    
        data/bin/module
    ADDED
    
    | 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'bundler/setup'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'portrayal'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class Address
         
     | 
| 
      
 7 
     | 
    
         
            +
              extend Portrayal
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              keyword :street
         
     | 
| 
      
 10 
     | 
    
         
            +
              keyword :city
         
     | 
| 
      
 11 
     | 
    
         
            +
              keyword :postcode
         
     | 
| 
      
 12 
     | 
    
         
            +
              keyword :country, default: nil
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            puts Address.portrayal.render_module_code
         
     | 
    
        data/lib/portrayal/default.rb
    CHANGED
    
    | 
         @@ -1,13 +1,12 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
     | 
    
         
            -
               
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            class Portrayal::Default
         
     | 
| 
      
 2 
     | 
    
         
            +
              attr_reader :value
         
     | 
| 
      
 3 
     | 
    
         
            +
              protected :value
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
                end
         
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
                def call?; @callable end
         
     | 
| 
       11 
     | 
    
         
            -
                def initialize_dup(src); super; @value = src.value.dup end
         
     | 
| 
      
 5 
     | 
    
         
            +
              def initialize(value)
         
     | 
| 
      
 6 
     | 
    
         
            +
                @value = value
         
     | 
| 
      
 7 
     | 
    
         
            +
                @callable = value.is_a?(Proc) && !value.lambda?
         
     | 
| 
       12 
8 
     | 
    
         
             
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def call(obj); @callable ? obj.instance_exec(&value) : value end
         
     | 
| 
      
 11 
     | 
    
         
            +
              def initialize_dup(src); @value = src.value.dup; super end
         
     | 
| 
       13 
12 
     | 
    
         
             
            end
         
     | 
    
        data/lib/portrayal/schema.rb
    CHANGED
    
    | 
         @@ -1,79 +1,72 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'portrayal/default'
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
               
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                  def ==(other)
         
     | 
| 
       19 
     | 
    
         
            -
                    return super unless other.class.is_a?(Portrayal)
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                    self.class.portrayal.attributes(self) ==
         
     | 
| 
       22 
     | 
    
         
            -
                      self.class.portrayal.attributes(other)
         
     | 
| 
       23 
     | 
    
         
            -
                  end
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
                  def freeze
         
     | 
| 
       26 
     | 
    
         
            -
                    self.class.portrayal.attributes(self).values.each(&:freeze)
         
     | 
| 
       27 
     | 
    
         
            -
                    super
         
     | 
| 
       28 
     | 
    
         
            -
                  end
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                  def initialize_dup(source)
         
     | 
| 
       31 
     | 
    
         
            -
                    self.class.portrayal.attributes(source).each do |key, value|
         
     | 
| 
       32 
     | 
    
         
            -
                      instance_variable_set("@\#{key}", value.dup)
         
     | 
| 
       33 
     | 
    
         
            -
                    end
         
     | 
| 
       34 
     | 
    
         
            -
                    super
         
     | 
| 
       35 
     | 
    
         
            -
                  end
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                  def initialize_clone(source)
         
     | 
| 
       38 
     | 
    
         
            -
                    self.class.portrayal.attributes(source).each do |key, value|
         
     | 
| 
       39 
     | 
    
         
            -
                      instance_variable_set("@\#{key}", value.clone)
         
     | 
| 
       40 
     | 
    
         
            -
                    end
         
     | 
| 
       41 
     | 
    
         
            -
                    super
         
     | 
| 
       42 
     | 
    
         
            -
                  end
         
     | 
| 
       43 
     | 
    
         
            -
                RUBY
         
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                def initialize; @schema = {}  end
         
     | 
| 
       46 
     | 
    
         
            -
                def keywords;   @schema.keys  end
         
     | 
| 
       47 
     | 
    
         
            -
                def [](name);   @schema[name] end
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
                def attributes(object)
         
     | 
| 
       50 
     | 
    
         
            -
                  Hash[object.class.portrayal.keywords.map { |k| [k, object.send(k)] }]
         
     | 
| 
       51 
     | 
    
         
            -
                end
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
                def camelize(string); string.to_s.gsub(/(?:^|_+)([^_])/) { $1.upcase } end
         
     | 
| 
      
 3 
     | 
    
         
            +
            class Portrayal::Schema
         
     | 
| 
      
 4 
     | 
    
         
            +
              attr_reader :schema, :module
         
     | 
| 
      
 5 
     | 
    
         
            +
              NULL = Object.new.freeze
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              def initialize;       @schema = {}; @module = Module.new                end
         
     | 
| 
      
 8 
     | 
    
         
            +
              def keywords;         @schema.keys                                      end
         
     | 
| 
      
 9 
     | 
    
         
            +
              def attributes(obj);  Hash[keywords.map { |k| [k, obj.send(k)] }]       end
         
     | 
| 
      
 10 
     | 
    
         
            +
              def camelize(string); string.to_s.gsub(/(?:^|_+)([^_])/) { $1.upcase }  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def initialize_dup(src)
         
     | 
| 
      
 13 
     | 
    
         
            +
                @schema = src.schema.transform_values(&:dup)
         
     | 
| 
      
 14 
     | 
    
         
            +
                @module = src.module.dup
         
     | 
| 
      
 15 
     | 
    
         
            +
                super
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
       54 
17 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
                 
     | 
| 
      
 18 
     | 
    
         
            +
              def add_keyword(name, default)
         
     | 
| 
      
 19 
     | 
    
         
            +
                name = name.to_sym
         
     | 
| 
      
 20 
     | 
    
         
            +
                @schema.delete(name) # Forcing keyword to be added at the end of the hash.
         
     | 
| 
      
 21 
     | 
    
         
            +
                @schema[name] = default.equal?(NULL) ? nil : Portrayal::Default.new(default)
         
     | 
| 
      
 22 
     | 
    
         
            +
                @module.module_eval(render_module_code)
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
       60 
24 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
      
 25 
     | 
    
         
            +
              def render_module_code
         
     | 
| 
      
 26 
     | 
    
         
            +
                args, inits, syms, hash, eqls, dups, clones, setters, freezes =
         
     | 
| 
      
 27 
     | 
    
         
            +
                  '', '', '', '', '', '', '', '', ''
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                @schema.each do |k, default|
         
     | 
| 
      
 30 
     | 
    
         
            +
                  args  << "#{k}:#{default && " self.class.portrayal.schema[:#{k}]"}, "
         
     | 
| 
      
 31 
     | 
    
         
            +
                  inits << "@#{k} = #{k}.is_a?(::Portrayal::Default) ? #{k}.(self) : #{k}; "
         
     | 
| 
      
 32 
     | 
    
         
            +
                  syms  << ":#{k}, "
         
     | 
| 
      
 33 
     | 
    
         
            +
                  hash  << "#{k}: @#{k}, "
         
     | 
| 
      
 34 
     | 
    
         
            +
                  eqls  << "@#{k} == other.instance_variable_get('@#{k}') && "
         
     | 
| 
      
 35 
     | 
    
         
            +
                  dups  << "@#{k} = src.instance_variable_get('@#{k}').dup; "
         
     | 
| 
      
 36 
     | 
    
         
            +
                  clones  << "@#{k} = src.instance_variable_get('@#{k}').clone; "
         
     | 
| 
      
 37 
     | 
    
         
            +
                  setters << ":#{k}=, "
         
     | 
| 
      
 38 
     | 
    
         
            +
                  freezes << "@#{k}.freeze; "
         
     | 
| 
       63 
39 
     | 
    
         
             
                end
         
     | 
| 
       64 
40 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                 
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
      
 41 
     | 
    
         
            +
                args.chomp!(', ')    # key1:, key2: self.class.portrayal.schema[:key2]
         
     | 
| 
      
 42 
     | 
    
         
            +
                inits.chomp!('; ')   # Assignments in initialize
         
     | 
| 
      
 43 
     | 
    
         
            +
                syms.chomp!(', ')    # :key1, :key2
         
     | 
| 
      
 44 
     | 
    
         
            +
                hash.chomp!(', ')    # key1: @key1, key2: @key2
         
     | 
| 
      
 45 
     | 
    
         
            +
                eqls.chomp!(' && ')  # @key1 == other.instance_variable_get('@key1') &&
         
     | 
| 
      
 46 
     | 
    
         
            +
                dups.chomp!('; ')    # @key1 = src.instance_variable_get('@key1').dup;
         
     | 
| 
      
 47 
     | 
    
         
            +
                clones.chomp!('; ')  # @key1 = src.instance_variable_get('@key1').clone;
         
     | 
| 
      
 48 
     | 
    
         
            +
                setters.chomp!(', ') # :key1=, :key2=
         
     | 
| 
      
 49 
     | 
    
         
            +
                freezes.chomp!('; ') # @key1.freeze; @key2.freeze
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                <<-RUBY
         
     | 
| 
      
 52 
     | 
    
         
            +
            attr_accessor #{syms}
         
     | 
| 
      
 53 
     | 
    
         
            +
            protected #{setters}
         
     | 
| 
      
 54 
     | 
    
         
            +
            def initialize(#{args}); #{inits} end
         
     | 
| 
      
 55 
     | 
    
         
            +
            def hash; [self.class, {#{hash}}].hash end
         
     | 
| 
      
 56 
     | 
    
         
            +
            def ==(other); self.class == other.class && #{eqls} end
         
     | 
| 
      
 57 
     | 
    
         
            +
            alias eql? ==
         
     | 
| 
      
 58 
     | 
    
         
            +
            def freeze; #{freezes}; super end
         
     | 
| 
      
 59 
     | 
    
         
            +
            def initialize_dup(src); #{dups}; super end
         
     | 
| 
      
 60 
     | 
    
         
            +
            def initialize_clone(src); #{clones}; super end
         
     | 
| 
      
 61 
     | 
    
         
            +
            def deconstruct
         
     | 
| 
      
 62 
     | 
    
         
            +
              public_syms = [#{syms}].select { |s| self.class.public_method_defined?(s) }
         
     | 
| 
      
 63 
     | 
    
         
            +
              public_syms.map { |s| public_send(s) }
         
     | 
| 
      
 64 
     | 
    
         
            +
            end
         
     | 
| 
      
 65 
     | 
    
         
            +
            def deconstruct_keys(keys)
         
     | 
| 
      
 66 
     | 
    
         
            +
              filtered_keys = [#{syms}].select {|s| self.class.public_method_defined?(s) }
         
     | 
| 
      
 67 
     | 
    
         
            +
              filtered_keys &= keys if Array === keys
         
     | 
| 
      
 68 
     | 
    
         
            +
              Hash[filtered_keys.map { |k| [k, public_send(k)] }]
         
     | 
| 
      
 69 
     | 
    
         
            +
            end
         
     | 
| 
      
 70 
     | 
    
         
            +
                RUBY
         
     | 
| 
       78 
71 
     | 
    
         
             
              end
         
     | 
| 
       79 
72 
     | 
    
         
             
            end
         
     | 
    
        data/lib/portrayal/version.rb
    CHANGED
    
    
    
        data/lib/portrayal.rb
    CHANGED
    
    | 
         @@ -2,32 +2,20 @@ require 'portrayal/version' 
     | 
|
| 
       2 
2 
     | 
    
         
             
            require 'portrayal/schema'
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            module Portrayal
         
     | 
| 
       5 
     | 
    
         
            -
               
     | 
| 
      
 5 
     | 
    
         
            +
              attr_reader :portrayal
         
     | 
| 
      
 6 
     | 
    
         
            +
              def self.extended(c); c.instance_variable_set(:@portrayal, Schema.new) end
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
              def  
     | 
| 
       8 
     | 
    
         
            -
                 
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                    def inherited(base)
         
     | 
| 
       12 
     | 
    
         
            -
                      base.instance_variable_set('@portrayal', portrayal.dup)
         
     | 
| 
       13 
     | 
    
         
            -
                    end
         
     | 
| 
       14 
     | 
    
         
            -
                  end
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                  @portrayal = Schema.new
         
     | 
| 
       17 
     | 
    
         
            -
                  class_eval(Schema::DEFINITION_OF_OBJECT_ENHANCEMENTS)
         
     | 
| 
       18 
     | 
    
         
            -
                end
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                attr_accessor name
         
     | 
| 
       21 
     | 
    
         
            -
                protected "#{name}="
         
     | 
| 
      
 8 
     | 
    
         
            +
              def inherited(c)
         
     | 
| 
      
 9 
     | 
    
         
            +
                c.instance_variable_set(:@portrayal, portrayal.dup)
         
     | 
| 
      
 10 
     | 
    
         
            +
                c.include(c.portrayal.module)
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
       22 
12 
     | 
    
         | 
| 
      
 13 
     | 
    
         
            +
              def keyword(name, default: Schema::NULL, define: nil, &block)
         
     | 
| 
      
 14 
     | 
    
         
            +
                include portrayal.module
         
     | 
| 
       23 
15 
     | 
    
         
             
                portrayal.add_keyword(name, default)
         
     | 
| 
       24 
     | 
    
         
            -
                 
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
                 
     | 
| 
       27 
     | 
    
         
            -
                  kw_class = Class.new(superclass) { extend Portrayal }
         
     | 
| 
       28 
     | 
    
         
            -
                  const_set(define || portrayal.camelize(name), kw_class).class_eval(&block)
         
     | 
| 
       29 
     | 
    
         
            -
                end
         
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
      
 16 
     | 
    
         
            +
                return name unless block_given?
         
     | 
| 
      
 17 
     | 
    
         
            +
                nested = Class.new(superclass) { extend ::Portrayal }
         
     | 
| 
      
 18 
     | 
    
         
            +
                const_set(define || portrayal.camelize(name), nested).class_eval(&block)
         
     | 
| 
       31 
19 
     | 
    
         
             
                name
         
     | 
| 
       32 
20 
     | 
    
         
             
              end
         
     | 
| 
       33 
21 
     | 
    
         
             
            end
         
     | 
    
        data/portrayal.gemspec
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: portrayal
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.9.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Max Chernyak
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2023- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2023-05-07 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: rake
         
     | 
| 
         @@ -52,6 +52,20 @@ dependencies: 
     | 
|
| 
       52 
52 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       53 
53 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       54 
54 
     | 
    
         
             
                    version: '0.14'
         
     | 
| 
      
 55 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 56 
     | 
    
         
            +
              name: benchmark-ips
         
     | 
| 
      
 57 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 58 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 59 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 60 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 61 
     | 
    
         
            +
                    version: '2.11'
         
     | 
| 
      
 62 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 63 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 64 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 66 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 67 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 68 
     | 
    
         
            +
                    version: '2.11'
         
     | 
| 
       55 
69 
     | 
    
         
             
            description: Inspired by dry-initializer and virtus, portrayal is a minimalist gem
         
     | 
| 
       56 
70 
     | 
    
         
             
              that takes a somewhat different approach to building struct-like classes. It steps
         
     | 
| 
       57 
71 
     | 
    
         
             
              away from types, coersion, and writer methods in favor of encouraging well-designed
         
     | 
| 
         @@ -71,8 +85,10 @@ files: 
     | 
|
| 
       71 
85 
     | 
    
         
             
            - LICENSE.txt
         
     | 
| 
       72 
86 
     | 
    
         
             
            - README.md
         
     | 
| 
       73 
87 
     | 
    
         
             
            - Rakefile
         
     | 
| 
      
 88 
     | 
    
         
            +
            - bin/bench
         
     | 
| 
       74 
89 
     | 
    
         
             
            - bin/console
         
     | 
| 
       75 
90 
     | 
    
         
             
            - bin/loc
         
     | 
| 
      
 91 
     | 
    
         
            +
            - bin/module
         
     | 
| 
       76 
92 
     | 
    
         
             
            - bin/setup
         
     | 
| 
       77 
93 
     | 
    
         
             
            - lib/portrayal.rb
         
     | 
| 
       78 
94 
     | 
    
         
             
            - lib/portrayal/default.rb
         
     |