attributable 0.0.4 → 0.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 +4 -4
- data/README.md +31 -9
- data/lib/attributable.rb +24 -27
- data/lib/attributable/version.rb +1 -1
- data/spec/attributable/accessors_spec.rb +1 -1
- data/spec/attributable/construction_spec.rb +17 -11
- data/spec/attributable/specialisation_spec.rb +119 -69
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff3f791c159b7f77582d20aea0a0a2a608bf0089
|
4
|
+
data.tar.gz: 73fbc9b201af71fc14149c134ea87e55d88f01e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86a48e5ec8a1e34b2c3535194a947d4a9f4ec4fdc0d571873cac0586392c0840c63f61fd5067273e31254db98f9828aa07731dea69d61626a022f6428cf9f451
|
7
|
+
data.tar.gz: 667d00b4f7c7bff03e151da759f2d6e75e773154627ec39a84f7c6e9e47159c779365935e813b2fd87e6a305bbf4db5155722f97e59bc86daedd78e333803ea0
|
data/README.md
CHANGED
@@ -98,31 +98,53 @@ Note that, by default, Attributable adds the following `initialize` method:
|
|
98
98
|
initialize_attributes(attributes)
|
99
99
|
end
|
100
100
|
|
101
|
-
##
|
101
|
+
## Reuse via inheritance and mix-ins
|
102
102
|
|
103
|
-
To
|
103
|
+
To reuse attribute declarations, either user Ruby's built-in inheritance, mix-ins, or both:
|
104
104
|
|
105
|
+
class Author < User
|
106
|
+
attributes blogs: []
|
107
|
+
end
|
108
|
+
|
109
|
+
ronson = Author.new(forename: "Jon", surname: "Ronson")
|
110
|
+
ronson.inspect # => <Author forename="Jon", surname="Ronson", blogs=[]>
|
111
|
+
|
112
|
+
The default values defined in superclasses or mixed-in modules can be changed:
|
113
|
+
|
114
|
+
class Ronson < User
|
115
|
+
attributes surname: "Ronson"
|
116
|
+
end
|
117
|
+
|
118
|
+
Ronson.new(forename: "Jon").inspect # <Ronson forename="Jon", surname="Ronson">
|
119
|
+
Ronson.new(forename: "Mark").inspect # <Ronson forename="Mark", surname="Ronson">
|
120
|
+
|
121
|
+
Here's the same example, but using a module:
|
122
|
+
|
123
|
+
module User
|
124
|
+
extend Attributable
|
125
|
+
|
126
|
+
attributes :forename, :surname
|
127
|
+
end
|
128
|
+
|
105
129
|
class Author
|
130
|
+
include User
|
106
131
|
extend Attributable
|
107
|
-
|
108
|
-
specialises User
|
109
132
|
attributes blogs: []
|
110
133
|
end
|
111
134
|
|
112
135
|
ronson = Author.new(forename: "Jon", surname: "Ronson")
|
113
136
|
ronson.inspect # => <Author forename="Jon", surname="Ronson", blogs=[]>
|
114
137
|
|
115
|
-
Specialising classes can override the defaults set in specialised classes.
|
116
|
-
|
117
138
|
class Ronson
|
139
|
+
include User
|
118
140
|
extend Attributable
|
119
|
-
|
120
|
-
specialises User
|
121
141
|
attributes surname: "Ronson"
|
122
142
|
end
|
123
|
-
|
143
|
+
|
124
144
|
Ronson.new(forename: "Jon").inspect # <Ronson forename="Jon", surname="Ronson">
|
125
145
|
Ronson.new(forename: "Mark").inspect # <Ronson forename="Mark", surname="Ronson">
|
146
|
+
|
147
|
+
__Note__: the `include` must occur before any call to `attributes`.
|
126
148
|
|
127
149
|
### Automatic specialisation for superclasses
|
128
150
|
|
data/lib/attributable.rb
CHANGED
@@ -2,24 +2,22 @@ require "attributable/version"
|
|
2
2
|
|
3
3
|
module Attributable
|
4
4
|
def attributes(*without_defaults, **with_defaults)
|
5
|
-
@
|
6
|
-
@
|
7
|
-
|
5
|
+
@attributes ||= {}
|
6
|
+
@attributes.merge!(attributes_from(superclass)) if respond_to?(:superclass)
|
7
|
+
@attributes.merge!(attributes_from(*included_modules))
|
8
|
+
@attributes.merge!(from(without_defaults, with_defaults))
|
9
|
+
add_instance_methods(@attributes)
|
8
10
|
end
|
9
11
|
|
10
|
-
|
11
|
-
unless clazz.kind_of? Attributable
|
12
|
-
fail ArgumentError, "specialisation requires a class that extends Attributable"
|
13
|
-
end
|
12
|
+
private
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
def attributes_from(*modules)
|
15
|
+
modules
|
16
|
+
.select { |m| m.kind_of?(Attributable) }
|
17
|
+
.map { |m| m.instance_variable_get(:@attributes) }
|
18
|
+
.reduce({}, &:merge)
|
19
19
|
end
|
20
20
|
|
21
|
-
private
|
22
|
-
|
23
21
|
# Converts a list of attribute names and a hash of attribute names to default values
|
24
22
|
# to a hash of attribute names to default values
|
25
23
|
def from(without_defaults, with_defaults)
|
@@ -29,24 +27,23 @@ module Attributable
|
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
32
|
-
def add_instance_methods(
|
33
|
-
add_constructor(
|
34
|
-
add_accessors(
|
35
|
-
add_equality_methods(
|
36
|
-
add_inspect_method(
|
30
|
+
def add_instance_methods(attributes)
|
31
|
+
add_constructor(attributes)
|
32
|
+
add_accessors(attributes.keys)
|
33
|
+
add_equality_methods(attributes.keys)
|
34
|
+
add_inspect_method(attributes.keys)
|
37
35
|
end
|
38
36
|
|
39
|
-
def add_constructor(
|
40
|
-
define_method "initialize" do |
|
41
|
-
initialize_attributes(
|
37
|
+
def add_constructor(attributes)
|
38
|
+
define_method "initialize" do |values = {}|
|
39
|
+
initialize_attributes(values)
|
42
40
|
end
|
43
41
|
|
44
|
-
define_method "initialize_attributes" do |
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@attributes = predefined_attributes.merge(attributes)
|
42
|
+
define_method "initialize_attributes" do |values = {}|
|
43
|
+
unknown_keys = values.keys - attributes.keys
|
44
|
+
fail KeyError, "Unknown attributes: #{(unknown_keys).join(", ")}" unless unknown_keys.empty?
|
45
|
+
|
46
|
+
@attributes = attributes.merge(values)
|
50
47
|
end
|
51
48
|
end
|
52
49
|
|
data/lib/attributable/version.rb
CHANGED
@@ -7,7 +7,7 @@ end
|
|
7
7
|
|
8
8
|
describe Attributable do
|
9
9
|
describe "accessors" do
|
10
|
-
it "should
|
10
|
+
it "should not have an accessor for an unknown attribute" do
|
11
11
|
i = User.new(id: 1, forename: "John", surname: "Doe")
|
12
12
|
expect(i.respond_to?(:address)).to be_false
|
13
13
|
end
|
@@ -5,17 +5,6 @@ class User
|
|
5
5
|
attributes :id, :forename, surname: "Bloggs"
|
6
6
|
end
|
7
7
|
|
8
|
-
class UserWithDerivedAttribute
|
9
|
-
extend Attributable
|
10
|
-
attributes :id, :forename, surname: "Bloggs"
|
11
|
-
attr_reader :fullname
|
12
|
-
|
13
|
-
def initialize(attributes = {})
|
14
|
-
initialize_attributes(attributes)
|
15
|
-
@fullname = "#{forename} #{surname}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
8
|
describe Attributable do
|
20
9
|
describe "construction" do
|
21
10
|
it "should accept a hash" do
|
@@ -41,10 +30,27 @@ describe Attributable do
|
|
41
30
|
expect(i.forename).to be_nil
|
42
31
|
expect(i.surname).to eq("Doe")
|
43
32
|
end
|
33
|
+
|
34
|
+
it "should raise error when values for unknown attributes are specified" do
|
35
|
+
expect { User.new(password: "secret", admin: true, surname: "Doe") }.to(
|
36
|
+
raise_error(KeyError, "Unknown attributes: password, admin")
|
37
|
+
)
|
38
|
+
end
|
44
39
|
end
|
45
40
|
|
46
41
|
describe "constructor" do
|
47
42
|
it "should be overridable" do
|
43
|
+
class UserWithDerivedAttribute
|
44
|
+
extend Attributable
|
45
|
+
attributes :id, :forename, surname: "Bloggs"
|
46
|
+
attr_reader :fullname
|
47
|
+
|
48
|
+
def initialize(attributes = {})
|
49
|
+
initialize_attributes(attributes)
|
50
|
+
@fullname = "#{forename} #{surname}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
48
54
|
i = UserWithDerivedAttribute.new(id: 1, forename: "John", surname: "Doe")
|
49
55
|
|
50
56
|
expect(i.id).to eq(1)
|
@@ -1,38 +1,35 @@
|
|
1
1
|
require "attributable"
|
2
2
|
|
3
|
-
class User
|
4
|
-
extend Attributable
|
5
|
-
attributes :id, :forename, surname: "Bloggs"
|
6
|
-
end
|
7
|
-
|
8
3
|
describe Attributable do
|
9
|
-
describe "specialisation" do
|
10
|
-
it "should
|
11
|
-
class
|
4
|
+
describe "prerequisites for specialisation" do
|
5
|
+
it "should be possible to call attributes more than once" do
|
6
|
+
class Person
|
12
7
|
extend Attributable
|
13
|
-
|
14
|
-
attributes :
|
15
|
-
specialises User
|
8
|
+
attributes :forename, surname: "Bloggs"
|
9
|
+
attributes :id, active: true
|
16
10
|
end
|
17
11
|
|
18
|
-
|
12
|
+
p = Person.new(id: 1, forename: "Bob")
|
19
13
|
|
20
|
-
expect(
|
21
|
-
expect(
|
22
|
-
expect(
|
23
|
-
expect(
|
24
|
-
expect(s.active).to be_false
|
14
|
+
expect(p.id).to eq(1)
|
15
|
+
expect(p.forename).to eq("Bob")
|
16
|
+
expect(p.surname).to eq("Bloggs")
|
17
|
+
expect(p.active).to be_true
|
25
18
|
end
|
19
|
+
end
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
|
21
|
+
describe "inheritance" do
|
22
|
+
class User
|
23
|
+
extend Attributable
|
24
|
+
attributes :id, :forename, surname: "Bloggs"
|
25
|
+
end
|
30
26
|
|
31
|
-
|
27
|
+
it "should inherit attributes from superclass" do
|
28
|
+
class SuperUser < User
|
32
29
|
attributes :password, active: true
|
33
30
|
end
|
34
31
|
|
35
|
-
s =
|
32
|
+
s = SuperUser.new(id: 1, forename: "Bob", password: "secret", active: false)
|
36
33
|
|
37
34
|
expect(s.id).to eq(1)
|
38
35
|
expect(s.forename).to eq("Bob")
|
@@ -41,84 +38,137 @@ describe Attributable do
|
|
41
38
|
expect(s.active).to be_false
|
42
39
|
end
|
43
40
|
|
44
|
-
it "should ensure that
|
45
|
-
class
|
46
|
-
extend Attributable
|
47
|
-
|
48
|
-
specialises User
|
41
|
+
it "should ensure that subclass's attributes precede superclass's" do
|
42
|
+
class SuperUser2 < User
|
49
43
|
attributes surname: "Smith"
|
50
44
|
end
|
51
45
|
|
52
|
-
s =
|
46
|
+
s = SuperUser2.new
|
53
47
|
|
54
48
|
expect(s.surname).to eq("Smith")
|
55
49
|
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "mixins" do
|
53
|
+
module Author
|
54
|
+
extend Attributable
|
55
|
+
attributes articles: []
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should inherit attributes from mixed-in module" do
|
59
|
+
class Columnist
|
60
|
+
include Author
|
56
61
|
|
57
|
-
it "should ensure that position of specialises does not affect precedence" do
|
58
|
-
class SuperUser4
|
59
62
|
extend Attributable
|
63
|
+
attributes :column
|
64
|
+
end
|
60
65
|
|
61
|
-
|
62
|
-
|
66
|
+
c = Columnist.new(column: "Agony Aunt", articles: [:teen_advice])
|
67
|
+
|
68
|
+
expect(c.column).to eq("Agony Aunt")
|
69
|
+
expect(c.articles).to eq([:teen_advice])
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should inherit defaults from mixed-in module" do
|
73
|
+
class Columnist2
|
74
|
+
include Author
|
75
|
+
|
76
|
+
extend Attributable
|
77
|
+
attributes :column
|
63
78
|
end
|
64
79
|
|
65
|
-
|
80
|
+
c = Columnist2.new
|
66
81
|
|
67
|
-
expect(
|
82
|
+
expect(c.articles).to eq([])
|
68
83
|
end
|
69
84
|
|
70
|
-
it "should
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
|
85
|
+
it "should ensure that includee's attributes precede included's" do
|
86
|
+
class Columnist3
|
87
|
+
include Author
|
88
|
+
|
89
|
+
extend Attributable
|
90
|
+
attributes articles: [:daily_column]
|
91
|
+
end
|
92
|
+
|
93
|
+
c = Columnist3.new
|
94
|
+
|
95
|
+
expect(c.articles).to eq([:daily_column])
|
80
96
|
end
|
81
|
-
end
|
82
97
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
attributes :
|
98
|
+
xit "should work when include is last" do
|
99
|
+
class Columnist4
|
100
|
+
extend Attributable
|
101
|
+
attributes :column
|
102
|
+
include Author
|
87
103
|
end
|
88
104
|
|
89
|
-
|
105
|
+
c = Columnist4.new(column: "Agony Aunt", articles: [:teen_advice])
|
90
106
|
|
91
|
-
expect(
|
92
|
-
expect(
|
93
|
-
expect(s.surname).to eq("Bloggs")
|
94
|
-
expect(s.password).to eq("secret")
|
95
|
-
expect(s.active).to be_false
|
107
|
+
expect(c.articles).to eq([:teen_advice])
|
108
|
+
expect(c).to eq(Columnist4.new(column: "Agony Aunt", articles: [:teen_advice]))
|
96
109
|
end
|
97
110
|
|
98
|
-
it "should
|
99
|
-
class
|
100
|
-
|
101
|
-
|
102
|
-
|
111
|
+
it "should work when include is in the middle" do
|
112
|
+
class Columnist5
|
113
|
+
extend Attributable
|
114
|
+
include Author
|
115
|
+
attributes :column
|
103
116
|
end
|
104
117
|
|
105
|
-
|
118
|
+
c = Columnist5.new(column: "Agony Aunt", articles: [:teen_advice])
|
106
119
|
|
107
|
-
expect(
|
108
|
-
expect(
|
120
|
+
expect(c.articles).to eq([:teen_advice])
|
121
|
+
expect(c).to eq(Columnist5.new(column: "Agony Aunt", articles: [:teen_advice]))
|
109
122
|
end
|
110
123
|
|
111
|
-
it "
|
112
|
-
|
113
|
-
|
124
|
+
it "should work with several includes" do
|
125
|
+
module Editor
|
126
|
+
extend Attributable
|
127
|
+
attributes :section
|
114
128
|
end
|
115
129
|
|
116
|
-
class
|
130
|
+
class AuthorAndEditor
|
131
|
+
include Author, Editor
|
117
132
|
extend Attributable
|
118
|
-
attributes :
|
133
|
+
attributes :name
|
119
134
|
end
|
120
135
|
|
121
|
-
|
136
|
+
ae = AuthorAndEditor.new(name: "Joe Bloggs", section: "Sport")
|
137
|
+
|
138
|
+
expect(ae.name).to eq("Joe Bloggs")
|
139
|
+
expect(ae.section).to eq("Sport")
|
140
|
+
expect(ae.articles).to eq([])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "inheritance and mixins" do
|
145
|
+
class Blogger < User
|
146
|
+
include Author
|
147
|
+
extend Attributable
|
148
|
+
attributes :blog_name
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should inherit attributes from both" do
|
152
|
+
b = Blogger.new(
|
153
|
+
id: 1,
|
154
|
+
forename: "John",
|
155
|
+
surname: "Doe",
|
156
|
+
articles: [:daily_column],
|
157
|
+
blog_name: "Research Rants"
|
158
|
+
)
|
159
|
+
|
160
|
+
expect(b.id).to eq(1)
|
161
|
+
expect(b.forename).to eq("John")
|
162
|
+
expect(b.surname).to eq("Doe")
|
163
|
+
expect(b.articles).to eq([:daily_column])
|
164
|
+
expect(b.blog_name).to eq("Research Rants")
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should inherit defaults from both" do
|
168
|
+
b = Blogger.new
|
169
|
+
|
170
|
+
expect(b.surname).to eq("Bloggs")
|
171
|
+
expect(b.articles).to eq([])
|
122
172
|
end
|
123
173
|
end
|
124
174
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attributable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Louis Rose
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|