sequel 3.22.0 → 3.23.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.
@@ -0,0 +1,51 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built in Serialization plugin doesn't check for modification
4
+ # of the serialized objects, because it requires an extra deserialization of a potentially
5
+ # very large object. This plugin can detect changes in serialized values by
6
+ # checking whether the current deserialized value is the same as the original
7
+ # deserialized value. This does require deserializing the value twice, but the
8
+ # original deserialized value is cached.
9
+ #
10
+ # == Example
11
+ #
12
+ # require 'sequel'
13
+ # require 'json'
14
+ # class User < Sequel::Model
15
+ # plugin :serialization, :json, :permissions
16
+ # plugin :serialization_modification_detection
17
+ # end
18
+ # user = User.create(:permissions => {})
19
+ # user.permissions[:global] = 'read-only'
20
+ # user.save_changes
21
+ module SerializationModificationDetection
22
+ # Load the serialization plugin automatically.
23
+ def self.apply(model)
24
+ model.plugin :serialization
25
+ end
26
+
27
+ module InstanceMethods
28
+ # Detect which serialized columns have changed.
29
+ def changed_columns
30
+ cc = super
31
+ deserialized_values.each{|c, v| cc << c if !cc.include?(c) && original_deserialized_value(c) != v}
32
+ cc
33
+ end
34
+
35
+ private
36
+
37
+ # Clear the cache of original deserialized values after saving so that it doesn't
38
+ # show the column is modified after saving.
39
+ def after_save
40
+ super
41
+ @original_deserialized_values.clear if @original_deserialized_values
42
+ end
43
+
44
+ # Return the original deserialized value of the column, caching it to improve performance.
45
+ def original_deserialized_value(column)
46
+ (@original_deserialized_values ||= {})[column] ||= deserialize_value(column, self[column])
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -266,7 +266,7 @@ module Sequel
266
266
 
267
267
  name_proc = model.xml_serialize_name_proc(opts)
268
268
  x = model.xml_builder(opts)
269
- x.send(name_proc[opts.fetch(:root_name, model.send(:underscore, model.name)).to_s]) do |x1|
269
+ x.send(name_proc[opts.fetch(:root_name, model.send(:underscore, model.name).gsub('/', '__')).to_s]) do |x1|
270
270
  cols.each do |c|
271
271
  attrs = {}
272
272
  if types
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 22
6
+ MINOR = 23
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -10,6 +10,11 @@ begin
10
10
  rescue LoadError
11
11
  end
12
12
 
13
+ if ENV['SEQUEL_COLUMNS_INTROSPECTION']
14
+ Sequel.extension :columns_introspection
15
+ Sequel::Dataset.introspect_all_columns
16
+ end
17
+
13
18
  class Sequel::Database
14
19
  def log_duration(duration, message)
15
20
  log_info(message)
@@ -5,6 +5,11 @@ unless Object.const_defined?('Sequel')
5
5
  require 'sequel/core'
6
6
  end
7
7
 
8
+ if ENV['SEQUEL_COLUMNS_INTROSPECTION']
9
+ Sequel.extension :columns_introspection
10
+ Sequel::Dataset.introspect_all_columns
11
+ end
12
+
8
13
  class MockDataset < Sequel::Dataset
9
14
  def insert(*args)
10
15
  @db.execute insert_sql(*args)
