mongo_mapper 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/UPGRADES +6 -0
  2. data/lib/mongo_mapper.rb +1 -2
  3. data/lib/mongo_mapper/document.rb +0 -1
  4. data/lib/mongo_mapper/embedded_document.rb +0 -1
  5. data/lib/mongo_mapper/extensions/float.rb +1 -1
  6. data/lib/mongo_mapper/plugins.rb +2 -8
  7. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +2 -0
  8. data/lib/mongo_mapper/plugins/associations/many_association.rb +4 -4
  9. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +9 -1
  10. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +22 -0
  11. data/lib/mongo_mapper/plugins/associations/one_association.rb +29 -1
  12. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +0 -1
  13. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +43 -16
  14. data/lib/mongo_mapper/plugins/clone.rb +1 -1
  15. data/lib/mongo_mapper/plugins/dirty.rb +6 -15
  16. data/lib/mongo_mapper/plugins/document.rb +5 -5
  17. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +3 -3
  18. data/lib/mongo_mapper/plugins/keys.rb +0 -19
  19. data/lib/mongo_mapper/plugins/keys/key.rb +0 -4
  20. data/lib/mongo_mapper/railtie.rb +1 -2
  21. data/lib/mongo_mapper/version.rb +1 -1
  22. data/test/functional/associations/test_belongs_to_proxy.rb +15 -0
  23. data/test/functional/associations/test_many_documents_proxy.rb +161 -0
  24. data/test/functional/associations/test_one_as_proxy.rb +485 -0
  25. data/test/functional/associations/test_one_proxy.rb +188 -27
  26. data/test/functional/test_dirty.rb +9 -0
  27. data/test/functional/test_document.rb +5 -0
  28. data/test/support/railtie.rb +4 -0
  29. data/test/support/railtie/autoloaded.rb +2 -0
  30. data/test/support/railtie/not_autoloaded.rb +3 -0
  31. data/test/support/railtie/parent.rb +3 -0
  32. data/test/unit/associations/test_one_association.rb +18 -0
  33. data/test/unit/test_extensions.rb +4 -0
  34. data/test/unit/test_keys.rb +0 -22
  35. data/test/unit/test_plugins.rb +1 -46
  36. data/test/unit/test_railtie.rb +61 -0
  37. metadata +18 -14
  38. data/lib/mongo_mapper/support/descendant_appends.rb +0 -45
  39. data/test/functional/test_string_id_compatibility.rb +0 -75
  40. data/test/unit/test_descendant_appends.rb +0 -63
data/UPGRADES CHANGED
@@ -1,3 +1,9 @@
1
+ 0.9 => 0.10
2
+ * Using String IDs are no longer supported. If you are declaring your own ID, ensure it is an ObjectId, and set the default
3
+ key :_id, ObjectId, :default => lambda { BSON::ObjectId.new }
4
+ * The :dependent association option now applies to both when the parent is destroyed and when the association is reassigned (one and many associations)
5
+ * The default :dependent option is now :nullify for both when the parent is destroyed and when the association is reassigned
6
+
1
7
  0.8.6 => 0.9
2
8
  * [attribute]_before_typecast should become [attribute]_before_type_cast (note the extra _)
3
9
  * Change validates_exclusion_of :within option to :in
@@ -72,6 +72,7 @@ module MongoMapper
72
72
  autoload :ManyEmbeddedPolymorphicProxy, 'mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy'
73
73
  autoload :ManyDocumentsAsProxy, 'mongo_mapper/plugins/associations/many_documents_as_proxy'
74
74
  autoload :OneProxy, 'mongo_mapper/plugins/associations/one_proxy'
75
+ autoload :OneAsProxy, 'mongo_mapper/plugins/associations/one_as_proxy'
75
76
  autoload :OneEmbeddedProxy, 'mongo_mapper/plugins/associations/one_embedded_proxy'
76
77
  autoload :InArrayProxy, 'mongo_mapper/plugins/associations/in_array_proxy'
77
78
  end
@@ -84,8 +85,6 @@ Dir[File.join(File.dirname(__FILE__), 'mongo_mapper', 'extensions', '*.rb')].eac
84
85
  require extension
85
86
  end
86
87
 
87
- require 'mongo_mapper/support/descendant_appends'
88
-
89
88
  # FIXME: autoload with proxy is failing, need to investigate
90
89
  require 'mongo_mapper/plugins/associations/proxy'
91
90
 
@@ -3,7 +3,6 @@ module MongoMapper
3
3
  module Document
4
4
  extend ActiveSupport::Concern
5
5
  extend Plugins
6
- extend Support::DescendantAppends
7
6
 
8
7
  include Plugins::ActiveModel
9
8
  include Plugins::Document
@@ -1,7 +1,6 @@
1
1
  # encoding: UTF-8
2
2
  module MongoMapper
3
3
  module EmbeddedDocument
4
- extend Support::DescendantAppends
5
4
  extend ActiveSupport::Concern
6
5
  extend Plugins
7
6
 
@@ -3,7 +3,7 @@ module MongoMapper
3
3
  module Extensions
4
4
  module Float
5
5
  def to_mongo(value)
6
- value.nil? ? nil : value.to_f
6
+ value.blank? ? nil : value.to_f
7
7
  end
8
8
  end
9
9
  end
@@ -8,14 +8,8 @@ module MongoMapper
8
8
  end
9
9
 
10
10
  def plugin(mod)
11
- if ActiveSupport::Concern === mod
12
- include mod
13
- else
14
- warn "[DEPRECATED] Plugins must extend ActiveSupport::Concern"
15
- extend mod::ClassMethods if mod.const_defined?(:ClassMethods)
16
- include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
17
- mod.configure(self) if mod.respond_to?(:configure)
18
- end
11
+ raise ArgumentError, "Plugins must extend ActiveSupport::Concern" unless ActiveSupport::Concern === mod
12
+ include mod
19
13
  direct_descendants.each {|model| model.send(:include, mod) }
20
14
  plugins << mod
21
15
  end
@@ -13,6 +13,8 @@ module MongoMapper
13
13
  end
14
14
 
15
15
  def setup(model)
16
+ model.key foreign_key, ObjectId unless model.key?(foreign_key)
17
+
16
18
  model.associations_module.module_eval <<-end_eval
17
19
  def #{name}
18
20
  proxy = get_proxy(associations[#{name.inspect}])
@@ -41,11 +41,11 @@ module MongoMapper
41
41
  end
42
42
  end_eval
43
43
 
44
- if options[:dependent] && !embeddable?
45
- association = self
46
- options = self.options
44
+ association = self
45
+ options = self.options
47
46
 
48
- model.after_destroy do
47
+ model.before_destroy do
48
+ if !association.embeddable?
49
49
  case options[:dependent]
50
50
  when :destroy
51
51
  self.get_proxy(association).destroy_all
@@ -8,7 +8,15 @@ module MongoMapper
8
8
 
9
9
  def replace(docs)
10
10
  load_target
11
- target.each { |t| t.destroy }
11
+
12
+ (target - docs).each do |t|
13
+ case options[:dependent]
14
+ when :destroy then t.destroy
15
+ when :delete_all then t.delete
16
+ else t.update_attributes(self.foreign_key => nil)
17
+ end
18
+ end
19
+
12
20
  docs.each { |doc| prepare(doc).save }
13
21
  reset
14
22
  end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Associations
5
+ class OneAsProxy < OneProxy
6
+ protected
7
+ def criteria
8
+ {type_key_name => proxy_owner.class.name, id_key_name => proxy_owner.id}
9
+ end
10
+
11
+ private
12
+ def type_key_name
13
+ "#{options[:as]}_type"
14
+ end
15
+
16
+ def id_key_name
17
+ "#{options[:as]}_id"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -8,7 +8,35 @@ module MongoMapper
8
8
  end
9
9
 
10
10
  def proxy_class
11
- @proxy_class ||= klass.embeddable? ? OneEmbeddedProxy : OneProxy
11
+ @proxy_class ||=
12
+ if klass.embeddable?
13
+ OneEmbeddedProxy
14
+ elsif as?
15
+ OneAsProxy
16
+ else
17
+ OneProxy
18
+ end
19
+ end
20
+
21
+ def setup(model)
22
+ super
23
+
24
+ association = self
25
+ options = self.options
26
+
27
+ model.before_destroy do
28
+ if !association.embeddable?
29
+ proxy = self.get_proxy(association)
30
+
31
+ unless proxy.nil?
32
+ case options[:dependent]
33
+ when :destroy then proxy.destroy
34
+ when :delete then proxy.delete
35
+ else proxy.nullify
36
+ end
37
+ end
38
+ end
39
+ end
12
40
  end
13
41
 
14
42
  def autosave?
@@ -16,7 +16,6 @@ module MongoMapper
16
16
  else
17
17
  @target = klass.load(doc)
18
18
  end
19
- @target.default_id_value if @target && @target.id.nil?
20
19
  assign_references(@target)
21
20
  loaded
22
21
  @target
@@ -19,38 +19,51 @@ module MongoMapper
19
19
  load_target
20
20
 
21
21
  if !target.nil? && target != doc
22
- if options[:dependent] && target.persisted?
22
+ if target.persisted?
23
23
  case options[:dependent]
24
- when :delete
25
- target.delete
26
- when :destroy
27
- target.destroy
28
- when :nullify
29
- target[foreign_key] = nil
24
+ when :delete then target.delete
25
+ when :destroy then target.destroy
26
+ else
27
+ nullify_scope(target)
30
28
  target.save
31
29
  end
32
30
  end
33
31
  end
34
-
35
- if doc.nil?
36
- target.update_attributes(foreign_key => nil) unless target.nil?
37
- else
32
+
33
+ unless doc.nil?
38
34
  proxy_owner.save unless proxy_owner.persisted?
39
35
  doc = klass.new(doc) unless doc.is_a?(klass)
40
- doc[foreign_key] = proxy_owner.id
36
+ apply_scope(doc)
41
37
  doc.save unless doc.persisted?
42
- loaded
43
- @target = doc
44
38
  end
39
+
40
+ loaded
41
+ @target = doc
42
+ end
43
+
44
+ def destroy
45
+ target.destroy
46
+ reset
47
+ end
48
+
49
+ def delete
50
+ target.delete
51
+ reset
52
+ end
53
+
54
+ def nullify
55
+ nullify_scope(target)
56
+ target.save
57
+ reset
45
58
  end
46
59
 
47
60
  protected
48
61
  def find_target
49
- target_class.first(association.query_options.merge(foreign_key => proxy_owner.id))
62
+ target_class.first(association.query_options.merge(criteria))
50
63
  end
51
64
 
52
65
  def instantiate_target(instantiator, attrs={})
53
- @target = target_class.send(instantiator, attrs.update(foreign_key => proxy_owner.id))
66
+ @target = target_class.send(instantiator, attrs.update(criteria))
54
67
  loaded
55
68
  @target
56
69
  end
@@ -62,6 +75,20 @@ module MongoMapper
62
75
  def foreign_key
63
76
  options[:foreign_key] || proxy_owner.class.name.foreign_key
64
77
  end
78
+
79
+ def criteria
80
+ {self.foreign_key => proxy_owner.id}
81
+ end
82
+
83
+ def nullify_scope(doc)
84
+ criteria.each { |key, value| doc[key] = nil }
85
+ doc
86
+ end
87
+
88
+ def apply_scope(doc)
89
+ criteria.each { |key, value| doc[key] = value }
90
+ doc
91
+ end
65
92
  end
66
93
  end
67
94
  end
@@ -8,7 +8,7 @@ module MongoMapper
8
8
  def initialize_copy(other)
9
9
  @_new = true
10
10
  @_destroyed = false
11
- default_id_value
11
+ @_id = nil
12
12
  associations.each do |name, association|
13
13
  instance_variable_set(association.ivar, nil)
14
14
  end
@@ -20,10 +20,6 @@ module MongoMapper
20
20
  clear_changes { super }
21
21
  end
22
22
 
23
- def save!(*)
24
- clear_changes { super }
25
- end
26
-
27
23
  def reload(*)
28
24
  super.tap { clear_changes }
29
25
  end
@@ -51,19 +47,14 @@ module MongoMapper
51
47
 
52
48
  def write_key(key, value)
53
49
  key = key.to_s
54
- old = read_key(key)
55
- attribute_will_change!(key) if attribute_should_change?(key, old, value)
56
- changed_attributes.delete(key) unless attribute_value_changed?(key, attribute_was(key), value)
57
- super(key, value)
58
- end
59
-
60
- def attribute_should_change?(key, old, value)
61
- attribute_changed?(key) == false && attribute_value_changed?(key, old, value)
50
+ attribute_will_change!(key) unless attribute_changed?(key)
51
+ super(key, value).tap do
52
+ changed_attributes.delete(key) unless attribute_value_changed?(key)
53
+ end
62
54
  end
63
55
 
64
- def attribute_value_changed?(key_name, old, value)
65
- value = nil if keys[key_name.to_s].number? && value.blank?
66
- old != value
56
+ def attribute_value_changed?(key_name)
57
+ attribute_was(key_name) != read_key(key_name)
67
58
  end
68
59
  end
69
60
  end
@@ -21,12 +21,12 @@ module MongoMapper
21
21
 
22
22
  def reload
23
23
  if doc = collection.find_one(:_id => id)
24
- tap do |instance|
25
- instance.class.associations.each_value do |association|
26
- get_proxy(association).reset
27
- end
28
- instance.attributes = doc
24
+ self.class.associations.each_value do |association|
25
+ get_proxy(association).reset
29
26
  end
27
+ instance_variables.each { |ivar| instance_variable_set(ivar, nil) }
28
+ self.attributes = doc
29
+ self
30
30
  else
31
31
  raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
32
32
  end
@@ -11,7 +11,7 @@ module MongoMapper
11
11
  end
12
12
 
13
13
  module InstanceMethods
14
- def run_callbacks(callback, &block)
14
+ def run_callbacks(callback, *args, &block)
15
15
  embedded_docs = []
16
16
 
17
17
  embedded_associations.each do |association|
@@ -20,12 +20,12 @@ module MongoMapper
20
20
 
21
21
  block = embedded_docs.inject(block) do |chain, doc|
22
22
  if doc.class.respond_to?("_#{callback}_callbacks")
23
- lambda { doc.run_callbacks(callback, &chain) }
23
+ lambda { doc.run_callbacks(callback, *args, &chain) }
24
24
  else
25
25
  chain
26
26
  end
27
27
  end
28
- super callback, &block
28
+ super callback, *args, &block
29
29
  end
30
30
  end
31
31
  end
@@ -47,11 +47,6 @@ module MongoMapper
47
47
  object_id_keys.include?(name.to_sym)
48
48
  end
49
49
 
50
- # API Private
51
- def can_default_id?
52
- keys['_id'].can_default_id?
53
- end
54
-
55
50
  def to_mongo(instance)
56
51
  return nil if instance.nil?
57
52
  instance.to_mongo
@@ -163,7 +158,6 @@ module MongoMapper
163
158
 
164
159
  module InstanceMethods
165
160
  def initialize(attrs={})
166
- default_id_value(attrs)
167
161
  @_new = true
168
162
  assign(attrs)
169
163
  end
@@ -171,7 +165,6 @@ module MongoMapper
171
165
  def initialize_from_database(attrs={})
172
166
  @_new = false
173
167
  load_from_database(attrs)
174
- default_id_value(attrs)
175
168
  self
176
169
  end
177
170
 
@@ -266,18 +259,6 @@ module MongoMapper
266
259
  keys.values.select { |key| key.embeddable? }
267
260
  end
268
261
 
269
- def default_id_value(attrs={})
270
- id_provided = !attrs.nil? && attrs.keys.map { |k| k.to_s }.detect { |k| k == 'id' || k == '_id' }
271
- if !id_provided && self.class.can_default_id?
272
- unless keys['_id'].default_value
273
- warn "[DEPRECATED] Custom IDs will no longer be automatically populated. If you definte your own :_id key, set a default:\n key :_id, String, :default => lambda { BSON::ObjectId.new }"
274
- warn caller.grep(/test/).join("\n")
275
- end
276
-
277
- write_key :_id, BSON::ObjectId.new
278
- end
279
- end
280
-
281
262
  private
282
263
  def load_from_database(attrs)
283
264
  return if attrs.blank?
@@ -21,10 +21,6 @@ module MongoMapper
21
21
  type.embeddable?
22
22
  end
23
23
 
24
- def can_default_id?
25
- type && (type == ObjectId || type == BSON::ObjectId || type == String)
26
- end
27
-
28
24
  def number?
29
25
  type == Integer || type == Float
30
26
  end
@@ -37,10 +37,9 @@ module MongoMapper
37
37
  initializer "mongo_mapper.prepare_dispatcher" do |app|
38
38
  # See http://groups.google.com/group/mongomapper/browse_thread/thread/68f62e8eda43b43a/4841dba76938290c
39
39
  # to_prepare is called before each request in development mode and the first request in production.
40
+
40
41
  ActionDispatch::Callbacks.to_prepare do
41
42
  unless app.config.cache_classes
42
- # Rails reloading was making descendants fill up and leak memory, these make sure they get cleared
43
- ActiveSupport::DescendantsTracker.clear
44
43
  MongoMapper::Plugins::IdentityMap.models.clear
45
44
  end
46
45
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: UTF-8
2
2
  module MongoMapper
3
- Version = '0.9.2'
3
+ Version = '0.10.0'
4
4
  end
@@ -102,6 +102,21 @@ class BelongsToProxyTest < Test::Unit::TestCase
102
102
  @comment_class.new(:name => 'Foo', :post_id => id).post.nil?.should be_true
103
103
  end
104
104
 
105
+ should "define foreign key if it doesn't exist" do
106
+ @category_class = Doc()
107
+ @post_class.belongs_to :category, :class => @category_class
108
+
109
+ @post_class.key?(:category_id).should be_true
110
+ end
111
+
112
+ should "not define foreign key if it already exists" do
113
+ @category_class = Doc()
114
+ @post_class.key :category_id, String
115
+ @post_class.belongs_to :category, :class => @category_class
116
+
117
+ @post_class.keys['category_id'].type.should == String
118
+ end
119
+
105
120
  context ":dependent" do
106
121
  setup do
107
122
  # FIXME: make use of already defined models