toystore 0.13.0 → 0.13.1

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.
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
@@ -13,8 +13,6 @@ module Toy
13
13
  value = value.duplicable? ? value.clone : value
14
14
  send("#{key}=", value)
15
15
  end
16
-
17
- write_attribute(:id, self.class.next_key(self))
18
16
  end
19
17
  end
20
- end
18
+ end
@@ -5,12 +5,6 @@ module Toy
5
5
  include Attributes
6
6
  include Cloneable
7
7
 
8
- def initialize(*)
9
- super
10
- # never register initial id assignment as a change
11
- @changed_attributes.delete('id') if @changed_attributes
12
- end
13
-
14
8
  def initialize_copy(*)
15
9
  super.tap do
16
10
  @previously_changed = {}
@@ -3,23 +3,12 @@ module Toy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  def eql?(other)
6
- return true if self.class.eql?(other.class) && id == other.id
7
- return true if other.respond_to?(:target) &&
8
- self.class.eql?(other.target.class) &&
9
- id == other.target.id
10
- false
11
- end
12
- alias :== :eql?
13
-
14
- def equal?(other)
15
- if other.respond_to?(:proxy_respond_to?) && other.respond_to?(:target)
16
- other = other.target
17
- end
18
- super other
6
+ self.class.eql?(other.class) && attributes == other.attributes
19
7
  end
8
+ alias_method :==, :eql?
20
9
 
21
10
  def hash
22
- id.hash
11
+ attributes.hash
23
12
  end
24
13
  end
25
14
  end
@@ -0,0 +1,17 @@
1
+ module Toy
2
+ module Extensions
3
+ module Symbol
4
+ def to_store(value, *)
5
+ value.nil? ? nil : value.to_s
6
+ end
7
+
8
+ def from_store(value, *)
9
+ value.nil? ? nil : value.to_sym
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ class Symbol
16
+ extend Toy::Extensions::Symbol
17
+ end
@@ -2,11 +2,15 @@ module Toy
2
2
  module Extensions
3
3
  module UUID
4
4
  def to_store(value, *)
5
- SimpleUUID::UUID.new(value)
5
+ return nil if value.nil?
6
+ return value if value.is_a?(self)
7
+ new(value)
6
8
  end
7
9
 
8
10
  def from_store(value, *)
9
- SimpleUUID::UUID.new(value)
11
+ return nil if value.nil?
12
+ return value if value.is_a?(self)
13
+ new(value)
10
14
  end
11
15
  end
12
16
  end
@@ -7,7 +7,7 @@ module Toy
7
7
  end
8
8
 
9
9
  module ClassMethods
10
- def key(name_or_factory = :uuid)
10
+ def key(name_or_factory = :uuid, options = {})
11
11
  @key_factory = if name_or_factory == :uuid
12
12
  UUIDKeyFactory.new
13
13
  elsif name_or_factory == :native_uuid
@@ -20,7 +20,7 @@ module Toy
20
20
  end
21
21
  end
22
22
 
23
- attribute :id, @key_factory.key_type
23
+ attribute :id, @key_factory.key_type, :virtual => true
24
24
  @key_factory
25
25
  end
26
26
 
@@ -42,5 +42,42 @@ module Toy
42
42
  def key_factory
43
43
  self.class.key_factory
44
44
  end
45
+
46
+ def initialize(*args)
47
+ super
48
+ write_attribute :id, self.class.next_key(self) unless id?
49
+
50
+ # never register initial id assignment as a change
51
+ @changed_attributes.delete('id') if @changed_attributes
52
+ end
53
+
54
+ def initialize_copy(*args)
55
+ super
56
+ write_attribute :id, self.class.next_key(self)
57
+ end
58
+
59
+ def eql?(other)
60
+ return true if self.class.eql?(other.class) &&
61
+ id == other.id
62
+
63
+ return true if other.respond_to?(:target) &&
64
+ self.class.eql?(other.target.class) &&
65
+ id == other.target.id
66
+
67
+ super
68
+ end
69
+
70
+ alias_method :==, :eql?
71
+
72
+ def equal?(other)
73
+ if other.respond_to?(:proxy_respond_to?) && other.respond_to?(:target)
74
+ other = other.target
75
+ end
76
+ super other
77
+ end
78
+
79
+ def to_key
80
+ key_factory.to_key(self)
81
+ end
45
82
  end
46
83
  end
@@ -4,21 +4,20 @@ module Toy
4
4
 
5
5
  module ClassMethods
6
6
  def inspect
7
- keys = attributes.keys - ['id']
7
+ keys = attributes.keys
8
8
  nice_string = keys.sort.map do |name|
9
9
  type = attributes[name].type
10
10
  "#{name}:#{type}"
11
11
  end.join(" ")
12
- "#{name}(id:#{attributes['id'].type} #{nice_string})"
12
+ "#{name}(#{nice_string})"
13
13
  end
14
14
  end
15
15
 
16
16
  def inspect
17
- keys = self.class.attributes.keys - ['id']
17
+ keys = self.class.attributes.keys
18
18
  attributes_as_nice_string = keys.map(&:to_s).sort.map do |name|
