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,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