mongo_mapper-unstable 2009.12.30 → 2010.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/README.rdoc +2 -17
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/mongo_mapper/associations/base.rb +19 -10
  5. data/lib/mongo_mapper/associations/in_array_proxy.rb +137 -0
  6. data/lib/mongo_mapper/associations/one_proxy.rb +64 -0
  7. data/lib/mongo_mapper/associations/proxy.rb +7 -4
  8. data/lib/mongo_mapper/associations.rb +11 -3
  9. data/lib/mongo_mapper/callbacks.rb +30 -78
  10. data/lib/mongo_mapper/dirty.rb +5 -24
  11. data/lib/mongo_mapper/document.rb +117 -144
  12. data/lib/mongo_mapper/embedded_document.rb +7 -11
  13. data/lib/mongo_mapper/finder_options.rb +13 -21
  14. data/lib/mongo_mapper/mongo_mapper.rb +125 -0
  15. data/lib/mongo_mapper/pagination.rb +12 -1
  16. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +1 -0
  17. data/lib/mongo_mapper/serialization.rb +2 -2
  18. data/lib/mongo_mapper/serializers/json_serializer.rb +2 -46
  19. data/lib/mongo_mapper/support.rb +2 -2
  20. data/lib/mongo_mapper.rb +8 -2
  21. data/mongo_mapper.gemspec +14 -8
  22. data/specs.watchr +3 -5
  23. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +8 -0
  24. data/test/functional/associations/test_belongs_to_proxy.rb +54 -9
  25. data/test/functional/associations/test_in_array_proxy.rb +309 -0
  26. data/test/functional/associations/test_many_documents_proxy.rb +103 -53
  27. data/test/functional/associations/test_many_embedded_proxy.rb +4 -14
  28. data/test/functional/associations/test_many_polymorphic_proxy.rb +2 -1
  29. data/test/functional/associations/test_one_proxy.rb +149 -0
  30. data/test/functional/test_binary.rb +13 -4
  31. data/test/functional/test_callbacks.rb +1 -5
  32. data/test/functional/test_dirty.rb +1 -4
  33. data/test/functional/test_document.rb +576 -640
  34. data/test/functional/test_embedded_document.rb +7 -20
  35. data/test/functional/test_modifiers.rb +238 -0
  36. data/test/functional/test_pagination.rb +1 -3
  37. data/test/functional/test_string_id_compatibility.rb +3 -8
  38. data/test/functional/test_validations.rb +13 -75
  39. data/test/models.rb +1 -1
  40. data/test/support/timing.rb +1 -1
  41. data/test/test_helper.rb +28 -0
  42. data/test/unit/associations/test_base.rb +54 -13
  43. data/test/unit/associations/test_proxy.rb +12 -0
  44. data/test/unit/test_document.rb +36 -26
  45. data/test/unit/test_embedded_document.rb +14 -51
  46. data/test/unit/test_finder_options.rb +20 -7
  47. data/test/unit/test_key.rb +1 -4
  48. data/test/unit/test_pagination.rb +6 -0
  49. data/test/unit/test_rails_compatibility.rb +4 -1
  50. data/test/unit/test_serializations.rb +1 -2
  51. data/test/unit/test_support.rb +4 -0
  52. data/test/unit/test_time_zones.rb +1 -2
  53. data/test/unit/test_validations.rb +3 -14
  54. metadata +12 -6
  55. data/lib/mongo_mapper/observing.rb +0 -50
  56. data/test/unit/test_observing.rb +0 -101
data/README.rdoc CHANGED
@@ -13,20 +13,9 @@ Releases are tagged on github and released on gemcutter. Master is pushed to whe
13
13
  * Send me a pull request. Bonus points for topic branches.
14
14
 
15
15
  == Install
16
-
17
- MongoMapper is only released on gemcutter. To install, you can setup gemcutter as your default gem source.
18
-
19
- $ gem install gemcutter
20
- $ gem tumble
21
-
22
- Then you can install MM:
23
16
 