19
19
  "#{name}: #{read_attribute(name).inspect}"
20
20
  end
21
- attributes_as_nice_string.unshift("id: #{read_attribute(:id).inspect}")
22
21
  "#<#{self.class}:#{object_id} #{attributes_as_nice_string.join(', ')}>"
23
22
  end
24
23
  end
@@ -1,6 +1,6 @@
1
1
  module Toy
2
2
  module Object
3
- extend ActiveSupport::Concern
3
+ extend ActiveSupport::Concern
4
4
  extend ActiveModel::Naming
5
5
  include ActiveModel::Conversion
6
6
  include ActiveModel::Validations
@@ -17,9 +17,5 @@ module Toy
17
17
  def persisted?
18
18
  false
19
19
  end
20
-
21
- def to_key
22
- key_factory.to_key(self)
23
- end
24
20
  end
25
21
  end
@@ -30,6 +30,15 @@ module Toy
30
30
  def destroy(*ids)
31
31
  ids.each { |id| get(id).try(:destroy) }
32
32
  end
33
+
34
+ def persisted_attributes
35
+ @persisted_attributes ||= attributes.values.select(&:persisted?)
36
+ end
37
+
38
+ def attribute(*args)
39
+ @persisted_attributes = nil
40
+ super
41
+ end
33
42
  end
34
43
 
35
44
  def adapter
@@ -81,7 +90,31 @@ module Toy
81
90
 
82
91
  def delete
83
92
  @_destroyed = true
84
- adapter.delete(id)
93
+ adapter.delete(persisted_id)
94
+ end
95
+
96
+ # Public: Choke point for overriding what id is used to write and delete.
97
+ def persisted_id
98
+ attribute_name = 'id'
99
+ attribute = attribute_instance(attribute_name)
100
+ attribute_value = read_attribute(attribute_name)
101
+ attribute.to_store(attribute_value)
102
+ end
103
+
104
+ # Public: Choke point for overriding what attributes get stored.
105
+ def persisted_attributes
106
+ attributes = {}
107
+ self.class.persisted_attributes.each do |attribute|
108
+ if (value = attribute.to_store(read_attribute(attribute.name)))
109
+ attributes[attribute.persisted_name] = value
110
+ end
111
+ end
112
+ attributes
113
+ end
114
+
115
+ # Public: Choke point for overriding how data gets written.
116
+ def persist
117
+ adapter.write(persisted_id, persisted_attributes)
85
118
  end
86
119
 
87
120
  private
@@ -96,9 +129,5 @@ module Toy
96
129
  persist
97
130
  true
98
131
  end
99
-
100
- def persist
101
- adapter.write(id, persisted_attributes)
102
- end
103
132
  end
104
133
  end
@@ -3,23 +3,23 @@ module Toy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
- def get(id, options = nil)
6
+ def read(id, options = nil)
7
7
  if (attrs = adapter.read(id, options))
8
8
  load(id, attrs)
9
9
  end
10
10
  end
11
11
 
12
- alias_method :read, :get
13
- alias_method :find, :get
12
+ alias_method :get, :read
13
+ alias_method :find, :read
14
14
 
15
- def get!(id, options = nil)
15
+ def read!(id, options = nil)
16
16
  get(id, options) || raise(Toy::NotFound.new(id))
17
17
  end
18
18
 
19
- alias_method :read!, :get!
20
- alias_method :find!, :get!
19
+ alias_method :get!, :read!
20
+ alias_method :find!, :read!
21
21
 
22
- def get_multiple(ids, options = nil)
22
+ def read_multiple(ids, options = nil)
23
23
  result = adapter.read_multiple(ids, options)
24
24
  result.each do |id, attrs|
25
25
  result[id] = attrs.nil? ? nil : load(id, attrs)
@@ -27,8 +27,8 @@ module Toy
27
27
  result
28
28
  end
29
29
 
30
- alias_method :read_multiple, :get_multiple
31
- alias_method :find_multiple, :get_multiple
30
+ alias_method :get_multiple, :read_multiple
31
+ alias_method :find_multiple, :read_multiple
32
32
 
33
33
  def get_or_new(id)
34
34
  get(id) || new(:id => id)
@@ -3,15 +3,15 @@ module Toy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  def reload
6
- if attrs = adapter.read(id)
7
- attrs['id'] = id
6
+ if attrs = adapter.read(persisted_id)
7
+ attrs['id'] = persisted_id
8
8
  instance_variables.each { |ivar| instance_variable_set(ivar, nil) }
9
9
  initialize_attributes
10
10
  send(:attributes=, attrs, new_record?)
11
11
  self.class.lists.each_key { |name| send(name).reset }
12
12
  self.class.references.each_key { |name| send("reset_#{name}") }
13
13
  else
14
- raise NotFound.new(id)
14
+ raise NotFound.new(persisted_id)
15
15
  end
16
16
  self
17
17
  end
@@ -3,6 +3,7 @@ module Toy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  include Toy::Object
6
+ include Identity
6
7
  include Persistence
7
8
  include MassAssignmentSecurity
8
9
  include DirtyStore
