mongo_mapper 0.13.1 → 0.15.2

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 (133) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +61 -0
  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 +4 -0
  10. data/lib/mongo_mapper/connection.rb +16 -38
  11. data/lib/mongo_mapper/document.rb +2 -0
  12. data/lib/mongo_mapper/extensions/array.rb +14 -6
  13. data/lib/mongo_mapper/extensions/hash.rb +15 -3
  14. data/lib/mongo_mapper/extensions/object.rb +4 -0
  15. data/lib/mongo_mapper/extensions/object_id.rb +5 -1
  16. data/lib/mongo_mapper/extensions/string.rb +13 -5
  17. data/lib/mongo_mapper/plugins/accessible.rb +13 -12
  18. data/lib/mongo_mapper/plugins/associations.rb +7 -6
  19. data/lib/mongo_mapper/plugins/associations/base.rb +23 -14
  20. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +1 -1
  21. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +9 -8
  22. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +18 -11
  23. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -4
  24. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +60 -29
  25. data/lib/mongo_mapper/plugins/associations/in_foreign_array_proxy.rb +136 -0
  26. data/lib/mongo_mapper/plugins/associations/many_association.rb +4 -2
  27. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +18 -16
  28. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +55 -48
  29. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +14 -13
  30. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +7 -6
  31. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +7 -5
  32. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +14 -11
  33. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +14 -13
  34. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +12 -10
  35. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +27 -26
  36. data/lib/mongo_mapper/plugins/associations/proxy.rb +38 -27
  37. data/lib/mongo_mapper/plugins/associations/single_association.rb +5 -4
  38. data/lib/mongo_mapper/plugins/callbacks.rb +13 -0
  39. data/lib/mongo_mapper/plugins/counter_cache.rb +23 -4
  40. data/lib/mongo_mapper/plugins/dirty.rb +29 -37
  41. data/lib/mongo_mapper/plugins/document.rb +1 -1
  42. data/lib/mongo_mapper/plugins/dynamic_querying.rb +10 -9
  43. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +18 -17
  44. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +2 -1
  45. data/lib/mongo_mapper/plugins/embedded_document.rb +2 -2
  46. data/lib/mongo_mapper/plugins/identity_map.rb +4 -2
  47. data/lib/mongo_mapper/plugins/indexes.rb +14 -7
  48. data/lib/mongo_mapper/plugins/keys.rb +164 -159
  49. data/lib/mongo_mapper/plugins/keys/key.rb +27 -16
  50. data/lib/mongo_mapper/plugins/keys/static.rb +45 -0
  51. data/lib/mongo_mapper/plugins/modifiers.rb +64 -38
  52. data/lib/mongo_mapper/plugins/partial_updates.rb +86 -0
  53. data/lib/mongo_mapper/plugins/persistence.rb +13 -8
  54. data/lib/mongo_mapper/plugins/protected.rb +6 -5
  55. data/lib/mongo_mapper/plugins/querying.rb +85 -42
  56. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +20 -15
  57. data/lib/mongo_mapper/plugins/safe.rb +10 -4
  58. data/lib/mongo_mapper/plugins/scopes.rb +94 -7
  59. data/lib/mongo_mapper/plugins/stats.rb +1 -3
  60. data/lib/mongo_mapper/plugins/strong_parameters.rb +26 -0
  61. data/lib/mongo_mapper/plugins/timestamps.rb +1 -0
  62. data/lib/mongo_mapper/plugins/validations.rb +0 -0
  63. data/lib/mongo_mapper/railtie.rb +1 -0
  64. data/lib/mongo_mapper/utils.rb +2 -2
  65. data/lib/mongo_mapper/version.rb +1 -1
  66. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +12 -13
  67. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +9 -9
  68. data/spec/examples.txt +1728 -0
  69. data/spec/functional/accessible_spec.rb +19 -13
  70. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +13 -13
  71. data/spec/functional/associations/belongs_to_proxy_spec.rb +54 -20
  72. data/spec/functional/associations/in_array_proxy_spec.rb +145 -10
  73. data/spec/functional/associations/in_foreign_array_proxy_spec.rb +321 -0
  74. data/spec/functional/associations/many_documents_as_proxy_spec.rb +6 -6
  75. data/spec/functional/associations/many_documents_proxy_spec.rb +85 -14
  76. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +13 -13
  77. data/spec/functional/associations/many_embedded_proxy_spec.rb +1 -1
  78. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +4 -4
  79. data/spec/functional/associations/one_as_proxy_spec.rb +10 -10
  80. data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +9 -9
  81. data/spec/functional/associations/one_embedded_proxy_spec.rb +31 -3
  82. data/spec/functional/associations/one_proxy_spec.rb +21 -11
  83. data/spec/functional/associations_spec.rb +3 -3
  84. data/spec/functional/binary_spec.rb +2 -2
  85. data/spec/functional/caching_spec.rb +8 -15
  86. data/spec/functional/callbacks_spec.rb +89 -2
  87. data/spec/functional/counter_cache_spec.rb +89 -0
  88. data/spec/functional/dirty_spec.rb +84 -46
  89. data/spec/functional/dirty_with_callbacks_spec.rb +59 -0
  90. data/spec/functional/document_spec.rb +2 -5
  91. data/spec/functional/dumpable_spec.rb +1 -1
  92. data/spec/functional/embedded_document_spec.rb +17 -17
  93. data/spec/functional/identity_map_spec.rb +29 -16
  94. data/spec/functional/indexes_spec.rb +19 -18
  95. data/spec/functional/keys_spec.rb +55 -28
  96. data/spec/functional/logger_spec.rb +3 -3
  97. data/spec/functional/modifiers_spec.rb +81 -19
  98. data/spec/functional/partial_updates_spec.rb +577 -0
  99. data/spec/functional/protected_spec.rb +14 -14
  100. data/spec/functional/querying_spec.rb +77 -28
  101. data/spec/functional/safe_spec.rb +23 -27
  102. data/spec/functional/sci_spec.rb +9 -9
  103. data/spec/functional/scopes_spec.rb +323 -2
  104. data/spec/functional/static_keys_spec.rb +153 -0
  105. data/spec/functional/stats_spec.rb +28 -16
  106. data/spec/functional/strong_parameters_spec.rb +49 -0
  107. data/spec/functional/touch_spec.rb +1 -1
  108. data/spec/functional/validations_spec.rb +51 -57
  109. data/spec/quality_spec.rb +2 -2
  110. data/spec/spec_helper.rb +37 -9
  111. data/spec/support/matchers.rb +5 -14
  112. data/spec/unit/associations/base_spec.rb +12 -12
  113. data/spec/unit/associations/belongs_to_association_spec.rb +2 -2
  114. data/spec/unit/associations/many_association_spec.rb +2 -2
  115. data/spec/unit/associations/one_association_spec.rb +2 -2
  116. data/spec/unit/associations/proxy_spec.rb +26 -20
  117. data/spec/unit/clone_spec.rb +1 -1
  118. data/spec/unit/document_spec.rb +8 -8
  119. data/spec/unit/dynamic_finder_spec.rb +8 -8
  120. data/spec/unit/embedded_document_spec.rb +18 -19
  121. data/spec/unit/extensions_spec.rb +17 -17
  122. data/spec/unit/identity_map_middleware_spec.rb +65 -96
  123. data/spec/unit/key_spec.rb +28 -26
  124. data/spec/unit/keys_spec.rb +20 -11
  125. data/spec/unit/mongo_mapper_spec.rb +38 -85
  126. data/spec/unit/serialization_spec.rb +1 -1
  127. data/spec/unit/time_zones_spec.rb +2 -2
  128. data/spec/unit/validations_spec.rb +46 -33
  129. metadata +56 -32
  130. data/README.rdoc +0 -59
  131. data/lib/mongo_mapper/connections/10gen.rb +0 -0
  132. data/lib/mongo_mapper/connections/moped.rb +0 -0
  133. data/lib/mongo_mapper/extensions/ordered_hash.rb +0 -23
