sequel 3.37.0 → 3.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. data/CHANGELOG +56 -0
  2. data/README.rdoc +82 -58
  3. data/Rakefile +6 -5
  4. data/bin/sequel +1 -1
  5. data/doc/active_record.rdoc +67 -52
  6. data/doc/advanced_associations.rdoc +33 -48
  7. data/doc/association_basics.rdoc +41 -51
  8. data/doc/cheat_sheet.rdoc +21 -21
  9. data/doc/core_extensions.rdoc +374 -0
  10. data/doc/dataset_basics.rdoc +5 -5
  11. data/doc/dataset_filtering.rdoc +47 -43
  12. data/doc/mass_assignment.rdoc +1 -1
  13. data/doc/migration.rdoc +4 -5
  14. data/doc/model_hooks.rdoc +3 -3
  15. data/doc/object_model.rdoc +31 -25
  16. data/doc/opening_databases.rdoc +19 -5
  17. data/doc/prepared_statements.rdoc +2 -2
  18. data/doc/querying.rdoc +109 -52
  19. data/doc/reflection.rdoc +6 -6
  20. data/doc/release_notes/3.38.0.txt +234 -0
  21. data/doc/schema_modification.rdoc +22 -13
  22. data/doc/sharding.rdoc +8 -9
  23. data/doc/sql.rdoc +154 -112
  24. data/doc/testing.rdoc +47 -7
  25. data/doc/thread_safety.rdoc +1 -1
  26. data/doc/transactions.rdoc +1 -1
  27. data/doc/validations.rdoc +1 -1
  28. data/doc/virtual_rows.rdoc +29 -43
  29. data/lib/sequel/adapters/do/postgres.rb +1 -4
  30. data/lib/sequel/adapters/jdbc.rb +14 -3
  31. data/lib/sequel/adapters/jdbc/db2.rb +9 -0
  32. data/lib/sequel/adapters/jdbc/derby.rb +41 -4
  33. data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
  34. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
  35. data/lib/sequel/adapters/mock.rb +10 -4
  36. data/lib/sequel/adapters/postgres.rb +1 -28
  37. data/lib/sequel/adapters/shared/mssql.rb +23 -13
  38. data/lib/sequel/adapters/shared/postgres.rb +46 -0
  39. data/lib/sequel/adapters/swift.rb +21 -13
  40. data/lib/sequel/adapters/swift/mysql.rb +1 -0
  41. data/lib/sequel/adapters/swift/postgres.rb +4 -5
  42. data/lib/sequel/adapters/swift/sqlite.rb +2 -1
  43. data/lib/sequel/adapters/tinytds.rb +14 -2
  44. data/lib/sequel/adapters/utils/pg_types.rb +5 -0
  45. data/lib/sequel/core.rb +29 -17
  46. data/lib/sequel/database/query.rb +1 -1
  47. data/lib/sequel/database/schema_generator.rb +3 -0
  48. data/lib/sequel/dataset/actions.rb +5 -6
  49. data/lib/sequel/dataset/query.rb +7 -7
  50. data/lib/sequel/dataset/sql.rb +5 -18
  51. data/lib/sequel/extensions/core_extensions.rb +8 -12
  52. data/lib/sequel/extensions/pg_array.rb +59 -33
  53. data/lib/sequel/extensions/pg_array_ops.rb +32 -4
  54. data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
  55. data/lib/sequel/extensions/pg_hstore.rb +32 -17
  56. data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
  57. data/lib/sequel/extensions/pg_inet.rb +1 -2
  58. data/lib/sequel/extensions/pg_interval.rb +0 -1
  59. data/lib/sequel/extensions/pg_json.rb +41 -23
  60. data/lib/sequel/extensions/pg_range.rb +36 -11
  61. data/lib/sequel/extensions/pg_range_ops.rb +32 -4
  62. data/lib/sequel/extensions/pg_row.rb +572 -0
  63. data/lib/sequel/extensions/pg_row_ops.rb +164 -0
  64. data/lib/sequel/extensions/query.rb +3 -3
  65. data/lib/sequel/extensions/schema_dumper.rb +7 -8
  66. data/lib/sequel/extensions/select_remove.rb +1 -1
  67. data/lib/sequel/model/base.rb +1 -0
  68. data/lib/sequel/no_core_ext.rb +1 -1
  69. data/lib/sequel/plugins/pg_row.rb +121 -0
  70. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +31 -0
  72. data/lib/sequel/sql.rb +64 -44
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/mssql_spec.rb +37 -12
  75. data/spec/adapters/mysql_spec.rb +39 -75
  76. data/spec/adapters/oracle_spec.rb +11 -11
  77. data/spec/adapters/postgres_spec.rb +414 -237
  78. data/spec/adapters/spec_helper.rb +1 -1
  79. data/spec/adapters/sqlite_spec.rb +14 -14
  80. data/spec/core/database_spec.rb +6 -6
  81. data/spec/core/dataset_spec.rb +169 -205
  82. data/spec/core/expression_filters_spec.rb +182 -295
  83. data/spec/core/object_graph_spec.rb +6 -6
  84. data/spec/core/schema_spec.rb +14 -14
  85. data/spec/core/spec_helper.rb +1 -0
  86. data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
  87. data/spec/extensions/columns_introspection_spec.rb +5 -5
  88. data/spec/extensions/hook_class_methods_spec.rb +28 -36
  89. data/spec/extensions/many_through_many_spec.rb +4 -4
  90. data/spec/extensions/pg_array_ops_spec.rb +15 -7
  91. data/spec/extensions/pg_array_spec.rb +81 -48
  92. data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
  93. data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
  94. data/spec/extensions/pg_hstore_spec.rb +66 -65
  95. data/spec/extensions/pg_inet_spec.rb +2 -4
  96. data/spec/extensions/pg_interval_spec.rb +2 -3
  97. data/spec/extensions/pg_json_spec.rb +20 -18
  98. data/spec/extensions/pg_range_ops_spec.rb +11 -4
  99. data/spec/extensions/pg_range_spec.rb +30 -7
  100. data/spec/extensions/pg_row_ops_spec.rb +48 -0
  101. data/spec/extensions/pg_row_plugin_spec.rb +45 -0
  102. data/spec/extensions/pg_row_spec.rb +323 -0
  103. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  104. data/spec/extensions/query_literals_spec.rb +11 -11
  105. data/spec/extensions/query_spec.rb +3 -3
  106. data/spec/extensions/schema_dumper_spec.rb +20 -4
  107. data/spec/extensions/schema_spec.rb +18 -41
  108. data/spec/extensions/select_remove_spec.rb +4 -4
  109. data/spec/extensions/spec_helper.rb +4 -8
  110. data/spec/extensions/to_dot_spec.rb +5 -5
  111. data/spec/extensions/validation_class_methods_spec.rb +28 -16
  112. data/spec/integration/associations_test.rb +20 -20
  113. data/spec/integration/dataset_test.rb +98 -98
  114. data/spec/integration/eager_loader_test.rb +13 -27
  115. data/spec/integration/plugin_test.rb +5 -5
  116. data/spec/integration/prepared_statement_test.rb +22 -13
  117. data/spec/integration/schema_test.rb +28 -18
  118. data/spec/integration/spec_helper.rb +1 -1
  119. data/spec/integration/timezone_test.rb +2 -2
  120. data/spec/integration/type_test.rb +15 -6
  121. data/spec/model/association_reflection_spec.rb +1 -1
  122. data/spec/model/associations_spec.rb +4 -4
  123. data/spec/model/base_spec.rb +5 -5
  124. data/spec/model/eager_loading_spec.rb +15 -15
  125. data/spec/model/model_spec.rb +32 -32
  126. data/spec/model/record_spec.rb +16 -0
  127. data/spec/model/spec_helper.rb +2 -6
  128. data/spec/model/validations_spec.rb +1 -1
  129. metadata +16 -4
