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/CHANGELOG
CHANGED
@@ -1,3 +1,45 @@
|
|
1
|
+
=== 3.13.0 (2010-07-01)
|
2
|
+
|
3
|
+
* Allow Model.find_or_create to take a block which is yielded the object to be created, if no object is found (zaius, jeremyevans)
|
4
|
+
|
5
|
+
* Make PlaceholderLiteralString a GenericExpression subclass (jeremyevans)
|
6
|
+
|
7
|
+
* Allow nil/NULL to be used as a CASE expression value (jeremyevans)
|
8
|
+
|
9
|
+
* Support bitwise operators on more databases (jeremyevans)
|
10
|
+
|
11
|
+
* Make PostgreSQL do bitwise xor instead of exponentiation for ^ operator (jeremyevans)
|
12
|
+
|
13
|
+
* Fix handling of tinyint(1) columns when connecting to MySQL via JDBC (jeremyevans)
|
14
|
+
|
15
|
+
* Handle arrays of two element arrays as filter hash values automatically (jeremyevans)
|
16
|
+
|
17
|
+
* Allow :frame option for windows to take a string that is used literally (jeremyevans)
|
18
|
+
|
19
|
+
* Support transaction isolation levels on PostgreSQL, MySQL, and MSSQL (jeremyevans)
|
20
|
+
|
21
|
+
* Support prepared transactions/two-phase commit on PostgreSQL, MySQL, and H2 (jeremyevans)
|
22
|
+
|
23
|
+
* Allow NULLS FIRST/LAST when ordering using the :nulls=>:first/:last option to asc and desc (jeremyevans)
|
24
|
+
|
25
|
+
* On PostgreSQL, if no :schema option is provided for #tables, #table_exists?, or #schema, assume all schemas except the default non-public ones (jeremyevans) (#305)
|
26
|
+
|
27
|
+
* Cache prepared statements when using the native sqlite driver, improving performance (jeremyevans)
|
28
|
+
|
29
|
+
* Add a Tree plugin for treating model objects as being part of a tree (jeremyevans, mwlang)
|
30
|
+
|
31
|
+
* Add a :methods_module association option, for choosing the module into which association methods are placed (jeremyevans)
|
32
|
+
|
33
|
+
* Add a List plugin for treating model objects as being part of a list (jeremyevans, aemadrid)
|
34
|
+
|
35
|
+
* Don't attempt to use class polymorphism in the class_table_inheritance plugin if no cti_key is defined (jeremyevans)
|
36
|
+
|
37
|
+
* Add a XmlSerializer plugin for serializing/deserializing model objects to/from XML (jeremyevans)
|
38
|
+
|
39
|
+
* Add a JsonSerializer plugin for serializing/deserializing model objects to/from JSON (jeremyevans)
|
40
|
+
|
41
|
+
* Handle unsigned integers in the schema dumper (jeremyevans)
|
42
|
+
|
1
43
|
=== 3.12.1 (2010-06-09)
|
2
44
|
|
3
45
|
* Make :encoding option work on MySQL even if config file specifies different encoding (jeremyevans) (#300)
|
data/README.rdoc
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
== Sequel: The Database Toolkit for Ruby
|
2
2
|
|
3
|
-
Sequel is a
|
3
|
+
Sequel is a simple, flexible, and powerful SQL database access
|
4
|
+
toolkit for Ruby.
|
4
5
|
|
5
|
-
* Sequel provides thread safety, connection pooling and a concise
|
6
|
-
for constructing
|
7
|
-
* Sequel
|
8
|
-
|
6
|
+
* Sequel provides thread safety, connection pooling and a concise
|
7
|
+
DSL for constructing SQL queries and table schemas.
|
8
|
+
* Sequel includes a comprehensive ORM layer for mapping
|
9
|
+
records to Ruby objects and handling associated records.
|
9
10
|
* Sequel supports advanced database features such as prepared
|
10
|
-
statements, bound variables, stored procedures,
|
11
|
+
statements, bound variables, stored procedures, savepoints,
|
12
|
+
two-phase commit, transaction isolation, master/slave
|
11
13
|
configurations, and database sharding.
|
12
|
-
* Sequel makes it easy to deal with multiple records without having
|
13
|
-
to break your teeth on SQL.
|
14
14
|
* Sequel currently has adapters for ADO, Amalgalite, DataObjects,
|
15
15
|
DB2, DBI, Firebird, Informix, JDBC, MySQL, ODBC, OpenBase, Oracle,
|
16
16
|
PostgreSQL and SQLite3.
|
@@ -18,7 +18,7 @@ Sequel is a lightweight database access toolkit for Ruby.
|
|
18
18
|
== Resources
|
19
19
|
|
20
20
|
* {Website}[http://sequel.rubyforge.org]
|
21
|
-
* {
|
21
|
+
* {Blog}[http://sequel.heroku.com]
|
22
22
|
* {Source code}[http://github.com/jeremyevans/sequel]
|
23
23
|
* {Bug tracking}[http://code.google.com/p/ruby-sequel/issues/list]
|
24
24
|
* {Google group}[http://groups.google.com/group/sequel-talk]
|
@@ -84,16 +84,16 @@ Which is equivalent to:
|
|
84
84
|
|
85
85
|
SELECT avg(GDP) FROM countries WHERE region = 'Middle East'
|
86
86
|
|
87
|
-
Since datasets retrieve records only when needed, they can be stored and later reused. Records are fetched as hashes (or custom model objects), and are accessed using an Enumerable interface:
|
87
|
+
Since datasets retrieve records only when needed, they can be stored and later reused. Records are fetched as hashes (or custom model objects), and are accessed using an +Enumerable+ interface:
|
88
88
|
|
89
89
|
middle_east = DB[:countries].filter(:region => 'Middle East')
|
90
90
|
middle_east.order(:name).each{|r| puts r[:name]}
|
91
91
|
|
92
|
-
Sequel also offers convenience methods for extracting data from Datasets, such as an extended map method:
|
92
|
+
Sequel also offers convenience methods for extracting data from Datasets, such as an extended +map+ method:
|
93
93
|
|
94
94
|
middle_east.map(:name) #=> ['Egypt', 'Greece', 'Israel', ...]
|
95
95
|
|
96
|
-
Or getting results as a
|
96
|
+
Or getting results as a hash via +to_hash+, with one column as key and another as value:
|
97
97
|
|
98
98
|
middle_east.to_hash(:name, :area) #=> {'Israel' => 20000, 'Greece' => 120000, ...}
|
99
99
|
|
@@ -101,27 +101,27 @@ Or getting results as a transposed hash, with one column as key and another as v
|
|
101
101
|
|
102
102
|
=== Connecting to a database
|
103
103
|
|
104
|
-
To connect to a database you simply provide Sequel with a URL:
|
104
|
+
To connect to a database you simply provide <tt>Sequel.connect</tt> with a URL:
|
105
105
|
|
106
106
|
require 'sequel'
|
107
107
|
DB = Sequel.connect('sqlite://blog.db')
|
108
108
|
|
109
|
-
The connection URL can also include such stuff as the user name and
|
109
|
+
The connection URL can also include such stuff as the user name, password, and port:
|
110
110
|
|
111
|
-
DB = Sequel.connect('postgres://
|
111
|
+
DB = Sequel.connect('postgres://user:password@host:port/database_name')
|
112
112
|
|
113
113
|
You can also specify optional parameters, such as the connection pool size, or loggers for logging SQL queries:
|
114
114
|
|
115
|
-
DB = Sequel.connect("postgres://
|
115
|
+
DB = Sequel.connect("postgres://user:password@host:port/database_name",
|
116
116
|
:max_connections => 10, :logger => Logger.new('log/db.log'))
|
117
117
|
|
118
118
|
You can specify a block to connect, which will disconnect from the database after it completes:
|
119
119
|
|
120
|
-
Sequel.connect('postgres://
|
120
|
+
Sequel.connect('postgres://user:password@host:port/database_name'){|db| db[:posts].delete}
|
121
121
|
|
122
122
|
=== Arbitrary SQL queries
|
123
123
|
|
124
|
-
You can execute arbitrary SQL code using Database#run
|
124
|
+
You can execute arbitrary SQL code using <tt>Database#run</tt>:
|
125
125
|
|
126
126
|
DB.run("create table t (a text, b text)")
|
127
127
|
DB.run("insert into t values ('a', 'b')")
|
@@ -147,7 +147,7 @@ You can use placeholders in your SQL string as well:
|
|
147
147
|
|
148
148
|
=== Getting Dataset Instances
|
149
149
|
|
150
|
-
Datasets are the primary way records are retrieved and manipulated. They are generally created via the Database#from or Database#[] methods:
|
150
|
+
Datasets are the primary way records are retrieved and manipulated. They are generally created via the <tt>Database#from</tt> or <tt>Database#[]</tt> methods:
|
151
151
|
|
152
152
|
posts = DB.from(:posts)
|
153
153
|
posts = DB[:posts] # same
|
@@ -156,14 +156,14 @@ Datasets will only fetch records when you tell them to. They can be manipulated
|
|
156
156
|
|
157
157
|
=== Retrieving Records
|
158
158
|
|
159
|
-
You can retrieve all records by using the all method:
|
159
|
+
You can retrieve all records by using the +all+ method:
|
160
160
|
|
161
161
|
posts.all
|
162
162
|
# SELECT * FROM posts
|
163
163
|
|
164
164
|
The all method returns an array of hashes, where each hash corresponds to a record.
|
165
165
|
|
166
|
-
You can also iterate through records one at a time
|
166
|
+
You can also iterate through records one at a time using +each+:
|
167
167
|
|
168
168
|
posts.each{|row| p row}
|
169
169
|
|
@@ -189,7 +189,7 @@ If the dataset is ordered, you can also ask for the last record:
|
|
189
189
|
|
190
190
|
=== Filtering Records
|
191
191
|
|
192
|
-
An easy way to filter records is to provide a hash of values to match
|
192
|
+
An easy way to filter records is to provide a hash of values to match to +filter+:
|
193
193
|
|
194
194
|
my_posts = posts.filter(:category => 'ruby', :author => 'david')
|
195
195
|
# WHERE category = 'ruby' AND author = 'david'
|
@@ -206,7 +206,7 @@ Or arrays of values:
|
|
206
206
|
|
207
207
|
Sequel also accepts expressions:
|
208
208
|
|
209
|
-
my_posts = posts.filter{
|
209
|
+
my_posts = posts.filter{stamp > Date.today << 1}
|
210
210
|
# WHERE stamp > '2010-06-14'
|
211
211
|
|
212
212
|
Some adapters will also let you specify Regexps:
|
@@ -214,10 +214,10 @@ Some adapters will also let you specify Regexps:
|
|
214
214
|
my_posts = posts.filter(:category => /ruby/i)
|
215
215
|
# WHERE category ~* 'ruby'
|
216
216
|
|
217
|
-
You can also use an inverse filter
|
217
|
+
You can also use an inverse filter via +exclude+:
|
218
218
|
|
219
|
-
my_posts = posts.exclude(:category =>
|
220
|
-
# WHERE category
|
219
|
+
my_posts = posts.exclude(:category => ['ruby', 'postgres', 'linux'])
|
220
|
+
# WHERE category NOT IN ('ruby', 'postgres', 'linux')
|
221
221
|
|
222
222
|
You can also specify a custom WHERE clause using a string:
|
223
223
|
|
@@ -229,11 +229,11 @@ You can use parameters in your string, as well:
|
|
229
229
|
author_name = 'JKR'
|
230
230
|
posts.filter('(stamp < ?) AND (author != ?)', Date.today - 3, author_name)
|
231
231
|
# WHERE (stamp < '2010-07-11') AND (author != 'JKR')
|
232
|
-
posts.filter{
|
232
|
+
posts.filter{(stamp < Date.today - 3) & ~{:author => author_name}} # same as above
|
233
233
|
|
234
234
|
Datasets can also be used as subqueries:
|
235
235
|
|
236
|
-
DB[:items].filter('price > ?', DB[:items].select{
|
236
|
+
DB[:items].filter('price > ?', DB[:items].select{avg(price) + 100})
|
237
237
|
# WHERE price > (SELECT avg(price) + 100 FROM items)
|
238
238
|
|
239
239
|
After filtering you can retrieve the matching records by using any of the retrieval methods:
|
@@ -244,12 +244,12 @@ See the doc/dataset_filtering.rdoc file for more details.
|
|
244
244
|
|
245
245
|
=== Summarizing Records
|
246
246
|
|
247
|
-
Counting records is easy
|
247
|
+
Counting records is easy using +count+:
|
248
248
|
|
249
|
-
posts.filter(:category
|
250
|
-
# SELECT COUNT(*) FROM posts WHERE category
|
249
|
+
posts.filter(:category.like('%ruby%')).count
|
250
|
+
# SELECT COUNT(*) FROM posts WHERE category LIKE '%ruby%'
|
251
251
|
|
252
|
-
And you can also query maximum/minimum values
|
252
|
+
And you can also query maximum/minimum values via +max+ and +min+:
|
253
253
|
|
254
254
|
max = DB[:history].max(:value)
|
255
255
|
# SELECT max(value) FROM history
|
@@ -257,7 +257,8 @@ And you can also query maximum/minimum values:
|
|
257
257
|
min = DB[:history].min(:value)
|
258
258
|
# SELECT min(value) FROM history
|
259
259
|
|
260
|
-
Or calculate a sum or average
|
260
|
+
Or calculate a sum or average via +sum+ and +avg+:
|
261
|
+
|
261
262
|
sum = DB[:items].sum(:price)
|
262
263
|
# SELECT sum(price) FROM items
|
263
264
|
avg = DB[:items].avg(:price)
|
@@ -265,21 +266,21 @@ Or calculate a sum or average:
|
|
265
266
|
|
266
267
|
=== Ordering Records
|
267
268
|
|
268
|
-
Ordering datasets is simple
|
269
|
+
Ordering datasets is simple using +order+:
|
269
270
|
|
270
271
|
posts.order(:stamp)
|
271
272
|
# ORDER BY stamp
|
272
273
|
posts.order(:stamp, :name)
|
273
274
|
# ORDER BY stamp, name
|
274
275
|
|
275
|
-
Chaining order doesn't work the same as filter
|
276
|
+
Chaining +order+ doesn't work the same as +filter+:
|
276
277
|
|
277
278
|
posts.order(:stamp).order(:name)
|
278
279
|
# ORDER BY name
|
279
280
|
|
280
|
-
The
|
281
|
+
The +order_append+ method chains this way, though:
|
281
282
|
|
282
|
-
posts.order(:stamp).
|
283
|
+
posts.order(:stamp).order_append(:name)
|
283
284
|
# ORDER BY stamp, name
|
284
285
|
|
285
286
|
You can also specify descending order:
|
@@ -289,32 +290,32 @@ You can also specify descending order:
|
|
289
290
|
|
290
291
|
=== Selecting Columns
|
291
292
|
|
292
|
-
Selecting specific columns to be returned is also simple
|
293
|
+
Selecting specific columns to be returned is also simple using +select+:
|
293
294
|
|
294
295
|
posts.select(:stamp)
|
295
296
|
# SELECT stamp FROM posts
|
296
297
|
posts.select(:stamp, :name)
|
297
298
|
# SELECT stamp, name FROM posts
|
298
299
|
|
299
|
-
Chaining select works like order
|
300
|
+
Chaining +select+ works like +order+, not +filter+:
|
300
301
|
|
301
302
|
posts.select(:stamp).select(:name)
|
302
303
|
# SELECT name FROM posts
|
303
304
|
|
304
|
-
As you might expect, there is an
|
305
|
+
As you might expect, there is an +order_append+ equivalent for +select+ called +select_append+:
|
305
306
|
|
306
|
-
posts.select(:stamp).
|
307
|
+
posts.select(:stamp).select_append(:name)
|
307
308
|
# SELECT stamp, name FROM posts
|
308
309
|
|
309
310
|
=== Deleting Records
|
310
311
|
|
311
|
-
Deleting records from the table is done with delete
|
312
|
+
Deleting records from the table is done with +delete+:
|
312
313
|
|
313
314
|
posts.filter('stamp < ?', Date.today - 3).delete
|
314
315
|
# DELETE FROM posts WHERE stamp < '2010-07-11'
|
315
316
|
|
316
|
-
Be very careful when deleting, as delete affects all rows in the dataset.
|
317
|
-
|
317
|
+
Be very careful when deleting, as +delete+ affects all rows in the dataset.
|
318
|
+
+filter+ first, +delete+ second, unless you want to empty the table:
|
318
319
|
|
319
320
|
# DO THIS:
|
320
321
|
posts.filter('stamp < ?', Date.today - 7).delete
|
@@ -323,14 +324,14 @@ Filter first, delete second, unless you want to empty the table:
|
|
323
324
|
|
324
325
|
=== Inserting Records
|
325
326
|
|
326
|
-
Inserting records into the table is done with insert
|
327
|
+
Inserting records into the table is done with +insert+:
|
327
328
|
|
328
329
|
posts.insert(:category => 'ruby', :author => 'david')
|
329
330
|
# INSERT INTO posts (category, author) VALUES ('ruby', 'david')
|
330
331
|
|
331
332
|
=== Updating Records
|
332
333
|
|
333
|
-
Updating records in the table is done with update
|
334
|
+
Updating records in the table is done with +update+:
|
334
335
|
|
335
336
|
posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived')
|
336
337
|
# UPDATE posts SET state = 'archived' WHERE stamp < '2010-07-07'
|
@@ -340,36 +341,52 @@ You can reference table columns when choosing what values to set:
|
|
340
341
|
posts.filter{|o| o.stamp < Date.today - 7}.update(:backup_number => :backup_number + 1)
|
341
342
|
# UPDATE posts SET backup_number = backup_number + 1 WHERE stamp < '2010-07-07'
|
342
343
|
|
343
|
-
As with delete
|
344
|
-
update second, unless you want to update all rows:
|
344
|
+
As with +delete+, +update+ affects all rows in the dataset, so +filter+ first,
|
345
|
+
+update+ second, unless you want to update all rows:
|
345
346
|
|
346
347
|
# DO THIS:
|
347
348
|
posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived')
|
348
349
|
# NOT THIS:
|
349
350
|
posts.update(:state => 'archived').filter('stamp < ?', Date.today - 7)
|
350
351
|
|
352
|
+
=== Transactions
|
353
|
+
|
354
|
+
You can wrap some code in a database transaction using the <tt>Database#transaction</tt> method:
|
355
|
+
|
356
|
+
DB.transaction do
|
357
|
+
posts.insert(:category => 'ruby', :author => 'david')
|
358
|
+
posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived')
|
359
|
+
end
|
360
|
+
|
361
|
+
If the block does not raise an exception, the transaction will be committed.
|
362
|
+
If the block does raise an exception, the transaction will be rolled back,
|
363
|
+
and the exception will be reraised. If you want to rollback the transaction
|
364
|
+
and not raise an exception outside the block, you can raise the
|
365
|
+
<tt>Sequel::Rollback</tt> exception inside the block:
|
366
|
+
|
367
|
+
DB.transaction do
|
368
|
+
posts.insert(:category => 'ruby', :author => 'david')
|
369
|
+
if posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived') == 0
|
370
|
+
raise Sequel::Rollback
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
351
374
|
=== Joining Tables
|
352
375
|
|
353
376
|
Sequel makes it easy to join tables:
|
354
377
|
|
355
378
|
order_items = DB[:items].join(:order_items, :item_id => :id).
|
356
379
|
filter(:order_items__order_id => 1234)
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
SELECT * FROM items INNER JOIN order_items
|
361
|
-
ON order_items.item_id = items.id
|
362
|
-
WHERE order_items.order_id = 1234
|
380
|
+
# SELECT * FROM items INNER JOIN order_items
|
381
|
+
# ON order_items.item_id = items.id
|
382
|
+
# WHERE order_items.order_id = 1234
|
363
383
|
|
364
384
|
You can then do anything you like with the dataset:
|
365
385
|
|
366
386
|
order_total = order_items.sum(:price)
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
SELECT sum(price) FROM items INNER JOIN order_items
|
371
|
-
ON order_items.item_id = items.id
|
372
|
-
WHERE order_items.order_id = 1234
|
387
|
+
# SELECT sum(price) FROM items INNER JOIN order_items
|
388
|
+
# ON order_items.item_id = items.id
|
389
|
+
# WHERE order_items.order_id = 1234
|
373
390
|
|
374
391
|
=== Graphing Datasets
|
375
392
|
|
@@ -378,12 +395,12 @@ When retrieving records from joined datasets, you get the results in a single ha
|
|
378
395
|
DB[:items].join(:order_items, :item_id => :id).first
|
379
396
|
=> {:id=>order_items.id, :item_id=>order_items.item_id}
|
380
397
|
|
381
|
-
Using graph
|
398
|
+
Using +graph+, you can split the result hashes into subhashes, one per join:
|
382
399
|
|
383
400
|
DB[:items].graph(:order_items, :item_id => :id).first
|
384
401
|
=> {:items=>{:id=>items.id}, :order_items=>{:id=>order_items.id, :item_id=>order_items.item_id}}
|
385
402
|
|
386
|
-
==
|
403
|
+
== Column references in Sequel
|
387
404
|
|
388
405
|
Sequel expects column names to be specified using symbols. In addition, returned hashes always use symbols as their keys. This allows you to freely mix literal values and column references in many cases. For example, the two following lines produce equivalent SQL:
|
389
406
|
|
@@ -399,21 +416,26 @@ Ruby strings are generally treated as SQL strings:
|
|
399
416
|
|
400
417
|
=== Qualifying column names
|
401
418
|
|
402
|
-
Column references can be qualified by using the double underscore special notation
|
419
|
+
Column references can be qualified by using the double underscore special notation <tt>:table__column</tt>:
|
403
420
|
|
404
421
|
items.literal(:items__price)
|
405
422
|
# items.price
|
406
423
|
|
424
|
+
Another way to qualify columns is to use the +qualify+ method:
|
425
|
+
|
426
|
+
items.literal(:price.qualify(:items))
|
427
|
+
# items.price
|
428
|
+
|
407
429
|
=== Column aliases
|
408
430
|
|
409
|
-
You can also alias columns by using the triple undersecore special notation
|
431
|
+
You can also alias columns by using the triple undersecore special notation <tt>:column___alias</tt> or <tt>:table__column___alias</tt>:
|
410
432
|
|
411
433
|
items.literal(:price___p)
|
412
434
|
# price AS p
|
413
435
|
items.literal(:items__price___p)
|
414
436
|
# items.price AS p
|
415
437
|
|
416
|
-
Another way to alias columns is to use the
|
438
|
+
Another way to alias columns is to use the +as+ method:
|
417
439
|
|
418
440
|
items.literal(:price.as(:p))
|
419
441
|
# price AS p
|
@@ -422,35 +444,35 @@ Another way to alias columns is to use the #as method:
|
|
422
444
|
|
423
445
|
A model class wraps a dataset, and an instance of that class wraps a single record in the dataset.
|
424
446
|
|
425
|
-
Model classes are defined as regular Ruby classes
|
447
|
+
Model classes are defined as regular Ruby classes inheriting from <tt>Sequel::Model</tt>:
|
426
448
|
|
427
449
|
DB = Sequel.connect('sqlite://blog.db')
|
428
450
|
class Post < Sequel::Model
|
429
451
|
end
|
430
452
|
|
431
|
-
|
453
|
+
Sequel model classes assume that the table name is an underscored plural of the class name:
|
432
454
|
|
433
455
|
Post.table_name #=> :posts
|
434
456
|
|
435
|
-
You can
|
457
|
+
You can explicitly set the table name or even the dataset used:
|
436
458
|
|
437
459
|
class Post < Sequel::Model(:my_posts)
|
438
460
|
end
|
439
461
|
# or:
|
440
462
|
Post.set_dataset :my_posts
|
441
463
|
|
442
|
-
If you
|
464
|
+
If you call +set_dataset+ with a symbol, it assumes you are referring to the table with the same name. You can also call it with a dataset, which will set the defaults for all retrievals for that model:
|
443
465
|
|
444
466
|
Post.set_dataset DB[:my_posts].filter(:category => 'ruby')
|
445
467
|
Post.set_dataset DB[:my_posts].select(:id, :name).order(:date)
|
446
468
|
|
447
469
|
=== Model instances
|
448
470
|
|
449
|
-
Model instances are identified by a primary key. In most cases, Sequel can
|
471
|
+
Model instances are identified by a primary key. In most cases, Sequel can query the database to determine the primary key, but if not, it defaults to using <tt>:id</tt>. The <tt>Model.[]</tt> method can be used to fetch records by their primary key:
|
450
472
|
|
451
473
|
post = Post[123]
|
452
474
|
|
453
|
-
The
|
475
|
+
The +pk+ method is used to retrieve the record's primary key value:
|
454
476
|
|
455
477
|
post.pk #=> 123
|
456
478
|
|
@@ -463,27 +485,29 @@ Sequel models allow you to use any column as a primary key, and even composite k
|
|
463
485
|
post = Post['ruby', 'hello world']
|
464
486
|
post.pk #=> ['ruby', 'hello world']
|
465
487
|
|
466
|
-
You can also define a model class that does not have a primary key
|
488
|
+
You can also define a model class that does not have a primary key via +no_primary_key+, but then you lose the ability to easily update and delete records:
|
489
|
+
|
490
|
+
Post.no_primary_key
|
467
491
|
|
468
|
-
A model instance can also be fetched by specifying a condition:
|
492
|
+
A single model instance can also be fetched by specifying a condition:
|
469
493
|
|
470
494
|
post = Post[:title => 'hello world']
|
471
|
-
post = Post.
|
495
|
+
post = Post.first{num_comments < 10}
|
472
496
|
|
473
497
|
=== Iterating over records
|
474
498
|
|
475
|
-
A model class lets you iterate over subsets of records by proxying many methods to the underlying dataset. This means that you can use most of the Dataset API to create customized queries that return model instances, e.g.:
|
499
|
+
A model class lets you iterate over subsets of records by proxying many methods to the underlying dataset. This means that you can use most of the +Dataset+ API to create customized queries that return model instances, e.g.:
|
476
500
|
|
477
501
|
Post.filter(:category => 'ruby').each{|post| p post}
|
478
502
|
|
479
503
|
You can also manipulate the records in the dataset:
|
480
504
|
|
481
|
-
Post.filter{
|
505
|
+
Post.filter{num_comments < 7}.delete
|
482
506
|
Post.filter(:title.like(/ruby/)).update(:category => 'ruby')
|
483
507
|
|
484
508
|
=== Accessing record values
|
485
509
|
|
486
|
-
A model
|
510
|
+
A model instance stores its values as a hash with column symbol keys, which you can access directly via the +values+ method:
|
487
511
|
|
488
512
|
post.values #=> {:id => 123, :category => 'ruby', :title => 'hello world'}
|
489
513
|
|
@@ -492,28 +516,28 @@ You can read the record values as object attributes, assuming the attribute name
|
|
492
516
|
post.id #=> 123
|
493
517
|
post.title #=> 'hello world'
|
494
518
|
|
495
|
-
If the record's attributes names are not valid columns in the model's dataset (maybe because you used
|
519
|
+
If the record's attributes names are not valid columns in the model's dataset (maybe because you used +select_append+ to add a computed value column), you can use <tt>Model#[]</tt> to access the values:
|
496
520
|
|
497
521
|
post[:id] #=> 123
|
498
522
|
post[:title] #=> 'hello world'
|
499
523
|
|
500
|
-
You can also
|
524
|
+
You can also modify record values using attribute setters or the +set+ method:
|
501
525
|
|
502
526
|
post.title = 'hey there'
|
503
527
|
# or
|
504
528
|
post.set(:title=>'hey there')
|
505
529
|
|
506
|
-
That will just change the value for the object, it will not
|
530
|
+
That will just change the value for the object, it will not update the row in the database. To update the database row, call the +save+ method:
|
507
531
|
|
508
532
|
post.save
|
509
533
|
|
510
|
-
|
534
|
+
You can modify record values and save the changes to the object in a single method call using the +update+ method:
|
511
535
|
|
512
536
|
post.update(:title => 'hey there')
|
513
537
|
|
514
538
|
=== Creating new records
|
515
539
|
|
516
|
-
New records can be created by calling Model.create
|
540
|
+
New records can be created by calling <tt>Model.create</tt>:
|
517
541
|
|
518
542
|
post = Post.create(:title => 'hello world')
|
519
543
|
|
@@ -523,7 +547,7 @@ Another way is to construct a new instance and save it later:
|
|
523
547
|
post.title = 'hello world'
|
524
548
|
post.save
|
525
549
|
|
526
|
-
You can also supply a block to Model.new and Model.create
|
550
|
+
You can also supply a block to <tt>Model.new</tt> and <tt>Model.create</tt>:
|
527
551
|
|
528
552
|
post = Post.new do |p|
|
529
553
|
p.title = 'hello world'
|
@@ -533,7 +557,7 @@ You can also supply a block to Model.new and Model.create:
|
|
533
557
|
|
534
558
|
=== Hooks
|
535
559
|
|
536
|
-
You can execute custom code when creating, updating, or deleting records by defining hook methods. The before_create and after_create hook methods wrap record creation. The before_update and after_update hook methods wrap record updating. The before_save and after_save hook methods wrap record creation and updating. The before_destroy and after_destroy hook methods wrap destruction. The before_validation and after_validation hook methods wrap validation. Example:
|
560
|
+
You can execute custom code when creating, updating, or deleting records by defining hook methods. The +before_create+ and +after_create+ hook methods wrap record creation. The +before_update+ and +after_update+ hook methods wrap record updating. The +before_save+ and +after_save+ hook methods wrap record creation and updating. The +before_destroy+ and +after_destroy+ hook methods wrap destruction. The +before_validation+ and +after_validation+ hook methods wrap validation. Example:
|
537
561
|
|
538
562
|
class Post < Sequel::Model
|
539
563
|
def after_create
|
@@ -547,29 +571,29 @@ You can execute custom code when creating, updating, or deleting records by defi
|
|
547
571
|
end
|
548
572
|
end
|
549
573
|
|
550
|
-
Note the use of super if you define your own hook methods. Almost all Sequel::Model class and instance methods (not just hook methods) can be overridden safely, but you have to make sure to call super when doing so, otherwise you risk breaking things.
|
574
|
+
Note the use of +super+ if you define your own hook methods. Almost all <tt>Sequel::Model</tt> class and instance methods (not just hook methods) can be overridden safely, but you have to make sure to call +super+ when doing so, otherwise you risk breaking things.
|
551
575
|
|
552
|
-
For the example above, you should probably use a database trigger if you can. Hooks can be used for data integrity, but they will only enforce that integrity when you are
|
576
|
+
For the example above, you should probably use a database trigger if you can. Hooks can be used for data integrity, but they will only enforce that integrity when you are modifying the database through model instances. If you plan on allowing any other access to the database, it's best to use database triggers for data integrity.
|
553
577
|
|
554
578
|
=== Deleting records
|
555
579
|
|
556
|
-
You can delete individual records by calling
|
580
|
+
You can delete individual records by calling +delete+ or +destroy+. The only difference between the two methods is that +destroy+ invokes +before_destroy+ and +after_destroy+ hook methods, while +delete+ does not:
|
557
581
|
|
558
|
-
post.delete
|
559
|
-
post.destroy
|
582
|
+
post.delete # => bypasses hooks
|
583
|
+
post.destroy # => runs hooks
|
560
584
|
|
561
|
-
Records can also be deleted en-masse by
|
585
|
+
Records can also be deleted en-masse by calling <tt>Model.delete</tt> and <tt>Model.destroy</tt>. As stated above, you can specify filters for the deleted records:
|
562
586
|
|
563
|
-
Post.filter(:category => 32).delete
|
564
|
-
Post.filter(:category => 32).destroy
|
587
|
+
Post.filter(:category => 32).delete # => bypasses hooks
|
588
|
+
Post.filter(:category => 32).destroy # => runs hooks
|
565
589
|
|
566
|
-
Please note that if Model.destroy is called, each record is deleted
|
567
|
-
separately, but Model.delete deletes all matching records with a single
|
590
|
+
Please note that if <tt>Model.destroy</tt> is called, each record is deleted
|
591
|
+
separately, but <tt>Model.delete</tt> deletes all matching records with a single
|
568
592
|
SQL query.
|
569
593
|
|
570
594
|
=== Associations
|
571
595
|
|
572
|
-
Associations are used in order to specify relationships between model classes that reflect relationships between tables in the database, which are usually specified using foreign keys.
|
596
|
+
Associations are used in order to specify relationships between model classes that reflect relationships between tables in the database, which are usually specified using foreign keys. You specify model associations via the +many_to_one+, +one_to_one+, +one_to_many+, and +many_to_many+ class methods:
|
573
597
|
|
574
598
|
class Post < Sequel::Model
|
575
599
|
many_to_one :author
|
@@ -577,29 +601,22 @@ Associations are used in order to specify relationships between model classes th
|
|
577
601
|
many_to_many :tags
|
578
602
|
end
|
579
603
|
|
580
|
-
many_to_one
|
581
|
-
|
582
|
-
class Post < Sequel::Model
|
583
|
-
many_to_one :author
|
584
|
-
end
|
604
|
+
+many_to_one+ and +one_to_one+ create a getter and setter for each model object:
|
585
605
|
|
586
606
|
post = Post.create(:name => 'hi!')
|
587
607
|
post.author = Author[:name => 'Sharon']
|
588
608
|
post.author
|
589
609
|
|
590
|
-
one_to_many and many_to_many create a getter method, a method for adding an object to the association, a method for removing an object from the association, and a method for removing all associated objects from the association:
|
591
|
-
|
592
|
-
class Post < Sequel::Model
|
593
|
-
one_to_many :comments
|
594
|
-
many_to_many :tags
|
595
|
-
end
|
610
|
+
+one_to_many+ and +many_to_many+ create a getter method, a method for adding an object to the association, a method for removing an object from the association, and a method for removing all associated objects from the association:
|
596
611
|
|
597
612
|
post = Post.create(:name => 'hi!')
|
598
613
|
post.comments
|
614
|
+
|
599
615
|
comment = Comment.create(:text=>'hi')
|
600
616
|
post.add_comment(comment)
|
601
617
|
post.remove_comment(comment)
|
602
618
|
post.remove_all_comments
|
619
|
+
|
603
620
|
tag = Tag.create(:tag=>'interesting')
|
604
621
|
post.add_tag(tag)
|
605
622
|
post.remove_tag(tag)
|
@@ -617,7 +634,7 @@ All associations add a dataset method that can be used to further filter or reor
|
|
617
634
|
|
618
635
|
=== Eager Loading
|
619
636
|
|
620
|
-
Associations can be eagerly loaded via
|
637
|
+
Associations can be eagerly loaded via +eager+ and the <tt>:eager</tt> association option. Eager loading is used when loading a group of objects. It loads all associated objects for all of the current objects in one query, instead of using a separate query to get the associated objects for each current object. Eager loading requires that you retrieve all model objects at once via +all+ (instead of individually by +each+). Eager loading can be cascaded, loading association's associated objects.
|
621
638
|
|
622
639
|
class Person < Sequel::Model
|
623
640
|
one_to_many :posts, :eager=>[:tags]
|
@@ -644,7 +661,7 @@ Associations can be eagerly loaded via .eager and the :eager association option.
|
|
644
661
|
Post.eager(:person).all
|
645
662
|
|
646
663
|
# eager is a dataset method, so it works with filters/orders/limits/etc.
|
647
|
-
Post.filter{
|
664
|
+
Post.filter{topic > 'M'}.order(:date).limit(5).eager(:person).all
|
648
665
|
|
649
666
|
person = Person.first
|
650
667
|
# Eager loading via :eager (will eagerly load the tags for this person's posts)
|
@@ -666,7 +683,7 @@ Associations can be eagerly loaded via .eager and the :eager association option.
|
|
666
683
|
# replies that have that tag. Uses a total of 8 queries.
|
667
684
|
Person.eager(:posts=>{:replies=>[:person, {:tags=>{:posts, :replies}}]}).all
|
668
685
|
|
669
|
-
In addition to using eager
|
686
|
+
In addition to using +eager+, you can also use +eager_graph+, which will use a single query to get the object and all associated objects. This may be necessary if you want to filter or order the result set based on columns in associated tables. It works with cascading as well, the syntax is exactly the same. Note that using eager_graph to eagerly load multiple *_to_many associations will cause the result set to be a cartesian product, so you should be very careful with your filters when using it in that case.
|
670
687
|
|
671
688
|
=== Extending the underlying dataset
|
672
689
|
|
@@ -674,7 +691,7 @@ The obvious way to add table-wide logic is to define class methods to the model
|
|
674
691
|
|
675
692
|
class Post < Sequel::Model
|
676
693
|
def self.posts_with_few_comments
|
677
|
-
filter{
|
694
|
+
filter{num_comments < 30}
|
678
695
|
end
|
679
696
|
|
680
697
|
def self.clean_posts_with_few_comments
|
@@ -682,11 +699,11 @@ The obvious way to add table-wide logic is to define class methods to the model
|
|
682
699
|
end
|
683
700
|
end
|
684
701
|
|
685
|
-
You can also implement table-wide logic by defining methods on the dataset
|
702
|
+
You can also implement table-wide logic by defining methods on the dataset using +def_dataset_method+:
|
686
703
|
|
687
704
|
class Post < Sequel::Model
|
688
705
|
def_dataset_method(:posts_with_few_comments) do
|
689
|
-
filter{
|
706
|
+
filter{num_comments < 30}
|
690
707
|
end
|
691
708
|
|
692
709
|
def_dataset_method(:clean_posts_with_few_comments) do
|
@@ -698,24 +715,26 @@ This is the recommended way of implementing table-wide operations, and allows yo
|
|
698
715
|
|
699
716
|
Post.filter(:category => 'ruby').clean_posts_with_few_comments
|
700
717
|
|
701
|
-
Sequel models also provide a
|
718
|
+
Sequel models also provide a +subset+ class method that creates a dataset method with a simple filter:
|
702
719
|
|
703
720
|
class Post < Sequel::Model
|
704
|
-
subset(:posts_with_few_comments){
|
721
|
+
subset(:posts_with_few_comments){num_comments < 30}
|
705
722
|
subset :invisible, ~:visible
|
706
723
|
end
|
707
724
|
|
708
725
|
=== Model Validations
|
709
726
|
|
710
|
-
You can define a validate method for your model, which
|
727
|
+
You can define a +validate+ method for your model, which +save+
|
711
728
|
will check before attempting to save the model in the database.
|
712
729
|
If an attribute of the model isn't valid, you should add a error
|
713
|
-
message for that attribute to the model object's errors
|
714
|
-
object has any errors added by the validate method, save will
|
715
|
-
raise an error or return false depending on how it is configured
|
730
|
+
message for that attribute to the model object's +errors+. If an
|
731
|
+
object has any errors added by the validate method, +save+ will
|
732
|
+
raise an error or return false depending on how it is configured
|
733
|
+
(the +raise_on_save_failure+ flag).
|
716
734
|
|
717
735
|
class Post < Sequel::Model
|
718
736
|
def validate
|
737
|
+
super
|
719
738
|
errors.add(:name, "can't be empty") if name.empty?
|
720
739
|
errors.add(:written_on, "should be in the past") if written_on >= Time.now
|
721
740
|
end
|