sequel 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +4 -4
  3. data/doc/release_notes/3.3.0.txt +1 -1
  4. data/doc/release_notes/3.4.0.txt +325 -0
  5. data/doc/sharding.rdoc +3 -3
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/firebird.rb +4 -9
  8. data/lib/sequel/adapters/jdbc.rb +21 -7
  9. data/lib/sequel/adapters/mysql.rb +2 -1
  10. data/lib/sequel/adapters/odbc.rb +7 -21
  11. data/lib/sequel/adapters/oracle.rb +1 -1
  12. data/lib/sequel/adapters/postgres.rb +6 -1
  13. data/lib/sequel/adapters/shared/mssql.rb +11 -0
  14. data/lib/sequel/adapters/shared/mysql.rb +8 -12
  15. data/lib/sequel/adapters/shared/oracle.rb +13 -0
  16. data/lib/sequel/adapters/shared/postgres.rb +5 -10
  17. data/lib/sequel/adapters/shared/sqlite.rb +21 -1
  18. data/lib/sequel/adapters/sqlite.rb +2 -2
  19. data/lib/sequel/core.rb +147 -11
  20. data/lib/sequel/database.rb +21 -9
  21. data/lib/sequel/dataset.rb +31 -6
  22. data/lib/sequel/dataset/convenience.rb +1 -1
  23. data/lib/sequel/dataset/sql.rb +76 -18
  24. data/lib/sequel/extensions/inflector.rb +2 -51
  25. data/lib/sequel/model.rb +16 -10
  26. data/lib/sequel/model/associations.rb +4 -1
  27. data/lib/sequel/model/base.rb +13 -6
  28. data/lib/sequel/model/default_inflections.rb +46 -0
  29. data/lib/sequel/model/inflections.rb +1 -51
  30. data/lib/sequel/plugins/boolean_readers.rb +52 -0
  31. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  32. data/lib/sequel/plugins/lazy_attributes.rb +13 -1
  33. data/lib/sequel/plugins/nested_attributes.rb +171 -0
  34. data/lib/sequel/plugins/serialization.rb +35 -16
  35. data/lib/sequel/plugins/timestamps.rb +87 -0
  36. data/lib/sequel/plugins/validation_helpers.rb +8 -1
  37. data/lib/sequel/sql.rb +33 -0
  38. data/lib/sequel/version.rb +1 -1
  39. data/spec/adapters/sqlite_spec.rb +11 -6
  40. data/spec/core/core_sql_spec.rb +29 -0
  41. data/spec/core/database_spec.rb +16 -7
  42. data/spec/core/dataset_spec.rb +264 -20
  43. data/spec/extensions/boolean_readers_spec.rb +86 -0
  44. data/spec/extensions/inflector_spec.rb +67 -4
  45. data/spec/extensions/instance_hooks_spec.rb +133 -0
  46. data/spec/extensions/lazy_attributes_spec.rb +45 -5
  47. data/spec/extensions/nested_attributes_spec.rb +272 -0
  48. data/spec/extensions/serialization_spec.rb +64 -1
  49. data/spec/extensions/timestamps_spec.rb +150 -0
  50. data/spec/extensions/validation_helpers_spec.rb +18 -0
  51. data/spec/integration/dataset_test.rb +79 -2
  52. data/spec/integration/schema_test.rb +17 -0
  53. data/spec/integration/timezone_test.rb +55 -0
  54. data/spec/model/associations_spec.rb +19 -7
  55. data/spec/model/model_spec.rb +29 -0
  56. data/spec/model/record_spec.rb +36 -0
  57. data/spec/spec_config.rb +1 -1
  58. metadata +14 -2
