mongo_mapper 0.7.0 → 0.7.1

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 (43) hide show
  1. data/README.rdoc +3 -1
  2. data/Rakefile +9 -12
  3. data/lib/mongo_mapper.rb +30 -10
  4. data/lib/mongo_mapper/document.rb +16 -74
  5. data/lib/mongo_mapper/embedded_document.rb +7 -1
  6. data/lib/mongo_mapper/plugins.rb +3 -0
  7. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +1 -12
  8. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +6 -1
  9. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +4 -1
  10. data/lib/mongo_mapper/plugins/callbacks.rb +183 -12
  11. data/lib/mongo_mapper/plugins/keys.rb +17 -5
  12. data/lib/mongo_mapper/plugins/modifiers.rb +87 -0
  13. data/lib/mongo_mapper/plugins/pagination/proxy.rb +7 -3
  14. data/lib/mongo_mapper/plugins/protected.rb +1 -1
  15. data/lib/mongo_mapper/plugins/rails.rb +16 -8
  16. data/lib/mongo_mapper/plugins/serialization.rb +51 -81
  17. data/lib/mongo_mapper/plugins/timestamps.rb +21 -0
  18. data/lib/mongo_mapper/plugins/userstamps.rb +14 -0
  19. data/lib/mongo_mapper/query.rb +1 -1
  20. data/lib/mongo_mapper/version.rb +3 -0
  21. data/mongo_mapper.gemspec +22 -11
  22. data/test/active_model_lint_test.rb +11 -0
  23. data/test/functional/associations/test_in_array_proxy.rb +16 -0
  24. data/test/functional/associations/test_many_documents_proxy.rb +22 -0
  25. data/test/functional/test_callbacks.rb +104 -34
  26. data/test/functional/test_document.rb +70 -149
  27. data/test/functional/test_embedded_document.rb +39 -34
  28. data/test/functional/test_indexing.rb +44 -0
  29. data/test/functional/test_modifiers.rb +297 -227
  30. data/test/functional/test_protected.rb +11 -5
  31. data/test/functional/test_timestamps.rb +64 -0
  32. data/test/functional/test_userstamps.rb +28 -0
  33. data/test/support/timing.rb +1 -1
  34. data/test/unit/serializers/test_json_serializer.rb +30 -17
  35. data/test/unit/test_embedded_document.rb +15 -15
  36. data/test/unit/test_keys.rb +15 -11
  37. data/test/unit/test_mongo_mapper.rb +31 -1
  38. data/test/unit/test_pagination.rb +33 -0
  39. data/test/unit/test_query.rb +6 -0
  40. data/test/unit/test_serialization.rb +3 -3
  41. data/test/unit/test_support.rb +9 -5
  42. metadata +17 -6
  43. data/VERSION +0 -1
@@ -170,11 +170,14 @@ module MongoMapper
170
170
 
171
171
  def attributes=(attrs)
172
172
  return if attrs.blank?
173
-
173
+
174
174
  attrs.each_pair do |name, value|
175
175
  writer_method = "#{name}="
176
176
 
177
177
  if respond_to?(writer_method)
178
+ if writer_method == '_root_document='
179
+ puts "_root_document= #{value.inspect}"
180
+ end
178
181
  self.send(writer_method, value)
179
182
  else
180
183
  self[name.to_s] = value
@@ -217,7 +220,7 @@ module MongoMapper
217
220
  def id
218
221
  _id
219
222
  end
220
-
223
+
221
224
  def id=(value)
222
225
  if self.class.using_object_id?
223
226
  value = MongoMapper.normalize_object_id(value)
@@ -259,7 +262,7 @@ module MongoMapper
259
262
  def assign_type_if_present
260
263
  self._type = self.class.name if respond_to?(:_type=)
261
264
  end
262
-
265
+
263
266
  def ensure_key_exists(name)
264
267
  self.class.key(name) unless respond_to?("#{name}=")
265
268
  end
