dm-core 1.0.0.rc2 → 1.0.0.rc3

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/Gemfile +1 -1
  2. data/LICENSE +1 -1
  3. data/README.rdoc +1 -1
  4. data/Rakefile +3 -4
  5. data/VERSION +1 -1
  6. data/dm-core.gemspec +7 -5
  7. data/lib/dm-core.rb +44 -0
  8. data/lib/dm-core/adapters.rb +1 -1
  9. data/lib/dm-core/adapters/abstract_adapter.rb +16 -0
  10. data/lib/dm-core/collection.rb +2 -2
  11. data/lib/dm-core/model.rb +64 -53
  12. data/lib/dm-core/model/property.rb +14 -6
  13. data/lib/dm-core/model/relationship.rb +10 -18
  14. data/lib/dm-core/property.rb +10 -10
  15. data/lib/dm-core/query.rb +8 -18
  16. data/lib/dm-core/resource.rb +3 -11
  17. data/lib/dm-core/resource/state.rb +13 -16
  18. data/lib/dm-core/resource/state/dirty.rb +11 -1
  19. data/lib/dm-core/resource/state/transient.rb +9 -1
  20. data/lib/dm-core/spec/lib/adapter_helpers.rb +5 -0
  21. data/lib/dm-core/spec/shared/adapter_spec.rb +2 -0
  22. data/lib/dm-core/spec/shared/resource_spec.rb +0 -31
  23. data/lib/dm-core/version.rb +1 -1
  24. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +2 -0
  25. data/spec/public/associations/many_to_many_spec.rb +2 -1
  26. data/spec/public/associations/many_to_one_spec.rb +1 -0
  27. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +1 -0
  28. data/spec/public/associations/one_to_many_spec.rb +2 -0
  29. data/spec/public/associations/one_to_one_spec.rb +2 -0
  30. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +1 -0
  31. data/spec/public/collection_spec.rb +2 -0
  32. data/spec/public/finalize_spec.rb +34 -0
  33. data/spec/public/model/hook_spec.rb +1 -0
  34. data/spec/public/model/property_spec.rb +1 -0
  35. data/spec/public/model/relationship_spec.rb +22 -0
  36. data/spec/public/model_spec.rb +138 -3
  37. data/spec/public/property/discriminator_spec.rb +1 -0
  38. data/spec/public/property/object_spec.rb +1 -0
  39. data/spec/public/property_spec.rb +13 -4
  40. data/spec/public/resource_spec.rb +1 -0
  41. data/spec/public/sel_spec.rb +2 -0
  42. data/spec/public/shared/collection_shared_spec.rb +0 -45
  43. data/spec/public/shared/finder_shared_spec.rb +110 -0
  44. data/spec/public/shared/property_shared_spec.rb +1 -1
  45. data/spec/rcov.opts +1 -1
  46. data/spec/semipublic/associations/many_to_many_spec.rb +3 -0
  47. data/spec/semipublic/associations/many_to_one_spec.rb +2 -0
  48. data/spec/semipublic/associations/one_to_many_spec.rb +2 -0
  49. data/spec/semipublic/associations/one_to_one_spec.rb +2 -0
  50. data/spec/semipublic/associations/relationship_spec.rb +6 -0
  51. data/spec/semipublic/query/conditions/comparison_spec.rb +3 -0
  52. data/spec/semipublic/query/conditions/operation_spec.rb +1 -0
  53. data/spec/semipublic/query/path_spec.rb +2 -0
  54. data/spec/semipublic/query_spec.rb +2 -3
  55. data/spec/semipublic/resource/state/clean_spec.rb +2 -1
  56. data/spec/semipublic/resource/state/deleted_spec.rb +2 -1
  57. data/spec/semipublic/resource/state/dirty_spec.rb +42 -20
  58. data/spec/semipublic/resource/state/immutable_spec.rb +7 -1
  59. data/spec/semipublic/resource/state/transient_spec.rb +69 -40
  60. data/spec/semipublic/resource/state_spec.rb +72 -66
  61. data/spec/semipublic/shared/property_shared_spec.rb +1 -0
  62. data/spec/semipublic/shared/resource_shared_spec.rb +1 -0
  63. data/spec/spec_helper.rb +0 -10
  64. data/tasks/spec.rake +3 -0
  65. metadata +9 -7
data/Gemfile CHANGED
@@ -71,7 +71,7 @@
71
71
  source 'http://rubygems.org'
72
72
 
73
73
  DATAMAPPER = 'git://github.com/datamapper'
