toystore 0.13.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Changelog.md +12 -0
  2. data/Guardfile +2 -2
  3. data/README.md +26 -35
  4. data/examples/attributes_abbreviation.rb +2 -2
  5. data/examples/attributes_virtual.rb +2 -2
  6. data/examples/identity_map.rb +2 -2
  7. data/examples/memcached.rb +2 -2
  8. data/examples/memory.rb +2 -2
  9. data/examples/mongo.rb +2 -2
  10. data/examples/namespacing_keys.rb +1 -1
  11. data/examples/plain_old_object.rb +2 -2
  12. data/examples/plain_old_object_on_roids.rb +13 -22
  13. data/examples/redis.rb +2 -2
  14. data/examples/riak.rb +2 -2
  15. data/lib/toy.rb +4 -0
  16. data/lib/toy/attributes.rb +10 -21
  17. data/lib/toy/cloneable.rb +1 -3
  18. data/lib/toy/dirty.rb +0 -6
  19. data/lib/toy/equality.rb +3 -14
  20. data/lib/toy/extensions/symbol.rb +17 -0
  21. data/lib/toy/extensions/uuid.rb +6 -2
  22. data/lib/toy/identity.rb +39 -2
  23. data/lib/toy/inspect.rb +3 -4
  24. data/lib/toy/object.rb +1 -5
  25. data/lib/toy/persistence.rb +34 -5
  26. data/lib/toy/querying.rb +9 -9
  27. data/lib/toy/reloadable.rb +3 -3
  28. data/lib/toy/store.rb +1 -0
  29. data/lib/toy/types/json.rb +20 -0
  30. data/lib/toy/version.rb +1 -1
  31. data/spec/helper.rb +1 -0
  32. data/spec/toy/attributes_spec.rb +21 -59
  33. data/spec/toy/cloneable_spec.rb +1 -8
  34. data/spec/toy/equality_spec.rb +18 -19
  35. data/spec/toy/extensions/symbol_spec.rb +26 -0
  36. data/spec/toy/extensions/uuid_spec.rb +31 -11
  37. data/spec/toy/identity/uuid_key_factory_spec.rb +4 -4
  38. data/spec/toy/identity_spec.rb +111 -5
  39. data/spec/toy/inheritance_spec.rb +4 -4
  40. data/spec/toy/inspect_spec.rb +3 -3
  41. data/spec/toy/object_spec.rb +0 -40
  42. data/spec/toy/persistence_spec.rb +99 -1
  43. data/spec/toy/reloadable_spec.rb +9 -4
  44. data/spec/toy/serialization_spec.rb +16 -21
  45. data/spec/toy/types/json_spec.rb +37 -0
  46. data/spec/toy/validations_spec.rb +13 -0
  47. metadata +10 -4
@@ -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$}) { |m| "spec/#{m[1]}_spec.rb" }
8
- watch('spec/helper.rb') { "spec" }
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, querying, callbacks, validations and a few simple associations (lists and references).
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
- puts Person.inspect
26
+ pp Person
27
27
 
28
- john = Person.new(:name => 'John', :age => 30)
28
+ john = Person.new(:name => 'John', :age => 30)
29
29
  steve = Person.new(:name => 'Steve', :age => 31)
30
30
 
31
31
  # Pretty inspecting
32
- puts john.inspect
32
+ pp john
33
33
 
34
34
  # Attribute dirty tracking
35
35
  john.name = 'NEW NAME!'
36
- puts john.changes.inspect # {"name"=>["John", "NEW NAME!"], "age"=>[nil, 30]}
37
- puts john.name_changed?.inspect # true
36
+ pp john.changes # {"name"=>["John", "NEW NAME!"], "age"=>[nil, 30]}
37
+ pp john.name_changed? # true
38
38
 
39
39
  # Equality goodies
40
- puts john.eql?(john) # true
41
- puts john.eql?(steve) # false
42
- puts john == john # true
43
- puts john == steve # false
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
- puts john.clone.inspect
46
+ pp john.clone
47
47
 
48
48
  # Inheritance
