mongo_mapper 0.14.0 → 0.15.4

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 (120) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +72 -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 +19 -17
  10. data/lib/mongo_mapper/connection.rb +16 -38
  11. data/lib/mongo_mapper/extensions/array.rb +1 -1
  12. data/lib/mongo_mapper/extensions/binary.rb +1 -1
  13. data/lib/mongo_mapper/extensions/date.rb +1 -1
  14. data/lib/mongo_mapper/extensions/float.rb +1 -1
  15. data/lib/mongo_mapper/extensions/hash.rb +1 -1
  16. data/lib/mongo_mapper/extensions/nil_class.rb +2 -2
  17. data/lib/mongo_mapper/extensions/object.rb +1 -1
  18. data/lib/mongo_mapper/extensions/object_id.rb +6 -2
  19. data/lib/mongo_mapper/extensions/set.rb +1 -1
  20. data/lib/mongo_mapper/extensions/string.rb +1 -1
  21. data/lib/mongo_mapper/plugins/accessible.rb +1 -1
  22. data/lib/mongo_mapper/plugins/associations/base.rb +10 -2
  23. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +1 -1
  24. data/lib/mongo_mapper/plugins/associations/many_association.rb +6 -5
  25. data/lib/mongo_mapper/plugins/associations/{belongs_to_polymorphic_proxy.rb → proxy/belongs_to_polymorphic_proxy.rb} +0 -0
  26. data/lib/mongo_mapper/plugins/associations/{belongs_to_proxy.rb → proxy/belongs_to_proxy.rb} +6 -0
  27. data/lib/mongo_mapper/plugins/associations/proxy/collection.rb +55 -0
  28. data/lib/mongo_mapper/plugins/associations/{embedded_collection.rb → proxy/embedded_collection.rb} +0 -0
  29. data/lib/mongo_mapper/plugins/associations/{in_array_proxy.rb → proxy/in_array_proxy.rb} +36 -6
  30. data/lib/mongo_mapper/plugins/associations/proxy/in_foreign_array_proxy.rb +136 -0
  31. data/lib/mongo_mapper/plugins/associations/{many_documents_as_proxy.rb → proxy/many_documents_as_proxy.rb} +0 -0
  32. data/lib/mongo_mapper/plugins/associations/{many_documents_proxy.rb → proxy/many_documents_proxy.rb} +0 -4
  33. data/lib/mongo_mapper/plugins/associations/{many_embedded_polymorphic_proxy.rb → proxy/many_embedded_polymorphic_proxy.rb} +0 -0
  34. data/lib/mongo_mapper/plugins/associations/{many_embedded_proxy.rb → proxy/many_embedded_proxy.rb} +0 -0
  35. data/lib/mongo_mapper/plugins/associations/{many_polymorphic_proxy.rb → proxy/many_polymorphic_proxy.rb} +0 -0
  36. data/lib/mongo_mapper/plugins/associations/{one_as_proxy.rb → proxy/one_as_proxy.rb} +0 -0
  37. data/lib/mongo_mapper/plugins/associations/{one_embedded_polymorphic_proxy.rb → proxy/one_embedded_polymorphic_proxy.rb} +0 -0
  38. data/lib/mongo_mapper/plugins/associations/{one_embedded_proxy.rb → proxy/one_embedded_proxy.rb} +3 -1
  39. data/lib/mongo_mapper/plugins/associations/{one_proxy.rb → proxy/one_proxy.rb} +0 -0
  40. data/lib/mongo_mapper/plugins/associations/proxy/proxy.rb +164 -0
  41. data/lib/mongo_mapper/plugins/associations/single_association.rb +5 -13
  42. data/lib/mongo_mapper/plugins/dirty.rb +29 -37
  43. data/lib/mongo_mapper/plugins/document.rb +1 -1
  44. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +1 -1
  45. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +1 -0
  46. data/lib/mongo_mapper/plugins/embedded_document.rb +2 -2
  47. data/lib/mongo_mapper/plugins/identity_map.rb +3 -1
  48. data/lib/mongo_mapper/plugins/indexes.rb +13 -6
  49. data/lib/mongo_mapper/plugins/keys.rb +12 -7
  50. data/lib/mongo_mapper/plugins/keys/key.rb +21 -13
  51. data/lib/mongo_mapper/plugins/modifiers.rb +39 -14
  52. data/lib/mongo_mapper/plugins/persistence.rb +6 -2
  53. data/lib/mongo_mapper/plugins/querying.rb +9 -3
  54. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +6 -6
  55. data/lib/mongo_mapper/plugins/safe.rb +10 -4
  56. data/lib/mongo_mapper/plugins/scopes.rb +19 -3
  57. data/lib/mongo_mapper/plugins/stats.rb +1 -3
  58. data/lib/mongo_mapper/plugins/strong_parameters.rb +26 -0
  59. data/lib/mongo_mapper/plugins/validations.rb +1 -1
  60. data/lib/mongo_mapper/railtie.rb +1 -0
  61. data/lib/mongo_mapper/utils.rb +2 -2
  62. data/lib/mongo_mapper/version.rb +1 -1
  63. data/spec/examples.txt +1731 -0
  64. data/spec/functional/accessible_spec.rb +7 -1
  65. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +2 -2
  66. data/spec/functional/associations/belongs_to_proxy_spec.rb +55 -5
  67. data/spec/functional/associations/in_array_proxy_spec.rb +149 -14
  68. data/spec/functional/associations/in_foreign_array_proxy_spec.rb +321 -0
  69. data/spec/functional/associations/many_documents_as_proxy_spec.rb +6 -6
  70. data/spec/functional/associations/many_documents_proxy_spec.rb +22 -22
  71. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +2 -2
  72. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +4 -4
  73. data/spec/functional/associations/one_as_proxy_spec.rb +8 -8
  74. data/spec/functional/associations/one_embedded_proxy_spec.rb +28 -0
  75. data/spec/functional/associations/one_proxy_spec.rb +19 -9
  76. data/spec/functional/associations_spec.rb +3 -3
  77. data/spec/functional/binary_spec.rb +2 -2
  78. data/spec/functional/caching_spec.rb +15 -22
  79. data/spec/functional/callbacks_spec.rb +2 -2
  80. data/spec/functional/counter_cache_spec.rb +10 -10
  81. data/spec/functional/dirty_spec.rb +48 -10
  82. data/spec/functional/dirty_with_callbacks_spec.rb +59 -0
  83. data/spec/functional/document_spec.rb +5 -8
  84. data/spec/functional/dumpable_spec.rb +1 -1
  85. data/spec/functional/embedded_document_spec.rb +5 -5
  86. data/spec/functional/identity_map_spec.rb +8 -8
  87. data/spec/functional/indexes_spec.rb +19 -18
  88. data/spec/functional/keys_spec.rb +64 -33
  89. data/spec/functional/logger_spec.rb +2 -2
  90. data/spec/functional/modifiers_spec.rb +81 -19
  91. data/spec/functional/partial_updates_spec.rb +8 -8
  92. data/spec/functional/protected_spec.rb +1 -1
  93. data/spec/functional/querying_spec.rb +70 -22
  94. data/spec/functional/safe_spec.rb +23 -27
  95. data/spec/functional/sci_spec.rb +7 -7
  96. data/spec/functional/scopes_spec.rb +89 -1
  97. data/spec/functional/static_keys_spec.rb +2 -2
  98. data/spec/functional/stats_spec.rb +28 -12
  99. data/spec/functional/strong_parameters_spec.rb +49 -0
  100. data/spec/functional/validations_spec.rb +8 -16
  101. data/spec/quality_spec.rb +1 -1
  102. data/spec/spec_helper.rb +39 -8
  103. data/spec/support/matchers.rb +1 -1
  104. data/spec/unit/associations/proxy_spec.rb +18 -10
  105. data/spec/unit/clone_spec.rb +1 -1
  106. data/spec/unit/document_spec.rb +3 -3
  107. data/spec/unit/embedded_document_spec.rb +4 -5
  108. data/spec/unit/extensions_spec.rb +12 -7
  109. data/spec/unit/identity_map_middleware_spec.rb +65 -96
  110. data/spec/unit/inspect_spec.rb +1 -1
  111. data/spec/unit/key_spec.rb +23 -18
  112. data/spec/unit/keys_spec.rb +17 -8
  113. data/spec/unit/mongo_mapper_spec.rb +41 -88
  114. data/spec/unit/rails_spec.rb +2 -2
  115. data/spec/unit/validations_spec.rb +18 -18
  116. metadata +70 -38
  117. data/README.rdoc +0 -56
  118. data/lib/mongo_mapper/extensions/ordered_hash.rb +0 -23
  119. data/lib/mongo_mapper/plugins/associations/collection.rb +0 -29
  120. data/lib/mongo_mapper/plugins/associations/proxy.rb +0 -141
