mongo_mapper 0.5.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.
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +87 -0
- data/VERSION +1 -0
- data/bin/mmconsole +55 -0
- data/lib/mongo_mapper.rb +92 -0
- data/lib/mongo_mapper/associations.rb +86 -0
- data/lib/mongo_mapper/associations/base.rb +83 -0
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
- data/lib/mongo_mapper/associations/proxy.rb +64 -0
- data/lib/mongo_mapper/callbacks.rb +106 -0
- data/lib/mongo_mapper/document.rb +317 -0
- data/lib/mongo_mapper/dynamic_finder.rb +35 -0
- data/lib/mongo_mapper/embedded_document.rb +354 -0
- data/lib/mongo_mapper/finder_options.rb +94 -0
- data/lib/mongo_mapper/key.rb +32 -0
- data/lib/mongo_mapper/observing.rb +50 -0
- data/lib/mongo_mapper/pagination.rb +51 -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/save_with_validation.rb +19 -0
- data/lib/mongo_mapper/serialization.rb +55 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
- data/lib/mongo_mapper/support.rb +157 -0
- data/lib/mongo_mapper/validations.rb +69 -0
- data/mongo_mapper.gemspec +156 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/custom_matchers.rb +48 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +54 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +46 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
- data/test/functional/associations/test_many_proxy.rb +331 -0
- data/test/functional/test_associations.rb +48 -0
- data/test/functional/test_binary.rb +18 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_document.rb +951 -0
- data/test/functional/test_embedded_document.rb +97 -0
- data/test/functional/test_pagination.rb +87 -0
- data/test/functional/test_rails_compatibility.rb +30 -0
- data/test/functional/test_validations.rb +279 -0
- data/test/models.rb +169 -0
- data/test/test_helper.rb +29 -0
- data/test/unit/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_association_base.rb +144 -0
- data/test/unit/test_document.rb +165 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +645 -0
- data/test/unit/test_finder_options.rb +193 -0
- data/test/unit/test_key.rb +163 -0
- data/test/unit/test_mongomapper.rb +28 -0
- data/test/unit/test_observing.rb +101 -0
- data/test/unit/test_pagination.rb +109 -0
- data/test/unit/test_rails_compatibility.rb +39 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +272 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- metadata +204 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class BelongsToPolymorphicProxy < Proxy
|
4
|
+
def replace(doc)
|
5
|
+
if doc
|
6
|
+
doc.save if doc.new?
|
7
|
+
id, type = doc.id, doc.class.name
|
8
|
+
end
|
9
|
+
|
10
|
+
@owner.send("#{@association.foreign_key}=", id)
|
11
|
+
@owner.send("#{@association.type_key_name}=", type)
|
12
|
+
reset
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
def find_target
|
17
|
+
if proxy_id && proxy_class
|
18
|
+
proxy_class.find_by_id(proxy_id)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def proxy_id
|
23
|
+
@proxy_id ||= @owner.send(@association.foreign_key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def proxy_class
|
27
|
+
@proxy_class ||= begin
|
28
|
+
klass = @owner.send(@association.type_key_name)
|
29
|
+
klass && klass.constantize
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class BelongsToProxy < Proxy
|
4
|
+
def replace(doc)
|
5
|
+
if doc
|
6
|
+
doc.save if doc.new?
|
7
|
+
id = doc.id
|
8
|
+
end
|
9
|
+
|
10
|
+
@owner.send("#{@association.foreign_key}=", id)
|
11
|
+
reset
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
def find_target
|
16
|
+
if association_id = @owner.send(@association.foreign_key)
|
17
|
+
@association.klass.find_by_id(association_id)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class ManyDocumentsAsProxy < ManyDocumentsProxy
|
4
|
+
protected
|
5
|
+
def scoped_conditions
|
6
|
+
{as_type_name => @owner.class.name, as_id_name => @owner.id}
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply_scope(doc)
|
10
|
+
ensure_owner_saved
|
11
|
+
|
12
|
+
doc.send("#{as_type_name}=", @owner.class.name)
|
13
|
+
doc.send("#{as_id_name}=", @owner.id)
|
14
|
+
|
15
|
+
doc
|
16
|
+
end
|
17
|
+
|
18
|
+
def as_type_name
|
19
|
+
@as_type_name ||= @association.options[:as].to_s + "_type"
|
20
|
+
end
|
21
|
+
|
22
|
+
def as_id_name
|
23
|
+
@as_id_name ||= @association.options[:as].to_s + "_id"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class ManyDocumentsProxy < Proxy
|
4
|
+
delegate :klass, :to => :@association
|
5
|
+
delegate :collection, :to => :klass
|
6
|
+
|
7
|
+
include MongoMapper::Finders
|
8
|
+
|
9
|
+
def find(*args)
|
10
|
+
options = args.extract_options!
|
11
|
+
klass.find(*args << scoped_options(options))
|
12
|
+
end
|
13
|
+
|
14
|
+
def paginate(options)
|
15
|
+
klass.paginate(scoped_options(options))
|
16
|
+
end
|
17
|
+
|
18
|
+
def all(options={})
|
19
|
+
find(:all, scoped_options(options))
|
20
|
+
end
|
21
|
+
|
22
|
+
def first(options={})
|
23
|
+
find(:first, scoped_options(options))
|
24
|
+
end
|
25
|
+
|
26
|
+
def last(options={})
|
27
|
+
find(:last, scoped_options(options))
|
28
|
+
end
|
29
|
+
|
30
|
+
def count(conditions={})
|
31
|
+
klass.count(conditions.deep_merge(scoped_conditions))
|
32
|
+
end
|
33
|
+
|
34
|
+
def replace(docs)
|
35
|
+
@target.map(&:destroy) if load_target
|
36
|
+
docs.each { |doc| apply_scope(doc).save }
|
37
|
+
reset
|
38
|
+
end
|
39
|
+
|
40
|
+
def <<(*docs)
|
41
|
+
ensure_owner_saved
|
42
|
+
flatten_deeper(docs).each { |doc| apply_scope(doc).save }
|
43
|
+
reset
|
44
|
+
end
|
45
|
+
alias_method :push, :<<
|
46
|
+
alias_method :concat, :<<
|
47
|
+
|
48
|
+
def build(attrs={})
|
49
|
+
doc = klass.new(attrs)
|
50
|
+
apply_scope(doc)
|
51
|
+
doc
|
52
|
+
end
|
53
|
+
|
54
|
+
def create(attrs={})
|
55
|
+
doc = klass.new(attrs)
|
56
|
+
apply_scope(doc).save
|
57
|
+
doc
|
58
|
+
end
|
59
|
+
|
60
|
+
def destroy_all(conditions={})
|
61
|
+
all(:conditions => conditions).map(&:destroy)
|
62
|
+
reset
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete_all(conditions={})
|
66
|
+
klass.delete_all(conditions.deep_merge(scoped_conditions))
|
67
|
+
reset
|
68
|
+
end
|
69
|
+
|
70
|
+
def nullify
|
71
|
+
criteria = FinderOptions.to_mongo_criteria(scoped_conditions)
|
72
|
+
all(criteria).each do |doc|
|
73
|
+
doc.update_attributes self.foreign_key => nil
|
74
|
+
end
|
75
|
+
reset
|
76
|
+
end
|
77
|
+
|
78
|
+
def method_missing(method, *args)
|
79
|
+
finder = DynamicFinder.new(method)
|
80
|
+
|
81
|
+
if finder.found?
|
82
|
+
dynamic_find(finder, args)
|
83
|
+
else
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
def scoped_conditions
|
90
|
+
{self.foreign_key => @owner.id}
|
91
|
+
end
|
92
|
+
|
93
|
+
def scoped_options(options)
|
94
|
+
options.deep_merge({:conditions => scoped_conditions})
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_target
|
98
|
+
find(:all)
|
99
|
+
end
|
100
|
+
|
101
|
+
def ensure_owner_saved
|
102
|
+
@owner.save if @owner.new?
|
103
|
+
end
|
104
|
+
|
105
|
+
def apply_scope(doc)
|
106
|
+
ensure_owner_saved
|
107
|
+
doc.send("#{self.foreign_key}=", @owner.id)
|
108
|
+
doc
|
109
|
+
end
|
110
|
+
|
111
|
+
def foreign_key
|
112
|
+
@association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class ManyEmbeddedPolymorphicProxy < Proxy
|
4
|
+
def replace(v)
|
5
|
+
@_values = v.map do |doc_or_hash|
|
6
|
+
if doc_or_hash.kind_of?(EmbeddedDocument)
|
7
|
+
doc = doc_or_hash
|
8
|
+
{@association.type_key_name => doc.class.name}.merge(doc.attributes)
|
9
|
+
else
|
10
|
+
doc_or_hash
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
def find_target
|
19
|
+
(@_values || []).map do |hash|
|
20
|
+
polymorphic_class(hash).new(hash)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def polymorphic_class(doc)
|
25
|
+
if class_name = doc[@association.type_key_name]
|
26
|
+
class_name.constantize
|
27
|
+
else
|
28
|
+
@association.klass
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class ManyEmbeddedProxy < Proxy
|
4
|
+
def replace(v)
|
5
|
+
@_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
|
6
|
+
reset
|
7
|
+
end
|
8
|
+
|
9
|
+
def build(opts={})
|
10
|
+
owner = @owner
|
11
|
+
child = @association.klass.new(opts)
|
12
|
+
assign_parent_reference(child)
|
13
|
+
child._root_document = owner
|
14
|
+
self << child
|
15
|
+
child
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(opts)
|
19
|
+
case opts
|
20
|
+
when :all
|
21
|
+
self
|
22
|
+
when String
|
23
|
+
if load_target
|
24
|
+
child = @target.detect {|item| item.id == opts}
|
25
|
+
assign_parent_reference(child)
|
26
|
+
child
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def <<(*docs)
|
32
|
+
if load_target
|
33
|
+
root = @owner._root_document || @owner
|
34
|
+
docs.each do |doc|
|
35
|
+
doc._root_document = root
|
36
|
+
@target << doc
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
alias_method :push, :<<
|
41
|
+
alias_method :concat, :<<
|
42
|
+
|
43
|
+
protected
|
44
|
+
def find_target
|
45
|
+
(@_values || []).map do |e|
|
46
|
+
child = @association.klass.new(e)
|
47
|
+
assign_parent_reference(child)
|
48
|
+
child
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def assign_parent_reference(child)
|
55
|
+
return unless child && @owner
|
56
|
+
return if @owner.class.name.blank?
|
57
|
+
owner = @owner
|
58
|
+
child.class_eval do
|
59
|
+
define_method(owner.class.name.underscore) do
|
60
|
+
owner
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,64 @@
|
|
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
|
+
protected
|
37
|
+
def method_missing(method, *args)
|
38
|
+
if load_target
|
39
|
+
if block_given?
|
40
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
41
|
+
else
|
42
|
+
@target.send(method, *args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_target
|
48
|
+
@target ||= find_target
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_target
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
55
|
+
# Array#flatten has problems with recursive arrays. Going one level
|
56
|
+
# deeper solves the majority of the problems.
|
57
|
+
def flatten_deeper(array)
|
58
|
+
array.collect do |element|
|
59
|
+
(element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
|
60
|
+
end.flatten
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
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
|