In my application I have two very similar types of objects: Bugs and
Stories. They share almost 95% of the same columns. (Bugs have 1 field
out of 20 that are not appropriate for stories; stories have 2 that
are not appropriate for bugs.) They both reference the same three
many_to_one parent tables, and reference four other many_to_one
enumeration tables that are quite similar. (For example, a Bug's
status goes through A/B/C/D/E, where a Story goes through statuses A'/
B'/C/D.) Bugs and Stories both have Comments and LogEntries and
Attachments; many_to_one relationships to a parent Bug or Story. Both
share in the same many_to_many relationship with tags, but Bugs have
one many_to_many relationship not appropriate for Stories, and Stories
have another many_to_one relationship with Tasks not shared by bugs.

In addition to all these data similarities, they have functional
similarities. Again, they share a large number of the same methods,
with Bugs adding a few custom calculation methods not appropriate for
stories, and vice versa.

Up until yesterday, I decided to model these with wholly separate
tables: bugs, bug_statuses, bug_comments, bug_log_entries, bugs_tags,
bug_attachments, ..., stories, story_statuses, story_comments,
story_log_entries, stories_tags, story_attachments, ..., etc. There
were about 30 tables and Sequel Models duplicated almost entirely, 15
of each, with the schema mostly identical and the content of the
enumerations almost identical.

Even modules used to factor out common code, the amount of code
duplication was horrific.

The clincher is that I really need the PKs of Bugs and Stories not to
collide.

Today, in a bout of frustration, I wrote a huge migration to merge and
delete half the tables. I went with a pseudo-STI implementation (not
using the Sequel plug-in that Jeremy just told me about). I made Bugs
and Stories both "ChangeRequests", with code like:

  class ChangeRequest < Sequel::Model
    # lots of shared methods
  end
  class Bug < ChangeRequest
    set_dataset DB[:change_requests].filter(cr_type:CRType::BUG)
    # custom bug methods
  end

But it's not feeling cleaner. Instead of bug_statuses and
story_statuses I have just 'statuses', with a field flagging the type
of change request the status applies to and duplicate enumeration
values. I have something like that in 4 spots. My table for
notification types has 95% duplication because most notifications
apply to both types.

I've just run into a situation where I'm trying to find all the
LogEntries for a particular day, and when iterating over them treat
some as bugs and some as stories. This fails, though, as it returns
only ChangeRequest objects in the results.
  class LogEntry < Sequel::Model
    many_to_one :change_request
  end
  LogEntry.filter( created_on: range ).all.each do |item|
    # Boy, I wish item was sometimes a Bug and sometimes a Story
  end

For those of you for whom this wasn't TL;DR, here's my question:

What would you go with? STI feels like it will DRY up some of my
application logic, but practice shows I need plenty of messy "if
item.is_a? Bug ... else ..." sprinkled throughout. The two-worlds
scenario feels cleaner (I get absolute referential integrity with
simple FK constraints) but requires industrial-grade bulldozers to
move all the code around.

I'd hate to think that I just wasted a day on a fool's errand, but if
I'm so on the fence that if someone says "You want to duplicate
everything and DRY up your code and views with modules" that I'm quite
liable to revert the last commit.

Thoughts?

-- 
You received this message because you are subscribed to the Google Groups 
"sequel-talk" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/sequel-talk?hl=en.

Reply via email to