sequel 3.37.0 → 3.38.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/README.rdoc +82 -58
- data/Rakefile +6 -5
- data/bin/sequel +1 -1
- data/doc/active_record.rdoc +67 -52
- data/doc/advanced_associations.rdoc +33 -48
- data/doc/association_basics.rdoc +41 -51
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +5 -5
- data/doc/dataset_filtering.rdoc +47 -43
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +4 -5
- data/doc/model_hooks.rdoc +3 -3
- data/doc/object_model.rdoc +31 -25
- data/doc/opening_databases.rdoc +19 -5
- data/doc/prepared_statements.rdoc +2 -2
- data/doc/querying.rdoc +109 -52
- data/doc/reflection.rdoc +6 -6
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/schema_modification.rdoc +22 -13
- data/doc/sharding.rdoc +8 -9
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +47 -7
- data/doc/thread_safety.rdoc +1 -1
- data/doc/transactions.rdoc +1 -1
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +29 -43
- data/lib/sequel/adapters/do/postgres.rb +1 -4
- data/lib/sequel/adapters/jdbc.rb +14 -3
- data/lib/sequel/adapters/jdbc/db2.rb +9 -0
- data/lib/sequel/adapters/jdbc/derby.rb +41 -4
- data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
- data/lib/sequel/adapters/mock.rb +10 -4
- data/lib/sequel/adapters/postgres.rb +1 -28
- data/lib/sequel/adapters/shared/mssql.rb +23 -13
- data/lib/sequel/adapters/shared/postgres.rb +46 -0
- data/lib/sequel/adapters/swift.rb +21 -13
- data/lib/sequel/adapters/swift/mysql.rb +1 -0
- data/lib/sequel/adapters/swift/postgres.rb +4 -5
- data/lib/sequel/adapters/swift/sqlite.rb +2 -1
- data/lib/sequel/adapters/tinytds.rb +14 -2
- data/lib/sequel/adapters/utils/pg_types.rb +5 -0
- data/lib/sequel/core.rb +29 -17
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/dataset/actions.rb +5 -6
- data/lib/sequel/dataset/query.rb +7 -7
- data/lib/sequel/dataset/sql.rb +5 -18
- data/lib/sequel/extensions/core_extensions.rb +8 -12
- data/lib/sequel/extensions/pg_array.rb +59 -33
- data/lib/sequel/extensions/pg_array_ops.rb +32 -4
- data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
- data/lib/sequel/extensions/pg_hstore.rb +32 -17
- data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
- data/lib/sequel/extensions/pg_inet.rb +1 -2
- data/lib/sequel/extensions/pg_interval.rb +0 -1
- data/lib/sequel/extensions/pg_json.rb +41 -23
- data/lib/sequel/extensions/pg_range.rb +36 -11
- data/lib/sequel/extensions/pg_range_ops.rb +32 -4
- data/lib/sequel/extensions/pg_row.rb +572 -0
- data/lib/sequel/extensions/pg_row_ops.rb +164 -0
- data/lib/sequel/extensions/query.rb +3 -3
- data/lib/sequel/extensions/schema_dumper.rb +7 -8
- data/lib/sequel/extensions/select_remove.rb +1 -1
- data/lib/sequel/model/base.rb +1 -0
- data/lib/sequel/no_core_ext.rb +1 -1
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
- data/lib/sequel/plugins/validation_helpers.rb +31 -0
- data/lib/sequel/sql.rb +64 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +37 -12
- data/spec/adapters/mysql_spec.rb +39 -75
- data/spec/adapters/oracle_spec.rb +11 -11
- data/spec/adapters/postgres_spec.rb +414 -237
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +14 -14
- data/spec/core/database_spec.rb +6 -6
- data/spec/core/dataset_spec.rb +169 -205
- data/spec/core/expression_filters_spec.rb +182 -295
- data/spec/core/object_graph_spec.rb +6 -6
- data/spec/core/schema_spec.rb +14 -14
- data/spec/core/spec_helper.rb +1 -0
- data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
- data/spec/extensions/columns_introspection_spec.rb +5 -5
- data/spec/extensions/hook_class_methods_spec.rb +28 -36
- data/spec/extensions/many_through_many_spec.rb +4 -4
- data/spec/extensions/pg_array_ops_spec.rb +15 -7
- data/spec/extensions/pg_array_spec.rb +81 -48
- data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
- data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
- data/spec/extensions/pg_hstore_spec.rb +66 -65
- data/spec/extensions/pg_inet_spec.rb +2 -4
- data/spec/extensions/pg_interval_spec.rb +2 -3
- data/spec/extensions/pg_json_spec.rb +20 -18
- data/spec/extensions/pg_range_ops_spec.rb +11 -4
- data/spec/extensions/pg_range_spec.rb +30 -7
- data/spec/extensions/pg_row_ops_spec.rb +48 -0
- data/spec/extensions/pg_row_plugin_spec.rb +45 -0
- data/spec/extensions/pg_row_spec.rb +323 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
- data/spec/extensions/query_literals_spec.rb +11 -11
- data/spec/extensions/query_spec.rb +3 -3
- data/spec/extensions/schema_dumper_spec.rb +20 -4
- data/spec/extensions/schema_spec.rb +18 -41
- data/spec/extensions/select_remove_spec.rb +4 -4
- data/spec/extensions/spec_helper.rb +4 -8
- data/spec/extensions/to_dot_spec.rb +5 -5
- data/spec/extensions/validation_class_methods_spec.rb +28 -16
- data/spec/integration/associations_test.rb +20 -20
- data/spec/integration/dataset_test.rb +98 -98
- data/spec/integration/eager_loader_test.rb +13 -27
- data/spec/integration/plugin_test.rb +5 -5
- data/spec/integration/prepared_statement_test.rb +22 -13
- data/spec/integration/schema_test.rb +28 -18
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/timezone_test.rb +2 -2
- data/spec/integration/type_test.rb +15 -6
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +4 -4
- data/spec/model/base_spec.rb +5 -5
- data/spec/model/eager_loading_spec.rb +15 -15
- data/spec/model/model_spec.rb +32 -32
- data/spec/model/record_spec.rb +16 -0
- data/spec/model/spec_helper.rb +2 -6
- data/spec/model/validations_spec.rb +1 -1
- 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
|
data/doc/active_record.rdoc
CHANGED
@@ -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
|
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
|
-
|
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.
|
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
|
-
|
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>
|
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'
|
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(
|
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
|
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
|
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.
|
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.
|
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.
|
509
|
-
Album.
|
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.
|
516
|
-
Album.
|
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.
|
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.
|
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.
|
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.
|
568
|
+
Album.where(:artist_id=>1).order(:name).last
|
555
569
|
# You could also do:
|
556
|
-
Album.
|
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.
|
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.
|
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.
|
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 +
|
615
|
+
For anything more complex, you can use +dataset_module+:
|
602
616
|
|
603
|
-
Album.
|
604
|
-
|
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.
|
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
|
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.
|
661
|
-
Album.
|
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.
|
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
|
-
+
|
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.
|
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.
|
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.
|
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
|
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.
|
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|
|
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.
|
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
|
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>.
|
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.
|
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.
|
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.
|
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
|
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
|
447
|
-
|
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
|
452
|
-
|
453
|
-
asset.save
|
454
|
-
end
|
447
|
+
asset.update(:attachable_id=>nil, :attachable_type=>nil)
|
448
|
+
end
|
455
449
|
def _remove_all_assets
|
456
|
-
|
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
|
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
|
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
|
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
|
-
|
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{
|
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.
|
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.
|
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
|
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.
|
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.
|
607
|
-
|
608
|
-
|
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.
|
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.
|
635
|
-
|
636
|
-
|
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.
|