sequel 3.11.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/CHANGELOG +70 -0
  2. data/Rakefile +1 -1
  3. data/doc/active_record.rdoc +896 -0
  4. data/doc/advanced_associations.rdoc +46 -31
  5. data/doc/association_basics.rdoc +14 -9
  6. data/doc/dataset_basics.rdoc +3 -3
  7. data/doc/migration.rdoc +1011 -0
  8. data/doc/model_hooks.rdoc +198 -0
  9. data/doc/querying.rdoc +811 -86
  10. data/doc/release_notes/3.12.0.txt +304 -0
  11. data/doc/sharding.rdoc +17 -0
  12. data/doc/sql.rdoc +537 -0
  13. data/doc/validations.rdoc +501 -0
  14. data/lib/sequel/adapters/jdbc.rb +19 -27
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
  16. data/lib/sequel/adapters/mysql.rb +5 -4
  17. data/lib/sequel/adapters/odbc.rb +3 -2
  18. data/lib/sequel/adapters/shared/mssql.rb +7 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +2 -8
  21. data/lib/sequel/adapters/shared/sqlite.rb +2 -5
  22. data/lib/sequel/adapters/sqlite.rb +4 -4
  23. data/lib/sequel/core.rb +0 -1
  24. data/lib/sequel/database.rb +2 -1060
  25. data/lib/sequel/database/connecting.rb +227 -0
  26. data/lib/sequel/database/dataset.rb +58 -0
  27. data/lib/sequel/database/dataset_defaults.rb +127 -0
  28. data/lib/sequel/database/logging.rb +62 -0
  29. data/lib/sequel/database/misc.rb +246 -0
  30. data/lib/sequel/database/query.rb +390 -0
  31. data/lib/sequel/database/schema_generator.rb +7 -3
  32. data/lib/sequel/database/schema_methods.rb +351 -7
  33. data/lib/sequel/dataset/actions.rb +9 -2
  34. data/lib/sequel/dataset/misc.rb +6 -2
  35. data/lib/sequel/dataset/mutation.rb +3 -11
  36. data/lib/sequel/dataset/query.rb +49 -6
  37. data/lib/sequel/exceptions.rb +3 -0
  38. data/lib/sequel/extensions/migration.rb +395 -113
  39. data/lib/sequel/extensions/schema_dumper.rb +21 -13
  40. data/lib/sequel/model.rb +27 -25
  41. data/lib/sequel/model/associations.rb +72 -34
  42. data/lib/sequel/model/base.rb +74 -18
  43. data/lib/sequel/model/errors.rb +8 -1
  44. data/lib/sequel/plugins/active_model.rb +8 -0
  45. data/lib/sequel/plugins/association_pks.rb +87 -0
  46. data/lib/sequel/plugins/association_proxies.rb +8 -0
  47. data/lib/sequel/plugins/boolean_readers.rb +12 -6
  48. data/lib/sequel/plugins/caching.rb +14 -7
  49. data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
  50. data/lib/sequel/plugins/composition.rb +2 -1
  51. data/lib/sequel/plugins/force_encoding.rb +10 -7
  52. data/lib/sequel/plugins/hook_class_methods.rb +12 -11
  53. data/lib/sequel/plugins/identity_map.rb +9 -0
  54. data/lib/sequel/plugins/instance_hooks.rb +23 -13
  55. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  56. data/lib/sequel/plugins/many_through_many.rb +18 -4
  57. data/lib/sequel/plugins/nested_attributes.rb +1 -0
  58. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  59. data/lib/sequel/plugins/rcte_tree.rb +9 -8
  60. data/lib/sequel/plugins/schema.rb +8 -0
  61. data/lib/sequel/plugins/serialization.rb +1 -3
  62. data/lib/sequel/plugins/sharding.rb +135 -0
  63. data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
  64. data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
  65. data/lib/sequel/plugins/string_stripper.rb +26 -0
  66. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
  67. data/lib/sequel/plugins/timestamps.rb +15 -2
  68. data/lib/sequel/plugins/touch.rb +13 -0
  69. data/lib/sequel/plugins/update_primary_key.rb +48 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +8 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +17 -20
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/postgres_spec.rb +5 -5
  75. data/spec/core/core_sql_spec.rb +17 -1
  76. data/spec/core/database_spec.rb +17 -5
  77. data/spec/core/dataset_spec.rb +31 -8
  78. data/spec/core/schema_generator_spec.rb +8 -1
  79. data/spec/core/schema_spec.rb +13 -0
  80. data/spec/extensions/association_pks_spec.rb +85 -0
  81. data/spec/extensions/hook_class_methods_spec.rb +9 -9
  82. data/spec/extensions/migration_spec.rb +339 -219
  83. data/spec/extensions/schema_dumper_spec.rb +28 -17
  84. data/spec/extensions/sharding_spec.rb +272 -0
  85. data/spec/extensions/single_table_inheritance_spec.rb +92 -4
  86. data/spec/extensions/skip_create_refresh_spec.rb +17 -0
  87. data/spec/extensions/string_stripper_spec.rb +23 -0
  88. data/spec/extensions/update_primary_key_spec.rb +65 -0
  89. data/spec/extensions/validation_class_methods_spec.rb +5 -5
  90. data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
  91. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
  92. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  93. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  94. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
  95. data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
  96. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
  97. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
  98. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
  99. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
  100. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
  101. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
  102. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
  103. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
  104. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  105. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
  106. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
  107. data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
  108. data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
  109. data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
  110. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  111. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
  112. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  113. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
  114. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  115. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
  116. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
  117. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  118. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  119. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
  120. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
  121. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
  122. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
  123. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
  124. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
  125. data/spec/integration/eager_loader_test.rb +20 -20
  126. data/spec/integration/migrator_test.rb +187 -0
  127. data/spec/integration/plugin_test.rb +150 -0
  128. data/spec/integration/schema_test.rb +13 -2
  129. data/spec/model/associations_spec.rb +41 -14
  130. data/spec/model/base_spec.rb +69 -0
  131. data/spec/model/eager_loading_spec.rb +7 -3
  132. data/spec/model/record_spec.rb +79 -4
  133. data/spec/model/validations_spec.rb +21 -9
  134. metadata +66 -5
  135. data/doc/schema.rdoc +0 -36
  136. data/lib/sequel/database/schema_sql.rb +0 -320
