datamapper-dm-core 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (192) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -39
  5. data/Manifest.txt +67 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +16 -15
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/dm-core.gemspec +11 -15
  12. data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
  13. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  14. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  15. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  16. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  19. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  20. data/lib/dm-core/adapters.rb +135 -16
  21. data/lib/dm-core/associations/many_to_many.rb +372 -90
  22. data/lib/dm-core/associations/many_to_one.rb +220 -73
  23. data/lib/dm-core/associations/one_to_many.rb +319 -255
  24. data/lib/dm-core/associations/one_to_one.rb +66 -53
  25. data/lib/dm-core/associations/relationship.rb +560 -158
  26. data/lib/dm-core/collection.rb +1104 -381
  27. data/lib/dm-core/core_ext/kernel.rb +12 -0
  28. data/lib/dm-core/core_ext/symbol.rb +10 -0
  29. data/lib/dm-core/identity_map.rb +4 -34
  30. data/lib/dm-core/migrations.rb +1283 -0
  31. data/lib/dm-core/model/descendant_set.rb +81 -0
  32. data/lib/dm-core/model/hook.rb +45 -0
  33. data/lib/dm-core/model/is.rb +32 -0
  34. data/lib/dm-core/model/property.rb +248 -0
  35. data/lib/dm-core/model/relationship.rb +335 -0
  36. data/lib/dm-core/model/scope.rb +90 -0
  37. data/lib/dm-core/model.rb +570 -369
  38. data/lib/dm-core/property.rb +753 -280
  39. data/lib/dm-core/property_set.rb +141 -98
  40. data/lib/dm-core/query/conditions/comparison.rb +814 -0
  41. data/lib/dm-core/query/conditions/operation.rb +247 -0
  42. data/lib/dm-core/query/direction.rb +43 -0
  43. data/lib/dm-core/query/operator.rb +42 -0
  44. data/lib/dm-core/query/path.rb +102 -0
  45. data/lib/dm-core/query/sort.rb +45 -0
  46. data/lib/dm-core/query.rb +974 -492
  47. data/lib/dm-core/repository.rb +147 -107
  48. data/lib/dm-core/resource.rb +644 -429
  49. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  50. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  51. data/lib/dm-core/support/chainable.rb +20 -0
  52. data/lib/dm-core/support/deprecate.rb +12 -0
  53. data/lib/dm-core/support/equalizer.rb +23 -0
  54. data/lib/dm-core/support/logger.rb +13 -0
  55. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  56. data/lib/dm-core/transaction.rb +333 -92
  57. data/lib/dm-core/type.rb +98 -60
  58. data/lib/dm-core/types/boolean.rb +1 -1
  59. data/lib/dm-core/types/discriminator.rb +34 -20
  60. data/lib/dm-core/types/object.rb +7 -4
  61. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  62. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  63. data/lib/dm-core/types/serial.rb +3 -3
  64. data/lib/dm-core/types/text.rb +3 -4
  65. data/lib/dm-core/version.rb +1 -1
  66. data/lib/dm-core.rb +106 -110
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/model/relationship_spec.rb +924 -0
  80. data/spec/public/model_spec.rb +159 -0
  81. data/spec/public/property_spec.rb +829 -0
  82. data/spec/public/resource_spec.rb +71 -0
  83. data/spec/public/sel_spec.rb +44 -0
  84. data/spec/public/setup_spec.rb +145 -0
  85. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  86. data/spec/public/shared/collection_shared_spec.rb +1723 -0
  87. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  88. data/spec/public/shared/resource_shared_spec.rb +924 -0
  89. data/spec/public/shared/sel_shared_spec.rb +112 -0
  90. data/spec/public/transaction_spec.rb +129 -0
  91. data/spec/public/types/discriminator_spec.rb +130 -0
  92. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  93. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  94. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  95. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  96. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  97. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  99. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  100. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  101. data/spec/semipublic/associations_spec.rb +177 -0
  102. data/spec/semipublic/collection_spec.rb +142 -0
  103. data/spec/semipublic/property_spec.rb +61 -0
  104. data/spec/semipublic/query/conditions_spec.rb +528 -0
  105. data/spec/semipublic/query/path_spec.rb +443 -0
  106. data/spec/semipublic/query_spec.rb +2626 -0
  107. data/spec/semipublic/resource_spec.rb +47 -0
  108. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  109. data/spec/spec.opts +3 -1
  110. data/spec/spec_helper.rb +80 -57
  111. data/tasks/ci.rb +19 -31
  112. data/tasks/dm.rb +43 -48
  113. data/tasks/doc.rb +8 -11
  114. data/tasks/gemspec.rb +5 -5
  115. data/tasks/hoe.rb +15 -16
  116. data/tasks/install.rb +8 -10
  117. metadata +72 -93
  118. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  119. data/lib/dm-core/associations.rb +0 -207
  120. data/lib/dm-core/auto_migrations.rb +0 -105
  121. data/lib/dm-core/dependency_queue.rb +0 -32
  122. data/lib/dm-core/hook.rb +0 -11
  123. data/lib/dm-core/is.rb +0 -16
  124. data/lib/dm-core/logger.rb +0 -232
  125. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  126. data/lib/dm-core/migrator.rb +0 -29
  127. data/lib/dm-core/scope.rb +0 -58
  128. data/lib/dm-core/support/array.rb +0 -13
  129. data/lib/dm-core/support/assertions.rb +0 -8
  130. data/lib/dm-core/support/errors.rb +0 -23
  131. data/lib/dm-core/support/kernel.rb +0 -11
  132. data/lib/dm-core/support/symbol.rb +0 -41
  133. data/lib/dm-core/support.rb +0 -7
  134. data/lib/dm-core/type_map.rb +0 -80
  135. data/lib/dm-core/types.rb +0 -19
  136. data/script/all +0 -4
  137. data/spec/integration/association_spec.rb +0 -1382
  138. data/spec/integration/association_through_spec.rb +0 -203
  139. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  140. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  141. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  142. data/spec/integration/auto_migrations_spec.rb +0 -413
  143. data/spec/integration/collection_spec.rb +0 -1073
  144. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  145. data/spec/integration/dependency_queue_spec.rb +0 -46
  146. data/spec/integration/model_spec.rb +0 -197
  147. data/spec/integration/mysql_adapter_spec.rb +0 -85
  148. data/spec/integration/postgres_adapter_spec.rb +0 -731
  149. data/spec/integration/property_spec.rb +0 -253
  150. data/spec/integration/query_spec.rb +0 -514
  151. data/spec/integration/repository_spec.rb +0 -61
  152. data/spec/integration/resource_spec.rb +0 -513
  153. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  154. data/spec/integration/sti_spec.rb +0 -273
  155. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  156. data/spec/integration/transaction_spec.rb +0 -75
  157. data/spec/integration/type_spec.rb +0 -275
  158. data/spec/lib/logging_helper.rb +0 -18
  159. data/spec/lib/mock_adapter.rb +0 -27
  160. data/spec/lib/model_loader.rb +0 -100
  161. data/spec/lib/publicize_methods.rb +0 -28
  162. data/spec/models/content.rb +0 -16
  163. data/spec/models/vehicles.rb +0 -34
  164. data/spec/models/zoo.rb +0 -48
  165. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  166. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  167. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  168. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  169. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  170. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  171. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  172. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  173. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  174. data/spec/unit/associations/relationship_spec.rb +0 -71
  175. data/spec/unit/associations_spec.rb +0 -242
  176. data/spec/unit/auto_migrations_spec.rb +0 -111
  177. data/spec/unit/collection_spec.rb +0 -182
  178. data/spec/unit/data_mapper_spec.rb +0 -35
  179. data/spec/unit/identity_map_spec.rb +0 -126
  180. data/spec/unit/is_spec.rb +0 -80
  181. data/spec/unit/migrator_spec.rb +0 -33
  182. data/spec/unit/model_spec.rb +0 -321
  183. data/spec/unit/naming_conventions_spec.rb +0 -36
  184. data/spec/unit/property_set_spec.rb +0 -90
  185. data/spec/unit/property_spec.rb +0 -753
  186. data/spec/unit/query_spec.rb +0 -571
  187. data/spec/unit/repository_spec.rb +0 -93
  188. data/spec/unit/resource_spec.rb +0 -649
  189. data/spec/unit/scope_spec.rb +0 -142
  190. data/spec/unit/transaction_spec.rb +0 -493
  191. data/spec/unit/type_map_spec.rb +0 -114
  192. data/spec/unit/type_spec.rb +0 -119
