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.
- data/.gitignore +10 -0
- data/LICENSE +20 -0
- data/README.rdoc +53 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/mmconsole +60 -0
- data/lib/mongo_mapper/associations/base.rb +110 -0
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
- data/lib/mongo_mapper/associations/collection.rb +19 -0
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongo_mapper/associations/proxy.rb +113 -0
- data/lib/mongo_mapper/associations.rb +70 -0
- data/lib/mongo_mapper/callbacks.rb +109 -0
- data/lib/mongo_mapper/dirty.rb +136 -0
- data/lib/mongo_mapper/document.rb +472 -0
- data/lib/mongo_mapper/dynamic_finder.rb +74 -0
- data/lib/mongo_mapper/embedded_document.rb +384 -0
- data/lib/mongo_mapper/finder_options.rb +133 -0
- data/lib/mongo_mapper/key.rb +36 -0
- data/lib/mongo_mapper/observing.rb +50 -0
- data/lib/mongo_mapper/pagination.rb +55 -0
- data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
- data/lib/mongo_mapper/serialization.rb +54 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
- data/lib/mongo_mapper/support.rb +206 -0
- data/lib/mongo_mapper/validations.rb +41 -0
- data/lib/mongo_mapper.rb +120 -0
- data/mongo_mapper.gemspec +173 -0
- data/specs.watchr +32 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +48 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
- data/test/functional/associations/test_many_documents_proxy.rb +387 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
- data/test/functional/test_associations.rb +44 -0
- data/test/functional/test_binary.rb +18 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_dirty.rb +159 -0
- data/test/functional/test_document.rb +1235 -0
- data/test/functional/test_embedded_document.rb +135 -0
- data/test/functional/test_logger.rb +20 -0
- data/test/functional/test_pagination.rb +95 -0
- data/test/functional/test_rails_compatibility.rb +25 -0
- data/test/functional/test_string_id_compatibility.rb +72 -0
- data/test/functional/test_validations.rb +378 -0
- data/test/models.rb +271 -0
- data/test/support/custom_matchers.rb +55 -0
- data/test/support/timing.rb +16 -0
- data/test/test_helper.rb +27 -0
- data/test/unit/associations/test_base.rb +166 -0
- data/test/unit/associations/test_proxy.rb +91 -0
- data/test/unit/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_document.rb +204 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +718 -0
- data/test/unit/test_finder_options.rb +296 -0
- data/test/unit/test_key.rb +172 -0
- data/test/unit/test_mongo_mapper.rb +65 -0
- data/test/unit/test_observing.rb +101 -0
- data/test/unit/test_pagination.rb +113 -0
- data/test/unit/test_rails_compatibility.rb +49 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +342 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- 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
|