sequel 3.22.0 → 3.23.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +28 -0
- data/README.rdoc +15 -1
- data/doc/association_basics.rdoc +121 -40
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +2 -2
- data/lib/sequel/model/associations.rb +168 -32
- data/lib/sequel/model/base.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/many_through_many.rb +28 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/spec_helper.rb +5 -0
- data/spec/core/spec_helper.rb +5 -0
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/many_through_many_spec.rb +21 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
- data/spec/extensions/spec_helper.rb +3 -1
- data/spec/extensions/xml_serializer_spec.rb +12 -0
- data/spec/integration/associations_test.rb +58 -0
- data/spec/integration/plugin_test.rb +15 -0
- data/spec/integration/spec_helper.rb +5 -0
- data/spec/model/associations_spec.rb +117 -0
- data/spec/model/eager_loading_spec.rb +269 -1
- data/spec/model/record_spec.rb +6 -0
- data/spec/model/spec_helper.rb +5 -0
- metadata +10 -4
@@ -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
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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)
|
data/spec/core/spec_helper.rb
CHANGED
@@ -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
|
|
@@ -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
|