mongo_mapper 0.13.0.beta2 → 0.15.0

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 (132) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +64 -0
  4. data/examples/keys.rb +3 -3
  5. data/examples/modifiers/set.rb +2 -2
  6. data/examples/querying.rb +3 -3
  7. data/examples/safe.rb +2 -2
  8. data/examples/scopes.rb +1 -1
  9. data/lib/mongo_mapper.rb +5 -0
  10. data/lib/mongo_mapper/connection.rb +16 -37
  11. data/lib/mongo_mapper/document.rb +4 -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/extensions/symbol.rb +18 -0
  18. data/lib/mongo_mapper/plugins/accessible.rb +14 -4
  19. data/lib/mongo_mapper/plugins/associations.rb +7 -6
  20. data/lib/mongo_mapper/plugins/associations/base.rb +18 -13
  21. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +10 -1
  22. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +9 -8
  23. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +12 -11
  24. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -4
  25. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +24 -23
  26. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +18 -16
  27. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +55 -48
  28. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +14 -13
  29. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +7 -6
  30. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +7 -5
  31. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +17 -14
  32. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +14 -13
  33. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +9 -9
  34. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +27 -26
  35. data/lib/mongo_mapper/plugins/associations/proxy.rb +31 -28
  36. data/lib/mongo_mapper/plugins/callbacks.rb +14 -1
  37. data/lib/mongo_mapper/plugins/counter_cache.rb +97 -0
  38. data/lib/mongo_mapper/plugins/dirty.rb +29 -37
  39. data/lib/mongo_mapper/plugins/document.rb +1 -1
  40. data/lib/mongo_mapper/plugins/dynamic_querying.rb +10 -9
  41. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +18 -17
  42. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +2 -1
  43. data/lib/mongo_mapper/plugins/embedded_document.rb +1 -1
  44. data/lib/mongo_mapper/plugins/identity_map.rb +1 -1
  45. data/lib/mongo_mapper/plugins/indexes.rb +37 -2
  46. data/lib/mongo_mapper/plugins/keys.rb +202 -142
  47. data/lib/mongo_mapper/plugins/keys/key.rb +22 -13
  48. data/lib/mongo_mapper/plugins/keys/static.rb +45 -0
  49. data/lib/mongo_mapper/plugins/modifiers.rb +59 -28
  50. data/lib/mongo_mapper/plugins/partial_updates.rb +86 -0
  51. data/lib/mongo_mapper/plugins/persistence.rb +13 -8
  52. data/lib/mongo_mapper/plugins/protected.rb +6 -5
  53. data/lib/mongo_mapper/plugins/querying.rb +85 -42
  54. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +32 -9
  55. data/lib/mongo_mapper/plugins/rails.rb +1 -0
  56. data/lib/mongo_mapper/plugins/safe.rb +10 -4
  57. data/lib/mongo_mapper/plugins/sci.rb +4 -1
  58. data/lib/mongo_mapper/plugins/scopes.rb +78 -7
  59. data/lib/mongo_mapper/plugins/stats.rb +17 -0
  60. data/lib/mongo_mapper/plugins/timestamps.rb +1 -0
  61. data/lib/mongo_mapper/plugins/touch.rb +1 -1
  62. data/lib/mongo_mapper/plugins/validations.rb +7 -2
  63. data/lib/mongo_mapper/railtie.rb +20 -0
  64. data/lib/mongo_mapper/railtie/database.rake +1 -1
  65. data/lib/mongo_mapper/utils.rb +2 -2
  66. data/lib/mongo_mapper/version.rb +1 -1
  67. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +12 -13
  68. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +9 -9
  69. data/spec/examples.txt +1643 -0
  70. data/spec/functional/accessible_spec.rb +13 -13
  71. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +13 -13
  72. data/spec/functional/associations/belongs_to_proxy_spec.rb +18 -19
  73. data/spec/functional/associations/in_array_proxy_spec.rb +10 -10
  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 +3 -3
  82. data/spec/functional/associations/one_proxy_spec.rb +10 -10
  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 +235 -0
  88. data/spec/functional/dirty_spec.rb +63 -46
  89. data/spec/functional/document_spec.rb +30 -2
  90. data/spec/functional/dumpable_spec.rb +1 -1
  91. data/spec/functional/embedded_document_spec.rb +18 -18
  92. data/spec/functional/identity_map_spec.rb +27 -14
  93. data/spec/functional/indexes_spec.rb +44 -19
  94. data/spec/functional/keys_spec.rb +117 -15
  95. data/spec/functional/logger_spec.rb +3 -3
  96. data/spec/functional/modifiers_spec.rb +67 -19
  97. data/spec/functional/partial_updates_spec.rb +577 -0
  98. data/spec/functional/protected_spec.rb +14 -14
  99. data/spec/functional/querying_spec.rb +55 -28
  100. data/spec/functional/safe_spec.rb +23 -27
  101. data/spec/functional/sci_spec.rb +49 -14
  102. data/spec/functional/scopes_spec.rb +235 -2
  103. data/spec/functional/static_keys_spec.rb +153 -0
  104. data/spec/functional/stats_spec.rb +86 -0
  105. data/spec/functional/touch_spec.rb +6 -6
  106. data/spec/functional/validations_spec.rb +51 -57
  107. data/spec/quality_spec.rb +51 -0
  108. data/spec/spec_helper.rb +37 -9
  109. data/spec/support/matchers.rb +5 -14
  110. data/spec/unit/associations/base_spec.rb +12 -12
  111. data/spec/unit/associations/belongs_to_association_spec.rb +2 -2
  112. data/spec/unit/associations/many_association_spec.rb +2 -2
  113. data/spec/unit/associations/one_association_spec.rb +2 -2
  114. data/spec/unit/associations/proxy_spec.rb +19 -16
  115. data/spec/unit/clone_spec.rb +1 -1
  116. data/spec/unit/document_spec.rb +8 -8
  117. data/spec/unit/dynamic_finder_spec.rb +8 -8
  118. data/spec/unit/embedded_document_spec.rb +18 -19
  119. data/spec/unit/extensions_spec.rb +41 -17
  120. data/spec/unit/identity_map_middleware_spec.rb +65 -96
  121. data/spec/unit/inspect_spec.rb +4 -4
  122. data/spec/unit/key_spec.rb +28 -26
  123. data/spec/unit/keys_spec.rb +10 -10
  124. data/spec/unit/model_generator_spec.rb +2 -4
  125. data/spec/unit/mongo_mapper_spec.rb +38 -85
  126. data/spec/unit/rails_spec.rb +5 -0
  127. data/spec/unit/serialization_spec.rb +1 -1
  128. data/spec/unit/time_zones_spec.rb +2 -2
  129. data/spec/unit/validations_spec.rb +28 -15
  130. metadata +188 -161
  131. data/README.rdoc +0 -55
  132. data/lib/mongo_mapper/extensions/ordered_hash.rb +0 -23
