sequel 3.12.1 → 3.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG +42 -0
  2. data/README.rdoc +137 -118
  3. data/Rakefile +21 -66
  4. data/doc/active_record.rdoc +9 -9
  5. data/doc/advanced_associations.rdoc +59 -188
  6. data/doc/association_basics.rdoc +15 -2
  7. data/doc/cheat_sheet.rdoc +38 -33
  8. data/doc/dataset_filtering.rdoc +16 -7
  9. data/doc/prepared_statements.rdoc +7 -7
  10. data/doc/querying.rdoc +5 -4
  11. data/doc/release_notes/3.13.0.txt +210 -0
  12. data/doc/sharding.rdoc +1 -1
  13. data/doc/sql.rdoc +5 -5
  14. data/doc/validations.rdoc +11 -11
  15. data/lib/sequel/adapters/ado.rb +1 -1
  16. data/lib/sequel/adapters/do.rb +3 -3
  17. data/lib/sequel/adapters/firebird.rb +3 -3
  18. data/lib/sequel/adapters/jdbc/h2.rb +39 -0
  19. data/lib/sequel/adapters/jdbc/mysql.rb +5 -0
  20. data/lib/sequel/adapters/jdbc/oracle.rb +3 -3
  21. data/lib/sequel/adapters/mysql.rb +7 -4
  22. data/lib/sequel/adapters/oracle.rb +3 -3
  23. data/lib/sequel/adapters/shared/mssql.rb +10 -1
  24. data/lib/sequel/adapters/shared/mysql.rb +63 -0
  25. data/lib/sequel/adapters/shared/postgres.rb +61 -3
  26. data/lib/sequel/adapters/sqlite.rb +105 -18
  27. data/lib/sequel/connection_pool.rb +31 -30
  28. data/lib/sequel/core.rb +58 -58
  29. data/lib/sequel/core_sql.rb +52 -43
  30. data/lib/sequel/database/misc.rb +11 -0
  31. data/lib/sequel/database/query.rb +55 -17
  32. data/lib/sequel/dataset/actions.rb +2 -1
  33. data/lib/sequel/dataset/query.rb +2 -3
  34. data/lib/sequel/dataset/sql.rb +24 -11
  35. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  36. data/lib/sequel/metaprogramming.rb +4 -0
  37. data/lib/sequel/model.rb +37 -19
  38. data/lib/sequel/model/associations.rb +33 -25
  39. data/lib/sequel/model/base.rb +2 -2
  40. data/lib/sequel/model/plugins.rb +7 -2
  41. data/lib/sequel/plugins/active_model.rb +1 -1
  42. data/lib/sequel/plugins/association_pks.rb +2 -2
  43. data/lib/sequel/plugins/association_proxies.rb +1 -1
  44. data/lib/sequel/plugins/boolean_readers.rb +2 -2
  45. data/lib/sequel/plugins/class_table_inheritance.rb +10 -2
  46. data/lib/sequel/plugins/identity_map.rb +3 -3
  47. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  48. data/lib/sequel/plugins/json_serializer.rb +212 -0
  49. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  50. data/lib/sequel/plugins/list.rb +174 -0
  51. data/lib/sequel/plugins/many_through_many.rb +2 -2
  52. data/lib/sequel/plugins/rcte_tree.rb +6 -7
  53. data/lib/sequel/plugins/tree.rb +118 -0
  54. data/lib/sequel/plugins/xml_serializer.rb +321 -0
  55. data/lib/sequel/sql.rb +315 -206
  56. data/lib/sequel/timezones.rb +40 -17
  57. data/lib/sequel/version.rb +8 -2
  58. data/spec/adapters/firebird_spec.rb +2 -2
  59. data/spec/adapters/informix_spec.rb +1 -1
  60. data/spec/adapters/mssql_spec.rb +2 -2
  61. data/spec/adapters/mysql_spec.rb +2 -2
  62. data/spec/adapters/oracle_spec.rb +1 -1
  63. data/spec/adapters/postgres_spec.rb +36 -6
  64. data/spec/adapters/spec_helper.rb +2 -2
  65. data/spec/adapters/sqlite_spec.rb +1 -1
  66. data/spec/core/connection_pool_spec.rb +3 -3
  67. data/spec/core/core_sql_spec.rb +31 -13
  68. data/spec/core/database_spec.rb +39 -2
  69. data/spec/core/dataset_spec.rb +24 -12
  70. data/spec/core/expression_filters_spec.rb +5 -1
  71. data/spec/core/object_graph_spec.rb +1 -1
  72. data/spec/core/schema_generator_spec.rb +1 -1
  73. data/spec/core/schema_spec.rb +1 -1
  74. data/spec/core/spec_helper.rb +1 -1
  75. data/spec/core/version_spec.rb +1 -1
  76. data/spec/extensions/active_model_spec.rb +82 -67
  77. data/spec/extensions/association_dependencies_spec.rb +1 -1
  78. data/spec/extensions/association_pks_spec.rb +1 -1
  79. data/spec/extensions/association_proxies_spec.rb +1 -1
  80. data/spec/extensions/blank_spec.rb +1 -1
  81. data/spec/extensions/boolean_readers_spec.rb +1 -1
  82. data/spec/extensions/caching_spec.rb +1 -1
  83. data/spec/extensions/class_table_inheritance_spec.rb +3 -2
  84. data/spec/extensions/composition_spec.rb +2 -5
  85. data/spec/extensions/force_encoding_spec.rb +3 -1
  86. data/spec/extensions/hook_class_methods_spec.rb +1 -1
  87. data/spec/extensions/identity_map_spec.rb +1 -1
  88. data/spec/extensions/inflector_spec.rb +1 -1
  89. data/spec/extensions/instance_filters_spec.rb +1 -1
  90. data/spec/extensions/instance_hooks_spec.rb +1 -1
  91. data/spec/extensions/json_serializer_spec.rb +154 -0
  92. data/spec/extensions/lazy_attributes_spec.rb +1 -2
  93. data/spec/extensions/list_spec.rb +251 -0
  94. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  95. data/spec/extensions/many_through_many_spec.rb +3 -3
  96. data/spec/extensions/migration_spec.rb +1 -1
  97. data/spec/extensions/named_timezones_spec.rb +5 -6
  98. data/spec/extensions/nested_attributes_spec.rb +1 -1
  99. data/spec/extensions/optimistic_locking_spec.rb +1 -1
  100. data/spec/extensions/pagination_spec.rb +1 -1
  101. data/spec/extensions/pretty_table_spec.rb +1 -1
  102. data/spec/extensions/query_spec.rb +1 -1
  103. data/spec/extensions/rcte_tree_spec.rb +1 -1
  104. data/spec/extensions/schema_dumper_spec.rb +3 -2
  105. data/spec/extensions/schema_spec.rb +1 -1
  106. data/spec/extensions/serialization_spec.rb +6 -2
  107. data/spec/extensions/sharding_spec.rb +1 -1
  108. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  109. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  110. data/spec/extensions/spec_helper.rb +7 -3
  111. data/spec/extensions/sql_expr_spec.rb +1 -1
  112. data/spec/extensions/string_date_time_spec.rb +1 -1
  113. data/spec/extensions/string_stripper_spec.rb +1 -1
  114. data/spec/extensions/subclasses_spec.rb +1 -1
  115. data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
  116. data/spec/extensions/thread_local_timezones_spec.rb +1 -1
  117. data/spec/extensions/timestamps_spec.rb +1 -1
  118. data/spec/extensions/touch_spec.rb +1 -1
  119. data/spec/extensions/tree_spec.rb +119 -0
  120. data/spec/extensions/typecast_on_load_spec.rb +1 -1
  121. data/spec/extensions/update_primary_key_spec.rb +1 -1
  122. data/spec/extensions/validation_class_methods_spec.rb +1 -1
  123. data/spec/extensions/validation_helpers_spec.rb +1 -1
  124. data/spec/extensions/xml_serializer_spec.rb +142 -0
  125. data/spec/integration/associations_test.rb +1 -1
  126. data/spec/integration/database_test.rb +1 -1
  127. data/spec/integration/dataset_test.rb +29 -14
  128. data/spec/integration/eager_loader_test.rb +1 -1
  129. data/spec/integration/migrator_test.rb +1 -1
  130. data/spec/integration/model_test.rb +1 -1
  131. data/spec/integration/plugin_test.rb +316 -1
  132. data/spec/integration/prepared_statement_test.rb +1 -1
  133. data/spec/integration/schema_test.rb +8 -8
  134. data/spec/integration/spec_helper.rb +1 -1
  135. data/spec/integration/timezone_test.rb +1 -1
  136. data/spec/integration/transaction_test.rb +35 -20
  137. data/spec/integration/type_test.rb +1 -1
  138. data/spec/model/association_reflection_spec.rb +1 -1
  139. data/spec/model/associations_spec.rb +49 -34
  140. data/spec/model/base_spec.rb +1 -1
  141. data/spec/model/dataset_methods_spec.rb +4 -4
  142. data/spec/model/eager_loading_spec.rb +1 -1
  143. data/spec/model/hooks_spec.rb +1 -1
  144. data/spec/model/inflector_spec.rb +1 -1
  145. data/spec/model/model_spec.rb +7 -1
  146. data/spec/model/plugins_spec.rb +1 -1
  147. data/spec/model/record_spec.rb +1 -3
  148. data/spec/model/spec_helper.rb +2 -2
  149. data/spec/model/validations_spec.rb +1 -1
  150. 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 lightweight database access toolkit for Ruby.
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 DSL
6
- for constructing database queries and table schemas.
7
- * Sequel also includes a lightweight but comprehensive ORM layer for
8
- mapping records to Ruby objects and handling associated records.
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, master/slave
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
- * {Website}[http://sequel.heroku.com]
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 transposed hash, with one column as key and another as value:
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 password:
109
+ The connection URL can also include such stuff as the user name, password, and port:
110
110
 
111
- DB = Sequel.connect('postgres://cico:12345@localhost:5432/mydb')
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://postgres:postgres@localhost/my_db",
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://cico:12345@localhost:5432/mydb'){|db| db[:posts].delete}
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{|o| o.stamp > Date.today << 1}
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 => /ruby/i)
220
- # WHERE category !~* 'ruby'
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{|o| (o.stamp < Date.today - 3) & ~{:author => author_name}} # same as above
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{|o| o.avg(:price) + 100})
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 => /ruby/i).count
250
- # SELECT COUNT(*) FROM posts WHERE category ~* 'ruby'
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 order_more method chains this way, though:
281
+ The +order_append+ method chains this way, though:
281
282
 
