attr_extras 4.0.0 → 4.1.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,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: []