data/bin/sequel CHANGED
@@ -135,7 +135,7 @@ begin
135
135
  DB = connect_proc[db]
136
136
  load_dirs.each{|d| d.is_a?(Array) ? require(d.first) : Dir["#{d}/**/*.rb"].each{|f| load(f)}}
137
137
  if migrate_dir
138
- Sequel.extension :migration
138
+ Sequel.extension :migration, :core_extensions
139
139
  Sequel::Migrator.apply(DB, migrate_dir, migrate_ver)
140
140
  exit
141
141
  end
@@ -75,7 +75,7 @@ Sequel's +hook_class_methods+ plugin is modeled directly on ActiveRecord's callb
75
75
  end
76
76
  end
77
77
 
78
- Observers can be implemented completely by hooks, so Sequel doesn't allow you to define them separately.
78
+ Observers can be implemented completely by hooks, so Sequel doesn't offer a separate observer class.
79
79
 
80
80
  == Inheritance
81
81
 
@@ -150,16 +150,12 @@ Sequel supports logging of all database queries by allowing multiple loggers for
150
150
  Sequel supports migrations and has a migrator similar to ActiveRecord:
151
151
 
152
152
  Sequel.migration do
153
- up do
153
+ change do
154
154
  create_table(:albums) do
155
155
  primary_key :id
156
156
  String :name
157
157
  end
158
158
  end
159
-
160
- down do
161
- drop_table(:albums)
162
- end
163
159
  end
164
160
 
165
161
  == Differences
@@ -175,7 +171,7 @@ Unlike ActiveRecord 2, Sequel uses method chains on datasets for retrieving obje
175
171
 
176
172
  Sequel uses:
177
173
 
178
- Album.filter{name > 'RF'}.filter(:artist_id=>1).order(:copies_sold).
174
+ Album.where{name > 'RF'}.where(:artist_id=>1).order(:copies_sold).
179
175
  select(:id, :name).all
180
176
 
181
177
  Note that the records aren't retrieved until +all+ is called.
@@ -184,11 +180,11 @@ ActiveRecord 3 adopts this method chaining approach, so if you are familiar with
184
180
 
185
181
  === No Need for SQL String Fragments
186
182
 
187
- As the example above shows, most ActiveRecord code uses SQL string fragments. With Sequel, you rarely need to. Sequel's DSL allows you to create complex queries without ever specifying SQL string fragments (called literal strings in Sequel).
183
+ Like the example above, most ActiveRecord code uses SQL string fragments. With Sequel, you rarely need to. Sequel's DSL allows you to create complex queries without ever specifying SQL string fragments (called literal strings in Sequel).
188
184
 
189
- If you want to use SQL string fragments, Sequel makes it easy by using the <tt>String#lit</tt> method:
185
+ If you want to use SQL string fragments, Sequel makes it easy by using the <tt>Sequel.lit</tt> method:
190
186
 
