mongo_mapper 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ begin
12
12
  gem.rubyforge_project = "mongomapper"
13
13
 
14
14
  gem.add_dependency('activesupport')
15
- gem.add_dependency('mongo', '0.15')
15
+ gem.add_dependency('mongo', '0.15.1')
16
16
  gem.add_dependency('jnunemaker-validatable', '1.7.3')
17
17
 
18
18
  gem.add_development_dependency('mocha', '0.9.4')
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.5.2
data/lib/mongo_mapper.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
 
3
3
  gem 'activesupport'
4
- gem 'mongo', '0.15'
4
+ gem 'mongo', '0.15.1'
5
5
  gem 'jnunemaker-validatable', '1.7.3'
6
6
 
7
7
  require 'activesupport'
@@ -82,6 +82,7 @@ require 'mongo_mapper/associations/many_embedded_polymorphic_proxy'
82
82
  require 'mongo_mapper/associations/many_documents_as_proxy'
83
83
  require 'mongo_mapper/callbacks'
84
84
  require 'mongo_mapper/finder_options'
85
+ require 'mongo_mapper/dirty'
85
86
  require 'mongo_mapper/dynamic_finder'
86
87
  require 'mongo_mapper/key'
87
88
  require 'mongo_mapper/observing'
@@ -0,0 +1,137 @@
1
+ module MongoMapper
2
+ module Dirty
3
+ DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
4
+
5
+ def self.included(base)
6
+ base.alias_method_chain :write_attribute, :dirty
7
+ base.alias_method_chain :save, :dirty
8
+ base.alias_method_chain :save!, :dirty
9
+ end
10
+
11
+ def method_missing(method, *args, &block)
12
+ if method.to_s =~ /(_changed\?|_change|_will_change!|_was)$/
13
+ method_suffix = $1
14
+ key = method.to_s.gsub(method_suffix, '')
15
+
16
+ if key_names.include?(key)
17
+ case method_suffix
18
+ when '_changed?'
19
+ key_changed?(key)
20
+ when '_change'
21
+ key_change(key)
22
+ when '_will_change!'
23
+ key_will_change!(key)
24
+ when '_was'
25
+ key_was(key)
26
+ end
27
+ else
28
+ super
29
+ end
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ def changed?
36
+ !changed_keys.empty?
37
+ end
38
+
39
+ # List of keys with unsaved changes.
40
+ # person.changed # => []
41
+ # person.name = 'bob'
42
+ # person.changed # => ['name']
43
+ def changed
44
+ changed_keys.keys
45
+ end
46
+
47
+ # Map of changed attrs => [original value, new value].
48
+ # person.changes # => {}
49
+ # person.name = 'bob'
50
+ # person.changes # => { 'name' => ['bill', 'bob'] }
51
+ def changes
52
+ changed.inject({}) { |h, attribute| h[attribute] = key_change(attribute); h }
53
+ end
54
+
55
+ # Attempts to +save+ the record and clears changed keys if successful.
56
+ def save_with_dirty(*args) #:nodoc:
57
+ if status = save_without_dirty(*args)
58
+ changed_keys.clear
59
+ end
60
+ status
61
+ end
62
+
63
+ # Attempts to <tt>save!</tt> the record and clears changed keys if successful.
64
+ def save_with_dirty!(*args) #:nodoc:
65
+ status = save_without_dirty!(*args)
66
+ changed_keys.clear
67
+ status
68
+ end
69
+
70
+ # <tt>reload</tt> the record and clears changed keys.
71
+ # def reload_with_dirty(*args) #:nodoc:
72
+ # record = reload_without_dirty(*args)
73
+ # changed_keys.clear
74
+ # record
75
+ # end
76
+
77
+ private
78
+ def clone_key_value(attribute_name)
79
+ value = send(:read_attribute, attribute_name)
80
+ value.duplicable? ? value.clone : value
81
+ rescue TypeError, NoMethodError
82
+ value
83
+ end
84
+
85
+ # Map of change <tt>attr => original value</tt>.
86
+ def changed_keys
87
+ @changed_keys ||= {}
88
+ end
89
+
90
+ # Handle <tt>*_changed?</tt> for +method_missing+.
91
+ def key_changed?(attribute)
92
+ changed_keys.include?(attribute)
93
+ end
94
+
95
+ # Handle <tt>*_change</tt> for +method_missing+.
96
+ def key_change(attribute)
97
+ [changed_keys[attribute], __send__(attribute)] if key_changed?(attribute)
98
+ end
99
+
100
+ # Handle <tt>*_was</tt> for +method_missing+.
101
+ def key_was(attribute)
102
+ key_changed?(attribute) ? changed_keys[attribute] : __send__(attribute)
103
+ end
104
+
105
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
106
+ def key_will_change!(attribute)
107
+ changed_keys[attribute] = clone_key_value(attribute)
108
+ end
109
+
110
+ # Wrap write_attribute to remember original key value.
111
+ def write_attribute_with_dirty(attribute, value)
112
+ attribute = attribute.to_s
113
+
114
+ # The key already has an unsaved change.
115
+ if changed_keys.include?(attribute)
116
+ old = changed_keys[attribute]
117
+ changed_keys.delete(attribute) unless value_changed?(attribute, old, value)
118
+ else
119
+ old = clone_key_value(attribute)
120
+ changed_keys[attribute] = old if value_changed?(attribute, old, value)
121
+ end
122
+
123
+ # Carry on.
124
+ write_attribute_without_dirty(attribute, value)
125
+ end
126
+
127
+ def value_changed?(key_name, old, value)
128
+ key = _keys[key_name]
129
+
130
+ if key.number? && value.blank?
131
+ value = nil
132
+ end
133
+
134
+ old != value
135
+ end
136
+ end
137
+ end
@@ -9,6 +9,7 @@ module MongoMapper
9
9
  include Observing
