datamapper 0.3.2 → 0.9.3

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 (180) hide show
  1. data/History.txt +0 -0
  2. data/Manifest.txt +5 -0
  3. data/README.txt +11 -0
  4. data/Rakefile +70 -0
  5. data/lib/datamapper.rb +8 -0
  6. metadata +152 -319
  7. data/CHANGELOG +0 -145
  8. data/FAQ +0 -96
  9. data/MIT-LICENSE +0 -22
  10. data/QUICKLINKS +0 -12
  11. data/README +0 -105
  12. data/environment.rb +0 -62
  13. data/example.rb +0 -156
  14. data/lib/data_mapper.rb +0 -88
  15. data/lib/data_mapper/adapters/abstract_adapter.rb +0 -43
  16. data/lib/data_mapper/adapters/data_object_adapter.rb +0 -480
  17. data/lib/data_mapper/adapters/mysql_adapter.rb +0 -72
  18. data/lib/data_mapper/adapters/postgresql_adapter.rb +0 -258
  19. data/lib/data_mapper/adapters/sql/coersion.rb +0 -134
  20. data/lib/data_mapper/adapters/sql/commands/load_command.rb +0 -545
  21. data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +0 -34
  22. data/lib/data_mapper/adapters/sql/mappings/column.rb +0 -279
  23. data/lib/data_mapper/adapters/sql/mappings/conditions.rb +0 -172
  24. data/lib/data_mapper/adapters/sql/mappings/schema.rb +0 -60
  25. data/lib/data_mapper/adapters/sql/mappings/table.rb +0 -459
  26. data/lib/data_mapper/adapters/sql/quoting.rb +0 -24
  27. data/lib/data_mapper/adapters/sqlite3_adapter.rb +0 -159
  28. data/lib/data_mapper/associations.rb +0 -106
  29. data/lib/data_mapper/associations/belongs_to_association.rb +0 -160
  30. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +0 -437
  31. data/lib/data_mapper/associations/has_many_association.rb +0 -283
  32. data/lib/data_mapper/associations/has_n_association.rb +0 -143
  33. data/lib/data_mapper/associations/reference.rb +0 -47
  34. data/lib/data_mapper/attributes.rb +0 -73
  35. data/lib/data_mapper/auto_migrations.rb +0 -36
  36. data/lib/data_mapper/base.rb +0 -17
  37. data/lib/data_mapper/callbacks.rb +0 -107
  38. data/lib/data_mapper/context.rb +0 -112
  39. data/lib/data_mapper/database.rb +0 -234
  40. data/lib/data_mapper/dependency_queue.rb +0 -28
  41. data/lib/data_mapper/embedded_value.rb +0 -145
  42. data/lib/data_mapper/identity_map.rb +0 -47
  43. data/lib/data_mapper/is/tree.rb +0 -121
  44. data/lib/data_mapper/migration.rb +0 -155
  45. data/lib/data_mapper/persistence.rb +0 -852
  46. data/lib/data_mapper/property.rb +0 -310
  47. data/lib/data_mapper/query.rb +0 -164
  48. data/lib/data_mapper/support/blank.rb +0 -35
  49. data/lib/data_mapper/support/connection_pool.rb +0 -117
  50. data/lib/data_mapper/support/enumerable.rb +0 -35
  51. data/lib/data_mapper/support/errors.rb +0 -16
  52. data/lib/data_mapper/support/inflector.rb +0 -265
  53. data/lib/data_mapper/support/object.rb +0 -54
  54. data/lib/data_mapper/support/serialization.rb +0 -96
  55. data/lib/data_mapper/support/silence.rb +0 -10
  56. data/lib/data_mapper/support/string.rb +0 -72
  57. data/lib/data_mapper/support/struct.rb +0 -7
  58. data/lib/data_mapper/support/symbol.rb +0 -82
  59. data/lib/data_mapper/support/typed_set.rb +0 -65
  60. data/lib/data_mapper/types/base.rb +0 -44
  61. data/lib/data_mapper/types/string.rb +0 -34
  62. data/lib/data_mapper/validatable_extensions/errors.rb +0 -12
  63. data/lib/data_mapper/validatable_extensions/macros.rb +0 -7
  64. data/lib/data_mapper/validatable_extensions/validatable_instance_methods.rb +0 -62
  65. data/lib/data_mapper/validatable_extensions/validation_base.rb +0 -18
  66. data/lib/data_mapper/validatable_extensions/validations/formats/email.rb +0 -43
  67. data/lib/data_mapper/validatable_extensions/validations/validates_acceptance_of.rb +0 -7
  68. data/lib/data_mapper/validatable_extensions/validations/validates_confirmation_of.rb +0 -7
  69. data/lib/data_mapper/validatable_extensions/validations/validates_each.rb +0 -7
  70. data/lib/data_mapper/validatable_extensions/validations/validates_format_of.rb +0 -28
  71. data/lib/data_mapper/validatable_extensions/validations/validates_length_of.rb +0 -15
  72. data/lib/data_mapper/validatable_extensions/validations/validates_numericality_of.rb +0 -7
  73. data/lib/data_mapper/validatable_extensions/validations/validates_presence_of.rb +0 -7
  74. data/lib/data_mapper/validatable_extensions/validations/validates_true_for.rb +0 -7
  75. data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +0 -40
  76. data/lib/data_mapper/validations.rb +0 -20
  77. data/lib/data_mapper/validations/number_validator.rb +0 -40
  78. data/lib/data_mapper/validations/string_validator.rb +0 -20
  79. data/lib/data_mapper/validations/validator.rb +0 -13
  80. data/performance.rb +0 -307
  81. data/plugins/can_has_sphinx/LICENSE +0 -23
  82. data/plugins/can_has_sphinx/README +0 -4
  83. data/plugins/can_has_sphinx/REVISION +0 -1
  84. data/plugins/can_has_sphinx/Rakefile +0 -22
  85. data/plugins/can_has_sphinx/init.rb +0 -1
  86. data/plugins/can_has_sphinx/install.rb +0 -1
  87. data/plugins/can_has_sphinx/lib/acts_as_sphinx.rb +0 -123
  88. data/plugins/can_has_sphinx/lib/sphinx.rb +0 -460
  89. data/plugins/can_has_sphinx/scripts/sphinx.sh +0 -47
  90. data/plugins/can_has_sphinx/tasks/acts_as_sphinx_tasks.rake +0 -41
  91. data/profile_data_mapper.rb +0 -40
  92. data/rakefile.rb +0 -159
  93. data/spec/acts_as_tree_spec.rb +0 -67
  94. data/spec/adapters/data_object_adapter_spec.rb +0 -31
  95. data/spec/associations/belongs_to_association_spec.rb +0 -98
  96. data/spec/associations/has_and_belongs_to_many_association_spec.rb +0 -377
  97. data/spec/associations/has_many_association_spec.rb +0 -337
  98. data/spec/attributes_spec.rb +0 -52
  99. data/spec/auto_migrations_spec.rb +0 -101
  100. data/spec/callbacks_spec.rb +0 -186
  101. data/spec/can_has_sphinx.rb +0 -5
  102. data/spec/coersion_spec.rb +0 -41
  103. data/spec/column_spec.rb +0 -114
  104. data/spec/count_command_spec.rb +0 -45
  105. data/spec/database_spec.rb +0 -18
  106. data/spec/dataobjects_spec.rb +0 -27
  107. data/spec/delete_command_spec.rb +0 -11
  108. data/spec/dependency_spec.rb +0 -29
  109. data/spec/embedded_value_spec.rb +0 -161
  110. data/spec/fixtures/animals.yaml +0 -33
  111. data/spec/fixtures/animals_exhibits.yaml +0 -2
  112. data/spec/fixtures/careers.yaml +0 -5
  113. data/spec/fixtures/comments.yaml +0 -1
  114. data/spec/fixtures/exhibits.yaml +0 -90
  115. data/spec/fixtures/fruit.yaml +0 -6
  116. data/spec/fixtures/people.yaml +0 -37
  117. data/spec/fixtures/posts.yaml +0 -3
  118. data/spec/fixtures/projects.yaml +0 -13
  119. data/spec/fixtures/sections.yaml +0 -5
  120. data/spec/fixtures/serializers.yaml +0 -6
  121. data/spec/fixtures/tasks.yaml +0 -6
  122. data/spec/fixtures/tasks_tasks.yaml +0 -2
  123. data/spec/fixtures/tomatoes.yaml +0 -1
  124. data/spec/fixtures/users.yaml +0 -1
  125. data/spec/fixtures/zoos.yaml +0 -24
  126. data/spec/is_a_tree_spec.rb +0 -149
  127. data/spec/legacy_spec.rb +0 -16
  128. data/spec/load_command_spec.rb +0 -322
  129. data/spec/magic_columns_spec.rb +0 -26
  130. data/spec/migration_spec.rb +0 -267
  131. data/spec/mock_adapter.rb +0 -20
  132. data/spec/models/animal.rb +0 -12
  133. data/spec/models/candidate.rb +0 -8
  134. data/spec/models/career.rb +0 -7
  135. data/spec/models/chain.rb +0 -8
  136. data/spec/models/comment.rb +0 -6
  137. data/spec/models/exhibit.rb +0 -14
  138. data/spec/models/fence.rb +0 -7
  139. data/spec/models/fruit.rb +0 -8
  140. data/spec/models/job.rb +0 -8
  141. data/spec/models/person.rb +0 -30
  142. data/spec/models/post.rb +0 -14
  143. data/spec/models/project.rb +0 -41
  144. data/spec/models/sales_person.rb +0 -5
  145. data/spec/models/section.rb +0 -8
  146. data/spec/models/serializer.rb +0 -5
  147. data/spec/models/task.rb +0 -9
  148. data/spec/models/tomato.rb +0 -27
  149. data/spec/models/user.rb +0 -12
  150. data/spec/models/zoo.rb +0 -13
  151. data/spec/natural_key_spec.rb +0 -36
  152. data/spec/paranoia_spec.rb +0 -38
  153. data/spec/persistence_spec.rb +0 -479
  154. data/spec/postgres_spec.rb +0 -96
  155. data/spec/property_spec.rb +0 -151
  156. data/spec/query_spec.rb +0 -77
  157. data/spec/save_command_spec.rb +0 -94
  158. data/spec/schema_spec.rb +0 -8
  159. data/spec/serialize_spec.rb +0 -19
  160. data/spec/single_table_inheritance_spec.rb +0 -43
  161. data/spec/spec_helper.rb +0 -45
  162. data/spec/support/blank_spec.rb +0 -8
  163. data/spec/support/inflector_spec.rb +0 -41
  164. data/spec/support/object_spec.rb +0 -9
  165. data/spec/support/serialization_spec.rb +0 -61
  166. data/spec/support/silence_spec.rb +0 -15
  167. data/spec/support/string_spec.rb +0 -7
  168. data/spec/support/struct_spec.rb +0 -12
  169. data/spec/support/typed_set_spec.rb +0 -66
  170. data/spec/symbolic_operators_spec.rb +0 -27
  171. data/spec/table_spec.rb +0 -79
  172. data/spec/types/string.rb +0 -81
  173. data/spec/validates_confirmation_of_spec.rb +0 -55
  174. data/spec/validates_format_of_spec.rb +0 -78
  175. data/spec/validates_length_of_spec.rb +0 -117
  176. data/spec/validates_uniqueness_of_spec.rb +0 -92
  177. data/spec/validations/number_validator.rb +0 -59
  178. data/spec/validations/string_validator.rb +0 -14
  179. data/spec/validations_spec.rb +0 -141
  180. data/tasks/fixtures.rb +0 -53
