attr_extras 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/README.md +108 -60
- data/attr_extras.gemspec +2 -4
- data/lib/attr_extras/attr_initialize.rb +3 -3
- data/lib/attr_extras/version.rb +1 -1
- data/spec/attr_initialize_spec.rb +7 -2
- metadata +23 -9
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
MDZkNDRkNzc2MDMyOGUwMzVjODE1YmE5NGNkODNhMjY4YTM5NzZjYw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 08a7329a2b945733d0baf670533dd8801c1880a9
|
4
|
+
data.tar.gz: 7f01ba7e4aadab65bb51ec5cdca127f97be8461e
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
MmZkM2U3NzNiOTBjMWVmYzQyODA4MzBhMWU3ODIyYmZkNjFjODRhYTAzOTlm
|
11
|
-
MGY0NjEzYWIxZjU5MDhlNjRmNDkzYmZlMWVjMmY0ODQ5MWY0YWI=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MWNiMmYwMDI5MDYzZjBjYWY2OGFhNTMyZWIyZjJjZDkzNDRlOWUxY2Q3NzNm
|
14
|
-
ZWJhYWViN2I5NmU1ZWY1OWFhODQ3NGEwOTY1YmE3MGZjNWY5YTU0NTY5YWU2
|
15
|
-
ODgwNjZmOGE2MjQzZmQ0MzE5ZDY5OTYyMzhjNjVlODk2NzMyMGQ=
|
6
|
+
metadata.gz: c3df45d212321d6d1e1046c7439fc9825e047b8b1d769f2b5e2b453f028449dec6bd07034560f821b1e43f3cb6446a13ac5a18edbb1e2725e49c6d44b895863d
|
7
|
+
data.tar.gz: ba8800473f4c53b5b83118bd125fa4212728610f2a65475c92af65ef5611094810c343cdf20ab493b675adfd1b5f9cdbe10bfaaae600a7572c5416c546fc186e
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ Takes some boilerplate out of Ruby, lowering the barrier to extracting small foc
|
|
6
6
|
|
7
7
|
Instead of
|
8
8
|
|
9
|
-
```
|
9
|
+
``` ruby
|
10
10
|
class InvoiceBuilder
|
11
11
|
def initialize(invoice, employee)
|
12
12
|
@invoice, @employee = invoice, employee
|
@@ -20,7 +20,7 @@ end
|
|
20
20
|
|
21
21
|
you can just do
|
22
22
|
|
23
|
-
```
|
23
|
+
``` ruby
|
24
24
|
class InvoiceBuilder
|
25
25
|
pattr_initialize :invoice, :employee
|
26
26
|
end
|
@@ -35,102 +35,146 @@ Also provides conveniences for creating value objects, method objects, query met
|
|
35
35
|
|
36
36
|
## Usage
|
37
37
|
|
38
|
+
* [`pattr_initialize`](#pattr_initialize)
|
39
|
+
* [`vattr_initialize`](#vattr_initialize)
|
40
|
+
* [`attr_initialize`](#attr_initialize)
|
41
|
+
* [`attr_private`](#attr_private)
|
42
|
+
* [`attr_value`](#attr_value)
|
43
|
+
* [`static_facade`](#static_facade)
|
44
|
+
* [`method_object`](#method_object)
|
45
|
+
* [`attr_implement`](#attr_implement)
|
46
|
+
* [`attr_query`](#attr_query)
|
47
|
+
* [`attr_id_query`](#attr_id_query)
|
38
48
|
|
39
|
-
### `attr_initialize :foo, :bar`
|
40
49
|
|
41
|
-
Defines an initializer that takes two arguments and assigns `@foo` and `@bar`.
|
42
50
|
|
43
|
-
|
51
|
+
### `pattr_initialize`
|
44
52
|
|
45
|
-
`
|
53
|
+
`pattr_initialize :foo, :bar` defines both initializer and private readers: shortcut for
|
46
54
|
|
55
|
+
``` ruby
|
56
|
+
attr_initialize :foo, :bar
|
57
|
+
attr_private :foo, :bar
|
58
|
+
```
|
47
59
|
|
48
|
-
|
60
|
+
Example:
|
49
61
|
|
50
|
-
|
62
|
+
``` ruby
|
63
|
+
class Item
|
64
|
+
pattr_initalize :name, :price
|
51
65
|
|
52
|
-
|
66
|
+
def price_with_vat
|
67
|
+
price * 1.25
|
68
|
+
end
|
69
|
+
end
|
53
70
|
|
71
|
+
Item.new("Pug", 100).price_with_vat # => 125.0
|
72
|
+
```
|
54
73
|
|
55
|
-
|
74
|
+
[The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `pattr_initialize :foo, [:bar, :baz!]`
|
56
75
|
|
57
|
-
Defines public readers. Does not define writers, as [value objects](http://en.wikipedia.org/wiki/Value_object) are typically immutable.
|
58
76
|
|
59
|
-
|
77
|
+
### `vattr_initialize`
|
60
78
|
|
79
|
+
`vattr_initialize :foo, :bar` defines initializer, public readers and [value object identity](#attr_value): shortcut for
|
61
80
|
|
62
|
-
|
81
|
+
``` ruby
|
82
|
+
attr_initialize :foo, :bar
|
83
|
+
attr_value :foo, :bar
|
84
|
+
```
|
63
85
|
|
64
|
-
|
86
|
+
Example:
|
65
87
|
|
66
|
-
```
|
67
|
-
|
68
|
-
|
88
|
+
``` ruby
|
89
|
+
class Country
|
90
|
+
vattr_initialize :code
|
91
|
+
end
|
92
|
+
|
93
|
+
Country.new("SE") == Country.new("SE") # => true
|
94
|
+
Country.new("SE").code # => "SE"
|
69
95
|
```
|
70
96
|
|
71
|
-
The `attr_initialize` notation for hash arguments is also supported: `
|
97
|
+
[The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `vattr_initialize :foo, [:bar, :baz!]`
|
72
98
|
|
73
99
|
|
74
|
-
### `
|
100
|
+
### `attr_initialize`
|
75
101
|
|
76
|
-
|
102
|
+
`attr_initialize :foo, :bar` defines an initializer that takes two arguments and assigns `@foo` and `@bar`.
|
77
103
|
|
78
|
-
|
79
|
-
attr_initialize :foo, :bar
|
80
|
-
attr_value :foo, :bar
|
81
|
-
```
|
104
|
+
`attr_initialize :foo, [:bar, :baz!]` defines an initializer that takes one regular argument, assigning `@foo`, and one hash argument, assigning `@bar` (optional) and `@baz` (required).
|
82
105
|
|
83
|
-
|
106
|
+
`attr_initialize [:bar, :baz!]` defines an initializer that takes one hash argument, assigning `@bar` (optional) and `@baz` (required).
|
84
107
|
|
108
|
+
`attr_initialize` can also accept a block which will be invoked after initialization. This is useful for calling `super` appropriately in subclasses or initializing private data as necessary.
|
109
|
+
|
110
|
+
|
111
|
+
### `attr_private`
|
112
|
+
|
113
|
+
`attr_private :foo, :bar` defines private readers for `@foo` and `@bar`.
|
114
|
+
|
115
|
+
|
116
|
+
### `attr_value`
|
117
|
+
|
118
|
+
`attr_value :foo, :bar` defines public readers for `@foo` and `@bar` and also defines object equality: two value objects of the same class with the same values will be considered equal (with `==` and `eql?`, in `Set`s, as `Hash` keys etc).
|
119
|
+
|
120
|
+
It does not define writers, because [value objects](http://en.wikipedia.org/wiki/Value_object) are typically immutable.
|
85
121
|
|
86
|
-
### `static_facade :fooable?, :foo`<br>
|
87
122
|
|
88
|
-
|
123
|
+
### `static_facade`
|
124
|
+
|
125
|
+
`static_facade :allow?, :user` defines an `.allow?` class method that delegates to an instance method by the same name, having first provided `user` as a private reader.
|
89
126
|
|
90
127
|
This is handy when a class-method API makes sense but you still want [the refactorability of instance methods](http://blog.codeclimate.com/blog/2012/11/14/why-ruby-class-methods-resist-refactoring/).
|
91
128
|
|
129
|
+
Example:
|
130
|
+
|
92
131
|
``` ruby
|
93
132
|
class PublishingPolicy
|
94
133
|
static_facade :allow?, :user
|
95
|
-
static_facade :disallow?, :user
|
96
134
|
|
97
135
|
def allow?
|
98
|
-
user.admin? &&
|
136
|
+
user.admin? && complicated_extracted_method
|
99
137
|
end
|
100
138
|
|
101
|
-
|
102
|
-
|
139
|
+
private
|
140
|
+
|
141
|
+
def complicated_extracted_method
|
142
|
+
# …
|
103
143
|
end
|
104
144
|
end
|
105
145
|
|
106
146
|
PublishingPolicy.allow?(user)
|
107
147
|
```
|
108
148
|
|
109
|
-
`static_facade :
|
149
|
+
`static_facade :allow?, :user` is a shortcut for
|
110
150
|
|
111
151
|
``` ruby
|
112
|
-
pattr_initialize :
|
152
|
+
pattr_initialize :user
|
113
153
|
|
114
|
-
def self.
|
115
|
-
new(
|
154
|
+
def self.allow?(user)
|
155
|
+
new(user).allow?
|
116
156
|
end
|
117
157
|
```
|
118
158
|
|
119
|
-
The `attr_initialize` notation for hash arguments is also supported: `static_facade :
|
159
|
+
[The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `static_facade :allow?, :user, [:user_agent, :ip!]`
|
160
|
+
|
161
|
+
You don't have to specify arguments/readers if you don't want them: just `static_facade :tuesday?` is also valid.
|
162
|
+
|
163
|
+
"Static façade" is the least bad name for this pattern we've come up with. Suggestions are welcome.
|
120
164
|
|
121
|
-
You don't have to specify arguments/readers if you don't want them: just `static_facade :fooable?` is also valid.
|
122
165
|
|
166
|
+
### `method_object`
|
123
167
|
|
124
|
-
|
168
|
+
*NOTE: v4.0.0 made a breaking change! [`static_facade`](#static_facade) does exactly what `method_object` used to do; the new `method_object` no longer accepts a method name argument.*
|
125
169
|
|
126
|
-
|
170
|
+
`method_object :foo` defines a `.call` class method that delegates to an instance method by the same name, having first provided `foo` as a private reader.
|
127
171
|
|
128
|
-
|
172
|
+
This is a special case of [`static_facade`](#static_facade) for when you want a [Method Object](http://refactoring.com/catalog/replaceMethodWithMethodObject.html), and the class name itself will communicate the action it performs.
|
129
173
|
|
130
|
-
|
174
|
+
Example:
|
131
175
|
|
132
176
|
``` ruby
|
133
|
-
class
|
177
|
+
class CalculatePrice
|
134
178
|
method_object :order
|
135
179
|
|
136
180
|
def call
|
@@ -150,11 +194,13 @@ end
|
|
150
194
|
|
151
195
|
class Order
|
152
196
|
def price
|
153
|
-
|
197
|
+
CalculatePrice.call(self)
|
154
198
|
end
|
155
199
|
end
|
156
200
|
```
|
157
201
|
|
202
|
+
You could even do `CalculatePrice.(self)` if you like, since we're using the [`call` convention](http://www.ruby-doc.org/core-2.2.0/Proc.html#method-i-call).
|
203
|
+
|
158
204
|
`method_object :foo` is a shortcut for
|
159
205
|
|
160
206
|
``` ruby
|
@@ -171,23 +217,14 @@ def self.call(foo)
|
|
171
217
|
end
|
172
218
|
```
|
173
219
|
|
174
|
-
The `attr_initialize` notation for hash arguments is also supported: `method_object :foo, [:bar, :baz!]`
|
220
|
+
[The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `method_object :foo, [:bar, :baz!]`
|
175
221
|
|
176
222
|
You don't have to specify arguments/readers if you don't want them: just `method_object` is also valid.
|
177
223
|
|
178
224
|
|
179
|
-
### `
|
180
|
-
|
181
|
-
Defines query methods like `foo?`, which is true if (and only if) `foo_id` is truthy. Goes well with Active Record.
|
182
|
-
|
183
|
-
|
184
|
-
### `attr_query :foo?, :bar?`<br>
|
225
|
+
### `attr_implement`
|
185
226
|
|
186
|
-
|
187
|
-
|
188
|
-
### `attr_implement :foo, :bar`<br>
|
189
|
-
|
190
|
-
Defines nullary (0-argument) methods `foo` and `bar` that raise e.g. `"Implement a 'foo()' method"`.
|
227
|
+
`attr_implement :foo, :bar` defines nullary (0-argument) methods `foo` and `bar` that raise e.g. `"Implement a 'foo()' method"`.
|
191
228
|
|
192
229
|
`attr_implement :foo, [:name, :age]` will define a binary (2-argument) method `foo` that raises `"Implement a 'foo(name, age)' method"`.
|
193
230
|
|
@@ -204,6 +241,16 @@ end
|
|
204
241
|
though it is shorter, more declarative, gives you a clear message and handles edge cases you might not have thought about (see tests).
|
205
242
|
|
206
243
|
|
244
|
+
### `attr_query`
|
245
|
+
|
246
|
+
`attr_query :foo?, :bar?` defines query methods like `foo?`, which is true if (and only if) `foo` is truthy.
|
247
|
+
|
248
|
+
|
249
|
+
### `attr_id_query`
|
250
|
+
|
251
|
+
`attr_id_query :foo?, :bar?` defines query methods like `foo?`, which is true if (and only if) `foo_id` is truthy. Goes well with Active Record.
|
252
|
+
|
253
|
+
|
207
254
|
## Philosophy
|
208
255
|
|
209
256
|
Findability is a core value.
|
@@ -211,14 +258,15 @@ Hence the long name `attr_initialize`, so you see it when scanning for the initi
|
|
211
258
|
and the enforced questionmarks with `attr_id_query :foo?`, so you can search for that method.
|
212
259
|
|
213
260
|
|
214
|
-
##
|
261
|
+
## Q & A
|
215
262
|
|
216
|
-
See: ["Struct inheritance is overused"](http://thepugautomatic.com/2013/08/struct-inheritance-is-overused/)
|
217
263
|
|
264
|
+
### Why not use `Struct` instead of `pattr_initialize`?
|
265
|
+
|
266
|
+
See: ["Struct inheritance is overused"](http://thepugautomatic.com/2013/08/struct-inheritance-is-overused/)
|
218
267
|
|
219
|
-
## Why not use `private; attr_reader :foo`?
|
220
268
|
|
221
|
-
|
269
|
+
### Why not use `private; attr_reader :foo` instead of `attr_private :foo`?
|
222
270
|
|
223
271
|
Other than being more to type, declaring `attr_reader` after `private` will actually give you a warning (deserved or not) if you run Ruby with warnings turned on.
|
224
272
|
|
@@ -242,7 +290,7 @@ Or install it yourself as:
|
|
242
290
|
|
243
291
|
## Running the tests
|
244
292
|
|
245
|
-
Run
|
293
|
+
Run them with:
|
246
294
|
|
247
295
|
`rake`
|
248
296
|
|
data/attr_extras.gemspec
CHANGED
@@ -15,10 +15,8 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.license = "MIT"
|
16
16
|
gem.version = AttrExtras::VERSION
|
17
17
|
|
18
|
+
gem.add_development_dependency "minitest", ">= 5"
|
19
|
+
|
18
20
|
# For Travis CI.
|
19
21
|
gem.add_development_dependency "rake"
|
20
|
-
|
21
|
-
if RUBY_VERSION < "1.9.3"
|
22
|
-
gem.add_development_dependency "minitest"
|
23
|
-
end
|
24
22
|
end
|
@@ -15,7 +15,7 @@ class AttrExtras::AttrInitialize
|
|
15
15
|
set_ivar_from_hash = method(:set_ivar_from_hash)
|
16
16
|
|
17
17
|
klass.send(:define_method, :initialize) do |*values|
|
18
|
-
validate_arity.call(values.length)
|
18
|
+
validate_arity.call(values.length, self.class)
|
19
19
|
|
20
20
|
names.zip(values).each do |name_or_names, value|
|
21
21
|
if name_or_names.is_a?(Array)
|
@@ -38,13 +38,13 @@ class AttrExtras::AttrInitialize
|
|
38
38
|
|
39
39
|
private
|
40
40
|
|
41
|
-
def validate_arity(provided_arity)
|
41
|
+
def validate_arity(provided_arity, klass)
|
42
42
|
arity_without_hashes = names.count { |name| not name.is_a?(Array) }
|
43
43
|
arity_with_hashes = names.length
|
44
44
|
|
45
45
|
unless (arity_without_hashes..arity_with_hashes).include?(provided_arity)
|
46
46
|
arity_range = [ arity_without_hashes, arity_with_hashes ].uniq.join("..")
|
47
|
-
raise ArgumentError, "wrong number of arguments (#{provided_arity} for #{arity_range})"
|
47
|
+
raise ArgumentError, "wrong number of arguments (#{provided_arity} for #{arity_range}) for #{klass.name} initializer"
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
data/lib/attr_extras/version.rb
CHANGED
@@ -4,6 +4,10 @@ describe Object, ".attr_initialize" do
|
|
4
4
|
let(:klass) do
|
5
5
|
Class.new do
|
6
6
|
attr_initialize :foo, :bar
|
7
|
+
|
8
|
+
def self.name
|
9
|
+
"ExampleClass"
|
10
|
+
end
|
7
11
|
end
|
8
12
|
end
|
9
13
|
|
@@ -14,7 +18,8 @@ describe Object, ".attr_initialize" do
|
|
14
18
|
end
|
15
19
|
|
16
20
|
it "requires all arguments" do
|
17
|
-
lambda { klass.new("Foo") }.must_raise ArgumentError
|
21
|
+
exception = lambda { klass.new("Foo") }.must_raise ArgumentError
|
22
|
+
exception.message.must_equal "wrong number of arguments (1 for 2) for ExampleClass initializer"
|
18
23
|
end
|
19
24
|
|
20
25
|
it "can set ivars from a hash" do
|
@@ -51,7 +56,7 @@ describe Object, ".attr_initialize" do
|
|
51
56
|
lambda { klass.new(:optional => "X") }.must_raise KeyError
|
52
57
|
end
|
53
58
|
|
54
|
-
it "
|
59
|
+
it "accepts a block for initialization" do
|
55
60
|
klass = Class.new do
|
56
61
|
attr_initialize :value do
|
57
62
|
@copy = @value
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attr_extras
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henrik Nyh
|
@@ -10,20 +10,34 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-
|
13
|
+
date: 2015-02-09 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: minitest
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '5'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '5'
|
15
29
|
- !ruby/object:Gem::Dependency
|
16
30
|
name: rake
|
17
31
|
requirement: !ruby/object:Gem::Requirement
|
18
32
|
requirements:
|
19
|
-
- -
|
33
|
+
- - ">="
|
20
34
|
- !ruby/object:Gem::Version
|
21
35
|
version: '0'
|
22
36
|
type: :development
|
23
37
|
prerelease: false
|
24
38
|
version_requirements: !ruby/object:Gem::Requirement
|
25
39
|
requirements:
|
26
|
-
- -
|
40
|
+
- - ">="
|
27
41
|
- !ruby/object:Gem::Version
|
28
42
|
version: '0'
|
29
43
|
description:
|
@@ -33,9 +47,9 @@ executables: []
|
|
33
47
|
extensions: []
|
34
48
|
extra_rdoc_files: []
|
35
49
|
files:
|
36
|
-
- .gitignore
|
37
|
-
- .rvmrc
|
38
|
-
- .travis.yml
|
50
|
+
- ".gitignore"
|
51
|
+
- ".rvmrc"
|
52
|
+
- ".travis.yml"
|
39
53
|
- Gemfile
|
40
54
|
- LICENSE.txt
|
41
55
|
- README.md
|
@@ -69,12 +83,12 @@ require_paths:
|
|
69
83
|
- lib
|
70
84
|
required_ruby_version: !ruby/object:Gem::Requirement
|
71
85
|
requirements:
|
72
|
-
- -
|
86
|
+
- - ">="
|
73
87
|
- !ruby/object:Gem::Version
|
74
88
|
version: '0'
|
75
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
90
|
requirements:
|
77
|
-
- -
|
91
|
+
- - ">="
|
78
92
|
- !ruby/object:Gem::Version
|
79
93
|
version: '0'
|
80
94
|
requirements: []
|