sequel 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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}"