@@ -1,437 +0,0 @@
1
- module DataMapper
2
- module Associations
3
-
4
- class HasAndBelongsToManyAssociation
5
-
6
- attr_reader :adapter
7
-
8
- def initialize(klass, association_name, options)
9
- @adapter = database.adapter
10
- @key_table = adapter.table(klass)
11
- @self_referential = (association_name.to_s == @key_table.name)
12
- @association_name = association_name.to_sym
13
- @options = options
14
-
15
- define_accessor(klass)
16
- end
17
-
18
- # def key_table
19
- # @key_table
20
- # end
21
-
22
- def name
23
- @association_name
24
- end
25
-
26
- def dependency
27
- @options[:dependent]
28
- end
29
-
30
- def foreign_name
31
- @foreign_name || (@foreign_name = (@options[:foreign_name] || @key_table.name).to_sym)
32
- end
33
-
34
- def self_referential?
35
- @self_referential
36
- end
37
-
38
- def constant
39
- @associated_class || @associated_class = begin
40
-
41
- if @options.has_key?(:class) || @options.has_key?(:class_name)
42
- associated_class_name = (@options[:class] || @options[:class_name])
43
- if associated_class_name.kind_of?(String)
44
- Kernel.const_get(Inflector.classify(associated_class_name))
45
- else
46
- associated_class_name
47
- end
48
- else
49
- Kernel.const_get(Inflector.classify(@association_name))
50
- end
51
-
52
- end
53
- end
54
-
55
- def activate!(force = false)
56
- join_columns.each {|column| column unless join_table.mapped_column_exists?(column.name)}
57
- join_table.create!(force)
58
- end
59
-
60
- def associated_columns
61
- associated_table.columns.reject { |column| column.lazy? } + join_columns
62
- end
63
-
64
- def join_columns
65
- [ left_foreign_key, right_foreign_key ]
66
- end
67
-
68
- def associated_table
69
- @associated_table || (@associated_table = adapter.table(constant))
70
- end
71
-
72
- def join_table
73
- @join_table || @join_table = begin
74
- join_table_name = @options[:join_table] ||
75
- [ @key_table.name.to_s, database.schema[constant].name.to_s ].sort.join('_')
76
-
77
- adapter.table(join_table_name)
78
- end
79
- end
80
-
81
- def left_foreign_key
82
- @left_foreign_key || @left_foreign_key = begin
83
- join_table.add_column(
84
- (@options[:left_foreign_key] || @key_table.default_foreign_key),
85
- :integer, :nullable => true, :key => true)
86
- end
87
- end
88
-
89
- def right_foreign_key
90
- if self_referential?
91
- @options[:right_foreign_key] ||= ["related_", associated_table.default_foreign_key].to_s
92
- end
93
-
94
- @right_foreign_key || @right_foreign_key = begin
95
- join_table.add_column(
96
- (@options[:right_foreign_key] || associated_table.default_foreign_key),
97
- :integer, :nullable => true, :key => true)
98
- end
99
- end
100
-
101
- def to_sql
102
- <<-EOS.compress_lines
103
- JOIN #{join_table.to_sql} ON
104
- #{left_foreign_key.to_sql(true)} = #{@key_table.key.to_sql(true)}
105
- JOIN #{associated_table.to_sql} ON
106
- #{associated_table.key.to_sql(true)} = #{right_foreign_key.to_sql(true)}
107
- EOS
108
- end
109
-
110
- def to_shallow_sql
111
- if self_referential?
112
- <<-EOS.compress_lines
113
- JOIN #{join_table.to_sql} ON
114
- #{right_foreign_key.to_sql(true)} = #{@key_table.key.to_sql(true)}
115
- EOS
116
- else
117
- <<-EOS.compress_lines
118
- JOIN #{join_table.to_sql} ON
119
- #{left_foreign_key.to_sql(true)} = #{@key_table.key.to_sql(true)}
120
- EOS
121
- end
122
- end
123
-
124
- def to_insert_sql
125
- <<-EOS.compress_lines
126
- INSERT INTO #{join_table.to_sql}
127
- (#{left_foreign_key.to_sql}, #{right_foreign_key.to_sql})
128
- VALUES
129
- EOS
130
- end
131
-
132
- def to_delete_sql
133
- <<-EOS.compress_lines
134
- DELETE FROM #{join_table.to_sql}
135
- WHERE #{left_foreign_key.to_sql} = ?
136
- EOS
137
- end
138
-
139
- def to_delete_set_sql
140
- <<-EOS.compress_lines
141
- DELETE FROM #{join_table.to_sql}
142
- WHERE #{left_foreign_key.to_sql} IN ?
143
- OR #{right_foreign_key.to_sql} IN ?
144
- EOS
145
- end
146
-
147
- def to_delete_members_sql
148
- <<-EOS.compress_lines
149
- DELETE FROM #{associated_table.to_sql}
150
- WHERE #{associated_table.key.to_sql} IN ?
151
- EOS
152
- end
153
-
154
- def to_delete_member_sql
155
- <<-EOS
156
- DELETE FROM #{join_table.to_sql}
157
- WHERE #{left_foreign_key.to_sql} = ?
158
- AND #{right_foreign_key.to_sql} = ?
159
- EOS
160
- end
161
-
162
- def to_disassociate_sql
163
- <<-EOS
164
- UPDATE #{join_table.to_sql}
165
- SET #{left_foreign_key.to_sql} = NULL
166
- WHERE #{left_foreign_key.to_sql} = ?
167
- EOS
168
- end
169
-
170
- # Define the association instance method (i.e. Project#tasks)
171
- def define_accessor(klass)
172
- klass.class_eval <<-EOS
173
- def #{@association_name}
174
- @#{@association_name} || (@#{@association_name} = HasAndBelongsToManyAssociation::Set.new(self, #{@association_name.inspect}))
175
- end
176
-
177
- def #{@association_name}=(value)
178
- #{@association_name}.set(value)
179
- end
180
-
181
- private
182
- def #{@association_name}_keys=(value)
183
- #{@association_name}.clear
184
-
185
- associated_constant = #{@association_name}.association.constant
186
- associated_table = #{@association_name}.association.associated_table
187
- associated_constant.all(associated_table.key => [*value]).each do |entry|
188
- #{@association_name} << entry
189
- end
190
- end
191
- EOS
192
- end
193
-
194
- class Set < Associations::Reference
195
-
196
- include Enumerable
197
-
198
- def each
199
- entries.each { |item| yield item }
200
- end
201
-
202
- def size
203
- entries.size
204
- end
205
- alias length size
206
-
207
- def count
208
- entries.size
209
- end
210
-
211
- def [](key)
212
- entries[key]
213
- end
214
-
215
- def empty?
216
- entries.empty?
217
- end
218
-
219
- def dirty?(cleared = ::Set.new)
220
- return false unless @entries
221
- @entries.any? {|item| cleared.include?(item) || item.dirty?(cleared) } || @associated_keys != @entries.map { |entry| entry.keys }
222
- end
223
-
224
- def validate_recursively(event, cleared)
225
- @entries.blank? || @entries.all? { |item| cleared.include?(item) || item.validate_recursively(event, cleared) }
226
- end
227
-
228
- def save_without_validation(database_context, cleared)
229
- unless @entries.nil?
230
-
231
- if dirty?(cleared)
232
- adapter = @instance.database_context.adapter
233
-
234
- adapter.connection do |db|
235
- command = db.create_command(association.to_delete_sql)
236
- command.execute_non_query(@instance.key)
237
- end
238
-
239
- unless @entries.empty?
240
- if adapter.batch_insertable?
241
- sql = association.to_insert_sql
242
- values = []
243
- keys = []
244
-
245
- @entries.each do |member|
246
- adapter.save_without_validation(database_context, member, cleared)
247
- values << "(?, ?)"
248
- keys << @instance.key << member.key
249
- end
250
-
251
- adapter.connection do |db|
252
- command = db.create_command(sql << ' ' << values.join(', '))
253
- command.execute_non_query(*keys)
254
- end
255
-
256
- else # adapter doesn't support batch inserts...
257
- @entries.each do |member|
258
- adapter.save_without_validation(database_context, member, cleared)
259
- end
260
-
261
- # Just to keep the same flow as the batch-insert mode.
262
- @entries.each do |member|
263
- adapter.connection do |db|
264
- command = db.create_command("#{association.to_insert_sql} (?, ?)")
265
- command.execute_non_query(@instance.key, member.key)
266
- end
267
- end
268
- end # if adapter.batch_insertable?
269
- end # unless @entries.empty?
270
- end # if dirty?
271
- end
272
- end
273
-
274
- def <<(member)
275
- return nil unless member
276
-
277
- if member.is_a?(Enumerable)
278
- member.each { |entry| entries << entry }
279
- else
280
- entries << member
281
- end
282
- end
283
-
284
- def clear
285
- @entries = Support::TypedSet.new(association.constant)
286
- end
287
-
288
- def reload!
289
- @entries = nil
290
- end
291
-
292
- def delete(member)
293
- if found_member = entries.detect { |entry| entry == member }
294
- entries.delete?(found_member)
295
- @instance.database_context.adapter.connection do |db|
296
- command = db.create_command(association.to_delete_member_sql)
297
- command.execute_non_query(@instance.key, member.key)
298
- end
299
- member
300
- else
301
- nil
302
- end
303
- end
304
-
305
- def method_missing(symbol, *args, &block)
306
- if entries.respond_to?(symbol)
307
- entries.send(symbol, *args, &block)
308
- elsif association.associated_table.associations.any? { |assoc| assoc.name == symbol }
309
- results = []
310
- each do |item|
311
- unless (val = item.send(symbol)).blank?
312
- results << (val.is_a?(Enumerable) ? val.entries : val)
313
- end
314
- end
315
- results.flatten
316
- else
317
- super
318
- end
319
- end
320
-
321
- def entries
322
- @entries || @entries = begin
323
-
324
- if @instance.loaded_set.nil?
325
- Support::TypedSet.new(association.constant)
326
- else
327
-
328
- associated_items = Hash.new { |h,k| h[k] = [] }
329
- left_key_index = nil
330
- association_constant = association.constant
331
- left_foreign_key = association.left_foreign_key
332
-
333
- matcher = lambda do |instance,columns,row|
334
-
335
- # Locate the column for the left-key.
336
- unless left_key_index
337
- columns.each_with_index do |column, index|
338
- if column.name == association.left_foreign_key.name
339
- left_key_index = index
340
- break
341
- end
342
- end
343
- end
344
-
345
- if instance.kind_of?(association_constant)
346
- associated_items[left_foreign_key.type_cast_value(row[left_key_index])] << instance
347
- end
348
- end
349
-
350
- @instance.database_context.all(association.constant,
351
- left_foreign_key => @instance.loaded_set.map(&:key),
352
- :shallow_include => association.foreign_name,
353
- :intercept_load => matcher
354
- )
355
-
356
- # do stsuff with associated_items hash.
357
- setter_method = "#{@association_name}=".to_sym
358
-
359
- @instance.loaded_set.each do |entry|
360
- entry.send(setter_method, associated_items[entry.key]) if entry.respond_to?(setter_method)
361
- end # @instance.loaded_set.each
362
-
363
- @entries
364
- end
365
- end
366
- end
367
-
368
- def set(results)
369
- if results.is_a?(Support::TypedSet)
370
- @entries = results
371
- else
372
- @entries = Support::TypedSet.new(association.constant)
373
- [*results].each { |item| @entries << item }
374
- end
375
- @associated_keys = @entries.map { |entry| entry.key }
376
- return @entries
377
- end
378
-
379
- def inspect
380
- entries.inspect
381
- end
382
-
383
- def first
384
- entries.entries.first
385
- end
386
-
387
- def last
388
- entries.entries.last
389
- end
390
-
391
- def deactivate
392
- case association.dependency
393
- when :destroy
394
- entries.each do |member|
395
- member.destroy! unless member.new_record?
396
- end
397
- when :delete
398
- delete_association
399
- when :protect
400
- unless entries.empty?
401
- raise AssociationProtectedError.new("You cannot delete this model while it has items associated with it.")
402
- end
403
- when :nullify
404
- nullify_association
405
- else
406
- nullify_association
407
- end
408
- end
409
-
410
- def delete_association
411
- @instance.database_context.adapter.connection do |db|
412
- associated_keys = entries.collect do |item|
413
- item.key unless item.new_record?
414
- end.compact
415
- parameters = [@instance.key] + associated_keys
416
-
417
- sql = association.to_delete_set_sql
418
- db.create_command(sql).execute_non_query(*[parameters, parameters])
419
-
420
- sql = association.to_delete_members_sql
421
- db.create_command(sql).execute_non_query(associated_keys)
422
- end
423
- end
424
-
425
- def nullify_association
426
- @instance.database_context.adapter.connection do |db|
427
- sql = association.to_delete_sql
428
- parameters = [@instance.key]
429
- db.create_command(sql).execute_non_query(*parameters)
430
- end
431
- end
432
- end
433
-
434
- end # class HasAndBelongsToManyAssociation
435
-
436
- end # module Associations
437
- end # module DataMapper
@@ -1,283 +0,0 @@
1
- require 'data_mapper/associations/has_n_association'
2
-
3
- module DataMapper
4
- module Associations
5
-
6
- class HasManyAssociation < HasNAssociation
7
-
8
- def dependency
9
- @options[:dependent]
10
- end
11
-
12
- # Define the association instance method (i.e. Project#tasks)
13
- def define_accessor(klass)
14
- klass.class_eval <<-EOS
15
- def #{@association_name}
16
- @#{@association_name} || (@#{@association_name} = DataMapper::Associations::HasManyAssociation::Set.new(self, #{@association_name.inspect}))
17
- end
18
-
19
- def #{@association_name}=(value)
20
- #{@association_name}.set(value)
21
- end
22
-
23
- private
24
- def #{@association_name}_keys=(value)
25
- #{@association_name}.clear
26
-
27
- associated_constant = #{@association_name}.association.associated_constant
28
- associated_table = #{@association_name}.association.associated_table
29
- associated_constant.all(associated_table.key => [*value]).each do |entry|
30
- #{@association_name} << entry
31
- end
32
- end
33
- EOS
34
- end
35
-
36
- def to_disassociate_sql
37
- "UPDATE #{associated_table.to_sql} SET #{foreign_key_column.to_sql} = NULL WHERE #{foreign_key_column.to_sql} = ?"
38
- end
39
-
40
- def to_delete_sql
41
- "DELETE FROM #{associated_table.to_sql} WHERE #{foreign_key_column.to_sql} = ?"
42
- end
43
-
44
- def instance_variable_name
45
- class << self
46
- attr_reader :instance_variable_name
47
- end
48
-
49
- @instance_variable_name = "@#{@association_name}"
50
- end
51
-
52
- class Set < Associations::Reference
53
-
54
- include Enumerable
55
-
56
- # Returns true if the association has zero items
57
- def nil?
58
- loaded_members.blank?
59
- end
60
-
61
- def dirty?(cleared = ::Set.new)
62
- loaded_members.any? { |member| cleared.include?(member) || member.dirty?(cleared) }
63
- end
64
-
65
- def validate_recursively(event, cleared)
66
- loaded_members.all? { |member| cleared.include?(member) || member.validate_recursively(event, cleared) }
67
- end
68
-
69
- def save_without_validation(database_context, cleared)
70
-
71
- adapter = @instance.database_context.adapter
72
-
73
- members = loaded_members
74
-
75
- adapter.connection do |db|
76
-
77
- sql = association.to_disassociate_sql
78
- parameters = [@instance.key]
79
-
80
- member_keys = members.map { |member| member.key }.compact
81
-
82
- unless member_keys.empty?
83
- sql << " AND #{association.associated_table.key} NOT IN ?"
84
- parameters << member_keys
85
- end
86
-
87
- db.create_command(sql).execute_non_query(*parameters)
88
- end
89
-
90
- unless members.blank?
91
-
92
- setter_method = "#{@association_name}=".to_sym
93
- ivar_name = association.foreign_key_column.instance_variable_name
94
- original_value_name = association.foreign_key_column.name
95
-
96
- members.each do |member|
97
- member.original_values.delete(original_value_name)
98
- member.instance_variable_set(ivar_name, @instance.key)
99
- @instance.database_context.adapter.save_without_validation(database_context, member, cleared)
100
- end
101
- end
102
- end
103
-
104
- def each
105
- items.each { |item| yield item }
106
- end
107
-
108
- # Builds a new item and returns it.
109
- def build(options)
110
- item = association.associated_constant.new(options)
111
- self << item
112
- item
113
- end
114
-
115
- # Builds and saves a new item, then returns it.
116
- def create(options)
117
- item = build(options)
118
- item.save
119
- item
120
- end
121
-
122
- def set(value)
123
- values = value.is_a?(Enumerable) ? value : [value]
124
- @items = Support::TypedSet.new(association.associated_constant)
125
- values.each do |item|
126
- self << item
127
- end
128
- end
129
-
130
- # Adds a new item to the association. The entire item collection is then returned.
131
- def <<(member)
132
- shallow_append(member)
133
-
134
- if complement = association.complementary_association
135
- member.send("#{complement.name}_association").shallow_append(@instance)
136
- end
137
-
138
- return self
139
- end
140
-
141
- def clear
142
- @pending_members = nil
143
- @items = Support::TypedSet.new(association.associated_constant)
144
- end
145
-
146
- def shallow_append(member)
147
- if @items
148
- self.items << member
149
- else
150
- pending_members << member
151
- end
152
- return self
153
- end
154
-
155
- def method_missing(symbol, *args, &block)
156
- if items.respond_to?(symbol)
157
- items.send(symbol, *args, &block)
158
- elsif association.associated_table.associations.any? { |assoc| assoc.name == symbol }
159
- results = []
160
- each do |item|
161
- unless (val = item.send(symbol)).blank?
162
- results << (val.is_a?(Enumerable) ? val.entries : val)
163
- end
164
- end
165
- results.flatten
166
- elsif items.size == 1 && items.entries.first.respond_to?(symbol)
167
- items.entries.first.send(symbol, *args, &block)
168
- else
169
- super
170
- end
171
- end
172
-
173
- def respond_to?(symbol)
174
- items.respond_to?(symbol) || super
175
- end
176
-
177
- def reload!
178
- @items = nil
179
- end
180
-
181
- def items
182
- @items || begin
183
- if @instance.loaded_set.nil?
184
- @items = Support::TypedSet.new(association.associated_constant)
185
- else
186
- associated_items = fetch_sets
187
-
188
- # This is where @items is set, by calling association=,
189
- # which in turn calls HasManyAssociation::Set#set.
190
- association_ivar_name = association.instance_variable_name
191
- setter_method = "#{@association_name}=".to_sym
192
- @instance.loaded_set.each do |entry|
193
- entry.send(setter_method, associated_items[entry.key]) if entry.respond_to?(setter_method)
194
- end # @instance.loaded_set.each
195
- end # if @instance.loaded_set.nil?
196
-
197
- if @pending_members
198
- pending_members.each do |member|
199
- @items << member
200
- end
201
-
202
- pending_members.clear
203
- end
204
-
205
- return @items
206
- end # begin
207
- end # def items
208
-
209
- def inspect
210
- entries.inspect
211
- end
212
-
213
- def first
214
- items.entries.first
215
- end
216
-
217
- def last
218
- items.entries.last
219
- end
220
-
221
- def ==(other)
222
- (items.size == 1 ? first : items) == other
223
- end
224
-
225
- def deactivate
226
- case association.dependency
227
- when :destroy
228
- items.entries.each do |member|
229
- status = member.destroy! unless member.new_record?
230
- return false unless status
231
- end
232
- when :delete
233
- @instance.database_context.adapter.connection do |db|
234
- sql = association.to_delete_sql
235
- parameters = [@instance.key]
236
- db.create_command(sql).execute_non_query(*parameters)
237
- end
238
- when :protect
239
- unless items.empty?
240
- raise AssociationProtectedError.new("You cannot delete this model while it has items associated with it.")
241
- end
242
- when :nullify
243
- nullify_association
244
- else
245
- nullify_association
246
- end
247
- end
248
-
249
- def nullify_association
250
- @instance.database_context.adapter.connection do |db|
251
- sql = association.to_disassociate_sql
252
- parameters = [@instance.key]
253
- db.create_command(sql).execute_non_query(*parameters)
254
- end
255
- end
256
-
257
- private
258
- def loaded_members
259
- pending_members + @items
260
- end
261
-
262
- def pending_members
263
- @pending_members || @pending_members = Support::TypedSet.new(association.associated_constant)
264
- end
265
-
266
- def fetch_sets
267
- finder_options = { association.foreign_key_column.to_sym => @instance.loaded_set.map { |item| item.key } }
268
- finder_options.merge!(association.finder_options)
269
-
270
- foreign_key_ivar_name = association.foreign_key_column.instance_variable_name
271
-
272
- @instance.database_context.all(
273
- association.associated_constant,
274
- finder_options
275
- ).group_by { |entry| entry.instance_variable_get(foreign_key_ivar_name) }
276
- end
277
-
278
- end
279
-
280
- end
281
-
282
- end
283
- end