couchrest_model 1.1.0.beta3 → 1.1.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0.beta3
1
+ 1.1.0.beta4
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
25
25
 
26
26
  s.add_dependency(%q<couchrest>, "1.1.0.pre2")
27
27
  s.add_dependency(%q<mime-types>, "~> 1.15")
28
- s.add_dependency(%q<activemodel>, "~> 3.0.0")
28
+ s.add_dependency(%q<activemodel>, "~> 3.0.5")
29
29
  s.add_dependency(%q<tzinfo>, "~> 0.3.22")
30
30
  s.add_dependency(%q<railties>, "~> 3.0.0")
31
31
  s.add_development_dependency(%q<rspec>, ">= 2.0.0")
data/history.txt CHANGED
@@ -1,4 +1,4 @@
1
- == 1.1.0.beta3
1
+ == 1.1.0.beta4
2
2
 
3
3
  * Major changes:
4
4
  * Fast Dirty Tracking! Many thanks to @sobakasu (Andrew Williams)
@@ -10,6 +10,10 @@
10
10
  * Added "auto_update_design_doc" configuration option.
11
11
  * Using #descending on View object will automatically swap startkey with endkey.
12
12
 
13
+ == 1.1.0.beta3
14
+
15
+ * Removed
16
+
13
17
  == 1.1.0.beta2
14
18
 
15
19
  * Minor enhancements:
@@ -18,8 +18,8 @@ module CouchRest
18
18
  include CouchRest::Model::Associations
19
19
  include CouchRest::Model::Validations
20
20
  include CouchRest::Model::Designs
21
- include CouchRest::Model::Dirty
22
21
  include CouchRest::Model::CastedBy
22
+ include CouchRest::Model::Dirty
23
23
 
24
24
  def self.subclasses
25
25
  @subclasses ||= []
@@ -74,14 +74,6 @@ module CouchRest
74
74
  super
75
75
  end
76
76
 
77
- ### instance methods
78
-
79
- # Checks if we're the top document
80
- # (overrides base_doc? in casted_by.rb)
81
- def base_doc?
82
- !@casted_by
83
- end
84
-
85
77
  ## Compatibility with ActiveSupport and older frameworks
86
78
 
87
79
  # Hack so that CouchRest::Document, which descends from Hash,
@@ -5,25 +5,36 @@
5
5
 
6
6
  module CouchRest::Model
7
7
  class CastedArray < Array
8
+ include CouchRest::Model::CastedBy
8
9
  include CouchRest::Model::Dirty
9
- attr_accessor :casted_by
10
- attr_accessor :property
10
+ attr_accessor :casted_by_property
11
11
 
12
- def initialize(array, property)
13
- self.property = property
12
+ def initialize(array, property, parent = nil)
13
+ self.casted_by_property = property
14
+ self.casted_by = parent unless parent.nil?
14
15
  super(array)
15
16
  end
16
-
17
+
18
+ # Adding new entries
19
+
17
20
  def << obj
18
- couchrest_parent_will_change! if use_dirty?
19
21
  super(instantiate_and_cast(obj))
20
22
  end
21
-
23
+
22
24
  def push(obj)
23
- couchrest_parent_will_change! if use_dirty?
24
25
  super(instantiate_and_cast(obj))
25
26
  end
26
27
 
28
+ def unshift(obj)
29
+ super(instantiate_and_cast(obj))
30
+ end
31
+
32
+ def []= index, obj
33
+ value = instantiate_and_cast(obj, false)
34
+ couchrest_parent_will_change! if use_dirty? && value != self[index]
35
+ super(index, value)
36
+ end
37
+
27
38
  def pop
28
39
  couchrest_parent_will_change! if use_dirty? && self.length > 0
29
40
  super
@@ -34,17 +45,6 @@ module CouchRest::Model
34
45
  super
35
46
  end
36
47
 
37
- def unshift(obj)
38
- couchrest_parent_will_change! if use_dirty?
39
- super(instantiate_and_cast(obj))
40
- end
41
-
42
- def []= index, obj
43
- value = instantiate_and_cast(obj)
44
- couchrest_parent_will_change! if use_dirty? && value != self[index]
45
- super(index, value)
46
- end
47
-
48
48
  def clear
