opt_struct 0.9.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54518e9eca18b69463f611c221bd896a94cbd5293bd804a23319b0dd7441573d
4
- data.tar.gz: 36584d846f30ebf6517d541a31668678a1a8dbd9025a4d9bef032eea2372c50f
3
+ metadata.gz: 3a55fe71d013b67b97989363367748f78f0cf50ba890aeafdd69183b11c9bcac
4
+ data.tar.gz: ee9465e01cac7b9978a3860cbabf0e26322246b7eda546b01a23ff6afb9715bf
5
5
  SHA512:
6
- metadata.gz: ff0a53a81253354676e0578028d775785eda00d30c7201b3f36414009b26ad551893f789dc9aabf82ec212b7abd5544432a045cfe4f973e59136ffeec1258867
7
- data.tar.gz: fbbd6f407b889e2cca7b6b9d5ce891cdad58bb73db10a1e437515d74d1221af442da0cbb7715f8f20db05ea0799035951e094d9ad5bece86775f7c05fdd17c1e
6
+ metadata.gz: d688e9d507c70fcc588aff5a6b9c7109eff834a7f121f0508796758b29e4c86d93c134c6e43396eca023fadc05113ca79ddae40087f20e049445c4d4343a7335
7
+ data.tar.gz: 21a4f2a0a46034a18c9973596ff0f4f569d853e02f4c4b98bb3331368baee5af2b4a8718ace0a42b21c6375168cb1b6f9eabd2dff045f09f5aeed681600c69f5
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # The Opt Struct
1
+ # The Opt Struct [![Build Status][travis-image]][travis-link]
2
+
3
+ [travis-image]: https://travis-ci.org/carlzulauf/opt_struct.svg?branch=master
4
+ [travis-link]: http://travis-ci.org/carlzulauf/opt_struct
2
5
 
3
6
  A struct around a hash. Great for encapsulating actions with complex configuration, like interactor/action classes.
4
7
 
@@ -6,137 +9,342 @@ A struct around a hash. Great for encapsulating actions with complex configurati
6
9
  gem "opt_struct"
7
10
  ```
8
11
 
9
- ## Example 1
12
+ # Examples
10
13
 
11
- Can work mostly like a regular struct, while accepting options
14
+ ## Creating an OptStruct
12
15
 
13
16
  ```ruby
