mongo_mapper 0.8.6 → 0.9.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 (107) hide show
  1. data/UPGRADES +10 -0
  2. data/bin/mmconsole +0 -1
  3. data/examples/identity_map/automatic.rb +1 -7
  4. data/examples/plugins.rb +9 -9
  5. data/examples/safe.rb +43 -0
  6. data/lib/mongo_mapper.rb +46 -33
  7. data/lib/mongo_mapper/document.rb +33 -32
  8. data/lib/mongo_mapper/embedded_document.rb +22 -22
  9. data/lib/mongo_mapper/locale/en.yml +5 -0
  10. data/lib/mongo_mapper/middleware/identity_map.rb +16 -0
  11. data/lib/mongo_mapper/plugins.rb +16 -3
  12. data/lib/mongo_mapper/plugins/accessible.rb +2 -0
  13. data/lib/mongo_mapper/plugins/active_model.rb +18 -0
  14. data/lib/mongo_mapper/plugins/associations.rb +37 -42
  15. data/lib/mongo_mapper/plugins/associations/base.rb +14 -50
  16. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +58 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +6 -1
  18. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +30 -2
  19. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -0
  20. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +12 -6
  21. data/lib/mongo_mapper/plugins/associations/many_association.rb +67 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +5 -5
  23. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +1 -1
  24. data/lib/mongo_mapper/plugins/associations/one_association.rb +20 -0
  25. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +5 -0
  26. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +7 -7
  27. data/lib/mongo_mapper/plugins/associations/proxy.rb +2 -2
  28. data/lib/mongo_mapper/plugins/caching.rb +3 -1
  29. data/lib/mongo_mapper/plugins/callbacks.rb +12 -221
  30. data/lib/mongo_mapper/plugins/clone.rb +3 -1
  31. data/lib/mongo_mapper/plugins/dirty.rb +38 -91
  32. data/lib/mongo_mapper/plugins/document.rb +4 -2
  33. data/lib/mongo_mapper/plugins/dynamic_querying.rb +2 -0
  34. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +43 -0
  35. data/lib/mongo_mapper/plugins/embedded_document.rb +16 -9
  36. data/lib/mongo_mapper/plugins/equality.rb +2 -0
  37. data/lib/mongo_mapper/plugins/identity_map.rb +4 -2
  38. data/lib/mongo_mapper/plugins/indexes.rb +2 -0
  39. data/lib/mongo_mapper/plugins/inspect.rb +3 -1
  40. data/lib/mongo_mapper/plugins/keys.rb +28 -22
  41. data/lib/mongo_mapper/plugins/keys/key.rb +12 -6
  42. data/lib/mongo_mapper/plugins/logger.rb +2 -0
  43. data/lib/mongo_mapper/plugins/modifiers.rb +3 -1
  44. data/lib/mongo_mapper/plugins/pagination.rb +2 -0
  45. data/lib/mongo_mapper/plugins/persistence.rb +2 -0
  46. data/lib/mongo_mapper/plugins/protected.rb +2 -0
  47. data/lib/mongo_mapper/plugins/querying.rb +5 -4
  48. data/lib/mongo_mapper/plugins/rails.rb +3 -5
  49. data/lib/mongo_mapper/plugins/safe.rb +2 -0
  50. data/lib/mongo_mapper/plugins/sci.rb +2 -0
  51. data/lib/mongo_mapper/plugins/scopes.rb +2 -0
  52. data/lib/mongo_mapper/plugins/serialization.rb +67 -46
  53. data/lib/mongo_mapper/plugins/timestamps.rb +3 -1
  54. data/lib/mongo_mapper/plugins/userstamps.rb +2 -0
  55. data/lib/mongo_mapper/plugins/validations.rb +40 -24
  56. data/lib/mongo_mapper/railtie.rb +49 -0
  57. data/lib/mongo_mapper/railtie/database.rake +60 -0
  58. data/lib/mongo_mapper/support/descendant_appends.rb +11 -11
  59. data/lib/mongo_mapper/translation.rb +10 -0
  60. data/lib/mongo_mapper/version.rb +1 -1
  61. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +24 -0
  62. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +18 -0
  63. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +23 -0
  64. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +11 -0
  65. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +1 -0
  66. data/test/functional/associations/test_belongs_to_proxy.rb +131 -1
  67. data/test/functional/associations/test_in_array_proxy.rb +30 -0
  68. data/test/functional/associations/test_many_documents_proxy.rb +30 -2
  69. data/test/functional/associations/test_many_embedded_proxy.rb +33 -0
  70. data/test/functional/associations/test_many_polymorphic_proxy.rb +1 -0
  71. data/test/functional/associations/test_one_embedded_proxy.rb +21 -2
  72. data/test/functional/associations/test_one_proxy.rb +49 -9
  73. data/test/functional/test_associations.rb +2 -0
  74. data/test/functional/test_caching.rb +3 -2
  75. data/test/functional/test_callbacks.rb +25 -18
  76. data/test/functional/test_dirty.rb +123 -1
  77. data/test/functional/test_document.rb +26 -2
  78. data/test/functional/test_embedded_document.rb +68 -2
  79. data/test/functional/test_identity_map.rb +3 -4
  80. data/test/functional/test_querying.rb +11 -0
  81. data/test/functional/test_userstamps.rb +2 -2
  82. data/test/functional/test_validations.rb +31 -29
  83. data/test/models.rb +10 -0
  84. data/test/test_active_model_lint.rb +1 -1
  85. data/test/test_helper.rb +9 -10
  86. data/test/unit/associations/test_base.rb +24 -100
  87. data/test/unit/associations/test_belongs_to_association.rb +29 -0
  88. data/test/unit/associations/test_many_association.rb +63 -0
  89. data/test/unit/associations/test_one_association.rb +18 -0
  90. data/test/unit/serializers/test_json_serializer.rb +0 -1
  91. data/test/unit/test_descendant_appends.rb +8 -16
  92. data/test/unit/test_document.rb +4 -9
  93. data/test/unit/test_dynamic_finder.rb +1 -1
  94. data/test/unit/test_embedded_document.rb +51 -18
  95. data/test/unit/test_identity_map_middleware.rb +34 -0
  96. data/test/unit/test_inspect.rb +22 -0
  97. data/test/unit/test_key.rb +21 -1
  98. data/test/unit/test_keys.rb +0 -2
  99. data/test/unit/test_plugins.rb +106 -20
  100. data/test/unit/test_rails.rb +8 -8
  101. data/test/unit/test_serialization.rb +116 -1
  102. data/test/unit/test_translation.rb +27 -0
  103. data/test/unit/test_validations.rb +66 -81
  104. metadata +103 -43
  105. data/examples/identity_map/middleware.rb +0 -14
  106. data/lib/mongo_mapper/plugins/descendants.rb +0 -17
  107. data/rails/init.rb +0 -19
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Associations
5
+ class OneAssociation < BelongsToAssociation
6
+ def embeddable?
7
+ klass.embeddable?
8
+ end
9
+
10
+ def proxy_class
11
+ @proxy_class ||= klass.embeddable? ? OneEmbeddedProxy : OneProxy
12
+ end
13
+
14
+ def autosave?
15
+ options.fetch(:autosave, embeddable?)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -16,11 +16,16 @@ module MongoMapper
16
16
  else