@@ -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,14 +28,13 @@ 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
- get_proxy(associations[#{name.inspect}])
33
+ get_proxy(associations[#{name.inspect}]).read
32
34
  end
33
35
 
34
36
  def #{name}=(value)
35
- get_proxy(associations[#{name.inspect}]).replace(value)
36
- value
37
+ get_proxy(associations[#{name.inspect}]).write(value)
37
38
  end
38
39
  end_eval
39
40
 
@@ -60,4 +61,4 @@ module MongoMapper
60
61
  end
61
62
  end
62
63
  end
63
- end
64
+ end
@@ -47,6 +47,12 @@ module MongoMapper
47
47
  loaded
48
48
  @target
49
49
  end
50
+
51
+ private
52
+
53
+ def stale_target?
54
+ loaded? && proxy_owner[association.foreign_key] != @target&.id
55
+ end
50
56
  end
51
57
  end
52
58
  end
@@ -0,0 +1,55 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Associations
5
+ class Collection < Proxy
6
+ include Enumerable
7
+
8
+ def to_a
9
+ load_target
10
+
11
+ target.is_a?(Array) ?
12
+ target :
13
+ Array(target)
14
+ end
15
+
16
+ alias_method :to_ary, :to_a
17
+
18
+ def each(&block)
19
+ to_a.each(&block)
20
+ end
21
+
22
+ def [](val)
23
+ objs = to_a
24
+ objs ? objs[val] : nil
25
+ end
26
+
27
+ def empty?
28
+ to_a.empty?
29
+ end
30
+
31
+ def size
32
+ to_a.size
33
+ end
34
+
35
+ def length
36
+ to_a.length
37
+ end
38
+
39
+ def reset
40
+ super
41
+ target = []
42
+ end
43
+
44
+ def read
45
+ self
46
+ end
47
+
48
+ def write(value)
49
+ replace(value)
50
+ read
51
+ end
52
+ end
53
+ end
54
+ end
55
+ 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={})
@@ -120,6 +132,13 @@ module MongoMapper
120
132
  valid.empty? ? nil : valid
121
133
  end
122
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
+
123
142
  def find_target
124
143
  return [] if ids.blank?
125
144
  all
@@ -128,7 +147,18 @@ module MongoMapper
128
147
  def ids
129
148
  proxy_owner[options[:in]]
130
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
160
+
131
161
  end
132
162
  end
133
163
  end
134
- 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
@@ -76,10 +76,6 @@ module MongoMapper
76
76
  @target.each { |doc| doc.save(options) } if @target
77
77
  end
78
78
 
79
- def each(&block)
80
- load_target.each(&block)
81
- end
82
-
83
79
  protected
84
80
 
85
81
  def query(options={})
@@ -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)
@@ -0,0 +1,164 @@
1
+ # encoding: UTF-8
2
+ require 'forwardable'
3
+
4
+ module MongoMapper
5
+ module Plugins
6
+ module Associations
7
+ class Proxy
8
+ extend Forwardable
9
+
10
+ class << self
11
+ def define_proxy_method(method)
12
+ define_method(method) do |*args, &block|
13
+ proxy_method(method, *args, &block)
14
+ end
15
+ end
16
+ end
17
+
18
+ attr_reader :proxy_owner, :association, :target
19
+
20
+ alias_method :proxy_respond_to?, :respond_to?
21
+ alias_method :proxy_extend, :extend
22
+ alias_method :proxy_association, :association
23
+
24
+ def_delegators :proxy_association, :klass, :options
25
+ def_delegator :klass, :collection
26
+
27
+ def initialize(owner, association)
28
+ @proxy_owner, @association, @loaded = owner, association, false
29
+ Array(association.options[:extend]).each { |ext| proxy_extend(ext) }
30
+ reset
31
+ end
32
+
33
+ [
34
+ :is_a?,
35
+ :to_mongo,
36
+ :==,
37
+ :!=,
38
+ :nil?,
39
+ :blank?,
40
+ :present?,
41
+ # Active support in rails 3 beta 4 can override to_json after this is loaded,
42
+ # at least when run in mongomapper tests. The implementation was changed in master
43
+ # some time after this, so not sure whether this is still a problem.
44
+ #
45
+ # In rails 2, this isn't a problem however it also solves an issue where
46
+ # to_json isn't forwarded because it supports to_json itself
47
+ :to_json,
48
+ # see comments to to_json
49
+ :as_json,
50
+ ].each do |m|
51
+ define_proxy_method(m)
52
+ end
53
+
54
+ def inspect
55
+ load_target
56
+ "#<#{self.class.inspect}:#{object_id} #{@target.inspect}>"
57
+ end
58
+
59
+ def loaded?
60
+ @loaded
61
+ end
62
+
63
+ def loaded
64
+ @loaded = true
65
+ end
66
+
67
+ def reload
68
+ reset
69
+ load_target
70
+ self unless target.nil?
71
+ end
72
+
73
+ # :nocov:
74
+ def replace(v)
75
+ raise NotImplementedError
76
+ end
77
+ # :nocov:
78
+
79
+ def reset
80
+ @loaded = false
81
+ @target = nil
82
+ end
83
+
84
+ def respond_to?(*args)
85
+ super || (load_target && target.respond_to?(*args))
86
+ end
87
+
88
+ def read
89
+ load_target
90
+ @target
91
+ end
92
+
93
+ def write(value)
94
+ replace(value)
95
+ read
96
+ end
97
+
98
+ def proxy_method(method, *args, &block)
99
+ load_target
100
+ target.public_send(method, *args, &block)
101
+ end
102
+
103
+ protected
104
+
105
+ def load_target
106
+ if !loaded? || stale_target?
107
+ if @target.is_a?(Array) && @target.any?
108
+ @target = find_target + @target.find_all { |record| !record.persisted? }
109
+ else
110
+ @target = find_target
111
+ end
112
+ loaded
113
+ end
114
+ @target
115
+ rescue MongoMapper::DocumentNotFound
116
+ reset
117
+ end
118
+
119
+ # :nocov:
120
+ def find_target
121
+ raise NotImplementedError
122
+ end
123
+ # :nocov:
124
+
125
+ def flatten_deeper(array)
126
+ array.collect do |element|
127
+ (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
128
+ end.flatten
129
+ end
130
+
131
+ private
132
+
133
+ def stale_target?
134
+ false
135
+ end
136
+
137
+ def define_proxy_method(method)
138
+ metaclass = class << self; self; end
139
+ metaclass.class_eval do
140
+ define_proxy_method(method)
141
+ end
142
+ end
143
+
144
+ def define_and_call_proxy_method(method, *args, &block)
145
+ define_proxy_method(method)
146
+ public_send(method, *args, &block)
147
+ end
148
+
149
+ def method_missing(method, *args, &block)
150
+ # load the target just in case it isn't loaded
151
+ load_target
152
+
153
+ # only define the method if the target has the method
154
+ # NOTE: include private methods!
155
+ if target.respond_to?(method, true)
156
+ define_and_call_proxy_method(method, *args, &block)
157
+ else
158
+ super
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end