danielharan-mongo_mapper 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- 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.rb +134 -0
- data/lib/mongo_mapper/associations.rb +183 -0
- data/lib/mongo_mapper/associations/base.rb +110 -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 +25 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +127 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +53 -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 +80 -0
- data/lib/mongo_mapper/callbacks.rb +109 -0
- data/lib/mongo_mapper/dirty.rb +136 -0
- data/lib/mongo_mapper/document.rb +481 -0
- data/lib/mongo_mapper/dynamic_finder.rb +35 -0
- data/lib/mongo_mapper/embedded_document.rb +386 -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 +53 -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 +193 -0
- data/lib/mongo_mapper/validations.rb +41 -0
- data/mongo_mapper.gemspec +171 -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_embedded_polymorphic_proxy.rb +156 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +196 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
- data/test/functional/associations/test_many_proxy.rb +384 -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 +1180 -0
- data/test/functional/test_embedded_document.rb +125 -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 +369 -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/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_association_base.rb +166 -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 +233 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
# Base class for keeping track of associations.
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
class Base
|
7
|
+
attr_reader :type, :name, :options, :finder_options
|
8
|
+
|
9
|
+
# Options that should not be considered MongoDB query options/criteria
|
10
|
+
AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :polymorphic]
|
11
|
+
|
12
|
+
def initialize(type, name, options={}, &extension)
|
13
|
+
@type, @name, @options, @finder_options = type, name, {}, {}
|
14
|
+
options.symbolize_keys!
|
15
|
+
|
16
|
+
options[:extend] = modulized_extensions(extension, options[:extend])
|
17
|
+
|
18
|
+
options.each_pair do |key, value|
|
19
|
+
if AssociationOptions.include?(key)
|
20
|
+
@options[key] = value
|
21
|
+
else
|
22
|
+
@finder_options[key] = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def class_name
|
28
|
+
@class_name ||= begin
|
29
|
+
if cn = options[:class_name]
|
30
|
+
cn
|
31
|
+
elsif many?
|
32
|
+
name.to_s.singularize.camelize
|
33
|
+
else
|
34
|
+
name.to_s.camelize
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def klass
|
40
|
+
@klass ||= options[:class] || class_name.constantize
|
41
|
+
end
|
42
|
+
|
43
|
+
def many?
|
44
|
+
@many_type ||= @type == :many
|
45
|
+
end
|
46
|
+
|
47
|
+
def belongs_to?
|
48
|
+
@belongs_to_type ||= @type == :belongs_to
|
49
|
+
end
|
50
|
+
|
51
|
+
def polymorphic?
|
52
|
+
!!@options[:polymorphic]
|
53
|
+
end
|
54
|
+
|
55
|
+
def as?
|
56
|
+
!!@options[:as]
|
57
|
+
end
|
58
|
+
|
59
|
+
def type_key_name
|
60
|
+
@type_key_name ||= many? ? '_type' : "#{as}_type"
|
61
|
+
end
|
62
|
+
|
63
|
+
def as
|
64
|
+
@options[:as] || self.name
|
65
|
+
end
|
66
|
+
|
67
|
+
def foreign_key
|
68
|
+
@options[:foreign_key] || "#{name}_id"
|
69
|
+
end
|
70
|
+
|
71
|
+
def ivar
|
72
|
+
@ivar ||= "@_#{name}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def embeddable?
|
76
|
+
many? && klass.embeddable?
|
77
|
+
end
|
78
|
+
|
79
|
+
def proxy_class
|
80
|
+
@proxy_class ||= begin
|
81
|
+
if many?
|
82
|
+
if self.klass.embeddable?
|
83
|
+
polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
|
84
|
+
else
|
85
|
+
if polymorphic?
|
86
|
+
ManyPolymorphicProxy
|
87
|
+
elsif as?
|
88
|
+
ManyDocumentsAsProxy
|
89
|
+
else
|
90
|
+
ManyProxy
|
91
|
+
end
|
92
|
+
end
|
93
|
+
else
|
94
|
+
polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
|
95
|
+
end
|
96
|
+
end # end begin
|
97
|
+
end # end proxy_class
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# @param [Array<Module, Proc>] extensions a collection of Modules or
|
102
|
+
# Procs that extend the behaviour of this association.
|
103
|
+
def modulized_extensions(*extensions)
|
104
|
+
extensions.flatten.compact.map do |extension|
|
105
|
+
Proc === extension ? Module.new(&extension) : extension
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -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,25 @@
|
|
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
|
+
doc.send("#{as_type_name}=", @owner.class.name)
|
12
|
+
doc.send("#{as_id_name}=", @owner._id)
|
13
|
+
doc
|
14
|
+
end
|
15
|
+
|
16
|
+
def as_type_name
|
17
|
+
@as_type_name ||= @association.options[:as].to_s + "_type"
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_id_name
|
21
|
+
@as_id_name ||= @association.options[:as].to_s + "_id"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,127 @@
|
|
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 find!(*args)
|
15
|
+
options = args.extract_options!
|
16
|
+
klass.find!(*args << scoped_options(options))
|
17
|
+
end
|
18
|
+
|
19
|
+
def paginate(options)
|
20
|
+
klass.paginate(scoped_options(options))
|
21
|
+
end
|
22
|
+
|
23
|
+
def all(options={})
|
24
|
+
klass.all(scoped_options(options))
|
25
|
+
end
|
26
|
+
|
27
|
+
def first(options={})
|
28
|
+
klass.first(scoped_options(options))
|
29
|
+
end
|
30
|
+
|
31
|
+
def last(options={})
|
32
|
+
klass.last(scoped_options(options))
|
33
|
+
end
|
34
|
+
|
35
|
+
def count(options={})
|
36
|
+
klass.count(scoped_options(options))
|
37
|
+
end
|
38
|
+
|
39
|
+
def replace(docs)
|
40
|
+
@target.map(&:destroy) if load_target
|
41
|
+
docs.each { |doc| apply_scope(doc).save }
|
42
|
+
reset
|
43
|
+
end
|
44
|
+
|
45
|
+
def <<(*docs)
|
46
|
+
ensure_owner_saved
|
47
|
+
flatten_deeper(docs).each { |doc| apply_scope(doc).save }
|
48
|
+
reset
|
49
|
+
end
|
50
|
+
alias_method :push, :<<
|
51
|
+
alias_method :concat, :<<
|
52
|
+
|
53
|
+
def build(attrs={})
|
54
|
+
doc = klass.new(attrs)
|
55
|
+
apply_scope(doc)
|
56
|
+
doc
|
57
|
+
end
|
58
|
+
|
59
|
+
def create(attrs={})
|
60
|
+
doc = klass.new(attrs)
|
61
|
+
apply_scope(doc).save
|
62
|
+
doc
|
63
|
+
end
|
64
|
+
|
65
|
+
def create!(attrs={})
|
66
|
+
doc = klass.new(attrs)
|
67
|
+
apply_scope(doc).save!
|
68
|
+
doc
|
69
|
+
end
|
70
|
+
|
71
|
+
def destroy_all(options={})
|
72
|
+
all(options).map(&:destroy)
|
73
|
+
reset
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete_all(options={})
|
77
|
+
klass.delete_all(options.merge(scoped_conditions))
|
78
|
+
reset
|
79
|
+
end
|
80
|
+
|
81
|
+
def nullify
|
82
|
+
criteria = FinderOptions.new(klass, scoped_conditions).criteria
|
83
|
+
all(criteria).each do |doc|
|
84
|
+
doc.update_attributes(self.foreign_key => nil)
|
85
|
+
end
|
86
|
+
reset
|
87
|
+
end
|
88
|
+
|
89
|
+
def method_missing(method, *args)
|
90
|
+
finder = DynamicFinder.new(method)
|
91
|
+
|
92
|
+
if finder.found?
|
93
|
+
dynamic_find(finder, args)
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
def scoped_conditions
|
101
|
+
{self.foreign_key => @owner._id}
|
102
|
+
end
|
103
|
+
|
104
|
+
def scoped_options(options)
|
105
|
+
@association.finder_options.merge(options).merge(scoped_conditions)
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_target
|
109
|
+
all
|
110
|
+
end
|
111
|
+
|
112
|
+
def ensure_owner_saved
|
113
|
+
@owner.save if @owner.new?
|
114
|
+
end
|
115
|
+
|
116
|
+
def apply_scope(doc)
|
117
|
+
ensure_owner_saved
|
118
|
+
doc.send("#{self.foreign_key}=", @owner._id)
|
119
|
+
doc
|
120
|
+
end
|
121
|
+
|
122
|
+
def foreign_key
|
123
|
+
@association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
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,53 @@
|
|
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(attributes={})
|
10
|
+
doc = @association.klass.new(attributes)
|
11
|
+
assign_root_document(doc)
|
12
|
+
self << doc
|
13
|
+
doc
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: test that both string and oid version work
|
17
|
+
def find(id)
|
18
|
+
load_target
|
19
|
+
@target.detect { |item| item.id.to_s == id || item.id == id }
|
20
|
+
end
|
21
|
+
|
22
|
+
def <<(*docs)
|
23
|
+
if load_target
|
24
|
+
docs.each do |doc|
|
25
|
+
assign_root_document(doc)
|
26
|
+
@target << doc
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :push, :<<
|
31
|
+
alias_method :concat, :<<
|
32
|
+
|
33
|
+
private
|
34
|
+
def find_target
|
35
|
+
(@_values || []).map do |e|
|
36
|
+
child = @association.klass.new(e)
|
37
|
+
assign_root_document(child)
|
38
|
+
child
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def root_document
|
43
|
+
@owner._root_document || @owner
|
44
|
+
end
|
45
|
+
|
46
|
+
def assign_root_document(*docs)
|
47
|
+
docs.each do |doc|
|
48
|
+
doc._root_document = root_document
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,80 @@
|
|
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
|
+
@association.options[:extend].each { |ext| class << self; self; end.instance_eval { include ext } }
|
10
|
+
reset
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to?(*methods)
|
14
|
+
(load_target && @target.respond_to?(*methods))
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset
|
18
|
+
@target = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def reload_target
|
22
|
+
reset
|
23
|
+
load_target
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def send(method, *args)
|
28
|
+
metaclass_instance_methods = class << self; self; end.instance_methods
|
29
|
+
|
30
|
+
if metaclass_instance_methods.any? { |m| m.to_s == method.to_s }
|
31
|
+
return __send__(method, *args)
|
32
|
+
end
|
33
|
+
|
34
|
+
load_target
|
35
|
+
@target.send(method, *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def replace(v)
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
def inspect
|
43
|
+
load_target
|
44
|
+
@target.inspect
|
45
|
+
end
|
46
|
+
|
47
|
+
def nil?
|
48
|
+
load_target
|
49
|
+
@target.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
def method_missing(method, *args, &block)
|
54
|
+
if load_target
|
55
|
+
if block.nil?
|
56
|
+
@target.send(method, *args)
|
57
|
+
else
|
58
|
+
@target.send(method, *args) { |*block_args| block.call(*block_args) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_target
|
64
|
+
@target ||= find_target
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_target
|
68
|
+
raise NotImplementedError
|
69
|
+
end
|
70
|
+
|
71
|
+
# Array#flatten has problems with recursive arrays. Going one level
|
72
|
+
# deeper solves the majority of the problems.
|
73
|
+
def flatten_deeper(array)
|
74
|
+
array.collect do |element|
|
75
|
+
(element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
|
76
|
+
end.flatten
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|