sequel 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +100 -0
- data/README.rdoc +3 -3
- data/bin/sequel +102 -19
- data/doc/reflection.rdoc +83 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/lib/sequel/adapters/ado.rb +11 -0
- data/lib/sequel/adapters/amalgalite.rb +5 -20
- data/lib/sequel/adapters/do.rb +44 -36
- data/lib/sequel/adapters/firebird.rb +29 -43
- data/lib/sequel/adapters/jdbc.rb +17 -27
- data/lib/sequel/adapters/mysql.rb +35 -40
- data/lib/sequel/adapters/odbc.rb +4 -23
- data/lib/sequel/adapters/oracle.rb +22 -19
- data/lib/sequel/adapters/postgres.rb +6 -15
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/adapters/shared/mysql.rb +29 -10
- data/lib/sequel/adapters/shared/oracle.rb +6 -8
- data/lib/sequel/adapters/shared/postgres.rb +28 -72
- data/lib/sequel/adapters/shared/sqlite.rb +5 -3
- data/lib/sequel/adapters/sqlite.rb +5 -20
- data/lib/sequel/adapters/utils/savepoint_transactions.rb +80 -0
- data/lib/sequel/adapters/utils/unsupported.rb +0 -12
- data/lib/sequel/core.rb +12 -3
- data/lib/sequel/core_sql.rb +1 -8
- data/lib/sequel/database.rb +107 -43
- data/lib/sequel/database/schema_generator.rb +1 -0
- data/lib/sequel/database/schema_methods.rb +38 -4
- data/lib/sequel/dataset.rb +6 -0
- data/lib/sequel/dataset/convenience.rb +2 -2
- data/lib/sequel/dataset/graph.rb +2 -2
- data/lib/sequel/dataset/prepared_statements.rb +3 -8
- data/lib/sequel/dataset/sql.rb +93 -19
- data/lib/sequel/extensions/blank.rb +2 -1
- data/lib/sequel/extensions/inflector.rb +4 -3
- data/lib/sequel/extensions/migration.rb +13 -2
- data/lib/sequel/extensions/pagination.rb +4 -0
- data/lib/sequel/extensions/pretty_table.rb +4 -0
- data/lib/sequel/extensions/query.rb +4 -0
- data/lib/sequel/extensions/schema_dumper.rb +100 -24
- data/lib/sequel/extensions/string_date_time.rb +3 -4
- data/lib/sequel/model.rb +2 -1
- data/lib/sequel/model/associations.rb +96 -38
- data/lib/sequel/model/base.rb +14 -14
- data/lib/sequel/model/plugins.rb +32 -21
- data/lib/sequel/plugins/caching.rb +13 -15
- data/lib/sequel/plugins/identity_map.rb +107 -0
- data/lib/sequel/plugins/lazy_attributes.rb +65 -0
- data/lib/sequel/plugins/many_through_many.rb +188 -0
- data/lib/sequel/plugins/schema.rb +13 -0
- data/lib/sequel/plugins/serialization.rb +53 -37
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/validation_class_methods.rb +28 -7
- data/lib/sequel/plugins/validation_helpers.rb +31 -24
- data/lib/sequel/sql.rb +16 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/ado_spec.rb +47 -1
- data/spec/adapters/firebird_spec.rb +39 -36
- data/spec/adapters/mysql_spec.rb +25 -9
- data/spec/adapters/postgres_spec.rb +11 -24
- data/spec/core/database_spec.rb +54 -13
- data/spec/core/dataset_spec.rb +147 -29
- data/spec/core/object_graph_spec.rb +6 -1
- data/spec/core/schema_spec.rb +34 -0
- data/spec/core/spec_helper.rb +0 -2
- data/spec/extensions/caching_spec.rb +7 -0
- data/spec/extensions/identity_map_spec.rb +158 -0
- data/spec/extensions/lazy_attributes_spec.rb +113 -0
- data/spec/extensions/many_through_many_spec.rb +813 -0
- data/spec/extensions/migration_spec.rb +4 -4
- data/spec/extensions/schema_dumper_spec.rb +114 -13
- data/spec/extensions/schema_spec.rb +19 -3
- data/spec/extensions/serialization_spec.rb +28 -0
- data/spec/extensions/single_table_inheritance_spec.rb +25 -1
- data/spec/extensions/spec_helper.rb +2 -7
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/validation_class_methods_spec.rb +10 -5
- data/spec/integration/dataset_test.rb +39 -6
- data/spec/integration/eager_loader_test.rb +7 -7
- data/spec/integration/spec_helper.rb +0 -1
- data/spec/integration/transaction_test.rb +28 -1
- data/spec/model/association_reflection_spec.rb +29 -3
- data/spec/model/associations_spec.rb +1 -0
- data/spec/model/eager_loading_spec.rb +70 -1
- data/spec/model/plugins_spec.rb +236 -50
- data/spec/model/spec_helper.rb +0 -2
- metadata +18 -5
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
|
2
2
|
|
3
3
|
describe "Eagerly loading a tree structure" do
|
4
4
|
before do
|
5
|
-
INTEGRATION_DB.instance_variable_set(:@schemas,
|
5
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, {})
|
6
6
|
INTEGRATION_DB.create_table!(:nodes) do
|
7
7
|
primary_key :id
|
8
8
|
foreign_key :parent_id, :nodes
|
@@ -144,7 +144,7 @@ describe "Association Extensions" do
|
|
144
144
|
first(:name=>name) || model.create(:name=>name, :author_id=>model_object.pk)
|
145
145
|
end
|
146
146
|
end
|
147
|
-
INTEGRATION_DB.instance_variable_set(:@schemas,
|
147
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, {})
|
148
148
|
INTEGRATION_DB.create_table!(:authors) do
|
149
149
|
primary_key :id
|
150
150
|
end
|
@@ -201,7 +201,7 @@ end
|
|
201
201
|
|
202
202
|
describe "has_many :through has_many and has_one :through belongs_to" do
|
203
203
|
before do
|
204
|
-
INTEGRATION_DB.instance_variable_set(:@schemas,
|
204
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, {})
|
205
205
|
INTEGRATION_DB.create_table!(:firms) do
|
206
206
|
primary_key :id
|
207
207
|
end
|
@@ -361,7 +361,7 @@ end
|
|
361
361
|
|
362
362
|
describe "Polymorphic Associations" do
|
363
363
|
before do
|
364
|
-
INTEGRATION_DB.instance_variable_set(:@schemas,
|
364
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, {})
|
365
365
|
INTEGRATION_DB.create_table!(:assets) do
|
366
366
|
primary_key :id
|
367
367
|
Integer :attachable_id
|
@@ -402,7 +402,7 @@ describe "Polymorphic Associations" do
|
|
402
402
|
primary_key :id
|
403
403
|
end
|
404
404
|
class ::Post < Sequel::Model
|
405
|
-
one_to_many :assets, :key=>:attachable_id do |ds|
|
405
|
+
one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable do |ds|
|
406
406
|
ds.filter(:attachable_type=>'Post')
|
407
407
|
end
|
408
408
|
|
@@ -428,7 +428,7 @@ describe "Polymorphic Associations" do
|
|
428
428
|
primary_key :id
|
429
429
|
end
|
430
430
|
class ::Note < Sequel::Model
|
431
|
-
one_to_many :assets, :key=>:attachable_id do |ds|
|
431
|
+
one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable do |ds|
|
432
432
|
ds.filter(:attachable_type=>'Note')
|
433
433
|
end
|
434
434
|
|
@@ -533,7 +533,7 @@ end
|
|
533
533
|
|
534
534
|
describe "many_to_one/one_to_many not referencing primary key" do
|
535
535
|
before do
|
536
|
-
INTEGRATION_DB.instance_variable_set(:@schemas,
|
536
|
+
INTEGRATION_DB.instance_variable_set(:@schemas, {})
|
537
537
|
INTEGRATION_DB.create_table!(:clients) do
|
538
538
|
primary_key :id
|
539
539
|
String :name
|
@@ -3,7 +3,7 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
|
3
3
|
describe "Database transactions" do
|
4
4
|
before do
|
5
5
|
INTEGRATION_DB.drop_table(:items) if INTEGRATION_DB.table_exists?(:items)
|
6
|
-
INTEGRATION_DB.create_table(:items){String :name; Integer :value}
|
6
|
+
INTEGRATION_DB.create_table(:items, :engine=>'InnoDB'){String :name; Integer :value}
|
7
7
|
@d = INTEGRATION_DB[:items]
|
8
8
|
clear_sqls
|
9
9
|
end
|
@@ -69,6 +69,33 @@ describe "Database transactions" do
|
|
69
69
|
end}.should raise_error(Interrupt)
|
70
70
|
@d.count.should == 0
|
71
71
|
end
|
72
|
+
|
73
|
+
if INTEGRATION_DB.supports_savepoints?
|
74
|
+
specify "should support nested transactions through savepoints using the savepoint option" do
|
75
|
+
@db = INTEGRATION_DB
|
76
|
+
@db.transaction do
|
77
|
+
@d << {:name => '1'}
|
78
|
+
@db.transaction(:savepoint=>true) do
|
79
|
+
@d << {:name => '2'}
|
80
|
+
@db.transaction do
|
81
|
+
@d << {:name => '3'}
|
82
|
+
raise Sequel::Rollback
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@d << {:name => '4'}
|
86
|
+
@db.transaction do
|
87
|
+
@d << {:name => '6'}
|
88
|
+
@db.transaction(:savepoint=>true) do
|
89
|
+
@d << {:name => '7'}
|
90
|
+
raise Sequel::Rollback
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@d << {:name => '5'}
|
94
|
+
end
|
95
|
+
|
96
|
+
@d.order(:name).map(:name).should == %w{1 4 5 6}
|
97
|
+
end
|
98
|
+
end
|
72
99
|
|
73
100
|
specify "should handle returning inside of the block by committing" do
|
74
101
|
def INTEGRATION_DB.ret_commit
|
@@ -37,6 +37,17 @@ describe Sequel::Model::Associations::AssociationReflection, "#primary_key" do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
describe Sequel::Model::Associations::AssociationReflection, "#reciprocal" do
|
40
|
+
before do
|
41
|
+
class ::ParParent < Sequel::Model; end
|
42
|
+
class ::ParParentTwo < Sequel::Model; end
|
43
|
+
class ::ParParentThree < Sequel::Model; end
|
44
|
+
end
|
45
|
+
after do
|
46
|
+
Object.send(:remove_const, :ParParent)
|
47
|
+
Object.send(:remove_const, :ParParentTwo)
|
48
|
+
Object.send(:remove_const, :ParParentThree)
|
49
|
+
end
|
50
|
+
|
40
51
|
it "should use the :reciprocal value if present" do
|
41
52
|
@c = Class.new(Sequel::Model)
|
42
53
|
@d = Class.new(Sequel::Model)
|
@@ -45,10 +56,25 @@ describe Sequel::Model::Associations::AssociationReflection, "#reciprocal" do
|
|
45
56
|
@c.association_reflection(:c).reciprocal.should == :xx
|
46
57
|
end
|
47
58
|
|
59
|
+
it "should require the associated class is the current class to be a reciprocal" do
|
60
|
+
ParParent.many_to_one :par_parent_two, :key=>:blah
|
61
|
+
ParParent.many_to_one :par_parent_three, :key=>:blah
|
62
|
+
ParParentTwo.one_to_many :par_parents, :key=>:blah
|
63
|
+
ParParentThree.one_to_many :par_parents, :key=>:blah
|
64
|
+
|
65
|
+
ParParentTwo.association_reflection(:par_parents).reciprocal.should == :par_parent_two
|
66
|
+
ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_three
|
67
|
+
|
68
|
+
ParParent.many_to_many :par_parent_twos, :left_key=>:l, :right_key=>:r, :join_table=>:jt
|
69
|
+
ParParent.many_to_many :par_parent_threes, :left_key=>:l, :right_key=>:r, :join_table=>:jt
|
70
|
+
ParParentTwo.many_to_many :par_parents, :right_key=>:l, :left_key=>:r, :join_table=>:jt
|
71
|
+
ParParentThree.many_to_many :par_parents, :right_key=>:l, :left_key=>:r, :join_table=>:jt
|
72
|
+
|
73
|
+
ParParentTwo.association_reflection(:par_parents).reciprocal.should == :par_parent_twos
|
74
|
+
ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_threes
|
75
|
+
end
|
76
|
+
|
48
77
|
it "should figure out the reciprocal if the :reciprocal value is not present" do
|
49
|
-
class ::ParParent < Sequel::Model; end
|
50
|
-
class ::ParParentTwo < Sequel::Model; end
|
51
|
-
class ::ParParentThree < Sequel::Model; end
|
52
78
|
ParParent.many_to_one :par_parent_two
|
53
79
|
ParParentTwo.one_to_many :par_parents
|
54
80
|
ParParent.many_to_many :par_parent_threes
|
@@ -1225,6 +1225,7 @@ describe Sequel::Model, "many_to_many" do
|
|
1225
1225
|
a = n.attributes_dataset
|
1226
1226
|
a.should be_a_kind_of(Sequel::Dataset)
|
1227
1227
|
a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) WHERE (a = 32)'
|
1228
|
+
n.attributes.should == [@c1.load({})]
|
1228
1229
|
end
|
1229
1230
|
|
1230
1231
|
it "should support an order option" do
|
@@ -22,6 +22,7 @@ describe Sequel::Model, "#eager" do
|
|
22
22
|
one_to_many :albums, :class=>'EagerAlbum', :key=>:band_id, :eager=>:tracks
|
23
23
|
one_to_many :graph_albums, :class=>'EagerAlbum', :key=>:band_id, :eager_graph=>:tracks
|
24
24
|
many_to_many :members, :class=>'EagerBandMember', :left_key=>:band_id, :right_key=>:member_id, :join_table=>:bm
|
25
|
+
many_to_many :graph_members, :clone=>:members, :eager_graph=>:bands
|
25
26
|
one_to_many :good_albums, :class=>'EagerAlbum', :key=>:band_id, :eager_block=>proc{|ds| ds.filter(:name=>'good')} do |ds|
|
26
27
|
ds.filter(:name=>'Good')
|
27
28
|
end
|
@@ -299,6 +300,27 @@ describe Sequel::Model, "#eager" do
|
|
299
300
|
MODEL_DB.sqls.length.should == 2
|
300
301
|
end
|
301
302
|
|
303
|
+
it "should cascade eagerly loading when the :eager_graph association option is used with a many_to_many association" do
|
304
|
+
EagerBandMember.dataset.extend(Module.new {
|
305
|
+
def columns
|
306
|
+
[:id]
|
307
|
+
end
|
308
|
+
def fetch_rows(sql)
|
309
|
+
@db << sql
|
310
|
+
yield({:id=>5, :bands_id=>2, :p_k=>6, :x_foreign_key_x=>2})
|
311
|
+
yield({:id=>5, :bands_id=>3, :p_k=>6, :x_foreign_key_x=>2})
|
312
|
+
end
|
313
|
+
})
|
314
|
+
a = EagerBand.eager(:graph_members).all
|
315
|
+
a.should == [EagerBand.load(:id=>2)]
|
316
|
+
MODEL_DB.sqls.should == ['SELECT * FROM bands',
|
317
|
+
'SELECT members.id, bands.id AS bands_id, bands.p_k, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2))) LEFT OUTER JOIN bm AS bm_0 ON (bm_0.member_id = members.id) LEFT OUTER JOIN bands ON (bands.id = bm_0.band_id) ORDER BY bands.id']
|
318
|
+
a = a.first
|
319
|
+
a.graph_members.should == [EagerBandMember.load(:id=>5)]
|
320
|
+
a.graph_members.first.bands.should == [EagerBand.load(:id=>2, :p_k=>6), EagerBand.load(:id=>3, :p_k=>6)]
|
321
|
+
MODEL_DB.sqls.length.should == 2
|
322
|
+
end
|
323
|
+
|
302
324
|
it "should respect :eager_graph when lazily loading an association" do
|
303
325
|
a = EagerBand.all
|
304
326
|
a.should be_a_kind_of(Array)
|
@@ -328,6 +350,24 @@ describe Sequel::Model, "#eager" do
|
|
328
350
|
MODEL_DB.sqls.length.should == 2
|
329
351
|
end
|
330
352
|
|
353
|
+
it "should respect :eager_graph when lazily loading a many_to_many association" do
|
354
|
+
EagerBandMember.dataset.extend(Module.new {
|
355
|
+
def columns
|
356
|
+
[:id]
|
357
|
+
end
|
358
|
+
def fetch_rows(sql)
|
359
|
+
@db << sql
|
360
|
+
yield({:id=>5, :bands_id=>2, :p_k=>6})
|
361
|
+
yield({:id=>5, :bands_id=>3, :p_k=>6})
|
362
|
+
end
|
363
|
+
})
|
364
|
+
a = EagerBand.load(:id=>2)
|
365
|
+
a.graph_members.should == [EagerBandMember.load(:id=>5)]
|
366
|
+
MODEL_DB.sqls.should == ['SELECT members.id, bands.id AS bands_id, bands.p_k FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id = 2)) LEFT OUTER JOIN bm AS bm_0 ON (bm_0.member_id = members.id) LEFT OUTER JOIN bands ON (bands.id = bm_0.band_id) ORDER BY bands.id']
|
367
|
+
a.graph_members.first.bands.should == [EagerBand.load(:id=>2, :p_k=>6), EagerBand.load(:id=>3, :p_k=>6)]
|
368
|
+
MODEL_DB.sqls.length.should == 1
|
369
|
+
end
|
370
|
+
|
331
371
|
it "should respect :conditions when eagerly loading" do
|
332
372
|
EagerBandMember.many_to_many :good_bands, :clone=>:bands, :conditions=>{:a=>32}
|
333
373
|
a = EagerBandMember.eager(:good_bands).all
|
@@ -816,6 +856,20 @@ describe Sequel::Model, "#eager_graph" do
|
|
816
856
|
a.members.last.values.should == {:id => 5}
|
817
857
|
end
|
818
858
|
|
859
|
+
it "should respect the :cartesian_product_number option" do
|
860
|
+
GraphBand.many_to_one :other_vocalist, :class=>'GraphBandMember', :key=>:vocalist_id, :cartesian_product_number=>1
|
861
|
+
ds = GraphBand.eager_graph(:members, :other_vocalist)
|
862
|
+
ds.sql.should == 'SELECT bands.id, bands.vocalist_id, members.id AS members_id, other_vocalist.id AS other_vocalist_id FROM bands LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id) LEFT OUTER JOIN members AS other_vocalist ON (other_vocalist.id = bands.vocalist_id)'
|
863
|
+
def ds.fetch_rows(sql, &block)
|
864
|
+
yield({:id=>2, :vocalist_id=>6, :members_id=>5, :other_vocalist_id=>6})
|
865
|
+
yield({:id=>2, :vocalist_id=>6, :members_id=>5, :other_vocalist_id=>6})
|
866
|
+
end
|
867
|
+
a = ds.all
|
868
|
+
a.should == [GraphBand.load(:id=>2, :vocalist_id => 6)]
|
869
|
+
a.first.other_vocalist.should == GraphBandMember.load(:id=>6)
|
870
|
+
a.first.members.should == [GraphBandMember.load(:id=>5)]
|
871
|
+
end
|
872
|
+
|
819
873
|
it "should drop duplicate items that occur in sequence if the graph could be a cartesian product" do
|
820
874
|
ds = GraphBand.eager_graph(:members, :genres)
|
821
875
|
ds.sql.should == 'SELECT bands.id, bands.vocalist_id, members.id AS members_id, genres.id AS genres_id FROM bands LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id) LEFT OUTER JOIN bg ON (bg.band_id = bands.id) LEFT OUTER JOIN genres ON (genres.id = bg.genre_id)'
|
@@ -1032,7 +1086,7 @@ describe Sequel::Model, "#eager_graph" do
|
|
1032
1086
|
GraphAlbum.eager_graph(:inner_genres).sql.should == 'SELECT albums.id, albums.band_id, inner_genres.id AS inner_genres_id FROM albums INNER JOIN ag ON (ag.album_id = albums.id) INNER JOIN genres AS inner_genres ON (inner_genres.id = ag.genre_id)'
|
1033
1087
|
end
|
1034
1088
|
|
1035
|
-
it "should respect the association's :
|
1089
|
+
it "should respect the association's :graph_join_table_join_type option" do
|
1036
1090
|
GraphAlbum.many_to_many :inner_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :graph_join_table_join_type=>:inner
|
1037
1091
|
GraphAlbum.eager_graph(:inner_genres).sql.should == 'SELECT albums.id, albums.band_id, inner_genres.id AS inner_genres_id FROM albums INNER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS inner_genres ON (inner_genres.id = ag.genre_id)'
|
1038
1092
|
|
@@ -1162,4 +1216,19 @@ describe Sequel::Model, "#eager_graph" do
|
|
1162
1216
|
GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :graph_conditions=>{:a=>:b}
|
1163
1217
|
GraphAlbum.eager_graph(:a_genres, :b_tracks).sql.should == 'SELECT albums.id, albums.band_id, a_genres.id AS a_genres_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS a_genres ON (a_genres.id = ag.genre_id) LEFT OUTER JOIN tracks AS b_tracks ON ((b_tracks.album_id = albums.id) AND (b_tracks.a = albums.b))'
|
1164
1218
|
end
|
1219
|
+
|
1220
|
+
it "should eagerly load associated records for classes that do not have a primary key" do
|
1221
|
+
GraphAlbum.no_primary_key
|
1222
|
+
GraphGenre.no_primary_key
|
1223
|
+
GraphAlbum.many_to_many :inner_genres, :class=>'GraphGenre', :left_key=>:album_id, :left_primary_key=>:band_id, :right_key=>:genre_id, :right_primary_key=>:xxx, :join_table=>:ag
|
1224
|
+
ds = GraphAlbum.eager_graph(:inner_genres)
|
1225
|
+
ds.sql.should == 'SELECT albums.id, albums.band_id, inner_genres.id AS inner_genres_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.band_id) LEFT OUTER JOIN genres AS inner_genres ON (inner_genres.xxx = ag.genre_id)'
|
1226
|
+
def ds.fetch_rows(sql, &block)
|
1227
|
+
yield({:id=>3, :band_id=>2, :inner_genres_id=>5, :xxx=>12})
|
1228
|
+
yield({:id=>3, :band_id=>2, :inner_genres_id=>6, :xxx=>22})
|
1229
|
+
end
|
1230
|
+
as = ds.all
|
1231
|
+
as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
|
1232
|
+
as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
|
1233
|
+
end
|
1165
1234
|
end
|
data/spec/model/plugins_spec.rb
CHANGED
@@ -1,72 +1,258 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
2
|
|
3
|
-
|
3
|
+
describe Sequel::Model, ".plugin" do
|
4
|
+
before do
|
5
|
+
module Sequel::Plugins
|
6
|
+
module Timestamped
|
7
|
+
module InstanceMethods
|
8
|
+
def get_stamp(*args); @values[:stamp] end
|
9
|
+
def abc; 123; end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def def; 234; end
|
14
|
+
end
|
4
15
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
module InstanceMethods
|
12
|
-
def get_stamp(*args); @values[:stamp] end
|
13
|
-
def abc; timestamped_opts; end
|
14
|
-
end
|
15
|
-
|
16
|
-
module ClassMethods
|
17
|
-
def deff; timestamped_opts; end
|
18
|
-
end
|
19
|
-
|
20
|
-
module DatasetMethods
|
21
|
-
def ghi; timestamped_opts; end
|
16
|
+
module DatasetMethods
|
17
|
+
def ghi; 345; end
|
18
|
+
end
|
19
|
+
end
|
22
20
|
end
|
21
|
+
@c = Class.new(Sequel::Model(:items))
|
22
|
+
@t = Sequel::Plugins::Timestamped
|
23
|
+
end
|
24
|
+
after do
|
25
|
+
Sequel::Plugins.send(:remove_const, :Timestamped)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should raise LoadError if the plugin is not found" do
|
29
|
+
proc{@c.plugin :something_or_other}.should raise_error(LoadError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should store the plugin in .plugins" do
|
33
|
+
@c.plugins.should_not include(@t)
|
34
|
+
@c.plugin @t
|
35
|
+
@c.plugins.should include(@t)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should be inherited in subclasses" do
|
39
|
+
@c.plugins.should_not include(@t)
|
40
|
+
c1 = Class.new(@c)
|
41
|
+
@c.plugin @t
|
42
|
+
c2 = Class.new(@c)
|
43
|
+
@c.plugins.should include(@t)
|
44
|
+
c1.plugins.should_not include(@t)
|
45
|
+
c2.plugins.should include(@t)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should accept a symbol and load the module from the Sequel::Plugins namespace" do
|
49
|
+
@c.plugin :timestamped
|
50
|
+
@c.plugins.should include(@t)
|
23
51
|
end
|
24
52
|
|
25
|
-
|
53
|
+
it "should accept a module" do
|
54
|
+
m = Module.new
|
55
|
+
@c.plugin m
|
56
|
+
@c.plugins.should include(m)
|
57
|
+
end
|
26
58
|
|
27
|
-
|
59
|
+
it "should not attempt to load a plugin twice" do
|
60
|
+
@c.plugins.should_not include(@t)
|
61
|
+
@c.plugin @t
|
62
|
+
@c.plugins.reject{|m| m != @t}.length.should == 1
|
63
|
+
@c.plugin @t
|
64
|
+
@c.plugins.reject{|m| m != @t}.length.should == 1
|
65
|
+
end
|
28
66
|
|
29
|
-
it "should
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
67
|
+
it "should call apply and configure if the plugin responds to it, with the args and block used" do
|
68
|
+
m = Module.new do
|
69
|
+
def self.args; @args; end
|
70
|
+
def self.block; @block; end
|
71
|
+
def self.block_call; @block.call; end
|
72
|
+
def self.args2; @args2; end
|
73
|
+
def self.block2; @block2; end
|
74
|
+
def self.block2_call; @block2.call; end
|
75
|
+
def self.apply(model, *args, &block)
|
76
|
+
@args = args
|
77
|
+
@block = block
|
78
|
+
model.send(:define_method, :blah){43}
|
79
|
+
end
|
80
|
+
def self.configure(model, *args, &block)
|
81
|
+
@args2 = args
|
82
|
+
@block2 = block
|
83
|
+
model.send(:define_method, :blag){44}
|
34
84
|
end
|
35
|
-
end
|
85
|
+
end
|
86
|
+
b = lambda{42}
|
87
|
+
@c.plugin(m, 123, 1=>2, &b)
|
88
|
+
|
89
|
+
m.args.should == [123, {1=>2}]
|
90
|
+
m.block.should == b
|
91
|
+
m.block_call.should == 42
|
92
|
+
@c.new.blah.should == 43
|
93
|
+
|
94
|
+
m.args2.should == [123, {1=>2}]
|
95
|
+
m.block2.should == b
|
96
|
+
m.block2_call.should == 42
|
97
|
+
@c.new.blag.should == 44
|
36
98
|
end
|
37
99
|
|
38
|
-
it "should
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
plugin :timestamped, :a => 1, :b => 2
|
100
|
+
it "should call configure even if the plugin has already been loaded" do
|
101
|
+
m = Module.new do
|
102
|
+
@args = []
|
103
|
+
def self.args; @args; end
|
104
|
+
def self.configure(model, *args, &block)
|
105
|
+
@args << [block, *args]
|
45
106
|
end
|
46
|
-
end
|
107
|
+
end
|
47
108
|
|
48
|
-
|
49
|
-
c.
|
109
|
+
b = lambda{42}
|
110
|
+
@c.plugin(m, 123, 1=>2, &b)
|
111
|
+
m.args.should == [[b, 123, {1=>2}]]
|
112
|
+
|
113
|
+
b2 = lambda{44}
|
114
|
+
@c.plugin(m, 234, 2=>3, &b2)
|
115
|
+
m.args.should == [[b, 123, {1=>2}], [b2, 234, {2=>3}]]
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should call things in the following order: apply, InstanceMethods, ClassMethods, DatasetMethods, configure" do
|
119
|
+
m = Module.new do
|
120
|
+
@args = []
|
121
|
+
def self.args; @args; end
|
122
|
+
def self.apply(model, *args, &block)
|
123
|
+
@args << :apply
|
124
|
+
end
|
125
|
+
def self.configure(model, *args, &block)
|
126
|
+
@args << :configure
|
127
|
+
end
|
128
|
+
im = Module.new do
|
129
|
+
def self.included(model)
|
130
|
+
model.plugins.last.args << :im
|
131
|
+
end
|
132
|
+
end
|
133
|
+
cm = Module.new do
|
134
|
+
def self.extended(model)
|
135
|
+
model.plugins.last.args << :cm
|
136
|
+
end
|
137
|
+
end
|
138
|
+
dm = Module.new do
|
139
|
+
def self.extended(dataset)
|
140
|
+
dataset.model.plugins.last.args << :dm
|
141
|
+
end
|
142
|
+
end
|
143
|
+
const_set(:InstanceMethods, im)
|
144
|
+
const_set(:ClassMethods, cm)
|
145
|
+
const_set(:DatasetMethods, dm)
|
146
|
+
end
|
50
147
|
|
51
|
-
|
52
|
-
m
|
148
|
+
b = lambda{44}
|
149
|
+
@c.plugin(m, 123, 1=>2, &b)
|
150
|
+
m.args.should == [:apply, :im, :cm, :dm, :configure]
|
151
|
+
@c.plugin(m, 234, 2=>3, &b)
|
152
|
+
m.args.should == [:apply, :im, :cm, :dm, :configure, :configure]
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should include an InstanceMethods module in the class if the plugin includes it" do
|
156
|
+
@c.plugin @t
|
157
|
+
m = @c.new
|
53
158
|
m.should respond_to(:get_stamp)
|
54
159
|
m.should respond_to(:abc)
|
55
|
-
m.abc.should ==
|
160
|
+
m.abc.should == 123
|
56
161
|
t = Time.now
|
57
162
|
m[:stamp] = t
|
58
163
|
m.get_stamp.should == t
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
c.
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should define a plugin_opts instance method if the plugin has an InstanceMethods module" do
|
167
|
+
@c.plugin :timestamped, 1, 2=>3
|
168
|
+
@c.new.timestamped_opts.should == [1, {2=>3}]
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should extend the class with a ClassMethods module if the plugin includes it" do
|
172
|
+
@c.plugin @t
|
173
|
+
@c.def.should == 234
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should define a plugin_opts class method if the plugin has a ClassMethods module" do
|
177
|
+
@c.plugin :timestamped, 1, 2=>3
|
178
|
+
@c.timestamped_opts.should == [1, {2=>3}]
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should extend the class's dataset with a DatasetMethods module if the plugin includes it" do
|
182
|
+
@c.plugin @t
|
183
|
+
@c.dataset.ghi.should == 345
|
184
|
+
@c.ghi.should == 345
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should define a plugin_opts dataset method if the plugin has a DatasetMethods module" do
|
188
|
+
@c.plugin :timestamped, 1, 2=>3
|
189
|
+
@c.dataset.timestamped_opts.should == [1, {2=>3}]
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should use a single arg for the plugin_opts method if only a single arg was given" do
|
193
|
+
@c.plugin :timestamped, 1
|
194
|
+
@c.new.timestamped_opts.should == 1
|
195
|
+
@c.timestamped_opts.should == 1
|
196
|
+
@c.dataset.timestamped_opts.should == 1
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should save the DatasetMethods module and apply it later if the class doesn't have a dataset" do
|
200
|
+
c = Class.new(Sequel::Model)
|
201
|
+
c.plugin @t
|
202
|
+
proc{c.ghi}.should raise_error(Sequel::Error)
|
203
|
+
c.dataset = MODEL_DB[:i]
|
204
|
+
c.dataset.ghi.should == 345
|
205
|
+
c.ghi.should == 345
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should save the DatasetMethods module and apply it later if the class has a dataset" do
|
209
|
+
@c.plugin @t
|
210
|
+
@c.dataset = MODEL_DB[:i]
|
211
|
+
@c.dataset.ghi.should == 345
|
212
|
+
@c.ghi.should == 345
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should define class methods for all public instance methods in DatasetMethod" do
|
216
|
+
m = Module.new do
|
217
|
+
dm = Module.new do
|
218
|
+
def a; 1; end
|
219
|
+
def b; 2; end
|
220
|
+
end
|
221
|
+
const_set(:DatasetMethods, dm)
|
222
|
+
end
|
223
|
+
@c.plugin m
|
224
|
+
@c.dataset.a.should == 1
|
225
|
+
@c.dataset.b.should == 2
|
226
|
+
@c.a.should == 1
|
227
|
+
@c.b.should == 2
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should define class methods for all public instance methods in DatasetMethod" do
|
231
|
+
m = Module.new do
|
232
|
+
dm = Module.new do
|
233
|
+
def b; 2; end
|
234
|
+
private
|
235
|
+
def a; 1; end
|
236
|
+
end
|
237
|
+
const_set(:DatasetMethods, dm)
|
238
|
+
end
|
239
|
+
@c.plugin m
|
240
|
+
@c.dataset.b.should == 2
|
241
|
+
lambda{@c.dataset.a}.should raise_error(NoMethodError)
|
242
|
+
@c.dataset.send(:a).should == 1
|
243
|
+
@c.b.should == 2
|
244
|
+
lambda{@c.a}.should raise_error(NoMethodError)
|
245
|
+
lambda{@c.send(:a)}.should raise_error(NoMethodError)
|
246
|
+
end
|
67
247
|
|
68
|
-
|
69
|
-
|
70
|
-
|
248
|
+
it "should not raise an error if the DatasetMethod module has no public instance methods" do
|
249
|
+
m = Module.new do
|
250
|
+
dm = Module.new do
|
251
|
+
private
|
252
|
+
def a; 1; end
|
253
|
+
end
|
254
|
+
const_set(:DatasetMethods, dm)
|
255
|
+
end
|
256
|
+
lambda{@c.plugin m}.should_not raise_error
|
71
257
|
end
|
72
258
|
end
|