u-struct 1.0.0 → 2.0.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +62 -17
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +137 -0
  5. data/.rubocop_todo.yml +10 -0
  6. data/.tool-versions +1 -0
  7. data/Appraisals +84 -0
  8. data/CHANGELOG.md +101 -47
  9. data/CLAUDE.md +144 -0
  10. data/Gemfile +20 -3
  11. data/README.md +187 -72
  12. data/Rakefile +35 -5
  13. data/bin/console +3 -3
  14. data/bin/matrix +16 -0
  15. data/bin/setup +4 -0
  16. data/bin/tapioca +28 -0
  17. data/examples/rgb/number.rb +1 -1
  18. data/examples/rgb_1.rb +3 -3
  19. data/examples/rgb_2.rb +2 -2
  20. data/examples/rgb_3.rb +1 -1
  21. data/lib/micro/struct/factory/create_struct.rb +12 -5
  22. data/lib/micro/struct/factory/members.rb +1 -0
  23. data/lib/micro/struct/factory.rb +10 -5
  24. data/lib/micro/struct/features.rb +18 -23
  25. data/lib/micro/struct/normalize_names.rb +4 -3
  26. data/lib/micro/struct/version.rb +2 -1
  27. data/lib/micro/struct.rb +32 -5
  28. data/lib/u-struct.rb +2 -0
  29. data/rbi/micro/struct/factory/create_struct.rbi +60 -0
  30. data/rbi/micro/struct/factory/members.rbi +67 -0
  31. data/rbi/micro/struct/factory.rbi +41 -0
  32. data/rbi/micro/struct/features.rbi +41 -0
  33. data/rbi/micro/struct/normalize_names.rbi +20 -0
  34. data/rbi/micro/struct/version.rbi +3 -0
  35. data/rbi/micro/struct.rbi +68 -0
  36. data/sorbet/config +8 -0
  37. data/sorbet/rbi/gems/ast@2.4.2.rbi +54 -0
  38. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  39. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +11 -0
  40. data/sorbet/rbi/gems/docile@1.4.0.rbi +54 -0
  41. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  42. data/sorbet/rbi/gems/minitest@5.15.0.rbi +345 -0
  43. data/sorbet/rbi/gems/parser@3.1.0.0.rbi +1196 -0
  44. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  45. data/sorbet/rbi/gems/rake@13.0.6.rbi +806 -0
  46. data/sorbet/rbi/gems/rbi@0.0.9.rbi +1602 -0
  47. data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +89 -0
  48. data/sorbet/rbi/gems/simplecov@0.21.2.rbi +577 -0
  49. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.3.rbi +8 -0
  50. data/sorbet/rbi/gems/spoom@1.1.8.rbi +1252 -0
  51. data/sorbet/rbi/gems/tapioca@0.6.2.rbi +1232 -0
  52. data/sorbet/rbi/gems/thor@1.2.1.rbi +844 -0
  53. data/sorbet/rbi/gems/unparser@0.6.3.rbi +8 -0
  54. data/sorbet/rbi/gems/webrick@1.7.0.rbi +601 -0
  55. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +199 -0
  56. data/sorbet/rbi/gems/yard@0.9.27.rbi +4112 -0
  57. data/sorbet/tapioca/config.yml +13 -0
  58. data/sorbet/tapioca/require.rb +4 -0
  59. data/u-struct.gemspec +9 -9
  60. metadata +60 -14
  61. data/.vscode/settings.json +0 -8
  62. data/bin/prepare_coverage +0 -27
  63. data/bin/test +0 -8
data/README.md CHANGED
@@ -1,28 +1,34 @@
1
1
  <p align="center">
2
2
  <h1 align="center">🧱 μ-struct</h1>
3
3
  <p align="center"><i>Create powered Ruby structs.</i></p>
4
- <br>
5
4
  </p>
6
5
 
7
6
  <p align="center">
8
- <img src="https://img.shields.io/badge/ruby%20%3E=%202.2,%20%3C%203.2-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
9
7
  <a href="https://rubygems.org/gems/u-struct">
10
8
  <img alt="Gem" src="https://img.shields.io/gem/v/u-struct.svg?style=flat-square">
11
9
  </a>