10
10
  include Callbacks
11
11
  include SaveWithValidation
12
+ include Dirty
12
13
  include RailsCompatibility::Document
13
14
  extend Validations::Macros
14
15
  extend ClassMethods
@@ -299,6 +299,12 @@ module MongoMapper
299
299
  _root_document.save
300
300
  end
301
301
  end
302
+
303
+ def save!
304
+ if _root_document
305
+ _root_document.save!
306
+ end
307
+ end
302
308
 
303
309
  def update_attributes(attrs={})
304
310
  self.attributes = attrs
@@ -348,7 +354,7 @@ module MongoMapper
348
354
  end.map do |name, association|
349
355
  association
350
356
  end
351
- end
357
+ end
352
358
  end # InstanceMethods
353
359
  end # EmbeddedDocument
354
360
  end # MongoMapper
@@ -74,14 +74,7 @@ module MongoMapper
74
74
  def self.to_mongo_sort(sort)
75
75
  return if sort.blank?
76
76
  pieces = sort.split(',')
77
- pairs = pieces.map { |s| to_mongo_sort_piece(s) }
78
-
79
- hash = OrderedHash.new
80
- pairs.each do |pair|
81
- field, sort_direction = pair
82
- hash[field] = sort_direction
83
- end
84
- hash.symbolize_keys
77
+ pieces.map { |s| to_mongo_sort_piece(s) }
85
78
  end
86
79
 
87
80
  def self.to_mongo_sort_piece(str)
@@ -20,6 +20,10 @@ module MongoMapper
20
20
  def embeddable?
21
21
  type.respond_to?(:embeddable?) && type.embeddable? ? true : false
22
22
  end
23
+
24
+ def number?
25
+ [Integer, Float].include?(type)
26
+ end
23
27
 
24
28
  def get(value)
25
29
  if value.nil? && !default_value.nil?
@@ -154,18 +154,4 @@ class Time
154
154
  Time.parse(value.to_s)
155
155
  end
156
156
  end
157
- end
158
-
159
- # duck punch to get access to internal mongo
160
- # logger instance until my patch goes thorugh
161
- module Mongo
162
- class Connection
163
- def logger
164
- @options[:logger]
165
- end
166
- end
167
-
168
- class DB
169
- attr_reader :logger
170
- end
171
157
  end
data/mongo_mapper.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongo_mapper}
8
- s.version = "0.5.1"
8
+ s.version = "0.5.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Nunemaker"]
12
- s.date = %q{2009-10-07}
12
+ s.date = %q{2009-10-08}
13
13
  s.default_executable = %q{mmconsole}
14
14
  s.email = %q{nunemaker@gmail.com}
15
15
  s.executables = ["mmconsole"]
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  "lib/mongo_mapper/associations/many_proxy.rb",
38
38
  "lib/mongo_mapper/associations/proxy.rb",
39
39
  "lib/mongo_mapper/callbacks.rb",