24
17
  $ gem install mongo_mapper
25
18
 
26
- You can also just declare the source:
27
-
28
- $ gem install mongo_mapper -s http://gemcutter.org
29
-
30
19
  == Dependencies
31
20
 
32
21
  * ActiveSupport (typically the latest version)
@@ -37,16 +26,12 @@ You can also just declare the source:
37
26
 
38
27
  Documentation is lacking right now because if you can't look through the code right now and feel comfortable, this is probably too young for you to use. Wait til it stabilizes a bit more.
39
28
 
40
- http://rdoc.info/projects/jnunemaker/mongomapper
41
29
  http://groups.google.com/group/mongomapper
42
30
 
43
31
  == More Info
44
32
 
45
- You can learn more about mongo here:
46
- http://www.mongodb.org/
47
-
48
- You can learn more about the mongo ruby driver here:
49
- http://github.com/mongodb/mongo-ruby-driver/tree/master
33
+ * http://www.mongodb.org/
34
+ * http://github.com/mongodb/mongo-ruby-driver/
50
35
 
51
36
  == Copyright
52
37
 
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ Jeweler::Tasks.new do |gem|
12
12
  gem.authors = ["John Nunemaker"]
13
13
 
14
14
  gem.add_dependency('activesupport', '>= 2.3')
15
- gem.add_dependency('mongo', '0.18.1')
15
+ gem.add_dependency('mongo', '0.18.2')
16
16
  gem.add_dependency('jnunemaker-validatable', '1.8.1')
17
17
 
18
18
  gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2009.12.30
1
+ 2010.01.04
@@ -1,13 +1,10 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- # Base class for keeping track of associations.
4
- #
5
- # @private
6
3
  class Base
7
4
  attr_reader :type, :name, :options, :finder_options
8
5
 
9
6
  # Options that should not be considered MongoDB query options/criteria
10
- AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :polymorphic]
7
+ AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :in, :polymorphic]
11
8
 
12
9
  def initialize(type, name, options={}, &extension)
13
10
  @type, @name, @options, @finder_options = type, name, {}, {}
@@ -47,6 +44,10 @@ module MongoMapper
47
44
  def belongs_to?
48
45
  @belongs_to_type ||= @type == :belongs_to
49
46
  end
47
+
48
+ def one?
49
+ @one_type ||= @type == :one
50
+ end
50
51
 
51
52
  def polymorphic?
52
53
  !!@options[:polymorphic]
@@ -55,6 +56,14 @@ module MongoMapper
55
56
  def as?
56
57
  !!@options[:as]
57
58
  end
59
+
60
+ def in_array?
61
+ !!@options[:in]
62
+ end
63
+
64
+ def embeddable?
65
+ many? && klass.embeddable?
66
+ end
58
67
 
59
68
  def type_key_name
60
69
  @type_key_name ||= many? ? '_type' : "#{as}_type"
@@ -72,10 +81,6 @@ module MongoMapper
72
81
  @ivar ||= "@_#{name}"
73
82
  end
74
83
 
75
- def embeddable?
76
- many? && klass.embeddable?
77
- end
78
-
79
84
  def proxy_class
80
85
  @proxy_class ||= begin
81
86
  if many?
@@ -86,15 +91,19 @@ module MongoMapper
86
91
  ManyPolymorphicProxy
87
92
  elsif as?
88
93
  ManyDocumentsAsProxy
94
+ elsif in_array?
95
+ InArrayProxy
89
96
  else
90
97
  ManyDocumentsProxy
91
98
  end
92
99
  end
100
+ elsif one?
101
+ OneProxy
93
102
  else
94
103
  polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
95
104
  end
96
- end # end begin
97
- end # end proxy_class
105
+ end
106
+ end
98
107
 
99
108
  private
100
109
 
