Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 759 lines (486 sloc) 27.113 kB
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
1 == Sequel: The Database Toolkit for Ruby
2
de44b4c @jeremyevans Minor updates to the README
authored
3 Sequel is a simple, flexible, and powerful SQL database access
4 toolkit for Ruby.
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
5
de44b4c @jeremyevans Minor updates to the README
authored
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.
645073f @jeremyevans Add DataObjects adapter, with PostgreSQL, MySQL, and SQLite subadapters
authored
10 * Sequel supports advanced database features such as prepared
de44b4c @jeremyevans Minor updates to the README
authored
11 statements, bound variables, stored procedures, savepoints,
12 two-phase commit, transaction isolation, master/slave
645073f @jeremyevans Add DataObjects adapter, with PostgreSQL, MySQL, and SQLite subadapters
authored
13 configurations, and database sharding.
8398b5b @jeremyevans Add Amalgalite adapter
authored
14 * Sequel currently has adapters for ADO, Amalgalite, DataObjects,
5727718 @jeremyevans Add IBM_DB as a supported adapter in the README
authored
15 DB2, DBI, Firebird, IBM_DB, Informix, JDBC, MySQL, Mysql2, ODBC,
16 OpenBase, Oracle, PostgreSQL, SQLite3, Swift, and TinyTDS.
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
17
18 == Resources
19
189fe30 @jeremyevans Use boof's website design for sequel.rubyforge.org
authored
20 * {Website}[http://sequel.rubyforge.org]
de44b4c @jeremyevans Minor updates to the README
authored
21 * {Blog}[http://sequel.heroku.com]
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
22 * {Source code}[http://github.com/jeremyevans/sequel]
0e9ad33 @jeremyevans Switch from Google Code Issue Tracker to GitHub Issues
authored
23 * {Bug tracking}[http://github.com/jeremyevans/sequel/issues]
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
24 * {Google group}[http://groups.google.com/group/sequel-talk]
189fe30 @jeremyevans Use boof's website design for sequel.rubyforge.org
authored
25 * {RDoc}[http://sequel.rubyforge.org/rdoc]
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
26
27 To check out the source code:
28
29 git clone git://github.com/jeremyevans/sequel.git
30
31 === Contact
32
33 If you have any comments or suggestions please post to the Google group.
34
35 == Installation
36
37 sudo gem install sequel
38
39 == A Short Example
40
41 require 'rubygems'
42 require 'sequel'
43
44 DB = Sequel.sqlite # memory database
45
76613bc @jeremyevans Update README.rdoc
authored
46 DB.create_table :items do
7c0583f @jackdempsey add in demonstration of primary_key method
jackdempsey authored
47 primary_key :id
5193867 @jeremyevans README fixes from jinguoli, thanks!
authored
48 String :name
49 Float :price
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
50 end
51
52 items = DB[:items] # Create a dataset
53
54 # Populate the table
76613bc @jeremyevans Update README.rdoc
authored
55 items.insert(:name => 'abc', :price => rand * 100)
5193867 @jeremyevans README fixes from jinguoli, thanks!
authored
56 items.insert(:name => 'def', :price => rand * 100)
76613bc @jeremyevans Update README.rdoc
authored
57 items.insert(:name => 'ghi', :price => rand * 100)
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
58
59 # Print out the number of records
60 puts "Item count: #{items.count}"
61
62 # Print out the average price
63 puts "The average price is: #{items.avg(:price)}"
64
65 == The Sequel Console
66
da44775 @jeremyevans Mention some bin/sequel options in the README
authored
67 Sequel includes an IRB console for quick access to databases (usually referred to as <tt>bin/sequel</tt>). You can use it like this:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
68
69 sequel sqlite://test.db # test.db in current directory
70
71 You get an IRB session with the database object stored in DB.
72
da44775 @jeremyevans Mention some bin/sequel options in the README
authored
73 In addition to providing an IRB shell (the default behavior), bin/sequel also has support for migrating databases (-m and -M), dumping schema migrations (-d and -D), and copying databases (-C).
74
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
75 == An Introduction
76
77 Sequel is designed to take the hassle away from connecting to databases and manipulating them. Sequel deals with all the boring stuff like maintaining connections, formatting SQL correctly and fetching records so you can concentrate on your application.
78
ce1c70f @jeremyevans Documentation cleanup
authored
79 Sequel uses the concept of datasets to retrieve data. A Dataset object encapsulates an SQL query and supports chainability, letting you fetch data using a convenient Ruby DSL that is both concise and flexible.
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
80
d331dc5 @jeremyevans Fix first example in the README
authored
81 For example, the following one-liner returns the average GDP for countries in the middle east region:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
82
d331dc5 @jeremyevans Fix first example in the README
authored
83 DB[:countries].filter(:region => 'Middle East').avg(:GDP)
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
84
85 Which is equivalent to:
86
d331dc5 @jeremyevans Fix first example in the README
authored
87 SELECT avg(GDP) FROM countries WHERE region = 'Middle East'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
88
de44b4c @jeremyevans Minor updates to the README
authored
89 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:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
90
91 middle_east = DB[:countries].filter(:region => 'Middle East')
ce1c70f @jeremyevans Documentation cleanup
authored
92 middle_east.order(:name).each{|r| puts r[:name]}
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
93
de44b4c @jeremyevans Minor updates to the README
authored
94 Sequel also offers convenience methods for extracting data from Datasets, such as an extended +map+ method:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
95
96 middle_east.map(:name) #=> ['Egypt', 'Greece', 'Israel', ...]
97
de44b4c @jeremyevans Minor updates to the README
authored
98 Or getting results as a hash via +to_hash+, with one column as key and another as value:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
99
100 middle_east.to_hash(:name, :area) #=> {'Israel' => 20000, 'Greece' => 120000, ...}
101
102 == Getting Started
103
104 === Connecting to a database
105
de44b4c @jeremyevans Minor updates to the README
authored
106 To connect to a database you simply provide <tt>Sequel.connect</tt> with a URL:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
107
108 require 'sequel'
109 DB = Sequel.connect('sqlite://blog.db')
110
de44b4c @jeremyevans Minor updates to the README
authored
111 The connection URL can also include such stuff as the user name, password, and port:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
112
de44b4c @jeremyevans Minor updates to the README
authored
113 DB = Sequel.connect('postgres://user:password@host:port/database_name')
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
114
115 You can also specify optional parameters, such as the connection pool size, or loggers for logging SQL queries:
116
de44b4c @jeremyevans Minor updates to the README
authored
117 DB = Sequel.connect("postgres://user:password@host:port/database_name",
c701a9e @jeremyevans Minor tweak to README (Fixes #288)
authored
118 :max_connections => 10, :logger => Logger.new('log/db.log'))
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
119
120 You can specify a block to connect, which will disconnect from the database after it completes:
121
de44b4c @jeremyevans Minor updates to the README
authored
122 Sequel.connect('postgres://user:password@host:port/database_name'){|db| db[:posts].delete}
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
123
124 === Arbitrary SQL queries
125
de44b4c @jeremyevans Minor updates to the README
authored
126 You can execute arbitrary SQL code using <tt>Database#run</tt>:
48c4fc6 @jeremyevans Minor README updates
authored
127
128 DB.run("create table t (a text, b text)")
129 DB.run("insert into t values ('a', 'b')")
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
130
131 You can also create datasets based on raw SQL:
132
76613bc @jeremyevans Update README.rdoc
authored
133 dataset = DB['select id from items']
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
134 dataset.count # will return the number of records in the result set
135 dataset.map(:id) # will return an array containing all values of the id column in the result set
136
137 You can also fetch records with raw SQL through the dataset:
138
139 DB['select * from items'].each do |row|
140 p row
141 end
142
ce1c70f @jeremyevans Documentation cleanup
authored
143 You can use placeholders in your SQL string as well:
144
48c4fc6 @jeremyevans Minor README updates
authored
145 name = 'Jim'
ce1c70f @jeremyevans Documentation cleanup
authored
146 DB['select * from items where name = ?', name].each do |row|
147 p row
148 end
149
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
150 === Getting Dataset Instances
151
de44b4c @jeremyevans Minor updates to the README
authored
152 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:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
153
154 posts = DB.from(:posts)
76613bc @jeremyevans Update README.rdoc
authored
155 posts = DB[:posts] # same
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
156
76613bc @jeremyevans Update README.rdoc
authored
157 Datasets will only fetch records when you tell them to. They can be manipulated to filter records, change ordering, join tables, etc..
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
158
159 === Retrieving Records
160
de44b4c @jeremyevans Minor updates to the README
authored
161 You can retrieve all records by using the +all+ method:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
162
163 posts.all
a39fc8f @jeremyevans Update the README
authored
164 # SELECT * FROM posts
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
165
166 The all method returns an array of hashes, where each hash corresponds to a record.
167
de44b4c @jeremyevans Minor updates to the README
authored
168 You can also iterate through records one at a time using +each+:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
169
170 posts.each{|row| p row}
171
172 Or perform more advanced stuff:
173
76613bc @jeremyevans Update README.rdoc
authored
174 names_and_dates = posts.map{|r| [r[:name], r[:date]]}
175 old_posts, recent_posts = posts.partition{|r| r[:date] < Date.today - 7}
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
176
177 You can also retrieve the first record in a dataset:
178
179 posts.first
a39fc8f @jeremyevans Update the README
authored
180 # SELECT * FROM posts LIMIT 1
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
181
182 Or retrieve a single record with a specific value:
183
184 posts[:id => 1]
a39fc8f @jeremyevans Update the README
authored
185 # SELECT * FROM posts WHERE id = 1 LIMIT 1
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
186
187 If the dataset is ordered, you can also ask for the last record:
188
189 posts.order(:stamp).last
a39fc8f @jeremyevans Update the README
authored
190 # SELECT * FROM posts ORDER BY stamp DESC LIMIT 1
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
191
192 === Filtering Records
193
de44b4c @jeremyevans Minor updates to the README
authored
194 An easy way to filter records is to provide a hash of values to match to +filter+:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
195
196 my_posts = posts.filter(:category => 'ruby', :author => 'david')
a39fc8f @jeremyevans Update the README
authored
197 # WHERE category = 'ruby' AND author = 'david'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
198
199 You can also specify ranges:
200
201 my_posts = posts.filter(:stamp => (Date.today - 14)..(Date.today - 7))
a39fc8f @jeremyevans Update the README
authored
202 # WHERE stamp >= '2010-06-30' AND stamp <= '2010-07-07'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
203
ce1c70f @jeremyevans Documentation cleanup
authored
204 Or arrays of values:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
205
206 my_posts = posts.filter(:category => ['ruby', 'postgres', 'linux'])
a39fc8f @jeremyevans Update the README
authored
207 # WHERE category IN ('ruby', 'postgres', 'linux')
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
208
209 Sequel also accepts expressions:
210
de44b4c @jeremyevans Minor updates to the README
authored
211 my_posts = posts.filter{stamp > Date.today << 1}
a39fc8f @jeremyevans Update the README
authored
212 # WHERE stamp > '2010-06-14'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
213
76613bc @jeremyevans Update README.rdoc
authored
214 Some adapters will also let you specify Regexps:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
215
216 my_posts = posts.filter(:category => /ruby/i)
a39fc8f @jeremyevans Update the README
authored
217 # WHERE category ~* 'ruby'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
218
de44b4c @jeremyevans Minor updates to the README
authored
219 You can also use an inverse filter via +exclude+:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
220
de44b4c @jeremyevans Minor updates to the README
authored
221 my_posts = posts.exclude(:category => ['ruby', 'postgres', 'linux'])
222 # WHERE category NOT IN ('ruby', 'postgres', 'linux')
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
223
224 You can also specify a custom WHERE clause using a string:
225
226 posts.filter('stamp IS NOT NULL')
a39fc8f @jeremyevans Update the README
authored
227 # WHERE stamp IS NOT NULL
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
228
76613bc @jeremyevans Update README.rdoc
authored
229 You can use parameters in your string, as well:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
230
a39fc8f @jeremyevans Update the README
authored
231 author_name = 'JKR'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
232 posts.filter('(stamp < ?) AND (author != ?)', Date.today - 3, author_name)
a39fc8f @jeremyevans Update the README
authored
233 # WHERE (stamp < '2010-07-11') AND (author != 'JKR')
de44b4c @jeremyevans Minor updates to the README
authored
234 posts.filter{(stamp < Date.today - 3) & ~{:author => author_name}} # same as above
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
235
236 Datasets can also be used as subqueries:
237
de44b4c @jeremyevans Minor updates to the README
authored
238 DB[:items].filter('price > ?', DB[:items].select{avg(price) + 100})
a39fc8f @jeremyevans Update the README
authored
239 # WHERE price > (SELECT avg(price) + 100 FROM items)
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
240
241 After filtering you can retrieve the matching records by using any of the retrieval methods:
242
243 my_posts.each{|row| p row}
244
245 See the doc/dataset_filtering.rdoc file for more details.
246
247 === Summarizing Records
248
de44b4c @jeremyevans Minor updates to the README
authored
249 Counting records is easy using +count+:
a39fc8f @jeremyevans Update the README
authored
250
de44b4c @jeremyevans Minor updates to the README
authored
251 posts.filter(:category.like('%ruby%')).count
252 # SELECT COUNT(*) FROM posts WHERE category LIKE '%ruby%'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
253
de44b4c @jeremyevans Minor updates to the README
authored
254 And you can also query maximum/minimum values via +max+ and +min+:
a39fc8f @jeremyevans Update the README
authored
255
76613bc @jeremyevans Update README.rdoc
authored
256 max = DB[:history].max(:value)
a39fc8f @jeremyevans Update the README
authored
257 # SELECT max(value) FROM history
258
76613bc @jeremyevans Update README.rdoc
authored
259 min = DB[:history].min(:value)
a39fc8f @jeremyevans Update the README
authored
260 # SELECT min(value) FROM history
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
261
de44b4c @jeremyevans Minor updates to the README
authored
262 Or calculate a sum or average via +sum+ and +avg+:
263
76613bc @jeremyevans Update README.rdoc
authored
264 sum = DB[:items].sum(:price)
a39fc8f @jeremyevans Update the README
authored
265 # SELECT sum(price) FROM items
76613bc @jeremyevans Update README.rdoc
authored
266 avg = DB[:items].avg(:price)
a39fc8f @jeremyevans Update the README
authored
267 # SELECT avg(price) FROM items
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
268
269 === Ordering Records
270
de44b4c @jeremyevans Minor updates to the README
authored
271 Ordering datasets is simple using +order+:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
272
a39fc8f @jeremyevans Update the README
authored
273 posts.order(:stamp)
274 # ORDER BY stamp
275 posts.order(:stamp, :name)
276 # ORDER BY stamp, name
ce1c70f @jeremyevans Documentation cleanup
authored
277
de44b4c @jeremyevans Minor updates to the README
authored
278 Chaining +order+ doesn't work the same as +filter+:
ce1c70f @jeremyevans Documentation cleanup
authored
279
a39fc8f @jeremyevans Update the README
authored
280 posts.order(:stamp).order(:name)
281 # ORDER BY name
ce1c70f @jeremyevans Documentation cleanup
authored
282
de44b4c @jeremyevans Minor updates to the README
authored
283 The +order_append+ method chains this way, though:
ce1c70f @jeremyevans Documentation cleanup
authored
284
de44b4c @jeremyevans Minor updates to the README
authored
285 posts.order(:stamp).order_append(:name)
a39fc8f @jeremyevans Update the README
authored
286 # ORDER BY stamp, name
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
287
76613bc @jeremyevans Update README.rdoc
authored
288 You can also specify descending order:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
289
a39fc8f @jeremyevans Update the README
authored
290 posts.order(:stamp.desc)
291 # ORDER BY stamp DESC
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
292
ce1c70f @jeremyevans Documentation cleanup
authored
293 === Selecting Columns
294
de44b4c @jeremyevans Minor updates to the README
authored
295 Selecting specific columns to be returned is also simple using +select+:
ce1c70f @jeremyevans Documentation cleanup
authored
296
a39fc8f @jeremyevans Update the README
authored
297 posts.select(:stamp)
298 # SELECT stamp FROM posts
299 posts.select(:stamp, :name)
300 # SELECT stamp, name FROM posts
ce1c70f @jeremyevans Documentation cleanup
authored
301
de44b4c @jeremyevans Minor updates to the README
authored
302 Chaining +select+ works like +order+, not +filter+:
ce1c70f @jeremyevans Documentation cleanup
authored
303
a39fc8f @jeremyevans Update the README
authored
304 posts.select(:stamp).select(:name)
305 # SELECT name FROM posts
ce1c70f @jeremyevans Documentation cleanup
authored
306
de44b4c @jeremyevans Minor updates to the README
authored
307 As you might expect, there is an +order_append+ equivalent for +select+ called +select_append+:
ce1c70f @jeremyevans Documentation cleanup
authored
308
de44b4c @jeremyevans Minor updates to the README
authored
309 posts.select(:stamp).select_append(:name)
a39fc8f @jeremyevans Update the README
authored
310 # SELECT stamp, name FROM posts
ce1c70f @jeremyevans Documentation cleanup
authored
311
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
312 === Deleting Records
313
de44b4c @jeremyevans Minor updates to the README
authored
314 Deleting records from the table is done with +delete+:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
315
316 posts.filter('stamp < ?', Date.today - 3).delete
a39fc8f @jeremyevans Update the README
authored
317 # DELETE FROM posts WHERE stamp < '2010-07-11'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
318
de44b4c @jeremyevans Minor updates to the README
authored
319 Be very careful when deleting, as +delete+ affects all rows in the dataset.
320 +filter+ first, +delete+ second, unless you want to empty the table:
a39fc8f @jeremyevans Update the README
authored
321
322 # DO THIS:
323 posts.filter('stamp < ?', Date.today - 7).delete
324 # NOT THIS:
325 posts.delete.filter('stamp < ?', Date.today - 7)
76613bc @jeremyevans Update README.rdoc
authored
326
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
327 === Inserting Records
328
de44b4c @jeremyevans Minor updates to the README
authored
329 Inserting records into the table is done with +insert+:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
330
331 posts.insert(:category => 'ruby', :author => 'david')
a39fc8f @jeremyevans Update the README
authored
332 # INSERT INTO posts (category, author) VALUES ('ruby', 'david')
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
333
334 === Updating Records
335
de44b4c @jeremyevans Minor updates to the README
authored
336 Updating records in the table is done with +update+:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
337
338 posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived')
a39fc8f @jeremyevans Update the README
authored
339 # UPDATE posts SET state = 'archived' WHERE stamp < '2010-07-07'
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
340
76613bc @jeremyevans Update README.rdoc
authored
341 You can reference table columns when choosing what values to set:
342
343 posts.filter{|o| o.stamp < Date.today - 7}.update(:backup_number => :backup_number + 1)
a39fc8f @jeremyevans Update the README
authored
344 # UPDATE posts SET backup_number = backup_number + 1 WHERE stamp < '2010-07-07'
76613bc @jeremyevans Update README.rdoc
authored
345
de44b4c @jeremyevans Minor updates to the README
authored
346 As with +delete+, +update+ affects all rows in the dataset, so +filter+ first,
347 +update+ second, unless you want to update all rows:
a39fc8f @jeremyevans Update the README
authored
348
349 # DO THIS:
350 posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived')
351 # NOT THIS:
352 posts.update(:state => 'archived').filter('stamp < ?', Date.today - 7)
76613bc @jeremyevans Update README.rdoc
authored
353
de44b4c @jeremyevans Minor updates to the README
authored
354 === Transactions
355
356 You can wrap some code in a database transaction using the <tt>Database#transaction</tt> method:
357
358 DB.transaction do
359 posts.insert(:category => 'ruby', :author => 'david')
360 posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived')
361 end
362
363 If the block does not raise an exception, the transaction will be committed.
364 If the block does raise an exception, the transaction will be rolled back,
365 and the exception will be reraised. If you want to rollback the transaction
366 and not raise an exception outside the block, you can raise the
367 <tt>Sequel::Rollback</tt> exception inside the block:
368
369 DB.transaction do
370 posts.insert(:category => 'ruby', :author => 'david')
371 if posts.filter('stamp < ?', Date.today - 7).update(:state => 'archived') == 0
372 raise Sequel::Rollback
373 end
374 end
375
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
376 === Joining Tables
377
76613bc @jeremyevans Update README.rdoc
authored
378 Sequel makes it easy to join tables:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
379
380 order_items = DB[:items].join(:order_items, :item_id => :id).
381 filter(:order_items__order_id => 1234)
de44b4c @jeremyevans Minor updates to the README
authored
382 # SELECT * FROM items INNER JOIN order_items
383 # ON order_items.item_id = items.id
384 # WHERE order_items.order_id = 1234
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
385
386 You can then do anything you like with the dataset:
387
388 order_total = order_items.sum(:price)
de44b4c @jeremyevans Minor updates to the README
authored
389 # SELECT sum(price) FROM items INNER JOIN order_items
390 # ON order_items.item_id = items.id
391 # WHERE order_items.order_id = 1234
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
392
393 === Graphing Datasets
394
ce1c70f @jeremyevans Documentation cleanup
authored
395 When retrieving records from joined datasets, you get the results in a single hash, which is subject to clobbering if you have columns with the same name in multiple tables:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
396
397 DB[:items].join(:order_items, :item_id => :id).first
e3b938c Fix typo in 'Graphing Datasets' section of README.rdoc
lachlan authored
398 => {:id=>order_items.id, :item_id=>order_items.item_id}
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
399
de44b4c @jeremyevans Minor updates to the README
authored
400 Using +graph+, you can split the result hashes into subhashes, one per join:
778a032 @jeremyevans Combine sequel and sequel_core into one gem
authored
401
402 DB[:items].graph(:order_items, :item_id => :id).first
403 => {:items=>{:id=>items.id}, :order_items=>{:id=>order_items.id, :item_id=>order_items.item_id}}
404
de44b4c @jeremyevans Minor updates to the README
authored
405 == Column references in Sequel
ce1c70f @jeremyevans Documentation cleanup
authored
406
a39fc8f @jeremyevans Update the README
authored
407 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:
ce1c70f @jeremyevans Documentation cleanup
authored
408
a39fc8f @jeremyevans Update the README
authored
409 items.filter(:x => 1)
410 # SELECT * FROM items WHERE (x = 1)
411 items.filter(1 => :x)
412 # SELECT * FROM items WHERE (1 = x)"
ce1c70f @jeremyevans Documentation cleanup
authored
413
76613bc @jeremyevans Update README.rdoc
authored
414 Ruby strings are generally treated as SQL strings:
415
a39fc8f @jeremyevans Update the README
authored
416 items.filter(:x => 'x')
417 # SELECT * FROM items WHERE (x = 'x')
76613bc @jeremyevans Update README.rdoc
authored
418
ce1c70f @jeremyevans Documentation cleanup
authored
419 === Qualifying column names
420
de44b4c @jeremyevans Minor updates to the README
authored
421 Column references can be qualified by using the double underscore special notation <tt>:table__column</tt>:
ce1c70f @jeremyevans Documentation cleanup
authored
422
a39fc8f @jeremyevans Update the README
authored
423 items.literal(:items__price)
424 # items.price
ce1c70f @jeremyevans Documentation cleanup
authored
425
de44b4c @jeremyevans Minor updates to the README
authored
426 Another way to qualify columns is to use the +qualify+ method:
427
428 items.literal(:price.qualify(:items))
429 # items.price
430
ce1c70f @jeremyevans Documentation cleanup
authored
431 === Column aliases
432
de44b4c @jeremyevans Minor updates to the README
authored
433 You can also alias columns by using the triple undersecore special notation <tt>:column___alias</tt> or <tt>:table__column___alias</tt>:
ce1c70f @jeremyevans Documentation cleanup
authored
434
a39fc8f @jeremyevans Update the README
authored
435 items.literal(:price___p)
436 # price AS p
437 items.literal(:items__price___p)
438 # items.price AS p
ce1c70f @jeremyevans Documentation cleanup
authored
439
de44b4c @jeremyevans Minor updates to the README
authored
440 Another way to alias columns is to use the +as+ method:
ce1c70f @jeremyevans Documentation cleanup
authored
441
a39fc8f @jeremyevans Update the README
authored
442 items.literal(:price.as(:p))
443 # price AS p
ce1c70f @jeremyevans Documentation cleanup
authored
444
a0181eb @jeremyevans Various doc fixes
authored
445 == Sequel Models
3ece811 @ciconia In preparation for 1.0.
ciconia authored
446
76613bc @jeremyevans Update README.rdoc
authored
447 A model class wraps a dataset, and an instance of that class wraps a single record in the dataset.
3ece811 @ciconia In preparation for 1.0.
ciconia authored
448
de44b4c @jeremyevans Minor updates to the README
authored
449 Model classes are defined as regular Ruby classes inheriting from <tt>Sequel::Model</tt>:
a0181eb @jeremyevans Various doc fixes
authored
450
01a6887 @jeremyevans Fix typo in README, thanks mwlang
authored
451 DB = Sequel.connect('sqlite://blog.db')
a0181eb @jeremyevans Various doc fixes
authored
452 class Post < Sequel::Model
453 end
454
de44b4c @jeremyevans Minor updates to the README
authored
455 Sequel model classes assume that the table name is an underscored plural of the class name:
a0181eb @jeremyevans Various doc fixes
authored
456
82d2593 @jeremyevans Some additions to sequel/README from the Google Code Wiki
authored
457 Post.table_name #=> :posts
458
de44b4c @jeremyevans Minor updates to the README
authored
459 You can explicitly set the table name or even the dataset used:
82d2593 @jeremyevans Some additions to sequel/README from the Google Code Wiki
authored
460
461 class Post < Sequel::Model(:my_posts)
a0181eb @jeremyevans Various doc fixes
authored
462 end
82d2593 @jeremyevans Some additions to sequel/README from the Google Code Wiki
authored
463 # or:
464 Post.set_dataset :my_posts
76613bc @jeremyevans Update README.rdoc
authored
465
de44b4c @jeremyevans Minor updates to the README
authored
466 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:
76613bc @jeremyevans Update README.rdoc
authored
467
468 Post.set_dataset DB[:my_posts].filter(:category => 'ruby')
469 Post.set_dataset DB[:my_posts].select(:id, :name).order(:date)
82d2593 @jeremyevans Some additions to sequel/README from the Google Code Wiki
authored
470
a0181eb @jeremyevans Various doc fixes
authored
471 === Model instances
3ece811 @ciconia In preparation for 1.0.
ciconia authored
472
de44b4c @jeremyevans Minor updates to the README
authored
473 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:
3ece811 @ciconia In preparation for 1.0.
ciconia authored
474
a0181eb @jeremyevans Various doc fixes
authored
475 post = Post[123]
3ece811 @ciconia In preparation for 1.0.
ciconia authored
476
de44b4c @jeremyevans Minor updates to the README
authored
477 The +pk+ method is used to retrieve the record's primary key value:
3ece811 @ciconia In preparation for 1.0.
ciconia authored
478
a0181eb @jeremyevans Various doc fixes
authored
479 post.pk #=> 123
3ece811 @ciconia In preparation for 1.0.
ciconia authored
480
a0181eb @jeremyevans Various doc fixes
authored
481 Sequel models allow you to use any column as a primary key, and even composite keys made from multiple columns:
3ece811 @ciconia In preparation for 1.0.
ciconia authored
482
a0181eb @jeremyevans Various doc fixes
authored
483 class Post < Sequel::Model
484 set_primary_key [:category, :title]
485 end
3ece811 @ciconia In preparation for 1.0.
ciconia authored
486
a0181eb @jeremyevans Various doc fixes
authored
487 post = Post['ruby', 'hello world']
488 post.pk #=> ['ruby', 'hello world']
3ece811 @ciconia In preparation for 1.0.
ciconia authored
489
de44b4c @jeremyevans Minor updates to the README
authored
490 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:
491
492 Post.no_primary_key
3ece811 @ciconia In preparation for 1.0.
ciconia authored
493
de44b4c @jeremyevans Minor updates to the README
authored
494 A single model instance can also be fetched by specifying a condition:
3ece811 @ciconia In preparation for 1.0.
ciconia authored
495
a0181eb @jeremyevans Various doc fixes
authored
496 post = Post[:title => 'hello world']
de44b4c @jeremyevans Minor updates to the README
authored
497 post = Post.first{num_comments < 10}
3ece811 @ciconia In preparation for 1.0.
ciconia authored
498
a0181eb @jeremyevans Various doc fixes
authored
499 === Iterating over records
3ece811 @ciconia In preparation for 1.0.
ciconia authored
500
de44b4c @jeremyevans Minor updates to the README
authored
501 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.:
3ece811 @ciconia In preparation for 1.0.
ciconia authored
502
a0181eb @jeremyevans Various doc fixes
authored
503 Post.filter(:category => 'ruby').each{|post| p post}
3ece811 @ciconia In preparation for 1.0.
ciconia authored
504
a0181eb @jeremyevans Various doc fixes
authored
505 You can also manipulate the records in the dataset:
3ece811 @ciconia In preparation for 1.0.
ciconia authored
506
de44b4c @jeremyevans Minor updates to the README
authored
507 Post.filter{num_comments < 7}.delete
17c0143 @jeremyevans Small documentation updates to the READMEs
authored
508 Post.filter(:title.like(/ruby/)).update(:category => 'ruby')
3ece811 @ciconia In preparation for 1.0.
ciconia authored
509
a0181eb @jeremyevans Various doc fixes
authored
510 === Accessing record values
3ece811 @ciconia In preparation for 1.0.
ciconia authored
511
de44b4c @jeremyevans Minor updates to the README
authored
512 A model instance stores its values as a hash with column symbol keys, which you can access directly via the +values+ method:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
513
a0181eb @jeremyevans Various doc fixes
authored
514 post.values #=> {:id => 123, :category => 'ruby', :title => 'hello world'}
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
515
a39fc8f @jeremyevans Update the README
authored
516 You can read the record values as object attributes, assuming the attribute names are valid columns in the model's dataset:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
517
a0181eb @jeremyevans Various doc fixes
authored
518 post.id #=> 123
519 post.title #=> 'hello world'
a39fc8f @jeremyevans Update the README
authored
520
de44b4c @jeremyevans Minor updates to the README
authored
521 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:
a39fc8f @jeremyevans Update the README
authored
522
523 post[:id] #=> 123
524 post[:title] #=> 'hello world'
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
525
de44b4c @jeremyevans Minor updates to the README
authored
526 You can also modify record values using attribute setters or the +set+ method:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
527
a0181eb @jeremyevans Various doc fixes
authored
528 post.title = 'hey there'
315bd5e @jeremyevans Minor changes to the README
authored
529 # or
530 post.set(:title=>'hey there')
531
de44b4c @jeremyevans Minor updates to the README
authored
532 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:
315bd5e @jeremyevans Minor changes to the README
authored
533
a0181eb @jeremyevans Various doc fixes
authored
534 post.save
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
535
de44b4c @jeremyevans Minor updates to the README
authored
536 You can modify record values and save the changes to the object in a single method call using the +update+ method:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
537
ce1c70f @jeremyevans Documentation cleanup
authored
538 post.update(:title => 'hey there')
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
539
a0181eb @jeremyevans Various doc fixes
authored
540 === Creating new records
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
541
de44b4c @jeremyevans Minor updates to the README
authored
542 New records can be created by calling <tt>Model.create</tt>:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
543
a0181eb @jeremyevans Various doc fixes
authored
544 post = Post.create(:title => 'hello world')
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
545
315bd5e @jeremyevans Minor changes to the README
authored
546 Another way is to construct a new instance and save it later:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
547
a0181eb @jeremyevans Various doc fixes
authored
548 post = Post.new
549 post.title = 'hello world'
550 post.save
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
551
de44b4c @jeremyevans Minor updates to the README
authored
552 You can also supply a block to <tt>Model.new</tt> and <tt>Model.create</tt>:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
553
a0181eb @jeremyevans Various doc fixes
authored
554 post = Post.new do |p|
555 p.title = 'hello world'
556 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
557
315bd5e @jeremyevans Minor changes to the README
authored
558 post = Post.create{|p| p.title = 'hello world'}
559
a0181eb @jeremyevans Various doc fixes
authored
560 === Hooks
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
561
de44b4c @jeremyevans Minor updates to the README
authored
562 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:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
563
a0181eb @jeremyevans Various doc fixes
authored
564 class Post < Sequel::Model
76613bc @jeremyevans Update README.rdoc
authored
565 def after_create
315bd5e @jeremyevans Minor changes to the README
authored
566 super
1d2404f @jeremyevans Fix confusing code in after_create example in sequel/README
authored
567 author.increase_post_count
a0181eb @jeremyevans Various doc fixes
authored
568 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
569
76613bc @jeremyevans Update README.rdoc
authored
570 def after_destroy
315bd5e @jeremyevans Minor changes to the README
authored
571 super
1d2404f @jeremyevans Fix confusing code in after_create example in sequel/README
authored
572 author.decrease_post_count
a0181eb @jeremyevans Various doc fixes
authored
573 end
574 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
575
de44b4c @jeremyevans Minor updates to the README
authored
576 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.
315bd5e @jeremyevans Minor changes to the README
authored
577
be09262 @jeremyevans Various RDoc documentation improvements
authored
578 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 and constraints for data integrity.
76613bc @jeremyevans Update README.rdoc
authored
579
a0181eb @jeremyevans Various doc fixes
authored
580 === Deleting records
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
581
de44b4c @jeremyevans Minor updates to the README
authored
582 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:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
583
de44b4c @jeremyevans Minor updates to the README
authored
584 post.delete # => bypasses hooks
585 post.destroy # => runs hooks
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
586
de44b4c @jeremyevans Minor updates to the README
authored
587 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:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
588
de44b4c @jeremyevans Minor updates to the README
authored
589 Post.filter(:category => 32).delete # => bypasses hooks
590 Post.filter(:category => 32).destroy # => runs hooks
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
591
de44b4c @jeremyevans Minor updates to the README
authored
592 Please note that if <tt>Model.destroy</tt> is called, each record is deleted
593 separately, but <tt>Model.delete</tt> deletes all matching records with a single
a39fc8f @jeremyevans Update the README
authored
594 SQL query.
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
595
a0181eb @jeremyevans Various doc fixes
authored
596 === Associations
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
597
de44b4c @jeremyevans Minor updates to the README
authored
598 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:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
599
a0181eb @jeremyevans Various doc fixes
authored
600 class Post < Sequel::Model
601 many_to_one :author
602 one_to_many :comments
603 many_to_many :tags
604 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
605
de44b4c @jeremyevans Minor updates to the README
authored
606 +many_to_one+ and +one_to_one+ create a getter and setter for each model object:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
607
a0181eb @jeremyevans Various doc fixes
authored
608 post = Post.create(:name => 'hi!')
609 post.author = Author[:name => 'Sharon']
610 post.author
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
611
de44b4c @jeremyevans Minor updates to the README
authored
612 +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:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
613
a0181eb @jeremyevans Various doc fixes
authored
614 post = Post.create(:name => 'hi!')
615 post.comments
de44b4c @jeremyevans Minor updates to the README
authored
616
a0181eb @jeremyevans Various doc fixes
authored
617 comment = Comment.create(:text=>'hi')
618 post.add_comment(comment)
619 post.remove_comment(comment)
17c0143 @jeremyevans Small documentation updates to the READMEs
authored
620 post.remove_all_comments
de44b4c @jeremyevans Minor updates to the README
authored
621
a0181eb @jeremyevans Various doc fixes
authored
622 tag = Tag.create(:tag=>'interesting')
623 post.add_tag(tag)
624 post.remove_tag(tag)
17c0143 @jeremyevans Small documentation updates to the READMEs
authored
625 post.remove_all_tags
a39fc8f @jeremyevans Update the README
authored
626
627 Note that the remove_* and remove_all_* methods do not delete the object from the database, they merely disassociate the associated object from the receiver.
a0181eb @jeremyevans Various doc fixes
authored
628
76613bc @jeremyevans Update README.rdoc
authored
629 All associations add a dataset method that can be used to further filter or reorder the returned objects, or modify all of them:
630
631 # Delete all of this post's comments from the database
bfb52ce Fix a few typos in 'Associations' section of README.rdoc
lachlan authored
632 post.comments_dataset.destroy
76613bc @jeremyevans Update README.rdoc
authored
633
634 # Return all tags related to this post with no subscribers, ordered by the tag's name
bfb52ce Fix a few typos in 'Associations' section of README.rdoc
lachlan authored
635 post.tags_dataset.filter(:subscribers=>0).order(:name).all
76613bc @jeremyevans Update README.rdoc
authored
636
a0181eb @jeremyevans Various doc fixes
authored
637 === Eager Loading
638
de44b4c @jeremyevans Minor updates to the README
authored
639 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.
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
640
a0181eb @jeremyevans Various doc fixes
authored
641 class Person < Sequel::Model
642 one_to_many :posts, :eager=>[:tags]
643 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
644
a0181eb @jeremyevans Various doc fixes
authored
645 class Post < Sequel::Model
646 many_to_one :person
647 one_to_many :replies
648 many_to_many :tags
649 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
650
a0181eb @jeremyevans Various doc fixes
authored
651 class Tag < Sequel::Model
652 many_to_many :posts
653 many_to_many :replies
654 end
655
656 class Reply < Sequel::Model
657 many_to_one :person
658 many_to_one :post
659 many_to_many :tags
660 end
661
662 # Eager loading via .eager
663 Post.eager(:person).all
2b572b9 @jeremyevans In the RDoc, give an example of .eager being used with filters, and d…
authored
664
665 # eager is a dataset method, so it works with filters/orders/limits/etc.
de44b4c @jeremyevans Minor updates to the README
authored
666 Post.filter{topic > 'M'}.order(:date).limit(5).eager(:person).all
a0181eb @jeremyevans Various doc fixes
authored
667
668 person = Person.first
669 # Eager loading via :eager (will eagerly load the tags for this person's posts)
670 person.posts
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
671
a0181eb @jeremyevans Various doc fixes
authored
672 # These are equivalent
673 Post.eager(:person, :tags).all
674 Post.eager(:person).eager(:tags).all
675
676 # Cascading via .eager
677 Tag.eager(:posts=>:replies).all
678
679 # Will also grab all associated posts' tags (because of :eager)
680 Reply.eager(:person=>:posts).all
681
682 # No depth limit (other than memory/stack), and will also grab posts' tags
683 # Loads all people, their posts, their posts' tags, replies to those posts,
684 # the person for each reply, the tag for each reply, and all posts and
685 # replies that have that tag. Uses a total of 8 queries.
be09262 @jeremyevans Various RDoc documentation improvements
authored
686 Person.eager(:posts=>{:replies=>[:person, {:tags=>[:posts, :replies]}]}).all
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
687
be09262 @jeremyevans Various RDoc documentation improvements
authored
688 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 API is very similar. Note that using +eager_graph+ to eagerly load multiple <tt>*_to_many</tt> 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.
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
689
4139dd9 @jeremyevans Update the docs with information on dynamic customization of regular …
authored
690 You can dynamically customize the eagerly loaded dataset by using using a proc. This proc is passed the dataset used for eager loading, and should return a modified copy of that dataset:
691
692 # Eagerly load only replies containing 'foo'
693 Post.eager(:replies=>proc{|ds| ds.filter(text.like('%foo%'))}).all
694
695 This also works when using +eager_graph+, in which case the proc is called with dataset to graph into the current dataset:
696
697 Post.eager_graph(:replies=>proc{|ds| ds.filter(text.like('%foo%'))}).all
698
699 You can dynamically customize eager loads for both +eager+ and +eager_graph+ while also cascading, by making the value a single entry hash with the proc as a key, and the cascaded associations as the value:
700
701 # Eagerly load only replies containing 'foo', and the person and tags for those replies
702 Post.eager(:replies=>{proc{|ds| ds.filter(text.like('%foo%'))}=>[:person, :tags]}).all
703
a0181eb @jeremyevans Various doc fixes
authored
704 === Extending the underlying dataset
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
705
a0181eb @jeremyevans Various doc fixes
authored
706 The obvious way to add table-wide logic is to define class methods to the model class definition. That way you can define subsets of the underlying dataset, change the ordering, or perform actions on multiple records:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
707
a0181eb @jeremyevans Various doc fixes
authored
708 class Post < Sequel::Model
709 def self.posts_with_few_comments
de44b4c @jeremyevans Minor updates to the README
authored
710 filter{num_comments < 30}
a0181eb @jeremyevans Various doc fixes
authored
711 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
712
a0181eb @jeremyevans Various doc fixes
authored
713 def self.clean_posts_with_few_comments
714 posts_with_few_comments.delete
715 end
716 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
717
de44b4c @jeremyevans Minor updates to the README
authored
718 You can also implement table-wide logic by defining methods on the dataset using +def_dataset_method+:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
719
a0181eb @jeremyevans Various doc fixes
authored
720 class Post < Sequel::Model
721 def_dataset_method(:posts_with_few_comments) do
de44b4c @jeremyevans Minor updates to the README
authored
722 filter{num_comments < 30}
a0181eb @jeremyevans Various doc fixes
authored
723 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
724
a0181eb @jeremyevans Various doc fixes
authored
725 def_dataset_method(:clean_posts_with_few_comments) do
726 posts_with_few_comments.delete
727 end
728 end
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
729
a0181eb @jeremyevans Various doc fixes
authored
730 This is the recommended way of implementing table-wide operations, and allows you to have access to your model API from filtered datasets as well:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
731
ce1c70f @jeremyevans Documentation cleanup
authored
732 Post.filter(:category => 'ruby').clean_posts_with_few_comments
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
733
de44b4c @jeremyevans Minor updates to the README
authored
734 Sequel models also provide a +subset+ class method that creates a dataset method with a simple filter:
de172d9 @ciconia Merged new associations branch into trunk.
ciconia authored
735
a0181eb @jeremyevans Various doc fixes
authored
736 class Post < Sequel::Model
de44b4c @jeremyevans Minor updates to the README
authored
737 subset(:posts_with_few_comments){num_comments < 30}
ce1c70f @jeremyevans Documentation cleanup
authored
738 subset :invisible, ~:visible
a0181eb @jeremyevans Various doc fixes
authored
739 end
740
76613bc @jeremyevans Update README.rdoc
authored
741 === Model Validations
742
de44b4c @jeremyevans Minor updates to the README
authored
743 You can define a +validate+ method for your model, which +save+
76613bc @jeremyevans Update README.rdoc
authored
744 will check before attempting to save the model in the database.
745 If an attribute of the model isn't valid, you should add a error
de44b4c @jeremyevans Minor updates to the README
authored
746 message for that attribute to the model object's +errors+. If an
747 object has any errors added by the validate method, +save+ will
748 raise an error or return false depending on how it is configured
749 (the +raise_on_save_failure+ flag).
76613bc @jeremyevans Update README.rdoc
authored
750
751 class Post < Sequel::Model
752 def validate
de44b4c @jeremyevans Minor updates to the README
authored
753 super
315bd5e @jeremyevans Minor changes to the README
authored
754 errors.add(:name, "can't be empty") if name.empty?
755 errors.add(:written_on, "should be in the past") if written_on >= Time.now
a0181eb @jeremyevans Various doc fixes
authored
756 end
757 end
98169b7 @jeremyevans Various small doc fixes
authored
758
Something went wrong with that request. Please try again.