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.
- data/History.txt +25 -5
- data/Manifest.txt +1 -0
- data/README.txt +67 -23
- data/Rakefile +0 -2
- data/deps.rip +1 -1
- data/dm-core.gemspec +6 -6
- data/lib/dm-core/adapters/abstract_adapter.rb +3 -76
- data/lib/dm-core/adapters/data_objects_adapter.rb +8 -39
- data/lib/dm-core/associations/many_to_many.rb +28 -16
- data/lib/dm-core/associations/many_to_one.rb +1 -45
- data/lib/dm-core/associations/one_to_many.rb +1 -38
- data/lib/dm-core/associations/relationship.rb +43 -20
- data/lib/dm-core/collection.rb +33 -32
- data/lib/dm-core/model/property.rb +8 -8
- data/lib/dm-core/model/relationship.rb +10 -12
- data/lib/dm-core/property.rb +20 -85
- data/lib/dm-core/property_set.rb +8 -8
- data/lib/dm-core/query/conditions/comparison.rb +13 -71
- data/lib/dm-core/query/conditions/operation.rb +73 -47
- data/lib/dm-core/query/operator.rb +3 -45
- data/lib/dm-core/query/path.rb +5 -41
- data/lib/dm-core/query.rb +37 -108
- data/lib/dm-core/repository.rb +3 -79
- data/lib/dm-core/resource.rb +54 -49
- data/lib/dm-core/support/chainable.rb +0 -2
- data/lib/dm-core/support/equalizer.rb +23 -0
- data/lib/dm-core/types/object.rb +4 -4
- data/lib/dm-core/version.rb +1 -1
- data/lib/dm-core.rb +3 -11
- data/spec/public/model/relationship_spec.rb +4 -4
- data/spec/public/property_spec.rb +5 -449
- data/spec/public/sel_spec.rb +52 -2
- data/spec/public/shared/collection_shared_spec.rb +79 -26
- data/spec/public/shared/finder_shared_spec.rb +6 -6
- data/spec/public/shared/resource_shared_spec.rb +2 -2
- data/spec/semipublic/property_spec.rb +524 -9
- data/spec/semipublic/query_spec.rb +6 -6
- data/tasks/hoe.rb +2 -2
- metadata +24 -4
data/History.txt
CHANGED
@@ -1,7 +1,27 @@
|
|
1
|
-
=== 0.10.
|
1
|
+
=== 0.10.1 / 2009-09-30
|
2
2
|
|
3
|
-
|
3
|
+
* 4 minor enhancements:
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
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.
|
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
|
-
|
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
|
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
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
<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,
|
76
|
+
property :name, String
|
80
77
|
property :notes, Text, :lazy => false
|
81
78
|
end
|
82
79
|
|
83
|
-
Plus, lazy-loading of
|
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
|
-
|
89
|
-
|
90
|
-
|
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,
|
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
data/deps.rip
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
git://github.com/datamapper/extlib.git
|
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.
|
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-
|
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.
|
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.
|
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.
|
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
|
-
|
316
|
-
fields
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
-
|
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
|
-
|
367
|
-
|
368
|
-
|
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.
|
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
|
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
|