191
- Album.select('id, name'.lit)
187
+ Album.select(Sequel.lit('id, name'))
192
188
 
193
189
  This usage is not encouraged, though. The recommended way is to use symbols to represent the columns:
194
190
 
@@ -214,17 +210,17 @@ A third reason to not use SQL string fragments is database independence. For ex
214
210
 
215
211
  This is because LIKE is case sensitive on PostgreSQL, but case insensitive on MySQL. With Sequel, you would do:
216
212
 
217
- Album.filter(:name.ilike('A%'))
213
+ Album.filter(Sequel.ilike(:name, 'A%'))
218
214
 
219
215
  This will do a case insensitive search on both databases. If you want a case sensitive search on both, you can use +like+ instead of +ilike+.
220
216
 
221
217
  String concatenation is a similar area, where MySQL and PostgreSQL handle things differently. With Sequel, the same code can work on both databases:
222
218
 
223
- Album.select(:name.sql_string + ' - Name')
219
+ Album.select(Sequel.join([:name, ' - Name']))
224
220
 
225
221
  == Flexible Overriding
226
222
 
227
- Unlike ActiveRecord, which forces you to alias methods if you want to override them, with Sequel you just override the methods and call super:
223
+ Unlike ActiveRecord 2, which forces you to alias methods if you want to override them, with Sequel you just override the methods and call super:
228
224
 
229
225
  class Sequel::Model
230
226
  def after_update
@@ -254,7 +250,7 @@ You can override almost all model class or instance methods this way, just remem
254
250
 
255
251
  == +method_missing+ Missing
256
252
 
257
- Sequel does not use +method_missing+ unless the object truely needs it. Neither <tt>Sequel::Model</tt> nor <tt>Sequel::Dataset</tt> nor <tt>Sequel::Database</tt> implement +method_missing+ at either a class or model level. So if you call +methods+, you can see which methods are available, and if they aren't listed, then the object won't respond to them. Among other things, this means Sequel does not support dynamic finders. So instead of:
253
+ Sequel does not use +method_missing+ unless it's required that the object respond to potentially any method. Neither <tt>Sequel::Model</tt> nor <tt>Sequel::Dataset</tt> nor <tt>Sequel::Database</tt> implement +method_missing+ at either a class or instance level. So if you call +methods+, you can see which methods are available, and if they aren't listed, then the object won't respond to them. Among other things, this means Sequel does not support dynamic finders. So instead of:
258
254
 
259
255
  Album.find_or_create_by_name("RF")
260
256
 
@@ -309,7 +305,7 @@ Sequel supports the same basic association hooks/callbacks as ActiveRecord. It
309
305
  If you pass a block to an association method, it's used to return a modified dataset used for the association, instead of to create an association extension:
310
306
 
311
307
  Artist.one_to_many :gold_albums, :class=>:Album do |ds|
312
- ds.filter{copies_sold > 500000}
308
+ ds.where{copies_sold > 500000}
313
309
  end
314
310
 
315
311
  If you want to create an association extension, you can use the <tt>:extend</tt> association option with a module, which ActiveRecord also supports. In Sequel, the extensions are applied to the association dataset, not to the array of associated objects. You can access the association dataset using the +association_dataset+ method:
@@ -319,13 +315,13 @@ If you want to create an association extension, you can use the <tt>:extend</tt>
319
315
 
320
316
  Association datasets are just like any other Sequel dataset, in that you can filter them and manipulate them further:
321
317
 
322
- gold_albums = artist.albums_dataset.filter{copies_sold > 500000}.order(:name).all
318
+ gold_albums = artist.albums_dataset.where{copies_sold > 500000}.order(:name).all
323
319
 
324
320
  Sequel caches associated objects similarly to ActiveRecord, and you can skip the cache by passing +true+ to the association method, just like ActiveRecord.
325
321
 
326
322
  === Eager Loading
327
323
 
328
- ActiveRecord tries to guess whether to use preloading or JOINs for eager loading by scanning the SQL string fragments you provide for table names. This is error prone and Sequel avoids it by giving you separate methods. In Sequel, +eager+ is used for preloading and +eager_graph+ is used for JOINs. Both have the same API:
324
+ ActiveRecord 2 tries to guess whether to use preloading or JOINs for eager loading by scanning the SQL string fragments you provide for table names. This is error prone and Sequel avoids it by giving you separate methods. In Sequel, +eager+ is used for preloading and +eager_graph+ is used for JOINs. Both have the same API:
329
325
 
330
326
  Artist.eager(:albums=>[:tags, :tracks])
331
327
  Album.eager_graph(:artist, :tracks)
@@ -361,6 +357,24 @@ Table aliasing when eager loading via +eager_graph+ is different in Sequel than
361
357
  # LEFT OUTER JOIN nodes AS children_0 ON (children_0.parent_id = children.id) -- grandchildren
362
358
  # LEFT OUTER JOIN nodes AS children_1 ON (children_1.parent_id = children_0.id) -- great grandchildren
363
359
 
