lab42_data_class 0.6.0 → 0.7.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/README.md +86 -400
- data/lib/lab42/data_class/duplicate_definition_error.rb +9 -0
- data/lib/lab42/data_class/proxy/constraints/maker.rb +2 -0
- data/lib/lab42/data_class/proxy/derived.rb +21 -0
- data/lib/lab42/data_class/proxy/memos.rb +8 -0
- data/lib/lab42/data_class/proxy.rb +55 -9
- data/lib/lab42/data_class/version.rb +1 -1
- data/lib/lab42/data_class.rb +18 -3
- metadata +4 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2ae30c490e9b798403214f49c4cbc43564bf47b741878f9aa174e27dc17a85b8
         | 
| 4 | 
            +
              data.tar.gz: 2e7ab04142e8eb38206ba14bd03464465844b307087708d382e253fc40dd8f1b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f40faa136f73115809cea4f73d722188ea62e9299319313b1ffcbcc852c710de0c399c3d1c54dcdb1ae1de57173baabfb66607f636ff62b747f133774e4c205a
         | 
| 7 | 
            +
              data.tar.gz: 294c975087b12a3cab2731eb78840d735dbdeac923f1c2edb8d5beaf598848caf18d571e3129b42dac0ad2d276657507f4df10268a41863ffb2150ea11df5d8a
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 |  | 
| 2 | 
            +
             | 
