dm-mongo-adapter 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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