dm-mongo-adapter 0.2.0.pre1
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 +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
|