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.
Files changed (65) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +130 -0
  4. data/Rakefile +36 -0
  5. data/TODO +33 -0
  6. data/VERSION.yml +5 -0
  7. data/bin/console +31 -0
  8. data/dm-mongo-adapter.gemspec +154 -0
  9. data/lib/mongo_adapter.rb +71 -0
  10. data/lib/mongo_adapter/adapter.rb +255 -0
  11. data/lib/mongo_adapter/aggregates.rb +21 -0
  12. data/lib/mongo_adapter/conditions.rb +100 -0
  13. data/lib/mongo_adapter/embedded_model.rb +187 -0
  14. data/lib/mongo_adapter/embedded_resource.rb +134 -0
  15. data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
  16. data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
  17. data/lib/mongo_adapter/embedments/relationship.rb +258 -0
  18. data/lib/mongo_adapter/migrations.rb +45 -0
  19. data/lib/mongo_adapter/model.rb +89 -0
  20. data/lib/mongo_adapter/model/embedment.rb +215 -0
  21. data/lib/mongo_adapter/modifier.rb +85 -0
  22. data/lib/mongo_adapter/query.rb +252 -0
  23. data/lib/mongo_adapter/query/java_script.rb +145 -0
  24. data/lib/mongo_adapter/resource.rb +147 -0
  25. data/lib/mongo_adapter/types/date.rb +28 -0
  26. data/lib/mongo_adapter/types/date_time.rb +28 -0
  27. data/lib/mongo_adapter/types/db_ref.rb +65 -0
  28. data/lib/mongo_adapter/types/discriminator.rb +32 -0
  29. data/lib/mongo_adapter/types/object_id.rb +72 -0
  30. data/lib/mongo_adapter/types/objects.rb +31 -0
  31. data/script/performance.rb +260 -0
  32. data/spec/legacy/README +6 -0
  33. data/spec/legacy/adapter_shared_spec.rb +299 -0
  34. data/spec/legacy/adapter_spec.rb +174 -0
  35. data/spec/legacy/associations_spec.rb +140 -0
  36. data/spec/legacy/embedded_resource_spec.rb +157 -0
  37. data/spec/legacy/embedments_spec.rb +177 -0
  38. data/spec/legacy/modifier_spec.rb +81 -0
  39. data/spec/legacy/property_spec.rb +51 -0
  40. data/spec/legacy/spec_helper.rb +3 -0
  41. data/spec/legacy/sti_spec.rb +53 -0
  42. data/spec/lib/cleanup_models.rb +32 -0
  43. data/spec/lib/raw_connections.rb +11 -0
  44. data/spec/public/embedded_collection_spec.rb +61 -0
  45. data/spec/public/embedded_resource_spec.rb +220 -0
  46. data/spec/public/model/embedment_spec.rb +186 -0
  47. data/spec/public/model_spec.rb +37 -0
  48. data/spec/public/resource_spec.rb +564 -0
  49. data/spec/public/shared/model_embedments_spec.rb +338 -0
  50. data/spec/public/shared/object_id_shared_spec.rb +56 -0
  51. data/spec/public/types/df_ref_spec.rb +6 -0
  52. data/spec/public/types/discriminator_spec.rb +118 -0
  53. data/spec/public/types/embedded_array_spec.rb +55 -0
  54. data/spec/public/types/embedded_hash_spec.rb +83 -0
  55. data/spec/public/types/object_id_spec.rb +6 -0
  56. data/spec/rcov.opts +6 -0
  57. data/spec/semipublic/embedded_model_spec.rb +43 -0
  58. data/spec/semipublic/model/embedment_spec.rb +42 -0
  59. data/spec/semipublic/resource_spec.rb +70 -0
  60. data/spec/spec.opts +4 -0
  61. data/spec/spec_helper.rb +45 -0
  62. data/tasks/spec.rake +37 -0
  63. data/tasks/yard.rake +9 -0
  64. data/tasks/yardstick.rake +21 -0
  65. 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