49
49
  couchrest_parent_will_change! if use_dirty? && self.length > 0
50
50
  super
@@ -52,11 +52,14 @@ module CouchRest::Model
52
52
 
53
53
  protected
54
54
 
55
- def instantiate_and_cast(obj)
56
- if self.casted_by && self.property && obj.class != self.property.type_class
57
- self.property.cast_value(self.casted_by, obj)
55
+ def instantiate_and_cast(obj, change = true)
56
+ property = casted_by_property
57
+ couchrest_parent_will_change! if change && use_dirty?
58
+ if casted_by && property && obj.class != property.type_class
59
+ property.cast_value(casted_by, obj)
58
60
  else
59
- obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
61
+ obj.casted_by = casted_by if obj.respond_to?(:casted_by)
62
+ obj.casted_by_property = casted_by_property if obj.respond_to?(:casted_by_property)
60
63
  obj
61
64
  end
62
65
  end
@@ -4,6 +4,7 @@ module CouchRest::Model
4
4
  extend ActiveSupport::Concern
5
5
  included do
6
6
  self.send(:attr_accessor, :casted_by)
7
+ self.send(:attr_accessor, :casted_by_property)
7
8
  end
8
9
 
9
10
  # Gets a reference to the actual document in the DB
@@ -11,13 +12,22 @@ module CouchRest::Model
11
12
  # Otherwise we're at the top and we return self
12
13
  def base_doc
13
14
  return self if base_doc?
14
- @casted_by ? @casted_by.base_doc : nil
15
+ casted_by ? casted_by.base_doc : nil
15
16
  end
16
17
 
17
18
  # Checks if we're the top document
18
19
  def base_doc?
19
- false
20
+ !casted_by
20
21
  end
21
22
 
23
+ # Provide the property this casted model instance has been
24
+ # used by. If it has not been set, search through the
25
+ # casted_by objects properties to try and find it.
26
+ #def casted_by_property
27
+ # return nil unless casted_by
28
+ # attrs = casted_by.attributes
29
+ # @casted_by_property ||= casted_by.properties.detect{ |k| attrs[k.to_s] === self }
30
+ #end
31
+
22
32
  end
23
33
  end
@@ -3,8 +3,16 @@
3
3
 
4
4
  module CouchRest::Model
5
5
  class CastedHash < Hash
6
+ include CouchRest::Model::CastedBy
6
7
  include CouchRest::Model::Dirty
7
- attr_accessor :casted_by
8
+ attr_accessor :casted_by_property
9
+
10
+ def self.[](hash, property, parent = nil)
11
+ obj = super(hash)
12
+ obj.casted_by_property = property
13
+ obj.casted_by = parent unless parent.nil?
14
+ obj
15
+ end
8
16
 
9
17
  # needed for dirty
10
18
  def attributes
@@ -1,6 +1,6 @@
1
1
  module CouchRest::Model
2
2
  module CastedModel
3
-
3
+
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
@@ -10,8 +10,14 @@ module CouchRest::Model
10
10
  include CouchRest::Model::PropertyProtection
11
11
  include CouchRest::Model::Associations
12
12
  include CouchRest::Model::Validations
13
+ include CouchRest::Model::CastedBy
13
14
  include CouchRest::Model::Dirty
14
- # attr_accessor :casted_by
15
+ class_eval do
16
+ # Override CastedBy's base_doc?
17
+ def base_doc?
18
+ false # Can never be base doc!
19
+ end
20
+ end
15
21
  end
16
22
 
17
23
  def initialize(keys = {})
@@ -21,7 +27,6 @@ module CouchRest::Model
21
27
  end
22
28
 
23
29
  def []= key, value
24
- couchrest_attribute_will_change!(key) if self[key] != value
25
30
  super(key.to_s, value)
26
31
  end
27
32
 
@@ -29,17 +34,10 @@ module CouchRest::Model
29
34
  super(key.to_s)
