dm-core 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/History.txt +25 -5
  2. data/Manifest.txt +1 -0
  3. data/README.txt +67 -23
  4. data/Rakefile +0 -2
  5. data/deps.rip +1 -1
  6. data/dm-core.gemspec +6 -6
  7. data/lib/dm-core/adapters/abstract_adapter.rb +3 -76
  8. data/lib/dm-core/adapters/data_objects_adapter.rb +8 -39
  9. data/lib/dm-core/associations/many_to_many.rb +28 -16
  10. data/lib/dm-core/associations/many_to_one.rb +1 -45
  11. data/lib/dm-core/associations/one_to_many.rb +1 -38
  12. data/lib/dm-core/associations/relationship.rb +43 -20
  13. data/lib/dm-core/collection.rb +33 -32
  14. data/lib/dm-core/model/property.rb +8 -8
  15. data/lib/dm-core/model/relationship.rb +10 -12
  16. data/lib/dm-core/property.rb +20 -85
  17. data/lib/dm-core/property_set.rb +8 -8
  18. data/lib/dm-core/query/conditions/comparison.rb +13 -71
  19. data/lib/dm-core/query/conditions/operation.rb +73 -47
  20. data/lib/dm-core/query/operator.rb +3 -45
  21. data/lib/dm-core/query/path.rb +5 -41
  22. data/lib/dm-core/query.rb +37 -108
  23. data/lib/dm-core/repository.rb +3 -79
  24. data/lib/dm-core/resource.rb +54 -49
  25. data/lib/dm-core/support/chainable.rb +0 -2
  26. data/lib/dm-core/support/equalizer.rb +23 -0
  27. data/lib/dm-core/types/object.rb +4 -4
  28. data/lib/dm-core/version.rb +1 -1
  29. data/lib/dm-core.rb +3 -11
  30. data/spec/public/model/relationship_spec.rb +4 -4
  31. data/spec/public/property_spec.rb +5 -449
  32. data/spec/public/sel_spec.rb +52 -2
  33. data/spec/public/shared/collection_shared_spec.rb +79 -26
  34. data/spec/public/shared/finder_shared_spec.rb +6 -6
  35. data/spec/public/shared/resource_shared_spec.rb +2 -2
  36. data/spec/semipublic/property_spec.rb +524 -9
  37. data/spec/semipublic/query_spec.rb +6 -6
  38. data/tasks/hoe.rb +2 -2
  39. metadata +24 -4
data/History.txt CHANGED
@@ -1,7 +1,27 @@
1
- === 0.10.0 / 2009-10-15
1
+ === 0.10.1 / 2009-09-30
2
2
 
3
- * Countless enhancements:
3
+ * 4 minor enhancements:
4
4
 
5
- * Total rewrite of most of dm-core, including YARD docs for more
6
- than half the methods and ~90% coverage of code. More to be done
7
- but this is a great start.
5
+ * On Ruby 1.8.7+ allow #pop and #shift in Collection to work with
6
+ multiple entries.
7
+ * Added NullOperation condition
8
+ * Added Resource#destroyed?
9
+ * Updated ManyToMany::Collection#intermediaries to be public
10
+
11
+ * 6 bug fixes:
12
+
13
+ * Fixed Query::Path to work with .like
14
+ * Replaced usage of base64 lib with pack/unpack
15
+ * Ensure the correct ordering of links for more complex joins
16
+ * Simplified SELECT query generation
17
+ * Simplified Relationship inheritance by subclasses
18
+ * Ensure Property.new options allow an Array of Symbols for :unique,
19
+ and :unique_index
20
+
21
+ === 0.10.0 / 2009-09-15
22
+
23
+ * Countless enhancements:
24
+
25
+ * Total rewrite of most of dm-core, including YARD docs for more
26
+ than half the methods and ~90% coverage of code. More to be done
27
+ but this is a great start.
data/Manifest.txt CHANGED
@@ -54,6 +54,7 @@ lib/dm-core/spec/adapter_shared_spec.rb
54
54
  lib/dm-core/spec/data_objects_adapter_shared_spec.rb
55
55
  lib/dm-core/support/chainable.rb
56
56
  lib/dm-core/support/deprecate.rb
57
+ lib/dm-core/support/equalizer.rb
57
58
  lib/dm-core/support/logger.rb
58
59
  lib/dm-core/support/naming_conventions.rb
59
60
  lib/dm-core/transaction.rb