14
- MyClass = OptStruct.new(:foo, :bar)
17
+ class User < OptStruct.new
18
+ required :email, :name
19
+ option :role, default: "member"
20
+
21
+ def formatted_email
22
+ %{"#{name}" <#{email}>}
23
+ end
24
+ end
25
+ ```
15
26
 
16
- MyClass.new
17
- # => argument error
27
+ ## Using an OptStruct
18
28
 
19
- MyClass.new "foo", "bar"
20
- # => #<MyClass>
29
+ ```ruby
30
+ user = User.new(email: "admin@user.com", name: "Ms. Admin", role: "admin")
31
+
32
+ # option accessors are available
33
+ user.name
34
+ # => "Ms. Admin"
35
+ user.formatted_email
36
+ # => "\"Ms. Admin\" <admin@user.com>"
37
+ user.name = "Amber Admin"
38
+ # => "Amber Admin"
39
+
40
+ # values are also accessible through the `#options` Hash
41
+ user.options
42
+ # => {:email=>"admin@user.com", :name=>"Amber Admin", :role=>"admin"}
43
+ user.options.fetch(:role)
44
+ # => "admin"
45
+ ```
21
46
 
22
- MyClass.new foo: "foo", bar: "bar"
23
- # => #<MyClass>
47
+ # Documentation
24
48
 
25
- i = MyClass.new "foo", "bar", yin: "yang"
26
- i.options
27
- # => {yin: "yang"}
28
- i.fetch(:yin)
29
- # => "yang"
30
- ```
49
+ ## Use As Inheritable Class
50
+
51
+ `OptStruct.new` returns an instance of `Class` that can be inherited or initialized directly.
31
52
 
32
- ## Example 2
53
+ The following are functionally equivalent
33
54
 
34
- Passing a hash promotes the keys (`:foo` below) to an `option` giving it getter/setter methods on the class. The value becomes the default. This is equivalent and can be combined with using the `option` macro.
55
+ ```ruby
56
+ class User < OptStruct.new
57
+ required :email
58
+ option :name
59
+ end
60
+ ```
61
+
62
+ ```ruby
63
+ User = OptStruct.new do
64
+ required :email
65
+ option :name
66
+ end
67
+ ```
35
68
 
36
- If an option is required it needs to be called out as such using `required`.
69
+ `OptStruct` classes can safely have descendants with their own isolated options.
37
70
 
38
71
  ```ruby
39
- class MyClass < OptStruct.new(foo: "bar")
40
- required :yin # equivalent to: `option :yin, required: true`
41
- option :bar, default: "foo"
72
+ class AdminUser < User
73
+ required :token
42
74
  end
43
75
 
44
- MyClass.new
45
- # => missing keyword argument :yin
76
+ User.new(email: "regular@user.com")
77
+ # => #<User:0x0... @options={:email=>"regular@user.com"}>
78
+
79
+ AdminUser.new(email: "admin@user.com")
80
+ # ArgumentError: missing required keywords: [:token]
46
81
 
47
- i = MyClass.new yin: "yang"
48
- # => #<MyClass>
49
- i.foo
50
- # => "bar"
51
- i.bar
52
- # => "foo"
53
- i.foo = "foo"
54
- i.options
55
- # => {foo: "foo", bar: "foo", yin: "yang"}
82
+ AdminUser.new(email: "admin@user.com", token: "a2236843f0227af2")
83
+ # => #<AdminUser:0x0... @options={:email=>"admin@user.com", :token=>"..."}>
56
84
  ```
57
85
 
58
- ## Example 3
86
+ ## Use As Mixin Module
59
87
 
60
- Works as a plain old mixin as well.
88
+ `OptStruct.build` returns an instance of `Module` that can be included into a class or another module.
89
+
90
+ The following are functionally equivalent
61
91
 
62
92
  ```ruby
63
- class MyClass
64
- include OptStruct
65
- required :foo
93
+ module Visitable
94
+ include OptStruct.build
95
+ options :expected_at, :arrived_at, :departed_at
96
+ end
97
+
98
+ class AuditLog
99
+ include Visitable
66
100
  end
101
+ ```
67
102
 
68
- MyClass.new
69
- # => missing keyword argument :foo
103
+ ```ruby
104
+ Visitable = OptStruct.build { options :expected_at, :arrived_at, :departed_at }
70
105
 
71
- MyClass.new(foo: "bar").foo
72
- # => "bar"
106
+ class AuditLog
107
+ include Visitable
108
+ end
73
109
  ```
74
110
 
75
- ## Example 4
111
+ These example results in an `AuditLog` class with identical behavior, but no explicit `Visitable` module.
76
112
 
77
- Options passed to `new` can be passed to `build` when used in module form.
113
+ ```ruby
114
+ class AuditLog
115
+ include OptStruct.build
116
+ options :expected_at, :arrived_at, :departed_at
117
+ end
118
+ ```
78
119
 
79
120
  ```ruby
80
- class MyClass
81
- include OptStruct.build(:foo, bar: nil)
121
+ class AuditLog
122
+ include(OptStruct.build do
123
+ options :expected_at, :arrived_at, :departed_at
124
+ end)
82
125
  end
126
+ ```
127
+
128
+ ## Optional Arguments
129
+
130
+ Optional arguments are simply accessor methods for values expected to be in the `#options` Hash. Optional arguments can be defined in multiple ways.
83
131
 
84
- MyClass.new
85
- # => argument error
132
+ All of the examples in this section are functionally equivalent.
86
133
 
87
- i = MyClass.new("something", bar: "foo")
88
- [i.foo, i.bar]
89
- # => ["something", "foo"]
134
+ ```ruby
135
+ class User < OptStruct.new
136
+ option :email
137
+ option :role, default: "member"
138
+ end
90
139
  ```
91
140
 
92
- ## Example 5
141
+ ```ruby
142
+ class User < OptStruct.new
143
+ options :email, role: "member"
144
+ end
145
+ ```
146
+
147
+ ```ruby
148
+ class User < OptStruct.new
149
+ options email: nil, role: "member"
150
+ end
151
+ ```
93
152
 
94
- Both `build` and `new` accept a block.
153
+ Passing a Hash to `.new` or `.build` is equivalent to passing the same hash to `options`
95
154
 
96
155
  ```ruby
97
- PersonClass = OptStruct.new do
98
- required :first_name
99
- option :last_name
156
+ User < OptStruct.new(email: nil, role: "member")
157
+ ```
100
158
 
101
- def name
102
- [first_name, last_name].compact.join(" ")
103
- end
159
+ Default blocks can also be used and are late evaluated on the each struct instance.
160
+
161
+ ```ruby
162
+ class User < OptStruct.new
163
+ option :email, default: -> { nil }
164
+ option :role, -> { "member" }
104
165
  end
166
+ ```
105
167
 
106
- t = PersonClass.new(first_name: "Trish")
107
- # => #<PersonClass>
108
- t.name
109
- # => "Trish"
110
- t.last_name = "Smith"
111
- t.name
112
- # => "Trish Smith"
168
+ ```ruby
169
+ class User < OptStruct.new
170
+ options :email, role: -> { "member" }
171
+ end
172
+ ```
113
173
 
114
- CarModule = OptStruct.build do
115
- required :make, :model
116
- options year: -> { Date.today.year }, transmission: :default_transmission
174
+ ```ruby
175
+ class User < OptStruct.new
176
+ option :email, nil
177
+ option :role, -> { default_role }
178
+
179
+ private
180
+
181
+ def default_role
182
+ "member"
183
+ end
184
+ end
185
+ ```
117
186
 
118
- def default_transmission
119
- "Automatic"
187
+ Default symbols are treated as method calls if the struct `#respond_to?` the method.
188
+
189
+ ```ruby
190
+ class User < OptStruct.new
191
+ options :email, :role => :default_role
192
+
193
+ def default_role
194
+ "member"
120
195
  end
196
+ end
197
+ ```
198
+
199
+ ## Required Arguments
200
+
201
+ Required arguments are just like optional arguments, except they are also added to the `.required_keys` collection, which is checked when an OptStruct is initialized. If the `#options` Hash does not contain all `.required_keys` then an `ArgumentError` is raised.
202
+
203
+ The following examples are functionally equivalent.
204
+
205
+ ```ruby
206
+ class Student < OptStruct.new
207
+ required :name
208
+ end
209
+ ```
210
+
211
+ ```ruby
212
+ class Student < OptStruct.new
213
+ option :name, required: true
214
+ end
215
+ ```
216
+
217
+ ```ruby
218
+ class Student < OptStruct.new
219
+ option :name
220
+ required_keys << :name
221
+ end
222
+ ```
223
+
224
+ ### Expected Arguments
225
+
226
+ OptStructs can accept non-keyword arguments if the struct knows to expect them.
227
+
228
+ For code like this to work...
229
+
230
+ ```ruby
231
+ user = User.new("admin@user.com", "admin")
232
+ user.email # => "admin@user.com"
233
+ user.role # => "admin"
234
+ ```
235
+
236
+ ... the OptStruct needs to have some `.expected_arguments`.
237
+
238
+ The following `User` class examples are functionally equivalent and allow the code above to function.
239
+
240
+ ```ruby
241
+ User = OptStruct.new(:email, :role)
242
+ ```
243
+
244
+ ```ruby
245
+ class User < OptStruct.new(:email)
246
+ expect_argument :role
247
+ end
248
+ ```
249
+
250
+ ```ruby
251
+ class User
252
+ include OptStruct.build(:email, :role)
253
+ end
254
+ ```
255
+
256
+ ```ruby
257
+ class User
258
+ include OptStruct.build
259
+ expect_arguments :email, :role
260
+ end
261
+ ```
121
262
 
263
+ ```ruby
264
+ class User < OptStruct.new(:email)
265
+ expected_arguments << :role
266
+ end
267
+ ```
268
+
269
+ Expected arguments are similar to required arguments, except they are in `.expected_arguments` collection, which is checked when an OptStruct is initialized.
270
+
271
+ Expected arguments can also be supplied using keywords. An `ArgumentError` is only raised if the expected argument is not in the list of arguments passed to `OptStruct#new` **and** the argument is not present in the `#options` Hash.
272
+
273
+ The following examples will initialize any of the `User` class examples above without error.
274
+
275
+ ```ruby
276
+ User.new(email: "example@user.com", role: "member")
277
+ User.new("example@user.com", role: "member")
278
+ User.new(role: "member", email: "example@user.com")
279
+ ```
280
+
281
+ ## The `#options` Hash
282
+
283
+ All OptStruct arguments are read from and stored in a single `Hash` instance. This Hash can be accessed directly using the `options` method.
284
+
285
+ ```ruby
286
+ Person = OptStruct.new(:name)
287
+ Person.new(name: "John", age: 32).options
288
+ # => {:name=>"John", :age=>32}
289
+ ```
290
+
291
+ Feel free to write your own accessor methods for things like dependent options or other complex/private behavior.
292
+
293
+ ```ruby
294
+ class Person < OptStruct.new
295
+ option :given_name
296
+ option :family_name
297
+
122
298
  def name
123
- [year, make, model].compact.join(" ")
299
+ options.fetch(:name) { "#{given_name} #{family_name}" }
124
300
  end
125
301
  end
302
+ ```
303
+
304
+ ## On Initialization
126
305
 
127
- class CarClass
128
- include CarModule
306
+ All of the following examples are functionally equivalent.
307
+
308
+ OptStruct classes are initialized in an `initialize` method (in `OptStruct::InstanceMethods`) like most classes. Also, like most classes, you can override `initialize` as long as you remember to call `super` properly to retain `OptStruct` functionality.
309
+
310
+ ```ruby
311
+ class UserReportBuilder < OptStruct.new(:user)
312
+ attr_reader :report
313
+
314
+ def initialize(*)
315
+ super
316
+ @report = []
317
+ end
129
318
  end
319
+ ```
130
320
 
131
- c = CarClass.new(make: "Infiniti", model: "G37", year: 2012)
132
- c.name
133
- # => "2012 Infinit G37"
321
+ `OptStruct` also provides initialization callbacks to make hooking into and customizing the initialization of OptStruct classes easier and require less code.
134
322
 
135
- c = CarClass.new(model: "WRX", make: "Subaru", year: nil)
136
- c.name
137
- # => "Subaru WRX"
323
+ ```ruby
324
+ class UserReportBuilder < OptStruct.new(:user)
325
+ attr_reader :report
326
+ init { @report = [] }
327
+ end
328
+ ```
138
329
 
139
- c = CarClass.new(model: "BRZ", make: "Subaru")
140
- c.name
141
- # => "2017 Subaru BRZ"
330
+ ```ruby
331
+ class UserReportBuilder < OptStruct.new(:user)
332
+ attr_reader :report
333
+
334
+ around_init do |instance|
335
+ instance.call
336
+ @report = []
337
+ end
338
+ end
142
339
  ```
340
+
341
+ Available callbacks
342
+
343
+ * `around_init`
344
+ * `before_init`
345
+ * `init`
346
+ * `after_init`
347
+
348
+ ## Inheritance, Expanded
349
+
350
+ See `spec/inheritance_spec.rb` for examples of just how crazy you can get.
@@ -17,8 +17,6 @@ module OptStruct
17
17
  end
18
18
 
19
19
  def option_reader(*keys)
20
- check_reserved_words(keys)
21
-
22
20
  keys.each do |key|
23
21
  define_method(key) do
24
22
  if options.key?(key)
@@ -31,14 +29,13 @@ module OptStruct
31
29
  end
32
30
 
33
31
  def option_writer(*keys)
34
- check_reserved_words(keys)
35
-
36
32
  keys.each do |key|
37
33
  define_method("#{key}=") { |value| options[key] = value }
38
34
  end
39
35
  end
40
36
 
41
37
  def option_accessor(*keys)
38
+ check_reserved_words(keys)
42
39
  option_reader *keys
43
40
  option_writer *keys
44
41
  end
@@ -66,6 +63,7 @@ module OptStruct
66
63
  required(*arguments)
67
64
  expected_arguments.concat(arguments)
68
65
  end
66
+ alias_method :expect_argument, :expect_arguments
69
67
 
70
68
  def expected_arguments
71
69
  @expected_arguments ||= []
@@ -1,3 +1,3 @@
1
1
  module OptStruct
2
- VERSION = "0.9.0"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opt_struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Zulauf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-05 00:00:00.000000000 Z
11
+ date: 2019-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler