mongo_mapper 0.14.0.rc1 → 0.15.3

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.
Files changed (95) hide show
  1. checksums.yaml +5 -13
  2. data/LICENSE +1 -1
  3. data/{README.rdoc → README.md} +26 -21
  4. data/examples/keys.rb +1 -1
  5. data/examples/modifiers/set.rb +1 -1
  6. data/examples/querying.rb +1 -1
  7. data/examples/safe.rb +2 -2
  8. data/examples/scopes.rb +1 -1
  9. data/lib/mongo_mapper.rb +3 -0
  10. data/lib/mongo_mapper/connection.rb +16 -38
  11. data/lib/mongo_mapper/extensions/object_id.rb +5 -1
  12. data/lib/mongo_mapper/plugins/accessible.rb +1 -1
  13. data/lib/mongo_mapper/plugins/associations/base.rb +10 -2
  14. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +1 -1
  15. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +6 -0
  16. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +36 -6
  17. data/lib/mongo_mapper/plugins/associations/in_foreign_array_proxy.rb +136 -0
  18. data/lib/mongo_mapper/plugins/associations/many_association.rb +4 -2
  19. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +3 -1
  20. data/lib/mongo_mapper/plugins/associations/proxy.rb +11 -3
  21. data/lib/mongo_mapper/plugins/associations/single_association.rb +5 -4
  22. data/lib/mongo_mapper/plugins/dirty.rb +29 -37
  23. data/lib/mongo_mapper/plugins/document.rb +1 -1
  24. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +1 -1
  25. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +1 -0
  26. data/lib/mongo_mapper/plugins/embedded_document.rb +2 -2
  27. data/lib/mongo_mapper/plugins/identity_map.rb +3 -1
  28. data/lib/mongo_mapper/plugins/indexes.rb +13 -6
  29. data/lib/mongo_mapper/plugins/keys.rb +12 -7
  30. data/lib/mongo_mapper/plugins/keys/key.rb +13 -8
  31. data/lib/mongo_mapper/plugins/modifiers.rb +39 -14
  32. data/lib/mongo_mapper/plugins/persistence.rb +6 -2
  33. data/lib/mongo_mapper/plugins/querying.rb +9 -3
  34. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +6 -6
  35. data/lib/mongo_mapper/plugins/safe.rb +10 -4
  36. data/lib/mongo_mapper/plugins/scopes.rb +19 -3
  37. data/lib/mongo_mapper/plugins/stats.rb +1 -3
  38. data/lib/mongo_mapper/plugins/strong_parameters.rb +26 -0
  39. data/lib/mongo_mapper/railtie.rb +1 -0
  40. data/lib/mongo_mapper/utils.rb +2 -2
  41. data/lib/mongo_mapper/version.rb +1 -1
  42. data/spec/examples.txt +1729 -0
  43. data/spec/functional/accessible_spec.rb +7 -1
  44. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +2 -2
  45. data/spec/functional/associations/belongs_to_proxy_spec.rb +55 -5
  46. data/spec/functional/associations/in_array_proxy_spec.rb +149 -14
  47. data/spec/functional/associations/in_foreign_array_proxy_spec.rb +321 -0
  48. data/spec/functional/associations/many_documents_as_proxy_spec.rb +6 -6
  49. data/spec/functional/associations/many_documents_proxy_spec.rb +22 -22
  50. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +2 -2
  51. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +4 -4
  52. data/spec/functional/associations/one_as_proxy_spec.rb +8 -8
  53. data/spec/functional/associations/one_embedded_proxy_spec.rb +28 -0
  54. data/spec/functional/associations/one_proxy_spec.rb +19 -9
  55. data/spec/functional/associations_spec.rb +3 -3
  56. data/spec/functional/binary_spec.rb +2 -2
  57. data/spec/functional/caching_spec.rb +15 -22
  58. data/spec/functional/callbacks_spec.rb +2 -2
  59. data/spec/functional/counter_cache_spec.rb +10 -10
  60. data/spec/functional/dirty_spec.rb +48 -10
  61. data/spec/functional/dirty_with_callbacks_spec.rb +59 -0
  62. data/spec/functional/document_spec.rb +5 -8
  63. data/spec/functional/dumpable_spec.rb +1 -1
  64. data/spec/functional/embedded_document_spec.rb +5 -5
  65. data/spec/functional/identity_map_spec.rb +8 -8
  66. data/spec/functional/indexes_spec.rb +19 -18
  67. data/spec/functional/keys_spec.rb +51 -33
  68. data/spec/functional/logger_spec.rb +2 -2
  69. data/spec/functional/modifiers_spec.rb +81 -19
  70. data/spec/functional/partial_updates_spec.rb +8 -8
  71. data/spec/functional/protected_spec.rb +1 -1
  72. data/spec/functional/querying_spec.rb +70 -22
  73. data/spec/functional/safe_spec.rb +23 -27
  74. data/spec/functional/sci_spec.rb +7 -7
  75. data/spec/functional/scopes_spec.rb +89 -1
  76. data/spec/functional/static_keys_spec.rb +2 -2
  77. data/spec/functional/stats_spec.rb +28 -12
  78. data/spec/functional/strong_parameters_spec.rb +49 -0
  79. data/spec/functional/validations_spec.rb +8 -16
  80. data/spec/quality_spec.rb +1 -1
  81. data/spec/spec_helper.rb +39 -8
  82. data/spec/support/matchers.rb +1 -1
  83. data/spec/unit/associations/proxy_spec.rb +13 -5
  84. data/spec/unit/clone_spec.rb +1 -1
  85. data/spec/unit/document_spec.rb +3 -3
  86. data/spec/unit/embedded_document_spec.rb +4 -5
  87. data/spec/unit/extensions_spec.rb +2 -2
  88. data/spec/unit/identity_map_middleware_spec.rb +65 -96
  89. data/spec/unit/key_spec.rb +16 -17
  90. data/spec/unit/keys_spec.rb +17 -8
  91. data/spec/unit/mongo_mapper_spec.rb +41 -88
  92. data/spec/unit/rails_spec.rb +2 -2
  93. data/spec/unit/validations_spec.rb +18 -18
  94. metadata +53 -31
  95. data/lib/mongo_mapper/extensions/ordered_hash.rb +0 -23