360
+ You can specify aliases on a per join basis, too:
361
+
362
+ Node.eager_graph(:parent=>Sequel.as(:parent, :grandparent),
363
+ :children=>{Sequel.as(:children, :grandchildren)=>Sequel.as(:children, :great_grandchildren)}).all
364
+
365
+ # SELECT nodes.id, nodes.parent_id,
366
+ # parent.id AS parent_id_0, parent.parent_id AS parent_parent_id,
367
+ # grandparent.id AS grandparent_id, grandparent.parent_id AS grandparent_parent_id,
368
+ # children.id AS children_id, children.parent_id AS children_parent_id,
369
+ # grandchildren.id AS grandchildren_id, grandchildren.parent_id AS grandchildren_parent_id,
370
+ # great_grandchildren.id AS great_grandchildren_id, great_grandchildren.parent_id AS great_grandchildren_parent_id
371
+ # FROM nodes
372
+ # LEFT OUTER JOIN nodes AS parent ON (parent.id = nodes.parent_id)
373
+ # LEFT OUTER JOIN nodes AS grandparent ON (grandparent.id = parent.parent_id)
374
+ # LEFT OUTER JOIN nodes AS children ON (children.parent_id = nodes.id)
375
+ # LEFT OUTER JOIN nodes AS grandchildren ON (grandchildren.parent_id = children.id)
376
+ # LEFT OUTER JOIN nodes AS great_grandchildren ON (great_grandchildren.parent_id = grandchildren.id)
377
+
364
378
  === Options
365
379
 
366
380
  Sequel supports many more association options than ActiveRecord, but here's a mapping of ActiveRecord association options to Sequel association options. Note that when you specify columns in Sequel, you use symbols, not strings. Where ActiveRecord would use an SQL string fragment with embedded commas for multiple columns, Sequel would use an array of column symbols.
@@ -383,7 +397,7 @@ ActiveRecord option :: Sequel option
383
397
  <tt>:polymorphic</tt>, <tt>:as</tt>, <tt>:source_type</tt> :: The +sequel_polymorphic+ external plugin
384
398
  <tt>:include</tt> :: <tt>:eager</tt>, <tt>:eager_graph</tt>
385
399
  <tt>:readonly</tt> :: No equivalent, the Sequel <tt>:read_only</tt> option just means the modification methods are not created (it makes the association read only, not records retrieved through the association)
386
- <tt>:through</tt>, <tt>:source</tt> :: Use a +many_to_many+ association
400
+ <tt>:through</tt>, <tt>:source</tt> :: Use a +many_to_many+ association, or the +many_through_many+ plugin
387
401
  <tt>:touch</tt> :: The +touch+ plugin
388
402
  <tt>:autosave</tt> :: A +before_save+ or +after_save+ hook
389
403
  <tt>:finder_sql</tt> :: <tt>:dataset</tt> to set a custom dataset
@@ -505,15 +519,15 @@ You can call +with_sql+ to set the SQL to use, and the +single_value+ to retriev
505
519
 
506
520
  Calling +delete+ directly on the class will probably delete all rows in the table. You want to filter first, then call +delete+:
507
521
 
508
- Album.filter(:id=>id).delete
509
- Album.filter("artist_id = ?", 5).delete
522
+ Album.where(:id=>id).delete
523
+ Album.where("artist_id = ?", 5).delete
510
524
 
511
525
  ==== +destroy+, +destroy_all+
512
526
 
513
527
  Similar to +delete+, you filter first, then +destroy+:
514
528
 
515
- Album.filter(:id=>id).destroy
516
- Album.filter("artist_id = ?", 5).destroy
529
+ Album.where(:id=>id).destroy
530
+ Album.where("artist_id = ?", 5).destroy
517
531
 
518
532
  ==== +establish_connection+
519
533
 
@@ -531,7 +545,7 @@ If you want a specific dataset in that database, you can use +set_dataset+ or <t
531
545
 
532
546
  You need to filter the dataset first, then call <tt>empty?</tt> and invert the result:
533
547
 
534
- !Album.filter(:id=>1).empty?
548
+ !Album.where(:id=>1).empty?
535
549
 
536
550
  ==== +find+
537
551
 
@@ -543,21 +557,21 @@ Note that Sequel returns nil if no record is found, it doesn't raise an exceptio
543
557
 
544
558
  If you want to find multiple objects using an array of primary keys:
545
559
 
546
- Album.filter(:id=>[1, 2, 3]).all
560
+ Album.where(:id=>[1, 2, 3]).all
547
561
 
548
562
  If you are using <tt>find(:first, ...)</tt>, you use a method chain instead of passing the options, and end it with +first+:
549
563
 
550
- Album.filter(:artist_id=>1).order(:name).first
564
+ Album.where(:artist_id=>1).order(:name).first
551
565
 
552
566
  If you are using <tt>find(:last, ...)</tt>, you need to specify an order in Sequel, but the same method chain approach is used, which you end with +last+:
553
567
 
554
- Album.filter(:artist_id=>1).order(:name).last
568
+ Album.where(:artist_id=>1).order(:name).last
555
569
  # You could also do:
556
- Album.filter(:artist_id=>1).order(:name.desc).first
570
+ Album.where(:artist_id=>1).reverse_order(:name).first
557
571
 
558
572
  If you are using <tt>find(:all, ...)</tt>, you use a method chain instead of passing the options, and end it with +all+:
559
573
 
560
- Album.filter(:artist_id=>1).order(:name).all
574
+ Album.where(:artist_id=>1).order(:name).all
561
575
 
562
576
  Here's a mapping of ActiveRecord +find+ options to <tt>Sequel::Dataset</tt> methods:
563
577
 
@@ -583,13 +597,13 @@ Similar to +count_by_sql+, you use +with_sql+, followed by +all+:
583
597
 
584
598
  Just like with <tt>find(:first, ...)</tt>, you use a method chain instead of passing the options, and end it with +first+:
