sequel 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG +100 -0
  2. data/README.rdoc +3 -3
  3. data/bin/sequel +102 -19
  4. data/doc/reflection.rdoc +83 -0
  5. data/doc/release_notes/3.1.0.txt +406 -0
  6. data/lib/sequel/adapters/ado.rb +11 -0
  7. data/lib/sequel/adapters/amalgalite.rb +5 -20
  8. data/lib/sequel/adapters/do.rb +44 -36
  9. data/lib/sequel/adapters/firebird.rb +29 -43
  10. data/lib/sequel/adapters/jdbc.rb +17 -27
  11. data/lib/sequel/adapters/mysql.rb +35 -40
  12. data/lib/sequel/adapters/odbc.rb +4 -23
  13. data/lib/sequel/adapters/oracle.rb +22 -19
  14. data/lib/sequel/adapters/postgres.rb +6 -15
  15. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  16. data/lib/sequel/adapters/shared/mysql.rb +29 -10
  17. data/lib/sequel/adapters/shared/oracle.rb +6 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +28 -72
  19. data/lib/sequel/adapters/shared/sqlite.rb +5 -3
  20. data/lib/sequel/adapters/sqlite.rb +5 -20
  21. data/lib/sequel/adapters/utils/savepoint_transactions.rb +80 -0
  22. data/lib/sequel/adapters/utils/unsupported.rb +0 -12
  23. data/lib/sequel/core.rb +12 -3
  24. data/lib/sequel/core_sql.rb +1 -8
  25. data/lib/sequel/database.rb +107 -43
  26. data/lib/sequel/database/schema_generator.rb +1 -0
  27. data/lib/sequel/database/schema_methods.rb +38 -4
  28. data/lib/sequel/dataset.rb +6 -0
  29. data/lib/sequel/dataset/convenience.rb +2 -2
  30. data/lib/sequel/dataset/graph.rb +2 -2
  31. data/lib/sequel/dataset/prepared_statements.rb +3 -8
  32. data/lib/sequel/dataset/sql.rb +93 -19
  33. data/lib/sequel/extensions/blank.rb +2 -1
  34. data/lib/sequel/extensions/inflector.rb +4 -3
  35. data/lib/sequel/extensions/migration.rb +13 -2
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pretty_table.rb +4 -0
  38. data/lib/sequel/extensions/query.rb +4 -0
  39. data/lib/sequel/extensions/schema_dumper.rb +100 -24
  40. data/lib/sequel/extensions/string_date_time.rb +3 -4
  41. data/lib/sequel/model.rb +2 -1
  42. data/lib/sequel/model/associations.rb +96 -38
  43. data/lib/sequel/model/base.rb +14 -14
  44. data/lib/sequel/model/plugins.rb +32 -21
  45. data/lib/sequel/plugins/caching.rb +13 -15
  46. data/lib/sequel/plugins/identity_map.rb +107 -0
  47. data/lib/sequel/plugins/lazy_attributes.rb +65 -0
  48. data/lib/sequel/plugins/many_through_many.rb +188 -0
  49. data/lib/sequel/plugins/schema.rb +13 -0
  50. data/lib/sequel/plugins/serialization.rb +53 -37
  51. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  52. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  53. data/lib/sequel/plugins/validation_class_methods.rb +28 -7
  54. data/lib/sequel/plugins/validation_helpers.rb +31 -24
  55. data/lib/sequel/sql.rb +16 -0
  56. data/lib/sequel/version.rb +1 -1
  57. data/spec/adapters/ado_spec.rb +47 -1
  58. data/spec/adapters/firebird_spec.rb +39 -36
  59. data/spec/adapters/mysql_spec.rb +25 -9
  60. data/spec/adapters/postgres_spec.rb +11 -24
  61. data/spec/core/database_spec.rb +54 -13
  62. data/spec/core/dataset_spec.rb +147 -29
  63. data/spec/core/object_graph_spec.rb +6 -1
  64. data/spec/core/schema_spec.rb +34 -0
  65. data/spec/core/spec_helper.rb +0 -2
  66. data/spec/extensions/caching_spec.rb +7 -0
  67. data/spec/extensions/identity_map_spec.rb +158 -0
  68. data/spec/extensions/lazy_attributes_spec.rb +113 -0
  69. data/spec/extensions/many_through_many_spec.rb +813 -0
  70. data/spec/extensions/migration_spec.rb +4 -4
  71. data/spec/extensions/schema_dumper_spec.rb +114 -13
  72. data/spec/extensions/schema_spec.rb +19 -3
  73. data/spec/extensions/serialization_spec.rb +28 -0
  74. data/spec/extensions/single_table_inheritance_spec.rb +25 -1
  75. data/spec/extensions/spec_helper.rb +2 -7
  76. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  77. data/spec/extensions/validation_class_methods_spec.rb +10 -5
  78. data/spec/integration/dataset_test.rb +39 -6
  79. data/spec/integration/eager_loader_test.rb +7 -7
  80. data/spec/integration/spec_helper.rb +0 -1
  81. data/spec/integration/transaction_test.rb +28 -1
  82. data/spec/model/association_reflection_spec.rb +29 -3
  83. data/spec/model/associations_spec.rb +1 -0
  84. data/spec/model/eager_loading_spec.rb +70 -1
  85. data/spec/model/plugins_spec.rb +236 -50
  86. data/spec/model/spec_helper.rb +0 -2
  87. 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, nil)
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, nil)
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, nil)
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, nil)
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, nil)
536
+ INTEGRATION_DB.instance_variable_set(:@schemas, {})
537
537
  INTEGRATION_DB.create_table!(:clients) do
538
538
  primary_key :id
539
539
  String :name
@@ -8,7 +8,6 @@ begin
8
8
  rescue LoadError
9
9
  end
10
10
 
11
- Sequel.virtual_row_instance_eval = true
12
11
  Sequel::Model.use_transactions = false
13
12
 
14
13
  $sqls = []
@@ -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 :graph_join_type option" do
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
@@ -1,72 +1,258 @@
1
1
  require File.join(File.dirname(__FILE__), "spec_helper")
2
2
 
3
- module Sequel::Plugins
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
- module Timestamped
6
- def self.apply(m, opts)
7
- m.meta_def(:stamp_opts) {opts}
8
- m.send(:define_method, :before_save){@values[:stamp] = Time.now}
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
- end
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
- describe Sequel::Model, "using a plugin" do
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 fail if the plugin is not found" do
30
- proc do
31
- c = Class.new(Sequel::Model)
32
- c.class_eval do
33
- plugin :something_or_other
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.should raise_error(LoadError)
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 apply the plugin to the class" do
39
- c = nil
40
- proc do
41
- c = Class.new(Sequel::Model)
42
- c.class_eval do
43
- set_dataset MODEL_DB[:items]
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.should_not raise_error(LoadError)
107
+ end
47
108
 
48
- c.should respond_to(:stamp_opts)
49
- c.stamp_opts.should == {:a => 1, :b => 2}
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
- # instance methods
52
- m = c.new
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 == {:a => 1, :b => 2}
160
+ m.abc.should == 123
56
161
  t = Time.now
57
162
  m[:stamp] = t
58
163
  m.get_stamp.should == t
59
-
60
- # class methods
61
- c.should respond_to(:deff)
62
- c.deff.should == {:a => 1, :b => 2}
63
-
64
- # dataset methods
65
- c.dataset.should respond_to(:ghi)
66
- c.dataset.ghi.should == {:a => 1, :b => 2}
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
- # dataset methods called on the class
69
- c.should respond_to(:ghi)
70
- c.ghi.should == {:a => 1, :b => 2}
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