data/README.txt CHANGED
@@ -25,7 +25,7 @@ see all <tt>false</tt> results. Do the same in DataMapper and it's
25
25
  @parent = Tree.first(:name => 'bob')
26
26
 
27
27
  @parent.children.each do |child|
28
- puts @parent.object_id == child.parent.object_id
28
+ puts @parent.equal?(child.parent) # => true
29
29
  end
30
30
  end
31
31
 
@@ -38,16 +38,13 @@ the fields that actually changed. So it plays well with others. You can
38
38
  use it in an Integration data-store without worrying that your application will
39
39
  be a bad actor causing trouble for all of your other processes.
40
40
 
41
- You can also configure which strategy you'd like to use to track dirtiness.
42
-
43
41
  == Eager Loading
44
42
 
45
- Ready for something amazing? The following example executes only two queries.
43
+ Ready for something amazing? The following example executes only two queries
44
+ regardless of how many rows the inner and outer queries return.
46
45
 
47
46
  repository do
48
- zoos = Zoo.all
49
- first = zoos.first
50
- first.exhibits # Loads the exhibits for all the Zoo objects in the zoos variable.
47
+ Zoo.all.each { |zoo| zoo.exhibits.to_a }
51
48
  end
52
49
 
53
50
  Pretty impressive huh? The idea is that you aren't going to load a set of
@@ -55,7 +52,7 @@ objects and use only an association in just one of them. This should hold up
55
52
  pretty well against a 99% rule. When you don't want it to work like this, just
56
53
  load the item you want in it's own set. So the DataMapper thinks ahead. We
57
54
  like to call it "performant by default". This feature single-handedly wipes
58
- out the "N+1 Query Problem". No need to specify an <tt>include</tt> option in
55
+ out the "N+1 Query Problem". No need to specify an <tt>:include</tt> option in
59
56
  your finders.
60
57
 
61
58
  == Laziness Can Be A Virtue
@@ -67,36 +64,84 @@ place to get what it needs. Since ActiveRecord returns everything by default,
67
64
  adding a text field to a table slows everything down drastically, across the
68
65
  board.
69
66
 
70
- Not so with the DataMapper. Text fields are treated like in-row associations
71
- by default, meaning they only load when you need them. If you want more
72
- control you can enable or disable this feature for any field (not just
73
- text-fields) by passing a @lazy@ option to your field mapping with a value of
74
- <tt>true</tt> or <tt>false</tt>.
67
+ Not so with the DataMapper. Text fields are lazily loaded, meaning they
68
+ only load when you need them. If you want more control you can enable or
69
+ disable this feature for any field (not just text-fields) by passing a
70
+ @:lazy@ option to your field mapping with a value of <tt>true</tt> or
71
+ <tt>false</tt>.
75
72
 
76
73
  class Animal
77
74
  include DataMapper::Resource
78
75
 
79
- property :name, String
76
+ property :name, String
80
77
  property :notes, Text, :lazy => false
81
78
  end
82
79
 
83
- Plus, lazy-loading of text fields happens automatically and intelligently when
80
+ Plus, lazy-loading of Text fields happens automatically and intelligently when
84
81
  working with associations. The following only issues 2 queries to load up all
85
82
  of the notes fields on each animal:
86
83
 
87
84
  repository do
88
- animals = Animal.all
89
- animals.each do |pet|
90
- pet.notes
85
+ Animal.all.each { |animal| animal.notes.to_a }
86
+ end
87
+
88
+ Did you notice the the <tt>#to_a</tt> call in the above example? That
89
+ was necessary because even DataMapper collections are lazy. If you don't
90
+ iterate over them, or in this case ask them to become Arrays, they won't
91
+ execute until you need them. We needed to call <tt>#to_a</tt> to force
92
+ the lazy load because without it, the above example would have only
93
+ executed one query. This extra bit of laziness can come in very handy,
94
+ for example:
95
+
96
+ animals = Animal.all
97
+
98
+ unless note.blank?
99
+ animals.each do |animal|
100
+ animal.update(:note => note)
101
+ end
102
+ end
103
+
104
+ In the above example, the Animals won't be retrieved until you actually
105
+ need them. This comes in handy in cases where you initialize the
106
+ collection before you know if you need it, like in a web app controller.
107
+
108
+ == Collection Chaining
109
+
110
+ DataMapper's lazy collections are also handy because you can get the
111
+ same effect as named scopes, without any special syntax, eg:
112
+
113
+ class Animal
114
+ # ... setup ...
115
+
116
+ def self.mammals
117
+ all(:mammal => true)
118
+ end
119
+
120
+ def self.zoo(zoo)
121
+ all(:zoo => zoo)
91
122
  end