585
599
 
586
- Album.filter(:artist_id=>1).order(:name).first
600
+ Album.where(:artist_id=>1).order(:name).first
587
601
 
588
602
  ==== +last+
589
603
 
590
604
  Just like with <tt>find(:last, ...)</tt>, you use a method chain instead of passing the options, make sure it includes an order, and end it with +last+:
591
605
 
592
- Album.filter(:artist_id=>1).order(:name).last
606
+ Album.where(:artist_id=>1).order(:name).last
593
607
 
594
608
  ==== +named_scope+
595
609
 
@@ -598,12 +612,17 @@ For a pure filter, you can use +subset+:
598
612
  Album.subset(:debut, :position => 1)
599
613
  Album.subset(:gold){copies_sold > 500000}
600
614
 
601
- For anything more complex, you can use +def_dataset_method+:
615
+ For anything more complex, you can use +dataset_module+:
602
616
 
603
- Album.def_dataset_method(:by_artist) do |artist_id|
604
- filter(:artist_id=>artist_id)
617
+ Album.dataset_module do
618
+ def by_artist(artist_id)
619
+ where(:artist_id=>artist_id)
620
+ end
621
+
622
+ def by_release_date
623
+ order(:release_date)
624
+ end
605
625
  end
606
- Album.def_dataset_method(:by_release_date){order(:release_date)}
607
626
 
608
627
  ==== +reset_column_information+
609
628
 
@@ -613,13 +632,13 @@ If you want to completely reload the schema for the table:
613
632
 
614
633
  ==== +serialize+, +seralized_attributes+
615
634
 
616
- Sequel ships with a +serialization+ plugin that you can use. It's more flexible than ActiveRecord's, since you can serialize to marshal or json in addition to yaml:
635
+ Sequel ships with a +serialization+ plugin that you can use.
617
636
 
618
637
  class Album < Sequel::Model
619
638
  plugin :serialization, :json, :permissions
620
639
  end
621
640
 
622
- For +serialized_attributes+, you can use +serialization_map+, which is also a hash, but keys are column symbols and values are either <tt>:marshal</tt>, <tt>:yaml</tt>, or <tt>:json</tt>, specifying the serialization format.
641
+ For +serialized_attributes+, you can use +serialization_map+, which is also a hash, but keys are column symbols and values are callable objects used to serialize the values.
623
642
 
624
643
  ==== +set_inheritance_column+
625
644
 
@@ -657,13 +676,13 @@ As mentioned earlier, +transaction+ is a database method in Sequel, which you ca
657
676
 
658
677
  Just like +delete+ and +destroy+, you filter first, then +update+:
659
678
 
660
- Album.filter(:id=>id).update(:name=>'RF')
661
- Album.filter("artist_id = ?", 5).update(:copies_sold=>0)
679
+ Album.where(:id=>id).update(:name=>'RF')
680
+ Album.where("artist_id = ?", 5).update(:copies_sold=>0)
662
681
 
663
682
  Note that +update+ in that case will operate on a dataset, so it won't run model validations or hooks. If you want those run:
664
683
 
665
684
  Album[id].update(:name=>'RF')
666
- Album.filter("artist_id = ?", 5).all{|a| a.update(:copies_sold=>0)}
685
+ Album.where("artist_id = ?", 5).all{|a| a.update(:copies_sold=>0)}
667
686
 
668
687
  ==== +with_scope+
669
688
 
@@ -694,7 +713,7 @@ ActiveRecord Method :: Sequel Method
694
713
  +set_primary_key+ :: +set_primary_key+
695
714
  +sum+ :: +sum+
696
715
  +table_name+ :: +table_name+
697
- +unescoped+ :: +unfiltered+
716
+ +unscoped+ :: +unfiltered+
698
717
 
699
718
  === Class Methods without an Equivalent
700
719
 
@@ -715,12 +734,12 @@ ActiveRecord Method :: Notes, Workarounds
715
734
  <tt>clear_active_connections!</tt> :: Sequel doesn't leak connections like ActiveRecord, so you don't need to worry about this
716
735
  <tt>clear_reloadable_connections!</tt> :: Sequel doesn't leak connections like ActiveRecord, so you don't need to worry about this
717
736
  +content_columns+ :: Not needed internally, you can probably do <tt>Album.columns.map{|x| x.to_s}.delete_if{|x| x == Album.primary_key || x =~ /_(id|count)\z/}</tt>
718
- +decrement_counter+ :: <tt>Album.filter(:id=>:id).update(:counter_name=>:counter_name - 1)</tt>
737
+ +decrement_counter+ :: <tt>Album.where(:id=>:id).update(:counter_name=>:counter_name - 1)</tt>
719
738
  +define_attribute_methods+, +define_read_methods+ :: <tt>def_column_accessor(*columns)</tt>, a private method
720
739
  <tt>descends_from_active_record?</tt> :: Not needed internally, if using single table inheritance, <tt>Album.sti_dataset.model == Album</tt>
721
740
  +find_each+, +find_in_batches+ :: Use the +pagination+ extension
722
741
  <tt>generated_methods?</tt> :: No equivalent
723
- +increment_counter+ :: <tt>Album.filter(:id=>:id).update(:counter_name=>:counter_name + 1)</tt>
742
+ +increment_counter+ :: <tt>Album.where(:id=>:id).update(:counter_name=>:counter_name + 1)</tt>
724
743
  <tt>instance_method_already_implemented?</tt> :: No equivalent, Sequel does not create column accessors that override other methods, it just skips them.