49
49
  class AwesomePerson < Person
50
50
  end
51
51
 
52
- puts Person.attributes.keys.sort.inspect # ["age", "id", "name"]
53
- puts AwesomePerson.attributes.keys.sort.inspect # ["age", "id", "name", "type"]
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.get(john.id)
88
- pp Person.get_multiple([john.id])
89
- pp Person.get('NOT HERE') # nil
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.get!('NOT HERE')
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.get(frank.id).equal?(frank) # true
155
- pp Person.get(frank.id).object_id == frank.object_id # true
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.get(frank.id).equal?(frank) # true
163
- pp Person.get(frank.id).object_id == frank.object_id # true
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] == key_for(key) })
179
- decode(record)
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.get(person.id) # Phil with age 56
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 Marshal.load(User.adapter.client[user.id])
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 Marshal.load(User.adapter.client[user.id])
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"}
@@ -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.get(user.id).equal?(user): #{User.get(user.id).equal?(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.get(user.id).equal?(user): #{User.get(user.id).equal?(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"}
@@ -18,8 +18,8 @@ end
18
18
  list = GameList.create(:source => {'foo' => 'bar'})
19
19
 
20
20
  pp list
21
- pp GameList.get(list.id)
21
+ pp GameList.read(list.id)
22
22
 
23
23
  list.destroy
24
24
 
25
- pp GameList.get(list.id)
25
+ pp GameList.read(list.id)
@@ -18,8 +18,8 @@ end
18
18
  user = User.create(:name => 'John')
19
19
 
20
20
  pp user
21
- pp User.get(user.id)
21
+ pp User.read(user.id)
22
22
 
23
23
  user.destroy
24
24
 
25
- pp User.get(user.id)
25
+ pp User.read(user.id)
@@ -18,8 +18,8 @@ end
18
18
  user = User.create(:name => 'John')
19
19
 
20
20
  pp user
21
- pp User.get(user.id)
21
+ pp User.read(user.id)
22
22
 
23
23
  user.destroy
24
24
 
25
- pp User.get(user.id)
25
+ pp User.read(user.id)
@@ -33,4 +33,4 @@ class Game
33
33
  include Toy::Store
34
34
  end
35
35
 
36
- puts Game.new.id # Game:some_uuid
36
+ puts Game.new.id # Game:some_uuid
@@ -46,8 +46,8 @@ pp john.clone
46
46
  class AwesomePerson < Person
47
47
  end
48
48
 
49
- pp Person.attributes.keys.sort # ["age", "id", "name"]
50
- pp AwesomePerson.attributes.keys.sort # ["age", "id", "name", "type"]
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.get(john.id)
37
- pp Person.get_multiple([john.id])
38
- pp Person.get('NOT HERE') # nil
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.get!('NOT HERE')
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.get(frank.id).equal?(frank) # true
104
- pp Person.get(frank.id).object_id == frank.object_id # true
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.get(frank.id).equal?(frank) # true
112
- pp Person.get(frank.id).object_id == frank.object_id # true
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] == key_for(key) })
128
- decode(record)
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.get(person.id) # Phil with age 56
151
+ pp Person.read(person.id) # Phil with age 56
@@ -18,8 +18,8 @@ end
18
18
  user = User.create(:name => 'John')
19
19
 
20
20
  pp user
21
- pp User.get(user.id)
21
+ pp User.read(user.id)
22
22
 
23
23
  user.destroy
24
24
 
25
- pp User.get(user.id)
25
+ pp User.read(user.id)
@@ -18,8 +18,8 @@ end
18
18
  list = GameList.create(:source => {'foo' => 'bar'})
19
19
 
20
20
  pp list
21
- pp GameList.get(list.id)
21
+ pp GameList.read(list.id)
22
22
 
23
23
  list.destroy
24
24
 
25
- pp GameList.get(list.id)
25
+ pp GameList.read(list.id)
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'
@@ -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 = self.class.attributes.fetch(key) {
90
- raise AttributeNotDefined, "#{self.class} does not have attribute #{key}"
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|