30
35
  end
31
36
 
32
- # Gets a reference to the top level extended
33
- # document that a model is saved inside of
34
- def base_doc
35
- return nil unless @casted_by
36
- @casted_by.base_doc
37
- end
38
-
39
37
  # False if the casted model has already
40
38
  # been saved in the containing document
41
39
  def new?
42
- @casted_by.nil? ? true : @casted_by.new?
40
+ casted_by.nil? ? true : casted_by.new?
43
41
  end
44
42
  alias :new_record? :new?
45
43
 
@@ -68,4 +66,5 @@ module CouchRest::Model
68
66
  alias :attributes= :update_attributes_without_saving
69
67
 
70
68
  end
69
+
71
70
  end
@@ -10,7 +10,6 @@ module CouchRest
10
10
  # This applies to both Model::Base and Model::CastedModel
11
11
  module Dirty
12
12
  extend ActiveSupport::Concern
13
- include CouchRest::Model::CastedBy # needed for base_doc
14
13
  include ActiveModel::Dirty
15
14
 
16
15
  included do
@@ -21,8 +20,8 @@ module CouchRest
21
20
  end
22
21
 
23
22
  def use_dirty?
24
- bdoc = base_doc
25
- bdoc && !bdoc.disable_dirty
23
+ doc = base_doc
24
+ doc && !doc.disable_dirty
26
25
  end
27
26
 
28
27
  def couchrest_attribute_will_change!(attr)
@@ -32,16 +31,7 @@ module CouchRest
32
31
  end
33
32
 
34
33
  def couchrest_parent_will_change!
35
- @casted_by.couchrest_attribute_will_change!(casted_by_attribute) if @casted_by
36
- end
37
-
38
- private
39
-
40
- # return the attribute name this object is referenced by in the parent
41
- def casted_by_attribute
42
- return @casted_by_attribute if @casted_by_attribute
43
- attr = @casted_by.attributes
44
- @casted_by_attribute = attr.keys.detect { |k| attr[k] == self }
34
+ casted_by.couchrest_attribute_will_change!(casted_by_property.name) if casted_by_property
45
35
  end
46
36
 
47
37
  end
@@ -30,7 +30,7 @@ module CouchRest
30
30
  def update(options = {})
31
31
  raise "Calling #{self.class.name}#update on document that has not been created!" if self.new?
32
32
  return false unless perform_validations(options)
33
- return true if !self.changed?
33
+ return true if !self.disable_dirty && !self.changed?
34
34
  _run_update_callbacks do
35
35
  _run_save_callbacks do
36
36
  result = database.save_doc(self)
@@ -143,20 +143,14 @@ module CouchRest
143
143
  # must be globally unique across all document types which share a
144
144
  # database, so if you'd like to scope uniqueness to this class, you
145
145
  # should use the class name as part of the unique id.
146
- def unique_id method = nil, &block
146
+ def unique_id(method = nil, &block)
147
147
  if method
148
- define_method :get_unique_id do
149
- self.send(method)
150
- end
151
148
  define_method :set_unique_id do
152
- self['_id'] ||= get_unique_id
149
+ self['_id'] ||= self.send(method)
153
150
  end
154
151
  elsif block
155
- define_method :get_unique_id do
156
- block.call(self)
157
- end
158
152
  define_method :set_unique_id do
159
- uniqid = get_unique_id
153
+ uniqid = block.call(self)
160
154
  raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
161
155
  self['_id'] ||= uniqid
162
156
  end
@@ -6,9 +6,9 @@ module CouchRest
6
6
 
7
7
  included do
8
8
  extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
9
- extlib_inheritable_accessor(:property_by_name) unless self.respond_to?(:property_by_name)
9
+ extlib_inheritable_accessor(:properties_by_name) unless self.respond_to?(:properties_by_name)
10
10
  self.properties ||= []
11
- self.property_by_name ||= {}
11
+ self.properties_by_name ||= {}
12
12
  raise "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (method_defined?(:[]) && method_defined?(:[]=))
13
13
  end
14
14
 