@@ -0,0 +1,91 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
2
+
3
+ describe "Sequel::Dataset.introspect_all_columns" do
4
+ before do
5
+ @db = MODEL_DB
6
+ @ds = @db[:a]
7
+ class Sequel::Dataset
8
+ # Handle case where introspect_all_columns has already been called
9
+ alias columns columns_without_introspection unless instance_methods(false).map{|x| x.to_s}.include?('columns')
10
+ end
11
+ Sequel::Dataset.introspect_all_columns
12
+ @db.reset
13
+ end
14
+ after do
15
+ class Sequel::Dataset
16
+ alias columns columns_without_introspection
17
+ end
18
+ end
19
+
20
+ specify "should turn on column introspection by default" do
21
+ @ds.select(:x).columns.should == [:x]
22
+ @db.sqls.length.should == 0
23
+ end
24
+ end
25
+
26
+ describe "columns_introspection extension" do
27
+ before do
28
+ @db = MODEL_DB
29
+ @ds = @db[:a]
30
+ @ds.extend(Sequel::ColumnsIntrospection.dup) # dup to allow multiple places in class hierarchy
31
+ @db.reset
32
+ end
33
+
34
+ specify "should not issue a database query if the columns are already loaded" do
35
+ @ds.instance_variable_set(:@columns, [:x])
36
+ @ds.columns.should == [:x]
37
+ @db.sqls.length.should == 0
38
+ end
39
+
40
+ specify "should handle plain symbols without a database query" do
41
+ @ds.select(:x).columns.should == [:x]
42
+ @db.sqls.length.should == 0
43
+ end
44
+
45
+ specify "should handle qualified symbols without a database query" do
46
+ @ds.select(:t__x).columns.should == [:x]
47
+ @db.sqls.length.should == 0
48
+ end
49
+
50
+ specify "should handle aliased symbols without a database query" do
51
+ @ds.select(:x___a).columns.should == [:a]
52
+ @db.sqls.length.should == 0
53
+ end
54
+
55
+ specify "should handle qualified and aliased symbols without a database query" do
56
+ @ds.select(:t__x___a).columns.should == [:a]
57
+ @db.sqls.length.should == 0
58
+ end
59
+
60
+ specify "should handle SQL::Identifiers " do
61
+ @ds.select(:x.identifier).columns.should == [:x]
62
+ @db.sqls.length.should == 0
63
+ end
64
+
65
+ specify "should handle SQL::QualifiedIdentifiers" do
66
+ @ds.select(:x.qualify(:t)).columns.should == [:x]
67
+ @ds.select(:x.identifier.qualify(:t)).columns.should == [:x]
68
+ @db.sqls.length.should == 0
69
+ end
70
+
71
+ specify "should handle SQL::AliasedExpressions" do
72
+ @ds.select(:x.as(:a)).columns.should == [:a]
73
+ @ds.select(:x.as(:a.identifier)).columns.should == [:a]
74
+ @db.sqls.length.should == 0
75
+ end
76
+
77
+ specify "should issue a database query if the wildcard is selected" do
78
+ @ds.columns
79
+ @db.sqls.length.should == 1
80
+ end
81
+
82
+ specify "should issue a database query if an unsupported type is used" do
83
+ @ds.select(1).columns
84
+ @db.sqls.length.should == 1
85
+ end
86
+
87
+ specify "should not have column introspection on by default" do
88
+ @db[:a].select(:x).columns
89
+ @db.sqls.length.should == 1
90
+ end
91
+ end
@@ -8,6 +8,7 @@ describe Sequel::Model, "many_through_many" do
8
8
  plugin :many_through_many
9
9
  end
10
10
  class ::Tag < Sequel::Model
11
+ columns :id, :h1, :h2
11
12
  end
12
13
  MODEL_DB.reset
13
14
  @c1 = Artist
@@ -111,6 +112,26 @@ describe Sequel::Model, "many_through_many" do
111
112
  n.tags.should == [@c2.load(:id=>1)]
112
113
  end
113
114
 
115
+ it "should allowing filtering by many_through_many associations" do
116
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
117
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE (albums_tags.tag_id = 1234)))'
118
+ end
119
+
120
+ it "should allowing filtering by many_through_many associations with a single through table" do
121
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id]]
122
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists WHERE (albums_artists.album_id = 1234)))'
123
+ end
124
+
125
+ it "should allowing filtering by many_through_many associations with aliased tables" do
126
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums_artists, :id, :id], [:albums_artists, :album_id, :tag_id]]
127
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.id = albums_artists.album_id) INNER JOIN albums_artists AS albums_artists_1 ON (albums_artists_1.album_id = albums_artists_0.id) WHERE (albums_artists_1.tag_id = 1234)))'
128
+ end
129
+
130
+ it "should allowing filtering by many_through_many associations with composite keys" do
131
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
132
+ @c1.filter(:tags=>@c2.load(:h1=>1234, :h2=>85)).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE ((albums_tags.g1 = 1234) AND (albums_tags.g2 = 85))))'
133
+ end
134
+
114
135
  it "should support a :conditions option" do
115
136
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
116
137
  n = @c1.load(:id => 1234)
@@ -0,0 +1,36 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "serialization_modification_detection plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:items))
6
+ @c.class_eval do
7
+ columns :id, :h
8
+ plugin :serialization, :yaml, :h
9
+ plugin :serialization_modification_detection
10
+ end
11
+ @o1 = @c.new
12
+ @o2 = @c.load(:id=>1, :h=>"--- {}\n\n")
13
+ MODEL_DB.reset
14
+ end
15
+
16
+ it "should not detect columns that haven't been changed" do
17
+ @o2.changed_columns.should == []
18
+ @o2.h.should == {}
19
+ @o2.h[1] = 2
20
+ @o2.h.clear
21
+ @o2.changed_columns.should == []
22
+ end
23
+
24
+ it "should detect columns that have been changed" do
25
+ @o2.changed_columns.should == []
26
+ @o2.h.should == {}
27
+ @o2.h[1] = 2
28
+ @o2.changed_columns.should == [:h]
29
+ end
30
+
31
+ it "should report correct changed_columns after saving" do
32
+ @o2.h[1] = 2
33
+ @o2.save_changes
34
+ @o2.changed_columns.should == []
35
+ end
36
+ end
@@ -8,9 +8,11 @@ unless Sequel.const_defined?('Model')
8
8
  require 'sequel/model'
9
9
  end
10
10
 
11
- Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper looser_typecasting sql_expr thread_local_timezones to_dot')
11
+ Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper looser_typecasting sql_expr thread_local_timezones to_dot columns_introspection')
12
12
  {:hook_class_methods=>[], :schema=>[], :validation_class_methods=>[]}.each{|p, opts| Sequel::Model.plugin(p, *opts)}
13
13
 
14
+ Sequel::Dataset.introspect_all_columns if ENV['SEQUEL_COLUMNS_INTROSPECTION']
15
+
14
16
  def skip_warn(s)
15
17
  warn "Skipping test of #{s}" if ENV["SKIPPED_TEST_WARN"]
16
18
  end
@@ -36,6 +36,18 @@ describe "Sequel::Plugins::XmlSerializer" do
36
36
  Album.from_xml(@album.to_xml).should == @album
37
37
  end
38
38
 
39
+ it "should round trip successfully for namespaced models" do
40
+ module XmlSerializerTest
41
+ class Artist < Sequel::Model
42
+ plugin :xml_serializer
43
+ columns :id, :name
44
+ @db_schema = {:id=>{:type=>:integer}, :name=>{:type=>:string}}
45
+ end
46
+ end
47
+ artist = XmlSerializerTest::Artist.load(:id=>2, :name=>'YJM')
48
+ XmlSerializerTest::Artist.from_xml(artist.to_xml).should == artist
49
+ end
50
+
39
51
  it "should round trip successfully with empty strings" do
40
52
  artist = Artist.load(:id=>2, :name=>'')
41
53
  Artist.from_xml(artist.to_xml).should == artist
@@ -22,6 +22,22 @@ shared_examples_for "regular and composite key associations" do
22
22
  @tag.albums.should == [@album]
23
23
  end
24
24
 
25
+ specify "should work correctly when filtering by associations" do
26
+ @album.update(:artist => @artist)
27
+ @album.add_tag(@tag)
28
+
29
+ @album.reload
30
+ @artist.reload
31
+ @tag.reload
32
+
33
+ Artist.filter(:albums=>@album).all.should == [@artist]
34
+ Album.filter(:artist=>@artist).all.should == [@album]
35
+ Album.filter(:tags=>@tag).all.should == [@album]
36
+ Tag.filter(:albums=>@album).all.should == [@tag]
37
+ Album.filter(:artist=>@artist, :tags=>@tag).all.should == [@album]
38
+ @artist.albums_dataset.filter(:tags=>@tag).all.should == [@album]
39
+ end
40
+
25
41
  specify "should have remove methods work" do
26
42
  @album.update(:artist => @artist)
27
43
  @album.add_tag(@tag)
@@ -216,6 +232,48 @@ describe "Sequel::Model Simple Associations" do
216
232
  @tag.reload.albums.should == []
217
233
  end
218
234
 