@@ -0,0 +1,136 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Associations
5
+ class InForeignArrayProxy < Collection
6
+ include DynamicQuerying::ClassMethods
7
+
8
+ def find(*args)
9
+ query.find(*scoped_ids(args))
10
+ end
11
+
12
+ def find!(*args)
13
+ query.find!(*scoped_ids(args))
14
+ end
15
+
16
+ def paginate(options)
17
+ query.paginate(options)
18
+ end
19
+
20
+ def all(options={})
21
+ query(options).all
22
+ end
23
+
24
+ def first(options={})
25
+ query(options).first
26
+ end
27
+
28
+ def last(options={})
29
+ query(options).last
30
+ end
31
+
32
+ def count(options={})
33
+ query(options).count
34
+ end
35
+
36
+ def destroy_all(options={})
37
+ all(options).each do |doc|
38
+ doc.destroy
39
+ end
40
+ reset
41
+ end
42
+
43
+ def delete_all(options={})
44
+ docs = query(options).fields(:_id).all
45
+ klass.delete(docs.map { |d| d.id })
46
+ reset
47
+ end
48
+
49
+ def nullify
50
+ replace([])
51
+ reset
52
+ end
53
+
54
+ def create(attrs={})
55
+ doc = klass.create(attrs)
56
+ if doc.persisted?
57
+ inverse_association(doc) << proxy_owner
58
+ doc.save
59
+ reset
60
+ end
61
+ doc
62
+ end
63
+
64
+ def create!(attrs={})
65
+ doc = klass.create!(attrs)
66
+
67
+ if doc.persisted?
68
+ inverse_association(doc) << proxy_owner
69
+ doc.save
70
+ reset
71
+ end
72
+ doc
73
+ end
74
+
75
+ def <<(*docs)
76
+ flatten_deeper(docs).each do |doc|
77
+ inverse_association(doc) << proxy_owner
78
+ doc.save
79
+ end
80
+ reset
81
+ end
82
+ alias_method :push, :<<
83
+ alias_method :concat, :<<
84
+
85
+ def replace(docs)
86
+ doc_ids = docs.map do |doc|
87
+ doc.save unless doc.persisted?
88
+ inverse_association(doc) << proxy_owner
89
+ doc.save
90
+ doc.id
91
+ end
92
+
93
+ replace_selector = { options[:from] => proxy_owner.id }
94
+ unless doc_ids.empty?
95
+ replace_selector[:_id] = {"$not" => {"$in" => doc_ids}}
96
+ end
97
+
98
+ klass.collection.update_many(replace_selector, {
99
+ "$pull" => { options[:from] => proxy_owner.id }
100
+ })
101
+
102
+ reset
103
+ end
104
+
105
+ private
106
+
107
+ def query(options={})
108
+ klass.query({}.
109
+ merge(association.query_options).
110
+ merge(options).
111
+ merge(criteria))
112
+ end
113
+
114
+ def criteria
115
+ {options[:from] => proxy_owner.id}
116
+ end
117
+
118
+ def scoped_ids(args)
119
+ valid = args.flatten.map do |id|
120
+ id = ObjectId.to_mongo(id) if klass.using_object_id?
121
+ id
122
+ end
123
+ valid.empty? ? nil : valid
124
+ end
125
+
126
+ def find_target
127
+ all
128
+ end
129
+
130
+ def inverse_association(doc)
131
+ doc.send(options[:as].to_s.pluralize)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -17,6 +17,8 @@ module MongoMapper
17
17
  ManyPolymorphicProxy