@@ -0,0 +1,137 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class InArrayProxy < Collection
4
+ include ::MongoMapper::Finders
5
+
6
+ def find(*args)
7
+ options = args.extract_options!
8
+
9
+ case args.first
10
+ when :first
11
+ first(options)
12
+ when :last
13
+ last(options)
14
+ when :all
15
+ all(options)
16
+ else
17
+ klass.find(*scoped_ids(args) << scoped_options(options))
18
+ end
19
+ end
20
+
21
+ def find!(*args)
22
+ options = args.extract_options!
23
+
24
+ case args.first
25
+ when :first
26
+ first(options)
27
+ when :last
28
+ last(options)
29
+ when :all
30
+ all(options)
31
+ else
32
+ klass.find!(*scoped_ids(args) << scoped_options(options))
33
+ end
34
+ end
35
+
36
+ def paginate(options)
37
+ klass.paginate(scoped_options(options))
38
+ end
39
+
40
+ def all(options={})
41
+ klass.all(scoped_options(options))
42
+ end
43
+
44
+ def first(options={})
45
+ klass.first(scoped_options(options))
46
+ end
47
+
48
+ def last(options={})
49
+ klass.last(scoped_options(options))
50
+ end
51
+
52
+ def count(options={})
53
+ options.blank? ? ids.size : klass.count(scoped_options(options))
54
+ end
55
+
56
+ def destroy_all(options={})
57
+ all(options).each do |doc|
58
+ ids.delete(doc.id)
59
+ doc.destroy
60
+ end
61
+ reset
62
+ end
63
+
64
+ def delete_all(options={})
65
+ docs = all(options.merge(:select => ['_id']))
66
+ docs.each { |doc| ids.delete(doc.id) }
67
+ klass.delete(docs.map(&:id))
68
+ reset
69
+ end
70
+
71
+ def nullify
72
+ replace([])
73
+ reset
74
+ end
75
+
76
+ def create(attrs={})
77
+ doc = klass.create(attrs)
78
+ unless doc.new?
79
+ ids << doc.id
80
+ owner.save
81
+ end
82
+ doc
83
+ end
84
+
85
+ def create!(attrs={})
86
+ doc = klass.create!(attrs)
87
+ unless doc.new?
88
+ ids << doc.id
89
+ owner.save
90
+ end
91
+ doc
92
+ end
93
+
94
+ def <<(*docs)
95
+ flatten_deeper(docs).each do |doc|
96
+ doc.save if doc.new?
97
+ unless ids.include?(doc.id)
98
+ ids << doc.id
99
+ end
100
+ end
101
+ reset
102
+ end
103
+ alias_method :push, :<<
104
+ alias_method :concat, :<<
105
+
106
+ def replace(docs)
107
+ doc_ids = docs.map do |doc|
108
+ doc.save if doc.new?
109
+ doc.id
110
+ end
111
+ ids.replace(doc_ids.uniq)
112
+ reset
113
+ end
114
+
115
+ private
116
+ def scoped_conditions
117
+ {:_id => ids}
118
+ end
119
+
120
+ def scoped_options(options)
121
+ reflection.finder_options.merge(options).merge(scoped_conditions)
122
+ end
123
+
124
+ def scoped_ids(args)
125
+ args.flatten.reject { |id| !ids.include?(id) }
126
+ end
127
+
128
+ def find_target
129
+ ids.blank? ? [] : all
130
+ end
131
+
132
+ def ids
133
+ owner[options[:in]]
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,64 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class OneProxy < Proxy
4
+ def build(attrs={})
5
+ instantiate_target(:new, attrs)
6
+ end
7
+
8
+ def create(attrs={})
9
+ instantiate_target(:create, attrs)
10
+ end
11
+
12
+ def create!(attrs={})
13
+ instantiate_target(:create!, attrs)
14
+ end
15
+
16
+ def replace(doc)
17
+ load_target
18
+
19
+ if !target.nil? && target != doc
20
+ if options[:dependent] && !target.new?
21
+ case options[:dependent]
22
+ when :delete
23
+ target.delete
24
+ when :destroy
25
+ target.destroy
26
+ when :nullify
27
+ target[foreign_key] = nil
28
+ target.save
29
+ end
30
+ end
31
+ end
32
+
33
+ reset
34
+
35
+ unless doc.nil?
36
+ owner.save if owner.new?
37
+ doc[foreign_key] = owner.id
38
+ doc.save if doc.new?
39
+ loaded
40
+ @target = doc
41
+ end
42
+ end
43
+
44
+ protected
45
+ def find_target
46
+ target_class.first(reflection.finder_options.merge(foreign_key => owner.id))
47
+ end
48
+
49
+ def instantiate_target(instantiator, attrs={})
50
+ @target = target_class.send(instantiator, attrs.update(foreign_key => owner.id))
51
+ loaded
52
+ @target
53
+ end
54
+
55
+ def target_class
56
+ @target_class ||= options[:class] || (options[:class_name] || reflection.name.to_s.camelize).constantize
57
+ end
58
+
59
+ def foreign_key
60
+ options[:foreign_key] || owner.class.name.foreign_key
61
+ end
62
+ end
63
+ end
64
+ end
@@ -17,7 +17,7 @@ module MongoMapper
17
17
  delegate :collection, :to => :klass
