opt_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 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