sequel 3.22.0 → 3.23.0

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