@@ -1,107 +1,254 @@
1
1
  module DataMapper
2
2
  module Associations
3
- module ManyToOne
4
- extend Assertions
5
-
6
- # Setup many to one relationship between two models
7
- # -
8
- # @api private
9
- def self.setup(name, model, options = {})
10
- assert_kind_of 'name', name, Symbol
11
- assert_kind_of 'model', model, Model
12
- assert_kind_of 'options', options, Hash
13
-
14
- repository_name = model.repository.name
15
-
16
- model.class_eval <<-EOS, __FILE__, __LINE__
17
- def #{name}
18
- #{name}_association.nil? ? nil : #{name}_association
19
- end
3
+ module ManyToOne #:nodoc:
4
+ # Relationship class with implementation specific
5
+ # to n side of 1 to n association
6
+ class Relationship < Associations::Relationship
7
+ OPTIONS = superclass::OPTIONS.dup << :nullable
8
+
9
+ # TODO: document
10
+ # @api semipublic
11
+ alias source_repository_name child_repository_name
12
+
13
+ # TODO: document
14
+ # @api semipublic
15
+ alias source_model child_model
16
+
17
+ # TODO: document
18
+ # @api semipublic
19
+ alias target_repository_name parent_repository_name
20
+
21
+ # TODO: document
22
+ # @api semipublic
23
+ alias target_model parent_model
24
+
25
+ # TODO: document
26
+ # @api semipublic
27
+ alias target_key parent_key
28
+
29
+ # TODO: document
30
+ # @api semipublic
31
+ def nullable?
32
+ @nullable
33
+ end
20
34
 