17
17
  @target = klass.load(doc)
18
18
  end
19
+ @target.default_id_value if @target && @target.id.nil?
19
20
  assign_references(@target)
20
21
  loaded
21
22
  @target
22
23
  end
23
24
 
25
+ def save_to_collection(options={})
26
+ @target.persist(options) if @target
27
+ end
28
+
24
29
  protected
25
30
 
26
31
  def find_target
@@ -19,7 +19,7 @@ module MongoMapper
19
19
  load_target
20
20
 
21
21
  if !target.nil? && target != doc
22
- if options[:dependent] && !target.new?
22
+ if options[:dependent] && target.persisted?
23
23
  case options[:dependent]
24
24
  when :delete
25
25
  target.delete
@@ -32,13 +32,13 @@ module MongoMapper
32
32
  end
33
33
  end
34
34
 
35
- reset
36
-
37
- unless doc.nil?
38
- proxy_owner.save if proxy_owner.new?
39
- doc = klass.new(doc) unless klass === doc
35
+ if doc.nil?
36
+ target.update_attributes(foreign_key => nil) unless target.nil?
37
+ else
38
+ proxy_owner.save unless proxy_owner.persisted?
39
+ doc = klass.new(doc) unless doc.is_a?(klass)
40
40
  doc[foreign_key] = proxy_owner.id
41
- doc.save if doc.new?
41
+ doc.save unless doc.persisted?
42
42
  loaded
43
43
  @target = doc
44
44
  end
@@ -9,7 +9,7 @@ module MongoMapper
9
9
  alias :proxy_respond_to? :respond_to?
10
10
  alias :proxy_extend :extend
11
11
 
12
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
12
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^respond_to_missing\?$|^object_id$)/ }
13
13
 
