tpitale-mongo_mapper 0.6.9

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 (75) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +53 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper/associations/base.rb +110 -0
  8. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
  9. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
  10. data/lib/mongo_mapper/associations/collection.rb +19 -0
  11. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
  12. data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
  13. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
  14. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
  15. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  16. data/lib/mongo_mapper/associations/proxy.rb +113 -0
  17. data/lib/mongo_mapper/associations.rb +70 -0
  18. data/lib/mongo_mapper/callbacks.rb +109 -0
  19. data/lib/mongo_mapper/dirty.rb +136 -0
  20. data/lib/mongo_mapper/document.rb +472 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  22. data/lib/mongo_mapper/embedded_document.rb +384 -0
  23. data/lib/mongo_mapper/finder_options.rb +133 -0
  24. data/lib/mongo_mapper/key.rb +36 -0
  25. data/lib/mongo_mapper/observing.rb +50 -0
  26. data/lib/mongo_mapper/pagination.rb +55 -0
  27. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  29. data/lib/mongo_mapper/serialization.rb +54 -0
  30. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  31. data/lib/mongo_mapper/support.rb +206 -0
  32. data/lib/mongo_mapper/validations.rb +41 -0
  33. data/lib/mongo_mapper.rb +120 -0
  34. data/mongo_mapper.gemspec +173 -0
  35. data/specs.watchr +32 -0
  36. data/test/NOTE_ON_TESTING +1 -0
  37. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  38. data/test/functional/associations/test_belongs_to_proxy.rb +48 -0
  39. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  40. data/test/functional/associations/test_many_documents_proxy.rb +387 -0
  41. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
  42. data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
  43. data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
  44. data/test/functional/test_associations.rb +44 -0
  45. data/test/functional/test_binary.rb +18 -0
  46. data/test/functional/test_callbacks.rb +85 -0
  47. data/test/functional/test_dirty.rb +159 -0
  48. data/test/functional/test_document.rb +1235 -0
  49. data/test/functional/test_embedded_document.rb +135 -0
  50. data/test/functional/test_logger.rb +20 -0
  51. data/test/functional/test_pagination.rb +95 -0
  52. data/test/functional/test_rails_compatibility.rb +25 -0
  53. data/test/functional/test_string_id_compatibility.rb +72 -0
  54. data/test/functional/test_validations.rb +378 -0
  55. data/test/models.rb +271 -0
  56. data/test/support/custom_matchers.rb +55 -0
  57. data/test/support/timing.rb +16 -0
  58. data/test/test_helper.rb +27 -0
  59. data/test/unit/associations/test_base.rb +166 -0
  60. data/test/unit/associations/test_proxy.rb +91 -0
  61. data/test/unit/serializers/test_json_serializer.rb +189 -0
  62. data/test/unit/test_document.rb +204 -0
  63. data/test/unit/test_dynamic_finder.rb +125 -0
  64. data/test/unit/test_embedded_document.rb +718 -0
  65. data/test/unit/test_finder_options.rb +296 -0
  66. data/test/unit/test_key.rb +172 -0
  67. data/test/unit/test_mongo_mapper.rb +65 -0
  68. data/test/unit/test_observing.rb +101 -0
  69. data/test/unit/test_pagination.rb +113 -0
  70. data/test/unit/test_rails_compatibility.rb +49 -0
  71. data/test/unit/test_serializations.rb +52 -0
  72. data/test/unit/test_support.rb +342 -0
  73. data/test/unit/test_time_zones.rb +40 -0
  74. data/test/unit/test_validations.rb +503 -0
  75. metadata +235 -0