@@ -10,14 +10,15 @@ module MongoMapper
10
10
  reset
11
11
  end
12
12
 
13
- private
14
- def find_target
15
- (@_values ||= []).map do |attrs|
16
- klass.load(attrs, true).tap do |child|
17
- assign_references(child)
18
- end
13
+ private
14
+
15
+ def find_target
16
+ (@_values ||= []).map do |attrs|
17
+ klass.load(attrs, true).tap do |child|
18
+ assign_references(child)
19
19
  end
20
20
  end
21
+ end
21
22
  end
22
23
  end
23
24
  end
@@ -3,11 +3,13 @@ module MongoMapper
3
3
  module Plugins
4
4
  module Associations
5
5
  class ManyPolymorphicProxy < ManyDocumentsProxy
6
- private
7
- def apply_scope(doc)
8
- doc[association.type_key_name] = doc.class.name
9
- super
10
- end
6
+
7
+ private
8
+
9
+ def apply_scope(doc)
10
+ doc[association.type_key_name] = doc.class.name
11
+ super
12
+ end
11
13
  end
12
14
  end
13
15
  end
@@ -3,20 +3,23 @@ module MongoMapper
3
3
  module Plugins
4
4
  module Associations
5
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
6
+
7
+ protected
8
+
9
+ def criteria
10
+ {type_key_name => proxy_owner.class.name, id_key_name => proxy_owner.id}
11
+ end
12
+
13
+ private
14
+
15
+ def type_key_name
16
+ "#{options[:as]}_type"
17
+ end
18
+
19
+ def id_key_name
20
+ "#{options[:as]}_id"
21
+ end
19
22
  end
20
23
  end
21
24
  end
22
- end
25
+ end
@@ -8,22 +8,23 @@ module MongoMapper
8
8
  reset
9
9
  end
10
10
 
11
- protected
12
- def find_target
13
- if @value
14
- child = polymorphic_class(@value).load(@value, true)
15
- assign_references(child)
16
- child
17
- end
11
+ protected
12
+
13
+ def find_target
14
+ if @value
15
+ child = polymorphic_class(@value).load(@value, true)
16
+ assign_references(child)
17
+ child
18
18
  end
19
+ end
19
20
 
20
- def polymorphic_class(doc)
21
- if class_name = doc[association.type_key_name]
22
- class_name.constantize
23
- else
24
- klass
25
- end
21
+ def polymorphic_class(doc)
22
+ if class_name = doc[association.type_key_name]
23
+ class_name.constantize
24
+ else
25
+ klass
26
26
  end
