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,134 @@
1
+ module DataMapper
2
+ module Mongo
3
+ module EmbeddedResource
4
+ # Raised when trying to save an EmbeddedResource which doesn't have a
5
+ # parent set.
6
+ class MissingParentError < StandardError; end
7
+
8
+ include Types
9
+ include DataMapper::Resource
10
+
11
+ def self.included(base)
12
+ base.extend(DataMapper::Mongo::EmbeddedModel)
13
+ end
14
+
15
+ # @api public
16
+ alias_method :model, :class
17
+
18
+ # Returns the resource to which this EmbeddedResource belongs.
19
+ #
20
+ # @return [DataMapper::Mongo::Resource]
21
+ # The parent
22
+ #
23
+ # @api public
24
+ attr_reader :parent
25
+
26
+ # Gets all the attributes of the EmbeddedResource instance
27
+ #
28
+ # @param [Symbol] key_on
29
+ # Use this attribute of the Property as keys.
30
+ # defaults to :name. :field is useful for adapters
31
+ # :property or nil use the actual Property object.
32
+ #
33
+ # @return [Hash]
34
+ # All the attributes
35
+ #
36
+ # @overrides DataMapper::Resource#attributes
37
+ #
38
+ # @api public
39
+ def attributes(key_on=:name)
40
+ attributes = {}
41
+
42
+ fields.each do |property|
43
+ if model.public_method_defined?(name = property.name)
44
+ key = case key_on
45
+ when :name then name
46
+ when :field then property.field
47
+ else property
48
+ end
49
+
50
+ attributes[key] = __send__(name)
51
+ end
52
+ end
53
+
54
+ attributes
55
+ end
56
+
57
+ # Sets the resource to which this EmbeddedResource belongs
58
+ #
59
+ # @param [DataMapper::Mongo::Resource] resource
60
+ # The new parent resource
61
+ #
62
+ # @api public
63
+ def parent=(resource)
64
+ @parent = resource
65
+ end
66
+
67
+ # Returns whether this resource (or rather, it's parent) has been saved
68
+ #
69
+ # @return [Boolean]
70
+ #
71
+ # @api public
72
+ def saved?
73
+ parent && parent.saved?
74
+ end
75
+
76
+ # Returns whether this resource (or rather, it's parent) is unsaved
77
+ #
78
+ # @return [Boolean]
79
+ #
80
+ # @api public
81
+ def new?
82
+ !parent? || parent.new?
83
+ end
84
+
85
+ # Returns if the EmbeddedResource has a parent set
86
+ #
87
+ # @return [Boolean]
88
+ #
89
+ # @api public
90
+ def parent?
91
+ !parent.nil?
92
+ end
93
+
94
+ # Saves the EmbeddedResource by saving the parent
95
+ #
96
+ # @return [Boolean]
97
+ # Returns true if the resource was successfully saved, false
98
+ # otherwise
99
+ #
100
+ # @raise [MissingParentError]
101
+ # Raises a MissingParentError if a parent has not been set
102
+ #
103
+ # @api public
104
+ def save
105
+ if parent?
106
+ if parent.save
107
+ original_attributes.clear
108
+ true
109
+ end
110
+ else
111
+ raise MissingParentError, 'EmbeddedResource needs a parent to be ' \
112
+ 'set before it can be saved'
113
+ end
114
+ end
115
+
116
+ # Checks if the resource has unsaved changes
117
+ #
118
+ # @return [Boolean]
119
+ # True if resource may be persisted
120
+ #
121
+ # @api public
122
+ def dirty_self?
123
+ if original_attributes.any?
124
+ true
125
+ elsif new?
126
+ properties.any? { |property| property.default? }
127
+ else
128
+ false
129
+ end
130
+ end
131
+
132
+ end # EmbeddedResource
133
+ end # Mongo
134
+ end # DataMapper
@@ -0,0 +1,139 @@
1
+ module DataMapper
2
+ module Mongo
3
+ module Embedments
4
+ module OneToMany
5
+ class Relationship < Embedments::Relationship
6
+ # Loads and returns embedment target for given source
7
+ #
8
+ # @param [DataMapper::Mongo::Resource] source
9
+ # The resource whose relationship value is to be retrieved.
10
+ #
11
+ # @return [DataMapper::Collection]
12
+ #
13
+ # @api semipublic
14
+ def get(source, other_query = nil)
15
+ assert_kind_of 'source', source, source_model
16
+
17
+ unless loaded?(source)
18
+ set!(source, collection_for(source, other_query))
19
+ end
20
+
21
+ get!(source)
22
+ end
23
+
24
+ # Sets and returns association target for given source
25
+ #
26
+ # @param [DataMapper::Mongo::Resource] source
27
+ # The parent resource whose target is to be set.
28
+ # @param [DataMapper::Mongo::EmbeddedResource] targets
29
+ # The embedded resources to be set to the relationship
30
+ # @param [Boolean] loading
31
+ # Do the attributes have to be loaded before being set? Setting
32
+ # this to true will typecase the attributes, and set the
33
+ # original_values on the resource.
34
+ #
35
+ # @api semipublic
36
+ def set(source, targets, loading=false)
37
+ assert_kind_of 'source', source, source_model
38
+ assert_kind_of 'targets', targets, Array
39
+
40
+ targets = targets.map do |target|
41
+ case target
42
+ when Hash
43
+ load_target(source, target)
44
+ when DataMapper::Mongo::EmbeddedResource
45
+ target.parent = source
46
+ target
47
+ else
48
+ raise ArgumentError, 'one-to-many embedment requires an ' \
49
+ 'EmbeddedResource or a hash'
50
+ end
51
+ end
52
+
53
+ set_original_attributes(source, targets) unless loading
54
+
55
+ unless loaded?(source)
56
+ set!(source, collection_for(source))
57
+ end
58
+
59
+ get!(source).replace(targets)
60
+ end
61
+
62
+ private
63
+
64
+ # Creates a new collection instance for the source resources.
65
+ #
66
+ # @param [DataMapper::Mongo::Resource] source
67
+ # The resources to be wrapped in a Collection.
68
+ #
69
+ # @return [DataMapper::Collection]
70
+ #
71
+ # @api private
72
+ def collection_for(source, other_query=nil)
73
+ Collection.new(source, target_model)
74
+ end
75
+ end
76
+
77
+ # Extends Array to ensure that each EmbeddedResource has it's +parent+
78
+ # attribute set.
79
+ class Collection < Array
80
+ # Returns the resource to which this collection belongs
81
+ #
82
+ # @return [DataMapper::Mongo::Resource]
83
+ # The resource to which the contained EmbeddedResource instances
84
+ # belong.
85
+ #
86
+ # @api semipublic
87
+ attr_reader :source
88
+
89
+ # @api semipublic
90
+ attr_reader :target_model
91
+
92
+ # Creates a new Collection instance
93
+ #
94
+ # @param [DataMapper::Mongo::Resource] source
95
+ # The resource to which the contained EmbeddedResource instances
96
+ # belong.
97
+ #
98
+ # @api semipublic
99
+ def initialize(source, target_model)
100
+ @source = source
101
+ @target_model = target_model
102
+ end
103
+
104
+ # Adds a new embedded resource to the collection
105
+ #
106
+ # @param [DataMapper::Mongo::EmbeddedResource] resource
107
+ # The embedded resource to be added.
108
+ #
109
+ # @api public
110
+ def <<(resource)
111
+ resource.parent = source
112
+ super(resource)
113
+ end
114
+
115
+ # TODO: document
116
+ # @api public
117
+ def dirty?
118
+ any? { |resource| resource.dirty? }
119
+ end
120
+
121
+ # TODO: document
122
+ # @api public
123
+ def save
124
+ source.save
125
+ end
126
+
127
+ # TODO: document
128
+ # @api public
129
+ def new(attributes={})
130
+ resource = target_model.new(attributes)
131
+ self. << resource
132
+ resource
133
+ end
134
+ end
135
+
136
+ end # OneToMany
137
+ end # Embedments
138
+ end # Mongo
139
+ end # DataMapper
@@ -0,0 +1,53 @@
1
+ module DataMapper
2
+ module Mongo
3
+ module Embedments
4
+ module OneToOne
5
+ class Relationship < Embedments::Relationship
6
+ # Loads and returns embedment target for given source
7
+ #
8
+ # Returns a new instance of the target model if there isn't one set.
9
+ #
10
+ # @param [DataMapper::Mongo::Resource] source
11
+ # The resource whose relationship value is to be retrieved.
12
+ #
13
+ # @return [DataMapper::Mongo::Resource]
14
+ #
15
+ # @api semipublic
16
+ def get(source, other_query = nil)
17
+ get!(source)
18
+ end
19
+
20
+ # Sets and returns association target for given source
21
+ #
22
+ # @param [DataMapper::Mongo::Resource] source
23
+ # The resource whose target is to be set.
24
+ # @param [DataMapper::Mongo::EmbeddedResource] target
25
+ # The value to be set to the target
26
+ # @param [Boolean] loading
27
+ # Do the attributes have to be loaded before being set? Setting
28
+ # this to true will typecase the attributes, and set the
29
+ # original_values on the resource.
30
+ #
31
+ # @api semipublic
32
+ def set(source, target, loading=false)
33
+ assert_kind_of 'source', source, source_model
34
+ assert_kind_of 'target', target, target_model, Hash, NilClass
35
+
36
+ unless target.nil?
37
+ if target.kind_of?(Hash)
38
+ target = load_target(source, target, loading)
39
+ else
40
+ target.parent = source
41
+ end
42
+
43
+ set_original_attributes(source, target) unless loading
44
+ end
45
+
46
+ set!(source, target)
47
+ end
48
+
49
+ end # Relationship
50
+ end # OneToOne
51
+ end # Embedments
52
+ end # Mongo
53
+ end # DataMapper
@@ -0,0 +1,258 @@
1
+ module DataMapper
2
+ module Mongo
3
+ module Embedments
4
+ # Base class for embedment relationships. Each type of relationship
5
+ # (1 to 1, 1 to n) implements a subclass of this class with methods like
6
+ # get and set overridden.
7
+ class Relationship
8
+ include Extlib::Assertions
9
+
10
+ # Relationship name
11
+ #
12
+ # @example for :parent relationship in
13
+ #
14
+ # class VersionControl::Commit
15
+ # # ...
16
+ # belongs_to :parent
17
+ # end
18
+ #
19
+ # name is :parent
20
+ #
21
+ # @api semipublic
22
+ attr_reader :name
23
+
24
+ # Returns the model class used by the parent side of the relationship
25
+ #
26
+ # @return [Resource]
27
+ # Model for relationship parent
28
+ #
29
+ # @api semipublic
30
+ attr_reader :source_model
31
+
32
+ # Options used to set up this relationship
33
+ #
34
+ # @example for :author relationship in
35
+ #
36
+ # class VersionControl::Commit
37
+ # # ...
38
+ #
39
+ # belongs_to :author, :model => 'Person'
40
+ # end
41
+ #
42
+ # options is a hash with a single key, :model
43
+ #
44
+ # @api semipublic
45
+ attr_reader :options
46
+
47
+ # The name of the variable used to store the relationship
48
+ #
49
+ # @example for :commits relationship in
50
+ #
51
+ # class VersionControl::Branch
52
+ # # ...
53
+ #
54
+ # has n, :commits
55
+ # end
56
+ #
57
+ # instance variable name for source will be @commits
58
+ #
59
+ # @api semipublic
60
+ attr_reader :instance_variable_name
61
+
62
+ # Returns query options for relationship.
63
+ #
64
+ # For this base class, always returns query options has been
65
+ # initialized with. Overridden in subclasses.
66
+ #
67
+ # @api private
68
+ attr_reader :query
69
+
70
+ # Returns the visibility for the source accessor
71
+ #
72
+ # @return [Symbol]
73
+ # the visibility for the accessor added to the source
74
+ #
75
+ # @api semipublic
76
+ attr_reader :reader_visibility
77
+
78
+ # Returns the visibility for the source accessor
79
+ #
80
+ # @return [Symbol]
81
+ # the visibility for the accessor added to the source
82
+ #
83
+ # @api semipublic
84
+ attr_reader :writer_visibility
85
+
86
+ # Loads and returns "other end" of the embedment
87
+ #
88
+ # Must be implemented in subclasses.
89
+ #
90
+ # @api semipublic
91
+ def get(resource, other_query = nil)
92
+ raise NotImplementedError, "#{self.class}#get not implemented"
93
+ end
94
+
95
+ # Gets "other end" of the embedment directly
96
+ #
97
+ # @api semipublic
98
+ def get!(resource)
99
+ resource.instance_variable_get(instance_variable_name)
100
+ end
101
+
102
+ # Sets value of the "other end" of the embedment on given resource
103
+ #
104
+ # Must be implemented in subclasses.
105
+ #
106
+ # @api semipublic
107
+ def set(resource, association)
108
+ raise NotImplementedError, "#{self.class}#set not implemented"
109
+ end
110
+
111
+ # Sets "other end" of the embedment directly.
112
+ #
113
+ # @api semipublic
114
+ def set!(resource, association)
115
+ resource.instance_variable_set(instance_variable_name, association)
116
+ end
117
+
118
+ # Returns the model class used by the child side of the relationship
119
+ #
120
+ # @return [Resource]
121
+ # Model for relationship child
122
+ #
123
+ # @api semipublic
124
+ def target_model
125
+ if defined?(@target_model)
126
+ @target_model
127
+ elsif @target_model_name
128
+ @target_model = (@source_model || Object).find_const(@target_model_name)
129
+ else
130
+ raise "No model type defined for relationship #{@name}"
131
+ end
132
+ end
133
+
134
+ # @api semipublic
135
+ def set_original_attributes(resource, association)
136
+ Array(association).each do |association|
137
+ resource.original_attributes[self] = association.original_attributes if association.dirty?
138
+ end
139
+ end
140
+
141
+ # Checks if "other end" of the embedment is loaded on given resource
142
+ #
143
+ # @api semipublic
144
+ def loaded?(resource)
145
+ assert_kind_of 'resource', resource, source_model
146
+
147
+ resource.instance_variable_defined?(instance_variable_name)
148
+ end
149
+
150
+ # Creates an instance of the target model with its attributes
151
+ #
152
+ # @param [DataMapper::Mongo::Resource] source
153
+ # The source model to which the target belongs.
154
+ # @param [Hash, #to_mash] attributes
155
+ # The attributes to be set on the embedded resource.
156
+ # @param [Boolean] loading
157
+ # Do the attributes have to be loaded before being set? Setting
158
+ # this to true will typecase the attributes, and set the
159
+ # original_values on the resource.
160
+ #
161
+ # @return [DataMapper::Mongo::EmbeddedResource]
162
+ # The initialized embedded resource instance.
163
+ #
164
+ # @api semipublic
165
+ def load_target(source, attributes, loading=false)
166
+ target = target_model.allocate
167
+ target.parent = source
168
+
169
+ attributes = attributes.to_mash
170
+
171
+ target_model.properties.each do |property|
172
+ property.send(loading ? :set! : :set, target, attributes[property.field])
173
+ end
174
+
175
+ target
176
+ end
177
+
178
+ # Creates and returns Query instance that represents the embedment
179
+ #
180
+ # The returned query can be used to fetch target resource(s)
181
+ # (ex.: articles) for given target resource (ex.: author).
182
+ #
183
+ # @return [DataMapper::Mongo::Query]
184
+ #
185
+ # @api semipublic
186
+ def query_for(source, other_query = nil)
187
+ Query.new
188
+ end
189
+
190
+ # Creates a hash of attributes for the relationship.
191
+ #
192
+ # @api semipublic
193
+ def value(relationship)
194
+ relationship.model.properties.map { |property| [property, property.get(relationship)] }.to_hash
195
+ end
196
+
197
+ # Test the resource to see if it is a valid target
198
+ #
199
+ # @param [Object] relationship
200
+ # The resource or collection to be tested
201
+ #
202
+ # @return [Boolean]
203
+ # True if the resource is valid
204
+ #
205
+ # @api semipublic
206
+ def valid?(relationship)
207
+ relationship.kind_of?(target_model)
208
+ end
209
+
210
+ # @api public
211
+ def method_missing(method, *args, &block)
212
+ target_model.send(method, args, &block)
213
+ end
214
+
215
+ private
216
+
217
+ # Creates a new Relationship instance
218
+ #
219
+ # @param [DataMapper::Mongo::EmbeddedModel, String] target_model
220
+ # The child side of the relationship.
221
+ # @param [DataMapper::Mongo::Model] source_model
222
+ # The parent side of the relationship.
223
+ # @param [Hash] options
224
+ # Options for customising the relationship.
225
+ #
226
+ # @option options [Symbol] :reader_visibility
227
+ # The visibility of the reader method created on the source model;
228
+ # one of public, protected or private.
229
+ # @option options [Symbol] :writer_visibility
230
+ # The visibility of the writer method created on the source model;
231
+ # one of public, protected or private.
232
+ #
233
+ def initialize(name, target_model, source_model, options={})
234
+ if target_model.kind_of?(EmbeddedModel)
235
+ @target_model = target_model
236
+ elsif target_model.nil?
237
+ # No model given, infer it from the name.
238
+ @target_model_name =
239
+ Extlib::Inflection.camelize(name.to_s.singular).freeze
240
+ else
241
+ # We were likely given a string as the target model -- perhaps
242
+ # because the user's app hasn't loaded yet; get the constant
243
+ # later in Relationship#target_model.
244
+ @target_model_name = target_model.to_str.dup.freeze
245
+ end
246
+
247
+ @name = name
248
+ @source_model = source_model
249
+ @instance_variable_name = "@#{@name}".freeze
250
+ @options = options.dup.freeze
251
+ @reader_visibility = @options.fetch(:reader_visibility, :public)
252
+ @writer_visibility = @options.fetch(:writer_visibility, :public)
253
+ end
254
+
255
+ end # Relationship
256
+ end # Embedments
257
+ end # Mongo
258
+ end # DataMapper