92
123
  end
93
124
 
125
+ zoo = Zoo.first(:name => 'Greater Vancouver Zoo')
126
+
127
+ Animal.mammals.zoo(zoo).to_a # => executes one query
128
+
129
+ In the above example, we ask the Animal model for all the mammals,
130
+ and then all the animals in a specific zoo, and DataMapper will chain
131
+ the collection queries together and execute a single query to retrieve
132
+ the matching records. There's no special syntax, and no custom DSLs
133
+ to learn, it's just plain ruby all the way down.
134
+
135
+ You can even use this on association collections, eg:
136
+
137
+ zoo.animals.mammals.to_a # => executes one query
138
+
94
139
  == Plays Well With Others
95
140
 
96
141
  In ActiveRecord, all your fields are mapped, whether you want them or not.
97
142
  This slows things down. In the DataMapper you define your mappings in your
98
143
  model. So instead of an _ALTER TABLE ADD field_ in your data-store, you simply
99
- add a <tt>property :name, :string</tt> to your model. DRY. No schema.rb. No
144
+ add a <tt>property :name, String</tt> to your model. DRY. No schema.rb. No
100
145
  migration files to conflict or die without reverting changes. Your model
101
146
  drives the data-store, not the other way around.
102
147
 
@@ -107,7 +152,9 @@ now? In DataMapper you control the mappings:
107
152
 
108
153
  class Fruit
109
154
  include DataMapper::Resource
155
+
110
156
  storage_names[:repo] = 'frt'
157
+
111
158
  property :name, String, :field => 'col2Name'
112
159
  end
113
160
 
@@ -127,21 +174,18 @@ It's just a little thing, but it's so much nicer than writing
127
174
  <tt>Zoo.find(:all, :conditions => ['name = ?', 'Dallas'])</tt>. What if you
128
175
  need other comparisons though? Try these:
129
176
 
130
- Zoo.first(:name => 'Galveston')
131
-
132
177
  # 'gt' means greater-than. We also do 'lt'.
133
178
  Person.all(:age.gt => 30)
134
179
 
135
180
  # 'gte' means greather-than-or-equal-to. We also do 'lte'.
136
181
  Person.all(:age.gte => 30)
137
182
 
183
+ # 'not' allows you to match all people without the name "bob"
138
184
  Person.all(:name.not => 'bob')
139
185
 
140
186
  # If the value of a pair is an Array, we do an IN-clause for you.
141
187
  Person.all(:name.like => 'S%', :id => [ 1, 2, 3, 4, 5 ])
142
188
 
143
- Zoo.get(11)
144
-
145
189
  # Does a NOT IN () clause for you.
146
190
  Person.all(:name.not => [ 'bob', 'rick', 'steve' ])
147
191
 
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env ruby -KU
2
-
3
1
  require 'pathname'
4
2
  require 'rubygems'
5
3
  require 'rake'
data/deps.rip CHANGED
@@ -1,2 +1,2 @@
1
- git://github.com/datamapper/extlib.git next
1
+ git://github.com/datamapper/extlib.git master
2
2
  git://github.com/sporkmonger/addressable.git addressable-2.1.0
data/dm-core.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{dm-core}
5
- s.version = "0.10.0"
5
+ s.version = "0.10.1"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Dan Kubb"]
9
- s.date = %q{2009-07-30}
9
+ s.date = %q{2009-09-30}
10
10
  s.description = %q{Faster, Better, Simpler.}
11
11
  s.email = ["dan.kubb@gmail.com"]
12
12
  s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