data/CHANGELOG CHANGED
@@ -1,3 +1,65 @@
1
+ === 3.4.0 (2009-09-02)
2
+
3
+ * Allow datasets without tables to work correctly on Oracle (mikegolod)
4
+
5
+ * Add #invert, #asc, and #desc to OrderedExpression (dlee)
6
+
7
+ * Allow validates_unique to take a block used to scope the uniqueness constraint (drfreeze, jeremyevans)
8
+
9
+ * Automatically save a new many_to_many associated object when associating the object via add_* (jeremyevans)
10
+
11
+ * Add a nested_attributes plugin for modifying associated objects directly through a model object (jeremyevans)
12
+
13
+ * Add an instance_hooks plugin for adding hooks to specific model instances (jeremyevans)
14
+
15
+ * Add a boolean_readers plugin for creating attribute? methods for boolean columns (jeremyevans)
16
+
17
+ * Add Dataset#ungrouped which removes existing grouping (jeremyevans)
18
+
19
+ * Make Dataset#group with nil or no arguments to remove existing grouping (dlee)
20
+
21
+ * Fix using multiple emulated ALTER TABLE statements (e.g. drop_column) in a single alter_table block on SQLite (jeremyevans)
22
+
23
+ * Don't allow inserting on a grouped dataset or a dataset that selects from multiple tables (jeremyevans)
24
+
25
+ * Allow class Item < Sequel::Model(DB2) to work (jeremyevans)
26
+
27
+ * Add Dataset#truncate for truncating tables (jeremyevans)
28
+
29
+ * Add Database#run method for executing arbitrary SQL on a database (jeremyevans)
30
+
31
+ * Handle index parsing correctly for tables in a non-default schema on JDBC (jfirebaugh)
32
+
33
+ * Handle unique index parsing correctly when connecting to MSSQL via JDBC (jfirebaugh)
34
+
35
+ * Add support for converting Time/DateTime to local or UTC time upon storage, retrieval, or typecasting (jeremyevans)
36
+
37
+ * Accept a hash when typecasting values to date, time, and datetime types (jeremyevans)
38
+
39
+ * Make JDBC adapter prepared statements support booleans, blobs, and potentially any type of object (jfirebaugh)
40
+
41
+ * Refactor the inflection support and modify the default inflections (jeremyevans, dlee)
42
+
43
+ * Make the serialization and lazy_attribute plugins add accessor methods to modules included in the class (jeremyevans)
44
+
45
+ * Make Database#schema on JDBC include a :column_size entry specifying the maximum length/precision for the column (jfirebaugh)
46
+
47
+ * Make Database#schema on JDBC accept a :schema option (dlee)
48
+
49
+ * Fix Dataset#import when called with a dataset (jeremyevans)
50
+
51
+ * Give a much more descriptive error message if the mysql.rb driver is detected (jeremyevans)
52
+
53
+ * Make postgres adapter work with a modified postgres-pr that raises PGError (jeremyevans)
54
+
55
+ * Make ODBC adapter respect Sequel.datetime_class (jeremyevans)
56
+
57
+ * Add support for generic concepts of CURRENT_{DATE,TIME,TIMESTAMP} (jeremyevans)
58
+
59
+ * Add a timestamps plugin for automatically creating hooks for create and update timestamps (jeremyevans)
60
+
61
+ * Add support for serializing to json (derdewey)
62
+
1
63
  === 3.3.0 (2009-08-03)
2
64
 
3
65
  * Add an assocation_proxies plugin that uses proxies for associations (jeremyevans)
data/README.rdoc CHANGED
@@ -327,7 +327,7 @@ Which is equivalent to the SQL:
327
327
  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:
328
328
 
329
329
  DB[:items].join(:order_items, :item_id => :id).first
330
- => {:id=>order_items.id), :item_id=>order_items.item_id}
330
+ => {:id=>order_items.id, :item_id=>order_items.item_id}
331
331
 
332
332
  Using graph, you can split the result hashes into subhashes, one per join:
333
333
 
@@ -518,7 +518,7 @@ many_to_one creates a getter and setter for each model object:
518
518
  post.author = Author[:name => 'Sharon']
519
519
  post.author
520
520
 
521
- 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 objected from the association:
521
+ 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:
522
522
 
523
523
  class Post < Sequel::Model
524
524
  one_to_many :comments
