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
@@ -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