27
+ end
27
28
  end
28
29
  end
29
30
  end
@@ -25,19 +25,19 @@ module MongoMapper
25
25
  @target.persist(options) if @target
26
26
  end
27
27
 
28
- protected
28
+ protected
29
29
 
30
- def find_target
31
- if @value
32
- klass.load(@value, true).tap do |child|
33
- assign_references(child)
34
- end
30
+ def find_target
31
+ if @value
32
+ klass.load(@value, true).tap do |child|
33
+ assign_references(child)
35
34
  end
36
35
  end
36
+ end
37
37
 
38
- def assign_references(doc)
39
- doc._parent_document = proxy_owner if doc
40
- end
38
+ def assign_references(doc)
39
+ doc._parent_document = proxy_owner if doc
40
+ end
41
41
  end
42
42
  end
43
43
  end
@@ -57,38 +57,39 @@ module MongoMapper
57
57
  reset
58
58
  end
59
59
 
60
- protected
61
- def find_target
62
- target_class.first(association.query_options.merge(criteria))
63
- end
60
+ protected
64
61
 
65
- def instantiate_target(instantiator, attrs={}, &block)
66
- @target = target_class.send(instantiator, attrs.update(criteria), &block)
67
- loaded
68
- @target
69
- end
62
+ def find_target
63
+ target_class.first(association.query_options.merge(criteria))
64
+ end
70
65
 
71
- def target_class
72
- @target_class ||= options[:class] || (options[:class_name] || association.name.to_s.camelize).constantize
73
- end
66
+ def instantiate_target(instantiator, attrs={}, &block)
67
+ @target = target_class.send(instantiator, attrs.update(criteria), &block)
68
+ loaded
69
+ @target
70
+ end
74
71
 
75
- def foreign_key
76
- options[:foreign_key] || proxy_owner.class.name.foreign_key
77
- end
72
+ def target_class
73
+ @target_class ||= options[:class] || (options[:class_name] || association.name.to_s.camelize).constantize
74
+ end
78
75
 
79
- def criteria
80
- {self.foreign_key => proxy_owner.id}
81
- end
76
+ def foreign_key
77
+ options[:foreign_key] || proxy_owner.class.name.foreign_key
78
+ end
82
79
 
83
- def nullify_scope(doc)
84
- criteria.each { |key, value| doc[key] = nil }
85
- doc
86
- end
80
+ def criteria
81
+ {self.foreign_key => proxy_owner.id}
82
+ end
87
83
 
88
- def apply_scope(doc)
89
- criteria.each { |key, value| doc[key] = value }
90
- doc
91
- end
84
+ def nullify_scope(doc)
85
+ criteria.each { |key, value| doc[key] = nil }
86
+ doc
87
+ end
88
+
89
+ def apply_scope(doc)
90
+ criteria.each { |key, value| doc[key] = value }
91
+ doc
92
+ end
92
93
  end
93
94
  end
94
95
  end
@@ -91,47 +91,50 @@ module MongoMapper
91
91
  proxy_respond_to?(*args) || (load_target && target.respond_to?(*args))
92
92
  end
93
93
 
94
- def send(method, *args)
94
+ def send(method, *args, &block)
95
95
  if proxy_respond_to?(method, true)
96
96
  super
97
97
  else
98
98
  load_target
99
- target.send(method, *args)
99
+ target.send(method, *args, &block)
100
100
  end
101
101
  end
102
102
 
103
- protected
104
- def method_missing(method, *args, &block)
105
- if load_target
106
- target.send(method, *args, &block)
107
- end
108
- end
103
+ protected
109
104
 
110
- def load_target
111
- unless loaded?
112
- if @target.is_a?(Array) && @target.any?
113
- @target = find_target + @target.find_all { |record| !record.persisted? }
114
- else
115
- @target = find_target
116
- end
117
- loaded
105
+ def load_target
106
+ unless loaded?
107
+ if @target.is_a?(Array) && @target.any?
108
+ @target = find_target + @target.find_all { |record| !record.persisted? }
109
+ else
110
+ @target = find_target
118
111
  end
119
- @target
120
- rescue MongoMapper::DocumentNotFound
121
- reset
112
+ loaded
122
113
  end
114
+ @target
115
+ rescue MongoMapper::DocumentNotFound
116
+ reset
117
+ end
123
118
 
124
- # :nocov:
125
- def find_target
126
- raise NotImplementedError
127
- end
128
- # :nocov:
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
129
132
 
