couchrest_model 1.1.0.beta3 → 1.1.0.beta4

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