c3-activerecord-oracle_enhanced-adapter 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/History.txt +182 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +32 -0
  4. data/README.rdoc +77 -0
  5. data/Rakefile +49 -0
  6. data/VERSION +1 -0
  7. data/activerecord-oracle_enhanced-adapter.gemspec +79 -0
  8. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1664 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
  27. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
  29. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
  30. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
  31. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +370 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +218 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +114 -0
  38. metadata +137 -0
@@ -0,0 +1,67 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "OracleEnhancedAdapter logging dbms_output from plsql" do
4
+ include LoggerSpecHelper
5
+
6
+ before(:all) do
7
+ @buffer = StringIO.new
8
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
9
+ ActiveRecord::Base.connection.execute <<-SQL
10
+ CREATE or REPLACE
11
+ FUNCTION MORE_THAN_FIVE_CHARACTERS_LONG (some_text VARCHAR2) RETURN INTEGER
12
+ AS
13
+ longer_than_five INTEGER;
14
+ BEGIN
15
+ dbms_output.put_line('before the if -' || some_text || '-');
16
+ IF length(some_text) > 5 THEN
17
+ dbms_output.put_line('it is longer than 5');
18
+ longer_than_five := 1;
19
+ ELSE
20
+ dbms_output.put_line('it is 5 or shorter');
21
+ longer_than_five := 0;
22
+ END IF;
23
+ dbms_output.put_line('about to return: ' || longer_than_five);
24
+ RETURN longer_than_five;
25
+ END;
26
+ SQL
27
+ end
28
+
29
+ after(:all) do
30
+ ActiveRecord::Base.connection.execute "DROP FUNCTION MORE_THAN_FIVE_CHARACTERS_LONG"
31
+ end
32
+
33
+ before(:each) do
34
+ @buffer = StringIO.new
35
+ log_to @buffer
36
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
37
+ @conn = ActiveRecord::Base.connection
38
+ end
39
+
40
+ it "should NOT log dbms output when dbms output is disabled" do
41
+ @conn.disable_dbms_output
42
+
43
+ @conn.select_all("select more_than_five_characters_long('hi there') is_it_long from dual").should == [{'is_it_long'=>1}]
44
+
45
+ @buffer.string.should_not match(/^DBMS_OUTPUT/)
46
+ end
47
+
48
+ it "should log dbms output lines to the rails log" do
49
+ @conn.enable_dbms_output
50
+
51
+ @conn.select_all("select more_than_five_characters_long('hi there') is_it_long from dual").should == [{'is_it_long'=>1}]
52
+
53
+ @buffer.string.should match(/^DBMS_OUTPUT: before the if -hi there-$/)
54
+ @buffer.string.should match(/^DBMS_OUTPUT: it is longer than 5$/)
55
+ @buffer.string.should match(/^DBMS_OUTPUT: about to return: 1$/)
56
+ end
57
+
58
+ it "should log dbms output lines to the rails log" do
59
+ @conn.enable_dbms_output
60
+
61
+ @conn.select_all("select more_than_five_characters_long('short') is_it_long from dual").should == [{'is_it_long'=>0}]
62
+
63
+ @buffer.string.should match(/^DBMS_OUTPUT: before the if -short-$/)
64
+ @buffer.string.should match(/^DBMS_OUTPUT: it is 5 or shorter$/)
65
+ @buffer.string.should match(/^DBMS_OUTPUT: about to return: 0$/)
66
+ end
67
+ end
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ if ActiveRecord::Base.instance_methods.include?('changed?')
4
+
5
+ describe "OracleEnhancedAdapter dirty object tracking" do
6
+
7
+ before(:all) do
8
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
9
+ @conn = ActiveRecord::Base.connection
10
+ @conn.execute <<-SQL
11
+ CREATE TABLE test_employees (
12
+ id NUMBER,
13
+ first_name VARCHAR2(20),
14
+ last_name VARCHAR2(25),
15
+ job_id NUMBER(6,0) NULL,
16
+ salary NUMBER(8,2),
17
+ comments CLOB,
18
+ hire_date DATE
19
+ )
20
+ SQL
21
+ @conn.execute <<-SQL
22
+ CREATE SEQUENCE test_employees_seq MINVALUE 1
23
+ INCREMENT BY 1 CACHE 20 NOORDER NOCYCLE
24
+ SQL
25
+ class TestEmployee < ActiveRecord::Base
26
+ end
27
+ end
28
+
29
+ after(:all) do
30
+ Object.send(:remove_const, "TestEmployee")
31
+ @conn.execute "DROP TABLE test_employees"
32
+ @conn.execute "DROP SEQUENCE test_employees_seq"
33
+ end
34
+
35
+ it "should not mark empty string (stored as NULL) as changed when reassigning it" do
36
+ @employee = TestEmployee.create!(:first_name => '')
37
+ @employee.first_name = ''
38
+ @employee.should_not be_changed
39
+ @employee.reload
40
+ @employee.first_name = ''
41
+ @employee.should_not be_changed
42
+ end
43
+
44
+ it "should not mark empty integer (stored as NULL) as changed when reassigning it" do
45
+ @employee = TestEmployee.create!(:job_id => '')
46
+ @employee.job_id = ''
47
+ @employee.should_not be_changed
48
+ @employee.reload
49
+ @employee.job_id = ''
50
+ @employee.should_not be_changed
51
+ end
52
+
53
+ it "should not mark empty decimal (stored as NULL) as changed when reassigning it" do
54
+ @employee = TestEmployee.create!(:salary => '')
55
+ @employee.salary = ''
56
+ @employee.should_not be_changed
57
+ @employee.reload
58
+ @employee.salary = ''
59
+ @employee.should_not be_changed
60
+ end
61
+
62
+ it "should not mark empty text (stored as NULL) as changed when reassigning it" do
63
+ @employee = TestEmployee.create!(:comments => '')
64
+ @employee.comments = ''
65
+ @employee.should_not be_changed
66
+ @employee.reload
67
+ @employee.comments = ''
68
+ @employee.should_not be_changed
69
+ end
70
+
71
+ it "should not mark empty date (stored as NULL) as changed when reassigning it" do
72
+ @employee = TestEmployee.create!(:hire_date => '')
73
+ @employee.hire_date = ''
74
+ @employee.should_not be_changed
75
+ @employee.reload
76
+ @employee.hire_date = ''
77
+ @employee.should_not be_changed
78
+ end
79
+
80
+ it "should not mark integer as changed when reassigning it" do
81
+ @employee = TestEmployee.new
82
+ @employee.job_id = 0
83
+ @employee.save!.should be_true
84
+
85
+ @employee.should_not be_changed
86
+
87
+ @employee.job_id = '0'
88
+ @employee.should_not be_changed
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "OracleEnhancedAdapter emulate OracleAdapter" do
4
+
5
+ before(:all) do
6
+ if defined?(ActiveRecord::ConnectionAdapters::OracleAdapter)
7
+ @old_oracle_adapter = ActiveRecord::ConnectionAdapters::OracleAdapter
8
+ ActiveRecord::ConnectionAdapters.send(:remove_const, :OracleAdapter)
9
+ end
10
+ end
11
+
12
+ it "should be an OracleAdapter" do
13
+ @conn = ActiveRecord::Base.establish_connection(CONNECTION_PARAMS.merge(:emulate_oracle_adapter => true))
14
+ ActiveRecord::Base.connection.should_not be_nil
15
+ ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters::OracleAdapter).should be_true
16
+ end
17
+
18
+ after(:all) do
19
+ if @old_oracle_adapter
20
+ ActiveRecord::ConnectionAdapters.send(:remove_const, :OracleAdapter)
21
+ ActiveRecord::ConnectionAdapters::OracleAdapter = @old_oracle_adapter
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,370 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "OracleEnhancedAdapter custom methods for create, update and destroy" do
4
+ include LoggerSpecHelper
5
+
6
+ before(:all) do
7
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
8
+ @conn = ActiveRecord::Base.connection
9
+ plsql.activerecord_class = ActiveRecord::Base
10
+ @conn.execute("DROP TABLE test_employees") rescue nil
11
+ @conn.execute <<-SQL
12
+ CREATE TABLE test_employees (
13
+ employee_id NUMBER(6,0),
14
+ first_name VARCHAR2(20),
15
+ last_name VARCHAR2(25),
16
+ hire_date DATE,
17
+ salary NUMBER(8,2),
18
+ description CLOB,
19
+ version NUMBER(15,0),
20
+ create_time DATE,
21
+ update_time DATE,
22
+ created_at DATE,
23
+ updated_at DATE
24
+ )
25
+ SQL
26
+ @conn.execute("DROP SEQUENCE test_employees_s") rescue nil
27
+ @conn.execute <<-SQL
28
+ CREATE SEQUENCE test_employees_s MINVALUE 1
29
+ INCREMENT BY 1 CACHE 20 NOORDER NOCYCLE
30
+ SQL
31
+ @conn.execute <<-SQL
32
+ CREATE OR REPLACE PACKAGE test_employees_pkg IS
33
+ PROCEDURE create_employee(
34
+ p_first_name VARCHAR2,
35
+ p_last_name VARCHAR2,
36
+ p_hire_date DATE,
37
+ p_salary NUMBER,
38
+ p_description VARCHAR2,
39
+ p_employee_id OUT NUMBER);
40
+ PROCEDURE update_employee(
41
+ p_employee_id NUMBER,
42
+ p_first_name VARCHAR2,
43
+ p_last_name VARCHAR2,
44
+ p_hire_date DATE,
45
+ p_salary NUMBER,
46
+ p_description VARCHAR2);
47
+ PROCEDURE delete_employee(
48
+ p_employee_id NUMBER);
49
+ END;
50
+ SQL
51
+ @conn.execute <<-SQL
52
+ CREATE OR REPLACE PACKAGE BODY test_employees_pkg IS
53
+ PROCEDURE create_employee(
54
+ p_first_name VARCHAR2,
55
+ p_last_name VARCHAR2,
56
+ p_hire_date DATE,
57
+ p_salary NUMBER,
58
+ p_description VARCHAR2,
59
+ p_employee_id OUT NUMBER)
60
+ IS
61
+ BEGIN
62
+ SELECT test_employees_s.NEXTVAL INTO p_employee_id FROM dual;
63
+ INSERT INTO test_employees (employee_id, first_name, last_name, hire_date, salary, description,
64
+ version, create_time, update_time)
65
+ VALUES (p_employee_id, p_first_name, p_last_name, p_hire_date, p_salary, p_description,
66
+ 1, SYSDATE, SYSDATE);
67
+ END create_employee;
68
+
69
+ PROCEDURE update_employee(
70
+ p_employee_id NUMBER,
71
+ p_first_name VARCHAR2,
72
+ p_last_name VARCHAR2,
73
+ p_hire_date DATE,
74
+ p_salary NUMBER,
75
+ p_description VARCHAR2)
76
+ IS
77
+ v_version NUMBER;
78
+ BEGIN
79
+ SELECT version INTO v_version FROM test_employees WHERE employee_id = p_employee_id FOR UPDATE;
80
+ UPDATE test_employees
81
+ SET employee_id = p_employee_id, first_name = p_first_name, last_name = p_last_name,
82
+ hire_date = p_hire_date, salary = p_salary, description = p_description,
83
+ version = v_version + 1, update_time = SYSDATE;
84
+ END update_employee;
85
+
86
+ PROCEDURE delete_employee(
87
+ p_employee_id NUMBER)
88
+ IS
89
+ BEGIN
90
+ DELETE FROM test_employees WHERE employee_id = p_employee_id;
91
+ END delete_employee;
92
+ END;
93
+ SQL
94
+
95
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
96
+
97
+ class ::TestEmployee < ActiveRecord::Base
98
+ set_primary_key :employee_id
99
+
100
+ validates_presence_of :first_name, :last_name, :hire_date
101
+
102
+ # should return ID of new record
103
+ set_create_method do
104
+ plsql.test_employees_pkg.create_employee(
105
+ :p_first_name => first_name,
106
+ :p_last_name => last_name,
107
+ :p_hire_date => hire_date,
108
+ :p_salary => salary,
109
+ :p_description => "#{first_name} #{last_name}",
110
+ :p_employee_id => nil
111
+ )[:p_employee_id]
112
+ end
113
+
114
+ # return value is ignored
115
+ set_update_method do
116
+ plsql.test_employees_pkg.update_employee(
117
+ :p_employee_id => id,
118
+ :p_first_name => first_name,
119
+ :p_last_name => last_name,
120
+ :p_hire_date => hire_date,
121
+ :p_salary => salary,
122
+ :p_description => "#{first_name} #{last_name}"
123
+ )
124
+ end
125
+
126
+ # return value is ignored
127
+ set_delete_method do
128
+ plsql.test_employees_pkg.delete_employee(
129
+ :p_employee_id => id
130
+ )
131
+ end
132
+
133
+ end
134
+ end
135
+
136
+ after(:all) do
137
+ Object.send(:remove_const, "TestEmployee")
138
+ @conn = ActiveRecord::Base.connection
139
+ @conn.execute "DROP TABLE test_employees"
140
+ @conn.execute "DROP SEQUENCE test_employees_s"
141
+ @conn.execute "DROP PACKAGE test_employees_pkg"
142
+ end
143
+
144
+ before(:each) do
145
+ @today = Date.new(2008,6,28)
146
+ @buffer = StringIO.new
147
+ end
148
+
149
+ it "should create record" do
150
+ @employee = TestEmployee.create(
151
+ :first_name => "First",
152
+ :last_name => "Last",
153
+ :hire_date => @today
154
+ )
155
+ @employee.reload
156
+ @employee.first_name.should == "First"
157
+ @employee.last_name.should == "Last"
158
+ @employee.hire_date.should == @today
159
+ @employee.description.should == "First Last"
160
+ @employee.create_time.should_not be_nil
161
+ @employee.update_time.should_not be_nil
162
+ end
163
+
164
+ it "should rollback record when exception is raised in after_create callback" do
165
+ @employee = TestEmployee.new(
166
+ :first_name => "First",
167
+ :last_name => "Last",
168
+ :hire_date => @today
169
+ )
170
+ TestEmployee.class_eval { def after_create() raise "Make the transaction rollback" end }
171
+ begin
172
+ employees_count = TestEmployee.count
173
+ lambda {
174
+ @employee.save
175
+ }.should raise_error("Make the transaction rollback")
176
+ @employee.id.should == nil
177
+ TestEmployee.count.should == employees_count
178
+ ensure
179
+ TestEmployee.class_eval { remove_method :after_create }
180
+ end
181
+ end
182
+
183
+ it "should update record" do
184
+ @employee = TestEmployee.create(
185
+ :first_name => "First",
186
+ :last_name => "Last",
187
+ :hire_date => @today,
188
+ :description => "description"
189
+ )
190
+ @employee.reload
191
+ @employee.first_name = "Second"
192
+ @employee.save!
193
+ @employee.reload
194
+ @employee.description.should == "Second Last"
195
+ end
196
+
197
+ it "should rollback record when exception is raised in after_update callback" do
198
+ TestEmployee.class_eval { def after_update() raise "Make the transaction rollback" end }
199
+ begin
200
+ @employee = TestEmployee.create(
201
+ :first_name => "First",
202
+ :last_name => "Last",
203
+ :hire_date => @today,
204
+ :description => "description"
205
+ )
206
+ empl_id = @employee.id
207
+ @employee.reload
208
+ @employee.first_name = "Second"
209
+ lambda {
210
+ @employee.save
211
+ }.should raise_error("Make the transaction rollback")
212
+ @employee.reload
213
+ @employee.first_name.should == "First"
214
+ ensure
215
+ TestEmployee.class_eval { remove_method :after_update }
216
+ end
217
+ end
218
+
219
+ it "should not update record if nothing is changed and partial updates are enabled" do
220
+ return pending("Not in this ActiveRecord version") unless TestEmployee.respond_to?(:partial_updates=)
221
+ TestEmployee.partial_updates = true
222
+ @employee = TestEmployee.create(
223
+ :first_name => "First",
224
+ :last_name => "Last",
225
+ :hire_date => @today
226
+ )
227
+ @employee.reload
228
+ @employee.save!
229
+ @employee.reload
230
+ @employee.version.should == 1
231
+ end
232
+
233
+ it "should update record if nothing is changed and partial updates are disabled" do
234
+ return pending("Not in this ActiveRecord version") unless TestEmployee.respond_to?(:partial_updates=)
235
+ TestEmployee.partial_updates = false
236
+ @employee = TestEmployee.create(
237
+ :first_name => "First",
238
+ :last_name => "Last",
239
+ :hire_date => @today
240
+ )
241
+ @employee.reload
242
+ @employee.save!
243
+ @employee.reload
244
+ @employee.version.should == 2
245
+ end
246
+
247
+ it "should delete record" do
248
+ @employee = TestEmployee.create(
249
+ :first_name => "First",
250
+ :last_name => "Last",
251
+ :hire_date => @today
252
+ )
253
+ @employee.reload
254
+ empl_id = @employee.id
255
+ @employee.destroy
256
+ @employee.should be_frozen
257
+ TestEmployee.find_by_employee_id(empl_id).should be_nil
258
+ end
259
+
260
+ it "should delete record and set destroyed flag" do
261
+ return pending("Not in this ActiveRecord version (requires >= 2.3.5)") unless TestEmployee.method_defined?(:destroyed?)
262
+ @employee = TestEmployee.create(
263
+ :first_name => "First",
264
+ :last_name => "Last",
265
+ :hire_date => @today
266
+ )
267
+ @employee.reload
268
+ @employee.destroy
269
+ @employee.should be_destroyed
270
+ end
271
+
272
+ it "should rollback record when exception is raised in after_desotry callback" do
273
+ TestEmployee.class_eval { def after_destroy() raise "Make the transaction rollback" end }
274
+ begin
275
+ @employee = TestEmployee.create(
276
+ :first_name => "First",
277
+ :last_name => "Last",
278
+ :hire_date => @today
279
+ )
280
+ @employee.reload
281
+ empl_id = @employee.id
282
+ lambda {
283
+ @employee.destroy
284
+ }.should raise_error("Make the transaction rollback")
285
+ @employee.id.should == empl_id
286
+ TestEmployee.find_by_employee_id(empl_id).should_not be_nil
287
+ ensure
288
+ TestEmployee.class_eval { remove_method :after_destroy }
289
+ end
290
+ end
291
+
292
+ it "should set timestamps when creating record" do
293
+ @employee = TestEmployee.create(
294
+ :first_name => "First",
295
+ :last_name => "Last",
296
+ :hire_date => @today
297
+ )
298
+ @employee.created_at.should_not be_nil
299
+ @employee.updated_at.should_not be_nil
300
+ end
301
+
302
+ it "should set timestamps when updating record" do
303
+ @employee = TestEmployee.create(
304
+ :first_name => "First",
305
+ :last_name => "Last",
306
+ :hire_date => @today
307
+ )
308
+ @employee.reload
309
+ @employee.created_at.should be_nil
310
+ @employee.updated_at.should be_nil
311
+ @employee.first_name = "Second"
312
+ @employee.save!
313
+ @employee.created_at.should be_nil
314
+ @employee.updated_at.should_not be_nil
315
+ end
316
+
317
+ it "should log create record" do
318
+ log_to @buffer
319
+ @employee = TestEmployee.create(
320
+ :first_name => "First",
321
+ :last_name => "Last",
322
+ :hire_date => @today
323
+ )
324
+ @buffer.string.should match(/^TestEmployee Create \(\d+\.\d+(ms)?\) custom create method$/)
325
+ end
326
+
327
+ it "should log update record" do
328
+ (TestEmployee.partial_updates = false) rescue nil
329
+ @employee = TestEmployee.create(
330
+ :first_name => "First",
331
+ :last_name => "Last",
332
+ :hire_date => @today
333
+ )
334
+ log_to @buffer
335
+ @employee.save!
336
+ @buffer.string.should match(/^TestEmployee Update \(\d+\.\d+(ms)?\) custom update method with employee_id=#{@employee.id}$/)
337
+ end
338
+
339
+ it "should log delete record" do
340
+ @employee = TestEmployee.create(
341
+ :first_name => "First",
342
+ :last_name => "Last",
343
+ :hire_date => @today
344
+ )
345
+ log_to @buffer
346
+ @employee.destroy
347
+ @buffer.string.should match(/^TestEmployee Destroy \(\d+\.\d+(ms)?\) custom delete method with employee_id=#{@employee.id}$/)
348
+ end
349
+
350
+ it "should validate new record before creation" do
351
+ @employee = TestEmployee.new(
352
+ :last_name => "Last",
353
+ :hire_date => @today
354
+ )
355
+ @employee.save.should be_false
356
+ @employee.errors.on(:first_name).should_not be_nil
357
+ end
358
+
359
+ it "should validate existing record before update" do
360
+ @employee = TestEmployee.create(
361
+ :first_name => "First",
362
+ :last_name => "Last",
363
+ :hire_date => @today
364
+ )
365
+ @employee.first_name = nil
366
+ @employee.save.should be_false
367
+ @employee.errors.on(:first_name).should_not be_nil
368
+ end
369
+
370
+ end