21
- def #{name}=(parent)
22
- #{name}_association.replace(parent)
23
- end
35
+ # Returns a set of keys that identify child model
36
+ #
37
+ # @return [DataMapper::PropertySet] a set of properties that identify child model
38
+ # @api private
39
+ def child_key
40
+ return @child_key if defined?(@child_key)
41
+
42
+ repository_name = child_repository_name || parent_repository_name
43
+ properties = child_model.properties(repository_name)
24
44
 
25
- private
45
+ child_key = parent_key.zip(@child_properties || []).map do |parent_property, property_name|
46
+ property_name ||= "#{name}_#{parent_property.name}".to_sym
26
47
 
27
- def #{name}_association
28
- @#{name}_association ||= begin
29
- unless relationship = model.relationships(#{repository_name.inspect})[:#{name}]
30
- raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
48
+ properties[property_name] || begin
49
+ # create the property within the correct repository
50
+ DataMapper.repository(repository_name) do
51
+ type = parent_property.send(parent_property.type == DataMapper::Types::Boolean ? :type : :primitive)
52
+ child_model.property(property_name, type, child_key_options(parent_property))
31
53
  end
32
- association = Proxy.new(relationship, self)
33
- child_associations << association
34
- association
35
54
  end
36
55
  end
37
- EOS
38
-
39
- model.relationships(repository_name)[name] = Relationship.new(
40
- name,
41
- repository_name,
42
- model,
43
- options.fetch(:class_name, Extlib::Inflection.classify(name)),
44
- options
45
- )
46
- end
47
-
48
- class Proxy
49
- include Assertions
50
56
 
51
- instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ object_id kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m.to_s) }
57
+ @child_key = properties.class.new(child_key).freeze
58
+ end
52
59
 