12
- <a href="https://github.com/serradura/u-struct/actions/workflows/ci.yml">
13
- <img alt="Build Status" src="https://github.com/serradura/u-struct/actions/workflows/ci.yml/badge.svg">
14
- </a>
15
- <a href="https://codeclimate.com/github/serradura/u-struct/maintainability">
16
- <img alt="Maintainability" src="https://api.codeclimate.com/v1/badges/2cc0204411cc2b392b7a/maintainability">
17
- </a>
18
- <a href="https://codeclimate.com/github/serradura/u-struct/test_coverage">
19
- <img alt="Test Coverage" src="https://api.codeclimate.com/v1/badges/2cc0204411cc2b392b7a/test_coverage">
10
+ <a href="https://github.com/u-gems/u-struct/actions/workflows/ci.yml">
11
+ <img alt="Build Status" src="https://github.com/u-gems/u-struct/actions/workflows/ci.yml/badge.svg">
20
12
  </a>
13
+ <br/>
14
+ <a href="https://qlty.sh/gh/u-gems/projects/u-struct"><img src="https://qlty.sh/gh/u-gems/projects/u-struct/maintainability.svg" alt="Maintainability" /></a>
15
+ <a href="https://qlty.sh/gh/u-gems/projects/u-struct"><img src="https://qlty.sh/gh/u-gems/projects/u-struct/coverage.svg" alt="Code Coverage" /></a>
16
+ <br/>
17
+ <img src="https://img.shields.io/badge/Ruby%20%3E%3D%202.7%2C%20%3C%3D%20Head-ruby.svg?colorA=444&colorB=333" alt="Ruby">
18
+ <img src="https://img.shields.io/badge/Rails%20%3E%3D%206.0%2C%20%3C%3D%20Edge-rails.svg?colorA=444&colorB=333" alt="Rails">
21
19
  </p>
22
20
 