282
- posts.order(:stamp).order_more(:name)
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, not filter:
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 order_more equivalent for select:
305
+ As you might expect, there is an +order_append+ equivalent for +select+ called +select_append+:
305
306
 
306
- posts.select(:stamp).select_more(:name)
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
- Filter first, delete second, unless you want to empty the table:
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, this affects all rows in the dataset, so filter first,
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
- This is equivalent to the SQL:
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
- Which is equivalent to the SQL:
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, you can split the result hashes into subhashes, one per join:
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
- == An aside: column references in Sequel
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 :table__column:
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 :column___alias or :table__column___alias:
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 #as method:
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
- Just like in DataMapper or ActiveRecord, Sequel model classes assume that the table name is a plural of the class name:
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, however, explicitly set the table name or even the dataset used:
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 use a symbol, it assumes you are referring to the table with the same name. You can also give it a dataset, which will set the defaults for all retrievals for that model:
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 introspec the database to determine the primary key, but if not, it defaults to using :id. The Model.[] method can be used to fetch records by their primary key:
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 Model#pk method is used to retrieve the record's primary key value:
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, but then you lose the ability to easily update and delete records.
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.find{|o| o.num_comments < 10}
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{|o| o.num_comments < 7}.delete
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 instances stores its values as a hash:
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 select_more to add a computed value column), you can use Model#[] to access the values:
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 change record values:
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 persist the changes to the database. To persist the record, call the #save method:
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
- If you want to modify record values and save the changes to the object after doing so, use the #update method:
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 using the model. If you plan on allowing any other access to the database, it's best to use database triggers for data integrity.
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 #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:
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 #=> bypasses hooks
559
- post.destroy #=> runs hooks
582
+ post.delete # => bypasses hooks
583
+ post.destroy # => runs hooks
560
584
 
561
- Records can also be deleted en-masse by invoking Model.delete and Model.destroy. As stated above, you can specify filters for the deleted records:
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 #=> bypasses hooks
564
- Post.filter(:category => 32).destroy #=> runs hooks
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 creates a getter and setter for each model object:
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 .eager and the :eager 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.
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{|o| o.topic > 'M'}.order(:date).limit(5).eager(:person).all
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, 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.
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{|o| o.num_comments < 30}
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{|o| o.num_comments < 30}
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 short hand notation for filters:
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){|o| o.num_comments < 30}
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 #save
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. If an
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