40
+ "lib/mongo_mapper/dirty.rb",
40
41
  "lib/mongo_mapper/document.rb",
41
42
  "lib/mongo_mapper/dynamic_finder.rb",
42
43
  "lib/mongo_mapper/embedded_document.rb",
@@ -65,6 +66,7 @@ Gem::Specification.new do |s|
65
66
  "test/functional/test_associations.rb",
66
67
  "test/functional/test_binary.rb",
67
68
  "test/functional/test_callbacks.rb",
69
+ "test/functional/test_dirty.rb",
68
70
  "test/functional/test_document.rb",
69
71
  "test/functional/test_embedded_document.rb",
70
72
  "test/functional/test_logger.rb",
@@ -107,6 +109,7 @@ Gem::Specification.new do |s|
107
109
  "test/functional/test_associations.rb",
108
110
  "test/functional/test_binary.rb",
109
111
  "test/functional/test_callbacks.rb",
112
+ "test/functional/test_dirty.rb",
110
113
  "test/functional/test_document.rb",
111
114
  "test/functional/test_embedded_document.rb",
112
115
  "test/functional/test_logger.rb",
@@ -138,20 +141,20 @@ Gem::Specification.new do |s|
138
141
 
139
142
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
140
143
  s.add_runtime_dependency(%q<activesupport>, [">= 0"])
141
- s.add_runtime_dependency(%q<mongo>, ["= 0.15"])
144
+ s.add_runtime_dependency(%q<mongo>, ["= 0.15.1"])
142
145
  s.add_runtime_dependency(%q<jnunemaker-validatable>, ["= 1.7.3"])
143
146
  s.add_development_dependency(%q<mocha>, ["= 0.9.4"])
144
147
  s.add_development_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
145
148
  else
146
149
  s.add_dependency(%q<activesupport>, [">= 0"])
147
- s.add_dependency(%q<mongo>, ["= 0.15"])
150
+ s.add_dependency(%q<mongo>, ["= 0.15.1"])
148
151
  s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.3"])
149
152
  s.add_dependency(%q<mocha>, ["= 0.9.4"])
150
153
  s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
151
154
  end
152
155
  else
153
156
  s.add_dependency(%q<activesupport>, [">= 0"])
154
- s.add_dependency(%q<mongo>, ["= 0.15"])
157
+ s.add_dependency(%q<mongo>, ["= 0.15.1"])
155
158
  s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.3"])
156
159
  s.add_dependency(%q<mocha>, ["= 0.9.4"])
157
160
  s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