@@ -280,11 +283,16 @@ module MongoMapper
280
283
 
281
284
  def write_key(name, value)
282
285
  key = keys[name]
286
+
287
+ if key.embeddable? && value.is_a?(key.type)
288
+ value._parent_document = self
289
+ end
290
+
283
291
  instance_variable_set "@#{name}_before_typecast", value
284
292
  instance_variable_set "@#{name}", key.set(value)
285
293
  end
286
294
  end
287
-
295
+
288
296
  class Key
289
297
  attr_accessor :name, :type, :options, :default_value
290
298
 
@@ -309,7 +317,11 @@ module MongoMapper
309
317
 
310
318
  def get(value)
311
319
  if value.nil? && !default_value.nil?
312
- return default_value
320
+ if default_value.respond_to?(:call)
321
+ return default_value.call
322
+ else
323
+ return default_value
324
+ end
313
325
  end
314
326
 
315
327
  type.from_mongo(value)
@@ -0,0 +1,87 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Modifiers
4
+ module ClassMethods
5
+ def increment(*args)
6
+ modifier_update('$inc', args)
7
+ end
8
+
9
+ def decrement(*args)
10
+ criteria, keys = criteria_and_keys_from_args(args)
11
+ values, to_decrement = keys.values, {}
12
+ keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
13
+ collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
14
+ end
15
+
16
+ def set(*args)
17
+ modifier_update('$set', args)
18
+ end
19
+
20
+ def push(*args)
21
+ modifier_update('$push', args)
22
+ end
23
+
24
+ def push_all(*args)
25
+ modifier_update('$pushAll', args)
26
+ end
27
+
28
+ def push_uniq(*args)
29
+ criteria, keys = criteria_and_keys_from_args(args)
30
+ keys.each { |key, value | criteria[key] = {'$ne' => value} }
31
+ collection.update(criteria, {'$push' => keys}, :multi => true)
32
+ end
33
+
34
+ def pull(*args)
35
+ modifier_update('$pull', args)
36
+ end
37
+
38
+ def pull_all(*args)
39
+ modifier_update('$pullAll', args)
40
+ end
41
+
42
+ def pop(*args)
43
+ modifier_update('$pop', args)
44
+ end
45
+
46
+ private
47
+ def modifier_update(modifier, args)
48
+ criteria, keys = criteria_and_keys_from_args(args)
49
+ modifiers = {modifier => keys}
50
+ collection.update(criteria, modifiers, :multi => true)
51
+ end
52
+
53
+ def criteria_and_keys_from_args(args)
54
+ keys = args.pop
55
+ criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
56
+ [to_criteria(criteria), keys]
57
+ end
58
+ end
59
+
60
+ module InstanceMethods
61
+ def increment(hash)
62
+ self.class.increment({:_id => id}, hash)
63
+ end
64
+
65
+ def decrement(hash)
66
+ self.class.decrement({:_id => id}, hash)
67
+ end
68
+
69
+ def set(hash)
70
+ self.class.set({:_id => id}, hash)
71
+ end
72
+
73
+ def push(hash)
74
+ self.class.push({:_id => id}, hash)
75
+ end
76
+
77
+ def pull(hash)
78
+ self.class.pull({:_id => id}, hash)
79
+ end
80
+
81
+ def push_uniq(hash)
82
+ self.class.push_uniq({:_id => id}, hash)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -2,8 +2,8 @@ module MongoMapper
2
2
  module Plugins
3
3
  module Pagination
4
4
  class Proxy
5
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
6
-
5
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|respond_to\?|proxy_|^object_id$)/ }
6
+
7
7
  attr_accessor :subject
8
8
  attr_reader :total_entries, :per_page, :current_page
9
9
  alias limit per_page
@@ -50,7 +50,11 @@ module MongoMapper
50
50
  def method_missing(name, *args, &block)
51
51
  @subject.send(name, *args, &block)
52
52
  end