53
- def replace(parent)
54
- @parent = parent
55
- @relationship.attach_parent(@child, @parent)
56
- self
60
+ # TODO: document
61
+ # @api semipublic
62
+ alias source_key child_key
63
+
64
+ # Returns a Resoruce for this relationship with a given source
65
+ #
66
+ # @param [Resource] source
67
+ # A Resource to scope the collection with
68
+ # @param [Query] other_query (optional)
69
+ # A Query to further scope the collection with
70
+ #
71
+ # @return [Resource]
72
+ # The resource scoped to the relationship, source and query
73
+ #
74
+ # @api private
75
+ def resource_for(source, other_query = nil)
76
+ query = query_for(source, other_query)
77
+
78
+ # TODO: lookup the resource in the Identity Map, and make sure
79
+ # it matches the query criteria, otherwise perform the query
80
+
81
+ target_model.first(query)
57
82
  end
58
83
 
59
- def save
60
- return false if @parent.nil?
61
- return true unless parent.new_record?
84
+ # Loads and returns association target (ex.: author) for given source resource
85
+ # (ex.: article)
86
+ #
87
+ # @param source [DataMapper::Resource]
88
+ # Child object (ex.: instance of article)
89
+ # @param other_query [DataMapper::Query]
90
+ # Query options
91
+ #
92
+ # @api semipublic
93
+ def get(source, other_query = nil)
94
+ assert_kind_of 'source', source, source_model
95
+
96
+ lazy_load(source) unless loaded?(source)
97
+
98
+ resource = get!(source)
99
+ if other_query.nil? || query_for(source, other_query).conditions.matches?(resource)
100
+ resource
101
+ end
102
+ end
62
103
 
63
- @relationship.with_repository(parent) do
64
- result = parent.save
65
- @relationship.child_key.set(@child, @relationship.parent_key.get(parent)) if result
66
- result
104
+ # Sets value of association target (ex.: author) for given source resource
105
+ # (ex.: article)
106
+ #
107
+ # @param source [DataMapper::Resource]
108
+ # Child object (ex.: instance of article)
109
+ #
110
+ # @param source [DataMapper::Resource]
111
+ # Parent object (ex.: instance of author)
112
+ #
113
+ # @api semipublic
114
+ def set(source, target)
115
+ assert_kind_of 'source', source, source_model
116
+ assert_kind_of 'target', target, target_model, Hash, NilClass
117
+
118
+ if target.kind_of?(Hash)
119
+ target = target_model.new(target)
67
120
  end
121
+
122
+ source_key.set(source, target.nil? ? [] : target_key.get(target))
123
+ set!(source, target)
68
124
  end
69
125
 
70
- def reload
71
- @parent = nil
72
- self
126
+ # TODO: document
127
+ # @api private
128
+ def inherited_by(model)
129
+ model.relationships(source_repository_name)[name] ||
130
+ self.class.new(name, model, parent_model_name, options_with_inverse)
131
+ end
132
+
133
+ private
134
+
135
+ # Initializes the relationship, always using max cardinality of 1.
136
+ #
137
+ # @api semipublic
138
+ def initialize(name, source_model, target_model, options = {})
139
+ @nullable = options.fetch(:nullable, false)
140
+ target_model ||= Extlib::Inflection.camelize(name)
141
+ options = { :min => @nullable ? 0 : 1, :max => 1 }.update(options)
142
+ super
73
143
  end
74
144
 