@@ -0,0 +1,20 @@
1
+ module Toy
2
+ module Types
3
+ module JSON
4
+ def self.to_store(value, *)
5
+ return value if value.nil?
6
+ ActiveSupport::JSON.encode(value)
7
+ end
8
+
9
+ def self.from_store(value, *)
10
+ return value if value.nil?
11
+
12
+ if value.is_a?(String)
13
+ ActiveSupport::JSON.decode(value)
14
+ else
15
+ value
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module Toy
2
- VERSION = "0.13.0"
2
+ VERSION = "0.13.1"
3
3
  end
@@ -29,6 +29,7 @@ RSpec.configure do |c|
29
29
  c.include(Support::Objects)
30
30
  c.include(IdentityMapMatcher)
31
31
 
32
+ c.fail_fast = true
32
33
  c.filter_run :focused => true
33
34
  c.alias_example_to :fit, :focused => true
34
35
  c.alias_example_to :xit, :pending => true
@@ -3,51 +3,9 @@ require 'helper'
3
3
  describe Toy::Attributes do
4
4
  uses_objects('User', 'Game')
5
5
 
6
- describe "including" do
7
- it "adds id attribute" do
8
- User.attributes.keys.should == ['id']
9
- end
10
- end
11
-
12
6
  describe ".attributes" do
13
- it "defaults to hash with id" do
14
- User.attributes.keys.should == ['id']
15
- end
16
- end
17
-
18
- describe "#persisted_attributes" do
19
- before do
20
- @over = Game.attribute(:over, Boolean)
21
- @score = Game.attribute(:creator_score, Integer, :virtual => true)
22
- @abbr = Game.attribute(:super_secret_hash, String, :abbr => :ssh)
23
- @rewards = Game.attribute(:rewards, Set)
24
- @game = Game.new({
25
- :over => true,
26
- :creator_score => 20,
27
- :rewards => %w(twigs berries).to_set,
28
- :ssh => 'h4x',
29
- })
30
- end
31
-
32
- it "includes persisted attributes" do
33
- @game.persisted_attributes.should have_key('over')
34
- end
35
-
36
- it "includes abbreviated names for abbreviated attributes" do
37
- @game.persisted_attributes.should have_key('ssh')
38
- end
39
-
40
- it "does not include full names for abbreviated attributes" do
41
- @game.persisted_attributes.should_not have_key('super_secret_hash')
42
- end
43
-
44
- it "does not include virtual attributes" do
45
- @game.persisted_attributes.should_not have_key(:creator_score)
46
- end
47
-
48
- it "includes to_store values for attributes" do
49
- @game.persisted_attributes['rewards'].should be_instance_of(Array)
50
- @game.persisted_attributes['rewards'].should == @rewards.to_store(@game.rewards)
7
+ it "defaults to empty hash" do
8
+ User.attributes.should eq({})
51
9
  end
52
10
  end
53
11
 
@@ -64,6 +22,22 @@ describe Toy::Attributes do
64
22
  it "excludes attributes without a default" do
65
23
  User.defaulted_attributes.should_not include(@name)
66
24
  end
25
+
26
+ it "memoizes after first call" do
27
+ User.should_receive(:attributes).once.and_return({
28
+ 'name' => @name,
29
+ 'age' => @age,
30
+ })
31
+ User.defaulted_attributes
32
+ User.defaulted_attributes
33
+ User.defaulted_attributes
34
+ end
35
+
36
+ it "is unmemoized when declaring a new attribute" do
37
+ User.defaulted_attributes
38
+ age = User.attribute :location, String, :default => 'IN'
39
+ User.defaulted_attributes.map(&:name).sort.should eq(%w[age location])
40
+ end
67
41
  end
68
42
 
69
43
  describe ".attribute?" do
@@ -90,17 +64,6 @@ describe Toy::Attributes do
90
64
  User.attribute :age, Integer
91
65
  end
92
66
 
93
- it "writes id" do
94
- id = User.new.id
95
- id.should_not be_nil
96
- id.size.should == 36
97
- end
98
-
99
- it "does not attempt to set id if already set" do
100
- user = User.new(:id => 'frank')
101
- user.id.should == 'frank'
102
- end
103
-
104
67
  it "sets attributes" do
105
68
  instance = User.new(:name => 'John', :age => 28)
106
69
  instance.name.should == 'John'
@@ -118,9 +81,9 @@ describe Toy::Attributes do
118
81
  end
119
82
 
120
83
  describe "#attributes" do
121
- it "defaults to hash with id" do
122
- attrs = ToyStore().new.attributes
123
- attrs.keys.should == ['id']
84
+ it "defaults to empty hash" do
85
+ attrs = ToyObject().new.attributes
86
+ attrs.should eq({})
124
87
  end
125
88
 
126
89
  it "includes all attributes that are not nil" do
@@ -128,7 +91,6 @@ describe Toy::Attributes do
128
91
  User.attribute(:active, Boolean, :default => true)
129
92
  user = User.new
130
93
  user.attributes.should == {
131
- 'id' => user.id,
132
94
  'active' => true,
133
95
  }
134
96
  end