@@ -0,0 +1,113 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class Proxy
4
+ alias :proxy_respond_to? :respond_to?
5
+ alias :proxy_extend :extend
6
+
7
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
8
+
9
+ attr_reader :owner, :reflection, :target
10
+
11
+ alias :proxy_owner :owner
12
+ alias :proxy_target :target
13
+ alias :proxy_reflection :reflection
14
+
15
+ delegate :klass, :to => :proxy_reflection
16
+ delegate :options, :to => :proxy_reflection
17
+ delegate :collection, :to => :klass
18
+
19
+ def initialize(owner, reflection)
20
+ @owner, @reflection = owner, reflection
21
+ Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
22
+ reset
23
+ end
24
+
25
+ def inspect
26
+ load_target
27
+ target.inspect
28
+ end
29
+
30
+ def loaded?
31
+ @loaded
32
+ end
33
+
34
+ def loaded
35
+ @loaded = true
36
+ end
37
+
38
+ def nil?
39
+ load_target
40
+ target.nil?
41
+ end
42
+
43
+ def blank?
44
+ load_target
45
+ target.blank?
46
+ end
47
+
48
+ def reload
49
+ reset
50
+ load_target
51
+ self unless target.nil?
52
+ end
53
+
54
+ def replace(v)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ def reset
59
+ @loaded = false
60
+ target = nil
61
+ end
62
+
63
+ def respond_to?(*args)
64
+ proxy_respond_to?(*args) || (load_target && target.respond_to?(*args))
65
+ end
66
+
67
+ def send(method, *args)
68
+ if proxy_respond_to?(method)
69
+ super
70
+ else
71
+ load_target
72
+ target.send(method, *args)
73
+ end
74
+ end
75
+
76
+ def ===(other)
77
+ load_target
78
+ other === target
79
+ end
80
+
81
+ protected
82
+ def method_missing(method, *args, &block)
83
+ if load_target
84
+ if block_given?
85
+ target.send(method, *args) { |*block_args| block.call(*block_args) }
86
+ else
87
+ target.send(method, *args)
88
+ end
89
+ end
90
+ end
91
+
92
+ def load_target
93
+ @target = find_target unless loaded?
94
+ loaded
95
+ @target
96
+ rescue MongoMapper::DocumentNotFound
97
+ reset
98
+ end
99
+
100
+ def find_target
101
+ raise NotImplementedError
102
+ end
103
+
104
+ # Array#flatten has problems with recursive arrays. Going one level
105
+ # deeper solves the majority of the problems.
106
+ def flatten_deeper(array)
107
+ array.collect do |element|
108
+ (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
109
+ end.flatten
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,70 @@
1
+ module MongoMapper
2
+ module Associations
3
+ module ClassMethods
4
+ def belongs_to(association_id, options={}, &extension)
5
+ create_association(:belongs_to, association_id, options, &extension)
6
+ self
7
+ end
8
+
9
+ def many(association_id, options={}, &extension)
10
+ create_association(:many, association_id, options, &extension)
11
+ self
12
+ end
13
+
14
+ def associations
15
+ @associations ||= HashWithIndifferentAccess.new
16
+ end
17
+
18
+ def associations=(hash)
19
+ @associations = hash
20
+ end
21
+
22
+ def inherited(subclass)
23
+ subclass.associations = associations.dup
24
+ super
25
+ end
26
+
27
+ private
28
+ def create_association(type, name, options, &extension)
29
+ association = Associations::Base.new(type, name, options, &extension)
30
+ associations[association.name] = association
31
+
32
+ define_method(association.name) do
33
+ get_proxy(association)
34
+ end
35
+
36
+ define_method("#{association.name}=") do |value|
37
+ get_proxy(association).replace(value)
38
+ value
39
+ end
40
+
41
+ if association.options[:dependent] && association.many? && !association.embeddable?
42
+ after_destroy do |doc|
43
+ case association.options[:dependent]
44
+ when :destroy
45
+ doc.get_proxy(association).destroy_all
46
+ when :delete_all
47
+ doc.get_proxy(association).delete_all
48
+ when :nullify
49
+ doc.get_proxy(association).nullify
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ module InstanceMethods
57
+ def associations
58
+ self.class.associations
59
+ end
60
+
61
+ def get_proxy(association)
62
+ unless proxy = self.instance_variable_get(association.ivar)
63
+ proxy = association.proxy_class.new(self, association)
64
+ self.instance_variable_set(association.ivar, proxy) if !frozen?
65
+ end
66
+ proxy
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,109 @@
1
+ module MongoMapper
2
+ # This module is mixed into the Document module to provide call-backs before
3
+ # and after the following events:
4
+ #
5
+ # * save
6
+ # * create
7
+ # * update
8
+ # * validation
9
+ # ** every validation
10
+ # ** validation when created
11
+ # ** validation when updated
12
+ # * destruction
13
+ #
14
+ # @see ActiveSupport::Callbacks
15
+ module Callbacks
16
+ def self.included(model) #:nodoc:
17
+ model.class_eval do
18
+ extend Observable
19
+ include ActiveSupport::Callbacks
20
+
21
+ callbacks = %w(
22
+ before_save
23
+ after_save
24
+ before_create
25
+ after_create
26
+ before_update
27
+ after_update
28
+ before_validation
29
+ after_validation
30
+ before_validation_on_create
31
+ after_validation_on_create
32
+ before_validation_on_update
33
+ after_validation_on_update
34
+ before_destroy
35
+ after_destroy
36
+ )
37
+
38
+ define_callbacks(*callbacks)
39
+
40
+ callbacks.each do |callback|
41
+ define_method(callback.to_sym) {}
42
+ end
43
+ end
44
+ end
45
+
46
+ def valid? #:nodoc:
47
+ return false if callback(:before_validation) == false
48
+ result = new? ? callback(:before_validation_on_create) : callback(:before_validation_on_update)
49
+ return false if false == result
50
+
51
+ result = super
52
+ callback(:after_validation)
53
+
54
+ new? ? callback(:after_validation_on_create) : callback(:after_validation_on_update)
55
+ return result
56
+ end
57
+
58
+ # Here we override the +destroy+ method to allow for the +before_destroy+
59
+ # and +after_destroy+ call-backs. Note that the +destroy+ call is aborted
60
+ # if the +before_destroy+ call-back returns +false+.
61
+ #
62
+ # @return the result of calling +destroy+ on the document
63
+ def destroy #:nodoc:
64
+ return false if callback(:before_destroy) == false
65
+ result = super
66
+ callback(:after_destroy)
67
+ result
68
+ end
69
+
70
+ private
71
+ def callback(method)
72
+ result = run_callbacks(method) { |result, object| false == result }
73
+
74
+ if result != false && respond_to?(method)
75
+ result = send(method)
76
+ end
77
+
78
+ notify(method)
79
+ return result
80
+ end
81
+
82
+ def notify(method) #:nodoc:
83
+ self.class.changed
84
+ self.class.notify_observers(method, self)
85
+ end
86
+
87
+ def create_or_update #:nodoc:
88
+ return false if callback(:before_save) == false
89
+ if result = super
90
+ callback(:after_save)
91
+ end
92
+ result
93
+ end
94
+
95
+ def create #:nodoc:
96
+ return false if callback(:before_create) == false
97
+ result = super
98
+ callback(:after_create)
99
+ result
100
+ end
101
+
102
+ def update(*args) #:nodoc:
103
+ return false if callback(:before_update) == false
104
+ result = super
105
+ callback(:after_update)
106
+ result
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,136 @@
1
+ module MongoMapper
2
+ module Dirty
3
+ DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
4
+
5
+ def method_missing(method, *args, &block)
6
+ if method.to_s =~ /(_changed\?|_change|_will_change!|_was)$/
7
+ method_suffix = $1
8
+ key = method.to_s.gsub(method_suffix, '')
9
+
10
+ if key_names.include?(key)
11
+ case method_suffix
12
+ when '_changed?'
13
+ key_changed?(key)
14
+ when '_change'
15
+ key_change(key)
16
+ when '_will_change!'
17
+ key_will_change!(key)
18
+ when '_was'
19
+ key_was(key)
20
+ end
21
+ else
22
+ super
23
+ end
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def changed?
30
+ !changed_keys.empty?
31
+ end
32
+
33
+ # List of keys with unsaved changes.
34
+ # person.changed # => []
35
+ # person.name = 'bob'
36
+ # person.changed # => ['name']
37
+ def changed
38
+ changed_keys.keys
39
+ end
40
+
41
+ # Map of changed attrs => [original value, new value].
42
+ # person.changes # => {}
43
+ # person.name = 'bob'
44
+ # person.changes # => { 'name' => ['bill', 'bob'] }
45
+ def changes
46
+ changed.inject({}) { |h, attribute| h[attribute] = key_change(attribute); h }
47
+ end
48
+
49
+ def initialize(attrs={})
50
+ super(attrs)
51
+ changed_keys.clear unless new?
52
+ end
53
+
54
+ # Attempts to +save+ the record and clears changed keys if successful.
55
+ def save(*args)
56
+ if status = super
57
+ changed_keys.clear
58
+ end
59
+ status
60
+ end
61
+
62
+ # Attempts to <tt>save!</tt> the record and clears changed keys if successful.
63
+ def save!(*args)
64
+ status = super
65
+ changed_keys.clear
66
+ status
67
+ end
68
+
69
+ # <tt>reload</tt> the record and clears changed keys.
70
+ # def reload(*args) #:nodoc:
71
+ # record = super
72
+ # changed_keys.clear
73
+ # record
74
+ # end
75
+
76
+ private
77
+ def clone_key_value(attribute_name)
78
+ value = send(:read_attribute, attribute_name)
79
+ value.duplicable? ? value.clone : value
80
+ rescue TypeError, NoMethodError
81
+ value
82
+ end
83
+
84
+ # Map of change <tt>attr => original value</tt>.
85
+ def changed_keys
86
+ @changed_keys ||= {}
87
+ end
88
+
89
+ # Handle <tt>*_changed?</tt> for +method_missing+.
90
+ def key_changed?(attribute)
91
+ changed_keys.include?(attribute)
92
+ end
93
+
94
+ # Handle <tt>*_change</tt> for +method_missing+.
95
+ def key_change(attribute)
96
+ [changed_keys[attribute], __send__(attribute)] if key_changed?(attribute)
97
+ end
98
+
99
+ # Handle <tt>*_was</tt> for +method_missing+.
100
+ def key_was(attribute)
101
+ key_changed?(attribute) ? changed_keys[attribute] : __send__(attribute)
102
+ end
103
+
104
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
105
+ def key_will_change!(attribute)
106
+ changed_keys[attribute] = clone_key_value(attribute)
107
+ end
108
+
109
+ # Wrap write_attribute to remember original key value.
110
+ def write_attribute(attribute, value)
111
+ attribute = attribute.to_s
112
+
113
+ # The key already has an unsaved change.
114
+ if changed_keys.include?(attribute)
115
+ old = changed_keys[attribute]
116
+ changed_keys.delete(attribute) unless value_changed?(attribute, old, value)
117
+ else
118
+ old = clone_key_value(attribute)
119
+ changed_keys[attribute] = old if value_changed?(attribute, old, value)
120
+ end
121
+
122
+ # Carry on.
123
+ super(attribute, value)
124
+ end
125
+
126
+ def value_changed?(key_name, old, value)
127
+ key = _keys[key_name]
128
+
129
+ if key.number? && value.blank?
130
+ value = nil
131
+ end
132
+
133
+ old != value
134
+ end
135
+ end
136
+ end