sequel 3.12.1 → 3.13.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +42 -0
- data/README.rdoc +137 -118
- data/Rakefile +21 -66
- data/doc/active_record.rdoc +9 -9
- data/doc/advanced_associations.rdoc +59 -188
- data/doc/association_basics.rdoc +15 -2
- data/doc/cheat_sheet.rdoc +38 -33
- data/doc/dataset_filtering.rdoc +16 -7
- data/doc/prepared_statements.rdoc +7 -7
- data/doc/querying.rdoc +5 -4
- data/doc/release_notes/3.13.0.txt +210 -0
- data/doc/sharding.rdoc +1 -1
- data/doc/sql.rdoc +5 -5
- data/doc/validations.rdoc +11 -11
- data/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/do.rb +3 -3
- data/lib/sequel/adapters/firebird.rb +3 -3
- data/lib/sequel/adapters/jdbc/h2.rb +39 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +5 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +3 -3
- data/lib/sequel/adapters/mysql.rb +7 -4
- data/lib/sequel/adapters/oracle.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +10 -1
- data/lib/sequel/adapters/shared/mysql.rb +63 -0
- data/lib/sequel/adapters/shared/postgres.rb +61 -3
- data/lib/sequel/adapters/sqlite.rb +105 -18
- data/lib/sequel/connection_pool.rb +31 -30
- data/lib/sequel/core.rb +58 -58
- data/lib/sequel/core_sql.rb +52 -43
- data/lib/sequel/database/misc.rb +11 -0
- data/lib/sequel/database/query.rb +55 -17
- data/lib/sequel/dataset/actions.rb +2 -1
- data/lib/sequel/dataset/query.rb +2 -3
- data/lib/sequel/dataset/sql.rb +24 -11
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/metaprogramming.rb +4 -0
- data/lib/sequel/model.rb +37 -19
- data/lib/sequel/model/associations.rb +33 -25
- data/lib/sequel/model/base.rb +2 -2
- data/lib/sequel/model/plugins.rb +7 -2
- data/lib/sequel/plugins/active_model.rb +1 -1
- data/lib/sequel/plugins/association_pks.rb +2 -2
- data/lib/sequel/plugins/association_proxies.rb +1 -1
- data/lib/sequel/plugins/boolean_readers.rb +2 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +10 -2
- data/lib/sequel/plugins/identity_map.rb +3 -3
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +212 -0
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/list.rb +174 -0
- data/lib/sequel/plugins/many_through_many.rb +2 -2
- data/lib/sequel/plugins/rcte_tree.rb +6 -7
- data/lib/sequel/plugins/tree.rb +118 -0
- data/lib/sequel/plugins/xml_serializer.rb +321 -0
- data/lib/sequel/sql.rb +315 -206
- data/lib/sequel/timezones.rb +40 -17
- data/lib/sequel/version.rb +8 -2
- data/spec/adapters/firebird_spec.rb +2 -2
- data/spec/adapters/informix_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +2 -2
- data/spec/adapters/mysql_spec.rb +2 -2
- data/spec/adapters/oracle_spec.rb +1 -1
- data/spec/adapters/postgres_spec.rb +36 -6
- data/spec/adapters/spec_helper.rb +2 -2
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +3 -3
- data/spec/core/core_sql_spec.rb +31 -13
- data/spec/core/database_spec.rb +39 -2
- data/spec/core/dataset_spec.rb +24 -12
- data/spec/core/expression_filters_spec.rb +5 -1
- data/spec/core/object_graph_spec.rb +1 -1
- data/spec/core/schema_generator_spec.rb +1 -1
- data/spec/core/schema_spec.rb +1 -1
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core/version_spec.rb +1 -1
- data/spec/extensions/active_model_spec.rb +82 -67
- data/spec/extensions/association_dependencies_spec.rb +1 -1
- data/spec/extensions/association_pks_spec.rb +1 -1
- data/spec/extensions/association_proxies_spec.rb +1 -1
- data/spec/extensions/blank_spec.rb +1 -1
- data/spec/extensions/boolean_readers_spec.rb +1 -1
- data/spec/extensions/caching_spec.rb +1 -1
- data/spec/extensions/class_table_inheritance_spec.rb +3 -2
- data/spec/extensions/composition_spec.rb +2 -5
- data/spec/extensions/force_encoding_spec.rb +3 -1
- data/spec/extensions/hook_class_methods_spec.rb +1 -1
- data/spec/extensions/identity_map_spec.rb +1 -1
- data/spec/extensions/inflector_spec.rb +1 -1
- data/spec/extensions/instance_filters_spec.rb +1 -1
- data/spec/extensions/instance_hooks_spec.rb +1 -1
- data/spec/extensions/json_serializer_spec.rb +154 -0
- data/spec/extensions/lazy_attributes_spec.rb +1 -2
- data/spec/extensions/list_spec.rb +251 -0
- data/spec/extensions/looser_typecasting_spec.rb +1 -1
- data/spec/extensions/many_through_many_spec.rb +3 -3
- data/spec/extensions/migration_spec.rb +1 -1
- data/spec/extensions/named_timezones_spec.rb +5 -6
- data/spec/extensions/nested_attributes_spec.rb +1 -1
- data/spec/extensions/optimistic_locking_spec.rb +1 -1
- data/spec/extensions/pagination_spec.rb +1 -1
- data/spec/extensions/pretty_table_spec.rb +1 -1
- data/spec/extensions/query_spec.rb +1 -1
- data/spec/extensions/rcte_tree_spec.rb +1 -1
- data/spec/extensions/schema_dumper_spec.rb +3 -2
- data/spec/extensions/schema_spec.rb +1 -1
- data/spec/extensions/serialization_spec.rb +6 -2
- data/spec/extensions/sharding_spec.rb +1 -1
- data/spec/extensions/single_table_inheritance_spec.rb +1 -1
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +7 -3
- data/spec/extensions/sql_expr_spec.rb +1 -1
- data/spec/extensions/string_date_time_spec.rb +1 -1
- data/spec/extensions/string_stripper_spec.rb +1 -1
- data/spec/extensions/subclasses_spec.rb +1 -1
- data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
- data/spec/extensions/thread_local_timezones_spec.rb +1 -1
- data/spec/extensions/timestamps_spec.rb +1 -1
- data/spec/extensions/touch_spec.rb +1 -1
- data/spec/extensions/tree_spec.rb +119 -0
- data/spec/extensions/typecast_on_load_spec.rb +1 -1
- data/spec/extensions/update_primary_key_spec.rb +1 -1
- data/spec/extensions/validation_class_methods_spec.rb +1 -1
- data/spec/extensions/validation_helpers_spec.rb +1 -1
- data/spec/extensions/xml_serializer_spec.rb +142 -0
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +29 -14
- data/spec/integration/eager_loader_test.rb +1 -1
- data/spec/integration/migrator_test.rb +1 -1
- data/spec/integration/model_test.rb +1 -1
- data/spec/integration/plugin_test.rb +316 -1
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/schema_test.rb +8 -8
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/timezone_test.rb +1 -1
- data/spec/integration/transaction_test.rb +35 -20
- data/spec/integration/type_test.rb +1 -1
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +49 -34
- data/spec/model/base_spec.rb +1 -1
- data/spec/model/dataset_methods_spec.rb +4 -4
- data/spec/model/eager_loading_spec.rb +1 -1
- data/spec/model/hooks_spec.rb +1 -1
- data/spec/model/inflector_spec.rb +1 -1
- data/spec/model/model_spec.rb +7 -1
- data/spec/model/plugins_spec.rb +1 -1
- data/spec/model/record_spec.rb +1 -3
- data/spec/model/spec_helper.rb +2 -2
- data/spec/model/validations_spec.rb +1 -1
- metadata +29 -5
data/Rakefile
CHANGED
@@ -89,82 +89,37 @@ end
|
|
89
89
|
begin
|
90
90
|
require "spec/rake/spectask"
|
91
91
|
|
92
|
-
|
93
|
-
lib_dir = File.join(File.dirname(__FILE__), 'lib')
|
92
|
+
spec = lambda do |name, files, d|
|
93
|
+
lib_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'lib')
|
94
94
|
ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{lib_dir}") : (ENV['RUBYLIB'] = lib_dir)
|
95
|
+
desc d
|
96
|
+
Spec::Rake::SpecTask.new(name) do |t|
|
97
|
+
t.spec_files = files
|
98
|
+
t.spec_opts = ENV['SEQUEL_SPEC_OPTS'].split if ENV['SEQUEL_SPEC_OPTS']
|
99
|
+
end
|
95
100
|
end
|
96
101
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
Spec::Rake::SpecTask.new("spec_coverage") do |t|
|
103
|
-
t.spec_files = Dir["spec/{core,model}/*_spec.rb"]
|
104
|
-
spec_opts.call
|
105
|
-
t.rcov, t.rcov_opts = rcov_opts.call
|
102
|
+
spec_with_cov = lambda do |name, files, d|
|
103
|
+
spec.call(name, files, d)
|
104
|
+
t = spec.call("#{name}_cov", files, "#{d} with coverage")
|
105
|
+
t.rcov = true
|
106
|
+
t.rcov_opts = File.read("spec/rcov.opts").split("\n")
|
106
107
|
end
|
107
108
|
|
108
|
-
desc "Run core and model specs"
|
109
109
|
task :default => [:spec]
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
desc "Run core specs"
|
116
|
-
Spec::Rake::SpecTask.new("spec_core") do |t|
|
117
|
-
t.spec_files = Dir["spec/core/*_spec.rb"]
|
118
|
-
spec_opts.call
|
119
|
-
end
|
120
|
-
|
121
|
-
desc "Run model specs"
|
122
|
-
Spec::Rake::SpecTask.new("spec_model") do |t|
|
123
|
-
t.spec_files = Dir["spec/model/*_spec.rb"]
|
124
|
-
spec_opts.call
|
125
|
-
end
|
126
|
-
|
127
|
-
desc "Run extension/plugin specs"
|
128
|
-
Spec::Rake::SpecTask.new("spec_plugin") do |t|
|
129
|
-
t.spec_files = Dir["spec/extensions/*_spec.rb"]
|
130
|
-
spec_opts.call
|
131
|
-
end
|
132
|
-
|
133
|
-
desc "Run extention/plugin specs with coverage"
|
134
|
-
Spec::Rake::SpecTask.new("spec_plugin_cov") do |t|
|
135
|
-
t.spec_files = Dir["spec/extensions/*_spec.rb"]
|
136
|
-
spec_opts.call
|
137
|
-
t.rcov, t.rcov_opts = rcov_opts.call
|
138
|
-
end
|
139
|
-
|
140
|
-
desc "Run integration tests"
|
141
|
-
Spec::Rake::SpecTask.new("integration") do |t|
|
142
|
-
t.spec_files = Dir["spec/integration/*_test.rb"]
|
143
|
-
spec_opts.call
|
144
|
-
end
|
145
|
-
|
146
|
-
desc "Run integration tests with coverage"
|
147
|
-
Spec::Rake::SpecTask.new("integration_cov") do |t|
|
148
|
-
t.spec_files = Dir["spec/integration/*_test.rb"]
|
149
|
-
spec_opts.call
|
150
|
-
t.rcov, t.rcov_opts = rcov_opts.call
|
151
|
-
end
|
110
|
+
spec_with_cov.call("spec", Dir["spec/{core,model}/*_spec.rb"], "Run core and model specs")
|
111
|
+
spec.call("spec_core", Dir["spec/core/*_spec.rb"], "Run core specs")
|
112
|
+
spec.call("spec_model", Dir["spec/model/*_spec.rb"], "Run model specs")
|
113
|
+
spec_with_cov.call("spec_plugin", Dir["spec/extensions/*_spec.rb"], "Run extension/plugin specs")
|
114
|
+
spec_with_cov.call("spec_integration", Dir["spec/integration/*_test.rb"], "Run integration tests")
|
152
115
|
|
153
116
|
%w'postgres sqlite mysql informix oracle firebird mssql'.each do |adapter|
|
154
|
-
|
155
|
-
Spec::Rake::SpecTask.new("spec_#{adapter}") do |t|
|
156
|
-
t.spec_files = ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"]
|
157
|
-
spec_opts.call
|
158
|
-
end
|
159
|
-
|
160
|
-
desc "Run #{adapter} specs with coverage"
|
161
|
-
Spec::Rake::SpecTask.new("spec_#{adapter}_cov") do |t|
|
162
|
-
t.spec_files = ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"]
|
163
|
-
spec_opts.call
|
164
|
-
t.rcov, t.rcov_opts = rcov_opts.call
|
165
|
-
end
|
117
|
+
spec_with_cov.call("spec_#{adapter}", ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"], "Run #{adapter} specs")
|
166
118
|
end
|
167
119
|
rescue LoadError
|
120
|
+
task :default do
|
121
|
+
puts "Must install rspec to run the default task (which runs specs)"
|
122
|
+
end
|
168
123
|
end
|
169
124
|
|
170
125
|
desc "check documentation coverage"
|
data/doc/active_record.rdoc
CHANGED
@@ -21,7 +21,7 @@ Sequel will autogenerate the column accessors, so you can do:
|
|
21
21
|
album = Album.new
|
22
22
|
album.name = 'RF'
|
23
23
|
|
24
|
-
If the table name for the class doesn't match the default one Sequel
|
24
|
+
If the table name for the class doesn't match the default one Sequel will choose, you can specify it manually:
|
25
25
|
|
26
26
|
class Album < Sequel::Model(:records)
|
27
27
|
end
|
@@ -61,7 +61,7 @@ Sequel's +validation_class_methods+ plugin is modeled directly on ActiveRecord's
|
|
61
61
|
|
62
62
|
== Hooks/Callbacks
|
63
63
|
|
64
|
-
Sequel's +hook_class_methods+ plugin is modeled directly on ActiveRecord's callbacks, but the recommended approach is to define
|
64
|
+
Sequel's +hook_class_methods+ plugin is modeled directly on ActiveRecord's callbacks, but the recommended approach is to define your hooks/callbacks as instance methods:
|
65
65
|
|
66
66
|
class Album < Sequel::Model
|
67
67
|
def before_create
|
@@ -98,7 +98,7 @@ Sequel supports both single table inheritance and class table inheritance using
|
|
98
98
|
|
99
99
|
== Transactions
|
100
100
|
|
101
|
-
Sequel supports transactions via the Database object (
|
101
|
+
Sequel supports transactions via the Database object (we recommend using the DB constant for single database applications):
|
102
102
|
|
103
103
|
DB.transaction do
|
104
104
|
album.artist.num_albums -= 1
|
@@ -132,7 +132,7 @@ Just like ActiveRecord, Sequel doesn't use sessions, it lets you modify objects
|
|
132
132
|
|
133
133
|
== Database Abstraction
|
134
134
|
|
135
|
-
Sequel supports far more database
|
135
|
+
Sequel supports far more database abstractions than ActiveRecord, and setting up the database connection is easy:
|
136
136
|
|
137
137
|
DB = Sequel.sqlite # memory database
|
138
138
|
DB = Sequel.connect('postgres://user:pass@host/database') # connection string
|
@@ -218,7 +218,7 @@ This is because LIKE is case sensitive on PostgreSQL, but case insensitive on My
|
|
218
218
|
|
219
219
|
This will do a case insensitive search on both databases. If you want a case sensitive search on both, you can use +like+ instead of +ilike+.
|
220
220
|
|
221
|
-
String
|
221
|
+
String concatenation is a similar area, where MySQL and PostgreSQL handle things differently. With Sequel, the same code can work on both databases:
|
222
222
|
|
223
223
|
Album.select(:name.sql_string + ' - Name')
|
224
224
|
|
@@ -302,7 +302,7 @@ Sequel doesn't have a <tt>has_many :through</tt> association, instead you can us
|
|
302
302
|
|
303
303
|
Sequel doesn't come with support for polymorphic associations. Using polymorphic associations is generally bad from a database design perspective, as it violates referential integrity. If you have an old database and must have polymorphic associations, there is an external +sequel_polymorphic+ plugin that can handle them, just by using the standard association options provided by Sequel.
|
304
304
|
|
305
|
-
Sequel doesn't directly support creating a bunch of associated objects and delaying saving them to the database
|
305
|
+
Sequel doesn't directly support creating a bunch of associated objects and delaying saving them to the database until the main object is saved, like you can with the <tt>association.build</tt> methods in ActiveRecord. You can use +before_save or +after_save+ hooks, or the +nested_attributes+ or +instance_hooks+ plugins to get similar support.
|
306
306
|
|
307
307
|
Sequel supports the same basic association hooks/callbacks as ActiveRecord. It also supports <tt>:after_load</tt>, which is run after the associated objects are loaded. For <tt>*_to_one</tt> associations, it supports +before_set+ and +after_set+ hooks, since a setter method is used instead of an add/remove method pair.
|
308
308
|
|
@@ -442,7 +442,7 @@ This part of the guide will list Sequel equivalents for ActiveRecord methods, ho
|
|
442
442
|
|
443
443
|
==== +abstract_class+, <tt>abstract_class=</tt>, <tt>abstract_class?</tt>
|
444
444
|
|
445
|
-
With Sequel, these methods don't exist because it
|
445
|
+
With Sequel, these methods don't exist because it doesn't default to using single table inheritance in subclasses. ActiveRecord assumes that subclasses of Model classes use single table inheritance, and you have to set <tt>abstract_class = true</tt> to use an abstract class. In Sequel, you must use the +single_table_inheritance+ or +class_tabble_inheritance+ plugin to configure inheritance in the database.
|
446
446
|
|
447
447
|
==== +all+
|
448
448
|
|
@@ -479,7 +479,7 @@ Note that +test_connection+ will return true if a connection can be made, but wi
|
|
479
479
|
|
480
480
|
==== +connection+
|
481
481
|
|
482
|
-
Sequel only uses connections for the minimum
|
482
|
+
Sequel only uses connections for the minimum amount of time necessary, checking them out to do a query, and returning them as soon as the query finishes. If you do want direct access to the connection object:
|
483
483
|
|
484
484
|
Sequel::Model.db.synchronize do |connection|
|
485
485
|
...
|
@@ -667,7 +667,7 @@ Note that +update+ in that case will operate on a dataset, so it won't run model
|
|
667
667
|
|
668
668
|
==== +with_scope+
|
669
669
|
|
670
|
-
Sequel works a little differently than with_scope. Instead of using nested blocks, you just use a cleaner method chain. +with_scope+ is often used
|
670
|
+
Sequel works a little differently than with_scope. Instead of using nested blocks, you just use a cleaner method chain. +with_scope+ is often used as an around_filter or similar construct, where in Sequel, you would just need to assign to a dataset in a before filter, and use that dataset in the action.
|
671
671
|
|
672
672
|
=== Class Methods with Roughly the Same Behavior
|
673
673
|
|
@@ -2,43 +2,41 @@
|
|
2
2
|
|
3
3
|
Sequel::Model has the most powerful and flexible associations of any ruby ORM.
|
4
4
|
|
5
|
-
"Extraordinary claims require extraordinary proof" - Carl Sagan
|
6
|
-
|
7
5
|
== Background: Sequel::Model association options
|
8
6
|
|
9
7
|
There are a bunch of advanced association options that are available to
|
10
|
-
handle
|
8
|
+
handle more complex cases. First we'll go over some of
|
11
9
|
the simpler ones:
|
12
10
|
|
13
11
|
All associations take a block that can be used to further filter/modify the
|
14
12
|
default dataset. There's also an :eager_block option if you want to use
|
15
|
-
a different block when eager loading via Dataset#eager
|
13
|
+
a different block when eager loading via <tt>Dataset#eager</tt>. Association blocks are
|
16
14
|
useful for things like:
|
17
15
|
|
18
16
|
Artist.one_to_many :gold_albums, :class=>:Album do |ds|
|
19
|
-
ds.filter{
|
17
|
+
ds.filter{copies_sold > 500000}
|
20
18
|
end
|
21
19
|
|
22
20
|
There are a whole bunch of options for changing how the association is eagerly
|
23
|
-
loaded via Dataset#eager_graph
|
24
|
-
|
21
|
+
loaded via <tt>Dataset#eager_graph</tt>: <tt>:graph_block</tt>, <tt>:graph_conditions</tt>,
|
22
|
+
<tt>:graph_only_conditions</tt>, <tt>:graph_join_type</tt> (and <tt>:graph_join_table_*</tt> ones for
|
25
23
|
JOINing to the join table in a many_to_many association).
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
25
|
+
:graph_join_type :: The type of join to do (<tt>:inner</tt>, <tt>:left</tt>, <tt>:right</tt>)
|
26
|
+
:graph_conditions :: Additional conditions to put on join (needs to be a
|
27
|
+
hash or array of all two pairs). Automatically assumes unqualified symbols
|
28
|
+
or first element of the pair to be columns of the associated model, and
|
29
|
+
unqualified symbols of the second element of the pair to be columns of the
|
30
|
+
current model.
|
31
|
+
:graph_block :: A block passed to +join_table+, allowing you to specify
|
32
|
+
conditions other than equality, or to use OR, or set up any arbitrary
|
33
|
+
condition. The block is passed the associated table alias, current table
|
34
|
+
alias, and an array of previous joins clause objects.
|
35
|
+
:graph_only_conditions :: Use these conditions instead of the standard
|
36
|
+
association conditions. This is necessary when you don't want to have an
|
37
|
+
equal condition between the foreign key and primary key of the tables.
|
38
|
+
You can also use this to have a JOIN USING (array of symbols), or a NATURAL
|
39
|
+
or CROSS JOIN (nil, with the appropriate <tt>:graph_join_type</tt>).
|
42
40
|
|
43
41
|
These can be used like this:
|
44
42
|
|
@@ -54,7 +52,7 @@ These can be used like this:
|
|
54
52
|
Artist.one_to_many :gold_albums, :class=>:Album, \
|
55
53
|
:graph_block=>proc{|j,lj,js| :copies_sold.qualify(j) > 500000}
|
56
54
|
|
57
|
-
# Handles the case where the
|
55
|
+
# Handles the case where the tables are associated by a case insensitive name string
|
58
56
|
Artist.one_to_many :albums, :key=>:artist_name, \
|
59
57
|
:graph_only_conditions=>nil, \
|
60
58
|
:graph_block=>proc{|j,lj,js| {:lower.sql_function(artist_name.qualify(j))=>:lower.sql_function(name.qualify(lj))}}
|
@@ -63,15 +61,15 @@ These can be used like this:
|
|
63
61
|
# a JOIN USING
|
64
62
|
Artist.one_to_many :albums, :key=>:artist_name, :graph_only_conditions=>[:artist_name]
|
65
63
|
|
66
|
-
Remember, using
|
64
|
+
Remember, using +eager_graph+ is generally only necessary when you need to
|
67
65
|
filter/order based on columns in an associated table, it is recommended to
|
68
|
-
use
|
66
|
+
use +eager+ for eager loading if possible.
|
69
67
|
|
70
|
-
For lazy loading (e.g. Model[1].association), the
|
68
|
+
For lazy loading (e.g. Model[1].association), the <tt>:dataset</tt> option can be used
|
71
69
|
to specify an arbitrary dataset (one that uses different keys, multiple keys,
|
72
70
|
joins to other tables, etc.).
|
73
71
|
|
74
|
-
For eager loading via
|
72
|
+
For eager loading via +eager+, the <tt>:eager_loader</tt> option can be used to specify
|
75
73
|
how to eagerly load a complex association. This is an extremely powerful
|
76
74
|
option. Though it can often be verbose (compared to other things in Sequel),
|
77
75
|
it allows you complete control over how to eagerly load associations for a
|
@@ -94,13 +92,13 @@ for new code.
|
|
94
92
|
Since you are given all of the records, you can do things like filter on
|
95
93
|
associations that are specified by multiple keys, or do multiple
|
96
94
|
queries depending on the content of the records (which would be
|
97
|
-
necessary for polymorphic associations). Inside the
|
95
|
+
necessary for polymorphic associations). Inside the <tt>:eager_loader</tt>
|
98
96
|
proc, you should get the related objects and populate the
|
99
97
|
associations cache for all objects in the array of records. The hash
|
100
98
|
of dependent associations is available for you to cascade the eager
|
101
99
|
loading down multiple levels, but it is up to you to use it. The
|
102
100
|
key_hash is a performance enhancement that is used by the default
|
103
|
-
|
101
|
+
association loaders and is also available to you. It is a hash with keys being
|
104
102
|
foreign/primary key symbols in the current table, and the values
|
105
103
|
being hashes where the key is foreign/primary key values and values
|
106
104
|
being arrays of current model objects having the foreign/primary key
|
@@ -115,38 +113,36 @@ give an example:
|
|
115
113
|
# The key_hash provided to the :eager_loader proc would be:
|
116
114
|
{:id=>{1=>[album1], 3=>[album2]}, :artist_id=>{2=>[album1, album2]}}
|
117
115
|
|
118
|
-
Using these options, you can build associations Sequel doesn't natively support,
|
116
|
+
Using these options, you can build associations that Sequel doesn't natively support,
|
119
117
|
and still be able to use the same eager loading features that you are used to.
|
120
118
|
|
121
119
|
== ActiveRecord associations
|
122
120
|
|
123
|
-
Sequel supports all of associations that ActiveRecord supports,
|
124
|
-
|
125
|
-
a swiss army chainsaw.
|
121
|
+
Sequel supports all of associations that ActiveRecord supports, though some
|
122
|
+
require different approaches or custom <tt>:eager_loader</tt> options.
|
126
123
|
|
127
124
|
=== Association callbacks
|
128
125
|
|
129
|
-
Sequel supports the same callbacks that ActiveRecord does on one_to_many and
|
130
|
-
many_to_many associations:
|
131
|
-
|
132
|
-
|
133
|
-
supports
|
134
|
-
Sequel supports :after_load, which is called after the association has been
|
126
|
+
Sequel supports the same callbacks that ActiveRecord does on +one_to_many+ and
|
127
|
+
+many_to_many+ associations: <tt>:before_add</tt>, <tt>:before_remove</tt>, <tt>:after_add</tt>, and
|
128
|
+
<tt>:after_remove</tt>. One +many_to_one+ associations and +one_to_one+ associations, Sequel
|
129
|
+
supports the <tt>:before_set</tt> and <tt>:after_set</tt> callbacks. On all associations,
|
130
|
+
Sequel supports <tt>:after_load</tt>, which is called after the association has been
|
135
131
|
loaded.
|
136
132
|
|
137
|
-
Each of these options can be a
|
138
|
-
that takes one argument (the associated object), or a
|
133
|
+
Each of these options can be a symbol specifying an instance method
|
134
|
+
that takes one argument (the associated object), or a proc that takes
|
139
135
|
two arguments (the current object and the associated object), or an
|
140
|
-
array of
|
136
|
+
array of symbols and procs. For <tt>:after_load</tt> with a *_to_many association,
|
141
137
|
the associated object argument is an array of associated objects.
|
142
138
|
|
143
|
-
If any of the before callbacks return false
|
144
|
-
does not happen and it either raises
|
145
|
-
returns
|
139
|
+
If any of the before callbacks return +false+, the adding/removing
|
140
|
+
does not happen and it either raises a <tt>Sequel::BeforeHookFailed</tt> (the default), or
|
141
|
+
returns false (if +raise_on_save_failure+ is false).
|
146
142
|
|
147
143
|
=== Association extensions
|
148
144
|
|
149
|
-
All associations come with an
|
145
|
+
All associations come with an <tt><i>association</i>_dataset</tt> method that can be further filtered or
|
150
146
|
otherwise modified:
|
151
147
|
|
152
148
|
class Author < Sequel::Model
|
@@ -154,10 +150,10 @@ otherwise modified:
|
|
154
150
|
end
|
155
151
|
Author.first.authorships_dataset.filter{|o| o.number < 10}.first
|
156
152
|
|
157
|
-
You can extend a dataset with a module
|
153
|
+
You can extend a dataset with a module using the <tt>:extend</tt> association option. You can reference
|
158
154
|
the model object that created the association dataset via the dataset's
|
159
|
-
model_object method, and the related association reflection via the dataset's
|
160
|
-
association_reflection method:
|
155
|
+
+model_object+ method, and the related association reflection via the dataset's
|
156
|
+
+association_reflection+ method:
|
161
157
|
|
162
158
|
module FindOrCreate
|
163
159
|
def find_or_create(vals)
|
@@ -169,11 +165,11 @@ association_reflection method:
|
|
169
165
|
end
|
170
166
|
Author.first.authorships_dataset.find_or_create(:name=>'Blah', :number=>10)
|
171
167
|
|
172
|
-
=== has_many :through associations
|
168
|
+
=== <tt>has_many :through</tt> associations
|
173
169
|
|
174
|
-
many_to_many handles the usual case of a has_many :through with a belongs_to in
|
170
|
+
+many_to_many+ handles the usual case of a <tt>has_many :through</tt> with a +belongs_to+ in
|
175
171
|
the associated model. It doesn't break on the case where the join table is a
|
176
|
-
model table, unlike ActiveRecord's has_and_belongs_to_many
|
172
|
+
model table, unlike ActiveRecord's +has_and_belongs_to_many+.
|
177
173
|
|
178
174
|
ActiveRecord:
|
179
175
|
|
@@ -205,8 +201,8 @@ Sequel::Model:
|
|
205
201
|
@author = Author.first
|
206
202
|
@author.books
|
207
203
|
|
208
|
-
If you use an association other than belongs_to in the associated model,
|
209
|
-
|
204
|
+
If you use an association other than +belongs_to+ in the associated model, such as a +has_many+,
|
205
|
+
you still use a +many_to_many+ association, but you need to use some options:
|
210
206
|
|
211
207
|
ActiveRecord:
|
212
208
|
|
@@ -241,10 +237,6 @@ Sequel::Model:
|
|
241
237
|
|
242
238
|
class Invoice < Sequel::Model
|
243
239
|
many_to_one :client
|
244
|
-
|
245
|
-
def firm
|
246
|
-
client.firm if client
|
247
|
-
end
|
248
240
|
end
|
249
241
|
|
250
242
|
Firm.first.invoices
|
@@ -262,7 +254,8 @@ you are stuck with an existing design that uses them.
|
|
262
254
|
|
263
255
|
If you must use them, look for the sequel_polymorphic plugin, as it makes using
|
264
256
|
polymorphic associations in Sequel about as easy as it is in ActiveRecord. However,
|
265
|
-
here's how they can be done using Sequel's custom associations
|
257
|
+
here's how they can be done using Sequel's custom associations (the sequel_polymorphic
|
258
|
+
plugin is just a generic version of this code):
|
266
259
|
|
267
260
|
ActiveRecord:
|
268
261
|
|
@@ -362,75 +355,12 @@ Sequel::Model:
|
|
362
355
|
@asset.attachable = @post
|
363
356
|
@asset.attachable = @note
|
364
357
|
|
365
|
-
==
|
366
|
-
|
367
|
-
So far, we've only shown that Sequel::Model has associations as powerful as
|
368
|
-
ActiveRecord's. Now we will show how Sequel::Model's associations are more
|
369
|
-
powerful.
|
370
|
-
|
371
|
-
=== many_to_one/one_to_many not referencing primary key
|
372
|
-
|
373
|
-
This can now be handled easily in Sequel using the :primary_key association
|
374
|
-
option. However, this example shows how the association was possible before
|
375
|
-
the introduction of that option.
|
376
|
-
|
377
|
-
Let's say you have two tables, invoices and clients, where each client is
|
378
|
-
associated with many invoices. However, instead of using the client's primary
|
379
|
-
key, the invoice is associated to the client by name (this is bad database
|
380
|
-
design, but sometimes you have to play with the cards you are dealt).
|
381
|
-
|
382
|
-
class Client < Sequel::Model
|
383
|
-
one_to_many :invoices, :reciprocal=>:client, \
|
384
|
-
:dataset=>proc{Invoice.filter(:client_name=>name)}, \
|
385
|
-
:eager_loader=>(proc do |eo|
|
386
|
-
id_map = {}
|
387
|
-
eo[:rows].each do |client|
|
388
|
-
id_map[client.name] = client
|
389
|
-
client.associations[:invoices] = []
|
390
|
-
end
|
391
|
-
Invoice.filter(:client_name=>id_map.keys.sort).all do |inv|
|
392
|
-
inv.associations[:client] = client = id_map[inv.client_name]
|
393
|
-
client.associations[:invoices] << inv
|
394
|
-
end
|
395
|
-
end)
|
396
|
-
|
397
|
-
private
|
398
|
-
|
399
|
-
def _add_invoice(invoice)
|
400
|
-
invoice.client_name = name
|
401
|
-
invoice.save
|
402
|
-
end
|
403
|
-
def _remove_invoice(invoice)
|
404
|
-
invoice.client_name = nil
|
405
|
-
invoice.save
|
406
|
-
end
|
407
|
-
def _remove_all_invoices
|
408
|
-
Invoice.filter(:client_name=>name).update(:client_name=>nil)
|
409
|
-
end
|
410
|
-
end
|
411
|
-
|
412
|
-
class Invoice < Sequel::Model
|
413
|
-
many_to_one :client, :key=>:client_name, \
|
414
|
-
:dataset=>proc{Client.filter(:name=>client_name)}, \
|
415
|
-
:eager_loader=>(proc do |eo|
|
416
|
-
id_map = eo[:key_hash][:client_name]
|
417
|
-
eo[:rows].each{|inv| inv.associations[:client] = nil}
|
418
|
-
Client.filter(:name=>id_map.keys).all do |client|
|
419
|
-
id_map[client.name].each{|inv| inv.associations[:client] = client}
|
420
|
-
end
|
421
|
-
end)
|
422
|
-
|
423
|
-
private
|
424
|
-
|
425
|
-
def _client=(client)
|
426
|
-
self.client_name = (client.name if client)
|
427
|
-
end
|
428
|
-
end
|
358
|
+
== Other advanced associations
|
429
359
|
|
430
360
|
=== Joining on multiple keys
|
431
361
|
|
432
362
|
Let's say you have two tables that are associated with each other with multiple
|
433
|
-
keys. This can
|
363
|
+
keys. This can be handled using Sequel's built in composite key support for
|
434
364
|
associations:
|
435
365
|
|
436
366
|
# Both of these models have an album_id, number, and disc_number fields.
|
@@ -441,42 +371,7 @@ associations:
|
|
441
371
|
many_to_one :favorite_track, :key=>[:disc_number, :number, :album_id], :primary_key=>[:disc_number, :number, :album_id]
|
442
372
|
end
|
443
373
|
class FavoriteTrack < Sequel::Model
|
444
|
-
|
445
|
-
end
|
446
|
-
|
447
|
-
Here's the old way to do it via custom associations:
|
448
|
-
|
449
|
-
class Track < Sequel::Model
|
450
|
-
many_to_one :favorite_track, \
|
451
|
-
:dataset=>(proc do
|
452
|
-
FavoriteTrack.filter(:disc_number=>disc_number, :number=>number, :album_id=>album_id)
|
453
|
-
end), \
|
454
|
-
:eager_loader=>(proc do |eo|
|
455
|
-
id_map = {}
|
456
|
-
eo[:rows].each do |t|
|
457
|
-
t.associations[:favorite_track] = nil
|
458
|
-
id_map[[t[:album_id], t[:disc_number], t[:number]]] = t
|
459
|
-
end
|
460
|
-
FavoriteTrack.filter([:album_id, :disc_number, :number]=>id_map.keys).all do |ft|
|
461
|
-
if t = id_map[[ft[:album_id], ft[:disc_number], ft[:number]]]
|
462
|
-
t.associations[:favorite_track] = ft
|
463
|
-
end
|
464
|
-
end
|
465
|
-
end)
|
466
|
-
end
|
467
|
-
|
468
|
-
class FavoriteTrack < Sequel::Model
|
469
|
-
many_to_one :track, \
|
470
|
-
:dataset=>(proc do
|
471
|
-
Track.filter(:disc_number=>disc_number, :number=>number, :album_id=>album_id)
|
472
|
-
end), \
|
473
|
-
:eager_loader=>(proc do |eo|
|
474
|
-
id_map = {}
|
475
|
-
eo[:rows].each{|ft| id_map[[ft[:album_id], ft[:disc_number], ft[:number]]] = ft}
|
476
|
-
Track.filter([:album_id, :disc_number, :number]=>id_map.keys).all do |t|
|
477
|
-
id_map[[t[:album_id], t[:disc_number], t[:number]]].associations[:track] = t
|
478
|
-
end
|
479
|
-
end)
|
374
|
+
one_to_one :tracks, :key=>[:disc_number, :number, :album_id], :primary_key=>[:disc_number, :number, :album_id]
|
480
375
|
end
|
481
376
|
|
482
377
|
=== Tree - All Ancestors and Descendents
|
@@ -552,37 +447,13 @@ without knowing the depth of the tree?
|
|
552
447
|
Note that unlike ActiveRecord, Sequel supports common table expressions, which allows you to use recursive queries.
|
553
448
|
The results are not the same as in the above case, as all descendents are stored in a single association,
|
554
449
|
but all descendants can be both lazy loaded or eager loaded in a single query (assuming your database
|
555
|
-
supports recursive common table expressions)
|
450
|
+
supports recursive common table expressions). Sequel ships with an +rcte_tree+ plugin that makes
|
451
|
+
this easy:
|
556
452
|
|
557
453
|
class Node < Sequel::Model
|
558
|
-
|
559
|
-
Node.from(:t).
|
560
|
-
with_recursive(:t, Node.filter(:parent_id=>pk),
|
561
|
-
Node.join(:t, :id=>:parent_id).
|
562
|
-
select(:nodes.*))
|
563
|
-
end),
|
564
|
-
:eager_loader=>(proc do |eo|
|
565
|
-
id_map = eo[:key_hash][:id]
|
566
|
-
eo[:rows].each{|n| n.associations[:descendants] = []}
|
567
|
-
Node.from(:t).
|
568
|
-
with_recursive(:t, Node.filter(:parent_id=>id_map.keys).
|
569
|
-
select(:parent_id___root, :id, :parent_id),
|
570
|
-
Node.join(:t, :id=>:parent_id).
|
571
|
-
select(:t__root, :nodes.*)).
|
572
|
-
all.each do |node|
|
573
|
-
if root = id_map[node.values.delete(:root)].first
|
574
|
-
root.associations[:descendants] << node
|
575
|
-
end
|
576
|
-
end
|
577
|
-
end)
|
454
|
+
plugin :rcte_tree
|
578
455
|
end
|
579
456
|
|
580
|
-
Sequel ships with an +rcte_tree+ plugin that allows simple creation
|
581
|
-
of ancestors and descendants relationships that use recursive common
|
582
|
-
table expressions:
|
583
|
-
|
584
|
-
Node.plugin :rcte_tree
|
585
|
-
|
586
457
|
=== Joining multiple keys to a single key, through a third table
|
587
458
|
|
588
459
|
Let's say you have a database, of songs, lyrics, and artists. Each song
|
@@ -654,6 +525,6 @@ associated tickets.
|
|
654
525
|
end
|
655
526
|
|
656
527
|
Note that it is often better to use a sum cache instead of this approach. You can implement
|
657
|
-
a sum cache using after_create and after_delete hooks, or using a database trigger
|
528
|
+
a sum cache using +after_create+ and +after_delete+ hooks, or using a database trigger
|
658
529
|
(the preferred method if you only have to support one database and that database supports
|
659
530
|
triggers).
|