@@ -0,0 +1,198 @@
1
+ = Model Hooks
2
+
3
+ This guide is based on http://guides.rubyonrails.org/activerecord_validations_callbacks.html
4
+
5
+ == Overview
6
+
7
+ Model hooks, also known as model callbacks, are used to specify actions that occur at a given point in a model instance's lifecycle, such as before or after the model object is saved, created, updated, destroyed, or validated.
8
+
9
+ == Basic Usage
10
+
11
+ <tt>Sequel::Model</tt> uses instance methods for hooks. To define a hook on a model, you just add an instance method to the model class:
12
+
13
+ class Album < Sequel::Model
14
+ def before_create
15
+ self.created_at ||= Time.now
16
+ super
17
+ end
18
+ end
19
+
20
+ The one important thing to note here is the call to +super+ inside the hook. Whenever you override one of Sequel::Model's methods, you should be calling +super+ to get the default behavior. Many of Sequel's built in plugins work by overriding the hook methods and calling +super+. If you use these plugins and override the hook methods but do not call +super+, it's likely the plugins will not work correctly.
21
+
22
+ == Available Hooks
23
+
24
+ Sequel calls hooks in the following order when saving/creating a new object (one that does not already exist in the database):
25
+
26
+ * +before_validation+
27
+ * +after_validation+
28
+ * +before_save+
29
+ * +before_create+
30
+ * INSERT QUERY
31
+ * +after_create+
32
+ * +after_save+
33
+
34
+ Sequel calls hooks in the following order when saving an existing object:
35
+
36
+ * +before_validation+
37
+ * +after_validation+
38
+ * +before_save+
39
+ * +before_update+
40
+ * UPDATE QUERY
41
+ * +after_update+
42
+ * +after_save+
43
+
44
+ Note that all of the hook calls are the same, except that +before_create+ and +after_create+ are used for a new object, and +before_update+ and +after_update+ are used for an existing object. Note that +before_save+ is called in both cases, before either +before_create+ or +before_update+, and that +after_save+ is also called in both cases, after either +after_create+ or +after_update+.
45
+
46
+ Also note that the validation hooks are not called if the <tt>:validate => false</tt> option is passed to save. However, the validation hooks are called if you call <tt>Model#valid?</tt> manually:
47
+
48
+ * +before_validation+
49
+ * VALIDATION HAPPENS
50
+ * +after_validation+
51
+
52
+ Sequel calls hooks in the following order when destroying an existing object:
53
+
54
+ * +before_destroy+
55
+ * DELETE QUERY
56
+ * +after_destroy+
57
+
58
+ Note that these hooks are only called when using <tt>Model#destroy</tt>, they are not called if you use <tt>Model#delete</tt>.
59
+
60
+ <tt>Sequel::Model</tt> does support one additional hook, +after_intialize+, which is called after the model object has been initalized. It can be used to set default attribute values for new objects, since by default new <tt>Sequel::Model</tt> objects have no attributes, and the attributes are not filled in until the model object is saved. You should be careful when you are using +after_initialize+, since it is called for every created record. So if you run a query that returns 1000 model objects, it will be called 1000 times.
61
+
62
+ == Running Hooks
63
+
64
+ Sequel does not provide a simple way to turn off the running of save/create/update hooks. If you attempt to save a model object, the save hooks are always called. All model instance methods that modify the database call save in some manner, so you can be sure that if you define the hooks, they will be called when you save the object.
65
+
66
+ However, you should note that there are plenty of ways to modify the database without saving a model object. One example is by using plain datasets, or one of the model's dataset methods:
67
+
68
+ Album.filter(:name=>'RF').update(:copies_sold=>:copies_sold + 1)
69
+ # UPDATE albums SET copies_sold = copies_sold + 1 WHERE name = 'RF'
70
+
71
+ In this case, the +update+ method is called on the dataset returned by <tt>Album.filter</tt>. Even if there is only a single object with the name RF, this will not call any hooks. If you want model hooks to be called, you need to make sure to operate on a model object:
72
+
73
+ album = Album.first(:name=>'RF')
74
+ album.update(:copies_sold=>album.copies_sold + 1)
75
+ # UPDATE albums SET copies_sold = 2 WHERE id = 1
76
+
77
+ For the destroy hooks, you need to make sure you call +destroy+ on the object:
78
+
79
+ album.destroy # runs destroy hooks
80
+
81
+ == Skipping Hooks
82
+
83
+ Sequel makes it easy to skip destroy hooks by calling +delete+ instead of +destroy+:
84
+
85
+ album.delete # does not run destroy hooks
86
+
87
+ However, skipping hooks is a bad idea in general and should be avoided. As mentioned above, Sequel doesn't allow you to turn off the running of save hooks. If you know what you are doing and really want to skip them, you need to drop down to the dataset level to do so. This can be done for a specific model object by using the +this+ method for a dataset that represents a single object:
88
+
89
+ album.this # dataset
90
+
91
+ The +this+ dataset works just like any other dataset, so you can call +update+ on it to modify it:
92
+
93
+ album.this.update(:copies_sold=>:copies_sold + 1)
94
+
95
+ If you want to insert a row into the model's table without running the creation hooks, you can use <tt>Model.insert</tt> instead of <tt>Model.create</tt>:
96
+
97
+ Album.insert(:name=>'RF') # does not run hooks
98
+
99
+ == Halting Hook Processing
100
+
101
+ Sequel uses a convention that if any <tt>before_*</tt> hook method returns false (but not nil), that the action will be canceled. You can use this to implement validation-like behavior, that will run even if validations are skipped. For example:
102
+
103
+ class Album < Sequel::Model
104
+ def before_save
105
+ return false if name == ''
106
+ super
107
+ end
108
+ end
109
+
110
+ While returning false is not really recommended, you should be aware of this behavior so that you do not inadvertently return false.
111
+
112
+ By default, Sequel runs hooks other than validation hooks inside a transaction, so if you abort the hook by returning false in a before hook or by raising an exception in the hook, Sequel will rollback the transaction. However, note that the implicit use of transactions when saving and destroying model objects is conditional (it depends on the model instance's +use_transactions+ setting).
113
+
114
+ == Conditional Hooks
115
+
116
+ Sometimes you only take to take a certain action in a hook if the object meets a certain condition. For example, let's say you only want to make sure a timestamp is set when updating if the object is at a certain status level:
117
+
118
+ class Album < Sequel::Model
119
+ def before_update
120
+ self.timestamp ||= Time.now if status_id > 3
121
+ super
122
+ end
123
+ end
124
+
125
+ Note how this hook action is made conditional just be using the standard ruby +if+ conditional. Sequel makes it easy to handle conditional hook actions by using standard ruby conditionals inside the instance methods.
126
+
127
+ == Using Hooks in Multiple Classes
128
+
129
+ If you want all your model classes to use the same hook, you can just define that hook in Sequel::Model:
130
+
131
+ class Sequel::Model
132
+ def before_create
133
+ self.created_at ||= Time.now
134
+ super
135
+ end
136
+ end
137
+
138
+ Just remember to call +super+ whenever you override the method in a subclass. Note that +super+ is also used when overriding the hook in <tt>Sequel::Model</tt> itself. This is important as if you add any plugins to Sequel::Model itself, if you override a hook in <tt>Sequel::Model</tt> and do not call +super+, the plugin may not work correctly.
139
+
140
+ If you don't want all classes to use the same hook, but want to reuse hooks in multiple classes, you should use a plugin or a simple module:
141
+
142
+ === Plugin
143
+
144
+ module SetCreatedAt
145
+ module InstanceMethods
146
+ def before_create
147
+ self.created_at ||= Time.now
148
+ super
149
+ end
150
+ end
151
+ end
152
+ Album.plugin(SetCreatedAt)
153
+ Artist.plugin(SetCreatedAt)
154
+
155
+ === Simple Module
156
+
157
+ module SetCreatedAt
158
+ def before_create
159
+ self.created_at ||= Time.now
160
+ super
161
+ end
162
+ end
163
+ Album.send(:include, SetCreatedAt)
164
+ Artist.send(:include, SetCreatedAt)
165
+
166
+ == +super+ Ordering
167
+
168
+ While it's not enforced anywhere, it's a good idea to make +super+ the last expression when you override a before hook, and the first expression when you override an after hook:
169
+
170
+ class Album < Sequel::Model
171
+ def before_save
172
+ self.updated_at ||= Time.now
173
+ super
174
+ end
175
+
176
+ def after_save
177
+ super
178
+ AuditLog.create(:log=>"Album #{name} created")
179
+ end
180
+ end
181
+
182
+ This allows the following general principles to be true:
183
+
184
+ * before hooks are run in reverse order of inclusion
185
+ * after hooks are run in order of inclusion
186
+
187
+ So if you define the same before hook in both a model and a plugin that the model uses, the hooks will be called in this order:
188
+
189
+ * model before hook
190
+ * plugin before hook
191
+ * plugin after hook
192
+ * model after hook
193
+
194
+ Again, Sequel does not enforce that, and you are free to call +super+ in an order other than the recommended one (just make sure that you call it).
195
+
196
+ == +hook_class_methods+
197
+
198
+ While it's recommended to write your hooks as instance methods, Sequel ships with a +hook_class_methods+ plugin that allows you to define hooks via class methods. It exists mostly for legacy compatibility, but is still supported.
data/doc/querying.rdoc CHANGED
@@ -12,80 +12,6 @@ While you can easily use raw SQL with Sequel, a large part of the
12
12
  advantage you get from using Sequel is Sequel's ability to abstract
13
13
  SQL from you and give you a much nicer interface.
14
14
 
15
- == Setup
16
-
17
- Some examples in this guide assume you will be using Sequel::Model
18
- for modeling, but most should work with plain datasets. The examples
19
- specific to Sequel::Model will have [Sequel::Model] in the heading.
20
-
21
- Many of the examples in this guide will refer to the following model
22
- classes:
23
-
24
- # All classes use :id as the primary key column
25
-
26
- class Artist < Sequel::Model
27
- one_to_many :albums
28
- one_to_one :address
29
- end
30
-
31
- class Album < Sequel::Model
32
- many_to_one :artist
33
- one_to_many :tracks
34
- many_to_many :tags
35
- end
36
-
37
- class Address < Sequel::Model
38
- many_to_one :artist
39
- end
40
-
41
- class Tag < Sequel::Model
42
- many_to_many :albums
43
- end
44
-
45
- class Track < Sequel::Model
46
- many_to_one :album
47
- end
48
-
49
- If you want to play with the code examples, here's some Sequel code
50
- that will set up the database structure for you:
51
-
52
- DB.create_table(:artists) do
53
- primary_key :id
54
- String :name
55
- end
56
-
57
- DB.create_table(:albums) do
58
- primary_key :id
59
- foreign_key :artist_id, :artists
60
- String :name
61
- end
62
-
63
- DB.create_table(:addresses) do
64
- primary_key :id
65
- foreign_key :artist_id, :artists, :unique=>true
66
- String :street
67
- String :city
68
- String :state
69
- String :zip
70
- end
71
-
72
- DB.create_table(:tags) do
73
- primary_key :id
74
- String :tag
75
- end
76
-
77
- DB.create_table(:albums_tags) do
78
- foreign_key :album_id, :albums
79
- foreign_key :tag_id, :tags
80
- end
81
-
82
- DB.create_table(:tracks) do
83
- primary_key :id
84
- foreign_key :album_id, :albums
85
- Integer :number
86
- String :name
87
- end
88
-
89
15
  == Retrieving Objects
90
16
 
91
17
  Sequel provides a few separate methods for retrieving objects from the
@@ -111,7 +37,7 @@ by its primary key value:
111
37
 
112
38
  # Find artist with primary key (id) 1
113
39
  artist = Artist[1]
114
- # SQL: SELECT * FROM artists WHERE id = 1
40
+ # SELECT * FROM artists WHERE id = 1
115
41
  => #<Artist @values={:name=>"YJM", :id=>1}>
116
42
 
117
43
  If there is no record with the given primary key, nil will be returned.
@@ -122,24 +48,24 @@ If you just want the first record in the dataset,
122
48
  <tt>Sequel::Dataset#first</tt> is probably the most obvious method to use:
123
49
 
124
50
  artist = Artist.first
125
- # SQL: SELECT * FROM artists LIMIT 1
51
+ # SELECT * FROM artists LIMIT 1
126
52
  => #<Artist @values={:name=>"YJM", :id=>1}>
127
53
 
128
54
  Any options you pass to +first+ will be used as a filter:
129
55
 
130
56
  artist = Artist.first(:name => 'YJM')
131
- # SQL: SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
57
+ # SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
132
58
  => #<Artist @values={:name=>"YJM", :id=>1}>
133
59
 
134
60
  artist = Artist.first(:name.like('Y%'))
135
- # SQL: SELECT * FROM artists WHERE (name LIKE 'Y%') LIMIT 1
61
+ # SELECT * FROM artists WHERE (name LIKE 'Y%') LIMIT 1
136
62
  => #<Artist @values={:name=>"YJM", :id=>1}>
137
63
 
138
64
  <tt>Sequel::Dataset#[]</tt> is basically an alias for +first+, except it
139
65
  requires an argument:
140
66
 
141
67
  DB[:artists][:name => 'YJM']
142
- # SQL: SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
68
+ # SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
143
69
  => {:name=>"YJM", :id=>1}
144
70
 
145
71
  Note that while Model.[] allows you to pass a primary key directly,
@@ -153,13 +79,13 @@ that last requires that the dataset be ordered. Without an order, any
153
79
  object can be considered the first as well as the last.
154
80
 
155
81
  artist = Artist.order(:name).last
156
- # SQL: SELECT * FROM artists ORDER BY name DESC LIMIT 1
82
+ # SELECT * FROM artists ORDER BY name DESC LIMIT 1
157
83
  => #<Artist @values={:name=>"YJM", :id=>1}>
158
84
 
159
85
  Note that all +last+ does is reverse the order of the dataset and then
160
86
  call +first+. This is why +last+ raises a Sequel::Error if there is no
161
87
  order on the dataset, because otherwise it would provide the same record
162
- as +first+, and most users would fine that confusing.
88
+ as +first+, and most users would find that confusing.
163
89
 
164
90
  Note that +last+ is not necessarily going to give you the last record
165
91
  in the dataset unless you give the dataset an unambiguous order.
@@ -171,7 +97,7 @@ a specific column. For this <tt>Sequel::Dataset#get</tt> is the method
171
97
  you want:
172
98
 
173
99
  artist_name = Artist.get(:name)
174
- # SQL: SELECT name FROM artists LIMIT 1
100
+ # SELECT name FROM artists LIMIT 1
175
101
  => "YJM"
176
102
 
177
103
  === Retrieving Multiple Objects
@@ -183,7 +109,7 @@ dataset, in which case <tt>Sequel::Dataset#all</tt> is the method you
183
109
  want to use:
184
110
 
185
111
  artists = Artist.all
186
- # SQL: SELECT * FROM artists
112
+ # SELECT * FROM artists
187
113
  => [#<Artist @values={:name=>"YJM", :id=>1}>,
188
114
  #<Artist @values={:name=>"AS", :id=>2}>]
189
115
 
@@ -194,7 +120,7 @@ method named each that yields hashes or model objects as they are retrieved
194
120
  from the database:
195
121
 
196
122
  Artist.each{|x| p x.name}
197
- # SQL: SELECT * FROM artists
123
+ # SELECT * FROM artists
198
124
  "YJM"
199
125
  "AS"
200
126
 
@@ -202,9 +128,808 @@ This means that all of the methods in the Enumerable module are available,
202
128
  such as +map+:
203
129
 
204
130
  artist_names = Artist.map{|x| x.name}
205
- # SQL: SELECT * FROM artists
131
+ # SELECT * FROM artists
206
132
  => ["YJM", "AS"]
207
133
 
208
134
  ==== As an Array of Column Values
209
135
 
210
- At 1.2.2 in Rails Guide
136
+ Sequel also has an extended +map+ method that takes an argument. If you
137
+ provide an argument to +map+, it will return an array of values for the
138
+ given column. So the previous example can be handled more easily with:
139
+
140
+ artist_names = Artist.map(:name)
141
+ # SELECT * FROM artists
142
+ => ["YJM", "AS"]
143
+
144
+ One difference between these two ways of return an array of values is
145
+ that providing +map+ with an argument is really doing:
146
+
147
+ artist_names = Artist.map{|x| x[:name]} # not x.name
148
+
149
+ Note that regardless of whether you provide +map+ with an argument, it
150
+ does not modify the columns selected. If you only want to select a
151
+ single column and return an array of the columns values, you can use
152
+ +select_map+:
153
+
154
+ artist_names = Artist.select_map(:name)
155
+ # SELECT name FROM artists
156
+ => ["YJM", "AS"]
157
+
158
+ It's also common to want to order such a map, so Sequel provides a
159
+ +select_order_map+ method as well:
160
+
161
+ artist_names = Artist.select_order_map(:name)
162
+ # SELECT name FROM artists ORDER BY name
163
+ => ["AS", "YJM"]
164
+
165
+ ==== As a Hash
166
+
167
+ Sequel makes it easy to take an SQL query and return it as a ruby hash,
168
+ using the +to_hash+ method:
169
+
170
+ artist_names = Artist.to_hash(:name, :id)
171
+ # SELECT * FROM artists
172
+ => {"YJM"=>1, "AS"=>2}
173
+
174
+ As you can see, the +to_hash+ method uses the first symbol as the key
175
+ and the second symbol as the value. So if you swap the two arguments:
176
+
177
+ artist_names = Artist.to_hash(:id, :name)
178
+ # SELECT * FROM artists
179
+ => {1=>"YJM", 2=>"AS"}
180
+
181
+ If you only provide one argument to +to_hash+, it uses the entire hash
182
+ or model object as the value:
183
+
184
+ artist_names = DB[:artists].to_hash(:name)
185
+ # SELECT * FROM artists
186
+ => {"YJM"=>{:id=>1, :name=>"YJM"}, "AS"=>{:id=>2, :name=>"AS"}}
187
+
188
+ Model datasets have a +to_hash+ method that can be called without any
189
+ arguments, in which case it will use the primary key as the key and
190
+ the model object as the value. This can be used to easily create an
191
+ identity map:
192
+
193
+ artist_names = Artist.to_hash
194
+ # SELECT * FROM artists
195
+ => {1=>#<Artist @values={:id=>1, :name=>"YGM"}>,
196
+ 2=>#<Artist @values={:id=>2, :name=>"AS"}>}
197
+
198
+ Note that +to_hash+ never modifies the columns selected. However, just
199
+ like Sequel has a +select_map+ method to modify the columns selected and
200
+ return an array, Sequel also has a +select_hash+ method to modify the
201
+ columns selected and return a hash:
202
+
203
+ artist_names = Artist.select_hash(:name, :id)
204
+ # SELECT name, id FROM artists
205
+ => {"YJM"=>1, "AS"=>2}
206
+
207
+ == Modifying datasets
208
+
209
+ Note that the retrieval methods discussed above just return
210
+ the row(s) included in the existing dataset. In most cases,
211
+ you aren't interested in every row in a table, but in a subset
212
+ of the rows, based on some criteria. In Sequel, filtering
213
+ the dataset is generally done separately than retrieving
214
+ the records.
215
+
216
+ There are really two types of dataset methods that you will
217
+ be using:
218
+
219
+ 1. Methods that return row(s), discussed above
220
+ 2. Methods that return modified datasets, discussed below
221
+
222
+ Sequel uses a method chaining, functional style API to
223
+ modify datasets. Let's start with a simple example.
224
+
225
+ This is a basic dataset that includes all records in the
226
+ table +artists+:
227
+
228
+ ds1 = DB[:artists]
229
+ # SELECT * FROM artists
230
+
231
+ Let's say we are only interested in the artists whose names
232
+ start with "A":
233
+
234
+ ds2 = ds1.where(:name.like('A%'))
235
+ # SELECT * FROM artists WHERE name LIKE 'A%'
236
+
237
+ Here we see that +where+ returns a dataset that adds a +WHERE+
238
+ clause to the query. It's important to note that +where+ does
239
+ not modify the receiver:
240
+
241
+ ds1
242
+ # SELECT * FROM artists
243
+ ds2
244
+ # SELECT * FROM artists WHERE name LIKE 'A%'
245
+
246
+ In Sequel, most dataset methods that you will be using will
247
+ not modify the dataset itself, so you can freely use the dataset in multiple
248
+ places without worrying that its usage in one place will affect its usage
249
+ in another place. This is what is meant by a functional style API.
250
+
251
+ Let's say we only want to select the id and name columns, and that
252
+ we want to order by name:
253
+
254
+ ds3 = ds.order(:name).select(:id, :name)
255
+ # SELECT id, name FROM artists WHERE name LIKE 'A%' ORDER BY name
256
+
257
+ Note how you don't need to assign the returned value of order to a variable,
258
+ and then call select on that. Because order just returns a dataset, you can
259
+ call select directly on the returned dataset. This is what is meant by a
260
+ method chaining API.
261
+
262
+ Also note how you can call methods that modify different clauses in any order.
263
+ In this case, the WHERE clause was added first, then the ORDER clause, then the
264
+ SELECT clause was modified. This makes for a flexible API, where you can modify
265
+ any part of the query at any time.
266
+
267
+ == Filters
268
+
269
+ Filtering is probably the most common dataset modifying action done in Sequel.
270
+ Both the +where+ and +filter+ methods filter the dataset by modifying the
271
+ dataset's WHERE clause. While not quite aliases of each other, they operate
272
+ the same in most cases. Both accept a wide variety of input formats, discussed
273
+ below.
274
+
275
+ === Hashes
276
+
277
+ The most common format for providing filters is via a hash. In general, Sequel
278
+ treats conditions specified with a hash as equality or inclusion. What type
279
+ of condition is used depends on the values in the hash.
280
+
281
+ Unless Sequel has special support for the value's class, it uses a simple
282
+ equality statement:
283
+
284
+ Artist.filter(:id=>1)
285
+ # SELECT * FROM artists WHERE id = 1
286
+
287
+ Artist.filter(:name=>'YJM')
288
+ # SELECT * FROM artists WHERE name = 'YJM'
289
+
290
+ For arrays, Sequel uses the IN operator.
291
+
292
+ Artist.filter(:id=>[1, 2])
293
+ # SELECT * FROM artists WHERE id IN (1, 2)
294
+
295
+ For datasets, Sequel uses the IN operator with a subselect:
296
+
297
+ Artist.filter(:id=>Album.select(:artist_id))
298
+ # SELECT * FROM artists WHERE id IN (
299
+ # SELECT artist_id FROM albums)
300
+
301
+ For boolean values such as nil, true, and false, Sequel uses the IS operator:
302
+
303
+ Artist.filter(:id=>nil)
304
+ # SELECT * FROM artists WHERE id IS NULL
305
+
306
+ For ranges, Sequel uses a pair of inequality statements:
307
+
308
+ Artist.filter(:id=>1..5)
309
+ # SELECT * FROM artists WHERE id >= 1 AND id <= 5
310
+
311
+ Finally, for regexps, Sequel uses an SQL regular expression. Note that this
312
+ is probably only supported on PostgreSQL and MySQL.
313
+
314
+ Artist.filter(:name=>/JM$/)
315
+ # SELECT * FROM artists WHERE name ~ 'JM$'
316
+
317
+ If there are multiple arguments in the hash, the filters are ANDed together:
318
+
319
+ Artist.filter(:id=>1, :name=>/JM$/)
320
+ # SELECT * FROM artists WHERE id = 1 AND name ~ 'JM$'
321
+
322
+ This works the same as if you used two separate filter calls:
323
+
324
+ Artist.filter(:id=>1).filter(:name=>/JM$/)
325
+ # SELECT * FROM artists WHERE id = 1 AND name ~ 'JM$'
326
+
327
+ === Array of Two Element Arrays
328
+
329
+ If you use an array of two element arrays, it is treated as a hash. The only
330
+ advantage to using an array of two element arrays is that it allows you to
331
+ duplicate keys, so you can do:
332
+
333
+ Artist.filter([[:name, /JM$/], [:name, /^YJ/]])
334
+ # SELECT * FROM artists WHERE name ~ 'JM$' AND name ~ '^YJ'
335
+
336
+ === Virtual Row Blocks
337
+
338
+ If a block is passed to filter, it is treated as a virtual row block:
339
+
340
+ Artist.filter{id > 5}
341
+ # SELECT * FROM artists WHERE id > 5
342
+
343
+ You can learn more about virtual row blocks in the {"Virtual Rows" guide}[link:files/doc/virtual_rows_rdoc.html].
344
+
345
+ You can provide both regular arguments and a block, in which case the results
346
+ will be ANDed together:
347
+
348
+ Artist.filter(:name=>'A'...'M'){id > 5}
349
+ # SELECT * FROM artists WHERE name >= 'A' AND name < 'M' AND id > 5
350
+
351
+ === Symbols
352
+
353
+ If you have a boolean column in the database, and you want only true
354
+ values, you can just provide the column symbol to filter:
355
+
356
+ Artist.where(:retired)
357
+ # SELECT * FROM artists WHERE retired
358
+
359
+ === SQL::Expression
360
+
361
+ Sequel has a DSL that allows easily creating SQL expressions. These SQL
362
+ expressions are instances of subclasses of Sequel::SQL::Expression. You've
363
+ already seen an example earlier:
364
+
365
+ Artist.filter(:name.like('Y%'))
366
+ # SELECT * FROM artists WHERE name LIKE 'Y%'
367
+
368
+ In this case Symbol#like returns a Sequel::SQL::BooleanExpression object,
369
+ which is used directly in the filter.
370
+
371
+ You can use the DSL to create arbitrarily complex expressions. SQL::Expression
372
+ objects support the & operator for +AND+, the | operator for +OR+, and the ~ operator
373
+ for inversion:
374
+
375
+ Artist.filter(:name.like('Y%') & ({:b=>1} | ~{:c=>3}))
376
+ # SELECT * FROM artists WHERE name LIKE 'Y%' AND (b = 1 OR c != 3)
377
+
378
+ You can combine these expression operators with the virtual row support:
379
+
380
+ Artist.filter{(a > 1) & ~((b(c) < 1) | d)}
381
+ # SELECT * FROM artists WHERE a > 1 AND b(c) >= 1 AND NOT d
382
+
383
+ Note the use of parentheses when using the & and | operators, as they have lower
384
+ precedence than other operators. The following will not work:
385
+
386
+ Artist.filter{a > 1 & ~(b(c) < 1 | d)}
387
+ # Raises a TypeError, as it calls Integer#| with a Sequel::SQL::Identifier
388
+
389
+ === Strings with Placeholders
390
+
391
+ Assuming you want to get your hands dirty and write some SQL, Sequel allows you
392
+ to use strings using placeholders for the values:
393
+
394
+ Artist.filter("name LIKE ?", 'Y%')
395
+ # SELECT * FROM artists WHERE name LIKE 'Y%'
396
+
397
+ This is the most common type of placeholder, where each question mark is substituted
398
+ with the next argument:
399
+
400
+ Artist.filter("name LIKE ? AND id = ?", 'Y%', 5)
401
+ # SELECT * FROM artists WHERE name LIKE 'Y%' AND id = 5
402
+
403
+ You can also use named placeholders with a hash, where the named placeholders use
404
+ colons before the placeholder names:
405
+
406
+ Artist.filter("name LIKE :name AND id = :id", :name=>'Y%', :id=>5)
407
+ # SELECT * FROM artists WHERE name LIKE 'Y%' AND id = 5
408
+
409
+ You don't have to provide any placeholders if you don't want to:
410
+
411
+ Artist.filter("id = 2")
412
+ # SELECT * FROM artists WHERE id = 2
413
+
414
+ However, if you are using any untrusted input, you should definitely be using placeholders.
415
+ In general, unless you are hardcoding values in the strings, you should use placeholders.
416
+ You should never pass a string that has been built using interpolation, unless you are
417
+ sure of what you are doing.
418
+
419
+ Artist.filter("id = #{params[:id]}") # Don't do this!
420
+ Artist.filter("id = ?", params[:id]) # Do this instead
421
+ Artist.filter(:id=>params[:id]) # Even better
422
+
423
+ === Inverting
424
+
425
+ You may be wondering how to specify a not equals condition in Sequel, or the NOT IN
426
+ operator. Sequel has generic support for inverting conditions, so to write a not
427
+ equals condition, you write an equals condition, and invert it:
428
+
429
+ Artist.filter(:id=>5).invert
430
+ # SELECT * FROM artists WHERE id != 5
431
+
432
+ Note that +invert+ inverts the entire filter:
433
+
434
+ Artist.filter(:id=>5).filter{name > 'A'}.invert
435
+ # SELECT * FROM artists WHERE id != 5 OR name <= 'A'
436
+
437
+ In general, +invert+ is used rarely, since +exclude+ allows you to invert only specific
438
+ filters:
439
+
440
+ Artist.exclude(:id=>5)
441
+ # SELECT * FROM artists WHERE id != 5
442
+
443
+ Artist.filter(:id=>5).exclude{name > 'A'}
444
+ # SELECT * FROM artists WHERE id = 5 OR name <= 'A'
445
+
446
+ So to do a NOT IN with an array:
447
+
448
+ Artist.exclude(:id=>[1, 2])
449
+ # SELECT * FROM artists WHERE id NOT IN (1, 2)
450
+
451
+ Or to use the NOT LIKE operator:
452
+
453
+ Artist.exclude(:name.like('%J%'))
454
+ # SELECT * FROM artists WHERE name NOT LIKE '%J%'
455
+
456
+ === Removing
457
+
458
+ To remove all existing filters, use +unfiltered+:
459
+
460
+ Artist.filter(:id=>1).unfiltered
461
+ # SELECT * FROM artists
462
+
463
+ == Ordering
464
+
465
+ Sequel offers quite a few methods to manipulate the SQL ORDER BY clause. The
466
+ most basic of these is +order+:
467
+
468
+ Artist.order(:id)
469
+ # SELECT * FROM artists ORDER BY id
470
+
471
+ You can specify multiple arguments to order by more than one column:
472
+
473
+ Album.order(:artist_id, :id)
474
+ # SELECT * FROM album ORDER BY artist_id, id
475
+
476
+ Note that unlike +filter+, +order+ replaces an existing order, it does not
477
+ append to an existing order:
478
+
479
+ Artist.order(:id).order(:name)
480
+ # SELECT * FROM artists ORDER BY name
481
+
482
+ If you want to add a column to the end of the existing order:
483
+
484
+ Artist.order(:id).order_append(:name)
485
+ # SELECT * FROM artists ORDER BY id, name
486
+
487
+ If you want to add a column to the beginning of the existing order:
488
+
489
+ Artist.order(:id).order_prepend(:name)
490
+ # SELECT * FROM artists ORDER BY name, id
491
+
492
+ === Reversing
493
+
494
+ Just like you can invert an existing filter, you can reverse an existing
495
+ order, using +reverse+:
496
+
497
+ Artist.order(:id).reverse
498
+ # SELECT FROM artists ORDER BY id DESC
499
+
500
+ As you might expect, +reverse+ is not used all that much. In general,
501
+ <tt>Symbol#desc</tt> is used more commonly to specify a descending order
502
+ for columns:
503
+
504
+ Artist.order(:id.desc)
505
+ # SELECT FROM artists ORDER BY id DESC
506
+
507
+ This allows you to easily use both ascending and descending orders:
508
+
509
+ Artist.order(:name, :id.desc)
510
+ # SELECT FROM artists ORDER BY name, id DESC
511
+
512
+ === Removing
513
+
514
+ Just like you can remove filters with +unfiltered+, you can remove
515
+ orders with +unordered+:
516
+
517
+ Artist.order(:name).unordered
518
+ # SELECT * FROM artists
519
+
520
+ == Selected Columns
521
+
522
+ Sequel offers a few methods to manipulate the columns selected. As
523
+ you may be able to guess, the main method used is +select+:
524
+
525
+ Artist.select(:id, :name)
526
+ # SELECT id, name FROM artists
527
+
528
+ You just specify all of the columns that you are selecting as
529
+ arguments to the method.
530
+
531
+ If you are dealing with model objects, you'll want to include the
532
+ primary key if you want to update or destroy the object. You'll
533
+ also want to include any keys (primary or foreign) related to
534
+ associations you plan to use.
535
+
536
+ If a column is not selected, and you attempt to access it, you will
537
+ get nil:
538
+
539
+ artist = Artist.select(:name).first
540
+ # SELECT name FROM artists LIMIT 1
541
+
542
+ artist[:id]
543
+ # => nil
544
+
545
+ Like +order+, +select+ replaces the existing selected columns:
546
+
547
+ Artist.select(:id).select(:name)
548
+ # SELECT name FROM artists
549
+
550
+ To add to the existing selected columns, use +select_append+:
551
+
552
+ Artist.select(:id).select_append(:name)
553
+ # SELECT id, name FROM artists
554
+
555
+ To remove specifically selected columns, and default back to all
556
+ columns, use +select_all+:
557
+
558
+ Artist.select(:id).select_all
559
+ # SELECT * FROM artists
560
+
561
+ === Distinct
562
+
563
+ To treat duplicate rows as a single row when retrieving the records,
564
+ use +distinct+:
565
+
566
+ Artist.distinct.select(:name)
567
+ # SELECT DISTINCT name FROM artists
568
+
569
+ Note that DISTINCT is a separate SQL clause, it's not a function
570
+ that you pass to select.
571
+
572
+ == Limit and Offset
573
+
574
+ You can limit the dataset to a given number of rows using +limit+:
575
+
576
+ Artist.limit(5)
577
+ # SELECT * FROM artists LIMIT 5
578
+
579
+ You can provide a second argument to +limit+ to specify an offset:
580
+
581
+ Artist.limit(5, 10)
582
+ # SELECT * FROM artists LIMIT 5 OFFSET 10
583
+
584
+ This would return the 11th through 15th records in the original
585
+ dataset.
586
+
587
+ To remove a limit from a dataset, use +unlimited+:
588
+
589
+ Artist.limit(5, 10).unlimited
590
+ # SELECT * FROM artists
591
+
592
+ == Grouping
593
+
594
+ The SQL GROUP BY clause is used to combine multiple rows based on
595
+ the values of a given group of columns.
596
+
597
+ To modify the GROUP BY clause of the SQL statement, you use +group+:
598
+
599
+ Album.group(:artist_id)
600
+ # SELECT * FROM albums GROUP BY artist_id
601
+
602
+ You can remove an existing grouping using +ungrouped+:
603
+
604
+ Album.group(:artist_id).ungrouped
605
+ # SELECT * FROM albums
606
+
607
+ A common use of grouping is to count based on the number of grouped rows,
608
+ and Sequel provides a +group_and_count+ method to make this easier:
609
+
610
+ Album.group_and_count(:artist_id)
611
+ # SELECT artist_id, COUNT(*) AS count FROM albums GROUP BY artist_id
612
+
613
+ This will return the number of albums for each artist_id.
614
+
615
+ == Having
616
+
617
+ The SQL HAVING clause is similar to the WHERE clause, except that
618
+ filters the results after the grouping has been applied, instead of
619
+ before. One possible use is if you only wanted to return artists
620
+ who had at least 10 albums:
621
+
622
+ Album.group_and_count(:artist_id).having{count >= 10}
623
+ # SELECT artist_id, COUNT(*) AS count FROM albums
624
+ # GROUP BY artist_id HAVING count >= 10
625
+
626
+ If you have an existing HAVING clause on your dataset, then +filter+
627
+ will add to the HAVING clause instead of the WHERE clause:
628
+
629
+ Album.group_and_count(:artist_id).
630
+ having{count >= 10}.filter{count < 15}
631
+ # SELECT artist_id, COUNT(*) AS count FROM albums
632
+ # GROUP BY artist_id HAVING count >= 10 AND count < 15
633
+
634
+ Unlike +filter+, +where+ always affects the WHERE clause:
635
+
636
+ Album.group_and_count(:artist_id).
637
+ having{count >= 10}.where(:name.like('A%'))
638
+ # SELECT artist_id, COUNT(*) AS count FROM albums
639
+ # WHERE name LIKE 'A%' GROUP BY artist_id HAVING count >= 10
640
+
641
+ Both the WHERE clause and the HAVING clause are removed by +unfiltered+:
642
+
643
+ Album.group_and_count(:artist_id).having{count >= 10}.
644
+ where(:name.like('A%')).unfiltered
645
+ # SELECT artist_id, COUNT(*) AS count FROM albums GROUP BY artist_id
646
+
647
+ == Joins
648
+
649
+ Sequel makes it very easy to join a dataset to another table or dataset.
650
+ The underlying method used is +join_table+:
651
+
652
+ Album.join_table(:inner, :artists, :id=>:artist_id)
653
+ # SELECT * FROM albums
654
+ # INNER JOIN artists ON artists.id = albums.artist_id
655
+
656
+ In most cases, you won't call +join_table+ directly, as Sequel provides
657
+ shortcuts for all common (and most uncommon) join types. For example
658
+ +join+ does an inner join:
659
+
660
+ Album.join(:artists, :id=>:artist_id)
661
+ # SELECT * FROM albums
662
+ # INNER JOIN artists ON artists.id = albums.artist_id
663
+
664
+ And +left_join+ does a LEFT JOIN:
665
+
666
+ Album.left_join(:artists, :id=>:artist_id)
667
+ # SELECT * FROM albums
668
+ # LEFT JOIN artists ON artists.id = albums.artist_id
669
+
670
+ === Table/Dataset to Join
671
+
672
+ For all of these specialized join methods, the first argument is
673
+ generally the name of the table to which you are joining. However, you
674
+ can also provide a model class:
675
+
676
+ Album.join(Artist, :id=>:artist_id)
677
+
678
+ Or a dataset, in which case a subselect is used:
679
+
680
+ Album.join(Artist.filter{name < 'A'}, :id=>:artist_id)
681
+ # SELECT * FROM albums
682
+ # INNER JOIN (SELECT * FROM artists WHERE (name < 'A')) AS t1
683
+ # ON (t1.id = albums.artist_id)
684
+
685
+ === Join Conditions
686
+
687
+ The second argument to the specialized join methods is the conditions
688
+ to use when joining, which is similar to a filter expression, with
689
+ a few minor exceptions.
690
+
691
+ ==== Implicit Qualification
692
+
693
+ A hash used as the join conditions operates similarly to a filter,
694
+ except that unqualified symbol keys are automatically qualified
695
+ with the table from the first argument, and unqualified symbol values
696
+ are automatically qualified with the first table or the last table
697
+ joined. This implicit qualification is one of the reasons that joins
698
+ in Sequel are easy to specify:
699
+
700
+ Album.join(:artists, :id=>:artist_id)
701
+ # SELECT * FROM albums
702
+ # INNER JOIN artists ON artists.id = albums.artist_id
703
+
704
+ Note how the <tt>:id</tt> symbol is automatically qualified with +artists+,
705
+ while the +artist_id+ symbol is automatically qualified with +albums+.
706
+
707
+ Because Sequel uses the last joined table for implicit qualifications
708
+ of values, you can do things like:
709
+
710
+ Album.join(:artists, :id=>:artist_id).
711
+ join(:members, :artist_id=>:id)
712
+ # SELECT * FROM albums
713
+ # INNER JOIN artists ON artists.id = albums.artist_id
714
+ # INNER JOIN members ON members.artist_id = artists.id
715
+
716
+ Note that when joining to the +members+ table, +artist_id+ is qualified
717
+ with +members+ and +id+ is qualified with +artists+.
718
+
719
+ While a good default, implicit qualification is not always correct:
720
+
721
+ Album.join(:artists, :id=>:artist_id).
722
+ join(:tracks, :album_id=>:id)
723
+ # SELECT * FROM albums
724
+ # INNER JOIN artists ON artists.id = albums.artist_id
725
+ # INNER JOIN tracks ON tracks.album_id = artists.id
726
+
727
+ Note here how +id+ is qualified with +artists+ instead of +albums+. This
728
+ is wrong as the foreign key <tt>tracks.album_id</tt> refers to <tt>albums.id</tt>, not
729
+ <tt>artists.id</tt>. To fix this, you need to explicitly qualify when joining:
730
+
731
+ Album.join(:artists, :id=>:artist_id).
732
+ join(:tracks, :album_id=>:albums__id)
733
+ # SELECT * FROM albums
734
+ # INNER JOIN artists ON artists.id = albums.artist_id
735
+ # INNER JOIN tracks ON tracks.album_id = albums.id
736
+
737
+ Just like in filters, an array of two element arrays is treated the same
738
+ as a hash, but allows for duplicate keys:
739
+
740
+ Album.join(:artists, [[:id, :artist_id], [:id, 1..5]])
741
+ # SELECT * FROM albums INNER JOIN artists
742
+ # ON artists.id = albums.artist_id
743
+ # AND artists.id >= 1 AND artists.id <= 5
744
+
745
+ And just like in the hash case, unqualified symbol elements in the
746
+ array are implicitly qualified.
747
+
748
+ ==== USING Joins
749
+
750
+ The most common type of join conditions is a JOIN ON, as displayed
751
+ above. However, the SQL standard allows for join conditions to be
752
+ specified with JOIN USING, which Sequel makes easy to use.
753
+
754
+ JOIN USING is useful when the columns you are using have the same
755
+ names in both tables. For example, if instead of having a primary
756
+ column named +id+ in all of your tables, you use +artist_id+ in your
757
+ +artists+ table and +album_id+ in your +albums+ table, you could do:
758
+
759
+ Album.join(:artists, [:artist_id])
760
+ # SELECT * FROM albums INNER JOIN artists USING (artist_id)
761
+
762
+ See here how you specify the USING columns as an array of symbols.
763
+
764
+ ==== NATURAL Joins
765
+
766
+ NATURAL Joins take it one step further than USING joins, by assuming
767
+ that all columns with the same names in both tables should be
768
+ used for joining:
769
+
770
+ Album.natural_join(:artists)
771
+ # SELECT * FROM albums NATURAL JOIN artists
772
+
773
+ In this case, you don't even need to specify any conditions.
774
+
775
+ ==== Join Blocks
776
+
777
+ You can provide a block to any of the join methods that accept
778
+ conditions. This block should accept 3 arguments, the table alias
779
+ for the table currently being joined, the table alias for the last
780
+ table joined (or first table), and an array of previous
781
+ <tt>Sequel::SQL::JoinClause</tt>s.
782
+
783
+ This allows you to qualify columns similar to how the implicit
784
+ qualification works, without worrying about the specific aliases
785
+ being used. For example, lets say you wanted to join the albums
786
+ and artists tables, but only want albums where the artist's name
787
+ comes before the album's name.
788
+
789
+ Album.join(:artists, :id=>:artist_id) do |j, lj, js|
790
+ :name.qualify(j) < :name.qualify(lj)
791
+ end
792
+ # SELECT * FROM albums INNER JOIN artists
793
+ # ON artists.id = albums.artist_id
794
+ # AND artists.name < albums.name
795
+
796
+ Because greater than can't be expressed with a hash in Sequel, you
797
+ need to use a block and qualify the tables manually.
798
+
799
+ == From
800
+
801
+ In general, the FROM table is the first clause populated when creating
802
+ a dataset. For a standard Sequel::Model, the dataset already has the
803
+ FROM clause populated, and the most common way to create datasets is
804
+ with the <tt>Database#[]</tt> method, which populates the FROM clause.
805
+
806
+ However, you can modify the tables you are selecting FROM using +from+:
807
+
808
+ Album.from(:albums, :old_albums)
809
+ # SELECT * FROM albums, old_albums
810
+
811
+ Be careful with this, as multiple tables in the FROM clause use a cross
812
+ join by default, so the number of rows will be number of albums times the
813
+ number of old albums.
814
+
815
+ Using multiple FROM tables and setting conditions in the WHERE clause is
816
+ an old-school way of joining tables:
817
+
818
+ DB.from(:albums, :artists).where(:artists__id=>:albums__artist_id)
819
+ # SELECT * FROM albums, artists WHERE artists.id = albums.artist_id
820
+
821
+ === Using the current dataset in a subselect
822
+
823
+ In some cases, you may want to wrap the current dataset in a subselect.
824
+ Here's an example using +from_self+:
825
+
826
+ Album.order(:artist_id).limit(100).from_self.group(:artist_id)
827
+ # SELECT * FROM (SELECT * FROM albums ORDER BY artist_id LIMIT 100)
828
+ # AS t1 GROUP BY artist_id
829
+
830
+ This is slightly different than without +from_self+:
831
+
832
+ Album.order(:artist_id).limit(100).group(:artist_id)
833
+ # SELECT * FROM albums GROUP BY artist_id ORDER BY name LIMIT 100
834
+
835
+ Without +from_self+, you are doing the grouping, and limiting the number
836
+ of grouped records returned to 100. So assuming you have albums by more
837
+ than 100 artists, you'll end up with 100 results.
838
+
839
+ With +from_self+, you are limiting the number of records before grouping.
840
+ So if the artist with the lowest id had 100 albums, you'd get 1 result,
841
+ not 100.
842
+
843
+ == Locking for Update
844
+
845
+ Sequel allows you to easily add a FOR UPDATE clause to your queries so
846
+ that the records returned can't be modified by another query until the
847
+ current transaction commits. You just use the +for_update+ dataset
848
+ method when returning the rows:
849
+
850
+ DB.transaction do
851
+ album = Album.for_update.first(:id=>1)
852
+ # SELECT * FROM albums WHERE id = 1 FOR UPDATE
853
+ album.num_tracks += 1
854
+ album.save
855
+ end
856
+
857
+ This will ensure that no other connection modifies the row between when you select
858
+ it and when the transaction ends.
859
+
860
+ === Optimistic Locking
861
+
862
+ One of Sequel's built-in model plugins is an optimistic locking plugin, which provides
863
+ a database independent way to detect and raise an error if two different connections
864
+ modify the same row. It's useful for things like web forms where you cannot keep a
865
+ transaction open while the user is looking at the form, because of the web's
866
+ stateless nature.
867
+
868
+ == Custom SQL
869
+
870
+ Sequel makes it easy to use custom SQL by providing it to the <tt>Database#[]</tt>
871
+ method as a string:
872
+
873
+ DB["SELECT * FROM artists"]
874
+ # SELECT * FROM artists
875
+
876
+ You can also use the +with_sql+ dataset method to return a dataset that uses that
877
+ exact SQL:
878
+
879
+ DB[:albums].with_sql("SELECT * FROM artists")
880
+ # SELECT * FROM artists
881
+
882
+ With either of these methods, you can use placeholders:
883
+
884
+ DB["SELECT * FROM artists WHERE id = ?", 5]
885
+ # SELECT * FROM artists WHERE id = 5
886
+
887
+ DB[:albums].with_sql("SELECT * FROM artists WHERE id = :id", :id=>5)
888
+ # SELECT * FROM artists WHERE id = 5
889
+
890
+ == Checking for Records
891
+
892
+ If you just want to know whether the current dataset would return any rows, use <tt>empty?</tt>:
893
+
894
+ Album.empty?
895
+ # SELECT 1 FROM albums LIMIT 1
896
+ => false
897
+
898
+ Album.filter(:id=>0).empty?
899
+ # SELECT 1 FROM albums WHERE id = 0 LIMIT 1
900
+ => true
901
+
902
+ Album.filter(:name.like('R%')).empty?
903
+ # SELECT 1 FROM albums WHERE name LIKE 'R%' LIMIT 1
904
+ => false
905
+
906
+ == Aggregate Calculations
907
+
908
+ The SQL standard defines a few helpful methods to get aggreate information about
909
+ datasets, such as +count+, +sum+, +avg+, +min+, and +max+. There are dataset methods
910
+ for each of these aggregate functions.
911
+
912
+ +count+ just returns the number of records in the dataset.
913
+
914
+ Album.count
915
+ # SELECT COUNT(*) AS count FROM albums LIMIT 1
916
+ => 2
917
+
918
+ The other methods take a column argument and call the aggregate function with
919
+ the argument:
920
+
921
+ Album.sum(:id)
922
+ # SELECT sum(id) FROM albums LIMIT 1
923
+ => 3
924
+
925
+ Album.avg(:id)
926
+ # SELECT avg(id) FROM albums LIMIT 1
927
+ => 1.5
928
+
929
+ Album.min(:id)
930
+ # SELECT min(id) FROM albums LIMIT 1
931
+ => 1
932
+
933
+ Album.max(:id)
934
+ # SELECT max(id) FROM albums LIMIT 1
935
+ => 2