@@ -539,10 +539,10 @@ one_to_many and many_to_many create a getter method, a method for adding an obje
539
539
  All associations add a dataset method that can be used to further filter or reorder the returned objects, or modify all of them:
540
540
 
541
541
  # Delete all of this post's comments from the database
542
- Post.comments_dataset.destroy
542
+ post.comments_dataset.destroy
543
543
 
544
544
  # Return all tags related to this post with no subscribers, ordered by the tag's name
545
- Post.tags_dataset.filter(:subscribers=>0).order(:name).all
545
+ post.tags_dataset.filter(:subscribers=>0).order(:name).all
546
546
 
547
547
  === Eager Loading
548
548
 
@@ -2,7 +2,7 @@ New Features
2
2
  ------------
3
3
 
4
4
  * An association_proxies plugin has been added. This is not a
5
- full-blown proxy implemention, but it allows you to write code
5
+ full-blown proxy implementation, but it allows you to write code
6
6
  such as:
7
7
 
8
8
  artist.albums.filter{num_tracks > 10}
@@ -0,0 +1,325 @@
1
+ New Plugins
2
+ -----------
3
+
4
+ * A nested_attributes plugin was added allowing you to modify
5
+ associated objects directly through a model object, similar to
6
+ ActiveRecord's Nested Attributes.
7
+
8
+ Artist.plugin :nested_attributes
9
+ Artist.one_to_many :albums
10
+ Artist.nested_attributes :albums
11
+ a = Artist.new(:name=>'YJM',
12
+ :albums_attributes=>[{:name=>'RF'}, {:name=>'MO'}])
13
+ # No database activity yet
14
+
15
+ a.save # Saves artist and both albums
16
+ a.albums.map{|x| x.name} # ['RF', 'MO']
17
+
18
+ It takes most of the same options as ActiveRecord, as well as a
19
+ a few additional options:
20
+
21
+ * :destroy - Allow destruction of nested records.
22
+ * :limit - For *_to_many associations, a limit on the number of
23
+ records that will be processed, to prevent denial of service
24
+ attacks.
25
+ * :remove - Allow disassociation of nested records (can remove the
26
+ associated object from the parent object, but not destroy the
27
+ associated object).
28
+ * :strict - Set to false to not raise an error message if a primary
29
+ key is provided in a record, but it doesn't match an existing
30
+ associated object.
31
+
32
+ If a block is provided, it is passed each nested attribute hash.
33
+ If the hash should be ignored, the block should return anything
34
+ except false or nil.
35
+
36
+ * A timestamps plugin was added for automatically adding
37
+ before_create and before_update hooks for setting values on
38
+ timestamp columns. There are a couple of existing external
39
+ plugins that handle timestamps, but the implementations are
40
+ suboptimal. The new built-in plugin supports the following
41
+ options (with the default in parentheses):
42
+
43
+ * :create - The field to hold the create timestamp (:created_at)
44
+ * :force - Whether to overwrite an existing create timestamp
45
+ (false)
46
+ * :update - The field to hold the update timestamp (:updated_at)
47
+ * :update_on_create - Whether to set the update timestamp to the
48
+ create timestamp when creating (false)
49
+
50
+ * An instance_hooks plugin was added for adding hooks to specific
51
+ w
52
+ model instances:
53
+
54
+ obj = Model.new
55
+ obj.after_save_hook{do_something}
56
+ obj.save # calls do_something after the obj has been saved
57
+
58
+ All of the standard hooks are supported, except for
59
+ after_initialize. Instance level before hooks are executed in
60
+ reverse order of addition before calling super. Instance level
61
+ after hooks are executed in order of addition after calling super.
62
+ If any of the instance level before hook blocks return false, no
63
+ more instance level before hooks are called and false is returned.
64
+
65
+ Instance level hooks are cleared when the object is saved
66
+ successfully.
67
+
68
+ * A boolean_readers plugin was added for creating attribute? methods
69
+ for boolean columns. This can provide a nicer API:
70
+
71
+ obj = Model[1]
72
+ obj.active # Sequel default column reader
73
+ obj.active? # Using the boolean_readers plugin
74
+
75
+ You can provide a block when loading the plugin to change the
76
+ criteria used to determine if the column is boolean:
77
+
78
+ Sequel::Model.plugin(:boolean_readers) do |c|
79
+ db_schema[c][:db_type] =~ /\Atinyint/
80
+ end
81
+
82
+ This may be useful if you are using MySQL and have some tinyint
83
+ columns that represent booleans and others that represent integers.
84
+ You can turn the convert_tinyint_to_bool setting off and use the
85
+ attribute methods for the integer value and the attribute? methods
86
+ for the boolean value.
87
+
88
+ Other New Features
89
+ ------------------
90
+
91
+ * Sequel now has support for converting Time/DateTime to local or UTC
92
+ time upon storage, retrieval, or typecasting.
93
+
94
+ There are three different timezone settings:
95
+
96
+ * Sequel.database_timezone - The timezone that timestamps use in
97
+ the database. If the database returns a time without an offset,
98
+ it is assumed to be in this timezone.
99
+
100
+ * Sequel.typecast_timezone - Similar to database_timezone, but used
101
+ for typecasting data from a source other than the database. This
102
+ is currently only used by the model typecasting code.
103
+
104
+ * Sequel.application_timezone - The timezone that the application
105
+ wants to deal with. All Time/DateTime objects are converted into
106
+ this timezone upon retrieval from the database.
107
+
108
+ Unlike most things in Sequel, these are only global settings, you
109
+ cannot change them per database. There are only three valid
110
+ timezone settings:
111
+
112
+ * nil (the default) - Don't do any timezone conversion. This is
113
+ the historical behavior.
114
+
115
+ * :local - Convert to local time/Consider time to be in local time.
116
+
117
+ * :utc - Convert to UTC/Consider time to be in UTC.
118
+
119
+ So if you want to store times in the database as UTC, but deal with
120
+ them in local time in the application:
121
+
122
+ Sequel.application_timezone = :local
123
+ Sequel.database_timezone = :utc
124
+
125
+ If you want to set all three timezones to the same value:
126
+
127
+ Sequel.default_timezone = :utc
128
+
129
+ There are three conversion methods that are called:
130
+
131
+ * Sequel.database_to_application_timestamp - Called on time objects
132
+ coming out of the database. If the object coming out of the
133
+ database (usually a string) does not have an offset, assume it is
134
+ already in the database_timezone. Return a Time/DateTime object
135
+ (depending on Sequel.datetime_class), in the application_timzone.
136
+
137
+ * Sequel.application_to_database_timestamp - Used when literalizing
138
+ Time/DateTime objects into an SQL string. Converts the object to
139
+ the database_timezone before literalizing them.
140
+
141
+ * Sequel.typecast_to_application_timestamp - Called when
142
+ typecasting objects for model datetime columns. If the object
143
+ being typecasted does not already have an offset, assume it is
144
+ already in the typecast_timezone. Return a Time/DateTime object
145
+ (depending on Sequel.datetime_class), in the
146
+ application_timezone.
147
+
148
+ Sequel does not yet support named timezones or per thread
149
+ modification of the timezone (for showing all timestamps in the
150
+ current user's timezone). Extensions to support both features are
151
+ planned for a future version.
152
+
153
+ * Dataset#truncate was added for truncating tables. Truncate allows
154
+ for fast removal of all rows in a table.
155
+
156
+ * Sequel now supports typecasting a hash to date, time, and datetime
157
+ types. This allows easy usage of Sequel with forms that split
158
+ the entry of these database types into separate from fields.
159
+ With this code, you can just have field names like:
160
+
161
+ date[year]
162
+ date[month]
163
+ date[day]
164
+
165
+ Rack will parse that into:
166
+
167
+ {'date'=>{'year'=>?, 'month'=>?, 'day'=>?}}
168
+
169
+ So then you can do:
170
+
171
+ obj.date = params['date']
172
+ # or
173
+ obj.set(params)
174
+
175
+ * validates_unique now takes a block that can be used to scope the
176
+ uniqueness constraint. This allows you to easily set up uniqueness
177
+ validations that are only necessary in a given scope. For example,
178
+ a validation on username, but only for active users (as inactive
179
+ users are soft deleted but remain in the table). You just pass a
180
+ block to validates_unique:
181
+
182
+ validates_unique(:name){|ds| ds.filter(:active)}
183
+
184
+ * The serialization plugin now supports json.
185
+
186
+ * Sequel now supports generic concepts of
187
+ CURRENT_{DATE,TIME,TIMESTAMP}. Most databases support these SQL
188
+ concepts, but not all, and some implementations act differently.
189
+
190
+ The Sequel::SQL::Constants module holds the three constants,
191
+ which are instances of SQL::Constant, an SQL::GenericExpression
192
+ subclass. This module is included in Sequel, so you can reference
193
+ the constants more easily (e.g. Sequel::CURRENT_TIMESTAMP).
194
+ It's separated out into a separate module so that you can just
195
+ include that module in the top level scope, allowing you to
196
+ reference the constants directly (e.g. CURRENT_TIMESTAMP).
197
+
198
+ DB[:events].filter{date < ::Sequel::CURRENT_DATE}
199
+ # or:
200
+ include Sequel::SQL::Constants
201
+ DB[:events].filter{date < ::CURRENT_DATE}
202
+
203
+ * Database#run was added for executing arbitrary SQL on a database.
204
+ It's an alias for Database#<<, but it allows for a nicer API inside
205
+ migrations, since you can now do:
206
+
207
+ run 'SQL'
208
+
209
+ instead of:
210
+
211
+ self << 'SQL'
212
+
213
+ You can also provide a :server option to run the SQL on the
214
+ given server/shard:
215
+
216
+ run 'SQL', :server=>:shard1
217
+
218
+ * Sequel::Model() can now take a database argument in addition to
219
+ a symbol or dataset argument. If a database is given, it'll create
220
+ an anonymous subclass attached to the given database. Other changes
221
+ were made to allow the following code to work:
222
+
223
+ class Item < Sequel::Model(DB2)
224
+ end
225
+
226
+ That will work correctly assuming a table named items in DB2.
227
+
228
+ * Dataset#ungrouped was added for removing a grouping from an
229
+ existing dataset. Also, Dataset#group when called with no arguments
230
+ or with a nil argument also removes any existing grouping instead
231
+ of resulting in invalid SQL.
232
+
233
+ * Model#modified? was added, letting you know if the model has been
234
+ modified. If the model hasn't been modified, calling
235
+ Model#save_changes will do nothing.
236
+
237
+ * SQL::OrderedExpression now supports #asc, #desc, and #invert.
238
+
239
+ Other Improvements
240
+ ------------------
241
+
242
+ * The serialization and lazy_attribute plugins now add accessor
243
+ methods to a module included in the class, instead of to the
244
+ model class itself. This allows the methods to be overridden
245
+ in the class and work well with super, as well for the plugins
246
+ to work together on the same column. Make sure the
247
+ lazy_attributes accessor is setup before the serialization
248
+ accessor if you want to have a lazy serialized column.
249
+
250
+ * Calling the add_* method for many_to_many association now saves the
251
+ record if the record is new. This makes it operate more similarly
252
+ to one_to_many associations. Previously, it raised an Error.
253
+
254
+ * Dataset#import now works correctly when called with a dataset.
255
+ Previously, it generated incorrect SQL.
256
+
257
+ * The JDBC adapter now converts byte arrays to/from SQL::Blob.
258
+
259
+ * The JDBC adapter now attempts to bind unknown types using
260
+ setObject instead of raising, so it can work with native Java
261
+ objects. It also binds boolean parameters correctly.
262
+
263
+ * Using multiple emulated ALTER TABLE statements (such as
264
+ drop_column) in a single alter_table block now works correctly
265
+ on SQLite.
266
+
267
+ * Database#indexes now works on JDBC for tables in a non-default
268
+ schema. It also now properly detects unique indexes on MSSQL.
269
+
270
+ * Database#schema on JDBC now accepts a :schema option. Also,
271
+ returned schema hashes now include a :column_size entry specifying
272
+ the maximum length/precision for the column, since the
273
+ :db_type entry doesn't have contain the information on JDBC.
274
+
275
+ * Datasets without tables now work correctly on Oracle, so things
276
+ like DB.get(...) now work.
277
+
278
+ * A descriptive error message is given if you attempt to use
279
+ Sequel with the mysql.rb driver (which Sequel doesn't support).
280
+
281
+ * The postgres adapter now works correctly with a modified
282
+ postgres-pr that raises PGErrors instead of RuntimeErrors
283
+ (e.g. http://github.com/jeremyevans/postgres-pr).
284
+
285
+ * You now get a Sequel::InvalidOperation instead of a NoMethodError
286
+ if you attempt to update a dataset without a table.
287
+
288
+ * The inflection support has been modified to reduce code
289
+ duplication.
290
+
291
+ Backwards Compatibility
292
+ -----------------------
293
+
294
+ * Sequel now includes fractional seconds in timestamps for all
295
+ adapters except MySQL. It's possible that this may break
296
+ timestamp columns for databases that are not regularly tested.
297
+
298
+ * Sequel now includes timezone values in timestamps on Microsoft
299
+ SQL Server, Oracle, PostgreSQL and SQLite. The modification for
300
+ SQLite is probably the biggest cause for concern, since SQLite
301
+ stores times as text. If you have an SQLite database that uses
302
+ timestamps and is accessed by something other than Sequel, you
303
+ should make sure that it works with the timestamp format that
304
+ Sequel now uses.
305
+
306
+ * The default timestamp format used by Sequel now uses a space
307
+ instead of 'T' between the date and time parts, which could
308
+ possibly affect some databases that are not regularly tested.
309
+
310
+ * Attempting to insert into a grouped dataset or a dataset that
311
+ selects from multiple tables will now raise an Error. Previously,
312
+ it would ignore any GROUP or JOIN settings and generate bad SQL if
313
+ there were multiple FROM tables.
314
+
315
+ * Database#<< now always returns nil. Before, the return value was
316
+ adapter dependent.
317
+
318
+ * ODBC::Time and ODBC::DateTime values are now converted to the
319
+ Sequel.datetime_class. Before, ODBC::Time used Time and
320
+ ODBC::DateTime used DateTime regardless of the
321
+ Sequel.datetime_class setting.
322
+
323
+ * The default inflections were modified, fixing some obvious errors
324
+ and possibly changing some existing inflections. Further changes
325
+ to the default inflections are unlikely.
data/doc/sharding.rdoc CHANGED
@@ -32,7 +32,7 @@ Let's say you have 4 slave database servers with names slave_server0,
32
32
  slave_server1, slave_server2, and slave_server3.
33
33
 
34
34
  DB=Sequel.connect('postgres://master_server/database', \
35
- :servers=>{:read_only=>proc{|db| :host=>db.get_slave_host}})
35
+ :servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}})
36
36
  def DB.get_slave_host
37
37
  @current_host ||= -1
38
38
  "slave_server#{(@current_host+=1)%4}"
@@ -50,8 +50,8 @@ it shows that the master database is named :default. So for 4 masters and
50
50
  4 slaves:
51
51
 
52
52
  DB=Sequel.connect('postgres://master_server/database', \
53
- :servers=>{:read_only=>proc{|db| :host=>db.get_slave_host}, \
54
- :default=>proc{|db| :host=>db.get_master_host}})
53
+ :servers=>{:read_only=>proc{|db| {:host=>db.get_slave_host}}, \
54
+ :default=>proc{|db| {:host=>db.get_master_host}}})
55
55
  def DB.get_slave_host
56
56
  @current_slave_host ||= -1
57
57
  "slave_server#{(@current_slave_host+=1)%4}"