@@ -0,0 +1,138 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class DirtyTest < Test::Unit::TestCase
5
+ def setup
6
+ @document = Class.new do
7
+ include MongoMapper::Document
8
+ set_collection_name 'test'
9
+ key :phrase, String
10
+ end
11
+ @document.collection.clear
12
+
13
+ Status.collection.clear
14
+ Project.collection.clear
15
+ end
16
+
17
+ context "marking changes" do
18
+ should "not happen if there are none" do
19
+ doc = @document.new
20
+ doc.phrase_changed?.should be_false
21
+ doc.phrase_change.should be_nil
22
+ end
23
+
24
+ should "happen when change happens" do
25
+ doc = @document.new
26
+ doc.phrase = 'Golly Gee Willikers Batman'
27
+ doc.phrase_changed?.should be_true
28
+ doc.phrase_was.should be_nil
29
+ doc.phrase_change.should == [nil, 'Golly Gee Willikers Batman']
30
+ end
31
+
32
+ should "clear changes on save" do
33
+ doc = @document.new
34
+ doc.phrase = 'Golly Gee Willikers Batman'
35
+ doc.phrase_changed?.should be_true
36
+ doc.save
37
+ doc.phrase_changed?.should_not be_true
38
+ doc.phrase_change.should be_nil
39
+ end
40
+
41
+ should "clear changes on save!" do
42
+ doc = @document.new
43
+ doc.phrase = 'Golly Gee Willikers Batman'
44
+ doc.phrase_changed?.should be_true
45
+ doc.save!
46
+ doc.phrase_changed?.should_not be_true
47
+ doc.phrase_change.should be_nil
48
+ end
49
+ end
50
+
51
+ context "blank new value and type integer" do
52
+ should "not mark changes" do
53
+ @document.key :age, Integer
54
+
55
+ [nil, ''].each do |value|
56
+ doc = @document.new
57
+ doc.age = value
58
+ doc.age_changed?.should be_false
59
+ doc.age_change.should be_nil
60
+ end
61
+ end
62
+ end
63
+
64
+ context "blank new value and type float" do
65
+ should "not mark changes" do
66
+ @document.key :amount, Float
67
+
68
+ [nil, ''].each do |value|
69
+ doc = @document.new
70
+ doc.amount = value
71
+ doc.amount_changed?.should be_false
72
+ doc.amount_change.should be_nil
73
+ end
74
+ end
75
+ end
76
+
77
+ context "changed?" do
78
+ should "be true if key changed" do
79
+ doc = @document.new
80
+ doc.phrase = 'A penny saved is a penny earned.'
81
+ doc.changed?.should be_true
82
+ end
83
+
84
+ should "be false if no keys changed" do
85
+ @document.new.changed?.should be_false
86
+ end
87
+ end
88
+
89
+ context "changes" do
90
+ should "be empty hash if no changes" do
91
+ @document.new.changes.should == {}
92
+ end
93
+
94
+ should "be hash of keys with values of changes if there are changes" do
95
+ doc = @document.new
96
+ doc.phrase = 'A penny saved is a penny earned.'
97
+ doc.changes.should == {'phrase' => [nil, 'A penny saved is a penny earned.']}
98
+ end
99
+ end
100
+
101
+ context "changed" do
102
+ should "be empty array if no changes" do
103
+ @document.new.changed.should == []
104
+ end
105
+
106
+ should "be array of keys that have changed if there are changes" do
107
+ doc = @document.new
108
+ doc.phrase = 'A penny saved is a penny earned.'
109
+ doc.changed.should == ['phrase']
110
+ end
111
+ end
112
+
113
+ context "will_change!" do
114
+ should "mark changes" do
115
+ doc = @document.create(:phrase => 'Foo')
116
+
117
+ doc.phrase << 'bar'
118
+ doc.phrase_changed?.should be_false
119
+
120
+ doc.phrase_will_change!
121
+ doc.phrase_changed?.should be_true
122
+ doc.phrase_change.should == ['Foobar', 'Foobar']
123
+
124
+ doc.phrase << '!'
125
+ doc.phrase_changed?.should be_true
126
+ doc.phrase_change.should == ['Foobar', 'Foobar!']
127
+ end
128
+ end
129
+
130
+ context "changing a foreign key through association" do
131
+ should "mark changes" do
132
+ status = Status.create(:name => 'Foo')
133
+ status.project = Project.create(:name => 'Bar')
134
+ status.changed?.should be_true
135
+ status.changed.should == %w(project_id)
136
+ end
137
+ end
138
+ end
@@ -75,53 +75,42 @@ class FinderOptionsTest < Test::Unit::TestCase
75
75
 
76
76
  context "ordering" do
77
77
  should "single field with ascending direction" do
78
- hash = OrderedHash.new
79
- hash[:foo] = 1
80
- FinderOptions.to_mongo_options(:order => 'foo asc')[:sort].should == hash
81
- FinderOptions.to_mongo_options(:order => 'foo ASC')[:sort].should == hash
78
+ sort = [['foo', 1]]
79
+ FinderOptions.to_mongo_options(:order => 'foo asc')[:sort].should == sort
80
+ FinderOptions.to_mongo_options(:order => 'foo ASC')[:sort].should == sort
82
81
  end
83
82
 
84
83
  should "single field with descending direction" do
85
- hash = OrderedHash.new
86
- hash[:foo] = -1
87
- FinderOptions.to_mongo_options(:order => 'foo desc')[:sort].should == hash
88
- FinderOptions.to_mongo_options(:order => 'foo DESC')[:sort].should == hash
84
+ sort = [['foo', -1]]
85
+ FinderOptions.to_mongo_options(:order => 'foo desc')[:sort].should == sort
86
+ FinderOptions.to_mongo_options(:order => 'foo DESC')[:sort].should == sort
89
87
  end
90
88
 
91
89
  should "convert field without direction to ascending" do
92
- hash = OrderedHash.new
93
- hash[:foo] = 1
94
- FinderOptions.to_mongo_options(:order => 'foo')[:sort].should == hash
90
+ sort = [['foo', 1]]
91
+ FinderOptions.to_mongo_options(:order => 'foo')[:sort].should == sort
95
92
  end
96
93
 
97
94
  should "convert multiple fields with directions" do