@@ -20,6 +20,12 @@ module CouchRest
20
20
  self.class.properties
21
21
  end
22
22
 
23
+ # Returns all the class's properties as a Hash where the key is the name
24
+ # of the property.
25
+ def properties_by_name
26
+ self.class.properties_by_name
27
+ end
28
+
23
29
  # Returns the Class properties with their values
24
30
  #
25
31
  # ==== Returns
@@ -43,31 +49,10 @@ module CouchRest
43
49
  def write_attribute(property, value)
44
50
  prop = find_property!(property)
45
51
  value = prop.is_a?(String) ? value : prop.cast(self, value)
46
- attribute_will_change!(prop.name) if use_dirty? && self[prop.name] != value
52
+ couchrest_attribute_will_change!(prop.name) if use_dirty? && self[prop.name] != value
47
53
  self[prop.name] = value
48
54
  end
49
55
 
50
- def []=(key,value)
51
- return super(key,value) unless use_dirty?
52
-
53
- has_changes = self.changed?
54
- if !has_changes && self.respond_to?(:get_unique_id)
55
- check_id_change = true
56
- old_id = get_unique_id
57
- end
58
-
59
- ret = super(key, value)
60
-
61
- if check_id_change
62
- # if we have set an attribute that results in the _id changing (unique_id),
63
- # force changed? to return true so that the record can be saved
64
- new_id = get_unique_id
65
- changed_attributes["_id"] = new_id if old_id != new_id
66
- end
67
-
68
- ret
69
- end
70
-
71
56
  # Takes a hash as argument, and applies the values by using writer methods
72
57
  # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
73
58
  # missing. In case of error, no attributes are changed.
@@ -90,7 +75,7 @@ module CouchRest
90
75
  protected
91
76
 
92
77
  def find_property(property)
93
- property.is_a?(Property) ? property : self.class.property_by_name[property.to_s]
78
+ property.is_a?(Property) ? property : self.class.properties_by_name[property.to_s]
94
79
  end
95
80
 
96
81
  # The following methods should be accessable by the Model::Base Class, but not by anything else!
@@ -212,7 +197,7 @@ module CouchRest
212
197
  validates_casted_model property.name
213
198
  end
214
199
  properties << property
215
- property_by_name[property.to_s] = property
200
+ properties_by_name[property.to_s] = property
216
201
  property
217
202
  end
218
203
 
@@ -38,16 +38,13 @@ module CouchRest::Model
38
38
  end
39
39
  arr = value.collect { |data| cast_value(parent, data) }
40
40
  # allow casted_by calls to be passed up chain by wrapping in CastedArray
41
- value = CastedArray.new(arr, self)
42
- value.casted_by = parent
41
+ CastedArray.new(arr, self, parent)
43
42
  elsif (type == Object || type == Hash) && (value.class == Hash)
44
43
  # allow casted_by calls to be passed up chain by wrapping in CastedHash
45
- value = CouchRest::Model::CastedHash[value]
46
- value.casted_by = parent
44
+ CastedHash[value, self, parent]
47
45
  elsif !value.nil?
48
- value = cast_value(parent, value)
46
+ cast_value(parent, value)
49
47
  end
50
- value
51
48
  end
52
49
 
53
50
  # Cast an individual value, not an array
@@ -71,6 +68,7 @@ module CouchRest::Model
71
68
 
72
69
  def associate_casted_value_to_parent(parent, value)
73
70
  value.casted_by = parent if value.respond_to?(:casted_by)
71
+ value.casted_by_property = self if value.respond_to?(:casted_by_property)
74
72
  value
75
73
  end
76
74
 
@@ -79,6 +79,10 @@ describe CouchRest::Model::CastedModel do
79
79
  @obj.name.should == 'Eric'
80
80
  @obj.details['color'].should == 'orange'
81
81
  end
82
+ it "should always return base_doc? as false" do
83
+ @obj.base_doc?.should be_false
84
+ end
85
+
82
86
  end
83
87
 
84
88
  describe "casted as an attribute, but without a value" do
