mongo_mapper-unstable 2009.10.11

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 (73) hide show
  1. data/.gitignore +8 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +50 -0
  4. data/Rakefile +87 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +55 -0
  7. data/lib/mongo_mapper/associations/base.rb +83 -0
  8. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  9. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
  10. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
  11. data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
  12. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  13. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
  14. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  15. data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
  16. data/lib/mongo_mapper/associations/proxy.rb +74 -0
  17. data/lib/mongo_mapper/associations.rb +86 -0
  18. data/lib/mongo_mapper/callbacks.rb +106 -0
  19. data/lib/mongo_mapper/dirty.rb +137 -0
  20. data/lib/mongo_mapper/document.rb +340 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +35 -0
  22. data/lib/mongo_mapper/embedded_document.rb +355 -0
  23. data/lib/mongo_mapper/finder_options.rb +98 -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 +51 -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/save_with_validation.rb +19 -0
  30. data/lib/mongo_mapper/serialization.rb +55 -0
  31. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongo_mapper/support.rb +161 -0
  33. data/lib/mongo_mapper/validations.rb +69 -0
  34. data/lib/mongo_mapper.rb +111 -0
  35. data/mongo_mapper.gemspec +162 -0
  36. data/specs.watchr +32 -0
  37. data/test/NOTE_ON_TESTING +1 -0
  38. data/test/custom_matchers.rb +55 -0
  39. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  40. data/test/functional/associations/test_belongs_to_proxy.rb +49 -0
  41. data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
  42. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
  43. data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
  44. data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
  45. data/test/functional/associations/test_many_proxy.rb +331 -0
  46. data/test/functional/test_associations.rb +44 -0
  47. data/test/functional/test_binary.rb +18 -0
  48. data/test/functional/test_callbacks.rb +85 -0
  49. data/test/functional/test_dirty.rb +138 -0
  50. data/test/functional/test_document.rb +1051 -0
  51. data/test/functional/test_embedded_document.rb +97 -0
  52. data/test/functional/test_logger.rb +20 -0
  53. data/test/functional/test_pagination.rb +87 -0
  54. data/test/functional/test_rails_compatibility.rb +30 -0
  55. data/test/functional/test_validations.rb +279 -0
  56. data/test/models.rb +195 -0
  57. data/test/test_helper.rb +30 -0
  58. data/test/unit/serializers/test_json_serializer.rb +189 -0
  59. data/test/unit/test_association_base.rb +144 -0
  60. data/test/unit/test_document.rb +184 -0
  61. data/test/unit/test_dynamic_finder.rb +125 -0
  62. data/test/unit/test_embedded_document.rb +656 -0
  63. data/test/unit/test_finder_options.rb +261 -0
  64. data/test/unit/test_key.rb +172 -0
  65. data/test/unit/test_mongomapper.rb +28 -0
  66. data/test/unit/test_observing.rb +101 -0
  67. data/test/unit/test_pagination.rb +109 -0
  68. data/test/unit/test_rails_compatibility.rb +39 -0
  69. data/test/unit/test_serializations.rb +52 -0
  70. data/test/unit/test_support.rb +291 -0
  71. data/test/unit/test_time_zones.rb +40 -0
  72. data/test/unit/test_validations.rb +503 -0
  73. metadata +210 -0