53
-
53
+
54
+ def respond_to?(name, *args, &block)
55
+ super || @subject.respond_to?(name, *args, &block)
56
+ end
57
+
54
58
  private
55
59
  def per_page=(value)
56
60
  value = 25 if value.blank?
@@ -37,7 +37,7 @@ module MongoMapper
37
37
  protected
38
38
  def filter_protected_attrs(attrs)
39
39
  return attrs if protected_attributes.blank?
40
- attrs.dup.delete_if { |key, val| protected_attributes.include?(key) }
40
+ attrs.dup.delete_if { |key, val| protected_attributes.include?(key.to_sym) }
41
41
  end
42
42
  end
43
43
  end
@@ -1,41 +1,49 @@
1
1
  module MongoMapper
2
2
  module Plugins
3
3
  module Rails
4
+ def self.configure(model)
5
+ model.extend ActiveModel::Naming if defined?(ActiveModel)
6
+ end
7
+
4
8
  module InstanceMethods
5
9
  def to_param
6
10
  id.to_s
7
11
  end
8
-
12
+
13
+ def to_model
14
+ self
15
+ end
16
+
9
17
  def new_record?
10
18
  new?
11
19
  end
12
-
20
+
13
21
  def read_attribute(name)
14
22
  self[name]
15
23
  end
16
-
24
+
17
25
  def read_attribute_before_typecast(name)
18
26
  read_key_before_typecast(name)
19
27
  end
20
-
28
+
21
29
  def write_attribute(name, value)
22
30
  self[name] = value
23
31
  end
24
32
  end
25
-
33
+
26
34
  module ClassMethods
27
35
  def has_one(*args)
28
36
  one(*args)
29
37
  end
30
-
38
+
31
39
  def has_many(*args)
32
40
  many(*args)
33
41
  end
34
-
42
+
35
43
  def column_names
36
44
  keys.keys
37
45
  end
38
-
46
+
39
47
  def human_name
40
48
  self.name.demodulize.titleize
41
49
  end
@@ -4,102 +4,72 @@ module MongoMapper
4
4
  module Plugins
5
5
  module Serialization
6
6
  def self.configure(model)
7
- model.class_eval { include Json }
7
+ model.class_eval { cattr_accessor :include_root_in_json, :instance_writer => true }
8
8
  end
9
9
 
10
- class Serializer
11
- attr_reader :options
12
-
13
- def initialize(record, options={})
14
- @record, @options = record, options.dup
15
- end
16
-
17
- def serializable_key_names
18
- key_names = @record.attributes.keys
19
-
20
- if options[:only]
21
- options.delete(:except)
22
- key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
23
- else
24
- options[:except] = Array(options[:except])
25
- key_names = key_names - options[:except].collect { |n| n.to_s }
26
- end
27
-
28
- key_names
29
- end
30
-
31
- def serializable_method_names
32
- Array(options[:methods]).inject([]) do |method_attributes, name|
33
- method_attributes << name if @record.respond_to?(name.to_s)
34
- method_attributes
10
+ module InstanceMethods
11
+ def as_json options={}
12
+ options ||= {}
13
+ unless options[:only]
14
+ methods = [options.delete(:methods)].flatten.compact
15
+ methods << :id
16
+ options[:methods] = methods.uniq
35
17
  end
36
- end
37
18
 
38
- def serializable_names
39
- serializable_key_names + serializable_method_names
40
- end
19
+ except = [options.delete(:except)].flatten.compact
20
+ except << :_id
21
+ options[:except] = except
41
22
 
42
- def serializable_record
43
- returning(serializable_record = {}) do
44
- serializable_names.each { |name| serializable_record[name] = @record.send(name) }
45
- end
46
- end
23
+ # Direct rip from Rails 3 ActiveModel Serialization (#serializable_hash)
24
+ hash = begin
25
+ options[:only] = Array.wrap(options[:only]).map { |n| n.to_s }
26
+ options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
47
27
 
