perpetuity 0.7.2 → 0.7.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9580976e3da148321a6f89b5c8aef63f43d4eba2
4
- data.tar.gz: 92849267be3d5db48a02ceaa6018dfbba882d79b
3
+ metadata.gz: 2244d23d7746c76519f66795e7799800d7ed84f5
4
+ data.tar.gz: f3aa364123d96e1477e64eeda2676e616a712515
5
5
  SHA512:
6
- metadata.gz: 83e137d58c0e537264726628f761d5819c503a801de7e803602121fa3448ee1cba2cc1db1c5fbd20534e4133fb64ca62250818cbd0ea048cc8d2797dfd43d18c
7
- data.tar.gz: a3f48dcfe2e00f862e4d2ef978c2cc11c5ded5e3b9bd955f623abe18f0b0b9ede6733794c05b6c9e40f121bb727e92cfe67f57eb5fb3f34b7cf8ea584950c0ae
6
+ metadata.gz: 4baa0facacc3099d0f8c8e36e0fb283ee1dd319067a6249ef2d6ffbdad6afddca95d70779bd8b140b713b6717778182afcfccb26661113e22401aa2cf18aed62
7
+ data.tar.gz: fea3b1c06ef2b0f90738d53d9f1783024527582f5c49bd4869233a71882e248623c7b5980b7679701d6e1c1f61f0bb78886807fd861570017e2710f21a4916df
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## Version 0.7.3
2
+
3
+ - Only save attributes which have changed
4
+
1
5
  ## Version 0.7.2
2
6
 