@@ -6,7 +6,7 @@ module MongoMapper
6
6
  attr_reader :name, :options, :query_options
7
7
 
8
8
  # Options that should not be considered MongoDB query options/criteria
9
- AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :in, :polymorphic, :autosave, :touch, :counter_cache]
9
+ AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :in, :from, :polymorphic, :autosave, :touch, :counter_cache, :ordered]
10
10
 
11
11
  def initialize(name, options={}, &extension)
12
12
  @name, @options, @query_options, @original_options = name.to_sym, {}, {}, options
@@ -28,13 +28,17 @@ module MongoMapper
28
28
  end
29
29
 
30
30
  def as?
31
- !!@options[:as]
31
+ !in_foreign_array? && !!@options[:as]
32
32
  end
33
33
 
34
34
  def in_array?
35
35
  !!@options[:in]
36
36
  end
37
37
 
38
+ def in_foreign_array?
39
+ !!@options[:from]
40
+ end
41
+
38
42
  def embeddable?
39
43
  klass.embeddable?
40
44
  end
@@ -47,6 +51,10 @@ module MongoMapper
47
51
  !!@options[:counter_cache]
48
52
  end
49
53
 
54
+ def ordered?
55
+ !!@options[:ordered]
56
+ end
57
+
50
58
  def type_key_name
