dm-mongo-adapter 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +130 -0
- data/Rakefile +36 -0
- data/TODO +33 -0
- data/VERSION.yml +5 -0
- data/bin/console +31 -0
- data/dm-mongo-adapter.gemspec +154 -0
- data/lib/mongo_adapter.rb +71 -0
- data/lib/mongo_adapter/adapter.rb +255 -0
- data/lib/mongo_adapter/aggregates.rb +21 -0
- data/lib/mongo_adapter/conditions.rb +100 -0
- data/lib/mongo_adapter/embedded_model.rb +187 -0
- data/lib/mongo_adapter/embedded_resource.rb +134 -0
- data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
- data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
- data/lib/mongo_adapter/embedments/relationship.rb +258 -0
- data/lib/mongo_adapter/migrations.rb +45 -0
- data/lib/mongo_adapter/model.rb +89 -0
- data/lib/mongo_adapter/model/embedment.rb +215 -0
- data/lib/mongo_adapter/modifier.rb +85 -0
- data/lib/mongo_adapter/query.rb +252 -0
- data/lib/mongo_adapter/query/java_script.rb +145 -0
- data/lib/mongo_adapter/resource.rb +147 -0
- data/lib/mongo_adapter/types/date.rb +28 -0
- data/lib/mongo_adapter/types/date_time.rb +28 -0
- data/lib/mongo_adapter/types/db_ref.rb +65 -0
- data/lib/mongo_adapter/types/discriminator.rb +32 -0
- data/lib/mongo_adapter/types/object_id.rb +72 -0
- data/lib/mongo_adapter/types/objects.rb +31 -0
- data/script/performance.rb +260 -0
- data/spec/legacy/README +6 -0
- data/spec/legacy/adapter_shared_spec.rb +299 -0
- data/spec/legacy/adapter_spec.rb +174 -0
- data/spec/legacy/associations_spec.rb +140 -0
- data/spec/legacy/embedded_resource_spec.rb +157 -0
- data/spec/legacy/embedments_spec.rb +177 -0
- data/spec/legacy/modifier_spec.rb +81 -0
- data/spec/legacy/property_spec.rb +51 -0
- data/spec/legacy/spec_helper.rb +3 -0
- data/spec/legacy/sti_spec.rb +53 -0
- data/spec/lib/cleanup_models.rb +32 -0
- data/spec/lib/raw_connections.rb +11 -0
- data/spec/public/embedded_collection_spec.rb +61 -0
- data/spec/public/embedded_resource_spec.rb +220 -0
- data/spec/public/model/embedment_spec.rb +186 -0
- data/spec/public/model_spec.rb +37 -0
- data/spec/public/resource_spec.rb +564 -0
- data/spec/public/shared/model_embedments_spec.rb +338 -0
- data/spec/public/shared/object_id_shared_spec.rb +56 -0
- data/spec/public/types/df_ref_spec.rb +6 -0
- data/spec/public/types/discriminator_spec.rb +118 -0
- data/spec/public/types/embedded_array_spec.rb +55 -0
- data/spec/public/types/embedded_hash_spec.rb +83 -0
- data/spec/public/types/object_id_spec.rb +6 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/embedded_model_spec.rb +43 -0
- data/spec/semipublic/model/embedment_spec.rb +42 -0
- data/spec/semipublic/resource_spec.rb +70 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +45 -0
- data/tasks/spec.rake +37 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +21 -0
- metadata +215 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
class Query
|
4
|
+
# TODO: document
|
5
|
+
module JavaScript
|
6
|
+
class Operation
|
7
|
+
attr_reader :initial
|
8
|
+
|
9
|
+
# TODO: document
|
10
|
+
# @api semipublic
|
11
|
+
def initialize(fields)
|
12
|
+
@initial = {}
|
13
|
+
@reduce = Reduce.new(fields)
|
14
|
+
|
15
|
+
if @reduce.operations.values.include?(:avg)
|
16
|
+
@finalize = Finalize.new(fields.select{ |field| field.operator == :avg })
|
17
|
+
end
|
18
|
+
|
19
|
+
fields.each do |field|
|
20
|
+
field_name = field.target.name
|
21
|
+
|
22
|
+
@initial[field_name] = 0
|
23
|
+
|
24
|
+
if field.operator == :avg
|
25
|
+
@initial["#{field_name}_count"] = 0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: document
|
31
|
+
# @api semipublic
|
32
|
+
def reduce
|
33
|
+
@reduce.create
|
34
|
+
end
|
35
|
+
|
36
|
+
# TODO: document
|
37
|
+
# @api semipublic
|
38
|
+
def finalize
|
39
|
+
@finalize ? @finalize.create : nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Function
|
44
|
+
attr_reader :operations
|
45
|
+
|
46
|
+
# TODO: document
|
47
|
+
# @api semipublic
|
48
|
+
def initialize(fields=[])
|
49
|
+
@operations = {}
|
50
|
+
|
51
|
+
fields.each do |field|
|
52
|
+
@operations[field.target.name] = field.operator
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# TODO: document
|
57
|
+
# @api public
|
58
|
+
def to_s
|
59
|
+
create
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO: document
|
63
|
+
# @api semipublic
|
64
|
+
def create(*args, &block)
|
65
|
+
@function ||= "function(#{args.join(', ')}) { #{yield.flatten.join(';') if block_given?} }"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Reduce < Function
|
70
|
+
# TODO: document
|
71
|
+
# @api semipublic
|
72
|
+
def create
|
73
|
+
super('doc', 'out') do
|
74
|
+
@operations.map do |field, operation|
|
75
|
+
send(operation, field)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# TODO: document
|
83
|
+
# @api private
|
84
|
+
def count(field)
|
85
|
+
"out.#{field}++"
|
86
|
+
end
|
87
|
+
|
88
|
+
# TODO: document
|
89
|
+
# @api private
|
90
|
+
def min(field)
|
91
|
+
<<-JS
|
92
|
+
if (doc.#{field} < doc.#{field} || out.#{field} == 0) {
|
93
|
+
out.#{field} = doc.#{field};
|
94
|
+
}
|
95
|
+
JS
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO: document
|
99
|
+
# @api private
|
100
|
+
def max(field)
|
101
|
+
<<-JS
|
102
|
+
if (doc.#{field} > out.#{field}) {
|
103
|
+
out.#{field} = doc.#{field};
|
104
|
+
}
|
105
|
+
JS
|
106
|
+
end
|
107
|
+
|
108
|
+
# TODO: document
|
109
|
+
# @api private
|
110
|
+
def sum(field)
|
111
|
+
<<-JS
|
112
|
+
out.#{field} += doc.#{field}
|
113
|
+
JS
|
114
|
+
end
|
115
|
+
|
116
|
+
# TODO: document
|
117
|
+
# @api private
|
118
|
+
def avg(field)
|
119
|
+
[sum(field), count("#{field}_count")]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Finalize < Function
|
124
|
+
# TODO: document
|
125
|
+
# @api semipublic
|
126
|
+
def create
|
127
|
+
super('out') do
|
128
|
+
@operations.map do |field, operation|
|
129
|
+
send(operation, field)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# TODO: document
|
137
|
+
# @api private
|
138
|
+
def avg(field)
|
139
|
+
"out.#{field} = out.#{field} / out.#{field}_count"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end # JavaScript
|
143
|
+
end # Query
|
144
|
+
end # Mongo
|
145
|
+
end # DataMapper
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
# Used in preference over DataMapper::Resource to add MongoDB-specific
|
4
|
+
# functionality to models.
|
5
|
+
module Resource
|
6
|
+
def self.included(model)
|
7
|
+
model.send(:include, DataMapper::Resource)
|
8
|
+
model.send(:include, ResourceMethods)
|
9
|
+
model.send(:include, Modifier)
|
10
|
+
|
11
|
+
# Needs to be after the inclusion of DM::Resource so as to overwrite
|
12
|
+
# methods added by DM::Model.
|
13
|
+
model.extend(Model)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ResourceMethods
|
17
|
+
# monkey patching based on this: http://github.com/datamapper/dm-core/commit/3332db6c25ab9cea9ba58ce62a9ad3038303baa1
|
18
|
+
# TODO: remove once dm-core 0.10.3 is released
|
19
|
+
def eager_load(properties)
|
20
|
+
unless properties.empty? || key.nil? || collection.nil?
|
21
|
+
collection.reload(:fields => properties)
|
22
|
+
end
|
23
|
+
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Assign values to multiple attributes in one call (mass assignment)
|
28
|
+
#
|
29
|
+
# Overrides attributes= in dm-core so as to permit assignments to
|
30
|
+
# embedments.
|
31
|
+
#
|
32
|
+
# @param [Hash] attributes
|
33
|
+
# names and values of attributes to assign
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
# names and values of attributes assigned
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def attributes=(attributes)
|
40
|
+
attributes.each do |name, value|
|
41
|
+
name.set(self, value) if name.kind_of?(Embedments::Relationship)
|
42
|
+
end
|
43
|
+
|
44
|
+
super(attributes)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Checks if the resource, or embedded documents, have unsaved changes
|
48
|
+
#
|
49
|
+
# @return [Boolean]
|
50
|
+
# True if resource may be persisted
|
51
|
+
#
|
52
|
+
# @overrides DataMapper::Resource#dirty?
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def dirty?
|
56
|
+
super || run_once(true) { dirty_embedments? }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Checks if any embedded documents have unsaved changes
|
60
|
+
#
|
61
|
+
# @return [Boolean]
|
62
|
+
# True if any embedded documents can be persisted
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def dirty_embedments?
|
66
|
+
embedments.values.any? do |embedment|
|
67
|
+
embedment.loaded?(self) && case embedment
|
68
|
+
when Embedments::OneToOne::Relationship then embedment.get!(self).dirty?
|
69
|
+
when Embedments::OneToMany::Relationship then embedment.get!(self).any? { |r| r.dirty? }
|
70
|
+
else false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Hash of attributes that have unsaved changes
|
76
|
+
#
|
77
|
+
# @return [Hash]
|
78
|
+
# attributes that have unsaved changes
|
79
|
+
#
|
80
|
+
# @overrides DataMapper::Resource#dirty_attributes
|
81
|
+
#
|
82
|
+
# @api semipublic
|
83
|
+
def dirty_attributes
|
84
|
+
embedded_attributes = {}
|
85
|
+
|
86
|
+
each_embedment do |name, target|
|
87
|
+
case (embedment = embedments[name])
|
88
|
+
when Embedments::OneToMany::Relationship
|
89
|
+
target.each do |resource|
|
90
|
+
if resource.dirty?
|
91
|
+
embedded_attributes[embedment] ||= []
|
92
|
+
embedded_attributes[embedment] << resource.dirty_attributes
|
93
|
+
end
|
94
|
+
end
|
95
|
+
when Embedments::OneToOne::Relationship
|
96
|
+
# Relationship target is a single resource.
|
97
|
+
if target.dirty?
|
98
|
+
embedded_attributes[embedment] = target.dirty_attributes
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
super.merge(embedded_attributes)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Saves the resource and it's embedments
|
107
|
+
#
|
108
|
+
# @return [Boolean]
|
109
|
+
# True if the resource was successfully saved
|
110
|
+
#
|
111
|
+
# @overrides DataMapper::Resource#save_self
|
112
|
+
#
|
113
|
+
# @api semipublic
|
114
|
+
def save_self(safe = true)
|
115
|
+
super && embedments.values.each do |e|
|
116
|
+
e.loaded?(self) && Array(e.get!(self)).each { |r| r.original_attributes.clear }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# The embedments (relationships to embedded objects) on this model
|
123
|
+
#
|
124
|
+
# @return [Hash<Symbol,Embedments::Relationship>]
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
def embedments
|
128
|
+
model.embedments
|
129
|
+
end
|
130
|
+
|
131
|
+
# Iterates through each loaded embedment, yielding the name and value
|
132
|
+
#
|
133
|
+
# @yieldparam [Symbol]
|
134
|
+
# The name of the embedment
|
135
|
+
# @yieldparam [Mongo::Collection]
|
136
|
+
# The embedded resource, or collection of embedded resources
|
137
|
+
#
|
138
|
+
# @api private
|
139
|
+
def each_embedment
|
140
|
+
embedments.each { |name, embedment|
|
141
|
+
embedment.loaded?(self) && yield(name, embedment.get!(self)) }
|
142
|
+
end
|
143
|
+
end # ResourceMethods
|
144
|
+
|
145
|
+
end # Resource
|
146
|
+
end # Mongo
|
147
|
+
end # DataMapper
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Types
|
4
|
+
class Date < DataMapper::Type
|
5
|
+
primitive ::Time
|
6
|
+
|
7
|
+
def self.load(value, property)
|
8
|
+
self.typecast(value, property)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.dump(value, property)
|
12
|
+
self.typecast(value, property)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.typecast(value, property)
|
16
|
+
case value
|
17
|
+
when ::Date
|
18
|
+
Time.utc(value.year, value.month, value.day)
|
19
|
+
when ::Time
|
20
|
+
::Date.new(value.year, value.month, value.day)
|
21
|
+
when NilClass, Range
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # Types
|
27
|
+
end # Mongo
|
28
|
+
end # DataMapper
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Types
|
4
|
+
class DateTime < DataMapper::Type
|
5
|
+
primitive Time
|
6
|
+
|
7
|
+
def self.load(value, property)
|
8
|
+
self.typecast(value, property)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.dump(value, property)
|
12
|
+
self.typecast(value, property)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.typecast(value, property)
|
16
|
+
case value
|
17
|
+
when Time
|
18
|
+
value.to_datetime
|
19
|
+
when ::DateTime
|
20
|
+
value.to_time.utc
|
21
|
+
when NilClass, Range
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # Types
|
27
|
+
end # Mongo
|
28
|
+
end # DataMapper
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Types
|
4
|
+
# Database references are references from one document (object) to
|
5
|
+
# another within a database. A database reference is a standard embedded
|
6
|
+
# object: this is a MongoDB convention, not a special type.
|
7
|
+
#
|
8
|
+
# The DBRef is made available via your model as a String.
|
9
|
+
#
|
10
|
+
# @see http://www.mongodb.org/display/DOCS/DB+Ref
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
class DBRef < DataMapper::Type
|
14
|
+
primitive ::Object
|
15
|
+
|
16
|
+
# Returns the DBRef as a string; suitable for use in a Resource
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
def self.load(value, property)
|
22
|
+
typecast(value, property)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the DBRef as a Mongo ObjectID; suitable to be passed to the
|
26
|
+
# Mongo library
|
27
|
+
#
|
28
|
+
# @return [Mongo::ObjectID] The dumped ID.
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def self.dump(value, property)
|
32
|
+
case value
|
33
|
+
when NilClass
|
34
|
+
nil
|
35
|
+
when String
|
36
|
+
::Mongo::ObjectID.from_string(value)
|
37
|
+
when ::Mongo::ObjectID
|
38
|
+
value
|
39
|
+
else
|
40
|
+
raise ArgumentError.new('+value+ must be nil, String, ObjectID')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the DBRef as a string
|
45
|
+
#
|
46
|
+
# @return [String]
|
47
|
+
#
|
48
|
+
# @api semipublic
|
49
|
+
def self.typecast(value, property)
|
50
|
+
case value
|
51
|
+
when NilClass
|
52
|
+
nil
|
53
|
+
when String
|
54
|
+
value
|
55
|
+
when ::Mongo::ObjectID
|
56
|
+
value.to_s
|
57
|
+
else
|
58
|
+
raise ArgumentError.new('+value+ must be nil, String, ObjectID')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end # DBRef
|
63
|
+
end # Types
|
64
|
+
end # Mongo
|
65
|
+
end # DataMapper
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Types
|
4
|
+
class Discriminator < DataMapper::Types::Discriminator
|
5
|
+
primitive String
|
6
|
+
default lambda { |resource, property| resource.model.to_s }
|
7
|
+
|
8
|
+
def self.load(value, property)
|
9
|
+
typecast(value, property)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.dump(value, property)
|
13
|
+
value.name
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.typecast(value, property)
|
17
|
+
if value
|
18
|
+
if value.is_a?(String)
|
19
|
+
Extlib::Inflection.constantize(value)
|
20
|
+
else
|
21
|
+
value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.==(other)
|
27
|
+
other == DataMapper::Types::Discriminator || super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|