u-struct 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +27 -0
- data/.gitignore +2 -0
- data/.vscode/settings.json +8 -0
- data/CHANGELOG.md +473 -12
- data/Gemfile +1 -1
- data/README.md +596 -16
- data/bin/prepare_coverage +27 -0
- data/bin/test +8 -0
- data/examples/rgb/color.rb +10 -12
- data/examples/rgb/number.rb +11 -9
- data/examples/rgb_1.rb +23 -16
- data/examples/rgb_2.rb +31 -19
- data/examples/rgb_3.rb +15 -3
- data/lib/micro/struct/factory/create_struct.rb +107 -0
- data/lib/micro/struct/factory/members.rb +37 -0
- data/lib/micro/struct/factory.rb +24 -0
- data/lib/micro/struct/features.rb +42 -19
- data/lib/micro/struct/version.rb +1 -1
- data/lib/micro/struct.rb +8 -3
- data/u-struct.gemspec +1 -1
- metadata +11 -8
- data/Gemfile.lock +0 -31
- data/lib/micro/struct/creator/create_module.rb +0 -67
- data/lib/micro/struct/creator/create_struct.rb +0 -51
- data/lib/micro/struct/creator.rb +0 -29
data/README.md
CHANGED
@@ -1,6 +1,174 @@
|
|
1
|
-
|
1
|
+
<p align="center">
|
2
|
+
<h1 align="center">🧱 μ-struct</h1>
|
3
|
+
<p align="center"><i>Create powered Ruby structs.</i></p>
|
4
|
+
<br>
|
5
|
+
</p>
|
6
|
+
|
7
|
+
<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
|
+
<a href="https://rubygems.org/gems/u-struct">
|
10
|
+
<img alt="Gem" src="https://img.shields.io/gem/v/u-struct.svg?style=flat-square">
|
11
|
+
</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">
|
20
|
+
</a>
|
21
|
+
</p>
|
22
|
+
|
23
|
+
# Table of contents: <!-- omit in toc -->
|
24
|
+
- [Introduction](#introduction)
|
25
|
+
- [Project Motivation](#project-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
|
+
- [`:to_ary`](#to_ary)
|
34
|
+
- [`:to_hash`](#to_hash)
|
35
|
+
- [`:to_proc`](#to_proc)
|
36
|
+
- [`:readonly`](#readonly)
|
37
|
+
- [`:instance_copy`](#instance_copy)
|
38
|
+
- [`:exposed_features`](#exposed_features)
|
39
|
+
- [`Micro::Struct.instance()` or `Micro::Struct.with(...).instance()`](#microstructinstance-or-microstructwithinstance)
|
40
|
+
- [TL;DR](#tldr)
|
41
|
+
- [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)
|
44
|
+
- [Development](#development)
|
45
|
+
- [Contributing](#contributing)
|
46
|
+
- [License](#license)
|
47
|
+
- [Code of Conduct](#code-of-conduct)
|
48
|
+
|
49
|
+
## Introduction
|
50
|
+
|
51
|
+
Ruby Struct is a versatile data structure because it can behave like an Array, Hash, and ordinary object. e.g.
|
2
52
|
|
3
|
-
|
53
|
+
```ruby
|
54
|
+
Person = Struct.new(:first_name, :last_name)
|
55
|
+
|
56
|
+
person = Person.new('Rodrigo', 'Serradura')
|
57
|
+
# #<struct Person first_name="Rodrigo", last_name="Serradura">
|
58
|
+
|
59
|
+
# -- Ordinary object behavior --
|
60
|
+
|
61
|
+
person.first_name # "Rodrigo"
|
62
|
+
person.last_name # "Serradura"
|
63
|
+
|
64
|
+
person.first_name = 'John' # "John"
|
65
|
+
person.last_name = 'Doe' # "Doe"
|
66
|
+
|
67
|
+
person
|
68
|
+
# #<struct Person first_name="John", last_name="Doe">
|
69
|
+
|
70
|
+
# -- Hash behavior --
|
71
|
+
|
72
|
+
person[:first_name] # "Doe"
|
73
|
+
person['last_name'] # "John"
|
74
|
+
|
75
|
+
person[:first_name] = 'Rodrigo' # "Rodrigo"
|
76
|
+
person['last_name'] = 'Serradura' # "Serradura"
|
77
|
+
|
78
|
+
person
|
79
|
+
# #<struct Person first_name="Rodrigo", last_name="Serradura">
|
80
|
+
|
81
|
+
|
82
|
+
# Transforming a Struct into a Hash
|
83
|
+
person.to_h
|
84
|
+
# {:first_name=>"Rodrigo", :last_name=>"Serradura"}
|
85
|
+
|
86
|
+
# -- Array behavior --
|
87
|
+
|
88
|
+
person[0] # "Rodrigo"
|
89
|
+
person[1] # "Serradura"
|
90
|
+
|
91
|
+
person[0] = 'John' # "John"
|
92
|
+
person[1] = 'Doe' # "Doe"
|
93
|
+
|
94
|
+
person
|
95
|
+
# #<struct Person first_name="John", last_name="Doe">
|
96
|
+
|
97
|
+
# Transforming a Struct into an Array
|
98
|
+
person.to_a
|
99
|
+
# ["John", "Doe"]
|
100
|
+
```
|
101
|
+
|
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.
|
103
|
+
|
104
|
+
Look at the example showing the Struct's `keyword_init:` option creating a constructor with optional keyword arguments:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
Person = Struct.new(:first_name, :last_name, keyword_init: true)
|
108
|
+
|
109
|
+
Person.superclass # Struct
|
110
|
+
|
111
|
+
Person.new
|
112
|
+
# #<struct Person first_name=nil, last_name=nil>
|
113
|
+
|
114
|
+
# Because of this, you will only see an exception
|
115
|
+
# if you pass one or more invalid keywords.
|
116
|
+
|
117
|
+
Person.new(foo: 1, bar: 2)
|
118
|
+
# ArgumentError (unknown keywords: foo, bar)
|
119
|
+
```
|
120
|
+
|
121
|
+
### Project Motivation
|
122
|
+
|
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.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
require 'u-struct'
|
127
|
+
|
128
|
+
Person = Micro::Struct.new(:first_name, :last_name)
|
129
|
+
|
130
|
+
Person.superclass
|
131
|
+
# Struct
|
132
|
+
|
133
|
+
Person.new
|
134
|
+
# ArgumentError (missing keywords: :first_name, :last_name)
|
135
|
+
```
|
136
|
+
|
137
|
+
As you can see, the struct instantiation raised an error because all of the keywords arguments are required.
|
138
|
+
|
139
|
+
But, if you need one or many optional arguments, you can use the `optional:` option to define them. e.g.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
Person = Micro::Struct.new(:first_name, optional: :last_name)
|
143
|
+
|
144
|
+
Person.new
|
145
|
+
# ArgumentError (missing keyword: :first_name)
|
146
|
+
|
147
|
+
Person.new(first_name: 'Rodrigo')
|
148
|
+
# #<struct Person first_name="Rodrigo", last_name=nil>
|
149
|
+
```
|
150
|
+
|
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.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
Person = Micro::Struct.new(optional: [:first_name, :last_name])
|
157
|
+
|
158
|
+
Person.new
|
159
|
+
# #<struct Person first_name=nil, last_name=nil>
|
160
|
+
```
|
161
|
+
|
162
|
+
You can also use the `required:` option to define required attributes.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
Person = Micro::Struct.new(
|
166
|
+
required: [:first_name, :last_name],
|
167
|
+
optional: [:age]
|
168
|
+
)
|
169
|
+
```
|
170
|
+
|
171
|
+
So, what did you think? If you liked it, continue the reading to understand what this gem can do for you.
|
4
172
|
|
5
173
|
## Installation
|
6
174
|
|
@@ -18,44 +186,368 @@ Or install it yourself as:
|
|
18
186
|
|
19
187
|
$ gem install u-struct
|
20
188
|
|
189
|
+
[⬆️ Back to Top](#table-of-contents-)
|
190
|
+
|
21
191
|
## Usage
|
22
192
|
|
193
|
+
### `Micro::Struct.new`
|
194
|
+
|
195
|
+
Like `Struct.new`, you will use `Micro::Struct.new` to create your Struct classes.
|
196
|
+
|
197
|
+
The key difference is: Structs created from `Micro::Struct` will use keyword arguments in their constructors.
|
198
|
+
|
23
199
|
```ruby
|
24
|
-
|
25
|
-
|
200
|
+
Person = Struct.new(:name) # Person
|
201
|
+
Persona = Micro::Struct.new(:name) # Persona
|
202
|
+
|
203
|
+
Person.ancestors # [Person, Struct, Enumerable, Object, Kernel, BasicObject]
|
204
|
+
Persona.ancestors # [Person, Struct, Enumerable, Object, Kernel, BasicObject]
|
205
|
+
|
206
|
+
Person.new('Rodrigo') # #<struct Person name="Rodrigo">
|
207
|
+
Persona.new(name: 'Rodrigo') # #<struct Person name="Rodrigo">
|
208
|
+
|
209
|
+
Person.new # #<struct Person name=nil>
|
210
|
+
|
211
|
+
Persona.new # ArgumentError (missing keyword: :name)
|
212
|
+
```
|
213
|
+
|
214
|
+
[⬆️ Back to Top](#table-of-contents-)
|
215
|
+
|
216
|
+
#### `optional:` option
|
217
|
+
|
218
|
+
But if you need optional attributes, you can use this to define them.
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
Person = Micro::Struct.new(:name, optional: :age)
|
222
|
+
|
223
|
+
Person.new
|
224
|
+
# ArgumentError (missing keyword: :name)
|
225
|
+
|
226
|
+
Person.new(name: 'John')
|
227
|
+
# #<struct Person name="John", age=nil>
|
228
|
+
```
|
229
|
+
|
230
|
+
Use an array to define multiple optional attributes.
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
Person = Micro::Struct.new(:name, optional: [:age, :nickname])
|
234
|
+
|
235
|
+
Person.new
|
236
|
+
# ArgumentError (missing keyword: :name)
|
237
|
+
|
238
|
+
Person.new(name: 'John')
|
239
|
+
# #<struct Person name="John", age=nil, nickname=nil>
|
240
|
+
```
|
241
|
+
|
242
|
+
[⬆️ Back to Top](#table-of-contents-)
|
243
|
+
|
244
|
+
#### `required:` option
|
245
|
+
|
246
|
+
It is an alternative way to define required attributes. Use a symbol to define one or an array to define multiple attributes.
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
Person = Micro::Struct.new(
|
250
|
+
required: [:first_name, :last_name],
|
251
|
+
optional: [:age]
|
252
|
+
)
|
253
|
+
|
254
|
+
Person.new
|
255
|
+
# ArgumentError (missing keywords: :first_name, :last_name)
|
256
|
+
|
257
|
+
Person.new first_name: 'John', last_name: 'Doe'
|
258
|
+
# #<struct Person first_name="John", last_name="Doe", age=nil>
|
259
|
+
```
|
260
|
+
|
261
|
+
[⬆️ Back to Top](#table-of-contents-)
|
262
|
+
|
263
|
+
#### Defining custom methods/behavior
|
264
|
+
|
265
|
+
The `Micro::Struct.new` accepts a block as a regular Struct, and you can use it to define some custom behavior/methods.
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
Person = Micro::Struct.new(:first_name, :last_name, optional: :age) do
|
269
|
+
def name
|
270
|
+
"#{first_name} #{last_name}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
|
275
|
+
# #<struct Person first_name="Rodrigo", last_name="Serradura", age=nil>
|
276
|
+
|
277
|
+
person.first_name # "Rodrigo"
|
278
|
+
person.last_name # "Serradura"
|
279
|
+
person.name # "Rodrigo Serradura"
|
280
|
+
```
|
281
|
+
|
282
|
+
[⬆️ Back to Top](#table-of-contents-)
|
283
|
+
|
284
|
+
### `Micro::Struct.with`
|
285
|
+
|
286
|
+
This method can do two things: first, it can create Struct factories; second, it sets some special behavior to their structs.
|
287
|
+
|
288
|
+
These are all of the available features which you can use (pick one, many, or all of them):
|
289
|
+
- [`:to_ary`](#to_ary)
|
290
|
+
- [`:to_hash`](#to_hash)
|
291
|
+
- [`:to_proc`](#to_proc)
|
292
|
+
- [`:readonly`](#readonly)
|
293
|
+
- [`:instance_copy`](#instance_copy)
|
294
|
+
- [`:exposed_features`](#exposed_features)
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
ReadonlyStruct = Micro::Struct.with(:readonly, :instance_copy)
|
298
|
+
|
299
|
+
Person = ReadonlyStruct.new(:first_name, :last_name)
|
300
|
+
|
301
|
+
Person.new # ArgumentError (missing keywords: :first_name, :last_name)
|
302
|
+
|
303
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
|
304
|
+
# #<struct Person first_name="Rodrigo", last_name="Rodrigues">
|
305
|
+
|
306
|
+
person.last_name = ''
|
307
|
+
# NoMethodError (private method `last_name=' called for #<struct Person ...>)
|
308
|
+
|
309
|
+
person[:last_name] = ''
|
310
|
+
# NoMethodError (private method `[]=' called for #<struct Person ...>)
|
311
|
+
|
312
|
+
person.with(last_name: 'Serradura')
|
313
|
+
# #<struct Person first_name="Rodrigo", last_name="Serradura">
|
314
|
+
```
|
315
|
+
|
316
|
+
[⬆️ Back to Top](#table-of-contents-)
|
317
|
+
|
318
|
+
#### `:to_ary`
|
319
|
+
|
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.
|
321
|
+
|
322
|
+
The `#to_ary` makes Ruby know how to deconstruct an object like an array.
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
Person = Micro::Struct.with(:to_ary).new(:first_name, :last_name)
|
326
|
+
|
327
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
|
328
|
+
|
329
|
+
first_name, last_name = person
|
26
330
|
|
331
|
+
p first_name # "Rodrigo"
|
332
|
+
p last_name # "Serradura"
|
333
|
+
|
334
|
+
*first_and_last_name = person
|
335
|
+
|
336
|
+
p first_and_last_name # ["Rodrigo", "Serradura"]
|
337
|
+
```
|
338
|
+
|
339
|
+
[⬆️ Back to Top](#table-of-contents-)
|
340
|
+
|
341
|
+
#### `:to_hash`
|
342
|
+
|
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.
|
344
|
+
|
345
|
+
The `#to_hash` makes Ruby know how to deconstruct an object like a hash.
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
Person = Micro::Struct.with(:to_hash).new(:first_name, :last_name)
|
349
|
+
|
350
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
|
351
|
+
|
352
|
+
def greet(first_name:, last_name:)
|
353
|
+
puts "Hi #{first_name} #{last_name}!"
|
354
|
+
end
|
355
|
+
|
356
|
+
greet(**person)
|
357
|
+
# Hi Rodrigo Serradura!
|
358
|
+
```
|
359
|
+
|
360
|
+
[⬆️ Back to Top](#table-of-contents-)
|
361
|
+
|
362
|
+
#### `:to_proc`
|
363
|
+
|
364
|
+
The `#to_proc` tells Ruby how to invoke it as a block replacement (by using `&`).
|
365
|
+
|
366
|
+
The lambda returned from the `#to_proc` will require a hash as its argument.
|
367
|
+
|
368
|
+
```ruby
|
369
|
+
Person = Micro::Struct.with(:to_proc).new(:first_name, :last_name)
|
370
|
+
|
371
|
+
[
|
372
|
+
{first_name: 'John', last_name: 'Doe'},
|
373
|
+
{first_name: 'Mary', last_name: 'Doe'}
|
374
|
+
].map(&Person)
|
375
|
+
# [
|
376
|
+
# #<struct Person::Struct first_name="John", last_name="Doe">,
|
377
|
+
# #<struct Person::Struct first_name="Mary", last_name="Doe">
|
378
|
+
# ]
|
379
|
+
```
|
380
|
+
|
381
|
+
[⬆️ Back to Top](#table-of-contents-)
|
382
|
+
|
383
|
+
#### `:readonly`
|
384
|
+
|
385
|
+
This feature sets the Struct members' writers as private.
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
Person = Micro::Struct.with(:readonly).new(:first_name, :last_name)
|
389
|
+
|
390
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
|
391
|
+
# #<struct Person first_name="Rodrigo", last_name="Rodrigues">
|
392
|
+
|
393
|
+
person.last_name = ''
|
394
|
+
# NoMethodError (private method `last_name=' called for #<struct Person ...>)
|
395
|
+
|
396
|
+
person[:last_name] = ''
|
397
|
+
# NoMethodError (private method `[]=' called for #<struct Person ...>)
|
398
|
+
```
|
399
|
+
|
400
|
+
[⬆️ Back to Top](#table-of-contents-)
|
401
|
+
|
402
|
+
#### `:instance_copy`
|
403
|
+
|
404
|
+
Creates the `#with` method, which will instantiate a struct of the same kind and reuse its current state.
|
405
|
+
|
406
|
+
```ruby
|
407
|
+
Person = Micro::Struct.with(:instance_copy).new(:first_name, :last_name)
|
408
|
+
|
409
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
|
410
|
+
# => #<struct Person::Struct first_name="Rodrigo", last_name="Serradura">
|
411
|
+
|
412
|
+
person.first_name = 'John'
|
413
|
+
# => "John"
|
414
|
+
|
415
|
+
person.inspect
|
416
|
+
# => #<struct Person::Struct first_name="John", last_name="Serradura">
|
417
|
+
|
418
|
+
new_person = person.with(last_name: 'Doe')
|
419
|
+
# => #<struct Person::Struct first_name="John", last_name="Doe">
|
420
|
+
|
421
|
+
person === new_person # => false
|
422
|
+
person.equal?(new_person) # => false
|
423
|
+
|
424
|
+
person.last_name # => "Serradura"
|
425
|
+
new_person.last_name # => "Doe"
|
426
|
+
```
|
427
|
+
|
428
|
+
[⬆️ Back to Top](#table-of-contents-)
|
429
|
+
|
430
|
+
#### `:exposed_features`
|
431
|
+
|
432
|
+
This feature exposes the struct's configured features. Via the methods: `.features` and `.__features__`.
|
433
|
+
|
434
|
+
```ruby
|
435
|
+
Person = Micro::Struct.with(:exposed_features, :readonly, :to_proc).new(:name)
|
436
|
+
|
437
|
+
Person.features
|
438
|
+
# => #<struct Micro::Struct::Features::Exposed
|
439
|
+
# names=[:readonly, :to_proc],
|
440
|
+
# options={:to_ary=>false, :to_hash=>false, :to_proc=>true, :readonly=>true, :instance_copy=>false}>
|
441
|
+
|
442
|
+
Person.__features__.equal?(Person.features) # `.__features__` is an alias of `.features` method
|
443
|
+
|
444
|
+
Person.features.names # => [:readonly, :to_proc]
|
445
|
+
Person.features.options # => {:to_ary=>false, :to_hash=>false, :to_proc=>true, :readonly=>true, :instance_copy=>false}
|
446
|
+
|
447
|
+
Person.features.option?(:to_proc) # => true
|
448
|
+
Person.features.option?(:readonly) # => true
|
449
|
+
|
450
|
+
Person.features.options?(:to_proc) # => true
|
451
|
+
Person.features.options?(:readonly) # => true
|
452
|
+
|
453
|
+
Person.features.options?(:to_proc, :readonly) # => true
|
454
|
+
Person.features.options?(:to_ary, :readonly) # => false
|
455
|
+
```
|
456
|
+
|
457
|
+
[⬆️ Back to Top](#table-of-contents-)
|
458
|
+
|
459
|
+
### `Micro::Struct.instance()` or `Micro::Struct.with(...).instance()`
|
460
|
+
|
461
|
+
Creates a struct instance from a given hash. This could be useful to create constants or a singleton value.
|
462
|
+
|
463
|
+
```ruby
|
464
|
+
person1 = Micro::Struct.instance(first_name: 'Rodrigo', last_name: 'Serradura')
|
465
|
+
# => #<struct first_name="Rodrigo", last_name="Serradura">
|
466
|
+
|
467
|
+
person1.first_name = 'John'
|
468
|
+
|
469
|
+
person1.first_name # => "John"
|
470
|
+
```
|
471
|
+
|
472
|
+
You can also use the instance method after defining some struct feature ([`Micro::Struct.with`](#microstructwith)).
|
473
|
+
|
474
|
+
```ruby
|
475
|
+
person2 = Micro::Struct.with(:readonly).instance(first_name: 'Rodrigo', last_name: 'Serradura')
|
476
|
+
# => #<struct first_name="Rodrigo", last_name="Serradura">
|
477
|
+
|
478
|
+
person2.first_name = 'John'
|
479
|
+
# NoMethodError (private method `first_name=' called for #<struct first_name="Rodrigo", last_name="Serradura">)
|
480
|
+
```
|
481
|
+
|
482
|
+
And if you need some custom behavior, use a block to define them.
|
483
|
+
|
484
|
+
```ruby
|
485
|
+
person3 = Micro::Struct.instance(first_name: 'Rodrigo', last_name: 'Serradura') do
|
486
|
+
def name
|
487
|
+
"#{first_name} #{last_name}"
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
person4 = Micro::Struct.with(:readonly).instance(first_name: 'Rodrigo', last_name: 'Serradura') do
|
492
|
+
def name
|
493
|
+
"#{first_name} #{last_name}"
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
person3.name # => "Rodrigo Serradura"
|
498
|
+
person4.name # => "Rodrigo Serradura"
|
499
|
+
```
|
500
|
+
|
501
|
+
[⬆️ Back to Top](#table-of-contents-)
|
502
|
+
|
503
|
+
### TL;DR
|
504
|
+
|
505
|
+
Like in a regular Struct, you can define one or many attributes.
|
506
|
+
But all of them will be required by default.
|
507
|
+
|
508
|
+
```ruby
|
27
509
|
Micro::Struct.new(:first_name, :last_name, ...)
|
510
|
+
```
|
28
511
|
|
29
|
-
|
512
|
+
Use the `optional:` arg if you want some optional attributes.
|
30
513
|
|
514
|
+
```ruby
|
31
515
|
Micro::Struct.new(:first_name, :last_name, optional: :gender)
|
32
516
|
|
33
517
|
# Using `optional:` to define all attributes are optional.
|
34
518
|
|
35
519
|
Micro::Struct.new(optional: [:first_name, :last_name])
|
520
|
+
```
|
36
521
|
|
37
|
-
|
522
|
+
Use the `required:` arg to define required attributes.
|
38
523
|
|
524
|
+
```ruby
|
39
525
|
Micro::Struct.new(
|
40
526
|
required: [:first_name, :last_name],
|
41
527
|
optional: [:gender, :age]
|
42
528
|
)
|
529
|
+
```
|
43
530
|
|
44
|
-
|
531
|
+
You can also pass a block to define custom methods.
|
45
532
|
|
533
|
+
```ruby
|
46
534
|
Micro::Struct.new(:name) {}
|
535
|
+
```
|
47
536
|
|
48
|
-
|
49
|
-
# .with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)
|
537
|
+
Available features (use one, many, or all) to create Structs with a special behavior:
|
50
538
|
|
51
|
-
|
52
|
-
Micro::Struct.with(:to_ary
|
53
|
-
Micro::Struct.with(:to_ary, :to_hash
|
54
|
-
Micro::Struct.with(:to_ary, :to_hash, :to_proc
|
55
|
-
Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly
|
539
|
+
```ruby
|
540
|
+
Micro::Struct.with(:to_ary)
|
541
|
+
Micro::Struct.with(:to_ary, :to_hash)
|
542
|
+
Micro::Struct.with(:to_ary, :to_hash, :to_proc)
|
543
|
+
Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly)
|
544
|
+
Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)
|
545
|
+
Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy, :exposed_features)
|
546
|
+
```
|
56
547
|
|
57
|
-
|
548
|
+
All of the possible combinations to create a Ruby Struct using `Micro::Struct`:
|
58
549
|
|
550
|
+
```ruby
|
59
551
|
Micro::Struct.new(*required)
|
60
552
|
Micro::Struct.new(*required) {}
|
61
553
|
|
@@ -70,26 +562,114 @@ Micro::Struct.new(*required, optional: *) {}
|
|
70
562
|
|
71
563
|
Micro::Struct.new(required: *, optional: *)
|
72
564
|
Micro::Struct.new(required: *, optional: *) {}
|
565
|
+
```
|
73
566
|
|
74
|
-
|
567
|
+
Any options above can be used by the `.new()` method of the struct creator returned by the `.with()` method.
|
75
568
|
|
569
|
+
```ruby
|
76
570
|
Micro::Struct.with(*features).new(...) {}
|
77
571
|
```
|
78
572
|
|
573
|
+
Use `Micro::Struct.instance()` or `Micro::Struct.with(...).instance()` to create a struct instance from a given hash.
|
574
|
+
|
575
|
+
[⬆️ Back to Top](#table-of-contents-)
|
576
|
+
|
577
|
+
## FAQ
|
578
|
+
|
579
|
+
### How to overwrite the Struct `.new` method?
|
580
|
+
|
581
|
+
The `.new` is an alias for the `.__new__` method, so you can use `.__new__` when overwriting it.
|
582
|
+
|
583
|
+
```ruby
|
584
|
+
module RGB
|
585
|
+
Number = ::Struct.new(:value) { def to_s; '%02x' % value; end }
|
586
|
+
|
587
|
+
Color = Micro::Struct.new(:red, :green, :blue) do
|
588
|
+
def to_hex
|
589
|
+
"##{red}#{green}#{blue}"
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
module Color
|
594
|
+
def self.new(r, g, b)
|
595
|
+
__new__(
|
596
|
+
red: Number.new(r),
|
597
|
+
green: Number.new(g),
|
598
|
+
blue: Number.new(b),
|
599
|
+
)
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
rgb_color = RGB::Color.new(1,5,255)
|
605
|
+
# => #<struct RGB::Color::Struct red=#<struct RGB::Number value=1>, green=#<struct RGB::Number value=5>, blue=#<struct RGB::Number value=255>>
|
606
|
+
|
607
|
+
rgb_color.to_hex
|
608
|
+
# => "#0105ff"
|
609
|
+
```
|
610
|
+
|
611
|
+
[⬆️ Back to Top](#table-of-contents-)
|
612
|
+
|
613
|
+
### Can I overwrite the Struct initializer?
|
614
|
+
|
615
|
+
Yes, you can, but the initializer must handle the arguments as positional ones.
|
616
|
+
|
617
|
+
```ruby
|
618
|
+
RGBColor = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
|
619
|
+
Number = ->(value) do
|
620
|
+
return value if value.is_a?(::Integer) && value >= 0 && value <= 255
|
621
|
+
|
622
|
+
raise TypeError, "#{value} must be an Integer(>= 0 and <= 255)"
|
623
|
+
end
|
624
|
+
|
625
|
+
def initialize(r, g, b)
|
626
|
+
super(Number[r], Number[g], Number[b])
|
627
|
+
end
|
628
|
+
|
629
|
+
def to_hex
|
630
|
+
'#%02x%02x%02x' % self
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
rgb_color = RGBColor.new(red: 1, green: 1, blue: 255)
|
635
|
+
# #<struct RGBColor red=1, green=1, blue=255>
|
636
|
+
|
637
|
+
r, g, b = rgb_color
|
638
|
+
|
639
|
+
[r,g,b]
|
640
|
+
# [1, 1, 255]
|
641
|
+
|
642
|
+
rgb_color.to_hex
|
643
|
+
# "#0101ff"
|
644
|
+
|
645
|
+
RGBColor.new(red: 1, green: -1, blue: 255)
|
646
|
+
# TypeError (-1 must be an Integer(>= 0 and <= 255))
|
647
|
+
```
|
648
|
+
|
649
|
+
[⬆️ Back to Top](#table-of-contents-)
|
650
|
+
|
79
651
|
## Development
|
80
652
|
|
81
653
|
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.
|
82
654
|
|
83
655
|
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).
|
84
656
|
|
657
|
+
[⬆️ Back to Top](#table-of-contents-)
|
658
|
+
|
85
659
|
## Contributing
|
86
660
|
|
87
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).
|
88
662
|
|
663
|
+
[⬆️ Back to Top](#table-of-contents-)
|
664
|
+
|
89
665
|
## License
|
90
666
|
|
91
667
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
92
668
|
|
669
|
+
[⬆️ Back to Top](#table-of-contents-)
|
670
|
+
|
93
671
|
## Code of Conduct
|
94
672
|
|
95
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).
|
674
|
+
|
675
|
+
[⬆️ Back to Top](#table-of-contents-)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
# Borrowed from https://gist.github.com/qortex/7e7c49f3731391a91ee898336183acef
|
4
|
+
|
5
|
+
# Temporary hack to get CodeClimate to work with SimpleCov 0.18 JSON format until issue is fixed
|
6
|
+
# upstream: https://github.com/codeclimate/test-reporter/issues/413
|
7
|
+
|
8
|
+
require "json"
|
9
|
+
|
10
|
+
filename = "coverage/.resultset.json"
|
11
|
+
contents = JSON.parse(File.read(filename))
|
12
|
+
|
13
|
+
def remove_lines_key(obj)
|
14
|
+
case obj
|
15
|
+
when Hash
|
16
|
+
obj.transform_values do |val|
|
17
|
+
val.is_a?(Hash) && val.key?("lines") ? val["lines"] : remove_lines_key(val)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
obj
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# overwrite
|
25
|
+
File.write(filename, JSON.generate(remove_lines_key(contents)))
|
26
|
+
|
27
|
+
puts Dir['coverage/.*.json']
|