sequel 3.3.0 → 3.4.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 +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
|