@@ -132,6 +136,10 @@ describe CouchRest::Model::CastedModel do
132
136
  @casted_obj.casted_by.should == @obj
133
137
  end
134
138
 
139
+ it "should know which property casted it" do
140
+ @casted_obj.casted_by_property.should == @obj.properties.detect{|p| p.to_s == 'casted_attribute'}
141
+ end
142
+
135
143
  it "should return nil for the 'no_value' attribute" do
136
144
  @casted_obj.no_value.should be_nil
137
145
  end
@@ -13,7 +13,7 @@ class WithCastedModelMixin < Hash
13
13
  property :casted_attribute, WithCastedModelMixin
14
14
  end
15
15
 
16
- class DummyModel < CouchRest::Model::Base
16
+ class DirtyModel < CouchRest::Model::Base
17
17
  use_database DB
18
18
 
19
19
  property :casted_attribute, WithCastedModelMixin
@@ -25,6 +25,16 @@ class DummyModel < CouchRest::Model::Base
25
25
  end
26
26
  end
27
27
 
28
+ class DirtyUniqueIdModel < CouchRest::Model::Base
29
+ use_database DB
30
+ attr_accessor :code
31
+ unique_id :code
32
+ property :title, String, :default => "Sample Title"
33
+ timestamps!
34
+
35
+ def code; self['_id'] || @code; end
36
+ end
37
+
28
38
  describe "Dirty" do
29
39
 
30
40
  describe "changes" do
@@ -66,18 +76,21 @@ describe "Dirty" do
66
76
  end
67
77
 
68
78
  it "should report no changes on a hash property with a default value" do
69
- @obj = DummyModel.new
79
+ @obj = DirtyModel.new
70
80
  @obj.details.changed?.should be_false
71
81
  end
72
82
 
73
- =begin
74
83
  # match activerecord behaviour
75
- # not currently working - not too important
76
84
  it "should report changes on a new object with attributes set" do
77
85
  @card = Card.new(:first_name => "matt")
78
86
  @card.changed?.should be_true
79
87
  end
80
- =end
88
+
89
+ it "should report no changes on new object with 'unique_id' set" do
90
+ @obj = DirtyUniqueIdModel.new
91
+ @obj.changed?.should be_false
92
+ @obj.changes.should be_empty
93
+ end
81
94
 
82
95
  it "should report no changes on objects fetched from the database" do
83
96
  card_id = Card.create!(:first_name => "matt").id
@@ -156,15 +169,37 @@ describe "Dirty" do
156
169
  it "should report changes to casted models" do
157
170
  @cat = Cat.create!(:name => "Felix", :favorite_toy => { :name => "Mouse" })
158
171
  @cat = Cat.find(@cat.id)
159
- @cat.favorite_toy['name'] = 'Feather'
172
+ @cat.favorite_toy.name = 'Feather'
160
173
  @cat.changed?.should be_true
161
174
  end
162
175
 
176
+ it "should report changes to casted model in array" do
177
+ @obj = Cat.create!(:name => 'felix', :toys => [{:name => "Catnip"}])
178
+ @obj = Cat.get(@obj.id)
179
+ @obj.toys.first.name.should eql('Catnip')
180
+ @obj.toys.first.changed?.should be_false
181
+ @obj.changed?.should be_false
182
+ @obj.toys.first.name = "Super Catnip"
183
+ @obj.toys.first.changed?.should be_true
184
+ @obj.changed?.should be_true
185
+ end
186
+
187
+ it "should report changes to anonymous casted models in array" do
188
+ @obj = DirtyModel.create!(:sub_models => [{:title => "Sample"}])
189
+ @obj = DirtyModel.get(@obj.id)
190
+ @obj.sub_models.first.title.should eql("Sample")
191
+ @obj.sub_models.first.changed?.should be_false
192
+ @obj.changed?.should be_false
193
+ @obj.sub_models.first.title = "Another Sample"
194
+ @obj.sub_models.first.changed?.should be_true
195
+ @obj.changed?.should be_true
196
+ end
197
+
163
198
  # casted arrays
164
199
 