@@ -0,0 +1,74 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class Proxy < BasicObject
4
+ attr_reader :owner, :association
5
+
6
+ def initialize(owner, association)
7
+ @owner = owner
8
+ @association = association
9
+ reset
10
+ end
11
+
12
+ def respond_to?(*methods)
13
+ (load_target && @target.respond_to?(*methods))
14
+ end
15
+
16
+ def reset
17
+ @target = nil
18
+ end
19
+
20
+ def reload_target
21
+ reset
22
+ load_target
23
+ self
24
+ end
25
+
26
+ def send(method, *args)
27
+ return super if methods.include?(method.to_s)
28
+ load_target
29
+ @target.send(method, *args)
30
+ end
31
+
32
+ def replace(v)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def inspect
37
+ load_target
38
+ @target.inspect
39
+ end
40
+
41
+ def nil?
42
+ load_target
43
+ @target.nil?
44
+ end
45
+
46
+ protected
47
+ def method_missing(method, *args)
48
+ if load_target
49
+ if block_given?
50
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
51
+ else
52
+ @target.send(method, *args)
53
+ end
54
+ end
55
+ end
56
+
57
+ def load_target
58
+ @target ||= find_target
59
+ end
60
+
61
+ def find_target
62
+ raise NotImplementedError
63
+ end
64
+
65
+ # Array#flatten has problems with recursive arrays. Going one level
66
+ # deeper solves the majority of the problems.
67
+ def flatten_deeper(array)
68
+ array.collect do |element|
69
+ (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
70
+ end.flatten
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ module MongoMapper
2
+ module Associations
3
+ module ClassMethods
4
+ def belongs_to(association_id, options = {})
5
+ create_association(:belongs_to, association_id, options)
6
+ self
7
+ end
8
+
9
+ def many(association_id, options = {})
10
+ create_association(:many, association_id, options)
11
+ self
12
+ end
13
+
14
+ def associations
15
+ @associations ||= self.superclass.respond_to?(:associations) ?
16
+ self.superclass.associations :
17
+ HashWithIndifferentAccess.new
18
+ end
19
+
20
+ private
21
+ def create_association(type, name, options)
22
+ association = Associations::Base.new(type, name, options)
23
+ associations[association.name] = association
24
+ define_association_methods(association)
25
+ define_dependent_callback(association)
26
+ association
27
+ end
28
+
29
+ def define_association_methods(association)
30
+ define_method(association.name) do
31
+ get_proxy(association)
32
+ end
33
+
34
+ define_method("#{association.name}=") do |value|
35
+ get_proxy(association).replace(value)
36
+ value
37
+ end
38
+ end
39
+
40
+ def define_dependent_callback(association)
41
+ if association.options[:dependent]
42
+ if association.many?
43
+ define_dependent_callback_for_many(association)
44
+ elsif association.belongs_to?
45
+ define_dependent_callback_for_belongs_to(association)
46
+ end
47
+ end
48
+ end
49
+
50
+ def define_dependent_callback_for_many(association)
51
+ return if association.embeddable?
52
+
53
+ after_destroy do |doc|
54
+ case association.options[:dependent]
55
+ when :destroy
56
+ doc.get_proxy(association).destroy_all
57
+ when :delete_all
58
+ doc.get_proxy(association).delete_all
59
+ when :nullify
60
+ doc.get_proxy(association).nullify
61
+ end
62
+ end
63
+ end
64
+
65
+ def define_dependent_callback_for_belongs_to(association)
66
+ after_destroy do |doc|
67
+ case association.options[:dependent]
68
+ when :destroy
69
+ doc.get_proxy(association).destroy
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ module InstanceMethods
76
+ def get_proxy(association)
77
+ unless proxy = self.instance_variable_get(association.ivar)
78
+ proxy = association.proxy_class.new(self, association)
79
+ self.instance_variable_set(association.ivar, proxy) if !frozen?
80
+ end
81
+
82
+ proxy
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,106 @@
1
+ module MongoMapper
2
+ module Callbacks
3
+ def self.included(model) #:nodoc:
4
+ model.class_eval do
5
+ extend Observable
6
+ include ActiveSupport::Callbacks
7
+
8
+ define_callbacks *%w(
9
+ before_save after_save before_create after_create before_update after_update before_validation
10
+ after_validation before_validation_on_create after_validation_on_create before_validation_on_update
11
+ after_validation_on_update before_destroy after_destroy
12
+ )
13
+
14
+ [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
15
+ alias_method_chain method, :callbacks
16
+ end
17
+ end
18
+ end
19
+
20
+ def before_save() end
21
+
22
+ def after_save() end
23
+ def create_or_update_with_callbacks #:nodoc:
24
+ return false if callback(:before_save) == false
25
+ if result = create_or_update_without_callbacks
26
+ callback(:after_save)
27
+ end
28
+ result
29
+ end
30
+ private :create_or_update_with_callbacks
31
+
32
+ def before_create() end
33
+
34
+ def after_create() end
35
+ def create_with_callbacks #:nodoc:
36
+ return false if callback(:before_create) == false
37
+ result = create_without_callbacks
38
+ callback(:after_create)
39
+ result
40
+ end
41
+ private :create_with_callbacks
42
+
43
+ def before_update() end
44
+
45
+ def after_update() end
46
+
47
+ def update_with_callbacks(*args) #:nodoc:
48
+ return false if callback(:before_update) == false
49
+ result = update_without_callbacks(*args)
50
+ callback(:after_update)
51
+ result
52
+ end
53
+ private :update_with_callbacks
54
+
55
+ def before_validation() end
56
+
57
+ def after_validation() end
58
+
59
+ def before_validation_on_create() end
60
+
61
+ def after_validation_on_create() end
62
+
63
+ def before_validation_on_update() end
64
+
65
+ def after_validation_on_update() end
66
+
67
+ def valid_with_callbacks? #:nodoc:
68
+ return false if callback(:before_validation) == false
69
+ result = new? ? callback(:before_validation_on_create) : callback(:before_validation_on_update)
70
+ return false if false == result
71
+
72
+ result = valid_without_callbacks?
73
+ callback(:after_validation)
74
+
75
+ new? ? callback(:after_validation_on_create) : callback(:after_validation_on_update)
76
+ return result
77
+ end
78
+
79
+ def before_destroy() end
80
+
81
+ def after_destroy() end
82
+ def destroy_with_callbacks #:nodoc:
83
+ return false if callback(:before_destroy) == false
84
+ result = destroy_without_callbacks
85
+ callback(:after_destroy)
86
+ result
87
+ end
88
+
89
+ private
90
+ def callback(method)
91
+ result = run_callbacks(method) { |result, object| false == result }
92
+
93
+ if result != false && respond_to?(method)
94
+ result = send(method)
95
+ end
96
+
97
+ notify(method)
98
+ return result
99
+ end
100
+
101
+ def notify(method) #:nodoc:
102
+ self.class.changed
103
+ self.class.notify_observers(method, self)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,137 @@
1
+ module MongoMapper
2
+ module Dirty
3
+ DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
4
+
5
+ def self.included(base)
6
+ base.alias_method_chain :write_attribute, :dirty
7
+ base.alias_method_chain :save, :dirty
8
+ base.alias_method_chain :save!, :dirty
9
+ end
10
+
11
+ def method_missing(method, *args, &block)
12
+ if method.to_s =~ /(_changed\?|_change|_will_change!|_was)$/
13
+ method_suffix = $1
14
+ key = method.to_s.gsub(method_suffix, '')
15
+
16
+ if key_names.include?(key)
17
+ case method_suffix
18
+ when '_changed?'
19
+ key_changed?(key)
20
+ when '_change'
21
+ key_change(key)
22
+ when '_will_change!'
23
+ key_will_change!(key)
24
+ when '_was'
25
+ key_was(key)
26
+ end
27
+ else
28
+ super
29
+ end
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ def changed?
36
+ !changed_keys.empty?
37
+ end
38
+
39
+ # List of keys with unsaved changes.
40
+ # person.changed # => []
41
+ # person.name = 'bob'
42
+ # person.changed # => ['name']
43
+ def changed
44
+ changed_keys.keys
45
+ end
46
+
47
+ # Map of changed attrs => [original value, new value].
48
+ # person.changes # => {}
49
+ # person.name = 'bob'
50
+ # person.changes # => { 'name' => ['bill', 'bob'] }
51
+ def changes
52
+ changed.inject({}) { |h, attribute| h[attribute] = key_change(attribute); h }
53
+ end
54
+
55
+ # Attempts to +save+ the record and clears changed keys if successful.
56
+ def save_with_dirty(*args) #:nodoc:
57
+ if status = save_without_dirty(*args)
58
+ changed_keys.clear
59
+ end
60
+ status
61
+ end
62
+
63
+ # Attempts to <tt>save!</tt> the record and clears changed keys if successful.
64
+ def save_with_dirty!(*args) #:nodoc:
65
+ status = save_without_dirty!(*args)
66
+ changed_keys.clear
67
+ status
68
+ end
69
+
70
+ # <tt>reload</tt> the record and clears changed keys.
71
+ # def reload_with_dirty(*args) #:nodoc:
72
+ # record = reload_without_dirty(*args)
73
+ # changed_keys.clear
74
+ # record
75
+ # end
76
+
77
+ private
78
+ def clone_key_value(attribute_name)
79
+ value = send(:read_attribute, attribute_name)
80
+ value.duplicable? ? value.clone : value
81
+ rescue TypeError, NoMethodError
82
+ value
83
+ end
84
+
85
+ # Map of change <tt>attr => original value</tt>.
86
+ def changed_keys
87
+ @changed_keys ||= {}
88
+ end
89
+
90
+ # Handle <tt>*_changed?</tt> for +method_missing+.
91
+ def key_changed?(attribute)
92
+ changed_keys.include?(attribute)
93
+ end
94
+
95
+ # Handle <tt>*_change</tt> for +method_missing+.
96
+ def key_change(attribute)
97
+ [changed_keys[attribute], __send__(attribute)] if key_changed?(attribute)
98
+ end
99
+
100
+ # Handle <tt>*_was</tt> for +method_missing+.
101
+ def key_was(attribute)
102
+ key_changed?(attribute) ? changed_keys[attribute] : __send__(attribute)
103
+ end
104
+
105
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
106
+ def key_will_change!(attribute)
107
+ changed_keys[attribute] = clone_key_value(attribute)
108
+ end
109
+
110
+ # Wrap write_attribute to remember original key value.
111
+ def write_attribute_with_dirty(attribute, value)
112
+ attribute = attribute.to_s
113
+
114
+ # The key already has an unsaved change.
115
+ if changed_keys.include?(attribute)
116
+ old = changed_keys[attribute]
117
+ changed_keys.delete(attribute) unless value_changed?(attribute, old, value)
118
+ else
119
+ old = clone_key_value(attribute)
120
+ changed_keys[attribute] = old if value_changed?(attribute, old, value)
121
+ end
122
+
123
+ # Carry on.
124
+ write_attribute_without_dirty(attribute, value)
125
+ end
126
+
127
+ def value_changed?(key_name, old, value)
128
+ key = _keys[key_name]
129
+
130
+ if key.number? && value.blank?
131
+ value = nil
132
+ end
133
+
134
+ old != value
135
+ end
136
+ end
137
+ end