attributable 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c22dc4806f8e50155cb39df375de2e883edfb086
4
- data.tar.gz: 5edda9c0de87966920404d14b07a8ca78e395657
3
+ metadata.gz: ff3f791c159b7f77582d20aea0a0a2a608bf0089
4
+ data.tar.gz: 73fbc9b201af71fc14149c134ea87e55d88f01e7
5
5
  SHA512:
6
- metadata.gz: b50d6942f2276f8571991bb33470191cceb7a215bc78d0e5318466249bba357c443a3e244db843b09ae8a582fdade35e03492666f29eecd1eccc43ebb4f02e77
7
- data.tar.gz: ee3fad3f3cffafc5fe94d0ef27c56c590e06cbbcc8708f73aa51fe1e0a429b2179b471a3cea5259df251428d45003265476c2e13abc800f5019f477e6be724c0
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
- ## Specialisation
101
+ ## Reuse via inheritance and mix-ins
102
102
 
103
- To allow reuse of attribute declarations, Attributable provides the `specialises` class method.
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
 
@@ -2,24 +2,22 @@ require "attributable/version"
2
2
 
3
3
  module Attributable
4
4
  def attributes(*without_defaults, **with_defaults)
5
- @predefined_attributes ||= {}
6
- @predefined_attributes = @predefined_attributes.merge(from(without_defaults, with_defaults))
7
- add_instance_methods(@predefined_attributes)
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
- def specialises(clazz)
11
- unless clazz.kind_of? Attributable
12
- fail ArgumentError, "specialisation requires a class that extends Attributable"
13
- end
12
+ private
14
13
 
15
- super_attributes = clazz.new.instance_variable_get(:@attributes)
16
- @predefined_attributes ||= {}
17
- @predefined_attributes = super_attributes.merge(@predefined_attributes)
18
- add_instance_methods(@predefined_attributes)
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(predefined_attributes)
33
- add_constructor(predefined_attributes)
34
- add_accessors(predefined_attributes.keys)
35
- add_equality_methods(predefined_attributes.keys)
36
- add_inspect_method(predefined_attributes.keys)
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(predefined_attributes)
40
- define_method "initialize" do |attributes = {}|
41
- initialize_attributes(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 |attributes = {}|
45
- if self.class.superclass.kind_of? Attributable
46
- super_attributes = self.class.superclass.new.instance_variable_get(:@attributes)
47
- predefined_attributes = super_attributes.merge(predefined_attributes)
48
- end
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
 
@@ -1,3 +1,3 @@
1
1
  module Attributable
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -7,7 +7,7 @@ end
7
7
 
8
8
  describe Attributable do
9
9
  describe "accessors" do
10
- it "should raise for an unknown attribute" do
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 pull in attributes from specified class" do
11
- class SuperUser
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 :password, active: true
15
- specialises User
8
+ attributes :forename, surname: "Bloggs"
9
+ attributes :id, active: true
16
10
  end
17
11
 
18
- s = SuperUser.new(id: 1, forename: "Bob", password: "secret", active: false)
12
+ p = Person.new(id: 1, forename: "Bob")
19
13
 
20
- expect(s.id).to eq(1)
21
- expect(s.forename).to eq("Bob")
22
- expect(s.surname).to eq("Bloggs")
23
- expect(s.password).to eq("secret")
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
- it "should be possible to declare specialisation before attributes" do
28
- class SuperUser2
29
- extend Attributable
21
+ describe "inheritance" do
22
+ class User
23
+ extend Attributable
24
+ attributes :id, :forename, surname: "Bloggs"
25
+ end
30
26
 
31
- specialises User
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 = SuperUser2.new(id: 1, forename: "Bob", password: "secret", active: false)
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 local attributes precede specialised attributes" do
45
- class SuperUser3
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 = SuperUser3.new
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
- attributes surname: "Smith"
62
- specialises User
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
- s = SuperUser4.new
80
+ c = Columnist2.new
66
81
 
67
- expect(s.surname).to eq("Smith")
82
+ expect(c.articles).to eq([])
68
83
  end
69
84
 
70
- it "should raise if specialising class is not an instance of Attributable" do
71
- expect do
72
- class SuperUser5
73
- extend Attributable
74
- specialises String
75
- end
76
- end.to raise_error(
77
- ArgumentError,
78
- "specialisation requires a class that extends Attributable"
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
- describe "automatic specialisation" do
84
- it "should automatically specialise if superclass is an instance of Attributable" do
85
- class SuperUser6 < User
86
- attributes :password, active: true
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
- s = SuperUser6.new(id: 1, forename: "Bob", password: "secret", active: false)
105
+ c = Columnist4.new(column: "Agony Aunt", articles: [:teen_advice])
90
106
 
91
- expect(s.id).to eq(1)
92
- expect(s.forename).to eq("Bob")
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 not override any custom methods" do
99
- class SuperUser7 < User
100
- def inspect
101
- "SUPERUSER"
102
- end
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
- s = SuperUser7.new(id: 1)
118
+ c = Columnist5.new(column: "Agony Aunt", articles: [:teen_advice])
106
119
 
107
- expect(s.id).to eq(1)
108
- expect(s.inspect).to eq("SUPERUSER")
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 "shouldn't automatically specialise unless superclass is an instance of Attributable" do
112
- class PORO
113
- attr_accessor :name
124
+ it "should work with several includes" do
125
+ module Editor
126
+ extend Attributable
127
+ attributes :section
114
128
  end
115
129
 
116
- class SuperUser8 < PORO
130
+ class AuthorAndEditor
131
+ include Author, Editor
117
132
  extend Attributable
118
- attributes :forename, :surname
133
+ attributes :name
119
134
  end
120
135
 
121
- expect { SuperUser8.new }.to_not raise_error
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
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-10 00:00:00.000000000 Z
11
+ date: 2014-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler