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.
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