18
18
  elsif as?
19
19
  ManyDocumentsAsProxy
20
+ elsif in_foreign_array?
21
+ InForeignArrayProxy
20
22
  elsif in_array?
21
23
  InArrayProxy
22
24
  else
@@ -26,7 +28,7 @@ module MongoMapper
26
28
  end
27
29
 
28
30
  def setup(model)
29
- model.associations_module.module_eval <<-end_eval
31
+ model.associations_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1)
30
32
  def #{name}
31
33
  get_proxy(associations[#{name.inspect}])
32
34
  end
@@ -60,4 +62,4 @@ module MongoMapper
60
62
  end
61
63
  end
62
64
  end
63
- end
65
+ end
@@ -11,7 +11,9 @@ module MongoMapper
11
11
  end
12
12
 
13
13
  def replace(doc)
14
- if doc.respond_to?(:attributes)
14
+ if doc.instance_of?(klass)
15
+ @target = doc
16
+ elsif doc.respond_to?(:attributes)
15
17
  @target = klass.load(doc.attributes, true)
16
18
  else
17
19
  @target = klass.load(doc, true)
@@ -13,7 +13,6 @@ module MongoMapper
13
13
 
14
14
  attr_reader :proxy_owner, :association, :target
15
15
 
16
- alias :proxy_target :target
17
16
  alias :proxy_association :association
18
17
 
19
18
  def_delegators :proxy_association, :klass, :options
@@ -100,10 +99,15 @@ module MongoMapper
100
99
  end
101
100
  end
102
101
 
102
+ def read
103
+ load_target
104
+ @target
105
+ end
106
+
103
107
  protected
104
108
 
105
109
  def load_target
106
- unless loaded?
110
+ if !loaded? || stale_target?
107
111
  if @target.is_a?(Array) && @target.any?
108
112
  @target = find_target + @target.find_all { |record| !record.persisted? }
109
113
  else
@@ -130,9 +134,13 @@ module MongoMapper
130
134
 
131
135
  private
132
136
 
137
+ def stale_target?
138
+ false
139
+ end
140
+
133
141
  def method_missing(method, *args, &block)
134
142
  if load_target
135
- target.send(method, *args, &block)
143
+ target.public_send(method, *args, &block)
136
144
  end
137
145
  end
138
146
  end
@@ -5,10 +5,11 @@ module MongoMapper
5
5
  class SingleAssociation < Base
6
6
  def setup(model)
7
7
  @model = model
8
- model.associations_module.module_eval <<-end_eval
8
+
9
+ model.associations_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1)
9
10
  def #{name}
10
11
  proxy = get_proxy(associations[#{name.inspect}])
11
- proxy.nil? ? nil : proxy
12
+ proxy.nil? ? nil : proxy.read
12
13
  end
13
14
 
14
15
  def #{name}=(value)
@@ -20,7 +21,7 @@ module MongoMapper
20
21
  end
21
22
 
22
23
  proxy.replace(value)
23
- value
24
+ proxy.read
24
25
  end
25
26
 
26
27
  def #{name}?
@@ -43,4 +44,4 @@ module MongoMapper
43
44
  end
44
45
  end
45
46
  end
46
- end
47
+ end
@@ -3,58 +3,50 @@ module MongoMapper
3
3
  module Plugins
4
4
  module Dirty
5
5
  extend ActiveSupport::Concern
6
-
7
6
  include ::ActiveModel::Dirty
8
7
 
9
- def initialize(*)
10
- # never register initial id assignment as a change
11
- # Chaining super into tap breaks implicit block passing in Ruby 1.8
12
- doc = super
13
- doc.tap { changed_attributes.delete('_id') }
14
- end
15
-
16
- def save(*)
17
- clear_changes { super }
18
- end
19
-
20
- def reload(*)
21
- doc = super
22
- doc.tap { clear_changes }
23
- end
24
-
25
- def clear_changes
26
- previous = changes
27
- (block_given? ? yield : true).tap do |result|
28
- unless result == false #failed validation; nil is OK.
29
- @previously_changed = previous
30
- changed_attributes.clear
8
+ module ClassMethods
9
+ def create_accessors_for(key)
10
+ super.tap do
11
+ define_attribute_methods([key.name])
31
12
  end
32
13
  end
33
14
  end
34
15
 
35
- protected
16
+ def save_to_collection(*)
17
+ super.tap do
18
+ changes_applied
19
+ end
20
+ end
36
21
 
37
- # We don't call super here to avoid invoking #attributes, which builds a whole new hash per call.
38
- def attribute_method?(attr_name)
39
- keys.key?(attr_name) || !embedded_associations.detect {|a| a.name == attr_name }.nil?
22
+ def reload!
23
+ super.tap do
24
+ clear_changes_information
25
+ end
40
26
  end
41
27
 
42
28
  private
43
29
 
44
- def write_key(key, value)
45
- key = unalias_key(key)
46
- if !keys.key?(key)
30
+ def write_key(key_name, value)
31
+ key_name = unalias_key(key_name)
32
+
33
+ if !keys.key?(key_name)
47
34
  super
48
35
  else
49
- attribute_will_change!(key) unless attribute_changed?(key)
50
- super.tap do
51
- changed_attributes.delete(key) unless attribute_value_changed?(key)
36
+ # find the MongoMapper::Plugins::Keys::Key
37
+ _, key = keys.detect { |n, v| n == key_name }
38
+
39
+ # typecast to the new value
40
+ old_value = read_key(key_name)
41
+ new_value = key.get(key.set(value))
42
+
43
+ # only mark changed if really changed value (after typecasting)
44
+ unless old_value == new_value
45
+ attribute_will_change!(key_name)
52
46
  end
53
- end
54
- end
55
47
 
56
- def attribute_value_changed?(key_name)
57
- changed_attributes[key_name] != read_key(key_name)
48
+ super
49
+ end
58
50
  end
59
51
  end
60
52
  end
@@ -19,7 +19,7 @@ module MongoMapper
19
19
  end
20
20
 
21
21
  def reload
22
- if doc = collection.find_one(:_id => id)
22
+ if doc = collection.find({:_id => id},{limit: -1}).first
23
23
  self.class.associations.each_value do |association|
24
24
  get_proxy(association).reset
25
25
  end
@@ -42,4 +42,4 @@ module MongoMapper
42
42
  end
43
43
  end
44
44
  end
45
- end
45
+ end
@@ -44,6 +44,7 @@ module MongoMapper
44
44
  definition.each do |prefix, suffixes|
45
45
  suffixes.each do |suffix|
46
46
  callback = "%s_%s" % [prefix, suffix]
47
+
47
48
  class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
48
49
  class << self
49
50
  alias_method :__original_#{callback}, :#{callback}
@@ -41,12 +41,12 @@ module MongoMapper
41
41
 
42
42
  def persist(options={})
43
43
  @_new = false
44
- clear_changes if respond_to?(:clear_changes)
44
+ changes_applied if respond_to?(:changes_applied)
45
45
  save_to_collection(options)
46
46
  end
47
47
 
48
48
  def _root_document
49
- @_root_document ||= _parent_document.try(:_root_document)
49
+ _parent_document.try(:_root_document)
50
50
  end
51
51
  end
52
52
  end
@@ -133,10 +133,12 @@ module PluckyMethods
133
133
  end
134
134
 
135
135
  def find_each(opts={})
136
+ return super if !block_given?
137
+
136
138
  query = clone.amend(opts)
137
139
  super(opts) do |doc|
138
140
  doc.remove_from_identity_map if doc && query.fields?
139
- yield doc if block_given?
141
+ yield doc
140
142
  end
141
143
  end
142
144
  end
@@ -6,19 +6,20 @@ module MongoMapper
6
6
 
7
7
  module ClassMethods
8
8
  def ensure_index(spec, options = {})
9
- collection.ensure_index dealias_options(spec), options
9
+ #TODO: should we emulate the mongo 1.x behaviour of caching attempts to create indexes?
10
+ collection.indexes.create_one dealias_options(spec), options
10
11
  end
11
12
 
12
13
  def create_index(spec, options = {})
13
- collection.create_index dealias_options(spec), options
14
+ collection.indexes.create_one dealias_options(spec), options
14
15
  end
15
16
 
16
17
  def drop_index(name)
17
- collection.drop_index name
18
+ collection.indexes.drop_one name
18
19
  end
19
20
 
20
21
  def drop_indexes
21
- collection.drop_indexes
22
+ collection.indexes.drop_all
22
23
  end
23
24
 
24
25
  private
@@ -26,11 +27,17 @@ module MongoMapper
26
27
  def dealias_options(options)
27
28
  case options
28
29
  when Symbol, String
29
- abbr(options)
30
+ {abbr(options) => 1}
30
31
  when Hash
31
32
  dealias_keys(options)
32
33
  when Array
33
- options.map {|o| dealias_options(o) }
34
+ if options.first.is_a?(Hash)
35
+ options.map {|o| dealias_options(o) }
36
+ elsif options.first.is_a?(Array) # [[:foo, 1], [:bar, 1]]
37
+ options.inject({}) {|acc, tuple| acc.merge(dealias_options(tuple))}
38
+ else
39
+ dealias_keys(Hash[*options])
40
+ end
34
41
  else
35
42
  options
36
43
  end
@@ -144,9 +144,8 @@ module MongoMapper
144
144
  end
145
145
 
146
146
  def create_accessors_for(key)
147
- accessors = ""
148
147
  if key.read_accessor?
149
- accessors << <<-end_eval
148
+ accessors_module.module_eval(<<-end_eval, __FILE__, __LINE__+1)
150
149
  def #{key.name}
151
150
  read_key(:#{key.name})
152
151
  end
@@ -158,7 +157,7 @@ module MongoMapper
158
157
  end
159
158
 
160
159
  if key.write_accessor?
161
- accessors << <<-end_eval
160
+ accessors_module.module_eval(<<-end_eval, __FILE__, __LINE__+1)
162
161
  def #{key.name}=(value)
163
162
  write_key(:#{key.name}, value)
164
163
  end
@@ -166,7 +165,7 @@ module MongoMapper
166
165
  end
167
166
 
168
167
  if key.predicate_accessor?
169
- accessors << <<-end_eval
168
+ accessors_module.module_eval(<<-end_eval, __FILE__, __LINE__+1)
170
169
  def #{key.name}?
171
170
  read_key(:#{key.name}).present?
172
171
  end
@@ -179,7 +178,6 @@ module MongoMapper
179
178
  end
180
179
  end
181
180
 
182
- accessors_module.module_eval accessors
183
181
  include accessors_module
184
182
  end
185
183
 
@@ -295,8 +293,16 @@ module MongoMapper
295
293
  end
296
294
  end
297
295
 
296
+ # NOTE: We can't use alias_method here as we need the #attributes=
297
+ # superclass method to get called (for example:
298
+ # MongoMapper::Plugins::Accessible filters non-permitted parameters
299
+ # through `attributes=`
300
+ def assign_attributes(new_attributes)
301
+ self.attributes = new_attributes
302
+ end
303
+
298
304
  def to_mongo(include_abbreviatons = true)
299
- BSON::OrderedHash.new.tap do |attrs|
305
+ Hash.new.tap do |attrs|
300
306
  self.class.unaliased_keys.each do |name, key|
301
307
  value = self.read_key(key.name)
302
308
  if key.type == ObjectId || !value.nil?
@@ -449,7 +455,6 @@ module MongoMapper
449
455
  else
450
456
  @_dynamic_attributes[key.name.to_sym] = as_typecast
451
457
  end
452
- @attributes = nil
453
458
  value
454
459
  end
455
460