sequel 3.3.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +62 -0
- data/README.rdoc +4 -4
- data/doc/release_notes/3.3.0.txt +1 -1
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/sharding.rdoc +3 -3
- data/lib/sequel/adapters/amalgalite.rb +1 -1
- data/lib/sequel/adapters/firebird.rb +4 -9
- data/lib/sequel/adapters/jdbc.rb +21 -7
- data/lib/sequel/adapters/mysql.rb +2 -1
- data/lib/sequel/adapters/odbc.rb +7 -21
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +6 -1
- data/lib/sequel/adapters/shared/mssql.rb +11 -0
- data/lib/sequel/adapters/shared/mysql.rb +8 -12
- data/lib/sequel/adapters/shared/oracle.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +5 -10
- data/lib/sequel/adapters/shared/sqlite.rb +21 -1
- data/lib/sequel/adapters/sqlite.rb +2 -2
- data/lib/sequel/core.rb +147 -11
- data/lib/sequel/database.rb +21 -9
- data/lib/sequel/dataset.rb +31 -6
- data/lib/sequel/dataset/convenience.rb +1 -1
- data/lib/sequel/dataset/sql.rb +76 -18
- data/lib/sequel/extensions/inflector.rb +2 -51
- data/lib/sequel/model.rb +16 -10
- data/lib/sequel/model/associations.rb +4 -1
- data/lib/sequel/model/base.rb +13 -6
- data/lib/sequel/model/default_inflections.rb +46 -0
- data/lib/sequel/model/inflections.rb +1 -51
- data/lib/sequel/plugins/boolean_readers.rb +52 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +13 -1
- data/lib/sequel/plugins/nested_attributes.rb +171 -0
- data/lib/sequel/plugins/serialization.rb +35 -16
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/validation_helpers.rb +8 -1
- data/lib/sequel/sql.rb +33 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +11 -6
- data/spec/core/core_sql_spec.rb +29 -0
- data/spec/core/database_spec.rb +16 -7
- data/spec/core/dataset_spec.rb +264 -20
- data/spec/extensions/boolean_readers_spec.rb +86 -0
- data/spec/extensions/inflector_spec.rb +67 -4
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +45 -5
- data/spec/extensions/nested_attributes_spec.rb +272 -0
- data/spec/extensions/serialization_spec.rb +64 -1
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/validation_helpers_spec.rb +18 -0
- data/spec/integration/dataset_test.rb +79 -2
- data/spec/integration/schema_test.rb +17 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/model/associations_spec.rb +19 -7
- data/spec/model/model_spec.rb +29 -0
- data/spec/model/record_spec.rb +36 -0
- data/spec/spec_config.rb +1 -1
- metadata +14 -2
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
describe Sequel::Model, "BooleanReaders plugin" do
|
4
|
+
before do
|
5
|
+
@db = Sequel::Database.new({})
|
6
|
+
def @db.schema(*args)
|
7
|
+
[[:id, {}], [:y, {:type=>:integer, :db_type=>'tinyint(1)'}], [:b, {:type=>:boolean, :db_type=>'boolean'}]]
|
8
|
+
end
|
9
|
+
|
10
|
+
@c = Class.new(Sequel::Model(@db[:items]))
|
11
|
+
@p =lambda do
|
12
|
+
@columns = [:id, :b, :y]
|
13
|
+
def columns; @columns; end
|
14
|
+
end
|
15
|
+
@c.instance_eval(&@p)
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "should create attribute? readers for all boolean attributes" do
|
19
|
+
@c.plugin(:boolean_readers)
|
20
|
+
o = @c.new
|
21
|
+
o.b?.should == nil
|
22
|
+
o.b = '1'
|
23
|
+
o.b?.should == true
|
24
|
+
o.b = '0'
|
25
|
+
o.b?.should == false
|
26
|
+
o.b = ''
|
27
|
+
o.b?.should == nil
|
28
|
+
end
|
29
|
+
|
30
|
+
specify "should not create attribute? readers for non-boolean attributes" do
|
31
|
+
@c.plugin(:boolean_readers)
|
32
|
+
proc{@c.new.y?}.should raise_error(NoMethodError)
|
33
|
+
proc{@c.new.id?}.should raise_error(NoMethodError)
|
34
|
+
end
|
35
|
+
|
36
|
+
specify "should accept a block to determine if an attribute is boolean" do
|
37
|
+
@c.plugin(:boolean_readers){|c| db_schema[c][:db_type] == 'tinyint(1)'}
|
38
|
+
proc{@c.new.b?}.should raise_error(NoMethodError)
|
39
|
+
o = @c.new
|
40
|
+
o.y.should == nil
|
41
|
+
o.y?.should == nil
|
42
|
+
o.y = '1'
|
43
|
+
o.y.should == 1
|
44
|
+
o.y?.should == true
|
45
|
+
o.y = '0'
|
46
|
+
o.y.should == 0
|
47
|
+
o.y?.should == false
|
48
|
+
o.y = ''
|
49
|
+
o.y.should == nil
|
50
|
+
o.y?.should == nil
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "should create boolean readers when set_dataset is defined" do
|
54
|
+
c = Class.new(Sequel::Model(@db))
|
55
|
+
c.instance_eval(&@p)
|
56
|
+
c.plugin(:boolean_readers)
|
57
|
+
c.set_dataset(@db[:a])
|
58
|
+
o = c.new
|
59
|
+
o.b?.should == nil
|
60
|
+
o.b = '1'
|
61
|
+
o.b?.should == true
|
62
|
+
o.b = '0'
|
63
|
+
o.b?.should == false
|
64
|
+
o.b = ''
|
65
|
+
o.b?.should == nil
|
66
|
+
proc{o.i?}.should raise_error(NoMethodError)
|
67
|
+
|
68
|
+
c = Class.new(Sequel::Model(@db))
|
69
|
+
c.instance_eval(&@p)
|
70
|
+
c.plugin(:boolean_readers){|x| db_schema[x][:db_type] == 'tinyint(1)'}
|
71
|
+
c.set_dataset(@db[:a])
|
72
|
+
o = c.new
|
73
|
+
o.y.should == nil
|
74
|
+
o.y?.should == nil
|
75
|
+
o.y = '1'
|
76
|
+
o.y.should == 1
|
77
|
+
o.y?.should == true
|
78
|
+
o.y = '0'
|
79
|
+
o.y.should == 0
|
80
|
+
o.y?.should == false
|
81
|
+
o.y = ''
|
82
|
+
o.y.should == nil
|
83
|
+
o.y?.should == nil
|
84
|
+
proc{o.b?}.should raise_error(NoMethodError)
|
85
|
+
end
|
86
|
+
end
|
@@ -48,16 +48,16 @@ describe String do
|
|
48
48
|
|
49
49
|
it "#pluralize should transform words from singular to plural" do
|
50
50
|
"post".pluralize.should == "posts"
|
51
|
-
"octopus".pluralize.should =="
|
51
|
+
"octopus".pluralize.should =="octopuses"
|
52
52
|
"the blue mailman".pluralize.should == "the blue mailmen"
|
53
|
-
"CamelOctopus".pluralize.should == "
|
53
|
+
"CamelOctopus".pluralize.should == "CamelOctopuses"
|
54
54
|
end
|
55
55
|
|
56
56
|
it "#singularize should transform words from plural to singular" do
|
57
57
|
"posts".singularize.should == "post"
|
58
|
-
"
|
58
|
+
"octopuses".singularize.should == "octopus"
|
59
59
|
"the blue mailmen".singularize.should == "the blue mailman"
|
60
|
-
"
|
60
|
+
"CamelOctopuses".singularize.should == "CamelOctopus"
|
61
61
|
end
|
62
62
|
|
63
63
|
it "#tableize should transform class names to table names" do
|
@@ -117,3 +117,66 @@ describe String::Inflections do
|
|
117
117
|
String.inflections{|i| i.should == String::Inflections}.should == String::Inflections
|
118
118
|
end
|
119
119
|
end
|
120
|
+
|
121
|
+
describe 'Default inflections' do
|
122
|
+
it "should support the default inflection rules" do
|
123
|
+
{
|
124
|
+
:test=>:tests,
|
125
|
+
:ax=>:axes,
|
126
|
+
:testis=>:testes,
|
127
|
+
:octopus=>:octopuses,
|
128
|
+
:virus=>:viruses,
|
129
|
+
:alias=>:aliases,
|
130
|
+
:status=>:statuses,
|
131
|
+
:bus=>:buses,
|
132
|
+
:buffalo=>:buffaloes,
|
133
|
+
:tomato=>:tomatoes,
|
134
|
+
:datum=>:data,
|
135
|
+
:bacterium=>:bacteria,
|
136
|
+
:analysis=>:analyses,
|
137
|
+
:basis=>:bases,
|
138
|
+
:diagnosis=>:diagnoses,
|
139
|
+
:parenthesis=>:parentheses,
|
140
|
+
:prognosis=>:prognoses,
|
141
|
+
:synopsis=>:synopses,
|
142
|
+
:thesis=>:theses,
|
143
|
+
:wife=>:wives,
|
144
|
+
:giraffe=>:giraffes,
|
145
|
+
:self=>:selves,
|
146
|
+
:dwarf=>:dwarves,
|
147
|
+
:hive=>:hives,
|
148
|
+
:fly=>:flies,
|
149
|
+
:buy=>:buys,
|
150
|
+
:soliloquy=>:soliloquies,
|
151
|
+
:day=>:days,
|
152
|
+
:attorney=>:attorneys,
|
153
|
+
:boy=>:boys,
|
154
|
+
:hoax=>:hoaxes,
|
155
|
+
:lunch=>:lunches,
|
156
|
+
:princess=>:princesses,
|
157
|
+
:matrix=>:matrices,
|
158
|
+
:vertex=>:vertices,
|
159
|
+
:index=>:indices,
|
160
|
+
:mouse=>:mice,
|
161
|
+
:louse=>:lice,
|
162
|
+
:ox=>:oxen,
|
163
|
+
:quiz=>:quizzes,
|
164
|
+
:motive=>:motives,
|
165
|
+
:movie=>:movies,
|
166
|
+
:series=>:series,
|
167
|
+
:crisis=>:crises,
|
168
|
+
:person=>:people,
|
169
|
+
:man=>:men,
|
170
|
+
:woman=>:women,
|
171
|
+
:child=>:children,
|
172
|
+
:sex=>:sexes,
|
173
|
+
:move=>:moves
|
174
|
+
}.each do |k, v|
|
175
|
+
k.to_s.pluralize.should == v.to_s
|
176
|
+
v.to_s.singularize.should == k.to_s
|
177
|
+
end
|
178
|
+
[:equipment, :information, :rice, :money, :species, :series, :fish, :sheep, :news].each do |a|
|
179
|
+
a.to_s.pluralize.should == a.to_s.singularize
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
describe "InstanceHooks plugin" do
|
4
|
+
def r(x)
|
5
|
+
@r << x
|
6
|
+
x
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
@c = Class.new(Sequel::Model(:items))
|
11
|
+
@c.plugin :instance_hooks
|
12
|
+
@c.raise_on_save_failure = false
|
13
|
+
@o = @c.new
|
14
|
+
@x = @c.load({:id=>1})
|
15
|
+
@r = []
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should support before_create_hook and after_create_hook" do
|
19
|
+
@o.after_create_hook{r 1}
|
20
|
+
@o.before_create_hook{r 2}
|
21
|
+
@o.after_create_hook{r 3}
|
22
|
+
@o.before_create_hook{r 4}
|
23
|
+
@o.save.should_not == nil
|
24
|
+
@r.should == [4, 2, 1, 3]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should cancel the save if before_create_hook block returns false" do
|
28
|
+
@o.after_create_hook{r 1}
|
29
|
+
@o.before_create_hook{r false}
|
30
|
+
@o.before_create_hook{r 4}
|
31
|
+
@o.save.should == nil
|
32
|
+
@r.should == [4, false]
|
33
|
+
@r.clear
|
34
|
+
@o.save.should == nil
|
35
|
+
@r.should == [4, false]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should support before_update_hook and after_update_hook" do
|
39
|
+
@x.after_update_hook{r 1}
|
40
|
+
@x.before_update_hook{r 2}
|
41
|
+
@x.after_update_hook{r 3}
|
42
|
+
@x.before_update_hook{r 4}
|
43
|
+
@x.save.should_not == nil
|
44
|
+
@r.should == [4, 2, 1, 3]
|
45
|
+
@x.save.should_not == nil
|
46
|
+
@r.should == [4, 2, 1, 3]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should cancel the save if before_update_hook block returns false" do
|
50
|
+
@x.after_update_hook{r 1}
|
51
|
+
@x.before_update_hook{r false}
|
52
|
+
@x.before_update_hook{r 4}
|
53
|
+
@x.save.should == nil
|
54
|
+
@r.should == [4, false]
|
55
|
+
@r.clear
|
56
|
+
@x.save.should == nil
|
57
|
+
@r.should == [4, false]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should support before_save_hook and after_save_hook" do
|
61
|
+
@o.after_save_hook{r 1}
|
62
|
+
@o.before_save_hook{r 2}
|
63
|
+
@o.after_save_hook{r 3}
|
64
|
+
@o.before_save_hook{r 4}
|
65
|
+
@o.save.should_not == nil
|
66
|
+
@r.should == [4, 2, 1, 3]
|
67
|
+
@r.clear
|
68
|
+
|
69
|
+
@x.after_save_hook{r 1}
|
70
|
+
@x.before_save_hook{r 2}
|
71
|
+
@x.after_save_hook{r 3}
|
72
|
+
@x.before_save_hook{r 4}
|
73
|
+
@x.save.should_not == nil
|
74
|
+
@r.should == [4, 2, 1, 3]
|
75
|
+
@x.save.should_not == nil
|
76
|
+
@r.should == [4, 2, 1, 3]
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should cancel the save if before_save_hook block returns false" do
|
80
|
+
@x.after_save_hook{r 1}
|
81
|
+
@x.before_save_hook{r false}
|
82
|
+
@x.before_save_hook{r 4}
|
83
|
+
@x.save.should == nil
|
84
|
+
@r.should == [4, false]
|
85
|
+
@r.clear
|
86
|
+
|
87
|
+
@x.after_save_hook{r 1}
|
88
|
+
@x.before_save_hook{r false}
|
89
|
+
@x.before_save_hook{r 4}
|
90
|
+
@x.save.should == nil
|
91
|
+
@r.should == [4, false]
|
92
|
+
@r.clear
|
93
|
+
@x.save.should == nil
|
94
|
+
@r.should == [4, false]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should support before_destroy_hook and after_destroy_hook" do
|
98
|
+
@x.after_destroy_hook{r 1}
|
99
|
+
@x.before_destroy_hook{r 2}
|
100
|
+
@x.after_destroy_hook{r 3}
|
101
|
+
@x.before_destroy_hook{r 4}
|
102
|
+
@x.destroy.should_not == nil
|
103
|
+
@r.should == [4, 2, 1, 3]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should cancel the destroy if before_destroy_hook block returns false" do
|
107
|
+
@x.after_destroy_hook{r 1}
|
108
|
+
@x.before_destroy_hook{r false}
|
109
|
+
@x.before_destroy_hook{r 4}
|
110
|
+
@x.destroy.should == nil
|
111
|
+
@r.should == [4, false]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should support before_validation_hook and after_validation_hook" do
|
115
|
+
@o.after_validation_hook{r 1}
|
116
|
+
@o.before_validation_hook{r 2}
|
117
|
+
@o.after_validation_hook{r 3}
|
118
|
+
@o.before_validation_hook{r 4}
|
119
|
+
@o.valid?.should == true
|
120
|
+
@r.should == [4, 2, 1, 3]
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should cancel the save if before_validation_hook block returns false" do
|
124
|
+
@o.after_validation_hook{r 1}
|
125
|
+
@o.before_validation_hook{r false}
|
126
|
+
@o.before_validation_hook{r 4}
|
127
|
+
@o.valid?.should == false
|
128
|
+
@r.should == [4, false]
|
129
|
+
@r.clear
|
130
|
+
@o.valid?.should == false
|
131
|
+
@r.should == [4, false]
|
132
|
+
end
|
133
|
+
end
|
@@ -4,7 +4,7 @@ describe "Sequel::Plugins::LazyAttributes" do
|
|
4
4
|
before do
|
5
5
|
class ::LazyAttributesModel < Sequel::Model(:la)
|
6
6
|
plugin :lazy_attributes
|
7
|
-
|
7
|
+
set_columns([:id, :name])
|
8
8
|
meta_def(:columns){[:id, :name]}
|
9
9
|
lazy_attributes :name
|
10
10
|
meta_def(:columns){[:id]}
|
@@ -13,10 +13,11 @@ describe "Sequel::Plugins::LazyAttributes" do
|
|
13
13
|
execute(sql)
|
14
14
|
select = @opts[:select]
|
15
15
|
where = @opts[:where]
|
16
|
+
block = @mod_block || proc{|s| s}
|
16
17
|
if !where
|
17
18
|
if select.include?(:name)
|
18
|
-
yield(:id=>1, :name=>'1')
|
19
|
-
yield(:id=>2, :name=>'2')
|
19
|
+
yield(block[:id=>1, :name=>'1'])
|
20
|
+
yield(block[:id=>2, :name=>'2'])
|
20
21
|
else
|
21
22
|
yield(:id=>1)
|
22
23
|
yield(:id=>2)
|
@@ -26,9 +27,9 @@ describe "Sequel::Plugins::LazyAttributes" do
|
|
26
27
|
i = i.instance_variable_get(:@array) if i.is_a?(Sequel::SQL::SQLArray)
|
27
28
|
Array(i).each do |x|
|
28
29
|
if sql =~ /SELECT name FROM/
|
29
|
-
yield(:name=>x.to_s)
|
30
|
+
yield(block[:name=>x.to_s])
|
30
31
|
else
|
31
|
-
yield(:id=>x, :name=>x.to_s)
|
32
|
+
yield(block[:id=>x, :name=>x.to_s])
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
@@ -110,4 +111,43 @@ describe "Sequel::Plugins::LazyAttributes" do
|
|
110
111
|
MODEL_DB.sqls.should == ['SELECT id FROM la', 'SELECT id, name FROM la WHERE (id IN (1, 2))']
|
111
112
|
end
|
112
113
|
end
|
114
|
+
|
115
|
+
it "should add the accessors to a module included in the class, so they can be easily overridden" do
|
116
|
+
@c.class_eval do
|
117
|
+
def name
|
118
|
+
"#{super}-blah"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@c.with_identity_map do
|
122
|
+
ms = @c.all
|
123
|
+
ms.map{|m| m.values}.should == [{:id=>1}, {:id=>2}]
|
124
|
+
ms.map{|m| m.name}.should == %w'1-blah 2-blah'
|
125
|
+
ms.map{|m| m.values}.should == [{:id=>1, :name=>'1'}, {:id=>2, :name=>'2'}]
|
126
|
+
MODEL_DB.sqls.should == ['SELECT id FROM la', 'SELECT id, name FROM la WHERE (id IN (1, 2))']
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should work with the serialization plugin" do
|
131
|
+
@c.plugin :serialization, :yaml, :name
|
132
|
+
@ds.instance_variable_set(:@mod_block, proc{|s| s.merge(:name=>"--- #{s[:name].to_i*3}\n")})
|
133
|
+
@c.with_identity_map do
|
134
|
+
ms = @ds.all
|
135
|
+
ms.map{|m| m.values}.should == [{:id=>1}, {:id=>2}]
|
136
|
+
ms.map{|m| m.name}.should == [3,6]
|
137
|
+
ms.map{|m| m.values}.should == [{:id=>1, :name=>"--- 3\n"}, {:id=>2, :name=>"--- 6\n"}]
|
138
|
+
ms.map{|m| m.deserialized_values}.should == [{:name=>3}, {:name=>6}]
|
139
|
+
ms.map{|m| m.name}.should == [3,6]
|
140
|
+
MODEL_DB.sqls.should == ['SELECT id FROM la', 'SELECT id, name FROM la WHERE (id IN (1, 2))']
|
141
|
+
end
|
142
|
+
MODEL_DB.reset
|
143
|
+
@c.with_identity_map do
|
144
|
+
m = @ds.first
|
145
|
+
m.values.should == {:id=>1}
|
146
|
+
m.name.should == 3
|
147
|
+
m.values.should == {:id=>1, :name=>"--- 3\n"}
|
148
|
+
m.deserialized_values.should == {:name=>3}
|
149
|
+
m.name.should == 3
|
150
|
+
MODEL_DB.sqls.should == ["SELECT id FROM la LIMIT 1", "SELECT name FROM la WHERE (id = 1) LIMIT 1"]
|
151
|
+
end
|
152
|
+
end
|
113
153
|
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
describe "NestedAttributes plugin" do
|
4
|
+
before do
|
5
|
+
mods = @mods = []
|
6
|
+
i = 0
|
7
|
+
gi = lambda{i}
|
8
|
+
ii = lambda{i+=1}
|
9
|
+
ds_mod = Module.new do
|
10
|
+
define_method(:insert) do |h|
|
11
|
+
x = ii.call
|
12
|
+
mods << [:i, first_source, h, x]
|
13
|
+
x
|
14
|
+
end
|
15
|
+
define_method(:insert_select) do |h|
|
16
|
+
x = ii.call
|
17
|
+
mods << [:is, first_source, h, x]
|
18
|
+
h.merge(:id=>x)
|
19
|
+
end
|
20
|
+
define_method(:update) do |h|
|
21
|
+
mods << [:u, first_source, h, literal(opts[:where])]
|
22
|
+
1
|
23
|
+
end
|
24
|
+
define_method(:delete) do
|
25
|
+
mods << [:d, first_source, literal(opts[:where])]
|
26
|
+
1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
db = Sequel::Database.new({})
|
30
|
+
db.meta_def(:dataset) do |*a|
|
31
|
+
x = super(*a)
|
32
|
+
x.extend(ds_mod)
|
33
|
+
x
|
34
|
+
end
|
35
|
+
@c = Class.new(Sequel::Model(db))
|
36
|
+
@c.plugin :nested_attributes
|
37
|
+
@Artist = Class.new(@c).set_dataset(:artists)
|
38
|
+
@Album = Class.new(@c).set_dataset(:albums)
|
39
|
+
@Tag = Class.new(@c).set_dataset(:tags)
|
40
|
+
[@Artist, @Album, @Tag].each do |m|
|
41
|
+
m.dataset.extend(ds_mod)
|
42
|
+
end
|
43
|
+
@Artist.columns :id, :name
|
44
|
+
@Album.columns :id, :name, :artist_id
|
45
|
+
@Tag.columns :id, :name
|
46
|
+
@Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
|
47
|
+
@Album.many_to_one :artist, :class=>@Artist
|
48
|
+
@Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
|
49
|
+
@Artist.nested_attributes :albums, :destroy=>true, :remove=>true
|
50
|
+
@Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should support creating new many_to_one objects" do
|
54
|
+
a = @Album.new({:name=>'Al', :artist_attributes=>{:name=>'Ar'}})
|
55
|
+
@mods.should == []
|
56
|
+
a.save
|
57
|
+
@mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should support creating new one_to_many objects" do
|
61
|
+
a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:name=>'Al'}]})
|
62
|
+
@mods.should == []
|
63
|
+
a.save
|
64
|
+
@mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should support creating new many_to_many objects" do
|
68
|
+
a = @Album.new({:name=>'Al', :tags_attributes=>[{:name=>'T'}]})
|
69
|
+
@mods.should == []
|
70
|
+
a.save
|
71
|
+
@mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3]]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should support updating many_to_one objects" do
|
75
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
76
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
77
|
+
al.associations[:artist] = ar
|
78
|
+
al.set(:artist_attributes=>{:id=>'20', :name=>'Ar2'})
|
79
|
+
@mods.should == []
|
80
|
+
al.save
|
81
|
+
@mods.should == [[:u, :albums, {:name=>"Al"}, '(id = 10)'], [:u, :artists, {:name=>"Ar2"}, '(id = 20)']]
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should support updating one_to_many objects" do
|
85
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
86
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
87
|
+
ar.associations[:albums] = [al]
|
88
|
+
ar.set(:albums_attributes=>[{:id=>10, :name=>'Al2'}])
|
89
|
+
@mods.should == []
|
90
|
+
ar.save
|
91
|
+
@mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)'], [:u, :albums, {:name=>"Al2"}, '(id = 10)']]
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should support updating many_to_many objects" do
|
95
|
+
a = @Album.load(:id=>10, :name=>'Al')
|
96
|
+
t = @Tag.load(:id=>20, :name=>'T')
|
97
|
+
a.associations[:tags] = [t]
|
98
|
+
a.set(:tags_attributes=>[{:id=>20, :name=>'T2'}])
|
99
|
+
@mods.should == []
|
100
|
+
a.save
|
101
|
+
@mods.should == [[:u, :albums, {:name=>"Al"}, '(id = 10)'], [:u, :tags, {:name=>"T2"}, '(id = 20)']]
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should support removing many_to_one objects" do
|
105
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
106
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
107
|
+
al.associations[:artist] = ar
|
108
|
+
al.set(:artist_attributes=>{:id=>'20', :_remove=>'1'})
|
109
|
+
@mods.should == []
|
110
|
+
al.save
|
111
|
+
@mods.should == [[:u, :albums, {:artist_id=>nil, :name=>'Al'}, '(id = 10)']]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should support removing one_to_many objects" do
|
115
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
116
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
117
|
+
ar.associations[:albums] = [al]
|
118
|
+
ar.set(:albums_attributes=>[{:id=>10, :_remove=>'t'}])
|
119
|
+
@mods.should == []
|
120
|
+
ar.save
|
121
|
+
@mods.should == [[:u, :albums, {:name=>"Al", :artist_id=>nil}, '(id = 10)'], [:u, :artists, {:name=>"Ar"}, '(id = 20)']]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should support removing many_to_many objects" do
|
125
|
+
a = @Album.load(:id=>10, :name=>'Al')
|
126
|
+
t = @Tag.load(:id=>20, :name=>'T')
|
127
|
+
a.associations[:tags] = [t]
|
128
|
+
a.set(:tags_attributes=>[{:id=>20, :_remove=>true}])
|
129
|
+
@mods.should == []
|
130
|
+
a.save
|
131
|
+
@mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)']]
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should support destroying many_to_one objects" do
|
135
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
136
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
137
|
+
al.associations[:artist] = ar
|
138
|
+
al.set(:artist_attributes=>{:id=>'20', :_delete=>'1'})
|
139
|
+
@mods.should == []
|
140
|
+
al.save
|
141
|
+
@mods.should == [[:u, :albums, {:artist_id=>nil, :name=>'Al'}, '(id = 10)'], [:d, :artists, '(id = 20)']]
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should support destroying one_to_many objects" do
|
145
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
146
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
147
|
+
ar.associations[:albums] = [al]
|
148
|
+
ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
|
149
|
+
@mods.should == []
|
150
|
+
ar.save
|
151
|
+
@mods.should == [[:u, :albums, {:name=>"Al", :artist_id=>nil}, '(id = 10)'], [:u, :artists, {:name=>"Ar"}, '(id = 20)'], [:d, :albums, '(id = 10)']]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should support destroying many_to_many objects" do
|
155
|
+
a = @Album.load(:id=>10, :name=>'Al')
|
156
|
+
t = @Tag.load(:id=>20, :name=>'T')
|
157
|
+
a.associations[:tags] = [t]
|
158
|
+
a.set(:tags_attributes=>[{:id=>20, :_delete=>true}])
|
159
|
+
@mods.should == []
|
160
|
+
a.save
|
161
|
+
@mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)'], [:d, :tags, '(id = 20)']]
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should support both string and symbol keys in nested attribute hashes" do
|
165
|
+
a = @Album.load(:id=>10, :name=>'Al')
|
166
|
+
t = @Tag.load(:id=>20, :name=>'T')
|
167
|
+
a.associations[:tags] = [t]
|
168
|
+
a.set('tags_attributes'=>[{'id'=>20, '_delete'=>true}])
|
169
|
+
@mods.should == []
|
170
|
+
a.save
|
171
|
+
@mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)'], [:d, :tags, '(id = 20)']]
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should support using a hash instead of an array for to_many nested attributes" do
|
175
|
+
a = @Album.load(:id=>10, :name=>'Al')
|
176
|
+
t = @Tag.load(:id=>20, :name=>'T')
|
177
|
+
a.associations[:tags] = [t]
|
178
|
+
a.set('tags_attributes'=>{'1'=>{'id'=>20, '_delete'=>true}})
|
179
|
+
@mods.should == []
|
180
|
+
a.save
|
181
|
+
@mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)'], [:d, :tags, '(id = 20)']]
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should only allow destroying associated objects if :destroy option is used in the nested_attributes call" do
|
185
|
+
a = @Album.load(:id=>10, :name=>'Al')
|
186
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
187
|
+
a.associations[:artist] = ar
|
188
|
+
@Album.nested_attributes :artist
|
189
|
+
proc{a.set(:artist_attributes=>{:id=>'20', :_delete=>'1'})}.should raise_error(Sequel::Error)
|
190
|
+
@Album.nested_attributes :artist, :destroy=>true
|
191
|
+
proc{a.set(:artist_attributes=>{:id=>'20', :_delete=>'1'})}.should_not raise_error(Sequel::Error)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should only allow removing associated objects if :remove option is used in the nested_attributes call" do
|
195
|
+
a = @Album.load(:id=>10, :name=>'Al')
|
196
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
197
|
+
a.associations[:artist] = ar
|
198
|
+
@Album.nested_attributes :artist
|
199
|
+
proc{a.set(:artist_attributes=>{:id=>'20', :_remove=>'1'})}.should raise_error(Sequel::Error)
|
200
|
+
@Album.nested_attributes :artist, :remove=>true
|
201
|
+
proc{a.set(:artist_attributes=>{:id=>'20', :_remove=>'1'})}.should_not raise_error(Sequel::Error)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should raise an Error if a primary key is given in a nested attribute hash, but no matching associated object exists" do
|
205
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
206
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
207
|
+
ar.associations[:albums] = [al]
|
208
|
+
proc{ar.set(:albums_attributes=>[{:id=>30, :_delete=>'t'}])}.should raise_error(Sequel::Error)
|
209
|
+
proc{ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])}.should_not raise_error(Sequel::Error)
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should not raise an Error if an unmatched primary key is given, if the :strict=>false option is used" do
|
213
|
+
@Artist.nested_attributes :albums, :strict=>false
|
214
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
215
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
216
|
+
ar.associations[:albums] = [al]
|
217
|
+
ar.set(:albums_attributes=>[{:id=>30, :_delete=>'t'}])
|
218
|
+
@mods.should == []
|
219
|
+
ar.save
|
220
|
+
@mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)']]
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should not save if nested attribute is not validate and should include nested attribute validation errors in the main object's validation errors" do
|
224
|
+
@Artist.class_eval do
|
225
|
+
def validate
|
226
|
+
super
|
227
|
+
errors.add(:name, 'cannot be Ar') if name == 'Ar'
|
228
|
+
end
|
229
|
+
end
|
230
|
+
a = @Album.new(:name=>'Al', :artist_attributes=>{:name=>'Ar'})
|
231
|
+
@mods.should == []
|
232
|
+
proc{a.save}.should raise_error(Sequel::ValidationFailed)
|
233
|
+
a.errors.full_messages.should == ['artist name cannot be Ar']
|
234
|
+
@mods.should == []
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should not accept nested attributes unless explicitly specified" do
|
238
|
+
@Artist.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
|
239
|
+
proc{@Artist.create({:name=>'Ar', :tags_attributes=>[{:name=>'T'}]})}.should raise_error(Sequel::Error)
|
240
|
+
@mods.should == []
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should save when save_changes or update is called if nested attribute associated objects changed but there are no changes to the main object" do
|
244
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
245
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
246
|
+
al.associations[:artist] = ar
|
247
|
+
al.update(:artist_attributes=>{:id=>'20', :name=>'Ar2'})
|
248
|
+
@mods.should == [[:u, :artists, {:name=>"Ar2"}, '(id = 20)']]
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should have a :limit option limiting the amount of entries" do
|
252
|
+
@Album.nested_attributes :tags, :limit=>2
|
253
|
+
arr = [{:name=>'T'}]
|
254
|
+
proc{@Album.new({:name=>'Al', :tags_attributes=>arr*3})}.should raise_error(Sequel::Error)
|
255
|
+
a = @Album.new({:name=>'Al', :tags_attributes=>arr*2})
|
256
|
+
@mods.should == []
|
257
|
+
a.save
|
258
|
+
@mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3], [:is, :tags, {:name=>"T"}, 4], [:i, :at, {:album_id=>1, :tag_id=>4}, 5]]
|
259
|
+
end
|
260
|
+
|
261
|
+
it "should accept a block that each hash gets passed to determine if it should be processed" do
|
262
|
+
@Album.nested_attributes(:tags){|h| h[:name].empty?}
|
263
|
+
a = @Album.new({:name=>'Al', :tags_attributes=>[{:name=>'T'}, {:name=>''}, {:name=>'T2'}]})
|
264
|
+
@mods.should == []
|
265
|
+
a.save
|
266
|
+
@mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3], [:is, :tags, {:name=>"T2"}, 4], [:i, :at, {:album_id=>1, :tag_id=>4}, 5]]
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should deal properly with accessing the same nested attribute associated object with conflicting actions" do
|
270
|
+
|
271
|
+
end
|
272
|
+
end
|