13
- s.files = [".autotest", ".gitignore", "CONTRIBUTING", "FAQ", "History.txt", "MIT-LICENSE", "Manifest.txt", "QUICKLINKS", "README.txt", "Rakefile", "SPECS", "TODO", "dm-core.gemspec", "lib/dm-core.rb", "lib/dm-core/adapters.rb", "lib/dm-core/adapters/abstract_adapter.rb", "lib/dm-core/adapters/data_objects_adapter.rb", "lib/dm-core/adapters/in_memory_adapter.rb", "lib/dm-core/adapters/mysql_adapter.rb", "lib/dm-core/adapters/oracle_adapter.rb", "lib/dm-core/adapters/postgres_adapter.rb", "lib/dm-core/adapters/sqlite3_adapter.rb", "lib/dm-core/adapters/yaml_adapter.rb", "lib/dm-core/associations/many_to_many.rb", "lib/dm-core/associations/many_to_one.rb", "lib/dm-core/associations/one_to_many.rb", "lib/dm-core/associations/one_to_one.rb", "lib/dm-core/associations/relationship.rb", "lib/dm-core/collection.rb", "lib/dm-core/core_ext/kernel.rb", "lib/dm-core/core_ext/symbol.rb", "lib/dm-core/identity_map.rb", "lib/dm-core/migrations.rb", "lib/dm-core/model.rb", "lib/dm-core/model/descendant_set.rb", "lib/dm-core/model/hook.rb", "lib/dm-core/model/is.rb", "lib/dm-core/model/property.rb", "lib/dm-core/model/relationship.rb", "lib/dm-core/model/scope.rb", "lib/dm-core/property.rb", "lib/dm-core/property_set.rb", "lib/dm-core/query.rb", "lib/dm-core/query/conditions/comparison.rb", "lib/dm-core/query/conditions/operation.rb", "lib/dm-core/query/direction.rb", "lib/dm-core/query/operator.rb", "lib/dm-core/query/path.rb", "lib/dm-core/query/sort.rb", "lib/dm-core/repository.rb", "lib/dm-core/resource.rb", "lib/dm-core/spec/adapter_shared_spec.rb", "lib/dm-core/spec/data_objects_adapter_shared_spec.rb", "lib/dm-core/support/chainable.rb", "lib/dm-core/support/deprecate.rb", "lib/dm-core/support/logger.rb", "lib/dm-core/support/naming_conventions.rb", "lib/dm-core/transaction.rb", "lib/dm-core/type.rb", "lib/dm-core/types/boolean.rb", "lib/dm-core/types/discriminator.rb", "lib/dm-core/types/object.rb", "lib/dm-core/types/paranoid_boolean.rb", "lib/dm-core/types/paranoid_datetime.rb", "lib/dm-core/types/serial.rb", "lib/dm-core/types/text.rb", "lib/dm-core/version.rb", "script/performance.rb", "script/profile.rb", "spec/lib/adapter_helpers.rb", "spec/lib/collection_helpers.rb", "spec/lib/counter_adapter.rb", "spec/lib/pending_helpers.rb", "spec/lib/rspec_immediate_feedback_formatter.rb", "spec/public/associations/many_to_many_spec.rb", "spec/public/associations/many_to_one_spec.rb", "spec/public/associations/one_to_many_spec.rb", "spec/public/associations/one_to_one_spec.rb", "spec/public/collection_spec.rb", "spec/public/model/relationship_spec.rb", "spec/public/model_spec.rb", "spec/public/property_spec.rb", "spec/public/resource_spec.rb", "spec/public/sel_spec.rb", "spec/public/setup_spec.rb", "spec/public/shared/association_collection_shared_spec.rb", "spec/public/shared/collection_shared_spec.rb", "spec/public/shared/finder_shared_spec.rb", "spec/public/shared/resource_shared_spec.rb", "spec/public/shared/sel_shared_spec.rb", "spec/public/transaction_spec.rb", "spec/public/types/discriminator_spec.rb", "spec/semipublic/adapters/abstract_adapter_spec.rb", "spec/semipublic/adapters/in_memory_adapter_spec.rb", "spec/semipublic/adapters/mysql_adapter_spec.rb", "spec/semipublic/adapters/oracle_adapter_spec.rb", "spec/semipublic/adapters/postgres_adapter_spec.rb", "spec/semipublic/adapters/sqlite3_adapter_spec.rb", "spec/semipublic/adapters/yaml_adapter_spec.rb", "spec/semipublic/associations/many_to_one_spec.rb", "spec/semipublic/associations/relationship_spec.rb", "spec/semipublic/associations_spec.rb", "spec/semipublic/collection_spec.rb", "spec/semipublic/property_spec.rb", "spec/semipublic/query/conditions_spec.rb", "spec/semipublic/query/path_spec.rb", "spec/semipublic/query_spec.rb", "spec/semipublic/resource_spec.rb", "spec/semipublic/shared/resource_shared_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/ci.rb", "tasks/dm.rb", "tasks/doc.rb", "tasks/gemspec.rb", "tasks/hoe.rb", "tasks/install.rb"]
13
+ s.files = [".autotest", ".gitignore", "CONTRIBUTING", "FAQ", "History.txt", "MIT-LICENSE", "Manifest.txt", "QUICKLINKS", "README.txt", "Rakefile", "SPECS", "TODO", "deps.rip", "dm-core.gemspec", "lib/dm-core.rb", "lib/dm-core/adapters.rb", "lib/dm-core/adapters/abstract_adapter.rb", "lib/dm-core/adapters/data_objects_adapter.rb", "lib/dm-core/adapters/in_memory_adapter.rb", "lib/dm-core/adapters/mysql_adapter.rb", "lib/dm-core/adapters/oracle_adapter.rb", "lib/dm-core/adapters/postgres_adapter.rb", "lib/dm-core/adapters/sqlite3_adapter.rb", "lib/dm-core/adapters/yaml_adapter.rb", "lib/dm-core/associations/many_to_many.rb", "lib/dm-core/associations/many_to_one.rb", "lib/dm-core/associations/one_to_many.rb", "lib/dm-core/associations/one_to_one.rb", "lib/dm-core/associations/relationship.rb", "lib/dm-core/collection.rb", "lib/dm-core/core_ext/kernel.rb", "lib/dm-core/core_ext/symbol.rb", "lib/dm-core/identity_map.rb", "lib/dm-core/migrations.rb", "lib/dm-core/model.rb", "lib/dm-core/model/descendant_set.rb", "lib/dm-core/model/hook.rb", "lib/dm-core/model/is.rb", "lib/dm-core/model/property.rb", "lib/dm-core/model/relationship.rb", "lib/dm-core/model/scope.rb", "lib/dm-core/property.rb", "lib/dm-core/property_set.rb", "lib/dm-core/query.rb", "lib/dm-core/query/conditions/comparison.rb", "lib/dm-core/query/conditions/operation.rb", "lib/dm-core/query/direction.rb", "lib/dm-core/query/operator.rb", "lib/dm-core/query/path.rb", "lib/dm-core/query/sort.rb", "lib/dm-core/repository.rb", "lib/dm-core/resource.rb", "lib/dm-core/spec/adapter_shared_spec.rb", "lib/dm-core/spec/data_objects_adapter_shared_spec.rb", "lib/dm-core/support/chainable.rb", "lib/dm-core/support/deprecate.rb", "lib/dm-core/support/equalizer.rb", "lib/dm-core/support/logger.rb", "lib/dm-core/support/naming_conventions.rb", "lib/dm-core/transaction.rb", "lib/dm-core/type.rb", "lib/dm-core/types/boolean.rb", "lib/dm-core/types/discriminator.rb", "lib/dm-core/types/object.rb", "lib/dm-core/types/paranoid_boolean.rb", "lib/dm-core/types/paranoid_datetime.rb", "lib/dm-core/types/serial.rb", "lib/dm-core/types/text.rb", "lib/dm-core/version.rb", "script/performance.rb", "script/profile.rb", "spec/lib/adapter_helpers.rb", "spec/lib/collection_helpers.rb", "spec/lib/counter_adapter.rb", "spec/lib/pending_helpers.rb", "spec/lib/rspec_immediate_feedback_formatter.rb", "spec/public/associations/many_to_many_spec.rb", "spec/public/associations/many_to_one_spec.rb", "spec/public/associations/one_to_many_spec.rb", "spec/public/associations/one_to_one_spec.rb", "spec/public/collection_spec.rb", "spec/public/migrations_spec.rb", "spec/public/model/relationship_spec.rb", "spec/public/model_spec.rb", "spec/public/property_spec.rb", "spec/public/resource_spec.rb", "spec/public/sel_spec.rb", "spec/public/setup_spec.rb", "spec/public/shared/association_collection_shared_spec.rb", "spec/public/shared/collection_shared_spec.rb", "spec/public/shared/finder_shared_spec.rb", "spec/public/shared/resource_shared_spec.rb", "spec/public/shared/sel_shared_spec.rb", "spec/public/transaction_spec.rb", "spec/public/types/discriminator_spec.rb", "spec/semipublic/adapters/abstract_adapter_spec.rb", "spec/semipublic/adapters/in_memory_adapter_spec.rb", "spec/semipublic/adapters/mysql_adapter_spec.rb", "spec/semipublic/adapters/oracle_adapter_spec.rb", "spec/semipublic/adapters/postgres_adapter_spec.rb", "spec/semipublic/adapters/sqlite3_adapter_spec.rb", "spec/semipublic/adapters/yaml_adapter_spec.rb", "spec/semipublic/associations/many_to_one_spec.rb", "spec/semipublic/associations/relationship_spec.rb", "spec/semipublic/associations_spec.rb", "spec/semipublic/collection_spec.rb", "spec/semipublic/property_spec.rb", "spec/semipublic/query/conditions_spec.rb", "spec/semipublic/query/path_spec.rb", "spec/semipublic/query_spec.rb", "spec/semipublic/resource_spec.rb", "spec/semipublic/shared/condition_shared_spec.rb", "spec/semipublic/shared/resource_shared_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/ci.rb", "tasks/dm.rb", "tasks/doc.rb", "tasks/gemspec.rb", "tasks/hoe.rb", "tasks/install.rb"]
14
14
  s.homepage = %q{http://datamapper.org}
15
15
  s.rdoc_options = ["--main", "README.txt"]
16
16
  s.require_paths = ["lib"]
@@ -24,13 +24,13 @@ Gem::Specification.new do |s|
24
24
 
25
25
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
26
  s.add_runtime_dependency(%q<extlib>, ["~> 0.9.13"])
27
- s.add_runtime_dependency(%q<addressable>, ["~> 2.0"])
27
+ s.add_runtime_dependency(%q<addressable>, ["~> 2.1"])
28
28
  else
29
29
  s.add_dependency(%q<extlib>, ["~> 0.9.13"])
30
- s.add_dependency(%q<addressable>, ["~> 2.0"])
30
+ s.add_dependency(%q<addressable>, ["~> 2.1"])
31
31
  end
32
32
  else
33
33
  s.add_dependency(%q<extlib>, ["~> 0.9.13"])
34
- s.add_dependency(%q<addressable>, ["~> 2.0"])
34
+ s.add_dependency(%q<addressable>, ["~> 2.1"])
35
35
  end
36
36
  end
@@ -14,6 +14,9 @@ module DataMapper
14
14
  class AbstractAdapter
15
15
  include Extlib::Assertions
16
16
  extend Extlib::Assertions
17
+ extend Equalizer
18
+
19
+ equalize :name, :options, :resource_naming_convention, :field_naming_convention
17
20
 
18
21
  # Adapter name
19
22
  #
@@ -139,64 +142,6 @@ module DataMapper
139
142
  raise NotImplementedError, "#{self.class}#delete not implemented"
140
143
  end
141
144
 
142
- # Compares another AbstractAdapter for equality
143
- #
144
- # @example with an equal adapter
145
- # adapter.eql?(equal_adapter) # => true
146
- #
147
- # @example with a different adapter
148
- # adapter.eql?(different_adapter) # => false
149
- #
150
- # AbstractAdapter is equal to +other+ if they are the same object (identity)
151
- # or if they are of the same class and have the same name
152
- #
153
- # @param [AbstractAdapter] other
154
- # the other AbstractAdapter to compare with
155
- #
156
- # @return [Boolean]
157
- # true if they are equal, false if not
158
- #
159
- # @api public
160
- def eql?(other)
161
- if equal?(other)
162
- return true
163
- end
164
-
165
- unless instance_of?(other.class)
166
- return false
167
- end
168
-
169
- cmp?(other, :eql?)
170
- end
171
-
172
- # Compares another AbstractAdapter for equivalency
173
- #
174
- # @example with an equivalent adapter
175
- # adapter == equivalent_adapter # => true
176
- #
177
- # @example with a different adapter
178
- # adapter == different_adapter # => false
179
- #
180
- # AbstractAdapter is equal to +other+ if they are the same object (identity)
181
- # or if they both have the same name
182
- #
183
- # @param [AbstractAdapter] other
184
- # the other AbstractAdapter to compare with
185
- #
186
- # @return [Boolean]
187
- # true if they are equal, false if not
188
- #
189
- # @api public
190
- def ==(other)
191
- return true if equal?(other)
192
-
193
- other.respond_to?(:name) &&
194
- other.respond_to?(:options) &&
195
- other.respond_to?(:resource_naming_convention) &&
196
- other.respond_to?(:field_naming_convention) &&
197
- cmp?(other, :==)
198
- end
199
-
200
145
  protected
201
146
 
202
147
  # Set the serial value of the Resource
@@ -254,24 +199,6 @@ module DataMapper
254
199
  @resource_naming_convention = NamingConventions::Resource::UnderscoredAndPluralized
255
200
  @field_naming_convention = NamingConventions::Field::Underscored
256
201
  end
257
-
258
- # Compare other object for equality of equivalency
259
- #
260
- # @param [AbstractAdapter] other
261
- # the other adapter
262
- # @param [Symbol] operator
263
- # the comparison operator
264
- #
265
- # @return [Boolean]
266
- # true if the other object is equal or equivalent
267
- #
268
- # @api private
269
- def cmp?(other, operator)
270
- name.send(operator, other.name) &&
271
- options.send(operator, other.options) &&
272
- resource_naming_convention.send(operator, other.resource_naming_convention) &&
273
- field_naming_convention.send(operator, other.field_naming_convention)
274
- end
275
202
  end # class AbstractAdapter
276
203
 
277
204
  const_added(:AbstractAdapter)
@@ -312,54 +312,23 @@ module DataMapper
312
312
  #
313
313
  # @api private
314
314
  def select_statement(query)
315
- model = query.model
316
- fields = query.fields
317
- conditions = query.conditions
318
- limit = query.limit
319
- offset = query.offset
320
- order_by = query.order
321
- group_by = nil
322
-
323
- # FIXME: using a boolean for qualify does not work in some cases,
324
- # such as when you have a self-referrential many to many association.
325
- # if you don't qualfiy the columns with a unique alias, then the
326
- # SQL query will fail. This may mean though, that it might not
327
- # be enough to pass in a Property, but we may need to know the
328
- # table and the alias we should use for the column.
329
-
330
- qualify = query.links.any?
331
-
332
- if qualify || query.unique?
333
- group_by = fields.select { |property| property.kind_of?(Property) }
315
+ qualify = query.links.any?
316
+ fields = query.fields
317
+ order_by = query.order
318
+ group_by = if qualify || query.unique?
319
+ fields.select { |property| property.kind_of?(Property) }
334
320
  end
335
321
 
336
- unless (limit && limit > 1) || offset > 0 || qualify
337
- # TODO: move this method to Query, so that it walks the conditions
338
- # and finds an OR operator
339
-
340
- # TODO: handle cases where two or more properties need to be
341
- # used together to be unique
342
-
343
- # if a unique property is used, and there is no OR operator, then an ORDER
344
- # and LIMIT are unecessary because it should only return a single row
345
- if conditions.kind_of?(Query::Conditions::AndOperation) &&
346
- conditions.any? { |operand| operand.kind_of?(Query::Conditions::EqualToComparison) && operand.subject.respond_to?(:unique?) && operand.subject.unique? } &&
347
- !conditions.any? { |operand| operand.kind_of?(Query::Conditions::OrOperation) }
348
- order_by = nil
349
- limit = nil
350
- end
351
- end
352
-
353
- conditions_statement, bind_values = conditions_statement(conditions, qualify)
322
+ conditions_statement, bind_values = conditions_statement(query.conditions, qualify)
354
323
 
355
324
  statement = "SELECT #{columns_statement(fields, qualify)}"
356
- statement << " FROM #{quote_name(model.storage_name(name))}"
325
+ statement << " FROM #{quote_name(query.model.storage_name(name))}"
357
326
  statement << join_statement(query, qualify) if qualify
358
327
  statement << " WHERE #{conditions_statement}" unless conditions_statement.blank?
359
328
  statement << " GROUP BY #{columns_statement(group_by, qualify)}" if group_by && group_by.any?
360
329
  statement << " ORDER BY #{order_statement(order_by, qualify)}" if order_by && order_by.any?
361
330
 
362
- add_limit_offset!(statement, limit, offset, bind_values)
331
+ add_limit_offset!(statement, query.limit, query.offset, bind_values)
363
332
 
364
333
  return statement, bind_values
365
334
  end
@@ -343,6 +343,26 @@ module DataMapper
343
343
  super
344
344
  end
345
345
 
346
+ # Return the intermediaries between the source and the targets
347
+ #
348
+ # @return [Collection]
349
+ # the intermediary collection
350
+ #
351
+ # @api public
352
+ def intermediaries
353
+ return @intermediaries if @intermediaries
354
+
355
+ intermediaries = if through.loaded?(source)
356
+ through.get!(source)
357
+ else
358
+ through.set!(source, through.collection_for(source))
359
+ end
360
+
361
+ scoped = intermediaries.all(via => self)
362
+
363
+ @intermediaries = scoped.query == intermediaries.query ? intermediaries : scoped
364
+ end
365
+
346
366
  private
347
367
 
348
368
  # TODO: document
@@ -363,9 +383,13 @@ module DataMapper
363
383
  # TODO: document
364
384
  # @api private
365
385
  def _save(safe)
366
- # delete only intermediaries linked to the removed targets
367
- unless @removed.empty? || intermediaries(@removed).send(safe ? :destroy : :destroy!)
368
- return false
386
+ if @removed.any?
387
+ # delete only intermediaries linked to the removed targets
388
+ removed_intermediaries = intermediaries.all(via => @removed).each do |resource|
389
+ intermediaries.delete(resource)
390
+ end
391
+
392
+ return false unless removed_intermediaries.send(safe ? :destroy : :destroy!)
369
393
  end
370
394
 
371
395
  if via.respond_to?(:resource_for)
@@ -374,25 +398,13 @@ module DataMapper
374
398
  else
375
399
  if intermediary = create_intermediary(safe)
376
400
  inverse = via.inverse
377
- loaded_entries.map { |resource| inverse.set(resource, intermediary) }
401
+ loaded_entries.each { |resource| inverse.set(resource, intermediary) }
378
402
  end
379
403
 
380
404
  super
381
405
  end
382
406
  end
383
407
 
384
- # TODO: document
385
- # @api private
386
- def intermediaries(targets = self)
387
- intermediaries = if through.loaded?(source)
388
- through.get!(source)
389
- else
390
- through.set!(source, through.collection_for(source))
391
- end
392
-
393
- intermediaries.all(via => targets)
394
- end
395
-
396
408
  # TODO: document
397
409
  # @api private
398
410
  def create_intermediary(safe, attributes = {})
@@ -61,7 +61,7 @@ module DataMapper
61
61
  # @api semipublic
62
62
  alias source_key child_key
63
63
 
64
- # Returns a Resoruce for this relationship with a given source
64
+ # Returns a Resource for this relationship with a given source
65
65
  #
66
66
  # @param [Resource] source
67
67
  # A Resource to scope the collection with
@@ -123,13 +123,6 @@ module DataMapper
123
123
  set!(source, target)
124
124
  end
125
125
 
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
126
  private
134
127
 
135
128
  # Initializes the relationship, always using max cardinality of 1.
@@ -142,43 +135,6 @@ module DataMapper
142
135
  super
143
136
  end
144
137
 
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
163
- end
164
-
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
180
- end
181
-
182
138
  # Loads association target and sets resulting value on
183
139
  # given source resource
184
140
  #
@@ -82,13 +82,6 @@ module DataMapper
82
82
  get!(source).replace(targets)
83
83
  end
84
84
 
85
- # TODO: document
86
- # @api private
87
- def inherited_by(model)
88
- model.relationships(source_repository_name)[name] ||
89
- self.class.new(name, child_model_name, model, options_with_inverse)
90
- end
91
-
92
85
  private
93
86
 
94
87
  # TODO: document
@@ -99,36 +92,6 @@ module DataMapper
99
92
  super
100
93
  end
101
94
 
102
- # Dynamically defines reader method for source side of association
103
- # (for instance, method paragraphs for model Article)
104
- #
105
- # @api semipublic
106
- def create_reader
107
- return if source_model.resource_method_defined?(name.to_s)
108
-
109
- source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
110
- def #{name}(query = nil) # def paragraphs(query = nil)
111
- relationships[#{name.inspect}].get(self, query) # relationships[:paragraphs].get(self, query)
112
- end # end
113
- RUBY
114
- end
115
-
116
- # Dynamically defines reader method for source side of association
117
- # (for instance, method paragraphs= for model Article)
118
- #
119
- # @api semipublic
120
- def create_writer
121
- writer_name = "#{name}="
122
-
123
- return if source_model.resource_method_defined?(writer_name)
124
-
125
- source_model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
126
- def #{writer_name}(targets) # def paragraphs=(targets)
127
- relationships[#{name.inspect}].set(self, targets) # relationships[:paragraphs].set(self, targets)
128
- end # end
129
- RUBY
130
- end
131
-
132
95
  # Loads association targets and sets resulting value on
133
96
  # given source resource
134
97
  #
@@ -315,7 +278,7 @@ module DataMapper
315
278
  assert_source_saved 'The source must be saved before saving the collection'
316
279
 
317
280
  # update removed resources to not reference the source
318
- @removed.all? { |resource| resource.send(safe ? :save : :save!) } && super
281
+ @removed.all? { |resource| resource.destroyed? || resource.send(safe ? :save : :save!) } && super
319
282
  end
320
283
 
321
284
  # TODO: document