21
+ > [!IMPORTANT]
22
+ > **No breaking API changes — ever.** `u-struct`'s public API is frozen: every release stays backward-compatible, so code that builds on it keeps working.
23
+ >
24
+ > Major version bumps signal only that a Ruby or Rails version was dropped from the supported matrix — per SemVer, a dependency-floor change. Your code keeps working.
25
+ >
26
+ > **Maintenance mode.** Ruby 3.2+ ships a native [`Data`](https://docs.ruby-lang.org/en/3.2/Data.html) type that covers this gem's use case; `u-struct` is kept only to support existing apps and gets no new features. New code should prefer `Data`.
27
+
23
28
  # Table of contents: <!-- omit in toc -->
29
+
24
30
  - [Introduction](#introduction)
25
- - [Project Motivation](#project-motivation)
31
+ - [Motivation](#motivation)
26
32
  - [Installation](#installation)
27
33
  - [Usage](#usage)
28
34
  - [`Micro::Struct.new`](#microstructnew)
@@ -30,25 +36,30 @@
30
36
  - [`required:` option](#required-option)
31
37
  - [Defining custom methods/behavior](#defining-custom-methodsbehavior)
32
38
  - [`Micro::Struct.with`](#microstructwith)
39
+ - [`Micro::Struct[]`](#microstruct)
33
40
  - [`:to_ary`](#to_ary)
34
41
  - [`:to_hash`](#to_hash)
35
42
  - [`:to_proc`](#to_proc)
36
43
  - [`:readonly`](#readonly)
37
44
  - [`:instance_copy`](#instance_copy)
38
45
  - [`:exposed_features`](#exposed_features)
39
- - [`Micro::Struct.instance()` or `Micro::Struct.with(...).instance()`](#microstructinstance-or-microstructwithinstance)
46
+ - [`Micro::Struct.instance` or `Micro::Struct.with(...).instance`](#microstructinstance-or-microstructwithinstance)
47
+ - [`Micro::Struct.immutable`](#microstructimmutable)
48
+ - [`Micro::Struct.readonly`](#microstructreadonly)
40
49
  - [TL;DR](#tldr)
41
50
  - [FAQ](#faq)
42
- - [How to overwrite the Struct `.new` method?](#how-to-overwrite-the-struct-new-method)
43
- - [Can I overwrite the Struct initializer?](#can-i-overwrite-the-struct-initializer)
51
+ - [How to override the Struct `.new` method?](#how-to-override-the-struct-new-method)
52
+ - [Can I override the Struct initializer?](#can-i-override-the-struct-initializer)
44
53
  - [Development](#development)
45
54
  - [Contributing](#contributing)
46
55
  - [License](#license)
47
56
  - [Code of Conduct](#code-of-conduct)
57
+ - [Contact](#contact)
58
+ - [Acknowledgments](#acknowledgments)
48
59
 
49
60
  ## Introduction
50
61
 
51
- Ruby Struct is a versatile data structure because it can behave like an Array, Hash, and ordinary object. e.g.
62
+ Ruby `Struct` is a versatile data structure because it can behave like an `Array`, `Hash`, and ordinary object:
52
63
 
53
64
  ```ruby
54
65
  Person = Struct.new(:first_name, :last_name)
@@ -99,7 +110,7 @@ person.to_a
99
110
  # ["John", "Doe"]
100
111
  ```
101
112
 
102
- Because of these characteristics, structs could be excellent candidates to create different kinds of POROs (Plain Old Ruby Objects). But, 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, as it doesn't require all the arguments, some developers can still avoid its usage.
113
+ 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.
103
114
 
104
115
  Look at the example showing the Struct's `keyword_init:` option creating a constructor with optional keyword arguments:
105
116
 
@@ -118,9 +129,9 @@ Person.new(foo: 1, bar: 2)
118
129
  # ArgumentError (unknown keywords: foo, bar)
119
130
  ```
120
131
 
121
- ### Project Motivation
132
+ ### Motivation
122
133
 
123
- So, given this introduction, the idea of this project is to provide a way of creating Ruby Structs with some [powerful features](#microstructwith). And to start, let's see how the `Micro::Struct.new()` works.
134
+ 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.
124
135
 
125
136
  ```ruby
126
137
  require 'u-struct'
@@ -136,7 +147,7 @@ Person.new
136
147
 
137
148
  As you can see, the struct instantiation raised an error because all of the keywords arguments are required.
138
149
 
139
- But, if you need one or many optional arguments, you can use the `optional:` option to define them. e.g.
150
+ If you need one or many optional arguments, you can use the `optional:` option to define them:
140
151
 
141
152
  ```ruby
142
153
  Person = Micro::Struct.new(:first_name, optional: :last_name)
@@ -148,9 +159,7 @@ Person.new(first_name: 'Rodrigo')
148
159
  # #<struct Person first_name="Rodrigo", last_name=nil>
149
160
  ```
150
161
 
151
- If you want a Struct only with optional members (or attributes), as the `keyword_init:` option does.
152
-
153
- You can declare all attributes within the `optional:` option.
162
+ 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:
154
163
 
155
164
  ```ruby
156
165
  Person = Micro::Struct.new(optional: [:first_name, :last_name])
@@ -186,15 +195,15 @@ Or install it yourself as:
186
195
 
187
196
  $ gem install u-struct
188
197
 
189
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
198
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
190
199
 
191
200
  ## Usage
192
201
 
193
202
  ### `Micro::Struct.new`
194
203
 
195
- Like `Struct.new`, you will use `Micro::Struct.new` to create your Struct classes.
204
+ Like `Struct.new`, you will use `Micro::Struct.new` to create your `Struct` classes.
196
205
 
197
- The key difference is: Structs created from `Micro::Struct` will use keyword arguments in their constructors.
206
+ The key difference is: the `Struct` created from `Micro::Struct` will use keyword arguments in their constructors.
198
207
 
199
208
  ```ruby
200
209
  Person = Struct.new(:name) # Person
@@ -211,11 +220,11 @@ Person.new # #<struct Person name=nil>
211
220
  Persona.new # ArgumentError (missing keyword: :name)
212
221
  ```
213
222
 
214
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
223
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
215
224
 
216
225
  #### `optional:` option
217
226
 
218
- But if you need optional attributes, you can use this to define them.
227
+ If you need optional attributes, you can use this to define them.
219
228
 
220
229
  ```ruby
221
230
  Person = Micro::Struct.new(:name, optional: :age)
@@ -239,7 +248,7 @@ Person.new(name: 'John')
239
248
  # #<struct Person name="John", age=nil, nickname=nil>
240
249
  ```
241
250
 
242
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
251
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
243
252
 
244
253
  #### `required:` option
245
254
 
@@ -258,11 +267,11 @@ Person.new first_name: 'John', last_name: 'Doe'
258
267
  # #<struct Person first_name="John", last_name="Doe", age=nil>
259
268
  ```
260
269
 
261
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
270
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
262
271
 
263
272
  #### Defining custom methods/behavior
264
273
 
265
- The `Micro::Struct.new` accepts a block as a regular Struct, and you can use it to define some custom behavior/methods.
274
+ The `Micro::Struct.new` accepts a block as a regular `Struct`, and you can use it to define some custom behavior/methods.
266
275
 
267
276
  ```ruby
268
277
  Person = Micro::Struct.new(:first_name, :last_name, optional: :age) do
@@ -279,13 +288,14 @@ person.last_name # "Serradura"
279
288
  person.name # "Rodrigo Serradura"
280
289
  ```
281
290
 
282
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
291
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
283
292
 
284
293
  ### `Micro::Struct.with`
285
294
 
286
- This method can do two things: first, it can create Struct factories; second, it sets some special behavior to their structs.
295
+ This method can do two things: first, it can create `Struct` factories; second, it sets some special behavior to their structs.
287
296
 
288
297
  These are all of the available features which you can use (pick one, many, or all of them):
298
+
289
299
  - [`:to_ary`](#to_ary)
290
300
  - [`:to_hash`](#to_hash)
291
301
  - [`:to_proc`](#to_proc)
@@ -293,31 +303,54 @@ These are all of the available features which you can use (pick one, many, or al
293
303
  - [`:instance_copy`](#instance_copy)
294
304
  - [`:exposed_features`](#exposed_features)
295
305
 
306
+ Look at an example of defining a `Struct` factory that can create "immutable" structs by picking the `:readonly`, `:instance_copy` features.
307
+
296
308
  ```ruby
297
309
  ReadonlyStruct = Micro::Struct.with(:readonly, :instance_copy)
298
310
 
299
- Person = ReadonlyStruct.new(:first_name, :last_name)
311
+ # Use the factory to create structs with the same characteristics:
300
312
 
301
- Person.new # ArgumentError (missing keywords: :first_name, :last_name)
313
+ Person = ReadonlyStruct.new(:first_name, :last_name)
302
314
 
303
315
  person = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
304
316
  # #<struct Person first_name="Rodrigo", last_name="Rodrigues">
305
317
 
318
+ # The `:readonly` sets all the Struct writers as private.
319
+
306
320
  person.last_name = ''
307
321
  # NoMethodError (private method `last_name=' called for #<struct Person ...>)
308
322
 
309
323
  person[:last_name] = ''
310
324
  # NoMethodError (private method `[]=' called for #<struct Person ...>)
311
325
 
312
- person.with(last_name: 'Serradura')
326
+ # The `:instance_copy` defines a `#with` instance method,
327
+ # which allows you to create a new instance from the current struct state.
328
+
329
+ new_person = person.with(last_name: 'Serradura')
313
330
  # #<struct Person first_name="Rodrigo", last_name="Serradura">
331
+
332
+ new_person == person
333
+ # false
334
+
335
+ new_person.class == person.class
336
+ # true
337
+ ```
338
+
339
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
340
+
341
+ ### `Micro::Struct[]`
342
+
343
+ The `[]` brackets method is as an alias of `Micro::Struct.with`. e.g.
344
+
345
+ ```ruby
346
+ Micro::Struct[:readonly, :to_hash] # is the same as Micro::Struct.with(:readonly, :to_hash)
314
347
  ```
315
348
 
316
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
349
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
317
350
 
318
351
  #### `:to_ary`
319
352
 
320
- Defines a `#to_ary` method which will invoke the struct `#to_a` method, so if you overwrite the `#to_a` method you will also affect it.
353
+ 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.
321
354
 
322
355
  The `#to_ary` makes Ruby know how to deconstruct an object like an array.
323
356
 
@@ -336,11 +369,11 @@ p last_name # "Serradura"
336
369
  p first_and_last_name # ["Rodrigo", "Serradura"]
337
370
  ```
338
371
 
339
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
372
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
340
373
 
341
374
  #### `:to_hash`
342
375
 
343
- Defines a `#to_hash` method which will invoke the struct `#to_h` method, so if you overwrite the `#to_a` method you will also affect it.
376
+ 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.
344
377
 
345
378
  The `#to_hash` makes Ruby know how to deconstruct an object like a hash.
346
379
 
@@ -357,7 +390,7 @@ greet(**person)
357
390
  # Hi Rodrigo Serradura!
358
391
  ```
359
392
 
360
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
393
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
361
394
 
362
395
  #### `:to_proc`
363
396
 
@@ -378,7 +411,7 @@ Person = Micro::Struct.with(:to_proc).new(:first_name, :last_name)
378
411
  # ]
379
412
  ```
380
413
 
381
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
414
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
382
415
 
383
416
  #### `:readonly`
384
417
 
@@ -397,7 +430,7 @@ person[:last_name] = ''
397
430
  # NoMethodError (private method `[]=' called for #<struct Person ...>)
398
431
  ```
399
432
 
400
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
433
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
401
434
 
402
435
  #### `:instance_copy`
403
436
 
@@ -425,7 +458,7 @@ person.last_name # => "Serradura"
425
458
  new_person.last_name # => "Doe"
426
459
  ```
427
460
 
428
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
461
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
429
462
 
430
463
  #### `:exposed_features`
431
464
 
@@ -454,9 +487,9 @@ Person.features.options?(:to_proc, :readonly) # => true
454
487
  Person.features.options?(:to_ary, :readonly) # => false
455
488
  ```
456
489
 
457
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
490
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
458
491
 
459
- ### `Micro::Struct.instance()` or `Micro::Struct.with(...).instance()`
492
+ ### `Micro::Struct.instance` or `Micro::Struct.with(...).instance`
460
493
 
461
494
  Creates a struct instance from a given hash. This could be useful to create constants or a singleton value.
462
495
 
@@ -498,18 +531,80 @@ person3.name # => "Rodrigo Serradura"
498
531
  person4.name # => "Rodrigo Serradura"
499
532
  ```
500
533
 
501
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
534
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
535
+
536
+ ### `Micro::Struct.immutable`
537
+
538
+ This method is as a shortcut to `Micro::Struct.with(:readonly, :instance_copy)`.
539
+ As it is quite common to see the usage of these two features, I decided to create this method to improve the DX.
540
+
541
+ Additional info:
542
+
543
+ 1. It accepts the `with:` option, which can be used to define additional features.
544
+ 2. The `.instance` method can be called after its usage.
545
+
546
+ Usage examples:
547
+
548
+ ```ruby
549
+ Micro::Struct.immutable.new(:name)
550
+
551
+ Micro::Struct.immutable.new(:name) do
552
+ def hi(other_name)
553
+ "Hi, #{other_name}! My name is #{name}"
554
+ end
555
+ end
556
+
557
+ Micro::Struct.immutable(with: :to_hash).new(:name)
558
+
559
+ Micro::Struct.immutable(with: [:to_hash, :to_proc]).new(:name)
560
+
561
+ Micro::Struct.immutable.instance(name: 'Rodrigo')
562
+
563
+ Micro::Struct.immutable(with: [:to_hash]).instance(name: 'Serradura')
564
+ ```
565
+
566
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
567
+
568
+ ### `Micro::Struct.readonly`
569
+
570
+ This method is as a shortcut to `Micro::Struct.with(:readonly)`.
571
+
572
+ Additional info:
573
+
574
+ 1. It accepts the `with:` option, which can be used to define additional features.
575
+ 2. The `.instance` method can be called after its usage.
576
+
577
+ Usage examples:
578
+
579
+ ```ruby
580
+ Micro::Struct.readonly.new(:name)
581
+
582
+ Micro::Struct.readonly.new(:name) do
583
+ def hi(other_name)
584
+ "Hi, #{other_name}! My name is #{name}"
585
+ end
586
+ end
587
+
588
+ Micro::Struct.readonly(with: :to_hash).new(:name)
589
+
590
+ Micro::Struct.readonly(with: [:to_hash, :to_proc]).new(:name)
591
+
592
+ Micro::Struct.readonly.instance(name: 'Rodrigo')
593
+
594
+ Micro::Struct.readonly(with: [:to_hash]).instance(name: 'Serradura')
595
+ ```
596
+
597
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
502
598
 
503
599
  ### TL;DR
504
600
 
505
- Like in a regular Struct, you can define one or many attributes.
506
- But all of them will be required by default.
601
+ Like in a regular `Struct`, you can define one or many attributes but all of them will be required by default.
507
602
 
508
603
  ```ruby
509
604
  Micro::Struct.new(:first_name, :last_name, ...)
510
605
  ```
511
606
 
512
- Use the `optional:` arg if you want some optional attributes.
607
+ Use the `optional:` argument if you want some optional attributes.
513
608
 
514
609
  ```ruby
515
610
  Micro::Struct.new(:first_name, :last_name, optional: :gender)
@@ -519,7 +614,7 @@ Micro::Struct.new(:first_name, :last_name, optional: :gender)
519
614
  Micro::Struct.new(optional: [:first_name, :last_name])
520
615
  ```
521
616
 
522
- Use the `required:` arg to define required attributes.
617
+ Use the `required:` argument to define required attributes.
523
618
 
524
619
  ```ruby
525
620
  Micro::Struct.new(
@@ -572,45 +667,50 @@ Micro::Struct.with(*features).new(...) {}
572
667
 
573
668
  Use `Micro::Struct.instance()` or `Micro::Struct.with(...).instance()` to create a struct instance from a given hash.
574
669
 
575
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
670
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
576
671
 
577
672
  ## FAQ
578
673
 
579
- ### How to overwrite the Struct `.new` method?
674
+ ### How to override the Struct `.new` method?
580
675
 
581
- The `.new` is an alias for the `.__new__` method, so you can use `.__new__` when overwriting it.
676
+ The `.new` is an alias for the `.__new__` method, so you can use `.__new__` when overriding it.
582
677
 
583
678
  ```ruby
584
679
  module RGB
585
- Number = ::Struct.new(:value) { def to_s; '%02x' % value; end }
680
+ Number = ->(value) do
681
+ return value if value.is_a?(::Integer) && value >= 0 && value <= 255
586
682
 
587
- Color = Micro::Struct.new(:red, :green, :blue) do
588
- def to_hex
589
- "##{red}#{green}#{blue}"
590
- end
683
+ raise TypeError, "#{value} must be an Integer(>= 0 and <= 255)"
591
684
  end
592
685
 
593
- module Color
686
+ Color = Micro::Struct.new(:red, :green, :blue) do
594
687
  def self.new(r, g, b)
595
688
  __new__(
596
- red: Number.new(r),
597
- green: Number.new(g),
598
- blue: Number.new(b),
689
+ red: Number[r],
690
+ green: Number[g],
691
+ blue: Number[b],
599
692
  )
600
693
  end
694
+
695
+ def to_hex
696
+ "##{red}#{green}#{blue}"
697
+ end
601
698
  end
602
699
  end
603
700
 
604
- rgb_color = RGB::Color.new(1,5,255)
701
+ rgb_color = RGB::Color.new(1, 5, 255)
605
702
  # => #<struct RGB::Color::Struct red=#<struct RGB::Number value=1>, green=#<struct RGB::Number value=5>, blue=#<struct RGB::Number value=255>>
606
703
 
607
704
  rgb_color.to_hex
608
705
  # => "#0105ff"
706
+
707
+ RGB::Color.new(-1, 5, 255)
708
+ # => TypeError: -1 must be an Integer(>= 0 and <= 255)
609
709
  ```
610
710
 
611
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
711
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
612
712
 
613
- ### Can I overwrite the Struct initializer?
713
+ ### Can I override the Struct initializer?
614
714
 
615
715
  Yes, you can, but the initializer must handle the arguments as positional ones.
616
716
 
@@ -646,30 +746,45 @@ RGBColor.new(red: 1, green: -1, blue: 255)
646
746
  # TypeError (-1 must be an Integer(>= 0 and <= 255))
647
747
  ```
648
748
 
649
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
749
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
650
750
 
651
751
  ## Development
652
752
 
653
753
  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.
654
754
 
755
+ Additional tools:
756
+
757
+ - Sorbet (type checker): `bundle exec srb tc` (requires `Ruby >= 2.7`).
758
+ - Rubocop (linter and code formatter): `bundle rubocop` (requires `Ruby >= 2.5`).
759
+
655
760
  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).
656
761
 
657
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
762
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
658
763
 
659
764
  ## Contributing
660
765
 
661
- 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).
766
+ Bug reports and pull requests are welcome on GitHub at https://github.com/u-gems/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/u-gems/u-struct/blob/main/CODE_OF_CONDUCT.md).
662
767
 
663
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
768
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
664
769
 
665
770
  ## License
666
771
 
667
772
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
668
773
 
669
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
774
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
670
775
 
671
776
  ## Code of Conduct
672
777
 
673
- 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).
778
+ 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/u-gems/u-struct/blob/main/CODE_OF_CONDUCT.md).
779
+
780
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
781
+
782
+ ## Contact
783
+
784
+ Rodrigo Serradura - [Twitter](https://twitter.com/serradura) | [LinkedIn](https://www.linkedin.com/in/rodrigo-serradura/).
785
+
786
+ <p align="right">(<a href="#table-of-contents-">⬆️ &nbsp;back to top</a>)</p>
787
+
788
+ ## Acknowledgments
674
789
 
675
- [⬆️ &nbsp;Back to Top](#table-of-contents-)
790
+ - [`@vitoravelino`](https://github.com/vitoravelino) thanks for talking about some gem's ideas and reviewing the documentation.
data/Rakefile CHANGED
@@ -1,12 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
5
 
6
6
  Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ end
11
+
12
+ require 'appraisal/task'
13
+
14
+ Appraisal::Task.new
15
+
16
+ desc 'Run the full test suite against every supported Rails version'
17
+ task :matrix do
18
+ appraisals =
19
+ if RUBY_VERSION < '3.1'
20
+ %w[rails-6-0 rails-6-1 rails-7-0 rails-7-1]
21
+ elsif RUBY_VERSION < '3.2'
22
+ %w[rails-7-0 rails-7-1 rails-7-2]
23
+ elsif RUBY_VERSION < '3.3'
24
+ %w[rails-7-0 rails-7-1 rails-7-2 rails-8-0]
25
+ elsif RUBY_VERSION < '3.4'
26
+ %w[rails-7-0 rails-7-1 rails-7-2 rails-8-0 rails-8-1 rails-edge]
27
+ elsif RUBY_VERSION < '4.0'
28
+ %w[rails-7-2 rails-8-0 rails-8-1 rails-edge]
29
+ else
30
+ %w[rails-8-1 rails-edge]
31
+ end
32
+
33
+ # Baseline (no activemodel)
34
+ sh 'bundle exec rake test'
35
+
36
+ # Each activemodel appraisal
37
+ appraisals.each do |appraisal|
38
+ sh "bundle exec appraisal #{appraisal} rake test"
39
+ end
10
40
  end
11
41
 
12
42
  task default: :test
data/bin/console CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
- require "micro/struct"
4
+ require 'bundler/setup'
5
+ require 'micro/struct'
6
6
 
7
7
  # You can add fixtures and/or initialization code here to make experimenting
8
8
  # with your gem easier. You can also use a different console, if you like.
@@ -11,5 +11,5 @@ require "micro/struct"
11
11
  # require "pry"
12
12
  # Pry.start
13
13
 
14
- require "irb"
14
+ require 'irb'
15
15
  IRB.start(__FILE__)
data/bin/matrix ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ ruby -v
7
+
8
+ rm -f Gemfile.lock
9
+
10
+ bundle install
11
+
12
+ bundle exec appraisal update
13
+
14
+ bundle exec rake matrix
15
+
16
+ ruby -v
data/bin/setup CHANGED
@@ -3,6 +3,10 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
+ rm -f Gemfile.lock
7
+
6
8
  bundle install
7
9
 
10
+ bundle exec appraisal install
11
+
8
12
  # Do any other automated setup that you need to do here
data/bin/tapioca ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tapioca' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', Pathname.new(__FILE__).realpath)
13
+
14
+ bundle_binstub = File.expand_path('bundle', __dir__)
15
+
16
+ if File.file?(bundle_binstub)
17
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
18
+ load(bundle_binstub)
19
+ else
20
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
21
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
22
+ end
23
+ end
24
+
25
+ require 'rubygems'
26
+ require 'bundler/setup'
27
+
28
+ load Gem.bin_path('tapioca', 'tapioca')
@@ -11,7 +11,7 @@ module RGB
11
11
  end
12
12
 
13
13
  def to_s
14
- @to_s ||= '%02x' % value
14
+ @to_s ||= format('%02x', value)
15
15
  end
16
16
 
17
17
  def inspect