sequel 3.0.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|