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