mongo_mapper 0.14.0 → 0.15.4

Sign up to get free protection for your applications and to get access to all the features.
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