725
744
  <tt>match_attribute_method?</tt> :: No equivalent
726
745
  +readonly_attributes+ :: No equivalent
@@ -729,7 +748,7 @@ ActiveRecord Method :: Notes, Workarounds
729
748
  +silence+ :: No equivalent. Because the logger is handled at the <tt>Sequel::Database</tt> level, there is no thread-safe way to turn it off for specific blocks.
730
749
  +scopes+ :: No equivalent
731
750
  +sti_name+ :: No equivalent.
732
- +update_counters+ :: <tt>Album.filter(:id=>:id).update(:counter_name=>:counter_name + 1, :other_counter=>:other_counter - 1)</tt>
751
+ +update_counters+ :: <tt>Album.where(:id=>:id).update(:counter_name=>:counter_name + 1, :other_counter=>:other_counter - 1)</tt>
733
752
  +uncached+ :: No equivalent
734
753
 
735
754
  === Instance Methods with Significantly Different Behavior
@@ -789,13 +808,6 @@ Assuming you want the full behavior of saving just one column without validating
789
808
  album.values[:column] -= 1 # or += 1 for increment!
790
809
  album.save(:column, :validate=>false)
791
810
 
792
- ==== +freeze+, <tt>frozen?</tt>
793
-
794
- Sequel doesn't support freezing objects directly, but you can do it yourself:
795
-
796
- album.values.freeze
797
- album.values.frozen?
798
-
799
811
  ==== <tt>has_attribute?</tt>
800
812
 
801
813
  You have to check the values hash:
@@ -870,6 +882,8 @@ ActiveRecord Method :: Sequel Method
870
882
  +destroy+ :: +destroy+
871
883
  <tt>eql?</tt> :: <tt>===</tt>
872
884
  +errors+ :: +errors+
885
+ +freeze+ :: +freeze+
886
+ <tt>frozen?</tt> :: <tt>frozen?</tt>
873
887
  +hash+ :: +hash+
874
888
  +id+ :: +pk+
875
889
  +inspect+ :: +inspect+
@@ -884,12 +898,13 @@ ActiveRecord Method :: Sequel Method
884
898
 
885
899
  ActiveRecord Method :: Notes, Workarounds
886
900
  +after_validation_on_create+, +after_validation_on_update+ :: Use +after_validation+ and <tt>if new?</tt> or <tt>unless new?</tt>
887
- +as_json+, +from_json+, +to_json+, +from_xml+, +to_xml+ :: No equivalent
901
+ +as_json+, +from_json+, +to_json+ :: Use the +json_serializer+ plugin
902
+ +from_xml+, +to_xml+ :: Use the +xml_serializer+ plugin
888
903
  +attribute_for_inspect+ :: <tt>album[:column].inspect</tt>
889
904
  <tt>attribute_present?</tt> :: <tt>!album[:column].blank?</tt> if using the +blank+ extension
890
905
  +attributes_before_type_cast+ :: Sequel typecasts at a low level, so model objects never see values before they are type cast
891
906
  +before_validation_on_create+, +before_validation_on_update+ :: Use +before_validation+ and <tt>if new?</tt> or <tt>unless new?</tt>
892
- <tt>id=</tt> :: Sequel doesn't have a special primary key setter method, but you can use: <tt>album.send("#{primary_key}=", value)</tt>
907
+ <tt>id=</tt> :: Sequel doesn't have a special primary key setter method, but you can use: <tt>album.send("#{Album.primary_key}=", value)</tt>
893
908
  +mark_for_destruction+, <tt>marked_for_destruction?</tt> :: Use a +before_save+ or +after_save+ hook or the +instance_hooks+ plugin
894
909
  <tt>readonly!</tt> :: No equivalent
895
910
  <tt>readonly?</tt> :: No equivalent
@@ -14,7 +14,7 @@ a different block when eager loading via <tt>Dataset#eager</tt>. Association blo
14
14
  useful for things like:
15
15
 
16
16
  Artist.one_to_many :gold_albums, :class=>:Album do |ds|
17
- ds.filter{copies_sold > 500000}
17
+ ds.where{copies_sold > 500000}
18
18
  end
19
19
 
20
20
  There are a whole bunch of options for changing how the association is eagerly
@@ -50,12 +50,12 @@ These can be used like this:
50
50
 
51
51
  # Only returns albums that have sold more than 500,000 copies
52
52
  Artist.one_to_many :gold_albums, :class=>:Album, \
53
- :graph_block=>proc{|j,lj,js| :copies_sold.qualify(j) > 500000}
53
+ :graph_block=>proc{|j,lj,js| Sequel.qualify(j, :copies_sold) > 500000}
54
54
 
55
55
  # Handles the case where the tables are associated by a case insensitive name string
56
56
  Artist.one_to_many :albums, :key=>:artist_name, \
57
57
  :graph_only_conditions=>nil, \
58
- :graph_block=>proc{|j,lj,js| {:lower.sql_function(artist_name.qualify(j))=>:lower.sql_function(name.qualify(lj))}}
58
+ :graph_block=>proc{|j,lj,js| {Sequel.function(:lower, Sequel.qualify(j, :artist_name))=>Sequel.function(:lower, Sequel.qualify(lj, :name))}}
59
59
 
60
60
  # Handles the case where both key columns have the name artist_name, and you want to use
61
61
  # a JOIN USING