235
+ specify "should handle dynamic callbacks for regular loading" do
236
+ @artist.add_album(@album)
237
+
238
+ @artist.albums.should == [@album]
239
+ @artist.albums(proc{|ds| ds.exclude(:id=>@album.id)}).should == []
240
+ @artist.albums(proc{|ds| ds.filter(:id=>@album.id)}).should == [@album]
241
+
242
+ @album.artist.should == @artist
243
+ @album.artist(proc{|ds| ds.exclude(:id=>@artist.id)}).should == nil
244
+ @album.artist(proc{|ds| ds.filter(:id=>@artist.id)}).should == @artist
245
+
246
+ if RUBY_VERSION >= '1.8.7'
247
+ @artist.albums{|ds| ds.exclude(:id=>@album.id)}.should == []
248
+ @artist.albums{|ds| ds.filter(:id=>@album.id)}.should == [@album]
249
+ @album.artist{|ds| ds.exclude(:id=>@artist.id)}.should == nil
250
+ @album.artist{|ds| ds.filter(:id=>@artist.id)}.should == @artist
251
+ end
252
+ end
253
+
254
+ specify "should handle dynamic callbacks for eager loading via eager and eager_graph" do
255
+ @artist.add_album(@album)
256
+ @album.add_tag(@tag)
257
+ album2 = @artist.add_album(:name=>'Foo')
258
+ tag2 = album2.add_tag(:name=>'T2')
259
+
260
+ artist = Artist.eager(:albums=>:tags).all.first
261
+ artist.albums.should == [@album, album2]
262
+ artist.albums.map{|x| x.tags}.should == [[@tag], [tag2]]
263
+
264
+ artist = Artist.eager_graph(:albums=>:tags).all.first
265
+ artist.albums.should == [@album, album2]
266
+ artist.albums.map{|x| x.tags}.should == [[@tag], [tag2]]
267
+
268
+ artist = Artist.eager(:albums=>{proc{|ds| ds.where(:id=>album2.id)}=>:tags}).all.first
269
+ artist.albums.should == [album2]
270
+ artist.albums.first.tags.should == [tag2]
271
+
272
+ artist = Artist.eager_graph(:albums=>{proc{|ds| ds.where(:id=>album2.id)}=>:tags}).all.first
273
+ artist.albums.should == [album2]
274
+ artist.albums.first.tags.should == [tag2]
275
+ end
276
+
219
277
  specify "should have remove method raise an error for one_to_many records if the object isn't already associated" do
220
278
  proc{@artist.remove_album(@album.id)}.should raise_error(Sequel::Error)
221
279
  proc{@artist.remove_album(@album)}.should raise_error(Sequel::Error)
@@ -205,6 +205,11 @@ describe "Many Through Many Plugin" do
205
205
  Artist.filter(:artists__id=>2).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A C'
206
206
  Artist.filter(:artists__id=>3).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B C'
207
207
  Artist.filter(:artists__id=>4).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
208
+
209
+ Artist.filter(:albums=>@album1).all.map{|a| a.name}.sort.should == %w'1 2'
210
+ Artist.filter(:albums=>@album2).all.map{|a| a.name}.sort.should == %w'3 4'
211
+ Artist.filter(:albums=>@album3).all.map{|a| a.name}.sort.should == %w'2 3'
212
+ Artist.filter(:albums=>@album4).all.map{|a| a.name}.sort.should == %w'1 4'
208
213
  end
209
214
 
210
215
  specify "should handle typical case with 3 join tables" do
@@ -223,6 +228,11 @@ describe "Many Through Many Plugin" do
223
228
  Artist.filter(:artists__id=>2).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 3'
224
229
  Artist.filter(:artists__id=>3).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'2 3 4'
225
230
  Artist.filter(:artists__id=>4).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 3 4'
231
+
232
+ Artist.filter(:related_artists=>@artist1).all.map{|a| a.name}.sort.should == %w'1 2 4'
233
+ Artist.filter(:related_artists=>@artist2).all.map{|a| a.name}.sort.should == %w'1 2 3'
234
+ Artist.filter(:related_artists=>@artist3).all.map{|a| a.name}.sort.should == %w'2 3 4'
235
+ Artist.filter(:related_artists=>@artist4).all.map{|a| a.name}.sort.should == %w'1 3 4'
226
236
  end
227
237
 
228
238
  specify "should handle extreme case with 5 join tables" do
@@ -250,6 +260,11 @@ describe "Many Through Many Plugin" do
250
260
  Artist.filter(:artists__id=>2).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C D'
251
261
  Artist.filter(:artists__id=>3).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B D'
252
262
  Artist.filter(:artists__id=>4).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
263
+
264
+ Artist.filter(:related_albums=>@album1).all.map{|a| a.name}.sort.should == %w'1 2 3'
265
+ Artist.filter(:related_albums=>@album2).all.map{|a| a.name}.sort.should == %w'1 2 3 4'
266
+ Artist.filter(:related_albums=>@album3).all.map{|a| a.name}.sort.should == %w'1 2'
267
+ Artist.filter(:related_albums=>@album4).all.map{|a| a.name}.sort.should == %w'2 3 4'
253
268
  end
254
269
  end
255
270
 