48
- def serialize
49
- # overwrite to implement
50
- end
28
+ attribute_names = attributes.keys.sort
29
+ if options[:only].any?
30
+ attribute_names &= options[:only]
31
+ elsif options[:except].any?
32
+ attribute_names -= options[:except]
33
+ end
51
34
 
52
- def to_s(&block)
53
- serialize(&block)
54
- end
55
- end
56
-
57
- module Json
58
- def self.included(base)
59
- base.cattr_accessor :include_root_in_json, :instance_writer => false
60
- base.extend ClassMethods
61
- end
35
+ method_names = Array.wrap(options[:methods]).inject([]) do |methods, name|
36
+ methods << name if respond_to?(name.to_s)
37
+ methods
38
+ end
62
39
 
63
- module ClassMethods
64
- def json_class_name
65
- @json_class_name ||= name.demodulize.underscore.inspect
40
+ (attribute_names + method_names).inject({}) { |hash, name|
41
+ hash[name] = send(name)
42
+ hash
43
+ }
66
44
  end
67
- end
68
-
69
- def to_json(options={})
70
- apply_to_json_defaults(options)
71
-
72
- if include_root_in_json
73
- "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
74
- else
75
- JsonSerializer.new(self, options).to_s
45
+ # End rip
46
+
47
+ options.delete(:only) if options[:only].nil? or options[:only].empty?
48
+
49
+ hash.each do |key, value|
50
+ if value.is_a?(Array)
51
+ hash[key] = value.map do |item|
52
+ item.respond_to?(:as_json) ? item.as_json(options) : item
53
+ end
54
+ elsif value.is_a? Mongo::ObjectID
55
+ hash[key] = value.to_s
56
+ elsif value.respond_to?(:as_json)
57
+ hash[key] = value.as_json(options)
58
+ end
76
59
  end
60
+
61
+ # Replicate Rails 3 naming - and also bin anytihng after : for use in our dynamic classes from unit tests
62
+ hash = { ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).gsub(/:.*/,'') => hash } if include_root_in_json
63
+ hash
77
64
  end
65
+ end
78
66
 
67
+ module ClassMethods
79
68
  def from_json(json)
80
- self.attributes = ActiveSupport::JSON.decode(json)
81
- self
69
+ self.new(ActiveSupport::JSON.decode(json))
82
70
  end
83
-
84
- class JsonSerializer < Serializer
85
- def serialize
86
- serializable_record.to_json
87
- end
88
- end
89
-
90
- private
91
- def apply_to_json_defaults(options)
92
- unless options[:only]
93
- methods = [options.delete(:methods)].flatten.compact
94
- methods << :id
95
- options[:methods] = methods.uniq
96
- end
97
-
98
- except = [options.delete(:except)].flatten.compact
99
- except << :_id
100
- options[:except] = except
101
- end
102
71
  end
72
+
103
73
  end
104
74
  end
105
75
  end
@@ -0,0 +1,21 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Timestamps
4
+ module ClassMethods
5
+ def timestamps!
6
+ key :created_at, Time
7
+ key :updated_at, Time
8
+ class_eval { before_save :update_timestamps }
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+ def update_timestamps
14
+ now = Time.now.utc
15
+ self[:created_at] = now if new? && !created_at?
16
+ self[:updated_at] = now
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Userstamps
4
+ module ClassMethods
5
+ def userstamps!
6
+ key :creator_id, ObjectId
7
+ key :updater_id, ObjectId
8
+ belongs_to :creator, :class_name => 'User'
9
+ belongs_to :updater, :class_name => 'User'
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -93,7 +93,7 @@ module MongoMapper
93
93
  def to_order(field, direction=nil)
94
94
  direction ||= 'ASC'
95
95
  direction = direction.upcase == 'ASC' ? 1 : -1
96
- [field.to_s, direction]
96
+ [normalized_key(field).to_s, direction]
97
97
  end
98
98
 
99
99
  def normalized_key(field)
@@ -0,0 +1,3 @@
1
+ module MongoMapper
2
+ Version = '0.7.1'
3
+ end