@@ -235,9 +235,9 @@ require different approaches or custom <tt>:eager_loader</tt> options.
235
235
 
236
236
  === Association callbacks
237
237
 
238
- Sequel supports the same callbacks that ActiveRecord does on +one_to_many+ and
238
+ Sequel supports the same callbacks that ActiveRecord does for +one_to_many+ and
239
239
  +many_to_many+ associations: <tt>:before_add</tt>, <tt>:before_remove</tt>, <tt>:after_add</tt>, and
240
- <tt>:after_remove</tt>. One +many_to_one+ associations and +one_to_one+ associations, Sequel
240
+ <tt>:after_remove</tt>. For +many_to_one+ associations and +one_to_one+ associations, Sequel
241
241
  supports the <tt>:before_set</tt> and <tt>:after_set</tt> callbacks. On all associations,
242
242
  Sequel supports <tt>:after_load</tt>, which is called after the association has been
243
243
  loaded.
@@ -260,7 +260,7 @@ otherwise modified:
260
260
  class Author < Sequel::Model
261
261
  one_to_many :authorships
262
262
  end
263
- Author.first.authorships_dataset.filter{|o| o.number < 10}.first
263
+ Author.first.authorships_dataset.where{number < 10}.first
264
264
 
265
265
  You can extend a dataset with a module using the <tt>:extend</tt> association option. You can reference
266
266
  the model object that created the association dataset via the dataset's
@@ -381,7 +381,7 @@ Polymorphic associations break referential integrity and are significantly more
381
381
  complex than non-polymorphic associations, so their use is not recommended unless
382
382
  you are stuck with an existing design that uses them.
383
383
 
384
- If you must use them, look for the sequel_polymorphic plugin, as it makes using
384
+ If you must use them, look for the sequel_polymorphic external plugin, as it makes using
385
385
  polymorphic associations in Sequel about as easy as it is in ActiveRecord. However,
386
386
  here's how they can be done using Sequel's custom associations (the sequel_polymorphic
387
387
  plugin is just a generic version of this code):
@@ -409,7 +409,7 @@ Sequel::Model:
409
409
  many_to_one :attachable, :reciprocal=>:assets, \
410
410
  :dataset=>(proc do
411
411
  klass = attachable_type.constantize
412
- klass.filter(klass.primary_key=>attachable_id)
412
+ klass.where(klass.primary_key=>attachable_id)
413
413
  end), \