130
- def flatten_deeper(array)
131
- array.collect do |element|
132
- (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
133
- end.flatten
133
+ def method_missing(method, *args, &block)
134
+ if load_target
135
+ target.send(method, *args, &block)
134
136
  end
137
+ end
135
138
  end
136
139
  end
137
140
  end
@@ -4,15 +4,28 @@ module MongoMapper
4
4
  module Callbacks
5
5
  extend ActiveSupport::Concern
6
6
 
7
+ def initialize(*)
8
+ run_callbacks(:initialize) { super }
9
+ end
10
+
11
+ def initialize_from_database(*)
12
+ run_callbacks(:initialize) do
13
+ run_callbacks(:find) do
14
+ super
15
+ end
16
+ end
17
+ end
18
+
7
19
  def destroy
8
20
  run_callbacks(:destroy) { super }
9
21
  end
10
-
22
+
11
23
  def touch(*)
12
24
  run_callbacks(:touch) { super }
13
25
  end
14
26
 
15
27
  private
28
+
16
29
  def create_or_update(*)
17
30
  run_callbacks(:save) { super }
18
31
  end
@@ -0,0 +1,97 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ # Counter Caching for MongoMapper::Document
4
+ #
5
+ # Examples:
6
+ #
7
+ # class Post
8
+ # belongs_to :user
9
+ # counter_cache :user
10
+ # end
11
+ #
12
+ # or:
13
+ #
14
+ # class Post
15
+ # belongs_to :user
16
+ # counter_cache :user, :custom_posts_count
17
+ # end
18
+ #
19
+ # Field names follow rails conventions, so counter_cache :user will increment the Integer field `posts_count' on User
20
+ #
21
+ # Alternatively, you can also use the more common ActiveRecord syntax:
22
+ #
23
+ # class Post
24
+ # belongs_to :user, :counter_cache => true
25
+ # end
26
+ #
27
+ # Or with an alternative field name:
28
+ #
29
+ # class Post
30
+ # belongs_to :user, :counter_cache => :custom_posts_count
31
+ # end
32
+ #
33
+ module CounterCache
34
+ class InvalidCounterCacheError < StandardError; end
35
+
36
+ extend ActiveSupport::Concern
37
+
38
+ module ClassMethods
39
+ def counter_cache(association_name, options = {})
40
+ options.symbolize_keys!
41
+
42
+ field = options[:field] ?
43
+ options[:field] :
44
+ "#{self.collection_name.gsub(/.*\./, '')}_count"
45
+
46
+ association = associations[association_name]
47
+
48
+ if !association
49
+ raise InvalidCounterCacheError, "You must define an association with name `#{association_name}' on model #{self}"
50
+ end
51
+
52
+ # make a define-time check to make sure the counter cache field is defined.
53
+ # note: this can only be done in non-polymorphic classes
54
+ # (since we may not know the class on the other side of the association)
55
+ if !association.polymorphic?
56
+ association_class = association.klass
57
+ key_names = association_class.keys.keys
58
+
59
+ if !key_names.include?(field.to_s)
60
+ _raise_when_missing_counter_cache_key(association_class, field)
61
+ end
62
+ end
63
+
64
+ after_create do
65
+ if obj = self.send(association_name)
66
+ if !obj.respond_to?(field)
67
+ self.class._raise_when_missing_counter_cache_key(obj.class, field)
68
+ end
69
+
70
+ obj.increment(field => 1)
71
+ obj.write_attribute(field, obj.read_attribute(field) + 1)
72
+ end
73
+
74
+ true
75
+ end
76
+
77
+ after_destroy do
78
+ if obj = self.send(association_name)
79
+ if !obj.respond_to?(field)
80
+ self.class._raise_when_missing_counter_cache_key(obj.class, field)
81
+ end
82
+
83
+ obj.decrement(field => 1)
84
+ obj.write_attribute(field, obj.read_attribute(field) - 1)
85
+ end
86
+
87
+ true
88
+ end
89
+ end
90
+
91
+ def _raise_when_missing_counter_cache_key(klass, field)
92
+ raise InvalidCounterCacheError, "Missing `key #{field.to_sym.inspect}, Integer, :default => 0' on model #{klass}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ 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 }
8
+ module ClassMethods
9
+ def create_accessors_for(key)
10
+ super.tap do
11
+ define_attribute_methods([key.name])
12
+ end
13
+ end
18
14
  end
19
15
 
20
- def reload(*)
21
- doc = super
22
- doc.tap { clear_changes }
16
+ def create_or_update(*)
17
+ super.tap do
18
+ changes_applied
19
+ end
23
20
  end
24
21
 
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
31
- end
22
+ def reload!
23
+ super.tap do
24
+ clear_changes_information
32
25
  end
33
26
  end
34
27
 
35
- protected
28
+ private
36
29
 
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?
40
- end
30
+ def write_key(key_name, value)
31
+ key_name = unalias_key(key_name)
41
32
 
42
- private
43
-
44
- def write_key(key, value)
45
- key = unalias_key(key)
46
- if !keys.key?(key)
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.type.to_mongo(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