sequel 5.82.0 → 5.84.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/sequel +9 -17
- data/lib/sequel/adapters/jdbc/derby.rb +1 -1
- data/lib/sequel/adapters/shared/db2.rb +1 -1
- data/lib/sequel/adapters/shared/mssql.rb +14 -2
- data/lib/sequel/adapters/shared/postgres.rb +42 -4
- data/lib/sequel/adapters/shared/sqlite.rb +3 -1
- data/lib/sequel/database/connecting.rb +1 -4
- data/lib/sequel/database/misc.rb +27 -7
- data/lib/sequel/database/schema_methods.rb +17 -1
- data/lib/sequel/dataset/sql.rb +13 -0
- data/lib/sequel/extensions/pg_json_ops.rb +328 -1
- data/lib/sequel/extensions/stdio_logger.rb +48 -0
- data/lib/sequel/extensions/string_agg.rb +15 -2
- data/lib/sequel/plugins/defaults_setter.rb +16 -4
- data/lib/sequel/plugins/optimistic_locking.rb +2 -0
- data/lib/sequel/sql.rb +8 -5
- data/lib/sequel/version.rb +1 -1
- metadata +4 -235
- data/CHANGELOG +0 -1377
- data/README.rdoc +0 -936
- data/doc/advanced_associations.rdoc +0 -884
- data/doc/association_basics.rdoc +0 -1859
- data/doc/bin_sequel.rdoc +0 -146
- data/doc/cheat_sheet.rdoc +0 -255
- data/doc/code_order.rdoc +0 -104
- data/doc/core_extensions.rdoc +0 -405
- data/doc/dataset_basics.rdoc +0 -96
- data/doc/dataset_filtering.rdoc +0 -222
- data/doc/extensions.rdoc +0 -77
- data/doc/fork_safety.rdoc +0 -84
- data/doc/mass_assignment.rdoc +0 -98
- data/doc/migration.rdoc +0 -660
- data/doc/model_dataset_method_design.rdoc +0 -129
- data/doc/model_hooks.rdoc +0 -254
- data/doc/model_plugins.rdoc +0 -270
- data/doc/mssql_stored_procedures.rdoc +0 -43
- data/doc/object_model.rdoc +0 -563
- data/doc/opening_databases.rdoc +0 -439
- data/doc/postgresql.rdoc +0 -611
- data/doc/prepared_statements.rdoc +0 -144
- data/doc/querying.rdoc +0 -1070
- data/doc/reflection.rdoc +0 -120
- data/doc/release_notes/5.0.0.txt +0 -159
- data/doc/release_notes/5.1.0.txt +0 -31
- data/doc/release_notes/5.10.0.txt +0 -84
- data/doc/release_notes/5.11.0.txt +0 -83
- data/doc/release_notes/5.12.0.txt +0 -141
- data/doc/release_notes/5.13.0.txt +0 -27
- data/doc/release_notes/5.14.0.txt +0 -63
- data/doc/release_notes/5.15.0.txt +0 -39
- data/doc/release_notes/5.16.0.txt +0 -110
- data/doc/release_notes/5.17.0.txt +0 -31
- data/doc/release_notes/5.18.0.txt +0 -69
- data/doc/release_notes/5.19.0.txt +0 -28
- data/doc/release_notes/5.2.0.txt +0 -33
- data/doc/release_notes/5.20.0.txt +0 -89
- data/doc/release_notes/5.21.0.txt +0 -87
- data/doc/release_notes/5.22.0.txt +0 -48
- data/doc/release_notes/5.23.0.txt +0 -56
- data/doc/release_notes/5.24.0.txt +0 -56
- data/doc/release_notes/5.25.0.txt +0 -32
- data/doc/release_notes/5.26.0.txt +0 -35
- data/doc/release_notes/5.27.0.txt +0 -21
- data/doc/release_notes/5.28.0.txt +0 -16
- data/doc/release_notes/5.29.0.txt +0 -22
- data/doc/release_notes/5.3.0.txt +0 -121
- data/doc/release_notes/5.30.0.txt +0 -20
- data/doc/release_notes/5.31.0.txt +0 -148
- data/doc/release_notes/5.32.0.txt +0 -46
- data/doc/release_notes/5.33.0.txt +0 -24
- data/doc/release_notes/5.34.0.txt +0 -40
- data/doc/release_notes/5.35.0.txt +0 -56
- data/doc/release_notes/5.36.0.txt +0 -60
- data/doc/release_notes/5.37.0.txt +0 -30
- data/doc/release_notes/5.38.0.txt +0 -28
- data/doc/release_notes/5.39.0.txt +0 -19
- data/doc/release_notes/5.4.0.txt +0 -80
- data/doc/release_notes/5.40.0.txt +0 -40
- data/doc/release_notes/5.41.0.txt +0 -25
- data/doc/release_notes/5.42.0.txt +0 -136
- data/doc/release_notes/5.43.0.txt +0 -98
- data/doc/release_notes/5.44.0.txt +0 -32
- data/doc/release_notes/5.45.0.txt +0 -34
- data/doc/release_notes/5.46.0.txt +0 -87
- data/doc/release_notes/5.47.0.txt +0 -59
- data/doc/release_notes/5.48.0.txt +0 -14
- data/doc/release_notes/5.49.0.txt +0 -59
- data/doc/release_notes/5.5.0.txt +0 -61
- data/doc/release_notes/5.50.0.txt +0 -78
- data/doc/release_notes/5.51.0.txt +0 -47
- data/doc/release_notes/5.52.0.txt +0 -87
- data/doc/release_notes/5.53.0.txt +0 -23
- data/doc/release_notes/5.54.0.txt +0 -27
- data/doc/release_notes/5.55.0.txt +0 -21
- data/doc/release_notes/5.56.0.txt +0 -51
- data/doc/release_notes/5.57.0.txt +0 -23
- data/doc/release_notes/5.58.0.txt +0 -31
- data/doc/release_notes/5.59.0.txt +0 -73
- data/doc/release_notes/5.6.0.txt +0 -31
- data/doc/release_notes/5.60.0.txt +0 -22
- data/doc/release_notes/5.61.0.txt +0 -43
- data/doc/release_notes/5.62.0.txt +0 -132
- data/doc/release_notes/5.63.0.txt +0 -33
- data/doc/release_notes/5.64.0.txt +0 -50
- data/doc/release_notes/5.65.0.txt +0 -21
- data/doc/release_notes/5.66.0.txt +0 -24
- data/doc/release_notes/5.67.0.txt +0 -32
- data/doc/release_notes/5.68.0.txt +0 -61
- data/doc/release_notes/5.69.0.txt +0 -26
- data/doc/release_notes/5.7.0.txt +0 -108
- data/doc/release_notes/5.70.0.txt +0 -35
- data/doc/release_notes/5.71.0.txt +0 -21
- data/doc/release_notes/5.72.0.txt +0 -33
- data/doc/release_notes/5.73.0.txt +0 -66
- data/doc/release_notes/5.74.0.txt +0 -45
- data/doc/release_notes/5.75.0.txt +0 -35
- data/doc/release_notes/5.76.0.txt +0 -86
- data/doc/release_notes/5.77.0.txt +0 -63
- data/doc/release_notes/5.78.0.txt +0 -67
- data/doc/release_notes/5.79.0.txt +0 -28
- data/doc/release_notes/5.8.0.txt +0 -170
- data/doc/release_notes/5.80.0.txt +0 -40
- data/doc/release_notes/5.81.0.txt +0 -31
- data/doc/release_notes/5.82.0.txt +0 -61
- data/doc/release_notes/5.9.0.txt +0 -99
- data/doc/schema_modification.rdoc +0 -679
- data/doc/security.rdoc +0 -443
- data/doc/sharding.rdoc +0 -286
- data/doc/sql.rdoc +0 -648
- data/doc/testing.rdoc +0 -204
- data/doc/thread_safety.rdoc +0 -15
- data/doc/transactions.rdoc +0 -250
- data/doc/validations.rdoc +0 -558
- data/doc/virtual_rows.rdoc +0 -265
data/doc/dataset_filtering.rdoc
DELETED
@@ -1,222 +0,0 @@
|
|
1
|
-
= Dataset Filtering
|
2
|
-
|
3
|
-
Sequel is very flexible when it comes to filtering records. You can specify your conditions as a hash of values to compare against, or as ruby code that Sequel translates into SQL expressions, or as an SQL code fragment (with optional parameters), .
|
4
|
-
|
5
|
-
== Filtering using a hash
|
6
|
-
|
7
|
-
If you just need to compare records against values, you can supply a hash:
|
8
|
-
|
9
|
-
items.where(category: 'ruby').sql
|
10
|
-
# "SELECT * FROM items WHERE (category = 'ruby')"
|
11
|
-
|
12
|
-
Sequel can check for null values:
|
13
|
-
|
14
|
-
items.where(category: nil).sql
|
15
|
-
# "SELECT * FROM items WHERE (category IS NULL)"
|
16
|
-
|
17
|
-
Or compare two columns:
|
18
|
-
|
19
|
-
items.where{{x: some_table[:y]}}.sql
|
20
|
-
# "SELECT * FROM items WHERE (x = some_table.y)"
|
21
|
-
|
22
|
-
And also compare against multiple values:
|
23
|
-
|
24
|
-
items.where(category: ['ruby', 'perl']).sql
|
25
|
-
# "SELECT * FROM items WHERE (category IN ('ruby', 'perl'))"
|
26
|
-
|
27
|
-
Ranges (both inclusive and exclusive) can also be used:
|
28
|
-
|
29
|
-
items.where(price: 100..200).sql
|
30
|
-
# "SELECT * FROM items WHERE (price >= 100 AND price <= 200)"
|
31
|
-
|
32
|
-
items.where(price: 100...200).sql
|
33
|
-
# "SELECT * FROM items WHERE (price >= 100 AND price < 200)"
|
34
|
-
|
35
|
-
== Filtering using an array
|
36
|
-
|
37
|
-
If you need to select multiple items from a dataset, you can supply an array:
|
38
|
-
|
39
|
-
items.where(id: [1, 38, 47, 99]).sql
|
40
|
-
# "SELECT * FROM items WHERE (id IN (1, 38, 47, 99))"
|
41
|
-
|
42
|
-
== Filtering using expressions
|
43
|
-
|
44
|
-
You can pass a block to where (referred to as a virtual row block), which is evaluated in a special context:
|
45
|
-
|
46
|
-
items.where{price * 2 < 50}.sql
|
47
|
-
# "SELECT * FROM items WHERE ((price * 2) < 50)
|
48
|
-
|
49
|
-
This works for the standard inequality and arithmetic operators:
|
50
|
-
|
51
|
-
items.where{price + 100 < 200}.sql
|
52
|
-
# "SELECT * FROM items WHERE ((price + 100) < 200)
|
53
|
-
|
54
|
-
items.where{price - 100 > 200}.sql
|
55
|
-
# "SELECT * FROM items WHERE ((price - 100) > 200)
|
56
|
-
|
57
|
-
items.where{price * 100 <= 200}.sql
|
58
|
-
# "SELECT * FROM items WHERE ((price * 100) <= 200)
|
59
|
-
|
60
|
-
items.where{price / 100 >= 200}.sql
|
61
|
-
# "SELECT * FROM items WHERE ((price / 100) >= 200)
|
62
|
-
|
63
|
-
items.where{price ** 2 >= 200}.sql
|
64
|
-
# "SELECT * FROM items WHERE (power(price, 2) >= 200)
|
65
|
-
|
66
|
-
You use the overloaded bitwise and (&) and or (|) operators to combine expressions:
|
67
|
-
|
68
|
-
items.where{(price + 100 < 200) & (price * 100 <= 200)}.sql
|
69
|
-
# "SELECT * FROM items WHERE (((price + 100) < 200) AND ((price * 100) <= 200))
|
70
|
-
|
71
|
-
items.where{(price - 100 > 200) | (price / 100 >= 200)}.sql
|
72
|
-
# "SELECT * FROM items WHERE (((price - 100) > 200) OR ((price / 100) >= 200))
|
73
|
-
|
74
|
-
To filter by equality, you use the standard hash, which can be combined with other expressions using Sequel.& and Sequel.|:
|
75
|
-
|
76
|
-
items.where{Sequel.&({category: 'ruby'}, (price + 100 < 200))}.sql
|
77
|
-
# "SELECT * FROM items WHERE ((category = 'ruby') AND ((price + 100) < 200))"
|
78
|
-
|
79
|
-
You can also use the =~ operator:
|
80
|
-
|
81
|
-
items.where{(category =~ 'ruby') & (price + 100 < 200)}.sql
|
82
|
-
# "SELECT * FROM items WHERE ((category = 'ruby') AND ((price + 100) < 200))"
|
83
|
-
|
84
|
-
This works with other hash values, such as arrays and ranges:
|
85
|
-
|
86
|
-
items.where{Sequel.|({category: ['ruby', 'other']}, (price - 100 > 200))}.sql
|
87
|
-
# "SELECT * FROM items WHERE ((category IN ('ruby', 'other')) OR ((price - 100) > 200))"
|
88
|
-
|
89
|
-
items.where{(price =~ (100..200)) & :active}.sql
|
90
|
-
# "SELECT * FROM items WHERE ((price >= 100 AND price <= 200) AND active)"
|
91
|
-
|
92
|
-
== Filtering using a custom filter string
|
93
|
-
|
94
|
-
If you wish to include an SQL fragment as part of a filter, you need to wrap it with +Sequel.lit+ to mark that it is literal SQL code, and pass it to the #where method:
|
95
|
-
|
96
|
-
items.where(Sequel.lit('x < 10')).sql
|
97
|
-
# "SELECT * FROM items WHERE x < 10"
|
98
|
-
|
99
|
-
In order to prevent SQL injection, you can replace literal values with question marks and supply the values as additional arguments to +Sequel.lit+:
|
100
|
-
|
101
|
-
items.where(Sequel.lit('category = ?', 'ruby')).sql
|
102
|
-
# "SELECT * FROM items WHERE category = 'ruby'"
|
103
|
-
|
104
|
-
You can also use placeholders with :placeholder and a hash of placeholder values:
|
105
|
-
|
106
|
-
items.where(Sequel.lit('category = :category', category: "ruby")).sql
|
107
|
-
# "SELECT * FROM items WHERE category = 'ruby'"
|
108
|
-
|
109
|
-
In order to combine AND and OR together, you have a few options:
|
110
|
-
|
111
|
-
items.where(category: nil).or(category: "ruby")
|
112
|
-
# SELECT * FROM items WHERE (category IS NULL) OR (category = 'ruby')
|
113
|
-
|
114
|
-
This won't work if you add other conditions:
|
115
|
-
|
116
|
-
items.where(name: "Programming in Ruby").where(category: nil).or(category: 'ruby')
|
117
|
-
# SELECT * FROM items WHERE ((name = 'Programming in Ruby') AND (category IS NULL)) OR (category = 'ruby')
|
118
|
-
|
119
|
-
The OR applies globally and not locally. To fix this, use & and |:
|
120
|
-
|
121
|
-
items.where(Sequel[name: "Programming in Ruby"] & (Sequel[category: nil] | Sequel[category: "ruby"]))
|
122
|
-
# SELECT * FROM items WHERE ((name = 'Programming in Ruby') AND ((category IS NULL) OR (category = 'ruby')))
|
123
|
-
|
124
|
-
=== Specifying SQL functions
|
125
|
-
|
126
|
-
Sequel also allows you to specify functions by using the Sequel.function method:
|
127
|
-
|
128
|
-
items.literal(Sequel.function(:avg, :price)) # "avg(price)"
|
129
|
-
|
130
|
-
If you are specifying a filter/selection/order, you can use a virtual row block:
|
131
|
-
|
132
|
-
items.select{avg(price)}
|
133
|
-
|
134
|
-
=== Negating conditions
|
135
|
-
|
136
|
-
You can use the exclude method to exclude whole conditions:
|
137
|
-
|
138
|
-
items.exclude(category: 'ruby').sql
|
139
|
-
# "SELECT * FROM items WHERE (category != 'ruby')"
|
140
|
-
|
141
|
-
items.exclude(:active).sql
|
142
|
-
# "SELECT * FROM items WHERE NOT active"
|
143
|
-
|
144
|
-
items.exclude{price / 100 >= 200}.sql
|
145
|
-
# "SELECT * FROM items WHERE ((price / 100) < 200)
|
146
|
-
|
147
|
-
To exclude only parts of conditions, you can use when in combination with Sequel.~ or the ~ method on Sequel expressions:
|
148
|
-
|
149
|
-
items.where{Sequel.&(Sequel.~(category: 'ruby'), (price + 100 < 200))}.sql
|
150
|
-
# "SELECT * FROM items WHERE ((category != 'ruby') AND ((price + 100) < 200))"
|
151
|
-
|
152
|
-
items.where{~(category =~ 'ruby') & (price + 100 < 200)}.sql
|
153
|
-
# "SELECT * FROM items WHERE ((category != 'ruby') AND ((price + 100) < 200))"
|
154
|
-
|
155
|
-
You can also use the !~ method:
|
156
|
-
|
157
|
-
items.where{(category !~ 'ruby') & (price + 100 < 200)}.sql
|
158
|
-
# "SELECT * FROM items WHERE ((category != 'ruby') AND ((price + 100) < 200))"
|
159
|
-
|
160
|
-
=== Comparing against column references
|
161
|
-
|
162
|
-
You can also compare against other columns:
|
163
|
-
|
164
|
-
items.where{credit > debit}.sql
|
165
|
-
# "SELECT * FROM items WHERE (credit > debit)
|
166
|
-
|
167
|
-
Or against SQL functions:
|
168
|
-
|
169
|
-
items.where{price - 100 < max(price)}.sql
|
170
|
-
# "SELECT * FROM items WHERE ((price - 100) < max(price))"
|
171
|
-
|
172
|
-
== String search functions
|
173
|
-
|
174
|
-
You can search SQL strings in a case sensitive manner using the Sequel.like method:
|
175
|
-
|
176
|
-
items.where(Sequel.like(:name, 'Acme%')).sql
|
177
|
-
# "SELECT * FROM items WHERE (name LIKE 'Acme%' ESCAPE '\')"
|
178
|
-
|
179
|
-
You can search SQL strings in a case insensitive manner using the Sequel.ilike method:
|
180
|
-
|
181
|
-
items.where(Sequel.ilike(:name, 'Acme%')).sql
|
182
|
-
# "SELECT * FROM items WHERE (name ILIKE 'Acme%' ESCAPE '\')"
|
183
|
-
|
184
|
-
You can specify a Regexp as a hash value (or like argument), but this will probably only work
|
185
|
-
on PostgreSQL and MySQL:
|
186
|
-
|
187
|
-
items.where(name: /Acme.*/).sql
|
188
|
-
# "SELECT * FROM items WHERE (name ~ 'Acme.*')"
|
189
|
-
|
190
|
-
Like can also take more than one argument:
|
191
|
-
|
192
|
-
items.where(Sequel.like(:name, 'Acme%', /Beta.*/)).sql
|
193
|
-
# "SELECT * FROM items WHERE ((name LIKE 'Acme%' ESCAPE '\') OR (name ~ 'Beta.*'))"
|
194
|
-
|
195
|
-
== String concatenation
|
196
|
-
|
197
|
-
You can concatenate SQL strings using Sequel.join:
|
198
|
-
|
199
|
-
items.where(Sequel.join([:name, :comment]).like('Jo%nice%')).sql
|
200
|
-
# "SELECT * FROM items WHERE ((name || comment) LIKE 'Jo%nice%' ESCAPE '\')"
|
201
|
-
|
202
|
-
Sequel.join also takes a join argument:
|
203
|
-
|
204
|
-
items.where(Sequel.join([:name, :comment], ':').like('John:%nice%')).sql
|
205
|
-
# "SELECT * FROM items WHERE ((name || ':' || comment) LIKE 'John:%nice%' ESCAPE '\')"
|
206
|
-
|
207
|
-
== Filtering using sub-queries
|
208
|
-
|
209
|
-
Datasets can be used as subqueries. Subqueries can be very useful for filtering records, and many times provide a simpler alternative to table joins. Subqueries can be used in all forms of filters:
|
210
|
-
|
211
|
-
refs = consumer_refs.where(:logged_in).select(:consumer_id)
|
212
|
-
consumers.where(id: refs).sql
|
213
|
-
# "SELECT * FROM consumers WHERE (id IN (SELECT consumer_id FROM consumer_refs WHERE logged_in))"
|
214
|
-
|
215
|
-
Note that if you are checking for the inclusion of a single column in a subselect, the subselect should only select a single column.
|
216
|
-
|
217
|
-
== Using OR instead of AND
|
218
|
-
|
219
|
-
By default, if you chain calls to +where+, the conditions get ANDed together. If you want to use an OR for a condition, you can use the +or+ method:
|
220
|
-
|
221
|
-
items.where(name: 'Food').or(vendor: 1).sql
|
222
|
-
# "SELECT * FROM items WHERE ((name = 'Food') OR (vendor = 1))"
|
data/doc/extensions.rdoc
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
= Sequel Extensions
|
2
|
-
|
3
|
-
Sequel has an official extension system, for adding global, Database, and Dataset extensions.
|
4
|
-
|
5
|
-
== Global Extensions
|
6
|
-
|
7
|
-
Global extensions can add or modify the behavior of any part of Sequel. Technically, they are not limited to affecting Sequel, as they can also modify code outside of Sequel (e.g. the blank extension). However, extensions that modify things outside of Sequel generally do so only for backwards compatibility.
|
8
|
-
|
9
|
-
Global extensions are loaded via <tt>Sequel.extension</tt>:
|
10
|
-
|
11
|
-
Sequel.extension :named_timezones
|
12
|
-
|
13
|
-
All this does is require the relevent extension from <tt>sequel/extensions/named_timezones</tt> somewhere in the ruby path. Global extensions are just a simpler, consistent way to require code that modifies Sequel.
|
14
|
-
|
15
|
-
== Database Extensions
|
16
|
-
|
17
|
-
Database extensions should add or modify the behavior of a single <tt>Sequel::Database</tt> instance. They are loaded via <tt>Sequel::Database#extension</tt>:
|
18
|
-
|
19
|
-
DB.extension :server_block
|
20
|
-
|
21
|
-
The first thing that this does is load the relevent extension globally. However, Database extensions should be structured in a way that loading the relevent extension globally just adds a module with the related behavior, it doesn't modify any other state. After loading the extension globally, it modifies the related <tt>Sequel::Database</tt> object to modify it's behavior, usually by extending it with a module.
|
22
|
-
|
23
|
-
If you want a Database extension loaded into all future Database instances, you can use <tt>Sequel::Database.extension</tt>:
|
24
|
-
|
25
|
-
Sequel::Database.extension :server_block
|
26
|
-
|
27
|
-
All future <tt>Sequel::Database</tt> instances created afterward will then automatically have the server_block extension loaded.
|
28
|
-
|
29
|
-
== Dataset Extensions
|
30
|
-
|
31
|
-
Dataset extensions should add or modify the behavior of a single <tt>Sequel::Dataset</tt> instance. They are loaded via <tt>Sequel::Dataset#extension</tt>. <tt>Sequel::Dataset#extension</tt> returns a modifies copy of the dataset that includes the extension (similar to how most dataset query methods work):
|
32
|
-
|
33
|
-
ds = DB[:a].extension(:columns_introspection)
|
34
|
-
|
35
|
-
The first thing loading a Dataset extension does is load the relevent extension globally. Similar to Database extensions, loading a Dataset extension globally should not affect state other than maybe adding a module. After loading the extension globally, it returned a modified copy of the <tt>Sequel::Dataset</tt> with the extension loaded into it.
|
36
|
-
|
37
|
-
If you want to load an extension into all future datasets for a given <tt>Sequel::Database</tt> instance, you can also load it as a Database extension:
|
38
|
-
|
39
|
-
DB.extension :columns_introspection
|
40
|
-
|
41
|
-
Likewise, if you want to load an extension into all future datasets for all future databases, you can load it via <tt>Sequel::Database.extension</tt>:
|
42
|
-
|
43
|
-
Sequel::Database.extension :columns_introspection
|
44
|
-
|
45
|
-
== Creating Global Extensions
|
46
|
-
|
47
|
-
If you want to create a global extension, you just need to store your code so that you can require it via <tt>sequel/extensions/extension_name</tt>. Then users can load it via:
|
48
|
-
|
49
|
-
Sequel.extension :extension_name
|
50
|
-
|
51
|
-
It is recommended you only create a global extension if what you want to do would not work as a Database or Dataset extension.
|
52
|
-
|
53
|
-
== Creating Database Extensions
|
54
|
-
|
55
|
-
Creating Database extensions is similar to global extensions in terms of creating the file. However, somewhere in the file, you need to call <tt>Sequel::Database.register_extension</tt>. Usually you would call this with the module that will be added to the related <tt>Sequel::Database</tt> instance when the extension is loaded. For example, the server_block extension uses something like:
|
56
|
-
|
57
|
-
Sequel::Database.register_extension(:server_block, Sequel::ServerBlock)
|
58
|
-
|
59
|
-
The first argument is the name of the extension as a symbol, and the second is the module.
|
60
|
-
|
61
|
-
In some cases, just extending the <tt>Sequel::Database</tt> instance with a module is not sufficient. So <tt>Sequel::Database.register_extension</tt> also accepts a proc instead of a second argument. This proc is called with the <tt>Sequel::Database</tt> instance, and can then run any related code:
|
62
|
-
|
63
|
-
Sequel::Database.register_extension(:arbitrary_servers){|db| db.pool.extend(Sequel::ArbitraryServers)}
|
64
|
-
|
65
|
-
== Creating Dataset Extensions
|
66
|
-
|
67
|
-
Creating Dataset extensions is very similar to creating Database extensions, but instead of calling <tt>Sequel::Database.register_extension</tt>, you call <tt>Sequel::Dataset.register_extension</tt>. In general, you would call this with the module that will be added to the related <tt>Sequel::Dataset</tt> instance when the extension is loaded. For example, the columns_introspection extension uses something like:
|
68
|
-
|
69
|
-
Sequel::Dataset.register_extension(:columns_introspection, Sequel::ColumnsIntrospection)
|
70
|
-
|
71
|
-
The first argument is the name of the extension as a symbol, and the second is the module. When you call the <tt>Sequel::Dataset.register_extension</tt> method with a module, it in turn calls <tt>Sequel::Database.register_extension</tt> and adds a Database extension that loads this Dataset extension into all future Datasets created from the Database.
|
72
|
-
|
73
|
-
You can also call <tt>Sequel::Dataset.register_extension</tt> with a proc:
|
74
|
-
|
75
|
-
Sequel::Dataset.register_extension(:extension_name){|ds| }
|
76
|
-
|
77
|
-
Note that if you use a proc, a corresponding Database extension will not be created automatically (you can still call <tt>Sequel::Database.register_extension</tt> manually in this case).
|
data/doc/fork_safety.rdoc
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
= Fork Safety
|
2
|
-
|
3
|
-
If you are forking or using a library that forks after you have created a
|
4
|
-
Sequel::Database instance, then you must disconnect database connections before forking. If you
|
5
|
-
don't do this, you can end up with child processes sharing database connections
|
6
|
-
and all sorts of weird behavior, including crashes. Sequel will automatically create new
|
7
|
-
connections on an as needed basis in the child processes, so you only need to do the following in
|
8
|
-
the parent process:
|
9
|
-
|
10
|
-
DB.disconnect
|
11
|
-
|
12
|
-
Or if you have connections to multiple databases:
|
13
|
-
|
14
|
-
Sequel::DATABASES.each(&:disconnect)
|
15
|
-
|
16
|
-
== Puma
|
17
|
-
|
18
|
-
When using the Puma web server in clustered mode (which is the default behavior in Puma 5+ when
|
19
|
-
using multiple processes), you should disconnect inside the +before_fork+ hook in your
|
20
|
-
Puma config:
|
21
|
-
|
22
|
-
before_fork do
|
23
|
-
Sequel::DATABASES.each(&:disconnect)
|
24
|
-
end
|
25
|
-
|
26
|
-
== Unicorn
|
27
|
-
|
28
|
-
When using the Unicorn web server and preloading the application (+preload_app true+ in the Unicorn
|
29
|
-
config), you should disconnect inside the +before_fork+ hook in the Unicorn config:
|
30
|
-
|
31
|
-
before_fork do
|
32
|
-
Sequel::DATABASES.each(&:disconnect)
|
33
|
-
end
|
34
|
-
|
35
|
-
== Passenger
|
36
|
-
|
37
|
-
In Passenger web server, you should disconnect inside the
|
38
|
-
+starting_worker_process+ event hook:
|
39
|
-
|
40
|
-
if defined?(PhusionPassenger)
|
41
|
-
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
42
|
-
Sequel::DATABASES.each(&:disconnect) if forked
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
Note that this disconnects after forking instead of before forking. Passenger does not
|
47
|
-
offer a before fork hook.
|
48
|
-
|
49
|
-
== Spring
|
50
|
-
|
51
|
-
In Spring application preloader, you should disconnect inside the +after_fork+ hook:
|
52
|
-
|
53
|
-
if defined?(Spring)
|
54
|
-
Spring.after_fork do
|
55
|
-
Sequel::DATABASES.each(&:disconnect)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
As the method indicates, this disconnects after forking instead of before forking.
|
60
|
-
Spring does not offer a before fork hook.
|
61
|
-
|
62
|
-
== Resque
|
63
|
-
|
64
|
-
In Resque, you should disconnect inside the +before_fork+ hook:
|
65
|
-
|
66
|
-
Resque.before_fork do |job|
|
67
|
-
Sequel::DATABASES.each(&:disconnect)
|
68
|
-
end
|
69
|
-
|
70
|
-
== Parallel
|
71
|
-
|
72
|
-
If you're using the Parallel gem with processes, you should disconnect before
|
73
|
-
calling it:
|
74
|
-
|
75
|
-
Sequel::DATABASES.each(&:disconnect)
|
76
|
-
Parallel.map(['a','b','c'], in_processes: 3) { |one_letter| }
|
77
|
-
|
78
|
-
== Other Libraries Calling fork
|
79
|
-
|
80
|
-
For any other library that calls fork, you should disconnect before calling
|
81
|
-
a method that forks:
|
82
|
-
|
83
|
-
Sequel::DATABASES.each(&:disconnect)
|
84
|
-
SomeLibrary.method_that_forks
|
data/doc/mass_assignment.rdoc
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
= Sequel::Model Mass Assignment
|
2
|
-
|
3
|
-
Most Model methods that take a hash of attribute keys and values, including <tt>Model.new</tt>,
|
4
|
-
<tt>Model.create</tt>, <tt>Model#set</tt> and <tt>Model#update</tt> are subject to Sequel's mass assignment rules.
|
5
|
-
|
6
|
-
If you have an instance of a plain Sequel::Model class:
|
7
|
-
|
8
|
-
class Post < Sequel::Model
|
9
|
-
end
|
10
|
-
post = Post.new
|
11
|
-
|
12
|
-
and you call a mass assignment method with a hash:
|
13
|
-
|
14
|
-
post.set(title: 'T', body: 'B')
|
15
|
-
|
16
|
-
the mass assignment method will go through each key in the hash, append <tt>=</tt> to it to determine the
|
17
|
-
setter method, and if the setter method is defined and access to it is not restricted, Sequel will call the
|
18
|
-
setter method with the hash value. So if we assume that the posts table has title and body columns, what
|
19
|
-
the above mass assignment call actually does is:
|
20
|
-
|
21
|
-
post.title=('T')
|
22
|
-
post.body=('B')
|
23
|
-
|
24
|
-
By default, there are two types of setter methods that are restricted.
|
25
|
-
The first is methods like <tt>typecast_on_assignment=</tt> and <tt>==</tt>, which don't affect columns.
|
26
|
-
These methods cannot be enabled for mass assignment.
|
27
|
-
The second is primary key setters.
|
28
|
-
|
29
|
-
So if you do:
|
30
|
-
|
31
|
-
post = Post.new(id: 1)
|
32
|
-
|
33
|
-
Sequel will raise a Sequel::MassAssignmentRestriction exception, since by default setting the primary key is not allowed.
|
34
|
-
|
35
|
-
To enable use of primary key setters, you need to call +unrestrict_primary_key+ for that model:
|
36
|
-
|
37
|
-
Post.unrestrict_primary_key
|
38
|
-
|
39
|
-
If you want to change mass assignment so it ignores attempts to access restricted setter methods, you can do:
|
40
|
-
|
41
|
-
# Global default
|
42
|
-
Sequel::Model.strict_param_setting = false
|
43
|
-
# Class level
|
44
|
-
Post.strict_param_setting = false
|
45
|
-
# Instance level
|
46
|
-
post.strict_param_setting = false
|
47
|
-
|
48
|
-
Since mass assignment by default allows modification of all column values except for primary key columns, it can be a security risk in some cases.
|
49
|
-
If you are dealing with untrusted input, you are generally going to want to restrict what should be updated.
|
50
|
-
|
51
|
-
Sequel has <tt>Model#set_fields</tt> and <tt>Model#update_fields</tt> methods, which are designed to be used with untrusted input.
|
52
|
-
These methods take two arguments, the untrusted hash as the first argument, and a trusted array of field names as the second argument:
|
53
|
-
|
54
|
-
post.set_fields({title: 'T', body: 'B'}, [:title, :body])
|
55
|
-
|
56
|
-
Instead of looking at every key in the untrusted hash, +set_fields+ will iterate over the trusted field names, looking each up in the hash, and
|
57
|
-
calling the setter method appropriately with the result. +set_fields+ basically translates the above method call to:
|
58
|
-
|
59
|
-
post.title=('T')
|
60
|
-
post.body=('B')
|
61
|
-
|
62
|
-
By using this method, you can be sure that the mass assignment method only sets the fields you expect it to set.
|
63
|
-
|
64
|
-
Note that if one of the fields does not exist in the hash:
|
65
|
-
|
66
|
-
post.set_fields({title: 'T'}, [:title, :body])
|
67
|
-
|
68
|
-
+set_fields+ will set the value to nil (the default hash value) by default, with behavior equivalent to:
|
69
|
-
|
70
|
-
post.title=('T')
|
71
|
-
post.body=(nil)
|
72
|
-
|
73
|
-
You can use the :missing option to +set_fields+ to change the behavior:
|
74
|
-
|
75
|
-
post.set_fields({title: 'T'}, [:title, :body], missing: :skip)
|
76
|
-
# post.title=('T') # only
|
77
|
-
|
78
|
-
post.set_fields({title: 'T'}, [:title, :body], missing: :raise)
|
79
|
-
# raises Sequel::Error
|
80
|
-
|
81
|
-
If you want to set a model level default for the +set_fields+ options, you can use the +default_set_fields_options+ class accessor:
|
82
|
-
|
83
|
-
# Global default
|
84
|
-
Sequel::Model.default_set_fields_options[:missing] = :skip
|
85
|
-
# Class level
|
86
|
-
Post.default_set_fields_options[:missing] = :skip
|
87
|
-
|
88
|
-
Here's a table describing Sequel's default mass assignment methods:
|
89
|
-
|
90
|
-
Model.new(hash) :: Creates a new model instance, then calls Model#set(hash)
|
91
|
-
Model.create(hash) :: Calls Model.new(hash).save
|
92
|
-
Model#set(hash) :: Calls related setter method (unless access is restricted) for each key in the hash, then returns self
|
93
|
-
Model#update(hash) :: Calls set(hash).save_changes
|
94
|
-
Model#set_fields(hash, columns, options) :: For each column in columns, looks up related entry in hash, and calls the related setter method
|
95
|
-
Model#update_fields(hash, columns, options) :: Calls set_fields(hash, columns, options).save_changes
|
96
|
-
|
97
|
-
For backwards compatibility, Sequel also ships with a whitelist_security and blacklist_security plugins that offer additional mass assignment
|
98
|
-
methods, but it is recommended to use +set_fields+ or +update_fields+ for untrusted input, and the other methods for trusted input.
|