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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +62 -17
- data/.gitignore +3 -0
- data/.rubocop.yml +137 -0
- data/.rubocop_todo.yml +10 -0
- data/.tool-versions +1 -0
- data/Appraisals +84 -0
- data/CHANGELOG.md +101 -47
- data/CLAUDE.md +144 -0
- data/Gemfile +20 -3
- data/README.md +187 -72
- data/Rakefile +35 -5
- data/bin/console +3 -3
- data/bin/matrix +16 -0
- data/bin/setup +4 -0
- data/bin/tapioca +28 -0
- data/examples/rgb/number.rb +1 -1
- data/examples/rgb_1.rb +3 -3
- data/examples/rgb_2.rb +2 -2
- data/examples/rgb_3.rb +1 -1
- data/lib/micro/struct/factory/create_struct.rb +12 -5
- data/lib/micro/struct/factory/members.rb +1 -0
- data/lib/micro/struct/factory.rb +10 -5
- data/lib/micro/struct/features.rb +18 -23
- data/lib/micro/struct/normalize_names.rb +4 -3
- data/lib/micro/struct/version.rb +2 -1
- data/lib/micro/struct.rb +32 -5
- data/lib/u-struct.rb +2 -0
- data/rbi/micro/struct/factory/create_struct.rbi +60 -0
- data/rbi/micro/struct/factory/members.rbi +67 -0
- data/rbi/micro/struct/factory.rbi +41 -0
- data/rbi/micro/struct/features.rbi +41 -0
- data/rbi/micro/struct/normalize_names.rbi +20 -0
- data/rbi/micro/struct/version.rbi +3 -0
- data/rbi/micro/struct.rbi +68 -0
- data/sorbet/config +8 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +54 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +11 -0
- data/sorbet/rbi/gems/docile@1.4.0.rbi +54 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
- data/sorbet/rbi/gems/minitest@5.15.0.rbi +345 -0
- data/sorbet/rbi/gems/parser@3.1.0.0.rbi +1196 -0
- data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +806 -0
- data/sorbet/rbi/gems/rbi@0.0.9.rbi +1602 -0
- data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +89 -0
- data/sorbet/rbi/gems/simplecov@0.21.2.rbi +577 -0
- data/sorbet/rbi/gems/simplecov_json_formatter@0.1.3.rbi +8 -0
- data/sorbet/rbi/gems/spoom@1.1.8.rbi +1252 -0
- data/sorbet/rbi/gems/tapioca@0.6.2.rbi +1232 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +844 -0
- data/sorbet/rbi/gems/unparser@0.6.3.rbi +8 -0
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +601 -0
- data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +199 -0
- data/sorbet/rbi/gems/yard@0.9.27.rbi +4112 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- data/u-struct.gemspec +9 -9
- metadata +60 -14
- data/.vscode/settings.json +0 -8
- data/bin/prepare_coverage +0 -27
- 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/
|
|
13
|
-
<img alt="Build Status" src="https://github.com/
|
|
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
|
-
- [
|
|
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
|
|
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
|
|
43
|
-
- [Can I
|
|
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
|
|
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).
|
|
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
|
-
###
|
|
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).
|
|
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
|
-
|
|
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
|
-
|
|
198
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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:
|
|
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
|
-
|
|
223
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ back to top</a>)</p>
|
|
215
224
|
|
|
216
225
|
#### `optional:` option
|
|
217
226
|
|
|
218
|
-
|
|
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
|
-
|
|
251
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
270
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
|
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
|
-
|
|
291
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
311
|
+
# Use the factory to create structs with the same characteristics:
|
|
300
312
|
|
|
301
|
-
Person.new
|
|
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
|
-
|
|
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-">⬆️ 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
|
-
|
|
349
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
|
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
|
-
|
|
372
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
|
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
|
-
|
|
393
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
414
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
433
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
461
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
490
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ back to top</a>)</p>
|
|
458
491
|
|
|
459
|
-
### `Micro::Struct.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
|
-
|
|
534
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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-">⬆️ 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-">⬆️ back to top</a>)</p>
|
|
502
598
|
|
|
503
599
|
### TL;DR
|
|
504
600
|
|
|
505
|
-
Like in a regular Struct
|
|
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:`
|
|
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:`
|
|
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
|
-
|
|
670
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ back to top</a>)</p>
|
|
576
671
|
|
|
577
672
|
## FAQ
|
|
578
673
|
|
|
579
|
-
### How to
|
|
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
|
|
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 =
|
|
680
|
+
Number = ->(value) do
|
|
681
|
+
return value if value.is_a?(::Integer) && value >= 0 && value <= 255
|
|
586
682
|
|
|
587
|
-
|
|
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
|
-
|
|
686
|
+
Color = Micro::Struct.new(:red, :green, :blue) do
|
|
594
687
|
def self.new(r, g, b)
|
|
595
688
|
__new__(
|
|
596
|
-
red: Number
|
|
597
|
-
green: Number
|
|
598
|
-
blue: Number
|
|
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
|
-
|
|
711
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ back to top</a>)</p>
|
|
612
712
|
|
|
613
|
-
### Can I
|
|
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
|
-
|
|
749
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
762
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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/
|
|
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
|
-
|
|
768
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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
|
-
|
|
774
|
+
<p align="right">(<a href="#table-of-contents-">⬆️ 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/
|
|
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-">⬆️ 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-">⬆️ back to top</a>)</p>
|
|
787
|
+
|
|
788
|
+
## Acknowledgments
|
|
674
789
|
|
|
675
|
-
[
|
|
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
|
|
4
|
-
require
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rake/testtask'
|
|
5
5
|
|
|
6
6
|
Rake::TestTask.new(:test) do |t|
|
|
7
|
-
t.libs <<
|
|
8
|
-
t.libs <<
|
|
9
|
-
t.test_files = FileList[
|
|
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
|
|
5
|
-
require
|
|
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
|
|
14
|
+
require 'irb'
|
|
15
15
|
IRB.start(__FILE__)
|
data/bin/matrix
ADDED
data/bin/setup
CHANGED
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')
|