74
- DM_VERSION = '~> 1.0.0.rc2'
74
+ DM_VERSION = '~> 1.0.0.rc3'
75
75
 
76
76
  group :runtime do # Runtime dependencies (as in the gemspec)
77
77
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Dan Kubb
1
+ Copyright (c) 2010 Dan Kubb
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -201,4 +201,4 @@ nice syntax tweaks DataMapper delivers out of the box...
201
201
 
202
202
  == Copyright
203
203
 
204
- Copyright (c) 2009 Dan Kubb. See LICENSE for details.
204
+ Copyright (c) 2010 Dan Kubb. See LICENSE for details.
data/Rakefile CHANGED
@@ -10,17 +10,16 @@ begin
10
10
  gem.summary = 'An Object/Relational Mapper for Ruby'
11
11
  gem.description = 'Faster, Better, Simpler.'
12
12
  gem.email = 'dan.kubb@gmail.com'
13
- gem.homepage = 'http://github.com/datamapper/dm-core'
13
+ gem.homepage = 'http://github.com/datamapper/%s' % gem.name
14
14
  gem.authors = [ 'Dan Kubb' ]
15
15
 
16
16
  gem.rubyforge_project = 'datamapper'
17
17
 
18
- gem.add_dependency 'extlib', '~> 0.9.14'
19
- gem.add_dependency 'addressable', '~> 2.1'
18
+ gem.add_dependency 'extlib', '~> 0.9.15'
19
+ gem.add_dependency 'addressable', '~> 2.1'
20
20
 
21
21
  gem.add_development_dependency 'rspec', '~> 1.3'
22
22
  gem.add_development_dependency 'jeweler', '~> 1.4'
23
-
24
23
  end
25
24
 
26
25
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0.rc2
1
+ 1.0.0.rc3
data/dm-core.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dm-core}
8
- s.version = "1.0.0.rc2"
8
+ s.version = "1.0.0.rc3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Dan Kubb"]
12
- s.date = %q{2010-05-19}
12
+ s.date = %q{2010-05-27}
13
13
  s.description = %q{Faster, Better, Simpler.}
