attr_extras 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/README.md +36 -56
- data/lib/attr_extras/attr_initialize.rb +57 -0
- data/lib/attr_extras/attr_query.rb +13 -0
- data/lib/attr_extras/utils.rb +5 -0
- data/lib/attr_extras/version.rb +1 -1
- data/lib/attr_extras.rb +8 -54
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZjhmZWFmMjMzZDY4YmFmYWYyZDFjYjIyNzU1ZGJlYmFlMmYxNGEwMQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZTI2NzNiMmEwZDRlNjM3ZjM5ODZiMDcyYTU0YWNmZmE0YTEyODNkOQ==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MzE5YmQ3ZjBmYjY5MzY4OTczY2VkNzNiMDVkNDMxNzUyYmFkMzFlMmJmNTA5
|
10
|
+
MjYxMjJkZDE1YmJkYzdlNjM2NzIwOWZhMjc2OTUxYjBjNmNjN2RiZDEwODIy
|
11
|
+
YWVmZGE0MWZjOGY3NWFmNGNkODg5Mzk0NDllMGMzOWJkN2Q3ZGQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
M2NkMWU1NjAxYTNkMjczY2VkYWEwMDMyODM5OGY0YjcxNDU4YjRlZWRkZmYy
|
14
|
+
MjZlYzM3NjgyOWVhMjg2NjMyNmJiZTk2OTdlNjk4ODFkZTU4Y2E2ZjFkNWY5
|
15
|
+
OTgxY2EwYjk0Nzc4MWNmYTc4MTBiZTM0ODQwNzllZTE5ZGY2OGI=
|
data/README.md
CHANGED
@@ -28,6 +28,10 @@ end
|
|
28
28
|
|
29
29
|
This nicely complements Ruby's built-in `attr_accessor`, `attr_reader` and `attr_writer`.
|
30
30
|
|
31
|
+
Supports positional arguments as well as optional and required hash arguments.
|
32
|
+
|
33
|
+
Also provides conveniences for creating value objects, method objects and query methods.
|
34
|
+
|
31
35
|
|
32
36
|
## Usage
|
33
37
|
|
@@ -79,83 +83,59 @@ The `attr_initialize` notation notation for hash arguments is also supported: `v
|
|
79
83
|
|
80
84
|
### `method_object :fooable?, :foo`<br>
|
81
85
|
|
82
|
-
Defines a `.fooable?` class method that takes
|
83
|
-
|
84
|
-
The `attr_initialize` notation notation for hash arguments is also supported: `method_object :fooable?, :foo, [:bar, :baz!]`
|
85
|
-
|
86
|
-
You don't have to specify readers if you don't want them: `method_object :fooable?` is also valid.
|
87
|
-
|
88
|
-
|
89
|
-
### `attr_id_query :foo?, :bar?`<br>
|
90
|
-
Defines query methods like `foo?`, which is true iff `foo_id` is truthy. Goes well with Active Record.
|
91
|
-
|
86
|
+
Defines a `.fooable?` class method that takes arguments (`foo`) and delegates to an instance method that can access those arguments as private readers.
|
92
87
|
|
93
|
-
|
94
|
-
Defines query methods like `foo?`, which is true iff `foo` is truthy.
|
88
|
+
This is useful for [method objects](http://refactoring.com/catalog/replaceMethodWithMethodObject.html):
|
95
89
|
|
90
|
+
``` ruby
|
91
|
+
class PriceCalculator
|
92
|
+
method_object :calculate,
|
93
|
+
:order
|
96
94
|
|
97
|
-
|
95
|
+
def calculate
|
96
|
+
order.price * factor
|
97
|
+
end
|
98
98
|
|
99
|
-
|
100
|
-
Hence the long name `attr_initialize`, so you see it when scanning for the initializer;
|
101
|
-
and the enforced questionmarks with `attr_id_query :foo?`, so you can search for that method.
|
99
|
+
private
|
102
100
|
|
101
|
+
def factor
|
102
|
+
1 + rand
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
103
106
|
|
104
|
-
|
107
|
+
Shortcut for
|
105
108
|
|
106
109
|
``` ruby
|
107
|
-
|
108
|
-
|
109
|
-
attr_id_query :item?
|
110
|
-
attr_query :oof?
|
111
|
-
|
112
|
-
def oof
|
113
|
-
foo.reverse
|
114
|
-
end
|
110
|
+
attr_initialize :foo
|
111
|
+
attr_private :foo
|
115
112
|
|
116
|
-
|
117
|
-
|
118
|
-
end
|
113
|
+
def self.fooable?(foo)
|
114
|
+
new(foo).fooable?
|
119
115
|
end
|
116
|
+
```
|
120
117
|
|
121
|
-
|
122
|
-
x.oof # => "!ooF"
|
123
|
-
x.foo # NoMethodError: private method `foo' called.
|
124
|
-
x.item? # => true
|
125
|
-
x.oof? # => true
|
118
|
+
The `attr_initialize` notation notation for hash arguments is also supported: `method_object :fooable?, :foo, [:bar, :baz!]`
|
126
119
|
|
120
|
+
You don't have to specify readers if you don't want them: `method_object :fooable?` is also valid.
|
127
121
|
|
128
|
-
class MyMethodObject
|
129
|
-
method_object :fooable?,
|
130
|
-
:foo
|
131
122
|
|
132
|
-
|
133
|
-
foo == :some_value
|
134
|
-
end
|
135
|
-
end
|
123
|
+
### `attr_id_query :foo?, :bar?`<br>
|
136
124
|
|
137
|
-
|
138
|
-
MyMethodObject.fooable?(:another_value) # => false
|
125
|
+
Defines query methods like `foo?`, which is true if (and only if) `foo_id` is truthy. Goes well with Active Record.
|
139
126
|
|
140
127
|
|
141
|
-
|
142
|
-
attr_initialize :foo, [:bar, :baz]
|
143
|
-
attr_reader :bar
|
144
|
-
end
|
128
|
+
### `attr_query :foo?, :bar?`<br>
|
145
129
|
|
146
|
-
|
147
|
-
x.bar # => "Bar!"
|
130
|
+
Defines query methods like `foo?`, which is true if (and only if) `foo` is truthy.
|
148
131
|
|
149
132
|
|
150
|
-
|
151
|
-
|
152
|
-
|
133
|
+
## Philosophy
|
134
|
+
|
135
|
+
Findability is a core value.
|
136
|
+
Hence the long name `attr_initialize`, so you see it when scanning for the initializer;
|
137
|
+
and the enforced questionmarks with `attr_id_query :foo?`, so you can search for that method.
|
153
138
|
|
154
|
-
x = MyValueObject.new(5, 10)
|
155
|
-
x.foo # => 5
|
156
|
-
x.bar # => 10
|
157
|
-
x.foo = 20 # NoMethodError: undefined method `foo=''`
|
158
|
-
```
|
159
139
|
|
160
140
|
## Why not use `Struct`?
|
161
141
|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class AttrExtras::AttrInitialize
|
2
|
+
def initialize(klass, names)
|
3
|
+
@klass, @names = klass, names
|
4
|
+
end
|
5
|
+
|
6
|
+
attr_reader :klass, :names
|
7
|
+
private :klass, :names
|
8
|
+
|
9
|
+
def apply
|
10
|
+
# The define_method block can't call our methods, so we need to make
|
11
|
+
# things available via local variables.
|
12
|
+
names = @names
|
13
|
+
validate_arity = method(:validate_arity)
|
14
|
+
set_ivar_from_hash = method(:set_ivar_from_hash)
|
15
|
+
|
16
|
+
klass.send(:define_method, :initialize) do |*values|
|
17
|
+
validate_arity.call(values.length)
|
18
|
+
|
19
|
+
names.zip(values).each do |name_or_names, value|
|
20
|
+
if name_or_names.is_a?(Array)
|
21
|
+
hash = value || {}
|
22
|
+
|
23
|
+
name_or_names.each do |name|
|
24
|
+
set_ivar_from_hash.call(self, name, hash)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
name = name_or_names
|
28
|
+
instance_variable_set("@#{name}", value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def validate_arity(provided_arity)
|
37
|
+
arity_without_hashes = names.count { |name| not name.is_a?(Array) }
|
38
|
+
arity_with_hashes = names.length
|
39
|
+
|
40
|
+
unless (arity_without_hashes..arity_with_hashes).include?(provided_arity)
|
41
|
+
arity_range = [arity_without_hashes, arity_with_hashes].uniq.join("..")
|
42
|
+
raise ArgumentError, "wrong number of arguments (#{provided_arity} for #{arity_range})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_ivar_from_hash(instance, name, hash)
|
47
|
+
if name.to_s.end_with?("!")
|
48
|
+
actual_name = name.to_s.chop.to_sym
|
49
|
+
value = hash.fetch(actual_name)
|
50
|
+
else
|
51
|
+
actual_name = name
|
52
|
+
value = hash[name]
|
53
|
+
end
|
54
|
+
|
55
|
+
instance.instance_variable_set("@#{actual_name}", value)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module AttrExtras::AttrQuery
|
2
|
+
def self.define_with_suffix(klass, suffix, *names)
|
3
|
+
names.each do |name|
|
4
|
+
name = name.to_s
|
5
|
+
|
6
|
+
raise "#{__method__} wants `#{name}?`, not `#{name}`." unless name.end_with?("?")
|
7
|
+
|
8
|
+
klass.send(:define_method, name) do # def foo?
|
9
|
+
!!send("#{name.chop}#{suffix}") # !!send("foo_id")
|
10
|
+
end # end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/attr_extras/version.rb
CHANGED
data/lib/attr_extras.rb
CHANGED
@@ -1,40 +1,12 @@
|
|
1
1
|
require "attr_extras/version"
|
2
|
+
require "attr_extras/attr_initialize"
|
3
|
+
require "attr_extras/attr_query"
|
4
|
+
require "attr_extras/utils"
|
2
5
|
|
3
6
|
module AttrExtras
|
4
7
|
module ClassMethods
|
5
8
|
def attr_initialize(*names)
|
6
|
-
|
7
|
-
max_arity = names.length
|
8
|
-
|
9
|
-
define_method(:initialize) do |*values|
|
10
|
-
provided_arity = values.length
|
11
|
-
|
12
|
-
unless (min_arity..max_arity).include?(provided_arity)
|
13
|
-
arity_range = [min_arity, max_arity].uniq.join("..")
|
14
|
-
raise ArgumentError, "wrong number of arguments (#{provided_arity} for #{arity_range})"
|
15
|
-
end
|
16
|
-
|
17
|
-
names.zip(values).each do |name_or_names, value|
|
18
|
-
if name_or_names.is_a?(Array)
|
19
|
-
value ||= {}
|
20
|
-
|
21
|
-
name_or_names.each do |name|
|
22
|
-
if name.to_s.end_with?("!")
|
23
|
-
actual_name = name.to_s.chop.to_sym
|
24
|
-
actual_value = value.fetch(actual_name)
|
25
|
-
else
|
26
|
-
actual_name = name
|
27
|
-
actual_value = value[name]
|
28
|
-
end
|
29
|
-
|
30
|
-
instance_variable_set("@#{actual_name}", actual_value)
|
31
|
-
end
|
32
|
-
else
|
33
|
-
name = name_or_names
|
34
|
-
instance_variable_set("@#{name}", value)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
9
|
+
AttrInitialize.new(self, names).apply
|
38
10
|
end
|
39
11
|
|
40
12
|
def attr_private(*names)
|
@@ -44,12 +16,12 @@ module AttrExtras
|
|
44
16
|
|
45
17
|
def pattr_initialize(*names)
|
46
18
|
attr_initialize(*names)
|
47
|
-
attr_private *
|
19
|
+
attr_private *Utils.flat_names(names)
|
48
20
|
end
|
49
21
|
|
50
22
|
def vattr_initialize(*names)
|
51
23
|
attr_initialize(*names)
|
52
|
-
attr_value *
|
24
|
+
attr_value *Utils.flat_names(names)
|
53
25
|
end
|
54
26
|
|
55
27
|
def attr_value(*names)
|
@@ -71,29 +43,11 @@ module AttrExtras
|
|
71
43
|
end
|
72
44
|
|
73
45
|
def attr_query(*names)
|
74
|
-
|
46
|
+
AttrQuery.define_with_suffix(self, "", *names)
|
75
47
|
end
|
76
48
|
|
77
49
|
def attr_id_query(*names)
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def attr_flat_names(names)
|
84
|
-
names.flatten.map { |x| x.to_s.sub(/!\z/, "") }
|
85
|
-
end
|
86
|
-
|
87
|
-
def attr_query_with_suffix(*names, suffix)
|
88
|
-
names.each do |name|
|
89
|
-
name = name.to_s
|
90
|
-
|
91
|
-
raise "#{__method__} wants `#{name}?`, not `#{name}`." unless name.end_with?("?")
|
92
|
-
|
93
|
-
define_method(name) do # def foo?
|
94
|
-
!!send("#{name.chop}#{suffix}") # !!send("foo_id")
|
95
|
-
end # end
|
96
|
-
end
|
50
|
+
AttrQuery.define_with_suffix(self, "_id", *names)
|
97
51
|
end
|
98
52
|
end
|
99
53
|
end
|
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: 2.0.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henrik Nyh
|
@@ -15,14 +15,14 @@ dependencies:
|
|
15
15
|
name: rake
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - '>='
|
18
|
+
- - ! '>='
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: '0'
|
21
21
|
type: :development
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- - '>='
|
25
|
+
- - ! '>='
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
28
|
description:
|
@@ -40,6 +40,9 @@ files:
|
|
40
40
|
- Rakefile
|
41
41
|
- attr_extras.gemspec
|
42
42
|
- lib/attr_extras.rb
|
43
|
+
- lib/attr_extras/attr_initialize.rb
|
44
|
+
- lib/attr_extras/attr_query.rb
|
45
|
+
- lib/attr_extras/utils.rb
|
43
46
|
- lib/attr_extras/version.rb
|
44
47
|
- script/test
|
45
48
|
- spec/attr_extras_spec.rb
|
@@ -53,17 +56,17 @@ require_paths:
|
|
53
56
|
- lib
|
54
57
|
required_ruby_version: !ruby/object:Gem::Requirement
|
55
58
|
requirements:
|
56
|
-
- - '>='
|
59
|
+
- - ! '>='
|
57
60
|
- !ruby/object:Gem::Version
|
58
61
|
version: '0'
|
59
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
63
|
requirements:
|
61
|
-
- - '>='
|
64
|
+
- - ! '>='
|
62
65
|
- !ruby/object:Gem::Version
|
63
66
|
version: '0'
|
64
67
|
requirements: []
|
65
68
|
rubyforge_project:
|
66
|
-
rubygems_version: 2.2.
|
69
|
+
rubygems_version: 2.2.1
|
67
70
|
signing_key:
|
68
71
|
specification_version: 4
|
69
72
|
summary: Takes some boilerplate out of Ruby with methods like attr_initialize.
|