98
- hash = OrderedHash.new
99
- hash[:foo] = -1
100
- hash[:bar] = 1
101
- hash[:baz] = -1
102
- FinderOptions.to_mongo_options(:order => 'foo desc, bar asc, baz desc')[:sort].should == hash
95
+ sort = [['foo', -1], ['bar', 1], ['baz', -1]]
96
+ FinderOptions.to_mongo_options(:order => 'foo desc, bar asc, baz desc')[:sort].should == sort
103
97
  end
104
98
 
105
99
  should "convert multiple fields with some missing directions" do
106
- hash = OrderedHash.new
107
- hash[:foo] = -1
108
- hash[:bar] = 1
109
- hash[:baz] = 1
110
- FinderOptions.to_mongo_options(:order => 'foo desc, bar, baz')[:sort].should == hash
100
+ sort = [['foo', -1], ['bar', 1], ['baz', 1]]
101
+ FinderOptions.to_mongo_options(:order => 'foo desc, bar, baz')[:sort].should == sort
111
102
  end
112
103
 
113
104
  should "just use sort if sort and order are present" do
114
- FinderOptions.to_mongo_options(:sort => {'$natural' => 1}, :order => 'foo asc')[:sort].should == {
115
- '$natural' => 1
116
- }
105
+ sort = [['$natural', 1]]
106
+ FinderOptions.to_mongo_options(:sort => sort, :order => 'foo asc')[:sort].should == sort
117
107
  end
118
108
 
119
109
  should "convert natural in order to proper" do
120
- hash = OrderedHash.new
121
- hash[:'$natural'] = 1
122
- FinderOptions.to_mongo_options(:order => '$natural asc')[:sort].should == hash
123
- hash[:'$natural'] = -1
124
- FinderOptions.to_mongo_options(:order => '$natural desc')[:sort].should == hash
110
+ sort = [['$natural', 1]]
111
+ FinderOptions.to_mongo_options(:order => '$natural asc')[:sort].should == sort
112
+ sort = [['$natural', -1]]
113
+ FinderOptions.to_mongo_options(:order => '$natural desc')[:sort].should == sort
125
114
  end
126
115
 
127
116
  should "work for natural order ascending" do
@@ -91,6 +91,15 @@ class KeyTest < Test::Unit::TestCase
91
91
  should "know if it is not a embedded_document" do
92
92
  Key.new(:name, String).embeddable?.should be_false
93
93
  end
94
+
95
+ should "know if it is a number" do
96
+ Key.new(:age, Integer).number?.should be_true
97
+ Key.new(:age, Float).number?.should be_true
98
+ end
99
+
100
+ should "know if it is not a number" do
101
+ Key.new(:age, String).number?.should be_false
102
+ end
94
103
  end
95
104
 
96
105
  context "setting a value with a custom type" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo_mapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-07 00:00:00 -04:00
12
+ date: 2009-10-08 00:00:00 -04:00
13
13
  default_executable: mmconsole
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - "="
32
32
  - !ruby/object:Gem::Version
33
- version: "0.15"
33
+ version: 0.15.1
34
34
  version:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: jnunemaker-validatable
@@ -91,6 +91,7 @@ files:
91
91
  - lib/mongo_mapper/associations/many_proxy.rb
92
92
  - lib/mongo_mapper/associations/proxy.rb
93
93
  - lib/mongo_mapper/callbacks.rb
94
+ - lib/mongo_mapper/dirty.rb
94
95
  - lib/mongo_mapper/document.rb
95
96
  - lib/mongo_mapper/dynamic_finder.rb
96
97
  - lib/mongo_mapper/embedded_document.rb
@@ -119,6 +120,7 @@ files:
119
120
  - test/functional/test_associations.rb
120
121
  - test/functional/test_binary.rb
121
122
  - test/functional/test_callbacks.rb
123
+ - test/functional/test_dirty.rb
122
124
  - test/functional/test_document.rb
123
125
  - test/functional/test_embedded_document.rb
124
126
  - test/functional/test_logger.rb
@@ -182,6 +184,7 @@ test_files:
182
184
  - test/functional/test_associations.rb
183
185
  - test/functional/test_binary.rb
184
186
  - test/functional/test_callbacks.rb
187
+ - test/functional/test_dirty.rb
185
188
  - test/functional/test_document.rb
186
189
  - test/functional/test_embedded_document.rb
187
190
  - test/functional/test_logger.rb