@@ -9,6 +9,11 @@ begin
9
9
  rescue LoadError
10
10
  end
11
11
 
12
+ if ENV['SEQUEL_COLUMNS_INTROSPECTION']
13
+ Sequel.extension :columns_introspection
14
+ Sequel::Dataset.introspect_all_columns
15
+ end
16
+
12
17
  Sequel::Model.use_transactions = false
13
18
 
14
19
  $sqls = []
@@ -357,6 +357,28 @@ describe Sequel::Model, "many_to_one" do
357
357
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 234) LIMIT 1"]
358
358
  end
359
359
 
360
+ it "should use a callback if given one as the argument" do
361
+ @c2.many_to_one :parent, :class => @c2
362
+
363
+ d = @c2.create(:id => 1)
364
+ MODEL_DB.reset
365
+ d.parent_id = 234
366
+ d.associations[:parent] = 42
367
+ d.parent(proc{|ds| ds.filter{name > 'M'}}).should_not == 42
368
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 234) AND (name > 'M')) LIMIT 1"]
369
+ end
370
+
371
+ it "should use a block given to the association method as a callback on ruby 1.8.7+" do
372
+ @c2.many_to_one :parent, :class => @c2
373
+
374
+ d = @c2.create(:id => 1)
375
+ MODEL_DB.reset
376
+ d.parent_id = 234
377
+ d.associations[:parent] = 42
378
+ d.parent{|ds| ds.filter{name > 'M'}}.should_not == 42
379
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.id = 234) AND (name > 'M')) LIMIT 1"]
380
+ end if RUBY_VERSION >= '1.8.7'
381
+
360
382
  it "should have the setter add to the reciprocal one_to_many cached association list if it exists" do
361
383
  @c2.many_to_one :parent, :class => @c2
362
384
  @c2.one_to_many :children, :class => @c2, :key=>:parent_id
@@ -1149,6 +1171,16 @@ describe Sequel::Model, "one_to_many" do
1149
1171
  v.model.should == Historical::Value
1150
1172
  end
1151
1173
 
1174
+ it "should use a callback if given one as the argument" do
1175
+ @c2.one_to_many :attributes, :class => @c1, :key => :nodeid
1176
+
1177
+ d = @c2.create(:id => 1234)
1178
+ MODEL_DB.reset
1179
+ d.associations[:attributes] = []
1180
+ d.attributes(proc{|ds| ds.filter{name > 'M'}}).should_not == []
1181
+ MODEL_DB.sqls.should == ["SELECT * FROM attributes WHERE ((attributes.nodeid = 1234) AND (name > 'M'))"]
1182
+ end
1183
+
1152
1184
  it "should use explicit key if given" do
1153
1185
  @c2.one_to_many :attributes, :class => @c1, :key => :nodeid
1154
1186
 
@@ -2724,3 +2756,88 @@ describe Sequel::Model, " association reflection methods" do
2724
2756
  c.instance_methods.map{|x| x.to_s}.should include('parent')
2725
2757
  end
2726
2758
  end
