I''m trying to implement a TDD design pattern in Rails for the first
time for a system maintenance reporting application, and running into
issues with STI (also a first-time user) and fixtures causing my tests
to throw errors.
class Report < ActiveRecord::Base
validates_presence_of :subject, :author, :category_id
validates_inclusion_of :status, :in => [true, false]
validates_inclusion_of :type, :in => ["MaintenanceReport",
"OutageReport"]
end
"Report" is the parent class I''m implementing. Each report -
be it a
maintenance report or an outage report - has the following:
(This is also my first fixture: reports.yml)
----------------------------------------
sample_report:
type: MaintenanceReport
subject: Sample Maintenance Report
affected_systems: "System 1, System 2"
author: Some Author
status: false
category_id: 1
misc_notes: "This is a test. Disregard."
finish_time: <%= Time.now %>
est_resolution_time: 3 days
By design, every report should be either a MaintenanceReport or an
OutageReport, meaning that if I simply strike "type" from my fixture
above, the test to make sure it''s one of those will fail.
My MaintenanceReport model:
------------------------------------------
class MaintenanceReport < Report
validates_presence_of :risks, :backout_plan, :description, :work_by,
:scheduled_start_time
end
Now, here''s where the rubber meets the road:
When running the actual tests, they all fail because it''s trying to
insert values into columns that don''t exist. For example:
ActiveRecord::StatementInvalid: Mysql::Error: Unknown column
''est_resolution_time'' in ''field list'':
INSERT INTO
`maintenance_reports` (`est_resolution_time`, [...snip]
It''s inserting into maintenance_reports when it should insert into
reports for this specific test (because it''s in reports.yml, correct?)
For the curious, that specific test is:
---------------
(report_test.rb)
def test_report_validations
report = Report.new
# Report shouldn''t save because it has no data for validations
assert !report.save
# Give it a type
report.type = "MaintenanceReport"
assert !report.save
# Subject
report.subject = "Test Report"
assert !report.save
# Author
report.author = "Some Author"
assert !report.save
# Status
report.status = false
assert !report.save
# Category ID
report.category_id = 1
# all required methods are now supplied, should save
assert report.save
end
------------------------
As you can see here, every report MUST have a type, so that''s set, and
it MUST be either MaintenanceReport or OutageReport (also set); this
is supposed to test only the Report class unto itself before any
testing of the MaintenanceReport class and its specific methods occurs
(logically speaking, it shouldn''t be necessary to test the same
methods in both classes).
The reverse is also an issue:
----------------------------------------
(fixtures/maintenance_reports.yml)
good_maintenance_report:
type: MaintenanceReport
subject: Sample Maintenance Report
affected_systems: "System 1, System 2"
author: Some Author
status: false
category_id: 1
misc_notes: "This is a test. Disregard."
finish_time: <%= Time.now %>
est_resolution_time: 3 days
risks: "This test might fail!"
backout_plan: "None - fix the problem and make the tests pass"
description: "test description"
work_by: Some Person
scheduled_start_time: <%= Time.now %>
---
(unit/maintenance_report_test.rb)
[snip]
def test_create_validate_save
report = maintenance_reports(:good_maintenance_report)
# By default this report is complete, so it should save.
assert report.save
# Remove a validated attribute
report.risks = nil
assert !report.save
report = maintenance_reports(:good_maintenance_report) # Reset
# Delete backout plan, test, reset
report.backout_plan = nil
assert !report.save
report = maintenance_reports(:good_maintenance_report)
# Delete description, test, reset
report.description = nil
assert !report.save
report = maintenance_reports(:good_maintenance_report)
# Delete work by, test, reset
report.work_by = nil
assert !report.save
report = maintenance_reports(:good_maintenance_report)
# Delete scheduled start time, test, reset
report.scheduled_start_time = nil
assert !report.save
report = maintenance_reports(:good_maintenance_report)
# Test save again
assert report.save
end
--------
1) Error:
test_create_validate_save(MaintenanceReportTest):
ActiveRecord::StatementInvalid: Mysql::Error: Unknown column
''est_resolution_time'' in ''field list'':
INSERT INTO
`maintenance_reports` (`est_resolution_time`, `created_at`,
`backout_plan`, `author`, `updated_at`, `affected_systems`, `id`,
`work_by`, `subject`, `risks`, `category_id`, `type`, `finish_time`,
`misc_notes`, `description`, `status`, `scheduled_start_time`) VALUES
(''3 days'', ''2009-05-11 16:59:47'',
''None - fix the problem and make the
tests pass'', ''Some Author'', ''2009-05-11
16:59:47'', ''System 1, System
2'', 525138997, ''Some Person'', ''Sample
Maintenance Report'', ''This test
might fail!'', 1, ''MaintenanceReport'', ''Mon
May 11 10:59:47 -0600
2009'', ''This is a test. Disregard.'', ''test
description'', 0, ''Mon May
11 10:59:47 -0600 2009'')
-------
So the obvious next question is, "what''s your database look
like?"
answer:
class CreateReports < ActiveRecord::Migration
def self.up
create_table :reports do |t|
t.string :type
t.string :subject
t.text :affected_systems
t.string :author
t.string :closer
t.boolean :status
t.integer :category_id
t.text :misc_notes
t.datetime :finish_time
t.string :est_resolution_time
t.timestamps
end
end
def self.down
drop_table :reports
end
end
class CreateMaintenanceReports < ActiveRecord::Migration
def self.up
create_table :maintenance_reports do |t|
t.text :risks
t.text :backout_plan
t.text :description
t.string :work_by
t.datetime :scheduled_start_time
t.timestamps
end
end
def self.down
drop_table :maintenance_reports
end
end
I''ve googled and read the API documentation for two days looking for
any examples as to how I can tell Rails that my fixtures are based off
STI. Can anyone offer any input on this?