u-struct 0.11.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +5 -5
  3. data/.rubocop.yml +129 -0
  4. data/.rubocop_todo.yml +10 -0
  5. data/.tool-versions +1 -0
  6. data/CHANGELOG.md +558 -13
  7. data/Gemfile +14 -3
  8. data/README.md +682 -16
  9. data/Rakefile +5 -5
  10. data/bin/console +3 -3
  11. data/bin/prepare_coverage +7 -9
  12. data/bin/run_ci +17 -0
  13. data/bin/tapioca +28 -0
  14. data/examples/rgb/number.rb +1 -1
  15. data/examples/rgb_1.rb +7 -6
  16. data/examples/rgb_2.rb +2 -2
  17. data/examples/rgb_3.rb +1 -1
  18. data/lib/micro/struct/factory/create_struct.rb +95 -69
  19. data/lib/micro/struct/factory/members.rb +1 -0
  20. data/lib/micro/struct/factory.rb +13 -4
  21. data/lib/micro/struct/features.rb +35 -16
  22. data/lib/micro/struct/normalize_names.rb +4 -3
  23. data/lib/micro/struct/version.rb +2 -1
  24. data/lib/micro/struct.rb +37 -5
  25. data/lib/u-struct.rb +2 -0
  26. data/rbi/micro/struct/factory/create_struct.rbi +60 -0
  27. data/rbi/micro/struct/factory/members.rbi +67 -0
  28. data/rbi/micro/struct/factory.rbi +41 -0
  29. data/rbi/micro/struct/features.rbi +41 -0
  30. data/rbi/micro/struct/normalize_names.rbi +20 -0
  31. data/rbi/micro/struct/version.rbi +3 -0
  32. data/rbi/micro/struct.rbi +68 -0
  33. data/sorbet/config +8 -0
  34. data/sorbet/rbi/gems/ast@2.4.2.rbi +54 -0
  35. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  36. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +11 -0
  37. data/sorbet/rbi/gems/docile@1.4.0.rbi +54 -0
  38. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  39. data/sorbet/rbi/gems/minitest@5.15.0.rbi +345 -0
  40. data/sorbet/rbi/gems/parser@3.1.0.0.rbi +1196 -0
  41. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  42. data/sorbet/rbi/gems/rake@13.0.6.rbi +806 -0
  43. data/sorbet/rbi/gems/rbi@0.0.9.rbi +1602 -0
  44. data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +89 -0
  45. data/sorbet/rbi/gems/simplecov@0.21.2.rbi +577 -0
  46. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.3.rbi +8 -0
  47. data/sorbet/rbi/gems/spoom@1.1.8.rbi +1252 -0
  48. data/sorbet/rbi/gems/tapioca@0.6.2.rbi +1232 -0
  49. data/sorbet/rbi/gems/thor@1.2.1.rbi +844 -0
  50. data/sorbet/rbi/gems/unparser@0.6.3.rbi +8 -0
  51. data/sorbet/rbi/gems/webrick@1.7.0.rbi +601 -0
  52. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +199 -0
  53. data/sorbet/rbi/gems/yard@0.9.27.rbi +4112 -0
  54. data/sorbet/tapioca/config.yml +13 -0
  55. data/sorbet/tapioca/require.rb +4 -0
  56. data/u-struct.gemspec +3 -3
  57. metadata +38 -4
  58. data/bin/test +0 -8
data/README.md CHANGED
@@ -20,6 +20,159 @@
20
20
  </a>
21
21
  </p>
22
22
 