165
200
  def test_casted_array(change_expected)
166
- obj = DummyModel.create!
167
- obj = DummyModel.get(obj.id)
201
+ obj = DirtyModel.create!
202
+ obj = DirtyModel.get(obj.id)
168
203
  array = obj.keywords
169
204
  yield array, obj
170
205
  if change_expected
@@ -249,8 +284,8 @@ describe "Dirty" do
249
284
  # Object, {} (casted hash)
250
285
 
251
286
  def test_casted_hash(change_expected)
252
- obj = DummyModel.create!
253
- obj = DummyModel.get(obj.id)
287
+ obj = DirtyModel.create!
288
+ obj = DirtyModel.get(obj.id)
254
289
  hash = obj.details
255
290
  yield hash, obj
256
291
  if change_expected
@@ -227,7 +227,7 @@ describe "Model Persistence" do
227
227
  @templated['important-field'] = 'not-important'
228
228
  @templated.save.should be_true
229
229
  t = WithTemplateAndUniqueID.get('very-important')
230
- t.should == @templated
230
+ t.id.should == @templated.id
231
231
  end
232
232
 
233
233
  it "should raise an error when the id is taken" do
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couchrest_model
3
3
  version: !ruby/object:Gem::Version
4
+ hash: -1848230064
4
5
  prerelease: true
5
6
  segments:
6
7
  - 1
7
8
  - 1
8
9
  - 0
9
- - beta3
10
- version: 1.1.0.beta3
10
+ - beta4
11
+ version: 1.1.0.beta4
11
12
  platform: ruby
12
13
  authors:
13
14
  - J. Chris Anderson
@@ -30,6 +31,7 @@ dependencies:
30
31
  requirements:
31
32
  - - "="
32
33
  - !ruby/object:Gem::Version
34
+ hash: -1876988167
33
35
  segments:
34
36
  - 1
35
37
  - 1
@@ -46,6 +48,7 @@ dependencies:
46
48
  requirements:
47
49
  - - ~>
48
50
  - !ruby/object:Gem::Version
51
+ hash: 17
49
52
  segments:
50
53
  - 1
51
54
  - 15
@@ -60,11 +63,12 @@ dependencies:
60
63
  requirements:
61
64
  - - ~>
62
65
  - !ruby/object:Gem::Version
66
+ hash: 13
63
67
  segments:
64
68
  - 3
65
69
  - 0
66
- - 0
67
- version: 3.0.0
70
+ - 5
71
+ version: 3.0.5
68
72
  type: :runtime
69
73
  version_requirements: *id003
70
74
  - !ruby/object:Gem::Dependency
@@ -75,6 +79,7 @@ dependencies:
75
79
  requirements:
76
80
  - - ~>
77
81
  - !ruby/object:Gem::Version
82
+ hash: 63
78
83
  segments:
79
84
  - 0
80
85
  - 3
@@ -90,6 +95,7 @@ dependencies:
90
95
  requirements:
91
96
  - - ~>
92
97
  - !ruby/object:Gem::Version
98
+ hash: 7
93
99
  segments:
94
100
  - 3
95
101
  - 0
@@ -105,6 +111,7 @@ dependencies:
105
111
  requirements:
106
112
  - - ">="
107
113
  - !ruby/object:Gem::Version
114
+ hash: 15
108
115
  segments:
109
116
  - 2
110
117
  - 0
@@ -120,6 +127,7 @@ dependencies:
120
127
  requirements:
121
128
  - - ">="
122
129
  - !ruby/object:Gem::Version
130
+ hash: 5
123
131
  segments:
124
132
  - 0
125
133
  - 5
@@ -247,6 +255,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
247
255
  requirements:
248
256
  - - ">="
249
257
  - !ruby/object:Gem::Version
258
+ hash: 3
250
259
  segments:
251
260
  - 0
252
261
  version: "0"
@@ -255,6 +264,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
255
264
  requirements:
256
265
  - - ">"
257
266
  - !ruby/object:Gem::Version
267
+ hash: 25
258
268
  segments:
259
269
  - 1
260
270
  - 3