3
7
  - Add license to gemspec for the benefit of [Rubygems.org](http://rubygems.org/gems/perpetuity)
@@ -17,5 +17,9 @@ module Perpetuity
17
17
  def to_s
18
18
  name
19
19
  end
20
+
21
+ def =~ regexp
22
+ name.to_s =~ regexp
23
+ end
20
24
  end
21
25
  end
@@ -13,7 +13,7 @@ module Perpetuity
13
13
  def << object
14
14
  klass = object.class
15
15
  id = object.instance_variable_get(:@id)
16
- map[klass][id.to_s] = object
16
+ map[klass][id.to_s] = object.dup
17
17
  end
18
18
  end
19
19
  end
@@ -47,18 +47,25 @@ module Perpetuity
47
47
 
48
48
  def reindex!
49
49
  indexes.each { |index| data_source.activate_index! index }
50
- (data_source.active_indexes(mapped_class) - indexes).reject do |index|
51
- # TODO: Make this not MongoDB-specific
52
- index.attribute.name.to_s == '_id'
53
- end.each do |index|
50
+ unspecified_indexes.each do |index|
54
51
  data_source.remove_index index
55
52
  end
56
53
  end
57
54
 
55
+ def unspecified_indexes
56
+ active_indexes = data_source.active_indexes(mapped_class)
57
+ active_but_unspecified_indexes = (active_indexes - indexes)
58
+ active_but_unspecified_indexes.reject { |index| index.attribute =~ /id/ }
59
+ end
60
+
58
61
  def attributes
59
62
  self.class.attributes
60
63
  end
61
64
 
65
+ def attribute_set
66
+ self.class.attribute_set
67
+ end
68
+
62
69
  def delete_all
63
70
  data_source.delete_all mapped_class
64
71
  end
@@ -66,14 +73,7 @@ module Perpetuity
66
73
  def insert object
67
74
  raise "#{object} is invalid and cannot be persisted." unless self.class.validations.valid?(object)
68
75
  objects = Array(object)
69
- serialized_objects = objects.map do |obj|
70
- attributes = serialize(obj)
71
- if o_id = obj.instance_exec(&self.class.id)
72
- attributes[:id] = o_id
73
- end
74
-
75
- attributes
76
- end
76
+ serialized_objects = objects.map { |obj| serialize(obj) }
77
77
 
78
78
  new_ids = data_source.insert(mapped_class, serialized_objects)
79
79
  objects.each_with_index do |obj, index|
@@ -87,6 +87,10 @@ module Perpetuity
87
87
  end
88
88
  end
89
89
 
90
+ def generate_id_for object
91
+ object.instance_exec(&self.class.id)
92
+ end
93
+
90
94
  def self.data_source(configuration=Perpetuity.configuration)
91
95
  configuration.data_source
92
96
  end
@@ -186,7 +190,12 @@ module Perpetuity
186
190
  end
187
191
 
188
192
  def save object
189
- update object, serialize(object), false
193
+ changed_attributes = serialize_changed_attributes(object)
194
+ if changed_attributes && changed_attributes.any?
195
+ update object, changed_attributes
196
+ else
197
+ update object, serialize(object), false
198
+ end
190
199
  end
191
200
 
192
201
  def increment object, attribute, count=1
@@ -226,7 +235,19 @@ module Perpetuity
226
235
  end
227
236
 
228
237
  def serialize object
229
- data_source.serialize(object, self)
238
+ attributes = data_source.serialize(object, self)
239
+ if o_id = generate_id_for(object)
240
+ attributes[:id] = o_id
241
+ end
242
+
243
+ attributes
244
+ end
245
+
246
+ def serialize_changed_attributes object
247
+ cached = identity_map[object.class, id_for(object)]
248
+ if cached
249
+ data_source.serialize_changed_attributes(object, cached, self)
250
+ end
230
251
  end
231
252
 
232
253
  def self.mapped_class
@@ -1,4 +1,5 @@
1
1
  require 'perpetuity/data_injectable'
2
+ require 'perpetuity/reference'
2
3
 
3
4
  module Perpetuity
4
5
  class MongoDB
@@ -22,7 +23,7 @@ module Perpetuity
22
23
  end
23
24
 
24
25
  def serialize object
25
- attrs = mapper.class.attribute_set.map do |attrib|
26
+ attrs = mapper.attribute_set.map do |attrib|
26
27
  next unless has_attribute? object, attrib.name
27
28
 
28
29
  value = attribute_for object, attrib.name
@@ -45,6 +46,10 @@ module Perpetuity
45
46
  Hash[attrs.compact]
46
47
  end
47
48
 
49
+ def serialize_changes changed, original
50
+ Hash[Array(serialize(changed)) - Array(serialize(original))]
51
+ end
52
+
48
53
  def unserialize data
49
54
  if data.is_a? Array
50
55
  unserialize_object_array data
@@ -146,7 +146,7 @@ module Perpetuity
146
146
  end
147
147
 
148
148
  def update klass, id, new_data
149
- find(klass, id).update(new_data)
149
+ find(klass, id).update('$set' => new_data)
150
150
  end
151
151
 
152
152
  def can_serialize? value
@@ -214,6 +214,10 @@ module Perpetuity
214
214
  Serializer.new(mapper).serialize object
215
215
  end
216
216
 
217
+ def serialize_changed_attributes object, original, mapper
218
+ Serializer.new(mapper).serialize_changes object, original
219
+ end
220
+
217
221
  def unserialize data, mapper
218
222
  Serializer.new(mapper).unserialize data
219
223
  end
@@ -1,3 +1,3 @@
1
1
  module Perpetuity
2
- VERSION = "0.7.2"
2
+ VERSION = "0.7.3"
3
3
  end
@@ -100,6 +100,38 @@ module Perpetuity
100
100
  retrieved_time.to_f.should be_within(0.001).of time.to_f
101
101
  end
102
102
 
103
+ describe 'serialization' do
104
+ let(:object) { Object.new }
105
+ let(:foo_attribute) { double('Attribute', name: :foo) }
106
+ let(:baz_attribute) { double('Attribute', name: :baz) }
107
+ let(:mapper) { double('Mapper',
108
+ mapped_class: Object,
109
+ mapper_registry: {},
110
+ attribute_set: Set[foo_attribute, baz_attribute],
111
+ data_source: mongo,
112
+ ) }
113
+
114
+ before do
115
+ object.instance_variable_set :@foo, 'bar'
116
+ object.instance_variable_set :@baz, 'quux'
117
+ end
118
+
119
+ it 'serializes objects' do
120
+ mongo.serialize(object, mapper).should == {
121
+ 'foo' => 'bar',
122
+ 'baz' => 'quux'
123
+ }
124
+ end
125
+
126
+ it 'can serialize only modified attributes of objects' do
127
+ updated = object.dup
128
+ updated.instance_variable_set :@foo, 'foo'
129
+
130
+ serialized = mongo.serialize_changed_attributes(updated, object, mapper)
131
+ serialized.should == { 'foo' => 'foo' }
132
+ end
133
+ end
134
+
103
135
  describe 'serializable objects' do
104
136
  let(:serializable_values) { [nil, true, false, 1, 1.2, '', [], {}, Time.now] }
105
137
 
@@ -26,6 +26,24 @@ describe 'updating' do
26
26
  mapper.find(mapper.id_for article).title.should eq new_title
27
27
  end
28
28
 
29
+ it 'only updates attributes which have changed since last retrieval' do
30
+ first_mapper = Perpetuity[Article]
31
+ second_mapper = Perpetuity[Article]
32
+ article_id = first_mapper.id_for(article)
33
+ first_article = first_mapper.find(article_id)
34
+ second_article = second_mapper.find(article_id)
35
+
36
+ # Change different attributes on each
37
+ first_article.title = 'New title'
38
+ second_article.views = 7
39
+ first_mapper.save first_article
40
+ second_mapper.save second_article
41
+
42
+ canonical_article = mapper.find(article_id)
43
+ canonical_article.title.should == 'New title'
44
+ canonical_article.views.should == 7
45
+ end
46
+
29
47
  it 'updates an object with referenced attributes' do
30
48
  user = User.new
31
49
  article.author = user
@@ -2,7 +2,9 @@ require 'perpetuity/attribute'
2
2
 
3
3
  module Perpetuity
4
4
  describe Attribute do
5
- subject { Attribute.new :article, Object }
5
+ let(:attribute) { Attribute.new :article, Object }
6
+ subject { attribute }
7
+
6
8
  it 'has a name' do
7
9
  subject.name.should == :article
8
10
  end
@@ -15,5 +17,9 @@ module Perpetuity
15
17
  attribute = Attribute.new :article, Object, embedded: true
16
18
  attribute.should be_embedded
17
19
  end
20
+
21
+ it 'can match a regex' do
22
+ expect(attribute =~ /article/).to be_true
23
+ end
18
24
  end
19
25
  end
@@ -18,7 +18,8 @@ module Perpetuity
18
18
  mapper.should_receive(:find).with(1) { first }
19
19
 
20
20
  derefer.load first_ref
21
- derefer[first_ref].should == first
21
+ id = derefer[first_ref].instance_variable_get(:@id)
22
+ id.should == 1
22
23
  end
23
24
  end
24
25
 
@@ -2,23 +2,29 @@ require 'perpetuity/identity_map'
2
2
 
3
3
  module Perpetuity
4
4
  describe IdentityMap do
5
- let(:object_mapper) { double('Mapper', id_for: 1) }
6
- let(:object) { double('Object', class: Object) }
7
5
  let(:id_map) { IdentityMap.new }
8
6
 
9
7
  context 'when the object exists in the IdentityMap' do
10
- let(:object) { double('Object', class: Object) }
8
+ let(:object) { Object.new }
11
9
 
12
- before { object.instance_variable_set :@id, 1 }
10
+ before do
11
+ object.instance_variable_set :@id, 1
12
+ id_map << object
13
+ end
13
14
 
14
15
  it 'returns the object with the given class and id' do
15
- id_map << object
16
- id_map[Object, 1].should == object
16
+ retrieved = id_map[Object, 1]
17
+
18
+ retrieved.instance_variable_get(:@id).should == 1
19
+ end
20
+
21
+ specify 'the object returned is a duplicate, not the same object' do
22
+ id_map[Object, 1].should_not be object
17
23
  end
18
24
 
19
25
  it 'stringifies keys when checking' do
20
- id_map << object
21
- id_map[Object, '1'].should == object
26
+ retrieved = id_map[Object, '1']
27
+ retrieved.instance_variable_get(:@id).should == 1
22
28
  end
23
29
  end
24
30
 
@@ -123,6 +123,22 @@ module Perpetuity
123
123
  mapper.save object
124
124
  end
125
125
 
126
+ it 'can serialize only changed attributes for updates' do
127
+ mapper_class.attribute :modified
128
+ mapper_class.attribute :unmodified
129
+ object = Object.new
130
+ object.instance_variable_set :@id, 1
131
+ object.instance_variable_set :@modified, false
132
+ object.instance_variable_set :@unmodified, false
133
+ mapper.identity_map << object
134
+
135
+ object.instance_variable_set :@modified, true
136
+
137
+ mapper.serialize_changed_attributes(object).should == {
138
+ 'modified' => true
139
+ }
140
+ end
141
+
126
142
  it 'deletes an object from the data source' do
127
143
  object = Object.new
128
144
 
@@ -61,6 +61,18 @@ module Perpetuity
61
61
  }
62
62
  end
63
63
 
64
+ it 'can serialize only changed attributes' do
65
+ book = Book.new('Original Title')
66
+ updated_book = book.dup
67
+ updated_book.title = 'New Title'
68
+ book_mapper.stub(data_source: data_source)
69
+ data_source.stub(:can_serialize?).with('New Title') { true }
70
+ data_source.stub(:can_serialize?).with('Original Title') { true }
71
+ serializer.serialize_changes(updated_book, book).should == {
72
+ 'title' => 'New Title'
73
+ }
74
+ end
75
+
64
76
  context 'with objects that have hashes as attributes' do
65
77
  let(:name_data) { {first_name: 'Jamie', last_name: 'Gaskins'} }
66
78
  let(:serialized_data) { { 'name' => name_data } }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perpetuity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamie Gaskins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-04 00:00:00.000000000 Z
11
+ date: 2013-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -161,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
161
  version: '0'
162
162
  requirements: []
163
163
  rubyforge_project:
164
- rubygems_version: 2.0.5
164
+ rubygems_version: 2.0.6
165
165
  signing_key:
166
166
  specification_version: 4
167
167
  summary: Persistence library allowing serialization of Ruby objects