75
- def kind_of?(klass)
76
- super || parent.kind_of?(klass)
145
+ # Dynamically defines reader method for source side of association
146
+ # (for instance, method article for model Paragraph)
147
+ #
148
+ # @api semipublic
149
+ def create_reader
150
+ return if source_model.resource_method_defined?(name.to_s)
151
+
152
+ source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
153
+ public # TODO: make this configurable
154
+
155
+ # FIXME: if the writer is used, caching nil in the ivar
156
+ # and then the FK(s) are set, the cache in the writer should
157
+ # be cleared.
158
+
159
+ def #{name}(query = nil) # def article(query = nil)
160
+ relationships[#{name.inspect}].get(self, query) # relationships["article"].get(self, query)
161
+ end # end
162
+ RUBY
77
163
  end
78
164
 
79
- def respond_to?(method, include_private = false)
80
- super || parent.respond_to?(method, include_private)
165
+ # Dynamically defines writer method for source side of association
166
+ # (for instance, method article= for model Paragraph)
167
+ #
168
+ # @api semipublic
169
+ def create_writer
170
+ writer_name = "#{name}="
171
+
172
+ return if source_model.resource_method_defined?(writer_name)
173
+
174
+ source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
175
+ public # TODO: make this configurable
176
+ def #{writer_name}(target) # def article=(target)
177
+ relationships[#{name.inspect}].set(self, target) # relationships["article"].set(self, target)
178
+ end # end
179
+ RUBY
81
180
  end
82
181
 
83
- def instance_variable_get(variable)
84
- super || parent.instance_variable_get(variable)
182
+ # Loads association target and sets resulting value on
183
+ # given source resource
184
+ #
185
+ # @param [Resource] source
186
+ # the source resource for the association
187
+ #
188
+ # @return [undefined]
189
+ #
190
+ # @api private
191
+ def lazy_load(source)
192
+ return unless source_key.get(source).all?
193
+
194
+ # SEL: load all related resources in the source collection
195
+ if source.saved? && source.collection.size > 1
196
+ eager_load(source.collection)
197
+ end
198
+
199
+ unless loaded?(source)
200
+ set!(source, resource_for(source))
201
+ end
85
202
  end
86
203
 
87
- private
204
+ # Sets the association targets in the resource
205
+ #
206
+ # @param [Resource] source
207
+ # the source to set
208
+ # @param [Array(Resource)] targets
209
+ # the target resource for the association
210
+ # @param [Query, Hash] query
211
+ # not used
212
+ #
213
+ # @return [undefined]
214
+ #
215
+ # @api private
216
+ def eager_load_targets(source, targets, query)
217
+ set(source, targets.first)
218
+ end
88
219
 
89
- def initialize(relationship, child)
90
- assert_kind_of 'relationship', relationship, Relationship
91
- assert_kind_of 'child', child, Resource
220
+ # Returns the inverse relationship class
221
+ #
222
+ # @api private
223
+ def inverse_class
224
+ OneToMany::Relationship
225
+ end
92
226
 
93
- @relationship = relationship
94
- @child = child
227
+ # Returns the inverse relationship name
228
+ #
229
+ # @api private
230
+ def inverse_name
231
+ super || Extlib::Inflection.underscore(Extlib::Inflection.demodulize(source_model.name)).pluralize.to_sym
95
232
  end
96
233
 
97
- def parent
98
- @parent ||= @relationship.get_parent(@child)
234
+ # TODO: document
235
+ # @api private
236
+ def child_key_options(parent_property)
237
+ options = parent_property.options.only(:length, :precision, :scale).update(:index => name, :nullable => nullable?)
238
+
239
+ if parent_property.primitive == Integer && parent_property.min && parent_property.max
240
+ options.update(:min => parent_property.min, :max => parent_property.max)
241
+ end
242
+
243
+ options
99
244
  end
100
245
 
101
- def method_missing(method, *args, &block)
102
- parent.__send__(method, *args, &block)
246
+ # TODO: document
247
+ # @api private
248
+ def child_properties
249
+ child_key.map { |property| property.name }
103
250
  end
104
- end # class Proxy
251
+ end # class Relationship
105
252
  end # module ManyToOne
106
253
  end # module Associations
107
254
  end # module DataMapper