14
14
  s.email = %q{dan.kubb@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -126,6 +126,7 @@ Gem::Specification.new do |s|
126
126
  "spec/public/associations/one_to_one_spec.rb",
127
127
  "spec/public/associations/one_to_one_with_boolean_cpk_spec.rb",
128
128
  "spec/public/collection_spec.rb",
129
+ "spec/public/finalize_spec.rb",
129
130
  "spec/public/model/hook_spec.rb",
130
131
  "spec/public/model/property_spec.rb",
131
132
  "spec/public/model/relationship_spec.rb",
@@ -227,6 +228,7 @@ Gem::Specification.new do |s|
227
228
  "spec/public/associations/one_to_one_spec.rb",
228
229
  "spec/public/associations/one_to_one_with_boolean_cpk_spec.rb",
229
230
  "spec/public/collection_spec.rb",
231
+ "spec/public/finalize_spec.rb",
230
232
  "spec/public/model/hook_spec.rb",
231
233
  "spec/public/model/property_spec.rb",
232
234
  "spec/public/model/relationship_spec.rb",
@@ -310,18 +312,18 @@ Gem::Specification.new do |s|
310
312
  s.specification_version = 3
311
313
 
312
314
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
313
- s.add_runtime_dependency(%q<extlib>, ["~> 0.9.14"])
315
+ s.add_runtime_dependency(%q<extlib>, ["~> 0.9.15"])
314
316
  s.add_runtime_dependency(%q<addressable>, ["~> 2.1"])
315
317
  s.add_development_dependency(%q<rspec>, ["~> 1.3"])
316
318
  s.add_development_dependency(%q<jeweler>, ["~> 1.4"])
317
319
  else
318
- s.add_dependency(%q<extlib>, ["~> 0.9.14"])
320
+ s.add_dependency(%q<extlib>, ["~> 0.9.15"])
319
321
  s.add_dependency(%q<addressable>, ["~> 2.1"])
320
322
  s.add_dependency(%q<rspec>, ["~> 1.3"])
321
323
  s.add_dependency(%q<jeweler>, ["~> 1.4"])
322
324
  end
323
325
  else
324
- s.add_dependency(%q<extlib>, ["~> 0.9.14"])
326
+ s.add_dependency(%q<extlib>, ["~> 0.9.15"])
325
327
  s.add_dependency(%q<addressable>, ["~> 2.1"])
326
328
  s.add_dependency(%q<rspec>, ["~> 1.3"])
327
329
  s.add_dependency(%q<jeweler>, ["~> 1.4"])
data/lib/dm-core.rb CHANGED
@@ -285,4 +285,48 @@ module DataMapper
285
285
  current_repository
286
286
  end
287
287
  end
288
+
289
+ # Perform necessary steps to finalize DataMapper for the current repository
290
+ #
291
+ # This method should be called after loading all models and plugins.
292
+ #
293
+ # It ensures foreign key properties and anonymous join models are created.
294
+ # These are otherwise lazily declared, which can lead to unexpected errors.
295
+ # It also performs basic validity checking of the DataMapper models.
296
+ #
297
+ # @return [DataMapper] The DataMapper module
298
+ #
299
+ # @api public
300
+ def self.finalize
301
+ Model.descendants.each do |model|
302
+ finalize_model(model)
303
+ end
304
+ self
305
+ end
306
+
307
+ private
308
+ # @api private
309
+ def self.finalize_model(model)
310
+ name = model.name
311
+ repository_name = model.repository_name
312
+ relationships = model.relationships(repository_name).values
313
+
314
+ if model.properties(repository_name).empty? &&
315
+ !relationships.any? { |relationship| relationship.kind_of?(Associations::ManyToOne::Relationship) }
316
+ raise IncompleteModelError, "#{name} must have at least one property or many to one relationship to be valid"
317
+ end
318
+
319
+ if model.key(repository_name).empty?
320
+ raise IncompleteModelError, "#{name} must have a key to be valid"
321
+ end
322
+
323
+
324
+ # initialize join models and target keys
325
+ relationships.each do |relationship|
326
+ relationship.child_key
327
+ relationship.through if relationship.respond_to?(:through)
328
+ relationship.via if relationship.respond_to?(:via)
329
+ end
330
+
331
+ end
288
332
  end
@@ -147,7 +147,7 @@ module DataMapper
147
147
  #
148
148
  # @api semipublic
149
149
  def adapter_name(const_name)
150
- const_name.to_s.sub('Adapter', '').downcase
150
+ const_name.to_s.chomp('Adapter').downcase
151
151
  end
152
152
 
153
153
  # Require the adapter library
@@ -18,6 +18,22 @@ module DataMapper
18
18
 
19
19
  equalize :name, :options, :resource_naming_convention, :field_naming_convention
20
20
 
21
+ # @api semipublic
22
+ def self.descendants
23
+ @descendants ||= Set.new
24
+ end
25
+
26
+ # @api private
27
+ def self.inherited(subclass)
28
+ add_descendant(subclass)
29
+ end
30
+
31
+ # @api private
32
+ def self.add_descendant(subclass)
33
+ descendants << subclass
34
+ superclass.add_descendant(subclass) if superclass.respond_to?(:add_descendant)
35
+ end
36
+
21
37
  # Adapter name
22
38
  #
23
39
  # @example
@@ -1441,7 +1441,7 @@ module DataMapper
1441
1441
  def method_missing(method, *args, &block)
1442
1442
  relationships = self.relationships
1443
1443
 
1444
- if model.model_method_defined?(method)
1444
+ if model.respond_to?(method)
1445
1445
  delegate_to_model(method, *args, &block)
1446
1446
  elsif relationship = relationships[method] || relationships[method.to_s.singularize.to_sym]
1447
1447
  delegate_to_relationship(relationship, *args)
@@ -1463,7 +1463,7 @@ module DataMapper
1463
1463
  # @api private
1464
1464
  def delegate_to_model(method, *args, &block)
1465
1465
  model = self.model
1466
- model.__send__(:with_scope, query) do
1466
+ model.send(:with_scope, query) do
1467
1467
  model.send(method, *args, &block)
1468
1468
  end
1469
1469
  end
data/lib/dm-core/model.rb CHANGED
@@ -7,6 +7,8 @@ module DataMapper
7
7
  module Model
8
8
  extend Chainable
9
9
 
10
+ include Enumerable
11
+
10
12
  # Creates a new Model class with default_storage_name +storage_name+
11
13
  #
12
14
  # If a block is passed, it will be eval'd in the context of the new Model
@@ -305,17 +307,23 @@ module DataMapper
305
307
  all.at(*args)
306
308
  end
307
309
 
310
+ def fetch(*args, &block)
311
+ all.fetch(*args, &block)
312
+ end
313
+
314
+ def values_at(*args)
315
+ all.values_at(*args)
316
+ end
317
+
308
318
  def reverse
309
319
  all.reverse
310
320
  end
311
321
 
312
- # TODO: spec this
313
- def entries
314
- all.entries
322
+ def each(&block)
323
+ all.each(&block)
324
+ self
315
325
  end
316
326
 
317
- alias to_a entries
318
-
319
327
  # Find a set of records matching an optional set of conditions. Additionally,
320
328
  # specify the order that the records are return.
321
329
  #
@@ -444,22 +452,6 @@ module DataMapper
444
452
  first(conditions) || create(conditions.merge(attributes))
445
453
  end
446
454
 
447
- # Initializes an instance of Resource with the given attributes
448
- #
449
- # @param [Hash(Symbol => Object)] attributes
450
- # hash of attributes to set
451
- #
452
- # @return [Resource]
453
- # the newly initialized Resource instance
454
- #
455
- # @api public
456
- chainable do
457
- def new(*args, &block)
458
- assert_valid
459
- super
460
- end
461
- end
462
-
463
455
  # Create a Resource
464
456
  #
465
457
  # @param [Hash(Symbol => Object)] attributes
@@ -486,6 +478,56 @@ module DataMapper
486
478
  _create(attributes, false)
487
479
  end
488
480
 
481
+ # Update every Resource
482
+ #
483
+ # Person.update(:allow_beer => true)
484
+ #
485
+ # @param [Hash] attributes
486
+ # attributes to update with
487
+ #
488
+ # @return [Boolean]
489
+ # true if the resources were successfully updated
490
+ #
491
+ # @api public
492
+ def update(attributes)
493
+ all.update(attributes)
494
+ end
495
+
496
+ # Update every Resource, bypassing validations
497
+ #
498
+ # Person.update!(:allow_beer => true)
499
+ #
500
+ # @param [Hash] attributes
501
+ # attributes to update with
502
+ #
503
+ # @return [Boolean]
504
+ # true if the resources were successfully updated
505
+ #
506
+ # @api public
507
+ def update!(attributes)
508
+ all.update!(attributes)
509
+ end
510
+
511
+ # Remove all Resources from the repository
512
+ #
513
+ # @return [Boolean]
514
+ # true if the resources were successfully destroyed
515
+ #
516
+ # @api public
517
+ def destroy
518
+ all.destroy
519
+ end
520
+
521
+ # Remove all Resources from the repository, bypassing validation
522
+ #
523
+ # @return [Boolean]
524
+ # true if the resources were successfully destroyed
525
+ #
526
+ # @api public
527
+ def destroy!
528
+ all.destroy!
529
+ end
530
+
489
531
  # Copy a set of records from one repository to another.
490
532
  #
491
533
  # @param [String] source_repository_name
@@ -668,16 +710,6 @@ module DataMapper
668
710
  [ repository ].to_set + @properties.keys.map { |repository_name| DataMapper.repository(repository_name) }
669
711
  end
670
712
 
671
- # @api private
672
- def model_method_defined?(method)
673
- model_methods.include?(method.to_s)
674
- end
675
-
676
- # @api private
677
- def resource_method_defined?(method)
678
- resource_methods.include?(method.to_s)
679
- end
680
-
681
713
  private
682
714
 
683
715
  # @api private
@@ -748,6 +780,7 @@ module DataMapper
748
780
  end
749
781
 
750
782
  # @api private
783
+ # TODO: Remove this once appropriate warnings can be added.
751
784
  def assert_valid(force = false) # :nodoc:
752
785
  return if @valid && !force
753
786
  @valid = true
@@ -774,28 +807,6 @@ module DataMapper
774
807
  end
775
808
  end
776
809
 
777
- # @api private
778
- def model_methods
779
- @model_methods ||= ancestor_instance_methods { |mod| mod.singleton_class }
780
- end
781
-
782
- # @api private
783
- def resource_methods
784
- @resource_methods ||= ancestor_instance_methods { |mod| mod }
785
- end
786
-
787
- # @api private
788
- def ancestor_instance_methods
789
- methods = Set.new
790
-
791
- ancestors.each do |mod|
792
- next unless mod <= DataMapper::Resource
793
- methods.merge(yield(mod).instance_methods(false).map { |method| method.to_s })
794
- end
795
-
796
- methods
797
- end
798
-
799
810
  # Raises an exception if #get receives the wrong number of arguments
800
811
  #
801
812
  # @param [Array] key
@@ -222,9 +222,8 @@ module DataMapper
222
222
  name = property.name.to_s
223
223
  reader_visibility = property.reader_visibility
224
224
  instance_variable_name = property.instance_variable_name
225
- primitive = property.primitive
226
225
 
227
- unless resource_method_defined?(name)
226
+ unless reserved_method?(name)
228
227
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
229
228
  chainable do
230
229
  #{reader_visibility}
@@ -239,10 +238,14 @@ module DataMapper
239
238
 
240
239
  boolean_reader_name = "#{name}?"
241
240
 
242
- if primitive == TrueClass && !resource_method_defined?(boolean_reader_name)
241
+ if property.kind_of?(DataMapper::Property::Boolean) && !reserved_method?(boolean_reader_name)
243
242
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
244
- #{reader_visibility}
245
- alias #{boolean_reader_name} #{name}
243
+ chainable do
244
+ #{reader_visibility}
245
+ def #{boolean_reader_name}
246
+ #{name}
247
+ end
248
+ end
246
249
  RUBY
247
250
  end
248
251
  end
@@ -256,7 +259,7 @@ module DataMapper
256
259
 
257
260
  writer_name = "#{name}="
258
261
 
259
- return if resource_method_defined?(writer_name)
262
+ return if reserved_method?(writer_name)
260
263
 
261
264
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
262
265
  chainable do
@@ -270,6 +273,11 @@ module DataMapper
270
273
  RUBY
271
274
  end
272
275
 
276
+ # @api private
277
+ def reserved_method?(name)
278
+ method_defined?(name) && !%w[ id type ].include?(name)
279
+ end
280
+
273
281
  chainable do
274
282
  # @api public
275
283
  def method_missing(method, *args, &block)
@@ -81,17 +81,13 @@ module DataMapper
81
81
  # cardinality that defines the association type and constraints
82
82
  # @param name [Symbol]
83
83
  # the name that the association will be referenced by
84
- # @param model [Model, #to_str]
85
- # the target model of the relationship
86
- # @param opts [Hash]
87
- # an options hash
84
+ # @param *args [Model, Hash] model and/or options hash
88
85
  #
89
- # @option :through[Symbol] A association that this join should go through to form
86
+ # @option *args :through[Symbol] A association that this join should go through to form
90
87
  # a many-to-many association
91
- # @option :model[Model, String] The name of the class to associate with, if omitted
88
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
92
89
  # then the association name is assumed to match the class name
93
- # @option :repository[Symbol]
94
- # name of child model repository
90
+ # @option *args :repository[Symbol] name of child model repository
95
91
  #
96
92
  # @return [Association::Relationship] the relationship that was
97
93
  # created to reflect either a one-to-one, one-to-many or many-to-many
@@ -148,15 +144,11 @@ module DataMapper
148
144
  #
149
145
  # @param name [Symbol]
150
146
  # the name that the association will be referenced by
151
- # @param model [Model, #to_str]
152
- # the target model of the relationship
153
- # @param opts [Hash]
154
- # an options hash
147
+ # @param *args [Model, Hash] model and/or options hash
155
148
  #
156
- # @option :model[Model, String] The name of the class to associate with, if omitted
149
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
157
150
  # then the association name is assumed to match the class name
158
- # @option :repository[Symbol]
159
- # name of child model repository
151
+ # @option *args :repository[Symbol] name of child model repository
160
152
  #
161
153
  # @return [Association::Relationship] The association created
162
154
  # should not be accessed directly
@@ -311,7 +303,7 @@ module DataMapper
311
303
  # :target_key (will mean something different for each relationship)
312
304
 
313
305
  [ :child_key, :parent_key ].each do |key|
314
- if options.key?(key) && !options[key].is_a?(Enumerable)
306
+ if options.key?(key)
315
307
  options[key] = Array(options[key])
316
308
  end
317
309
  end
@@ -328,7 +320,7 @@ module DataMapper
328
320
  name = relationship.name
329
321
  reader_name = name.to_s
330
322
 
331
- return if resource_method_defined?(reader_name)
323
+ return if method_defined?(reader_name)
332
324
 
333
325
  reader_visibility = relationship.reader_visibility
334
326
 
@@ -355,7 +347,7 @@ module DataMapper
355
347
  name = relationship.name
356
348
  writer_name = "#{name}="
357
349
 
358
- return if resource_method_defined?(writer_name)
350
+ return if method_defined?(writer_name)
359
351
 
360
352
  writer_visibility = relationship.writer_visibility
361
353