414
414
  :eager_loader=>(proc do |eo|
415
415
  id_map = {}
@@ -419,7 +419,7 @@ Sequel::Model:
419
419
  end
420
420
  id_map.each do |klass_name, id_map|
421
421
  klass = klass_name.constantize
422
- klass.filter(klass.primary_key=>id_map.keys).all do |attach|
422
+ klass.where(klass.primary_key=>id_map.keys).all do |attach|
423
423
  id_map[attach.pk].each do |asset|
424
424
  asset.associations[:attachable] = attach
425
425
  end
@@ -436,48 +436,34 @@ Sequel::Model:
436
436
  end
437
437
 
438
438
  class Post < Sequel::Model
439
- one_to_many :assets, :key=>:attachable_id do |ds|
440
- ds.filter(:attachable_type=>'Post')
441
- end
439
+ one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable, :conditions=>{:attachable_type=>'Post'}
442
440
 
443
441
  private
444
442
 
445
443
  def _add_asset(asset)
446
- asset.attachable_id = pk
447
- asset.attachable_type = 'Post'
448
- asset.save
449
- end
444
+ asset.update(:attachable_id=>pk, :attachable_type=>'Post')
445
+ end
450
446
  def _remove_asset(asset)
451
- asset.attachable_id = nil
452
- asset.attachable_type = nil
453
- asset.save
454
- end
447
+ asset.update(:attachable_id=>nil, :attachable_type=>nil)
448
+ end
455
449
  def _remove_all_assets
456
- Asset.filter(:attachable_id=>pk, :attachable_type=>'Post')\
457
- .update(:attachable_id=>nil, :attachable_type=>nil)
450
+ assets_dataset.update(:attachable_id=>nil, :attachable_type=>nil)
458
451
  end
459
452
  end
460
453
 
461
454
  class Note < Sequel::Model
462
- one_to_many :assets, :key=>:attachable_id do |ds|
463
- ds.filter(:attachable_type=>'Note')
464
- end
455
+ one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable, :conditions=>{:attachable_type=>'Note'}
465
456
 
466
457
  private
467
458
 
468
459
  def _add_asset(asset)
469
- asset.attachable_id = pk
470
- asset.attachable_type = 'Note'
471
- asset.save
460
+ asset.update(:attachable_id=>pk, :attachable_type=>'Note')
472
461
  end
473
462
  def _remove_asset(asset)
474
- asset.attachable_id = nil
475
- asset.attachable_type = nil
476
- asset.save
463
+ asset.update(:attachable_id=>nil, :attachable_type=>nil)
477
464
  end
478
465
  def _remove_all_assets
479
- Asset.filter(:attachable_id=>pk, :attachable_type=>'Note')\
480
- .update(:attachable_id=>nil, :attachable_type=>nil)
466
+ assets_dataset.update(:attachable_id=>nil, :attachable_type=>nil)
481
467
  end
482
468
  end
483
469
 
@@ -519,7 +505,7 @@ node.children. You can even eager load the relationship up to a certain depth:
519
505
  # Eager load three generations of generations of children for a given node
520
506
  Node.filter(:id=>1).eager(:children=>{:children=>:children}).all.first
521
507
  # Load parents and grandparents for a group of nodes
522
- Node.filter{|o| o.id < 10}.eager(:parent=>:parent).all
508
+ Node.filter{id < 10}.eager(:parent=>:parent).all
523
509
 
524
510
  What if you want to get all ancestors up to the root node, or all descendents,
525
511
  without knowing the depth of the tree?
@@ -544,7 +530,7 @@ without knowing the depth of the tree?
544
530
  non_root_nodes.each{|n| (id_map[n.parent_id] ||= []) << n}
545
531
  # Doesn't cause an infinte loop, because when only the root node
546
532
  # is left, this is not called.
547
- Node.filter(Node.primary_key=>id_map.keys).eager(:ancestors).all do |node|
533
+ Node.where(Node.primary_key=>id_map.keys).eager(:ancestors).all do |node|
548
534
  # Populate the parent association for each node
549
535
  id_map[node.pk].each{|n| n.associations[:parent] = node}
550
536
  end
@@ -562,7 +548,7 @@ without knowing the depth of the tree?
562
548
  # if no records are returned. Exclude id = parent_id to avoid infinite loop
563
549
  # if the root note is one of the returned records and it has parent_id = id
564
550
  # instead of parent_id = NULL.
565
- Node.filter(:parent_id=>id_map.keys).exclude(:id=>:parent_id).eager(:descendants).all do |node|
551
+ Node.where(:parent_id=>id_map.keys).exclude(:id=>:parent_id).eager(:descendants).all do |node|
566
552
  # Get the parent from the identity map
567
553
  parent = id_map[node.parent_id]
568
554
  # Set the child's parent association to the parent
@@ -585,7 +571,7 @@ this easy:
585
571
 
586
572
  === Joining multiple keys to a single key, through a third table
587
573
 
588
- Let's say you have a database, of songs, lyrics, and artists. Each song
574
+ Let's say you have a database of songs, lyrics, and artists. Each song
589
575
  may or may not have a lyric (most songs are instrumental). The lyric can be
590
576
  associated to an artist in each of four ways: composer, arranger, vocalist,
591
577
  or lyricist. These may all be the same, or they could all be different, and
@@ -598,14 +584,15 @@ name, with no duplicates?
598
584
 
599
585
  class Artist < Sequel::Model
600
586
  one_to_many :songs, :order=>:songs__name, \
601
- :dataset=>proc{Song.select(:songs.*).join(Lyric, :id=>:lyric_id, id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])}, \
587
+ :dataset=>proc{Song.select_all(:songs).join(Lyric, :id=>:lyric_id, id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])}, \
602
588
  :eager_loader=>(proc do |eo|
603
589
  h = eo[:id_map]
604
590
  ids = h.keys
605
591
  eo[:rows].each{|r| r.associations[:songs] = []}
606
- Song.select(:songs.*, :lyrics__composer_id, :lyrics__arranger_id, :lyrics__vocalist_id, :lyrics__lyricist_id)\
607
- .join(Lyric, :id=>:lyric_id){{:composer_id=>ids, :arranger_id=>ids, :vocalist_id=>ids, :lyricist_id=>ids}.sql_or}\
608
- .order(:songs__name).all do |song|
592
+ Song.select_all(:songs).
593
+ select_append(:lyrics__composer_id, :lyrics__arranger_id, :lyrics__vocalist_id, :lyrics__lyricist_id).
594
+ join(Lyric, :id=>:lyric_id){Sequel.or(:composer_id=>ids, :arranger_id=>ids, :vocalist_id=>ids, :lyricist_id=>ids)}.
595
+ order(:songs__name).all do |song|
609
596
  [:composer_id, :arranger_id, :vocalist_id, :lyricist_id].each do |x|
610
597
  recs = h[song.values.delete(x)]
611
598
  recs.each{|r| r.associations[:songs] << song} if recs
@@ -628,12 +615,12 @@ associated tickets.
628
615
  class Project < Sequel::Model
629
616
  one_to_many :tickets
630
617
  many_to_one :ticket_hours, :read_only=>true, :key=>:id,
631
- :dataset=>proc{Ticket.filter(:project_id=>id).select{sum(hours).as(hours)}},
618
+ :dataset=>proc{Ticket.where(:project_id=>id).select{sum(hours).as(hours)}},
632
619
  :eager_loader=>(proc do |eo|
633
620
  eo[:rows].each{|p| p.associations[:ticket_hours] = nil}
634
- Ticket.filter(:project_id=>eo[:id_map].keys).
635
- group(:project_id).
636
- select{[project_id, sum(hours).as(hours)]}.
621
+ Ticket.where(:project_id=>eo[:id_map].keys).
622
+ select_group(:project_id).
623
+ select_append{sum(hours).as(hours)}.
637
624
  all do |t|
638
625
  p = eo[:id_map][t.values.delete(:project_id)].first
639
626
  p.associations[:ticket_hours] = t
@@ -654,6 +641,4 @@ associated tickets.
654
641
  end
655
642
 
656
643
  Note that it is often better to use a sum cache instead of this approach. You can implement
657
- a sum cache using +after_create+ and +after_delete+ hooks, or using a database trigger
658
- (the preferred method if you only have to support one database and that database supports
659
- triggers).
644
+ a sum cache using +after_create+ and +after_delete+ hooks, or preferrably using a database trigger.