2759
+
2760
+ describe "Filtering by associations" do
2761
+ before do
2762
+ @Album = Class.new(Sequel::Model(:albums))
2763
+ artist = @Artist = Class.new(Sequel::Model(:artists))
2764
+ tag = @Tag = Class.new(Sequel::Model(:tags))
2765
+ track = @Track = Class.new(Sequel::Model(:tracks))
2766
+ album_info = @AlbumInfo = Class.new(Sequel::Model(:album_infos))
2767
+ @Artist.columns :id, :id1, :id2
2768
+ @Tag.columns :id, :tid1, :tid2
2769
+ @Track.columns :id, :album_id, :album_id1, :album_id2
2770
+ @AlbumInfo.columns :id, :album_id, :album_id1, :album_id2
2771
+ @Album.class_eval do
2772
+ columns :id, :id1, :id2, :artist_id, :artist_id1, :artist_id2
2773
+ many_to_one :artist, :class=>artist
2774
+ one_to_many :tracks, :class=>track, :key=>:album_id
2775
+ one_to_one :album_info, :class=>album_info, :key=>:album_id
2776
+ many_to_many :tags, :class=>tag, :left_key=>:album_id, :join_table=>:albums_tags
2777
+
2778
+ many_to_one :cartist, :class=>artist, :key=>[:artist_id1, :artist_id2], :primary_key=>[:id1, :id2]
2779
+ one_to_many :ctracks, :class=>track, :key=>[:album_id1, :album_id2], :primary_key=>[:id1, :id2]
2780
+ one_to_one :calbum_info, :class=>album_info, :key=>[:album_id1, :album_id2], :primary_key=>[:id1, :id2]
2781
+ many_to_many :ctags, :class=>tag, :left_key=>[:album_id1, :album_id2], :left_primary_key=>[:id1, :id2], :right_key=>[:tag_id1, :tag_id2], :right_primary_key=>[:tid1, :tid2], :join_table=>:albums_tags
2782
+ end
2783
+ end
2784
+
2785
+ it "should be able to filter on many_to_one associations" do
2786
+ @Album.filter(:artist=>@Artist.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE (artist_id = 3)'
2787
+ end
2788
+
2789
+ it "should be able to filter on one_to_many associations" do
2790
+ @Album.filter(:tracks=>@Track.load(:album_id=>3)).sql.should == 'SELECT * FROM albums WHERE (id = 3)'
2791
+ end
2792
+
2793
+ it "should be able to filter on one_to_one associations" do
2794
+ @Album.filter(:album_info=>@AlbumInfo.load(:album_id=>3)).sql.should == 'SELECT * FROM albums WHERE (id = 3)'
2795
+ end
2796
+
2797
+ it "should be able to filter on many_to_many associations" do
2798
+ @Album.filter(:tags=>@Tag.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE (id IN (SELECT album_id FROM albums_tags WHERE (tag_id = 3)))'
2799
+ end
2800
+
2801
+ it "should be able to filter on many_to_one associations with composite keys" do
2802
+ @Album.filter(:cartist=>@Artist.load(:id1=>3, :id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((artist_id1 = 3) AND (artist_id2 = 4))'
2803
+ end
2804
+
2805
+ it "should be able to filter on one_to_many associations with composite keys" do
2806
+ @Album.filter(:ctracks=>@Track.load(:album_id1=>3, :album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((id1 = 3) AND (id2 = 4))'
2807
+ end
2808
+
2809
+ it "should be able to filter on one_to_one associations with composite keys" do
2810
+ @Album.filter(:calbum_info=>@AlbumInfo.load(:album_id1=>3, :album_id2=>4)).sql.should == 'SELECT * FROM albums WHERE ((id1 = 3) AND (id2 = 4))'
2811
+ end
2812
+
2813
+ it "should be able to filter on many_to_many associations with composite keys" do
2814
+ @Album.filter(:ctags=>@Tag.load(:tid1=>3, :tid2=>4)).sql.should == 'SELECT * FROM albums WHERE ((id1, id2) IN (SELECT album_id1, album_id2 FROM albums_tags WHERE ((tag_id1 = 3) AND (tag_id2 = 4))))'
2815
+ end
2816
+
2817
+ it "should work inside a complex filter" do
2818
+ artist = @Artist.load(:id=>3)
2819
+ @Album.filter{foo & {:artist=>artist}}.sql.should == 'SELECT * FROM albums WHERE (foo AND (artist_id = 3))'
2820
+ track = @Track.load(:album_id=>4)
2821
+ @Album.filter{foo & [[:artist, artist], [:tracks, track]]}.sql.should == 'SELECT * FROM albums WHERE (foo AND (artist_id = 3) AND (id = 4))'
2822
+ end
2823
+
2824
+ it "should raise for an invalid association name" do
2825
+ proc{@Album.filter(:foo=>@Artist.load(:id=>3)).sql}.should raise_error(Sequel::Error)
2826
+ end
2827
+
2828
+ it "should raise for an invalid association type" do
2829
+ @Album.many_to_many :iatags, :clone=>:tags
2830
+ @Album.association_reflection(:iatags)[:type] = :foo
2831
+ proc{@Album.filter(:mtmtags=>@Tag.load(:id=>3)).sql}.should raise_error(Sequel::Error)
2832
+ end
2833
+
2834
+ it "should raise for an invalid associated object class " do
2835
+ proc{@Album.filter(:tags=>@Artist.load(:id=>3)).sql}.should raise_error(Sequel::Error)
2836
+ end
2837
+
2838
+ it "should work correctly in subclasses" do
2839
+ c = Class.new(@Album)
2840
+ c.many_to_one :sartist, :class=>@Artist
2841
+ c.filter(:sartist=>@Artist.load(:id=>3)).sql.should == 'SELECT * FROM albums WHERE (sartist_id = 3)'
2842
+ end
2843
+ end