STI and Fixtures


#1

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 P.
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 P.’, ‘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?


#2

On May 11, 6:06 pm, Phoenix R. removed_email_address@domain.invalid wrote:

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?

Fixtures are a bit odd - they’re more table based than class based
(sortof - hence the odd). If you stick everything in reports.yml you
should be ok.

Fred


#3

Frederick C. wrote:
[…]

Fixtures are a bit odd - they’re more table based than class based
(sortof - hence the odd). If you stick everything in reports.yml you
should be ok.

Or just use factories. At least with Machinist, they seem to work
directly with the class structure. I’ve never used STI with Machinist,
but I’d be very surprised, given Machinist’s architecture, if it
didn’t work as expected, without the gotchas of fixtures.

Best,

Marnen Laibow-Koser
http://www.marnen.org
removed_email_address@domain.invalid