dm-core 0.10.0 → 0.10.1
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.
- 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
|