| 2 3 | 
             
            [](https://codeclimate.com/github/RobertDober/lab42_data_class)
         | 
| 3 4 | 
             
            [](https://github.com/robertdober/lab42_data_class/actions)
         | 
| 4 5 | 
             
            [](https://coveralls.io/github/RobertDober/lab42_data_class?branch=main)
         | 
| @@ -11,9 +12,31 @@ | |
| 11 12 |  | 
| 12 13 | 
             
            An Immutable DataClass for Ruby
         | 
| 13 14 |  | 
| 14 | 
            -
            Exposes a class factory function `Kernel::DataClass` and a  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 15 | 
            +
            Exposes a class factory function `Kernel::DataClass` and a module `Lab42::DataClass` which can
         | 
| 16 | 
            +
            extend classes to become _Data Classes_.
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            Also exposes two _tuple_ classes, `Pair` and `Triple`
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## Synopsis
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            Having immutable Objects has many well known advantages that I will not ponder upon in detail here.
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            One advantage which is of particular interest though is that, as every, _modification_ is in fact the
         | 
| 25 | 
            +
            creation of a new object **strong contraints** on the data can **easily** be maintained, and this
         | 
| 26 | 
            +
            library makes that available to the user.
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            Therefore we can summarise the features (or not so features, that is for you to decide and you to chose to use or not):
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              - Immutable with an Interface à la `OpenStruct`
         | 
| 31 | 
            +
              - Attributes are predefined and can have **default values**
         | 
| 32 | 
            +
              - Construction with _keyword arguments_, **exclusively**
         | 
| 33 | 
            +
              - Conversion to `Hash` instances (if you must)
         | 
| 34 | 
            +
              - Pattern matching exactly like `Hash` instances
         | 
| 35 | 
            +
              - Possibility to impose **strong constraints** on attributes
         | 
| 36 | 
            +
              - Predefined constraints and concise syntax for constraints
         | 
| 37 | 
            +
              - Possibility to impose **arbitrary validation** (constraints on the whole object)
         | 
| 38 | 
            +
              - Declaration of **dependent attributes** which are memoized (thank you _Immutability_)
         | 
| 39 | 
            +
              - Inheritance with **mixin of other dataclasses** (multiple if you must)
         | 
| 17 40 |  | 
| 18 41 | 
             
            ## Usage
         | 
| 19 42 |  | 
| @@ -33,465 +56,128 @@ In your code | |
| 33 56 | 
             
            require 'lab42/data_class'
         | 
| 34 57 | 
             
            ```
         | 
| 35 58 |  | 
| 59 | 
            +
            ## Speculations (literate specs)
         | 
| 36 60 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
            Well let us [speculate about](https://github.com/RobertDober/speculate_about) it to find out:
         | 
| 40 | 
            -
             | 
| 41 | 
            -
            ## Context `DataClass`
         | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
            ### Context: `DataClass` function
         | 
| 45 | 
            -
             | 
| 46 | 
            -
            Given
         | 
| 47 | 
            -
            ```ruby
         | 
| 48 | 
            -
                let(:my_data_class) { DataClass(:name, email: nil) }
         | 
| 49 | 
            -
                let(:my_instance) { my_data_class.new(name: "robert") }
         | 
| 50 | 
            -
            ```
         | 
| 51 | 
            -
             | 
| 52 | 
            -
            Then we can access its fields
         | 
| 53 | 
            -
            ```ruby
         | 
| 54 | 
            -
                expect(my_instance.name).to eq("robert")
         | 
| 55 | 
            -
                expect(my_instance.email).to be_nil
         | 
| 56 | 
            -
            ```
         | 
| 57 | 
            -
             | 
| 58 | 
            -
            But we cannot access undefined fields
         | 
| 59 | 
            -
            ```ruby
         | 
| 60 | 
            -
                expect{ my_instance.undefined }.to raise_error(NoMethodError)
         | 
| 61 | 
            -
            ```
         | 
| 62 | 
            -
             | 
| 63 | 
            -
            And we need to provide values to fields without defaults
         | 
| 64 | 
            -
            ```ruby
         | 
| 65 | 
            -
                expect{ my_data_class.new(email: "some@mail.org") }
         | 
| 66 | 
            -
                  .to raise_error(ArgumentError, "missing initializers for [:name]")
         | 
| 67 | 
            -
            ```
         | 
| 68 | 
            -
            And we can extract the values
         | 
| 69 | 
            -
            ```ruby
         | 
| 70 | 
            -
                expect(my_instance.to_h).to eq(name: "robert", email: nil)
         | 
| 71 | 
            -
            ```
         | 
| 72 | 
            -
             | 
| 73 | 
            -
            #### Context: Immutable → self
         | 
| 74 | 
            -
             | 
| 75 | 
            -
            Then `my_instance` is frozen:
         | 
| 76 | 
            -
            ```ruby
         | 
| 77 | 
            -
                expect(my_instance).to be_frozen
         | 
| 78 | 
            -
            ```
         | 
| 79 | 
            -
            And we cannot even mute `my_instance`  by means of metaprogramming
         | 
| 80 | 
            -
            ```ruby
         | 
| 81 | 
            -
                expect{ my_instance.instance_variable_set("@x", nil) }.to raise_error(FrozenError)
         | 
| 82 | 
            -
            ```
         | 
| 83 | 
            -
             | 
| 84 | 
            -
            #### Context: Immutable → Cloning
         | 
| 85 | 
            -
             | 
| 86 | 
            -
            Given
         | 
| 87 | 
            -
            ```ruby
         | 
| 88 | 
            -
                let(:other_instance) { my_instance.merge(email: "robert@mail.provider") }
         | 
| 89 | 
            -
            ```
         | 
| 90 | 
            -
            Then we have a new instance with the old instance unchanged
         | 
| 91 | 
            -
            ```ruby
         | 
| 92 | 
            -
                expect(other_instance.to_h).to eq(name: "robert", email: "robert@mail.provider")
         | 
| 93 | 
            -
                expect(my_instance.to_h).to eq(name: "robert", email: nil)
         | 
| 94 | 
            -
            ```
         | 
| 95 | 
            -
            And the new instance is frozen again
         | 
| 96 | 
            -
            ```ruby
         | 
| 97 | 
            -
                expect(other_instance).to be_frozen
         | 
| 98 | 
            -
            ```
         | 
| 99 | 
            -
             | 
| 100 | 
            -
            ### Context: Defining behavior with blocks
         | 
| 101 | 
            -
             | 
| 102 | 
            -
            Given
         | 
| 103 | 
            -
            ```ruby
         | 
| 104 | 
            -
                let :my_data_class do
         | 
| 105 | 
            -
                  DataClass :value, prefix: "<", suffix: ">" do
         | 
| 106 | 
            -
                    def show
         | 
| 107 | 
            -
                      [prefix, value, suffix].join
         | 
| 108 | 
            -
                    end
         | 
| 109 | 
            -
                  end
         | 
| 110 | 
            -
                end
         | 
| 111 | 
            -
                let(:my_instance) { my_data_class.new(value: 42) }
         | 
| 112 | 
            -
            ```
         | 
| 113 | 
            -
             | 
| 114 | 
            -
            Then I have defined a method on my dataclass
         | 
| 115 | 
            -
            ```ruby
         | 
| 116 | 
            -
                expect(my_instance.show).to eq("<42>")
         | 
| 117 | 
            -
            ```
         | 
| 118 | 
            -
             | 
| 119 | 
            -
            ### Context: Equality
         | 
| 120 | 
            -
             | 
| 121 | 
            -
            Given two instances of a DataClass
         | 
| 122 | 
            -
            ```ruby
         | 
| 123 | 
            -
                let(:data_class) { DataClass :a }
         | 
| 124 | 
            -
                let(:instance1) { data_class.new(a: 1) }
         | 
| 125 | 
            -
                let(:instance2) { data_class.new(a: 1) }
         | 
| 126 | 
            -
            ```
         | 
| 127 | 
            -
            Then they are equal in the sense of `==` and `eql?`
         | 
| 128 | 
            -
            ```ruby
         | 
| 129 | 
            -
                expect(instance1).to eq(instance2)
         | 
| 130 | 
            -
                expect(instance2).to eq(instance1)
         | 
| 131 | 
            -
                expect(instance1 == instance2).to be_truthy
         | 
| 132 | 
            -
                expect(instance2 == instance1).to be_truthy
         | 
| 133 | 
            -
            ```
         | 
| 134 | 
            -
            But not in the sense of `equal?`, of course
         | 
| 135 | 
            -
            ```ruby
         | 
| 136 | 
            -
                expect(instance1).not_to be_equal(instance2)
         | 
| 137 | 
            -
                expect(instance2).not_to be_equal(instance1)
         | 
| 138 | 
            -
            ```
         | 
| 139 | 
            -
             | 
| 140 | 
            -
            #### Context: Immutability of `dataclass` modified classes
         | 
| 141 | 
            -
             | 
| 142 | 
            -
            Then we still get frozen instances
         | 
| 143 | 
            -
            ```ruby
         | 
| 144 | 
            -
                expect(instance1).to be_frozen
         | 
| 145 | 
            -
            ```
         | 
| 146 | 
            -
             | 
| 147 | 
            -
            ### Context: Inheritance
         | 
| 148 | 
            -
             | 
| 149 | 
            -
            ... is a no, we do not want inheritance although we **like** code reuse, how to do it then?
         | 
| 150 | 
            -
             | 
| 151 | 
            -
            Well there shall be many different possibilities, depending on your style, use case and
         | 
| 152 | 
            -
            context, here is just one example:
         | 
| 153 | 
            -
             | 
| 154 | 
            -
            Given a class factory
         | 
| 155 | 
            -
            ```ruby
         | 
| 156 | 
            -
                let :token do
         | 
| 157 | 
            -
                  ->(*a, **k) do
         | 
| 158 | 
            -
                    DataClass(*a, **(k.merge(text: "")))
         | 
| 159 | 
            -
                    end
         | 
| 160 | 
            -
                end
         | 
| 161 | 
            -
            ```
         | 
| 162 | 
            -
             | 
| 163 | 
            -
            Then we have reused the `token` successfully
         | 
| 164 | 
            -
            ```ruby
         | 
| 165 | 
            -
                empty = token.()
         | 
| 166 | 
            -
                integer = token.(:value)
         | 
| 167 | 
            -
                boolean = token.(value: false)
         | 
| 168 | 
            -
             | 
| 169 | 
            -
                expect(empty.new.to_h).to eq(text: "")
         | 
| 170 | 
            -
                expect(integer.new(value: -1).to_h).to eq(text: "", value: -1)
         | 
| 171 | 
            -
                expect(boolean.new.value).to eq(false)
         | 
| 172 | 
            -
            ```
         | 
| 173 | 
            -
             | 
| 174 | 
            -
            #### Context: Mixing in a module can be used of course
         | 
| 175 | 
            -
             | 
| 176 | 
            -
            Given a behavior like
         | 
| 177 | 
            -
            ```ruby
         | 
| 178 | 
            -
                module Humanize
         | 
| 179 | 
            -
                  def humanize
         | 
| 180 | 
            -
                    "my value is #{value}"
         | 
| 181 | 
            -
                  end
         | 
| 182 | 
            -
                end
         | 
| 183 | 
            -
             | 
| 184 | 
            -
                let(:class_level) { DataClass(value: 1).include(Humanize) }
         | 
| 185 | 
            -
            ```
         | 
| 186 | 
            -
             | 
| 187 | 
            -
            Then we can access the included method
         | 
| 188 | 
            -
            ```ruby
         | 
| 189 | 
            -
                expect(class_level.new.humanize).to eq("my value is 1")
         | 
| 190 | 
            -
            ```
         | 
| 191 | 
            -
             | 
| 192 | 
            -
            ### Context: Pattern Matching
         | 
| 193 | 
            -
             | 
| 194 | 
            -
            A `DataClass` object behaves like the result of it's `to_h` in pattern matching
         | 
| 195 | 
            -
             | 
| 196 | 
            -
            Given
         | 
| 197 | 
            -
            ```ruby
         | 
| 198 | 
            -
                let(:numbers) { DataClass(:name, values: []) }
         | 
| 199 | 
            -
                let(:odds) { numbers.new(name: "odds", values: (1..4).map{ _1 + _1 + 1}) }
         | 
| 200 | 
            -
                let(:evens) { numbers.new(name: "evens", values: (1..4).map{ _1 + _1}) }
         | 
| 201 | 
            -
            ```
         | 
| 202 | 
            -
             | 
| 203 | 
            -
            Then we can match accordingly
         | 
| 204 | 
            -
            ```ruby
         | 
| 205 | 
            -
                match = case odds
         | 
| 206 | 
            -
                        in {name: "odds", values: [1, *]}
         | 
| 207 | 
            -
                          :not_really
         | 
| 208 | 
            -
                        in {name: "evens"}
         | 
| 209 | 
            -
                          :still_naaah
         | 
| 210 | 
            -
                        in {name: "odds", values: [hd, *]}
         | 
| 211 | 
            -
                          hd
         | 
| 212 | 
            -
                        else
         | 
| 213 | 
            -
                          :strange
         | 
| 214 | 
            -
                        end
         | 
| 215 | 
            -
                expect(match).to eq(3)
         | 
| 216 | 
            -
            ```
         | 
| 217 | 
            -
             | 
| 218 | 
            -
            And in `in` expressions
         | 
| 219 | 
            -
            ```ruby
         | 
| 220 | 
            -
                evens => {values: [_, second, *]}
         | 
| 221 | 
            -
                expect(second).to eq(4)
         | 
| 222 | 
            -
            ```
         | 
| 223 | 
            -
             | 
| 224 | 
            -
            #### Context: In Case Statements
         | 
| 225 | 
            -
             | 
| 226 | 
            -
            Given a nice little dataclass `Box`
         | 
| 227 | 
            -
            ```ruby
         | 
| 228 | 
            -
                let(:box) { DataClass content: nil }
         | 
| 229 | 
            -
            ```
         | 
| 230 | 
            -
             | 
| 231 | 
            -
            Then we can also use it in a case statement
         | 
| 232 | 
            -
            ```ruby
         | 
| 233 | 
            -
                value = case box.new
         | 
| 234 | 
            -
                  when box
         | 
| 235 | 
            -
                    42
         | 
| 236 | 
            -
                  else
         | 
| 237 | 
            -
                    0
         | 
| 238 | 
            -
                  end
         | 
| 239 | 
            -
                expect(value).to eq(42)
         | 
| 240 | 
            -
            ```
         | 
| 241 | 
            -
             | 
| 242 | 
            -
            And all the associated methods
         | 
| 243 | 
            -
            ```ruby
         | 
| 244 | 
            -
                expect(box.new).to be_a(box)
         | 
| 245 | 
            -
                expect(box === box.new).to be_truthy
         | 
| 246 | 
            -
            ```
         | 
| 247 | 
            -
             | 
| 248 | 
            -
            ### Context: Behaving like a `Proc`
         | 
| 249 | 
            -
             | 
| 250 | 
            -
            It is useful to be able to filter heterogeneous lists of `DataClass` instances by means of `&to_proc`, therefore
         | 
| 61 | 
            +
            The following specs are executed with the [speculate about](https://github.com/RobertDober/speculate_about) gem.
         | 
| 251 62 |  | 
| 252 | 
            -
            Given  | 
| 63 | 
            +
            Given that we have imported the `Lab42` namespace
         | 
| 253 64 | 
             
            ```ruby
         | 
| 254 | 
            -
                 | 
| 255 | 
            -
                let(:class2) { DataClass :value }
         | 
| 65 | 
            +
                DataClass = Lab42::DataClass
         | 
| 256 66 | 
             
            ```
         | 
| 257 67 |  | 
| 258 | 
            -
             | 
| 259 | 
            -
            ```ruby
         | 
| 260 | 
            -
                let(:list) {[class1.new(value: 1), class2.new(value: 2), class1.new(value: 3)]}
         | 
| 261 | 
            -
            ```
         | 
| 262 | 
            -
             | 
| 263 | 
            -
            Then we can filter
         | 
| 264 | 
            -
            ```ruby
         | 
| 265 | 
            -
                expect(list.filter(&class2)).to eq([class2.new(value: 2)])
         | 
| 266 | 
            -
            ```
         | 
| 267 | 
            -
             | 
| 268 | 
            -
            ### Context: Behaving like a `Hash`
         | 
| 269 | 
            -
             | 
| 270 | 
            -
            We have already seen the `to_h` method, however if we want to pass an instance of `DataClass` as 
         | 
| 271 | 
            -
            keyword parameters we need an implementation of `to_hash`, which of course is just an alias
         | 
| 272 | 
            -
             | 
| 273 | 
            -
            Given this keyword method
         | 
| 274 | 
            -
            ```ruby
         | 
| 275 | 
            -
                def extract_value(value:, **others)
         | 
| 276 | 
            -
                  [value, others]
         | 
| 277 | 
            -
                end
         | 
| 278 | 
            -
            ```
         | 
| 279 | 
            -
            And this `DataClass`:
         | 
| 280 | 
            -
            ```ruby
         | 
| 281 | 
            -
                let(:my_class) { DataClass(value: 1, base: 2) }
         | 
| 282 | 
            -
            ```
         | 
| 68 | 
            +
            ## Context: Data Classes
         | 
| 283 69 |  | 
| 284 | 
            -
             | 
| 285 | 
            -
            ```ruby
         | 
| 286 | 
            -
                expect(extract_value(**my_class.new)).to eq([1, base: 2])
         | 
| 287 | 
            -
            ```
         | 
| 288 | 
            -
             | 
| 289 | 
            -
            ### Context: Constraints
         | 
| 290 | 
            -
             | 
| 291 | 
            -
            Values of attributes of a `DataClass` can have constraints
         | 
| 70 | 
            +
            ### Basic Use Case
         | 
| 292 71 |  | 
| 293 | 
            -
            Given a  | 
| 72 | 
            +
            Given a simple Data Class
         | 
| 294 73 | 
             
            ```ruby
         | 
| 295 | 
            -
                 | 
| 296 | 
            -
                  DataClass | 
| 74 | 
            +
                class SimpleDataClass
         | 
| 75 | 
            +
                  extend DataClass
         | 
| 76 | 
            +
                  attributes :a, :b
         | 
| 297 77 | 
             
                end
         | 
| 298 78 | 
             
            ```
         | 
| 299 79 |  | 
| 300 | 
            -
             | 
| 80 | 
            +
            And an instance of it
         | 
| 301 81 | 
             
            ```ruby
         | 
| 302 | 
            -
                 | 
| 303 | 
            -
                expect(switch.new.merge(on: true).on).to eq(true)
         | 
| 82 | 
            +
                let(:simple_instance) { SimpleDataClass.new(a: 1, b: 2) }
         | 
| 304 83 | 
             
            ```
         | 
| 305 84 |  | 
| 306 | 
            -
             | 
| 85 | 
            +
            Then we access the fields
         | 
| 307 86 | 
             
            ```ruby
         | 
| 308 | 
            -
                expect | 
| 309 | 
            -
             | 
| 310 | 
            -
                expect{ switch.new.merge(on: 42) }
         | 
| 311 | 
            -
                 .to raise_error(Lab42::DataClass::ConstraintError, "value 42 is not allowed for attribute :on")
         | 
| 87 | 
            +
                expect(simple_instance.a).to eq(1)
         | 
| 88 | 
            +
                expect(simple_instance.b).to eq(2)
         | 
| 312 89 | 
             
            ```
         | 
| 313 90 |  | 
| 314 | 
            -
            And  | 
| 91 | 
            +
            And we convert to a hash
         | 
| 315 92 | 
             
            ```ruby
         | 
| 316 | 
            -
                 | 
| 317 | 
            -
                error_body = "  undefined method `>' for nil:NilClass"
         | 
| 318 | 
            -
                error_message = [error_head, error_body].join("\n")
         | 
| 319 | 
            -
             | 
| 320 | 
            -
                expect{ DataClass(value: nil).with_constraint(value: -> { _1 > 0 }) }
         | 
| 321 | 
            -
                  .to raise_error(Lab42::DataClass::ConstraintError, /#{error_message}/)
         | 
| 93 | 
            +
                expect(simple_instance.to_h).to eq(a: 1, b: 2)
         | 
| 322 94 | 
             
            ```
         | 
| 323 95 |  | 
| 324 | 
            -
            And  | 
| 96 | 
            +
            And we can derive new instances
         | 
| 325 97 | 
             
            ```ruby
         | 
| 326 | 
            -
                 | 
| 327 | 
            -
             | 
| 98 | 
            +
                new_instance = simple_instance.merge(b: 3)
         | 
| 99 | 
            +
                expect(new_instance.to_h).to eq(a: 1, b: 3)
         | 
| 100 | 
            +
                expect(simple_instance.to_h).to eq(a: 1, b: 2)
         | 
| 328 101 | 
             
            ```
         | 
| 329 102 |  | 
| 103 | 
            +
            For detailed speculations please see [here](speculations/DATA_CLASSES.md)
         | 
| 330 104 |  | 
| 331 | 
            -
             | 
| 105 | 
            +
            ## Context: `DataClass` function
         | 
| 332 106 |  | 
| 333 | 
            -
             | 
| 334 | 
            -
            it  | 
| 107 | 
            +
            As seen in the speculations above it seems appropriate to declare a `Class` and
         | 
| 108 | 
            +
            extend it as we will add quite some code for constraints, derived attributes and validations.
         | 
| 335 109 |  | 
| 336 | 
            -
             | 
| 337 | 
            -
            ```ruby
         | 
| 338 | 
            -
                let(:constraint_error) { Lab42::DataClass::ConstraintError }
         | 
| 339 | 
            -
                let(:positive) { DataClass(:value) }
         | 
| 340 | 
            -
            ```
         | 
| 110 | 
            +
            However a more concise _Factory Function_ might still be very useful in some use cases...
         | 
| 341 111 |  | 
| 342 | 
            -
             | 
| 112 | 
            +
            Enter `Kernel::DataClass` **The Function**
         | 
| 343 113 |  | 
| 344 | 
            -
             | 
| 114 | 
            +
            ### Context: Just Attributes
         | 
| 345 115 |  | 
| 346 | 
            -
             | 
| 347 | 
            -
             | 
| 348 | 
            -
                positive_by_symbol = positive.with_constraint(value: :positive?)
         | 
| 349 | 
            -
             | 
| 350 | 
            -
                expect(positive_by_symbol.new(value: 1).value).to eq(1)
         | 
| 351 | 
            -
                expect{positive_by_symbol.new(value: 0)}.to raise_error(constraint_error)
         | 
| 352 | 
            -
            ```
         | 
| 116 | 
            +
            If there are no _Constraints_, _Derived Attributes_, _Validation_ or _Inheritance_ this concise syntax
         | 
| 117 | 
            +
            might easily be preferred by many:
         | 
| 353 118 |  | 
| 354 | 
            -
             | 
| 355 | 
            -
             | 
| 356 | 
            -
            ... are also sent to the value of the attribute, this time we can provide paramaters
         | 
| 357 | 
            -
            And we can implement a different form of `Positive`
         | 
| 119 | 
            +
            Given some example instances like these
         | 
| 358 120 | 
             
            ```ruby
         | 
| 359 | 
            -
                 | 
| 360 | 
            -
             | 
| 361 | 
            -
                expect(positive_by_ary.new(value: 1).value).to eq(1)
         | 
| 362 | 
            -
                expect{positive_by_ary.new(value: 0)}.to raise_error(constraint_error)
         | 
| 121 | 
            +
                let(:my_data_class) { DataClass(:name, email: nil) }
         | 
| 122 | 
            +
                let(:my_instance) { my_data_class.new(name: "robert") }
         | 
| 363 123 | 
             
            ```
         | 
| 364 124 |  | 
| 365 | 
            -
             | 
| 366 | 
            -
             | 
| 367 | 
            -
            ##### Membership
         | 
| 368 | 
            -
             | 
| 369 | 
            -
            And this works with a `Set`
         | 
| 125 | 
            +
            Then we can access its fields
         | 
| 370 126 | 
             
            ```ruby
         | 
| 371 | 
            -
                 | 
| 372 | 
            -
             | 
| 373 | 
            -
                expect(positive_by_set.new(value: 1).value).to eq(1)
         | 
| 374 | 
            -
                expect{positive_by_set.new(value: 0)}.to raise_error(constraint_error)
         | 
| 127 | 
            +
                expect(my_instance.name).to eq("robert")
         | 
| 128 | 
            +
                expect(my_instance[:email]).to be_nil
         | 
| 375 129 | 
             
            ```
         | 
| 376 130 |  | 
| 377 | 
            -
             | 
| 131 | 
            +
            But we cannot access undefined fields
         | 
| 378 132 | 
             
            ```ruby
         | 
| 379 | 
            -
                 | 
| 380 | 
            -
             | 
| 381 | 
            -
                expect(positive_by_range.new(value: 1).value).to eq(1)
         | 
| 382 | 
            -
                expect{positive_by_range.new(value: 0)}.to raise_error(constraint_error)
         | 
| 133 | 
            +
                expect{ my_instance.undefined }.to raise_error(NoMethodError)
         | 
| 383 134 | 
             
            ```
         | 
| 384 135 |  | 
| 385 | 
            -
             | 
| 386 | 
            -
             | 
| 387 | 
            -
            This seems quite obvious, and of course it works
         | 
| 388 | 
            -
             | 
| 389 | 
            -
            Then we can also have a regex based constraint
         | 
| 136 | 
            +
            And this is even true for the `[]` syntax
         | 
| 390 137 | 
             
            ```ruby
         | 
| 391 | 
            -
                 | 
| 392 | 
            -
             | 
| 393 | 
            -
                expect(vowel.new(word: "alpha").word).to eq("alpha")
         | 
| 394 | 
            -
                expect{vowel.new(word: "krk")}.to raise_error(constraint_error)
         | 
| 138 | 
            +
                expect{ my_instance[:undefined] }.to raise_error(KeyError)
         | 
| 395 139 | 
             
            ```
         | 
| 396 140 |  | 
| 397 | 
            -
             | 
| 398 | 
            -
             | 
| 399 | 
            -
             | 
| 400 | 
            -
            Then we can also use instance methods to implement our `Positive`
         | 
| 141 | 
            +
            And we need to provide values to fields without defaults
         | 
| 401 142 | 
             
            ```ruby
         | 
| 402 | 
            -
                 | 
| 403 | 
            -
             | 
| 404 | 
            -
                expect(positive_by_instance_method.new(value: 1).value).to eq(1)
         | 
| 405 | 
            -
                expect{positive_by_instance_method.new(value: 0)}.to raise_error(constraint_error)
         | 
| 143 | 
            +
                expect{ my_data_class.new(email: "some@mail.org") }
         | 
| 144 | 
            +
                  .to raise_error(ArgumentError, "missing initializers for [:name]")
         | 
| 406 145 | 
             
            ```
         | 
| 407 | 
            -
             | 
| 408 | 
            -
            Or we can use methods to implement it
         | 
| 146 | 
            +
            And we can extract the values
         | 
| 409 147 | 
             
            ```ruby
         | 
| 410 | 
            -
                 | 
| 411 | 
            -
             | 
| 412 | 
            -
                expect(positive_by_method.new(value: 1).value).to eq(1)
         | 
| 413 | 
            -
                expect{positive_by_method.new(value: 0)}.to raise_error(constraint_error)
         | 
| 148 | 
            +
                expect(my_instance.to_h).to eq(name: "robert", email: nil)
         | 
| 414 149 | 
             
            ```
         | 
| 415 150 |  | 
| 416 | 
            -
            #### Context:  | 
| 417 | 
            -
             | 
| 418 | 
            -
            So far we have only speculated about constraints concerning one attribute, however sometimes we want
         | 
| 419 | 
            -
            to have arbitrary constraints which can only be calculated by access to more attributes
         | 
| 420 | 
            -
             | 
| 421 | 
            -
            Given a `Point` DataClass
         | 
| 422 | 
            -
            ```ruby
         | 
| 423 | 
            -
                let(:point) { DataClass(:x, :y).validate{ |point| point.x > point.y } }
         | 
| 424 | 
            -
                let(:validation_error) { Lab42::DataClass::ValidationError }
         | 
| 425 | 
            -
            ```
         | 
| 151 | 
            +
            #### Context: Immutable → self
         | 
| 426 152 |  | 
| 427 | 
            -
            Then  | 
| 153 | 
            +
            Then `my_instance` is frozen:
         | 
| 428 154 | 
             
            ```ruby
         | 
| 429 | 
            -
                expect | 
| 430 | 
            -
                  .to raise_error(validation_error)
         | 
| 155 | 
            +
                expect(my_instance).to be_frozen
         | 
| 431 156 | 
             
            ```
         | 
| 432 | 
            -
             | 
| 433 | 
            -
            But as validation might need more than the default values we will not execute them at compile time
         | 
| 157 | 
            +
            And we cannot even mute `my_instance`  by means of metaprogramming
         | 
| 434 158 | 
             
            ```ruby
         | 
| 435 | 
            -
                expect{  | 
| 436 | 
            -
                  .to_not raise_error
         | 
| 159 | 
            +
                expect{ my_instance.instance_variable_set("@x", nil) }.to raise_error(FrozenError)
         | 
| 437 160 | 
             
            ```
         | 
| 438 161 |  | 
| 439 | 
            -
             | 
| 440 | 
            -
            ```ruby
         | 
| 441 | 
            -
                better_point = DataClass(:x, :y).validate(:too_left){ |point| point.x > point.y }
         | 
| 442 | 
            -
                ok_point     = better_point.new(x: 1, y: 0)
         | 
| 443 | 
            -
                expect{ ok_point.merge(y: 1) }
         | 
| 444 | 
            -
                  .to raise_error(validation_error, "too_left")
         | 
| 445 | 
            -
            ```
         | 
| 162 | 
            +
            #### Context: Immutable → Cloning
         | 
| 446 163 |  | 
| 447 | 
            -
             | 
| 164 | 
            +
            Given
         | 
| 448 165 | 
             
            ```ruby
         | 
| 449 | 
            -
                 | 
| 450 | 
            -
                   \#<Proc:0x[0-9a-f]+ \s .* spec/speculations/README_spec\.rb: \d+ > \z
         | 
| 451 | 
            -
                }x
         | 
| 452 | 
            -
                expect{ point.new(x: 0, y: 1) }
         | 
| 453 | 
            -
                  .to raise_error(validation_error, error_message_rgx)
         | 
| 166 | 
            +
                let(:other_instance) { my_instance.merge(email: "robert@mail.provider") }
         | 
| 454 167 | 
             
            ```
         | 
| 455 | 
            -
             | 
| 456 | 
            -
            ### Context: Usage with `extend`
         | 
| 457 | 
            -
             | 
| 458 | 
            -
            All the above mentioned features can be achieved with a more conventional syntax by extending a class
         | 
| 459 | 
            -
            with `Lab42::DataClass`
         | 
| 460 | 
            -
             | 
| 461 | 
            -
            Given a class that extends `DataClass`
         | 
| 168 | 
            +
            Then we have a new instance with the old instance unchanged
         | 
| 462 169 | 
             
            ```ruby
         | 
| 463 | 
            -
                 | 
| 464 | 
            -
             | 
| 465 | 
            -
                    extend Lab42::DataClass
         | 
| 466 | 
            -
                    attributes :age, member: false
         | 
| 467 | 
            -
                    constraint :member, Set.new([false, true])
         | 
| 468 | 
            -
                    validate(:too_young_for_member) { |instance| !(instance.member && instance.age < 18) }
         | 
| 469 | 
            -
                  end
         | 
| 470 | 
            -
                end
         | 
| 471 | 
            -
                let(:constraint_error) { Lab42::DataClass::ConstraintError }
         | 
| 472 | 
            -
                let(:validation_error) { Lab42::DataClass::ValidationError }
         | 
| 473 | 
            -
                let(:my_instance) { my_class.new(age: 42) }
         | 
| 474 | 
            -
                let(:my_vip)      { my_instance.merge(member: true) }
         | 
| 170 | 
            +
                expect(other_instance.to_h).to eq(name: "robert", email: "robert@mail.provider")
         | 
| 171 | 
            +
                expect(my_instance.to_h).to eq(name: "robert", email: nil)
         | 
| 475 172 | 
             
            ```
         | 
| 476 | 
            -
             | 
| 477 | 
            -
            Then we can observe that instances of such a class
         | 
| 173 | 
            +
            And the new instance is frozen again
         | 
| 478 174 | 
             
            ```ruby
         | 
| 479 | 
            -
                expect( | 
| 480 | 
            -
                expect(my_vip.to_h).to eq(age: 42, member: true)
         | 
| 481 | 
            -
                expect(my_instance.member).to be_falsy
         | 
| 175 | 
            +
                expect(other_instance).to be_frozen
         | 
| 482 176 | 
             
            ```
         | 
| 483 177 |  | 
| 484 | 
            -
             | 
| 485 | 
            -
             | 
| 486 | 
            -
                expect{my_instance.merge(member: nil)}
         | 
| 487 | 
            -
                  .to raise_error(constraint_error)
         | 
| 488 | 
            -
            ```
         | 
| 178 | 
            +
            For speculations how to add all the other features to the _Factory Function_ syntax please
         | 
| 179 | 
            +
            look [here](speculations/FACTORY_FUNCTION.md)
         | 
| 489 180 |  | 
| 490 | 
            -
            And of course validations still work too
         | 
| 491 | 
            -
            ```ruby
         | 
| 492 | 
            -
                expect{ my_vip.merge(age: 17) }
         | 
| 493 | 
            -
                  .to raise_error(validation_error, "too_young_for_member")
         | 
| 494 | 
            -
            ```
         | 
| 495 181 |  | 
| 496 182 |  | 
| 497 183 | 
             
            ## Context: `Pair` and `Triple`
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Lab42
         | 
| 4 | 
            +
              module DataClass
         | 
| 5 | 
            +
                class Proxy
         | 
| 6 | 
            +
                  module Derived
         | 
| 7 | 
            +
                    private
         | 
| 8 | 
            +
                    def _define_derived
         | 
| 9 | 
            +
                      proxy = self
         | 
| 10 | 
            +
                      ->(*) do
         | 
| 11 | 
            +
                        define_method :derive do |att_name, &blk|
         | 
| 12 | 
            +
                          proxy.define_derived_attribute(att_name, &blk)
         | 
| 13 | 
            +
                          self
         | 
| 14 | 
            +
                        end
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
            # SPDX-License-Identifier: Apache-2.0
         | 
| @@ -4,6 +4,10 @@ module Lab42 | |
| 4 4 | 
             
              module DataClass
         | 
| 5 5 | 
             
                class Proxy
         | 
| 6 6 | 
             
                  module Memos
         | 
| 7 | 
            +
                    def all_attributes
         | 
| 8 | 
            +
                      @__all_attributes__ ||= members&.union(Set.new(derived_attributes.keys))
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
             | 
| 7 11 | 
             
                    def constraints
         | 
| 8 12 | 
             
                      @__constraints__ ||= Hash.new { |h, k| h[k] = [] }
         | 
| 9 13 | 
             
                    end
         | 
| @@ -12,6 +16,10 @@ module Lab42 | |
| 12 16 | 
             
                      @__defaults__ ||= {}
         | 
| 13 17 | 
             
                    end
         | 
| 14 18 |  | 
| 19 | 
            +
                    def derived_attributes
         | 
| 20 | 
            +
                      @__derived_attributes__ ||= {}
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 15 23 | 
             
                    def members
         | 
| 16 24 | 
             
                      @__members__ ||= unless (positionals + defaults.keys).empty?
         | 
| 17 25 | 
             
                                         Set.new(positionals + defaults.keys)
         | 
| @@ -2,15 +2,33 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'set'
         | 
| 4 4 | 
             
            require_relative 'proxy/constraints'
         | 
| 5 | 
            +
            require_relative 'proxy/derived'
         | 
| 5 6 | 
             
            require_relative 'proxy/memos'
         | 
| 6 7 | 
             
            require_relative 'proxy/validations'
         | 
| 7 8 | 
             
            require_relative 'proxy/mixin'
         | 
| 8 9 | 
             
            module Lab42
         | 
| 9 10 | 
             
              module DataClass
         | 
| 10 11 | 
             
                class Proxy
         | 
| 11 | 
            -
                  include Constraints, Memos, Validations
         | 
| 12 | 
            +
                  include Constraints, Derived, Memos, Validations
         | 
| 12 13 |  | 
| 13 | 
            -
                  attr_reader :actual_params, :block, :klass
         | 
| 14 | 
            +
                  attr_reader :actual_params, :block, :klass, :klass_defined
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def self.from_parent(parent, klass)
         | 
| 17 | 
            +
                    new(klass).tap do |proxy|
         | 
| 18 | 
            +
                      proxy.positionals.push(*parent.positionals)
         | 
| 19 | 
            +
                      proxy.defaults.update(parent.defaults)
         | 
| 20 | 
            +
                      proxy.constraints.update(parent.constraints)
         | 
| 21 | 
            +
                      proxy.validations.push(*parent.validations)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def access(data_class_instance, key)
         | 
| 26 | 
            +
                    if all_attributes.member?(key)
         | 
| 27 | 
            +
                      data_class_instance.send(key)
         | 
| 28 | 
            +
                    else
         | 
| 29 | 
            +
                      raise KeyError, "#{key} is not an attribute of #{data_class_instance}"
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 14 32 |  | 
| 15 33 | 
             
                  def check!(**params)
         | 
| 16 34 | 
             
                    @actual_params = params
         | 
| @@ -21,22 +39,33 @@ module Lab42 | |
| 21 39 | 
             
                  end
         | 
| 22 40 |  | 
| 23 41 | 
             
                  def define_class!
         | 
| 42 | 
            +
                    return if @klass_defined
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    @klass_defined = true
         | 
| 24 45 | 
             
                    klass.module_eval(&_define_attr_reader)
         | 
| 25 | 
            -
                    klass.module_eval(&_define_initializer)
         | 
| 46 | 
            +
                    klass.module_eval(&_define_initializer) if Class === klass
         | 
| 26 47 | 
             
                    _define_methods
         | 
| 27 48 | 
             
                    klass.include(Mixin)
         | 
| 28 49 | 
             
                    klass.module_eval(&block) if block
         | 
| 29 50 | 
             
                    klass
         | 
| 30 51 | 
             
                  end
         | 
| 31 52 |  | 
| 53 | 
            +
                  def define_derived_attribute(name, &blk)
         | 
| 54 | 
            +
                    positionals.delete(name)
         | 
| 55 | 
            +
                    defaults.delete(name)
         | 
| 56 | 
            +
                    derived_attributes.update(name => true) do |_key, _old,|
         | 
| 57 | 
            +
                      raise DuplicateDefinitionError, "Redefinition of derived attribute #{name}"
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                    klass.module_eval(&_define_derived_attribute(name, &blk))
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 32 62 | 
             
                  def init(data_class, **params)
         | 
| 33 63 | 
             
                    _init(data_class, defaults.merge(params))
         | 
| 34 64 | 
             
                  end
         | 
| 35 65 |  | 
| 36 66 | 
             
                  def to_hash(data_class_instance)
         | 
| 37 | 
            -
                     | 
| 38 | 
            -
                      . | 
| 39 | 
            -
                      .to_h
         | 
| 67 | 
            +
                    all_attributes
         | 
| 68 | 
            +
                      .inject({}) { |result, (k, _)| result.merge(k => data_class_instance[k]) }
         | 
| 40 69 | 
             
                  end
         | 
| 41 70 |  | 
| 42 71 | 
             
                  def update!(with_positionals, with_keywords)
         | 
| @@ -46,12 +75,13 @@ module Lab42 | |
| 46 75 |  | 
| 47 76 | 
             
                  private
         | 
| 48 77 | 
             
                  def initialize(*args, **kwds, &blk)
         | 
| 49 | 
            -
                    @klass = if  | 
| 78 | 
            +
                    @klass = if Module === args.first
         | 
| 50 79 | 
             
                               args.shift
         | 
| 51 80 | 
             
                             else
         | 
| 52 81 | 
             
                               Class.new
         | 
| 53 82 | 
             
                             end
         | 
| 54 83 |  | 
| 84 | 
            +
                    @klass_defined = false
         | 
| 55 85 | 
             
                    @block = blk
         | 
| 56 86 | 
             
                    defaults.update(kwds)
         | 
| 57 87 | 
             
                    positionals.push(*args)
         | 
| @@ -64,6 +94,12 @@ module Lab42 | |
| 64 94 | 
             
                    end
         | 
| 65 95 | 
             
                  end
         | 
| 66 96 |  | 
| 97 | 
            +
                  def _define_derived_attribute(name, &blk)
         | 
| 98 | 
            +
                    ->(*) do
         | 
| 99 | 
            +
                      define_method(name) { blk.call(self) }
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 67 103 | 
             
                  def _define_freezing_constructor
         | 
| 68 104 | 
             
                    ->(*) do
         | 
| 69 105 | 
             
                      define_method :new do |*a, **p, &b|
         | 
| @@ -93,8 +129,8 @@ module Lab42 | |
| 93 129 | 
             
                  end
         | 
| 94 130 |  | 
| 95 131 | 
             
                  def _define_methods
         | 
| 96 | 
            -
                     | 
| 97 | 
            -
             | 
| 132 | 
            +
                    _define_singleton_methods(klass.singleton_class)
         | 
| 133 | 
            +
                    klass.module_eval(&_define_access)
         | 
| 98 134 | 
             
                    klass.module_eval(&_define_to_h)
         | 
| 99 135 | 
             
                    klass.module_eval(&_define_merge)
         | 
| 100 136 | 
             
                  end
         | 
| @@ -103,9 +139,19 @@ module Lab42 | |
| 103 139 | 
             
                    singleton.module_eval(&_define_freezing_constructor)
         | 
| 104 140 | 
             
                    singleton.module_eval(&_define_to_proc)
         | 
| 105 141 | 
             
                    singleton.module_eval(&_define_with_constraint)
         | 
| 142 | 
            +
                    singleton.module_eval(&_define_derived)
         | 
| 106 143 | 
             
                    singleton.module_eval(&_define_with_validations)
         | 
| 107 144 | 
             
                  end
         | 
| 108 145 |  | 
| 146 | 
            +
                  def _define_access
         | 
| 147 | 
            +
                    proxy = self
         | 
| 148 | 
            +
                    ->(*) do
         | 
| 149 | 
            +
                      define_method :[] do |key|
         | 
| 150 | 
            +
                        proxy.access(self, key)
         | 
| 151 | 
            +
                      end
         | 
| 152 | 
            +
                    end
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 109 155 | 
             
                  def _define_to_h
         | 
| 110 156 | 
             
                    proxy = self
         | 
| 111 157 | 
             
                    ->(*) do
         | 
    
        data/lib/lab42/data_class.rb
    CHANGED
    
    | @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require_relative './data_class/constraint_error'
         | 
| 4 | 
            +
            require_relative './data_class/duplicate_definition_error'
         | 
| 4 5 | 
             
            require_relative './data_class/kernel'
         | 
| 5 6 | 
             
            require_relative './data_class/validation_error'
         | 
| 6 7 | 
             
            require_relative './data_class/proxy'
         | 
| @@ -9,10 +10,18 @@ require_relative './triple' | |
| 9 10 |  | 
| 10 11 | 
             
            module Lab42
         | 
| 11 12 | 
             
              module DataClass
         | 
| 12 | 
            -
                def self.extended( | 
| 13 | 
            -
                   | 
| 13 | 
            +
                def self.extended(extendee)
         | 
| 14 | 
            +
                  base_proxy =
         | 
| 15 | 
            +
                    extendee
         | 
| 16 | 
            +
                    .ancestors
         | 
| 17 | 
            +
                    .grep(self)
         | 
| 18 | 
            +
                    .drop(1)
         | 
| 19 | 
            +
                    .first
         | 
| 20 | 
            +
                      &.__data_class_proxy__
         | 
| 14 21 |  | 
| 15 | 
            -
                   | 
| 22 | 
            +
                  proxy = base_proxy ? Proxy.from_parent(base_proxy, extendee) : Proxy.new(extendee)
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  extendee.module_eval do
         | 
| 16 25 | 
             
                    define_singleton_method(:__data_class_proxy__){ proxy }
         | 
| 17 26 | 
             
                  end
         | 
| 18 27 | 
             
                end
         | 
| @@ -24,10 +33,16 @@ module Lab42 | |
| 24 33 | 
             
                  end
         | 
| 25 34 | 
             
                end
         | 
| 26 35 |  | 
| 36 | 
            +
                def derive(att_name, &blk)
         | 
| 37 | 
            +
                  __data_class_proxy__.define_derived_attribute(att_name, &blk)
         | 
| 38 | 
            +
                  __data_class_proxy__.define_class!
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 27 41 | 
             
                def constraint(member, constraint = nil, &block)
         | 
| 28 42 | 
             
                  raise ArgumentError, "must not provide constraint (2nd argument) and a block" if block && constraint
         | 
| 29 43 |  | 
| 30 44 | 
             
                  __data_class_proxy__.define_constraints(member => constraint || block)
         | 
| 45 | 
            +
                  __data_class_proxy__.define_class!
         | 
| 31 46 | 
             
                end
         | 
| 32 47 | 
             
              end
         | 
| 33 48 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: lab42_data_class
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.7.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Robert Dober
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2022-02- | 
| 11 | 
            +
            date: 2022-02-27 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies: []
         | 
| 13 13 | 
             
            description: |
         | 
| 14 14 | 
             
              An Immutable DataClass for Ruby
         | 
| @@ -25,10 +25,12 @@ files: | |
| 25 25 | 
             
            - README.md
         | 
| 26 26 | 
             
            - lib/lab42/data_class.rb
         | 
| 27 27 | 
             
            - lib/lab42/data_class/constraint_error.rb
         | 
| 28 | 
            +
            - lib/lab42/data_class/duplicate_definition_error.rb
         | 
| 28 29 | 
             
            - lib/lab42/data_class/kernel.rb
         | 
| 29 30 | 
             
            - lib/lab42/data_class/proxy.rb
         | 
| 30 31 | 
             
            - lib/lab42/data_class/proxy/constraints.rb
         | 
| 31 32 | 
             
            - lib/lab42/data_class/proxy/constraints/maker.rb
         | 
| 33 | 
            +
            - lib/lab42/data_class/proxy/derived.rb
         | 
| 32 34 | 
             
            - lib/lab42/data_class/proxy/memos.rb
         | 
| 33 35 | 
             
            - lib/lab42/data_class/proxy/mixin.rb
         | 
| 34 36 | 
             
            - lib/lab42/data_class/proxy/validations.rb
         |