14
14
  attr_reader :proxy_owner, :association, :target
15
15
 
@@ -113,7 +113,7 @@ module MongoMapper
113
113
  def load_target
114
114
  unless loaded?
115
115
  if @target.is_a?(Array) && @target.any?
116
- @target = find_target + @target.find_all { |record| record.new? }
116
+ @target = find_target + @target.find_all { |record| !record.persisted? }
117
117
  else
118
118
  @target = find_target
119
119
  end
@@ -2,10 +2,12 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Caching
5
+ extend ActiveSupport::Concern
6
+
5
7
  module InstanceMethods
6
8
  def cache_key(*suffixes)
7
9
  cache_key = case
8
- when new?
10
+ when !persisted?
9
11
  "#{self.class.name}/new"
10
12
  when timestamp = self[:updated_at]
11
13
  "#{self.class.name}/#{id}-#{timestamp.to_s(:number)}"
@@ -1,240 +1,31 @@
1
1
  # encoding: UTF-8
2
- # Almost all of this callback stuff is pulled directly from ActiveSupport
3
- # in the interest of support rails 2 and 3 at the same time and is the
4
- # same copyright as rails.
5
2
  module MongoMapper
6
3
  module Plugins
7
4
  module Callbacks
8
- def self.configure(model)
9
- model.class_eval do
10
- define_callbacks(
11
- :before_save, :after_save,
12
- :before_create, :after_create,
13
- :before_update, :after_update,
14
- :before_validation, :after_validation,
15
- :before_validation_on_create, :after_validation_on_create,
16
- :before_validation_on_update, :after_validation_on_update,
17
- :before_destroy, :after_destroy,
18
- :validate_on_create, :validate_on_update,
19
- :validate
20
- )
21
- end
22
- end
23
-
24
- module ClassMethods
25
- def define_callbacks(*callbacks)
26
- callbacks.each do |callback|
27
- class_eval <<-"end_eval"
28
- def self.#{callback}(*methods, &block)
29
- callbacks = CallbackChain.build(:#{callback}, *methods, &block)
30
- @#{callback}_callbacks ||= CallbackChain.new
31
- @#{callback}_callbacks.concat callbacks
32
- end
33
-
34
- def self.#{callback}_callback_chain
35
- @#{callback}_callbacks ||= CallbackChain.new
36
-
37
- if superclass.respond_to?(:#{callback}_callback_chain)
38
- CallbackChain.new(
39
- superclass.#{callback}_callback_chain +
40
- @#{callback}_callbacks
41
- )
42
- else
43
- @#{callback}_callbacks
44
- end
45
- end
46
- end_eval
47
- end
48
- end
49
- end
5
+ extend ActiveSupport::Concern
50
6
 
51
7
  module InstanceMethods
52
- def valid?
53
- action = new? ? 'create' : 'update'
54
- run_callbacks(:before_validation)
55
- run_callbacks("before_validation_on_#{action}".to_sym)
56
- result = super
57
- run_callbacks("after_validation_on_#{action}".to_sym)
58
- run_callbacks(:after_validation)
59
- result
60
- end
61
-
62
- # Overriding validatable's valid_for_group? to integrate validation callbacks
63
- def valid_for_group?(group) #:nodoc:
64
- errors.clear
65
- run_before_validations
66
- run_callbacks(:validate)
67
- new? ? run_callbacks(:validate_on_create) : run_callbacks(:validate_on_update)
68
- self.class.validate_children(self, group)
69
- self.validate_group(group)
70
- errors.empty?
8
+ def valid?(context = nil)
9
+ context ||= (new_record? ? :create : :update)
10
+ super(context) && errors.empty?
71
11
  end
72
12
 
73
13
  def destroy
74
- run_callbacks(:before_destroy)
75
- result = super
76
- run_callbacks(:after_destroy)
77
- result
78
- end
79
-
80
- def run_callbacks(kind, options={}, &block)
81
- callback_chain_method = "#{kind}_callback_chain"
82
- return unless self.class.respond_to?(callback_chain_method)
83
- self.class.send(callback_chain_method).run(self, options, &block)
84
- self.embedded_associations.each do |association|
85
- if association.one?
86
- self.send(association.name).run_callbacks(kind, options, &block)
87
- else
88
- self.send(association.name).each do |document|
89
- document.run_callbacks(kind, options, &block)
90
- end
91
- end
92
- end
93
- end
94
-
95
- private
96
- def create_or_update(*args)
97
- run_callbacks(:before_save)
98
- if result = super
99
- run_callbacks(:after_save)
100
- end
101
- result
102
- end
103
-
104
- def create(*args)
105
- run_callbacks(:before_create)
106
- result = super
107
- run_callbacks(:after_create)
108
- result
109
- end
110
-
111
- def update(*args)
112
- run_callbacks(:before_update)
113
- result = super
114
- run_callbacks(:after_update)
115
- result
116
- end
117
- end
118
-
119
- class CallbackChain < Array
120
- def self.build(kind, *methods, &block)
121
- methods, options = extract_options(*methods, &block)
122
- methods.map! { |method| Callback.new(kind, method, options) }
123
- new(methods)
124
- end
125
-
126
- def run(object, options={}, &terminator)
127
- enumerator = options[:enumerator] || :each
128
-
129
- unless block_given?
130
- send(enumerator) { |callback| callback.call(object) }
131
- else
132
- send(enumerator) do |callback|
133
- result = callback.call(object)
134
- break result if terminator.call(result, object)
135
- end
136
- end
137
- end
138
-
139
- # TODO: Decompose into more Array like behavior
140
- def replace_or_append!(chain)
141
- if index = index(chain)
142
- self[index] = chain
143
- else
144
- self << chain
145
- end
146
- self
147
- end
148
-
149
- def find(callback, &block)
150
- select { |c| c == callback && (!block_given? || yield(c)) }.first
14
+ run_callbacks(:destroy) { super }
151
15
  end
152
16
 
153
- def delete(callback)
154
- super(callback.is_a?(Callback) ? callback : find(callback))
17
+ private
18
+ def create_or_update(*)
19
+ run_callbacks(:save) { super }
155
20
  end
156
21
 
157
- private
158
- def self.extract_options(*methods, &block)
159
- methods.flatten!
160
- options = methods.extract_options!
161
- methods << block if block_given?
162
- return methods, options
163
- end
164
-
165
- def extract_options(*methods, &block)
166
- self.class.extract_options(*methods, &block)
167
- end
168
- end
169
-
170
- class Callback
171
- attr_reader :kind, :method, :identifier, :options
172
-
173
- def initialize(kind, method, options={})
174
- @kind = kind
175
- @method = method
176
- @identifier = options[:identifier]
177
- @options = options
22
+ def create(*)
23
+ run_callbacks(:create) { super }
178
24
  end
179
25
 
180
- def ==(other)
181
- case other
182
- when Callback
183
- (self.identifier && self.identifier == other.identifier) || self.method == other.method
184
- else
185
- (self.identifier && self.identifier == other) || self.method == other
186
- end
26
+ def update(*)
27
+ run_callbacks(:update) { super }
187
28
  end
188
-
189
- def eql?(other)
190
- self == other
191
- end
192
-
193
- def dup
194
- self.class.new(@kind, @method, @options.dup)
195
- end
196
-
197
- def hash
198
- if @identifier
199
- @identifier.hash
200
- else
201
- @method.hash
202
- end
203
- end
204
-
205
- def call(*args, &block)
206
- evaluate_method(method, *args, &block) if should_run_callback?(*args)
207
- rescue LocalJumpError
208
- raise ArgumentError,
209
- "Cannot yield from a Proc type filter. The Proc must take two " +
210
- "arguments and execute #call on the second argument."
211
- end
212
-
213
- private
214
- def evaluate_method(method, *args, &block)
215
- case method
216
- when Symbol
217
- object = args.shift
218
- object.send(method, *args, &block)
219
- when String
220
- eval(method, args.first.instance_eval { binding })
221
- when Proc, Method
222
- method.call(*args, &block)
223
- else
224
- if method.respond_to?(kind)
225
- method.send(kind, *args, &block)
226
- else
227
- raise ArgumentError,
228
- "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
229
- "a block to be invoked, or an object responding to the callback method."
230
- end
231
- end
232
- end
233
-
234
- def should_run_callback?(*args)
235
- [options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
236
- ![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
237
- end
238
29
  end
239
30
  end
240
31
  end
@@ -2,11 +2,13 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Clone
5
+ extend ActiveSupport::Concern
6
+
5
7
  module InstanceMethods
6
8
  def initialize_copy(other)
7
9
  @_new = true
8
10
  @_destroyed = false
9
- default_id_value({})
11
+ default_id_value
10
12
  associations.each do |name, association|
11
13
  instance_variable_set(association.ivar, nil)
12
14
  end
@@ -2,122 +2,69 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Dirty
5
- module InstanceMethods
6
- def method_missing(method, *args, &block)
7
- if method.to_s =~ /(_changed\?|_change|_will_change!|_was)$/
8
- method_suffix = $1
9
- key = method.to_s.gsub(method_suffix, '')
5
+ extend ActiveSupport::Concern
10
6
 
11
- if key_names.include?(key)
12
- case method_suffix
13
- when '_changed?'
14
- key_changed?(key)
15
- when '_change'
16
- key_change(key)
17
- when '_will_change!'
18
- key_will_change!(key)
19
- when '_was'
20
- key_was(key)
21
- end
22
- else
23
- super
24
- end
25
- else
26
- super
27
- end
28
- end
29
-
30
- def changed?
31
- !changed_keys.empty?
32
- end
33
-
34
- def changed
35
- changed_keys.keys
36
- end
37
-
38
- def changes
39
- changed.inject({}) { |h, key| h[key] = key_change(key); h }
40
- end
7
+ include ::ActiveModel::Dirty
41
8
 
9
+ module InstanceMethods
42
10
  def initialize(*)
43
11
  # never register initial id assignment as a change
44
- super.tap { changed_keys.delete('_id') }
12
+ super.tap { changed_attributes.delete('_id') }
45
13
  end
46
14
 
47
15
  def initialize_from_database(*)
48
- super.tap { changed_keys.clear }
16
+ super.tap { changed_attributes.clear }
49
17
  end
50
18
 
51
19
  def save(*)
52
- if status = super
53
- changed_keys.clear
54
- end
55
- status
20
+ clear_changes { super }
56
21
  end
57
22
 
58
23
  def save!(*)
59
- status = super
60
- changed_keys.clear
61
- status
24
+ clear_changes { super }
62
25
  end
63
26
 
64
27
  def reload(*)
65
- document = super
66
- changed_keys.clear
67
- document
28
+ super.tap { clear_changes }
68
29
  end
69
30
 
70
- private
71
- def clone_key_value(key)
72
- value = read_key(key)
73
- value.duplicable? ? value.clone : value
74
- rescue TypeError, NoMethodError
75
- value
76
- end
77
-
78
- def changed_keys
79
- @changed_keys ||= {}
80
- end
81
-
82
- def key_changed?(key)
83
- changed_keys.include?(key)
84
- end
85
-
86
- def key_change(key)
87
- [changed_keys[key], __send__(key)] if key_changed?(key)
88
- end
89
-
90
- def key_was(key)
91
- key_changed?(key) ? changed_keys[key] : __send__(key)
92
- end
31
+ protected
93
32
 
94
- def key_will_change!(key)
95
- changed_keys[key] = clone_key_value(key)
96
- end
97
-
98
- def write_key(key, value)
99
- key = key.to_s
33
+ def attribute_method?(attr)
34
+ # This overrides ::ActiveSupport::Dirty#attribute_method? to allow attributes to be any key
35
+ # in the attributes hash ( default ) or any key defined on the model that may not yet have
36
+ # had a value stored in the attributes collection.
37
+ super || key_names.include?(attr)
38
+ end
100
39
 
101
- if changed_keys.include?(key)
102
- old = changed_keys[key]
103
- changed_keys.delete(key) unless value_changed?(key, old, value)
104
- else
105
- old = clone_key_value(key)
106
- changed_keys[key] = old if value_changed?(key, old, value)
40
+ def clear_changes
41
+ previous = changes
42
+ (block_given? ? yield : true).tap do |result|
43
+ unless result == false #failed validation; nil is OK.
44
+ @previously_changed = previous
45
+ changed_attributes.clear
107
46
  end
108
-
109
- super(key, value)
110
47
  end
48
+ end
111
49
 
112
- def value_changed?(key_name, old, value)
113
- key = keys[key_name]
50
+ private
114
51
 
115
- if key.number? && value.blank?
116
- value = nil
117
- end
52
+ def write_key(key, value)
53
+ key = key.to_s
54
+ old = read_key(key)
55
+ attribute_will_change!(key) if attribute_should_change?(key, old, value)
56
+ changed_attributes.delete(key) unless value_changed?(key, attribute_was(key), value)
57
+ super(key, value)
58
+ end
118
59
 
119
- old != value
120
- end
60
+ def attribute_should_change?(key, old, value)
61
+ attribute_changed?(key) == false && value_changed?(key, old, value)
62
+ end
63
+
64
+ def value_changed?(key_name, old, value)
65
+ value = nil if keys[key_name.to_s].number? && value.blank?
66
+ old != value
67
+ end
121
68
  end
122
69
  end
123
70
  end