51
59
  "_type"
52
60
  end
@@ -76,22 +84,23 @@ module MongoMapper
76
84
  def setup(model)
77
85
  end
78
86
 
79
- private
80
- def separate_options_and_conditions
81
- @original_options.each_pair do |key, value|
82
- if AssociationOptions.include?(key)
83
- @options[key] = value
84
- else
85
- @query_options[key] = value
86
- end
87
+ private
88
+
89
+ def separate_options_and_conditions
90
+ @original_options.each_pair do |key, value|
91
+ if AssociationOptions.include?(key)
92
+ @options[key] = value
93
+ else
94
+ @query_options[key] = value
87
95
  end
88
96
  end
97
+ end
89
98
 
90
- def modularized_extensions(*extensions)
91
- extensions.flatten.compact.map do |extension|
92
- Proc === extension ? Module.new(&extension) : extension
93
- end
99
+ def modularized_extensions(*extensions)
100
+ extensions.flatten.compact.map do |extension|
101
+ Proc === extension ? Module.new(&extension) : extension
94
102
  end
103
+ end
95
104
  end
96
105
  end
97
106
  end
@@ -29,7 +29,7 @@ module MongoMapper
29
29
 
30
30
  def add_touch_callbacks
31
31
  name = self.name
32
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
32
+ method_name = :"belongs_to_touch_after_save_or_destroy_for_#{name}"
33
33
  touch = options.fetch(:touch)
34
34
 
35
35
  @model.send(:define_method, method_name) do
@@ -19,15 +19,16 @@ module MongoMapper
19
19
  @target
20
20
  end
21
21
 
22
- protected
23
- def find_target
24
- return nil if association_class.nil? || proxy_owner[association.foreign_key].nil?
25
- association_class.find_by_id(proxy_owner[association.foreign_key])
26
- end
22
+ protected
27
23
 
28
- def association_class
29
- proxy_owner[association.type_key_name] ? proxy_owner[association.type_key_name].constantize : nil
30
- end
24
+ def find_target
25
+ return nil if association_class.nil? || proxy_owner[association.foreign_key].nil?
26
+ association_class.find_by_id(proxy_owner[association.foreign_key])
27
+ end
28
+
29
+ def association_class
30
+ proxy_owner[association.type_key_name] ? proxy_owner[association.type_key_name].constantize : nil
31
+ end
31
32
  end
32
33
  end
33
34
  end
@@ -34,18 +34,25 @@ module MongoMapper
34
34
  @target.save(options) if @target
35
35
  end
36
36
 
37
- protected
38
- def find_target
39
- return nil if proxy_owner[association.foreign_key].nil?
40
- klass.find_by_id(proxy_owner[association.foreign_key])
41
- end
37
+ protected
42
38
 
43
- def instantiate_target(instantiator, attrs={}, &block)
44
- @target = klass.send(instantiator, attrs, &block)
45
- proxy_owner[association.foreign_key] = @target.id
46
- loaded
47
- @target
48
- end
39
+ def find_target
40
+ return nil if proxy_owner[association.foreign_key].nil?
41
+ klass.find_by_id(proxy_owner[association.foreign_key])
42
+ end
43
+
44
+ def instantiate_target(instantiator, attrs={}, &block)
45
+ @target = klass.send(instantiator, attrs, &block)
46
+ proxy_owner[association.foreign_key] = @target.id
47
+ loaded
48
+ @target
49
+ end
50
+
51
+ private
52
+
53
+ def stale_target?
54
+ loaded? && @target && proxy_owner[association.foreign_key] != @target.id
55
+ end
49
56
  end
50
57
  end
51
58
  end
@@ -34,10 +34,10 @@ module MongoMapper
34
34
  @target.each { |doc| doc.persist(options) } if @target
35
35
  end
36
36
 
37
- private
38
- def assign_references(*docs)
39
- docs.each { |doc| doc._parent_document = proxy_owner }
40
- end
37
+ private
38
+ def assign_references(*docs)
39
+ docs.each { |doc| doc._parent_document = proxy_owner }
40
+ end
41
41
  end
42
42
  end
43
43
  end
@@ -6,11 +6,11 @@ module MongoMapper
6
6
  include DynamicQuerying::ClassMethods
7
7
 
8
8
  def find(*args)
9
- query.find(*scoped_ids(args))
9
+ order_results(query.find(*scoped_ids(args)))
10
10
  end
11
11
 
