toystore 0.13.0 → 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog.md +12 -0
- data/Guardfile +2 -2
- data/README.md +26 -35
- data/examples/attributes_abbreviation.rb +2 -2
- data/examples/attributes_virtual.rb +2 -2
- data/examples/identity_map.rb +2 -2
- data/examples/memcached.rb +2 -2
- data/examples/memory.rb +2 -2
- data/examples/mongo.rb +2 -2
- data/examples/namespacing_keys.rb +1 -1
- data/examples/plain_old_object.rb +2 -2
- data/examples/plain_old_object_on_roids.rb +13 -22
- data/examples/redis.rb +2 -2
- data/examples/riak.rb +2 -2
- data/lib/toy.rb +4 -0
- data/lib/toy/attributes.rb +10 -21
- data/lib/toy/cloneable.rb +1 -3
- data/lib/toy/dirty.rb +0 -6
- data/lib/toy/equality.rb +3 -14
- data/lib/toy/extensions/symbol.rb +17 -0
- data/lib/toy/extensions/uuid.rb +6 -2
- data/lib/toy/identity.rb +39 -2
- data/lib/toy/inspect.rb +3 -4
- data/lib/toy/object.rb +1 -5
- data/lib/toy/persistence.rb +34 -5
- data/lib/toy/querying.rb +9 -9
- data/lib/toy/reloadable.rb +3 -3
- data/lib/toy/store.rb +1 -0
- data/lib/toy/types/json.rb +20 -0
- data/lib/toy/version.rb +1 -1
- data/spec/helper.rb +1 -0
- data/spec/toy/attributes_spec.rb +21 -59
- data/spec/toy/cloneable_spec.rb +1 -8
- data/spec/toy/equality_spec.rb +18 -19
- data/spec/toy/extensions/symbol_spec.rb +26 -0
- data/spec/toy/extensions/uuid_spec.rb +31 -11
- data/spec/toy/identity/uuid_key_factory_spec.rb +4 -4
- data/spec/toy/identity_spec.rb +111 -5
- data/spec/toy/inheritance_spec.rb +4 -4
- data/spec/toy/inspect_spec.rb +3 -3
- data/spec/toy/object_spec.rb +0 -40
- data/spec/toy/persistence_spec.rb +99 -1
- data/spec/toy/reloadable_spec.rb +9 -4
- data/spec/toy/serialization_spec.rb +16 -21
- data/spec/toy/types/json_spec.rb +37 -0
- data/spec/toy/validations_spec.rb +13 -0
- metadata +10 -4
data/Changelog.md
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
I will do my best to keep this up to date with significant changes here, starting in 0.8.3.
|
4
4
|
|
5
|
+
## master
|
6
|
+
|
7
|
+
* No longer defaulting uuid to new instance, use :default instead. This allows for nil values for attributes of uuid type.
|
8
|
+
|
9
|
+
## 0.14.0
|
10
|
+
|
11
|
+
* No longer persisting nil attributes
|
12
|
+
* Added Toy::Types::JSON for storing serialized JSON as an attribute value
|
13
|
+
* Added #persisted_id and made it public so people can override confidently. It is now used in adapter.write and adapter.delete.
|
14
|
+
* Made #persist method public so people can override confidently.
|
15
|
+
* Moved Identity to Toy::Store from Toy::Object
|
16
|
+
|
5
17
|
## 0.13.0
|
6
18
|
|
7
19
|
* Update to adapter 0.7.0
|
data/Guardfile
CHANGED
@@ -4,8 +4,8 @@ rspec_options = {
|
|
4
4
|
|
5
5
|
guard 'rspec', rspec_options do
|
6
6
|
watch(%r{^spec/.+_spec\.rb$})
|
7
|
-
watch(%r{^lib/(.+)\.rb$})
|
8
|
-
watch('spec/helper.rb')
|
7
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
8
|
+
watch('spec/helper.rb') { "spec" }
|
9
9
|
end
|
10
10
|
|
11
11
|
guard 'bundler' do
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ The project comes with two main includes that you can use -- Toy::Object and Toy
|
|
8
8
|
|
9
9
|
**Toy::Object** comes with all the goods you need for plain old ruby objects -- attributes, dirty attribute tracking, equality, inheritance, serialization, cloning, logging and pretty inspecting.
|
10
10
|
|
11
|
-
**Toy::Store** includes Toy::Object and adds persistence through adapters, mass assignment,
|
11
|
+
**Toy::Store** includes Toy::Object and adds identity, persistence and querying through adapters, mass assignment, callbacks, validations and a few simple associations (lists and references).
|
12
12
|
|
13
13
|
### Toy::Object
|
14
14
|
|
@@ -23,34 +23,34 @@ class Person
|
|
23
23
|
end
|
24
24
|
|
25
25
|
# Pretty class inspecting
|
26
|
-
|
26
|
+
pp Person
|
27
27
|
|
28
|
-
john
|
28
|
+
john = Person.new(:name => 'John', :age => 30)
|
29
29
|
steve = Person.new(:name => 'Steve', :age => 31)
|
30
30
|
|
31
31
|
# Pretty inspecting
|
32
|
-
|
32
|
+
pp john
|
33
33
|
|
34
34
|
# Attribute dirty tracking
|
35
35
|
john.name = 'NEW NAME!'
|
36
|
-
|
37
|
-
|
36
|
+
pp john.changes # {"name"=>["John", "NEW NAME!"], "age"=>[nil, 30]}
|
37
|
+
pp john.name_changed? # true
|
38
38
|
|
39
39
|
# Equality goodies
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
pp john.eql?(john) # true
|
41
|
+
pp john.eql?(steve) # false
|
42
|
+
pp john == john # true
|
43
|
+
pp john == steve # false
|
44
44
|
|
45
45
|
# Cloning
|
46
|
-
|
46
|
+
pp john.clone
|
47
47
|
|
48
48
|
# Inheritance
|
49
49
|
class AwesomePerson < Person
|
50
50
|
end
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
pp Person.attributes.keys.sort # ["age", "name"]
|
53
|
+
pp AwesomePerson.attributes.keys.sort # ["age", "name", "type"]
|
54
54
|
|
55
55
|
# Serialization
|
56
56
|
puts john.to_json
|
@@ -84,13 +84,12 @@ person = Person.new(:name => 'Hacker', :age => 13, :role => 'admin')
|
|
84
84
|
pp person.role # "guest"
|
85
85
|
|
86
86
|
# Querying
|
87
|
-
pp Person.
|
88
|
-
pp Person.
|
89
|
-
pp Person.
|
90
|
-
pp Person.get_or_new('NOT HERE') # new person with id of 'NOT HERE'
|
87
|
+
pp Person.read(john.id)
|
88
|
+
pp Person.read_multiple([john.id])
|
89
|
+
pp Person.read('NOT HERE') # nil
|
91
90
|
|
92
91
|
begin
|
93
|
-
Person.
|
92
|
+
Person.read!('NOT HERE')
|
94
93
|
rescue Toy::NotFound
|
95
94
|
puts "Could not find person with id of 'NOT HERE'"
|
96
95
|
end
|
@@ -100,15 +99,13 @@ pp john.reload
|
|
100
99
|
|
101
100
|
# Callbacks
|
102
101
|
class Person
|
102
|
+
before_create :add_fifty_to_age
|
103
|
+
|
103
104
|
def add_fifty_to_age
|
104
105
|
self.age += 50
|
105
106
|
end
|
106
107
|
end
|
107
108
|
|
108
|
-
class Person
|
109
|
-
before_create :add_fifty_to_age
|
110
|
-
end
|
111
|
-
|
112
109
|
pp Person.create(:age => 10).age # 60
|
113
110
|
|
114
111
|
# Validations
|
@@ -151,16 +148,16 @@ pp john.reload.mom_id == mom.id # true
|
|
151
148
|
Toy::IdentityMap.use do
|
152
149
|
frank = Person.create(:name => 'Frank')
|
153
150
|
|
154
|
-
pp Person.
|
155
|
-
pp Person.
|
151
|
+
pp Person.read(frank.id).equal?(frank) # true
|
152
|
+
pp Person.read(frank.id).object_id == frank.object_id # true
|
156
153
|
end
|
157
154
|
|
158
155
|
# Or you can turn it on globally
|
159
156
|
Toy::IdentityMap.enabled = true
|
160
157
|
frank = Person.create(:name => 'Frank')
|
161
158
|
|
162
|
-
pp Person.
|
163
|
-
pp Person.
|
159
|
+
pp Person.read(frank.id).equal?(frank) # true
|
160
|
+
pp Person.read(frank.id).object_id == frank.object_id # true
|
164
161
|
|
165
162
|
# All persistence runs through an adapter.
|
166
163
|
# All of the above examples used the default in-memory adapter.
|
@@ -170,25 +167,19 @@ Person.adapter :memory, {}
|
|
170
167
|
puts "Adapter: #{Person.adapter.inspect}"
|
171
168
|
|
172
169
|
# You can make a new adapter to your awesome new/old data store
|
173
|
-
# Always use #key_for, #encode, and #decode. Feel free to override
|
174
|
-
# them if you like, but always use them. Default encode/decode is
|
175
|
-
# most likely marshaling, but you can use anything.
|
176
170
|
Adapter.define(:append_only_array) do
|
177
171
|
def read(key)
|
178
|
-
if (record = client.reverse.detect { |row| row[0] ==
|
179
|
-
|
172
|
+
if (record = client.reverse.detect { |row| row[0] == key })
|
173
|
+
record
|
180
174
|
end
|
181
175
|
end
|
182
176
|
|
183
177
|
def write(key, value)
|
184
|
-
key = key_for(key)
|
185
|
-
value = encode(value)
|
186
178
|
client << [key, value]
|
187
179
|
value
|
188
180
|
end
|
189
181
|
|
190
182
|
def delete(key)
|
191
|
-
key = key_for(key)
|
192
183
|
client.delete_if { |row| row[0] == key }
|
193
184
|
end
|
194
185
|
|
@@ -208,7 +199,7 @@ person.save
|
|
208
199
|
|
209
200
|
pp client
|
210
201
|
|
211
|
-
pp Person.
|
202
|
+
pp Person.read(person.id) # Phil with age 56
|
212
203
|
```
|
213
204
|
|
214
205
|
If that doesn't excite you, nothing will. At this point, you are probably wishing for more.
|
@@ -20,6 +20,6 @@ user = User.create({
|
|
20
20
|
:my_really_long_field_name => 'something',
|
21
21
|
})
|
22
22
|
|
23
|
-
pp
|
23
|
+
pp User.adapter.client[user.id]
|
24
24
|
# Abbreviated attributes are stored in the database as the abbreviation for when you want to conserve space. The abbreviation and the full attribute name work exactly the same in Ruby, the only difference is how they get persisted.
|
25
|
-
# {"my"=>"something", "email"=>"nunemaker@gmail.com"}
|
25
|
+
# {"my"=>"something", "email"=>"nunemaker@gmail.com"}
|
@@ -35,6 +35,6 @@ user = User.create({
|
|
35
35
|
:password_confirmation => 'testing',
|
36
36
|
})
|
37
37
|
|
38
|
-
pp
|
38
|
+
pp User.adapter.client[user.id]
|
39
39
|
# Virtual attributes are never persisted. In the data store, only email and crypted_password are stored.
|
40
|
-
# {"crypted_password"=>"testing", "email"=>"nunemaker@gmail.com"}
|
40
|
+
# {"crypted_password"=>"testing", "email"=>"nunemaker@gmail.com"}
|
data/examples/identity_map.rb
CHANGED
@@ -28,12 +28,12 @@ user = User.create(:name => 'John')
|
|
28
28
|
# {"name"=>"John"}
|
29
29
|
# ToyStore IMS User "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
30
30
|
|
31
|
-
puts "With identity map - User.
|
31
|
+
puts "With identity map - User.read(user.id).equal?(user): #{User.read(user.id).equal?(user)}"
|
32
32
|
# User is retrieved from identity map instead of querying
|
33
33
|
# ToyStore IMG User "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
34
34
|
|
35
35
|
Toy::IdentityMap.without do
|
36
|
-
puts "Without identity map - User.
|
36
|
+
puts "Without identity map - User.read(user.id).equal?(user): #{User.read(user.id).equal?(user)}"
|
37
37
|
# User is queried from database
|
38
38
|
# ToyStore GET User :memory "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
39
39
|
# {"name"=>"John"}
|
data/examples/memcached.rb
CHANGED
data/examples/memory.rb
CHANGED
data/examples/mongo.rb
CHANGED
@@ -46,8 +46,8 @@ pp john.clone
|
|
46
46
|
class AwesomePerson < Person
|
47
47
|
end
|
48
48
|
|
49
|
-
pp Person.attributes.keys.sort # ["age", "
|
50
|
-
pp AwesomePerson.attributes.keys.sort # ["age", "
|
49
|
+
pp Person.attributes.keys.sort # ["age", "name"]
|
50
|
+
pp AwesomePerson.attributes.keys.sort # ["age", "name", "type"]
|
51
51
|
|
52
52
|
# Serialization
|
53
53
|
puts john.to_json
|
@@ -33,13 +33,12 @@ person = Person.new(:name => 'Hacker', :age => 13, :role => 'admin')
|
|
33
33
|
pp person.role # "guest"
|
34
34
|
|
35
35
|
# Querying
|
36
|
-
pp Person.
|
37
|
-
pp Person.
|
38
|
-
pp Person.
|
39
|
-
pp Person.get_or_new('NOT HERE') # new person with id of 'NOT HERE'
|
36
|
+
pp Person.read(john.id)
|
37
|
+
pp Person.read_multiple([john.id])
|
38
|
+
pp Person.read('NOT HERE') # nil
|
40
39
|
|
41
40
|
begin
|
42
|
-
Person.
|
41
|
+
Person.read!('NOT HERE')
|
43
42
|
rescue Toy::NotFound
|
44
43
|
puts "Could not find person with id of 'NOT HERE'"
|
45
44
|
end
|
@@ -49,15 +48,13 @@ pp john.reload
|
|
49
48
|
|
50
49
|
# Callbacks
|
51
50
|
class Person
|
51
|
+
before_create :add_fifty_to_age
|
52
|
+
|
52
53
|
def add_fifty_to_age
|
53
54
|
self.age += 50
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
57
|
-
class Person
|
58
|
-
before_create :add_fifty_to_age
|
59
|
-
end
|
60
|
-
|
61
58
|
pp Person.create(:age => 10).age # 60
|
62
59
|
|
63
60
|
# Validations
|
@@ -100,16 +97,16 @@ pp john.reload.mom_id == mom.id # true
|
|
100
97
|
Toy::IdentityMap.use do
|
101
98
|
frank = Person.create(:name => 'Frank')
|
102
99
|
|
103
|
-
pp Person.
|
104
|
-
pp Person.
|
100
|
+
pp Person.read(frank.id).equal?(frank) # true
|
101
|
+
pp Person.read(frank.id).object_id == frank.object_id # true
|
105
102
|
end
|
106
103
|
|
107
104
|
# Or you can turn it on globally
|
108
105
|
Toy::IdentityMap.enabled = true
|
109
106
|
frank = Person.create(:name => 'Frank')
|
110
107
|
|
111
|
-
pp Person.
|
112
|
-
pp Person.
|
108
|
+
pp Person.read(frank.id).equal?(frank) # true
|
109
|
+
pp Person.read(frank.id).object_id == frank.object_id # true
|
113
110
|
|
114
111
|
# All persistence runs through an adapter.
|
115
112
|
# All of the above examples used the default in-memory adapter.
|
@@ -119,25 +116,19 @@ Person.adapter :memory, {}
|
|
119
116
|
puts "Adapter: #{Person.adapter.inspect}"
|
120
117
|
|
121
118
|
# You can make a new adapter to your awesome new/old data store
|
122
|
-
# Always use #key_for, #encode, and #decode. Feel free to override
|
123
|
-
# them if you like, but always use them. Default encode/decode is
|
124
|
-
# most likely marshaling, but you can use anything.
|
125
119
|
Adapter.define(:append_only_array) do
|
126
120
|
def read(key)
|
127
|
-
if (record = client.reverse.detect { |row| row[0] ==
|
128
|
-
|
121
|
+
if (record = client.reverse.detect { |row| row[0] == key })
|
122
|
+
record
|
129
123
|
end
|
130
124
|
end
|
131
125
|
|
132
126
|
def write(key, value)
|
133
|
-
key = key_for(key)
|
134
|
-
value = encode(value)
|
135
127
|
client << [key, value]
|
136
128
|
value
|
137
129
|
end
|
138
130
|
|
139
131
|
def delete(key)
|
140
|
-
key = key_for(key)
|
141
132
|
client.delete_if { |row| row[0] == key }
|
142
133
|
end
|
143
134
|
|
@@ -157,4 +148,4 @@ person.save
|
|
157
148
|
|
158
149
|
pp client
|
159
150
|
|
160
|
-
pp Person.
|
151
|
+
pp Person.read(person.id) # Phil with age 56
|
data/examples/redis.rb
CHANGED
data/examples/riak.rb
CHANGED
data/lib/toy.rb
CHANGED
@@ -68,6 +68,10 @@ module Toy
|
|
68
68
|
autoload 'References', 'toy/references'
|
69
69
|
autoload 'Identity', 'toy/identity'
|
70
70
|
|
71
|
+
module Types
|
72
|
+
autoload 'JSON', 'toy/types/json'
|
73
|
+
end
|
74
|
+
|
71
75
|
module Identity
|
72
76
|
autoload 'AbstractKeyFactory', 'toy/identity/abstract_key_factory'
|
73
77
|
autoload 'UUIDKeyFactory', 'toy/identity/uuid_key_factory'
|
data/lib/toy/attributes.rb
CHANGED
@@ -4,8 +4,6 @@ module Toy
|
|
4
4
|
include ActiveModel::AttributeMethods
|
5
5
|
|
6
6
|
included do
|
7
|
-
include Identity
|
8
|
-
|
9
7
|
# blank suffix is no longer needed in 3.2+
|
10
8
|
# open to suggestions on how to do this better
|
11
9
|
if ActiveSupport::VERSION::MAJOR == 3 && ActiveSupport::VERSION::MINOR < 2
|
@@ -22,10 +20,11 @@ module Toy
|
|
22
20
|
end
|
23
21
|
|
24
22
|
def defaulted_attributes
|
25
|
-
attributes.values.select(&:default?)
|
23
|
+
@defaulted_attributes ||= attributes.values.select(&:default?)
|
26
24
|
end
|
27
25
|
|
28
26
|
def attribute(key, type, options = {})
|
27
|
+
@defaulted_attributes = nil
|
29
28
|
attribute = Attribute.new(self, key, type, options)
|
30
29
|
define_attribute_methods [attribute.name]
|
31
30
|
attribute
|
@@ -39,26 +38,12 @@ module Toy
|
|
39
38
|
def initialize(attrs={})
|
40
39
|
initialize_attributes
|
41
40
|
self.attributes = attrs
|
42
|
-
write_attribute :id, self.class.next_key(self) unless id?
|
43
|
-
end
|
44
|
-
|
45
|
-
def id
|
46
|
-
read_attribute(:id)
|
47
41
|
end
|
48
42
|
|
49
43
|
def attributes
|
50
44
|
@attributes
|
51
45
|
end
|
52
46
|
|
53
|
-
def persisted_attributes
|
54
|
-
{}.tap do |attrs|
|
55
|
-
self.class.attributes.except('id').each do |name, attribute|
|
56
|
-
next if attribute.virtual?
|
57
|
-
attrs[attribute.persisted_name] = attribute.to_store(read_attribute(attribute.name))
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
47
|
def attributes=(attrs, *)
|
63
48
|
return if attrs.nil?
|
64
49
|
attrs.each do |key, value|
|
@@ -86,10 +71,8 @@ module Toy
|
|
86
71
|
|
87
72
|
def write_attribute(key, value)
|
88
73
|
key = key.to_s
|
89
|
-
attribute =
|
90
|
-
|
91
|
-
}
|
92
|
-
@attributes[key.to_s] = attribute.from_store(value)
|
74
|
+
attribute = attribute_instance(key)
|
75
|
+
@attributes[key] = attribute.from_store(value)
|
93
76
|
end
|
94
77
|
|
95
78
|
def attribute_method?(key)
|
@@ -108,6 +91,12 @@ module Toy
|
|
108
91
|
read_attribute(key).present?
|
109
92
|
end
|
110
93
|
|
94
|
+
def attribute_instance(key)
|
95
|
+
self.class.attributes.fetch(key) {
|
96
|
+
raise AttributeNotDefined, "#{self.class} does not have attribute #{key}"
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
111
100
|
def initialize_attributes
|
112
101
|
@attributes ||= {}
|
113
102
|
self.class.defaulted_attributes.each do |attribute|
|