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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YTEzMGU2ZTk5YTFhZTRkOGRjN2NjMDNkYTU4MDM4NTVkNDExNmVkOQ==
5
- data.tar.gz: !binary |-
6
- MDZkNDRkNzc2MDMyOGUwMzVjODE1YmE5NGNkODNhMjY4YTM5NzZjYw==
2
+ SHA1:
3
+ metadata.gz: 08a7329a2b945733d0baf670533dd8801c1880a9
4
+ data.tar.gz: 7f01ba7e4aadab65bb51ec5cdca127f97be8461e
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MWNjNjMyYjQ3ZTMyMDE5MTQwYWIyY2NjYzQxMjJkNTNjNTQxOGZhMzY2NjAw
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
- `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).
51
+ ### `pattr_initialize`
44
52
 
45
- `attr_initialize [:bar, :baz!]` defines an initializer that takes one hash argument, assigning `@bar` (optional) and `@baz` (required).
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
- `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.
60
+ Example:
49
61
 
50
- ### `attr_private :foo, :bar`
62
+ ``` ruby
63
+ class Item
64
+ pattr_initalize :name, :price
51
65
 
52
- Defines private readers for `@foo` and `@bar`.
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
- ### `attr_value :foo, :bar`
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
- Defines object equality: two value objects of the same class with the same values are equal.
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
- ### `pattr_initialize :foo, :bar`
81
+ ``` ruby
82
+ attr_initialize :foo, :bar
83
+ attr_value :foo, :bar
84
+ ```
63
85
 
64
- Defines both initializer and private readers: shortcut for
86
+ Example:
65
87
 
66
- ```
67
- attr_initialize :foo, :bar
68
- attr_private :foo, :bar
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: `pattr_initialize :foo, [:bar, :baz!]`
97
+ [The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `vattr_initialize :foo, [:bar, :baz!]`
72
98
 
73
99
 
74
- ### `vattr_initialize :foo, :bar`
100
+ ### `attr_initialize`
75
101
 
76
- Defines initializer, public readers and value object identity: shortcut for
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
- The `attr_initialize` notation for hash arguments is also supported: `vattr_initialize :foo, [:bar, :baz!]`
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
- Defines a `.fooable?` class method that delegates to an instance method by the same name, having first provided `foo` as a private reader.
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
- def disallow?
102
- !allow?
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 :fooable?, :foo` is a shortcut for
149
+ `static_facade :allow?, :user` is a shortcut for
110
150
 
111
151
  ``` ruby
112
- pattr_initialize :foo
152
+ pattr_initialize :user
113
153
 
114
- def self.fooable?(foo)
115
- new(foo).fooable?
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 :fooable?, :foo, [:bar, :baz!]`
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
- ### `method_object :foo`
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
- *NOTE: v4.0.0 made a breaking change! `static_facade` does exactly what `method_object` used to do; the new `method_object` no longer accepts a method name argument.*
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
- Defines a `.call` class method that delegates to an instance method by the same name, having first provided `foo` as a private reader.
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
- This is a special case of `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.
174
+ Example:
131
175
 
132
176
  ``` ruby
133
- class PriceCalculator
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
- PriceCalculator.call(self)
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
- ### `attr_id_query :foo?, :bar?`<br>
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
- Defines query methods like `foo?`, which is true if (and only if) `foo` is truthy.
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
- ## Why not use `Struct`?
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
- Instead of `attr_private :foo`, you could do `private; attr_reader :foo`.
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 then with:
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
 
@@ -1,3 +1,3 @@
1
1
  module AttrExtras
2
- VERSION = "4.0.0"
2
+ VERSION = "4.1.0"
3
3
  end
@@ -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 "can accept a block for initialization" do
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.0.0
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-01-16 00:00:00.000000000 Z
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: []