12
12
  def find!(*args)
13
- query.find!(*scoped_ids(args))
13
+ order_results(query.find!(*scoped_ids(args)))
14
14
  end
15
15
 
16
16
  def paginate(options)
@@ -20,17 +20,29 @@ module MongoMapper
20
20
 
21
21
  def all(options={})
22
22
  return [] if ids.blank?
23
- query(options).all
23
+ order_results(query(options).all)
24
24
  end
25
25
 
26
26
  def first(options={})
27
27
  return nil if ids.blank?
28
- query(options).first
28
+
29
+ if ordered?
30
+ ids = find_ordered_ids(options)
31
+ find!(ids.first) if ids.any?
32
+ else
33
+ query(options).first
34
+ end
29
35
  end
30
36
 
31
37
  def last(options={})
32
38
  return nil if ids.blank?
33
- query(options).last
39
+
40
+ if ordered?
41
+ ids = find_ordered_ids(options)
42
+ find!(ids.last) if ids.any?
43
+ else
44
+ query(options).last
45
+ end
34
46
  end
35
47
 
36
48
  def count(options={})
@@ -99,35 +111,54 @@ module MongoMapper
99
111
  reset
100
112
  end
101
113
 
102
- private
103
- def query(options={})
104
- klass.
105
- query(association.query_options).
106
- amend(options).
107
- amend(criteria)
108
- end
114
+ private
109
115
 
110
- def criteria
111
- {:_id => ids}
112
- end
116
+ def query(options={})
117
+ klass.
118
+ query(association.query_options).
119
+ amend(options).
120
+ amend(criteria)
121
+ end
113
122
 
114
- def scoped_ids(args)
115
- valid = args.flatten.select do |id|
116
- id = ObjectId.to_mongo(id) if klass.using_object_id?
117
- ids.include?(id)
118
- end
119
- valid.empty? ? nil : valid
120
- end
123
+ def criteria
124
+ {:_id => ids}
125
+ end
121
126
 
122
- def find_target
123
- return [] if ids.blank?
124
- all
127
+ def scoped_ids(args)
128
+ valid = args.flatten.select do |id|
129
+ id = ObjectId.to_mongo(id) if klass.using_object_id?
130
+ ids.include?(id)
125
131
  end
132
+ valid.empty? ? nil : valid
133
+ end
134
+
135
+ def find_ordered_ids(options={})
136
+ return ids if options.empty?
137
+
138
+ matched_ids = klass.collection.distinct(:_id, query(options).criteria.to_hash)
139
+ matched_ids.sort_by! { |matched_id| ids.index(matched_id) }
140
+ end
141
+
142
+ def find_target
143
+ return [] if ids.blank?
144
+ all
145
+ end
146
+
147
+ def ids
148
+ proxy_owner[options[:in]]
149
+ end
150
+
151
+ def order_results(objects)
152
+ return objects if !ordered?
153
+ return objects unless objects.respond_to?(:to_a) && objects.respond_to?(:sort_by)
154
+ objects.sort_by { |obj| ids.index(obj.id) }
155
+ end
156
+
157
+ def ordered?
158
+ association.ordered?
159
+ end
126
160
 
127
- def ids
128
- proxy_owner[options[:in]]
129
- end
130
161
  end
131
162
  end
132
163
  end
133
- end
164
+ end
@@ -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
@@ -3,25 +3,27 @@ module MongoMapper
3
3
  module Plugins
4
4
  module Associations
5
5
  class ManyDocumentsAsProxy < ManyDocumentsProxy
6
- protected
7
- def criteria
8
- {type_key_name => proxy_owner.class.name, id_key_name => proxy_owner.id}
9
- end
6
+ protected
10
7
 
11
- def apply_scope(doc)
12
- ensure_owner_saved
13
- criteria.each { |key, value| doc[key] = value }
14
- doc
15
- end
8
+ def criteria
9
+ {type_key_name => proxy_owner.class.name, id_key_name => proxy_owner.id}
10
+ end
16
11
 
17
- private
18
- def type_key_name
19
- "#{options[:as]}_type"
20
- end
12
+ def apply_scope(doc)
13
+ ensure_owner_saved
14
+ criteria.each { |key, value| doc[key] = value }
15
+ doc
16
+ end
21
17
 
22
- def id_key_name
23
- "#{options[:as]}_id"
24
- end
18
+ private
19
+
20
+ def type_key_name
21
+ "#{options[:as]}_type"
22
+ end
23
+
24
+ def id_key_name
25
+ "#{options[:as]}_id"
26
+ end
25
27
  end
26
28
  end
27
29
  end