23
+ # Table of contents: <!-- omit in toc -->
24
+ - [Introduction](#introduction)
25
+ - [Motivation](#motivation)
26
+ - [Installation](#installation)
27
+ - [Usage](#usage)
28
+ - [`Micro::Struct.new`](#microstructnew)
29
+ - [`optional:` option](#optional-option)
30
+ - [`required:` option](#required-option)
31
+ - [Defining custom methods/behavior](#defining-custom-methodsbehavior)
32
+ - [`Micro::Struct.with`](#microstructwith)
33
+ - [`Micro::Struct[]`](#microstruct)
34
+ - [`:to_ary`](#to_ary)
35
+ - [`:to_hash`](#to_hash)
36
+ - [`:to_proc`](#to_proc)
37
+ - [`:readonly`](#readonly)
38
+ - [`:instance_copy`](#instance_copy)
39
+ - [`:exposed_features`](#exposed_features)
40
+ - [`Micro::Struct.instance` or `Micro::Struct.with(...).instance`](#microstructinstance-or-microstructwithinstance)
41
+ - [`Micro::Struct.immutable`](#microstructimmutable)
42
+ - [`Micro::Struct.readonly`](#microstructreadonly)
43
+ - [TL;DR](#tldr)
44
+ - [FAQ](#faq)
45
+ - [How to override the Struct `.new` method?](#how-to-override-the-struct-new-method)
46
+ - [Can I override the Struct initializer?](#can-i-override-the-struct-initializer)
47
+ - [Development](#development)
48
+ - [Contributing](#contributing)
49
+ - [License](#license)
50
+ - [Code of Conduct](#code-of-conduct)
51
+ - [Contact](#contact)
52
+ - [Acknowledgments](#acknowledgments)
53
+
54
+ ## Introduction
55
+
56
+ Ruby `Struct` is a versatile data structure because it can behave like an `Array`, `Hash`, and ordinary object:
57
+
58
+ ```ruby
59
+ Person = Struct.new(:first_name, :last_name)
60
+
61
+ person = Person.new('Rodrigo', 'Serradura')
62
+ # #<struct Person first_name="Rodrigo", last_name="Serradura">
63
+
64
+ # -- Ordinary object behavior --
65
+
66
+ person.first_name # "Rodrigo"
67
+ person.last_name # "Serradura"
68
+
69
+ person.first_name = 'John' # "John"
70
+ person.last_name = 'Doe' # "Doe"
71
+
72
+ person
73
+ # #<struct Person first_name="John", last_name="Doe">
74
+
75
+ # -- Hash behavior --
76
+
77
+ person[:first_name] # "Doe"
78
+ person['last_name'] # "John"
79
+
80
+ person[:first_name] = 'Rodrigo' # "Rodrigo"
81
+ person['last_name'] = 'Serradura' # "Serradura"
82
+
83
+ person
84
+ # #<struct Person first_name="Rodrigo", last_name="Serradura">
85
+
86
+
87
+ # Transforming a Struct into a Hash
88
+ person.to_h
89
+ # {:first_name=>"Rodrigo", :last_name=>"Serradura"}
90
+
91
+ # -- Array behavior --
92
+
93
+ person[0] # "Rodrigo"
94
+ person[1] # "Serradura"
95
+
96
+ person[0] = 'John' # "John"
97
+ person[1] = 'Doe' # "Doe"
98
+
99
+ person
100
+ # #<struct Person first_name="John", last_name="Doe">
101
+
102
+ # Transforming a Struct into an Array
103
+ person.to_a
104
+ # ["John", "Doe"]
105
+ ```
106
+
107
+ Because of these characteristics, structs could be excellent candidates to create different kinds of POROs (Plain Old Ruby Objects). However, it is very common to see developers avoiding its usage because of some of its behaviors, like setters or the constructor's positional arguments. The addition of keywords arguments on its constructor ([available on Ruby >= 2.5](https://www.bigbinary.com/blog/ruby-2-5-allows-creating-structs-with-keyword-arguments)) improved the experience to instantiate `Struct` objects but it doesn't require all the arguments. Some developers can still feel uncomfortable with that and they might avoid its usage.
108
+
109
+ Look at the example showing the Struct's `keyword_init:` option creating a constructor with optional keyword arguments:
110
+
111
+ ```ruby
112
+ Person = Struct.new(:first_name, :last_name, keyword_init: true)
113
+
114
+ Person.superclass # Struct
115
+
116
+ Person.new
117
+ # #<struct Person first_name=nil, last_name=nil>
118
+
119
+ # Because of this, you will only see an exception
120
+ # if you pass one or more invalid keywords.
121
+
122
+ Person.new(foo: 1, bar: 2)
123
+ # ArgumentError (unknown keywords: foo, bar)
124
+ ```
125
+
126
+ ### Motivation
127
+
128
+ So, given this introduction, the idea of this project is to provide a way of creating Ruby Structs with some [powerful features](#microstructwith). Let's see how the `Micro::Struct.new()` works.
129
+
130
+ ```ruby
131
+ require 'u-struct'
132
+
133
+ Person = Micro::Struct.new(:first_name, :last_name)
134
+
135
+ Person.superclass
136
+ # Struct
137
+
138
+ Person.new
139
+ # ArgumentError (missing keywords: :first_name, :last_name)
140
+ ```
141
+
142
+ As you can see, the struct instantiation raised an error because all of the keywords arguments are required.
143
+
144
+ If you need one or many optional arguments, you can use the `optional:` option to define them:
145
+
146
+ ```ruby
147
+ Person = Micro::Struct.new(:first_name, optional: :last_name)
148
+
149
+ Person.new
150
+ # ArgumentError (missing keyword: :first_name)
151
+
152
+ Person.new(first_name: 'Rodrigo')
153
+ # #<struct Person first_name="Rodrigo", last_name=nil>
154
+ ```
155
+
156
+ If you want a `Struct` only with optional members (or attributes), as the `keyword_init:` option does, you can declare all attributes within the optional: option:
157
+
158
+ ```ruby
159
+ Person = Micro::Struct.new(optional: [:first_name, :last_name])
160
+
161
+ Person.new
162
+ # #<struct Person first_name=nil, last_name=nil>
163
+ ```
164
+
165
+ You can also use the `required:` option to define required attributes.
166
+
167
+ ```ruby
168
+ Person = Micro::Struct.new(
169
+ required: [:first_name, :last_name],
170
+ optional: [:age]
171
+ )
172
+ ```
173
+
174
+ So, what did you think? If you liked it, continue the reading to understand what this gem can do for you.
175
+
23
176
  ## Installation
24
177
 
25
178
  Add this line to your application's Gemfile:
@@ -36,44 +189,450 @@ Or install it yourself as:
36
189
 
37
190
  $ gem install u-struct
38
191
 
192
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
193
+
39
194
  ## Usage
40
195
 
196
+ ### `Micro::Struct.new`
197
+
198
+ Like `Struct.new`, you will use `Micro::Struct.new` to create your `Struct` classes.
199
+
200
+ The key difference is: the `Struct` created from `Micro::Struct` will use keyword arguments in their constructors.
201
+
202
+ ```ruby
203
+ Person = Struct.new(:name) # Person
204
+ Persona = Micro::Struct.new(:name) # Persona
205
+
206
+ Person.ancestors # [Person, Struct, Enumerable, Object, Kernel, BasicObject]
207
+ Persona.ancestors # [Person, Struct, Enumerable, Object, Kernel, BasicObject]
208
+
209
+ Person.new('Rodrigo') # #<struct Person name="Rodrigo">
210
+ Persona.new(name: 'Rodrigo') # #<struct Person name="Rodrigo">
211
+
212
+ Person.new # #<struct Person name=nil>
213
+
214
+ Persona.new # ArgumentError (missing keyword: :name)
215
+ ```
216
+
217
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
218
+
219
+ #### `optional:` option
220
+
221
+ If you need optional attributes, you can use this to define them.
222
+
223
+ ```ruby
224
+ Person = Micro::Struct.new(:name, optional: :age)
225
+
226
+ Person.new
227
+ # ArgumentError (missing keyword: :name)
228
+
229
+ Person.new(name: 'John')
230
+ # #<struct Person name="John", age=nil>
231
+ ```
232
+
233
+ Use an array to define multiple optional attributes.
234
+
235
+ ```ruby
236
+ Person = Micro::Struct.new(:name, optional: [:age, :nickname])
237
+
238
+ Person.new
239
+ # ArgumentError (missing keyword: :name)
240
+
241
+ Person.new(name: 'John')
242
+ # #<struct Person name="John", age=nil, nickname=nil>
243
+ ```
244
+
245
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
246
+
247
+ #### `required:` option
248
+
249
+ It is an alternative way to define required attributes. Use a symbol to define one or an array to define multiple attributes.
250
+
251
+ ```ruby
252
+ Person = Micro::Struct.new(
253
+ required: [:first_name, :last_name],
254
+ optional: [:age]
255
+ )
256
+
257
+ Person.new
258
+ # ArgumentError (missing keywords: :first_name, :last_name)
259
+
260
+ Person.new first_name: 'John', last_name: 'Doe'
261
+ # #<struct Person first_name="John", last_name="Doe", age=nil>
262
+ ```
263
+
264
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
265
+
266
+ #### Defining custom methods/behavior
267
+
268
+ The `Micro::Struct.new` accepts a block as a regular `Struct`, and you can use it to define some custom behavior/methods.
269
+
270
+ ```ruby
271
+ Person = Micro::Struct.new(:first_name, :last_name, optional: :age) do
272
+ def name
273
+ "#{first_name} #{last_name}"
274
+ end
275
+ end
276
+
277
+ person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
278
+ # #<struct Person first_name="Rodrigo", last_name="Serradura", age=nil>
279
+
280
+ person.first_name # "Rodrigo"
281
+ person.last_name # "Serradura"
282
+ person.name # "Rodrigo Serradura"
283
+ ```
284
+
285
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
286
+
287
+ ### `Micro::Struct.with`
288
+
289
+ This method can do two things: first, it can create `Struct` factories; second, it sets some special behavior to their structs.
290
+
291
+ These are all of the available features which you can use (pick one, many, or all of them):
292
+ - [`:to_ary`](#to_ary)
293
+ - [`:to_hash`](#to_hash)
294
+ - [`:to_proc`](#to_proc)
295
+ - [`:readonly`](#readonly)
296
+ - [`:instance_copy`](#instance_copy)
297
+ - [`:exposed_features`](#exposed_features)
298
+
299
+ Look at an example of defining a `Struct` factory that can create "immutable" structs by picking the `:readonly`, `:instance_copy` features.
300
+
301
+ ```ruby
302
+ ReadonlyStruct = Micro::Struct.with(:readonly, :instance_copy)
303
+
304
+ # Use the factory to create structs with the same characteristics:
305
+
306
+ Person = ReadonlyStruct.new(:first_name, :last_name)
307
+
308
+ person = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
309
+ # #<struct Person first_name="Rodrigo", last_name="Rodrigues">
310
+
311
+ # The `:readonly` sets all the Struct writers as private.
312
+
313
+ person.last_name = ''
314
+ # NoMethodError (private method `last_name=' called for #<struct Person ...>)
315
+
316
+ person[:last_name] = ''
317
+ # NoMethodError (private method `[]=' called for #<struct Person ...>)
318
+
319
+ # The `:instance_copy` defines a `#with` instance method,
320
+ # which allows you to create a new instance from the current struct state.
321
+
322
+ new_person = person.with(last_name: 'Serradura')
323
+ # #<struct Person first_name="Rodrigo", last_name="Serradura">
324
+
325
+ new_person == person
326
+ # false
327
+
328
+ new_person.class == person.class
329
+ # true
330
+ ```
331
+
332
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
333
+
334
+ ### `Micro::Struct[]`
335
+
336
+ The `[]` brackets method is as an alias of `Micro::Struct.with`. e.g.
337
+ ```ruby
338
+ Micro::Struct[:readonly, :to_hash] # is the same as Micro::Struct.with(:readonly, :to_hash)
339
+ ```
340
+
341
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
342
+
343
+ #### `:to_ary`
344
+
345
+ Defines a `#to_ary` method which will invoke the struct `#to_a` method. If you override the `#to_a` method you will also affect it.
346
+
347
+ The `#to_ary` makes Ruby know how to deconstruct an object like an array.
348
+
349
+ ```ruby
350
+ Person = Micro::Struct.with(:to_ary).new(:first_name, :last_name)
351
+
352
+ person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
353
+
354
+ first_name, last_name = person
355
+
356
+ p first_name # "Rodrigo"
357
+ p last_name # "Serradura"
358
+
359
+ *first_and_last_name = person
360
+
361
+ p first_and_last_name # ["Rodrigo", "Serradura"]
362
+ ```
363
+
364
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
365
+
366
+ #### `:to_hash`
367
+
368
+ Defines a `#to_hash` method which will invoke the struct `#to_h` method. If you override the `#to_h` method you will also affect it.
369
+
370
+ The `#to_hash` makes Ruby know how to deconstruct an object like a hash.
371
+
41
372
  ```ruby
42
- # Like in a regular Struct, you can define one or many attributes.
43
- # But all of them will be required by default.
373
+ Person = Micro::Struct.with(:to_hash).new(:first_name, :last_name)
44
374
 
375
+ person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
376
+
377
+ def greet(first_name:, last_name:)
378
+ puts "Hi #{first_name} #{last_name}!"
379
+ end
380
+
381
+ greet(**person)
382
+ # Hi Rodrigo Serradura!
383
+ ```
384
+
385
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
386
+
387
+ #### `:to_proc`
388
+
389
+ The `#to_proc` tells Ruby how to invoke it as a block replacement (by using `&`).
390
+
391
+ The lambda returned from the `#to_proc` will require a hash as its argument.
392
+
393
+ ```ruby
394
+ Person = Micro::Struct.with(:to_proc).new(:first_name, :last_name)
395
+
396
+ [
397
+ {first_name: 'John', last_name: 'Doe'},
398
+ {first_name: 'Mary', last_name: 'Doe'}
399
+ ].map(&Person)
400
+ # [
401
+ # #<struct Person::Struct first_name="John", last_name="Doe">,
402
+ # #<struct Person::Struct first_name="Mary", last_name="Doe">
403
+ # ]
404
+ ```
405
+
406
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
407
+
408
+ #### `:readonly`
409
+
410
+ This feature sets the Struct members' writers as private.
411
+
412
+ ```ruby
413
+ Person = Micro::Struct.with(:readonly).new(:first_name, :last_name)
414
+
415
+ person = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
416
+ # #<struct Person first_name="Rodrigo", last_name="Rodrigues">
417
+
418
+ person.last_name = ''
419
+ # NoMethodError (private method `last_name=' called for #<struct Person ...>)
420
+
421
+ person[:last_name] = ''
422
+ # NoMethodError (private method `[]=' called for #<struct Person ...>)
423
+ ```
424
+
425
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
426
+
427
+ #### `:instance_copy`
428
+
429
+ Creates the `#with` method, which will instantiate a struct of the same kind and reuse its current state.
430
+
431
+ ```ruby
432
+ Person = Micro::Struct.with(:instance_copy).new(:first_name, :last_name)
433
+
434
+ person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
435
+ # => #<struct Person::Struct first_name="Rodrigo", last_name="Serradura">
436
+
437
+ person.first_name = 'John'
438
+ # => "John"
439
+
440
+ person.inspect
441
+ # => #<struct Person::Struct first_name="John", last_name="Serradura">
442
+
443
+ new_person = person.with(last_name: 'Doe')
444
+ # => #<struct Person::Struct first_name="John", last_name="Doe">
445
+
446
+ person === new_person # => false
447
+ person.equal?(new_person) # => false
448
+
449
+ person.last_name # => "Serradura"
450
+ new_person.last_name # => "Doe"
451
+ ```
452
+
453
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
454
+
455
+ #### `:exposed_features`
456
+
457
+ This feature exposes the struct's configured features. Via the methods: `.features` and `.__features__`.
458
+
459
+ ```ruby
460
+ Person = Micro::Struct.with(:exposed_features, :readonly, :to_proc).new(:name)
461
+
462
+ Person.features
463
+ # => #<struct Micro::Struct::Features::Exposed
464
+ # names=[:readonly, :to_proc],
465
+ # options={:to_ary=>false, :to_hash=>false, :to_proc=>true, :readonly=>true, :instance_copy=>false}>
466
+
467
+ Person.__features__.equal?(Person.features) # `.__features__` is an alias of `.features` method
468
+
469
+ Person.features.names # => [:readonly, :to_proc]
470
+ Person.features.options # => {:to_ary=>false, :to_hash=>false, :to_proc=>true, :readonly=>true, :instance_copy=>false}
471
+
472
+ Person.features.option?(:to_proc) # => true
473
+ Person.features.option?(:readonly) # => true
474
+
475
+ Person.features.options?(:to_proc) # => true
476
+ Person.features.options?(:readonly) # => true
477
+
478
+ Person.features.options?(:to_proc, :readonly) # => true
479
+ Person.features.options?(:to_ary, :readonly) # => false
480
+ ```
481
+
482
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
483
+
484
+ ### `Micro::Struct.instance` or `Micro::Struct.with(...).instance`
485
+
486
+ Creates a struct instance from a given hash. This could be useful to create constants or a singleton value.
487
+
488
+ ```ruby
489
+ person1 = Micro::Struct.instance(first_name: 'Rodrigo', last_name: 'Serradura')
490
+ # => #<struct first_name="Rodrigo", last_name="Serradura">
491
+
492
+ person1.first_name = 'John'
493
+
494
+ person1.first_name # => "John"
495
+ ```
496
+
497
+ You can also use the instance method after defining some struct feature ([`Micro::Struct.with`](#microstructwith)).
498
+
499
+ ```ruby
500
+ person2 = Micro::Struct.with(:readonly).instance(first_name: 'Rodrigo', last_name: 'Serradura')
501
+ # => #<struct first_name="Rodrigo", last_name="Serradura">
502
+
503
+ person2.first_name = 'John'
504
+ # NoMethodError (private method `first_name=' called for #<struct first_name="Rodrigo", last_name="Serradura">)
505
+ ```
506
+
507
+ And if you need some custom behavior, use a block to define them.
508
+
509
+ ```ruby
510
+ person3 = Micro::Struct.instance(first_name: 'Rodrigo', last_name: 'Serradura') do
511
+ def name
512
+ "#{first_name} #{last_name}"
513
+ end
514
+ end
515
+
516
+ person4 = Micro::Struct.with(:readonly).instance(first_name: 'Rodrigo', last_name: 'Serradura') do
517
+ def name
518
+ "#{first_name} #{last_name}"
519
+ end
520
+ end
521
+
522
+ person3.name # => "Rodrigo Serradura"
523
+ person4.name # => "Rodrigo Serradura"
524
+ ```
525
+
526
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
527
+
528
+ ### `Micro::Struct.immutable`
529
+
530
+ This method is as a shortcut to `Micro::Struct.with(:readonly, :instance_copy)`.
531
+ As it is quite common to see the usage of these two features, I decided to create this method to improve the DX.
532
+
533
+ Additional info:
534
+ 1. It accepts the `with:` option, which can be used to define additional features.
535
+ 2. The `.instance` method can be called after its usage.
536
+
537
+ Usage examples:
538
+
539
+ ```ruby
540
+ Micro::Struct.immutable.new(:name)
541
+
542
+ Micro::Struct.immutable.new(:name) do
543
+ def hi(other_name)
544
+ "Hi, #{other_name}! My name is #{name}"
545
+ end
546
+ end
547
+
548
+ Micro::Struct.immutable(with: :to_hash).new(:name)
549
+
550
+ Micro::Struct.immutable(with: [:to_hash, :to_proc]).new(:name)
551
+
552
+ Micro::Struct.immutable.instance(name: 'Rodrigo')
553
+
554
+ Micro::Struct.immutable(with: [:to_hash]).instance(name: 'Serradura')
555
+ ```
556
+
557
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
558
+
559
+ ### `Micro::Struct.readonly`
560
+
561
+ This method is as a shortcut to `Micro::Struct.with(:readonly)`.
562
+
563
+ Additional info:
564
+ 1. It accepts the `with:` option, which can be used to define additional features.
565
+ 2. The `.instance` method can be called after its usage.
566
+
567
+ Usage examples:
568
+
569
+ ```ruby
570
+ Micro::Struct.readonly.new(:name)
571
+
572
+ Micro::Struct.readonly.new(:name) do
573
+ def hi(other_name)
574
+ "Hi, #{other_name}! My name is #{name}"
575
+ end
576
+ end
577
+
578
+ Micro::Struct.readonly(with: :to_hash).new(:name)
579
+
580
+ Micro::Struct.readonly(with: [:to_hash, :to_proc]).new(:name)
581
+
582
+ Micro::Struct.readonly.instance(name: 'Rodrigo')
583
+
584
+ Micro::Struct.readonly(with: [:to_hash]).instance(name: 'Serradura')
585
+ ```
586
+
587
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
588
+
589
+ ### TL;DR
590
+
591
+ Like in a regular `Struct`, you can define one or many attributes but all of them will be required by default.
592
+
593
+ ```ruby
45
594
  Micro::Struct.new(:first_name, :last_name, ...)
595
+ ```
46
596
 
47
- # Use the `optional:` arg if you want some optional attributes.
597
+ Use the `optional:` argument if you want some optional attributes.
48
598
 
599
+ ```ruby
49
600
  Micro::Struct.new(:first_name, :last_name, optional: :gender)
50
601
 
51
602
  # Using `optional:` to define all attributes are optional.
52
603
 
53
604
  Micro::Struct.new(optional: [:first_name, :last_name])
605
+ ```
54
606
 
55
- # Use the `required:` arg to define required attributes.
607
+ Use the `required:` argument to define required attributes.
56
608
 
609
+ ```ruby
57
610
  Micro::Struct.new(
58
611
  required: [:first_name, :last_name],
59
612
  optional: [:gender, :age]
60
613
  )
614
+ ```
61
615
 
62
- # You can also pass a block to define custom methods.
616
+ You can also pass a block to define custom methods.
63
617
 
618
+ ```ruby
64
619
  Micro::Struct.new(:name) {}
620
+ ```
65
621
 
66
- # Available features (use one, many, or all) to create Structs with a special behavior:
67
- # .with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)
622
+ Available features (use one, many, or all) to create Structs with a special behavior:
68
623
 
69
- Micro::Struct.with(:to_ary).new(:name)
70
- Micro::Struct.with(:to_ary, :to_hash).new(:name)
71
- Micro::Struct.with(:to_ary, :to_hash, :to_proc).new(:name)
72
- Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly).new(:name)
73
- Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy).new(:name)
624
+ ```ruby
625
+ Micro::Struct.with(:to_ary)
626
+ Micro::Struct.with(:to_ary, :to_hash)
627
+ Micro::Struct.with(:to_ary, :to_hash, :to_proc)
628
+ Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly)
629
+ Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)
630
+ Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy, :exposed_features)
631
+ ```
74
632
 
75
- # All of the possible combinations to create a Ruby Struct. ;)
633
+ All of the possible combinations to create a Ruby Struct using `Micro::Struct`:
76
634
 
635
+ ```ruby
77
636
  Micro::Struct.new(*required)
78
637
  Micro::Struct.new(*required) {}
79
638
 
@@ -88,26 +647,133 @@ Micro::Struct.new(*required, optional: *) {}
88
647
 
89
648
  Micro::Struct.new(required: *, optional: *)
90
649
  Micro::Struct.new(required: *, optional: *) {}
650
+ ```
91
651
 
92
- # Any options above can be used by the `.new()` method of the struct creator returned by the `.with()` method.
652
+ Any options above can be used by the `.new()` method of the struct creator returned by the `.with()` method.
93
653
 
654
+ ```ruby
94
655
  Micro::Struct.with(*features).new(...) {}
95
656
  ```
96
657
 
658
+ Use `Micro::Struct.instance()` or `Micro::Struct.with(...).instance()` to create a struct instance from a given hash.
659
+
660
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
661
+
662
+ ## FAQ
663
+
664
+ ### How to override the Struct `.new` method?
665
+
666
+ The `.new` is an alias for the `.__new__` method, so you can use `.__new__` when overriding it.
667
+
668
+ ```ruby
669
+ module RGB
670
+ Number = ->(value) do
671
+ return value if value.is_a?(::Integer) && value >= 0 && value <= 255
672
+
673
+ raise TypeError, "#{value} must be an Integer(>= 0 and <= 255)"
674
+ end
675
+
676
+ Color = Micro::Struct.new(:red, :green, :blue) do
677
+ def self.new(r, g, b)
678
+ __new__(
679
+ red: Number[r],
680
+ green: Number[g],
681
+ blue: Number[b],
682
+ )
683
+ end
684
+
685
+ def to_hex
686
+ "##{red}#{green}#{blue}"
687
+ end
688
+ end
689
+ end
690
+
691
+ rgb_color = RGB::Color.new(1, 5, 255)
692
+ # => #<struct RGB::Color::Struct red=#<struct RGB::Number value=1>, green=#<struct RGB::Number value=5>, blue=#<struct RGB::Number value=255>>
693
+
694
+ rgb_color.to_hex
695
+ # => "#0105ff"
696
+
697
+ RGB::Color.new(-1, 5, 255)
698
+ # => TypeError: -1 must be an Integer(>= 0 and <= 255)
699
+ ```
700
+
701
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
702
+
703
+ ### Can I override the Struct initializer?
704
+
705
+ Yes, you can, but the initializer must handle the arguments as positional ones.
706
+
707
+ ```ruby
708
+ RGBColor = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
709
+ Number = ->(value) do
710
+ return value if value.is_a?(::Integer) && value >= 0 && value <= 255
711
+
712
+ raise TypeError, "#{value} must be an Integer(>= 0 and <= 255)"
713
+ end
714
+
715
+ def initialize(r, g, b)
716
+ super(Number[r], Number[g], Number[b])
717
+ end
718
+
719
+ def to_hex
720
+ '#%02x%02x%02x' % self
721
+ end
722
+ end
723
+
724
+ rgb_color = RGBColor.new(red: 1, green: 1, blue: 255)
725
+ # #<struct RGBColor red=1, green=1, blue=255>
726
+
727
+ r, g, b = rgb_color
728
+
729
+ [r,g,b]
730
+ # [1, 1, 255]
731
+
732
+ rgb_color.to_hex
733
+ # "#0101ff"
734
+
735
+ RGBColor.new(red: 1, green: -1, blue: 255)
736
+ # TypeError (-1 must be an Integer(>= 0 and <= 255))
737
+ ```
738
+
739
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
740
+
97
741
  ## Development
98
742
 
99
743
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
100
744
 
745
+ Additional tools:
746
+ - Sorbet (type checker): `bundle exec srb tc` (requires `Ruby >= 2.7`).
747
+ - Rubocop (linter and code formatter): `bundle rubocop` (requires `Ruby >= 2.5`).
748
+
101
749
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
102
750
 
751
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
752
+
103
753
  ## Contributing
104
754
 
105
- Bug reports and pull requests are welcome on GitHub at https://github.com/serradura/u-struct. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/serradura/u-struct/blob/master/CODE_OF_CONDUCT.md).
755
+ Bug reports and pull requests are welcome on GitHub at https://github.com/serradura/u-struct. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/serradura/u-struct/blob/main/CODE_OF_CONDUCT.md).
756
+
757
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
106
758
 
107
759
  ## License
108
760
 
109
761
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
110
762
 
763
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
764
+
111
765
  ## Code of Conduct
112
766
 
113
- Everyone interacting in the Micro::Struct project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/u-struct/blob/master/CODE_OF_CONDUCT.md).
767
+ Everyone interacting in the `Micro::Struct` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/u-struct/blob/main/CODE_OF_CONDUCT.md).
768
+
769
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
770
+
771
+ ## Contact
772
+
773
+ Rodrigo Serradura - [Twitter](https://twitter.com/serradura) | [LinkedIn](https://www.linkedin.com/in/rodrigo-serradura/).
774
+
775
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
776
+
777
+ ## Acknowledgments
778
+
779
+ - [`@vitoravelino`](https://github.com/vitoravelino) thanks for talking about some gem's ideas and reviewing the documentation.