18
18
 
19
19
  def initialize(owner, reflection)
20
- @owner, @reflection = owner, reflection
20
+ @owner, @reflection, @loaded = owner, reflection, false
21
21
  Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
22
22
  reset
23
23
  end
@@ -45,6 +45,11 @@ module MongoMapper
45
45
  target.blank?
46
46
  end
47
47
 
48
+ def present?
49
+ load_target
50
+ target.present?
51
+ end
52
+
48
53
  def reload
49
54
  reset
50
55
  load_target
@@ -57,7 +62,7 @@ module MongoMapper
57
62
 
58
63
  def reset
59
64
  @loaded = false
60
- target = nil
65
+ @target = nil
61
66
  end
62
67
 
63
68
  def respond_to?(*args)
@@ -101,8 +106,6 @@ module MongoMapper
101
106
  raise NotImplementedError
102
107
  end
103
108
 
104
- # Array#flatten has problems with recursive arrays. Going one level
105
- # deeper solves the majority of the problems.
106
109
  def flatten_deeper(array)
107
110
  array.collect do |element|
108
111
  (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
@@ -3,12 +3,14 @@ module MongoMapper
3
3
  module ClassMethods
4
4
  def belongs_to(association_id, options={}, &extension)
5
5
  create_association(:belongs_to, association_id, options, &extension)
6
- self
7
6
  end
8
7
 
9
8
  def many(association_id, options={}, &extension)
10
9
  create_association(:many, association_id, options, &extension)
11
- self
10
+ end
11
+
12
+ def one(association_id, options={}, &extension)
13
+ create_association(:one, association_id, options, &extension)
12
14
  end
13
15
 
14
16
  def associations
@@ -38,6 +40,12 @@ module MongoMapper
38
40
  value
39
41
  end
40
42
 
43
+ if association.one? || association.belongs_to?
44
+ define_method("#{association.name}?") do
45
+ get_proxy(association).present?
46
+ end
47
+ end
48
+
41
49
  if association.options[:dependent] && association.many? && !association.embeddable?
42
50
  after_destroy do |doc|
43
51
  case association.options[:dependent]
@@ -61,7 +69,7 @@ module MongoMapper
61
69
  def get_proxy(association)
62
70
  unless proxy = self.instance_variable_get(association.ivar)
63
71
  proxy = association.proxy_class.new(self, association)
64
- self.instance_variable_set(association.ivar, proxy) if !frozen?
72
+ self.instance_variable_set(association.ivar, proxy)
65
73
  end
66
74
  proxy
67
75
  end
@@ -1,108 +1,60 @@
1
1
  module MongoMapper
2
- # This module is mixed into the Document module to provide call-backs before
3
- # and after the following events:
4
- #
5
- # * save
6
- # * create
7
- # * update
8
- # * validation
9
- # ** every validation
10
- # ** validation when created
11
- # ** validation when updated
12
- # * destruction
13
- #
14
- # @see ActiveSupport::Callbacks
15
2
  module Callbacks
16
- def self.included(model) #:nodoc:
3
+ def self.included(model)
17
4
  model.class_eval do
18
- extend Observable
19
5
  include ActiveSupport::Callbacks
20
6
 
21
- callbacks = %w(
22
- before_save
23
- after_save
24
- before_create
25
- after_create
26
- before_update
27
- after_update
28
- before_validation
29
- after_validation
30
- before_validation_on_create
31
- after_validation_on_create
32
- before_validation_on_update
33
- after_validation_on_update
34
- before_destroy
35
- after_destroy
7
+ define_callbacks(
8
+ :before_save, :after_save,
9
+ :before_create, :after_create,
10
+ :before_update, :after_update,
11
+ :before_validation, :after_validation,
12
+ :before_validation_on_create, :after_validation_on_create,
13
+ :before_validation_on_update, :after_validation_on_update,
14
+ :before_destroy, :after_destroy
36
15
  )
37
-
38
- define_callbacks(*callbacks)
39
-
40
- callbacks.each do |callback|
41
- define_method(callback.to_sym) {}
42
- end
43
16
  end
44
17
  end
45
18
 
46
- def valid? #:nodoc:
47
- return false if callback(:before_validation) == false
48
- result = new? ? callback(:before_validation_on_create) : callback(:before_validation_on_update)
49
- return false if false == result
50
-
19
+ def valid?
20
+ action = new? ? 'create' : 'update'
21
+
22
+ run_callbacks(:before_validation)
23
+ run_callbacks("before_validation_on_#{action}".to_sym)
51
24
  result = super
52
- callback(:after_validation)
53
-
54
- new? ? callback(:after_validation_on_create) : callback(:after_validation_on_update)
55
- return result
25
+ run_callbacks("after_validation_on_#{action}".to_sym)
26
+ run_callbacks(:after_validation)
27
+
28
+ result
56
29
  end
57
30
 
58
- # Here we override the +destroy+ method to allow for the +before_destroy+
59
- # and +after_destroy+ call-backs. Note that the +destroy+ call is aborted
60
- # if the +before_destroy+ call-back returns +false+.
61
- #
62
- # @return the result of calling +destroy+ on the document
63
- def destroy #:nodoc:
64
- return false if callback(:before_destroy) == false
31
+ def destroy
32
+ run_callbacks(:before_destroy)
65
33
  result = super
66
- callback(:after_destroy)
34
+ run_callbacks(:after_destroy)
67
35
  result
68
36
  end
69
37
 
70
38
  private
71
- def callback(method)
72
- result = run_callbacks(method) { |result, object| false == result }
73
-
74
- if result != false && respond_to?(method)
75
- result = send(method)
76
- end
77
-
78
- notify(method)
79
- return result
80
- end
81
-
82
- def notify(method) #:nodoc:
83
- self.class.changed
84
- self.class.notify_observers(method, self)
85
- end
86
-
87
- def create_or_update #:nodoc:
88
- return false if callback(:before_save) == false
39
+ def create_or_update(*args)
40
+ run_callbacks(:before_save)
89
41
  if result = super
90
- callback(:after_save)
42
+ run_callbacks(:after_save)
91
43
  end
92
44
  result
93
45
  end
94
46
 
95
- def create #:nodoc:
96
- return false if callback(:before_create) == false
47
+ def create(*args)
48
+ run_callbacks(:before_create)
97
49
  result = super
98
- callback(:after_create)
50
+ run_callbacks(:after_create)
99
51
  result
100
52
  end
101
53
 
102
- def update(*args) #:nodoc:
103
- return false if callback(:before_update) == false
54
+ def update(*args)
55
+ run_callbacks(:before_update)
104
56
  result = super
105
- callback(:after_update)
57
+ run_callbacks(:after_update)
106
58
  result
107
59
  end
108
60
  end
@@ -30,18 +30,10 @@ module MongoMapper
30
30
  !changed_keys.empty?
31
31
  end
32
32
 
33
- # List of keys with unsaved changes.
34
- # person.changed # => []
35
- # person.name = 'bob'
36
- # person.changed # => ['name']
37
33
  def changed
38
34
  changed_keys.keys
39
35
  end
40
36
 
41
- # Map of changed attrs => [original value, new value].
42
- # person.changes # => {}
43
- # person.name = 'bob'
44
- # person.changes # => { 'name' => ['bill', 'bob'] }
45
37
  def changes
46
38
  changed.inject({}) { |h, attribute| h[attribute] = key_change(attribute); h }
47
39
  end
@@ -51,7 +43,6 @@ module MongoMapper
51
43
  changed_keys.clear unless new?
52
44
  end
53
45
 
54
- # Attempts to +save+ the record and clears changed keys if successful.
55
46
  def save(*args)
56
47
  if status = super
57
48
  changed_keys.clear
@@ -59,19 +50,17 @@ module MongoMapper
59
50
  status
60
51
  end
61
52
 
62
- # Attempts to <tt>save!</tt> the record and clears changed keys if successful.
63
53
  def save!(*args)
64
54
  status = super
65
55
  changed_keys.clear
66
56
  status
67
57
  end
68
58
 
69
- # <tt>reload</tt> the record and clears changed keys.
70
- # def reload(*args) #:nodoc:
71
- # record = super
72
- # changed_keys.clear
73
- # record
74
- # end
59
+ def reload(*args)
60
+ record = super
61
+ changed_keys.clear
62
+ record
63
+ end
75
64
 
76
65
  private
77
66
  def clone_key_value(attribute_name)
@@ -81,36 +70,29 @@ module MongoMapper
81
70
  value
82
71
  end
83
72
 
84
- # Map of change <tt>attr => original value</tt>.
85
73
  def changed_keys
86
74
  @changed_keys ||= {}
87
75
  end
88
76
 
89
- # Handle <tt>*_changed?</tt> for +method_missing+.
90
77
  def key_changed?(attribute)
91
78
  changed_keys.include?(attribute)
92
79
  end
93
80
 
94
- # Handle <tt>*_change</tt> for +method_missing+.
95
81
  def key_change(attribute)
96
82
  [changed_keys[attribute], __send__(attribute)] if key_changed?(attribute)
97
83
  end
98
84
 
99
- # Handle <tt>*_was</tt> for +method_missing+.
100
85
  def key_was(attribute)
101
86
  key_changed?(attribute) ? changed_keys[attribute] : __send__(attribute)
102
87
  end
103
88
 
104
- # Handle <tt>*_will_change!</tt> for +method_missing+.
105
89
  def key_will_change!(attribute)
106
90
  changed_keys[attribute] = clone_key_value(attribute)
107
91
  end
108
92
 
109
- # Wrap write_attribute to remember original key value.
110
93
  def write_attribute(attribute, value)
111
94
  attribute = attribute.to_s
112
95
 
113
- # The key already has an unsaved change.
114
96
  if changed_keys.include?(attribute)
115
97
  old = changed_keys[attribute]
116
98
  changed_keys.delete(attribute) unless value_changed?(attribute, old, value)
@@ -119,7 +101,6 @@ module MongoMapper
119
101
  changed_keys[attribute] = old if value_changed?(attribute, old, value)
120
102
  end
121
103
 
122
- # Carry on.
123
104
  super(attribute, value)
124
105
  end
125
106