views:

288

answers:

1

Let's suppose I have a the following simplified example database consisting of three tables:

CREATE TABLE people (
    person_id   INTEGER PRIMARY KEY,
    person_name VARCHAR(100)
);

CREATE TABLE events (
    event_id       INTEGER PRIMARY KEY,
    event_name     VARCHAR(100),
    event_creator  INTEGER
                   CONSTRAINT fk_event_creator REFERENCES people(person_id)
);

CREATE TABLE event_attendees (
    event_id  INTEGER NOT NULL
              CONSTRAINT fk_event_attendee_event
              REFERENCES events(event_id),
    person_id INTEGER NOT NULL
              CONSTRAINT fk_event_attendee_person
              REFERENCES people(person_id),
    role      CHAR(1), -- O: organizer, P: performer, S: speaker, G: guest
    CONSTRAINT pk_event_attendees PRIMARY KEY (event_id, person_id)
);

Given an event_id, I might want to query for the names of all organizers, given a person_id I might want to find names of all events where this person is a guest or creator of the event so on and so forth.

I know how to do all that using simple SQL. Could you tell me which result classes I need to set up and what kinds of relationships I need to specify when using DBIx::Class?

+4  A: 

Are you familiar with DBIx::Class::Schema::Loader? Although it can be used in one-off scripts to create a DBIC schema dynamically in memory, it also has the ability to run in a "one-shot" mode where it writes the schema definitions to disk for you to edit and build on, and it's way more advanced than you might think.

First, you want to have the schema actually present in a database, so that the loader can scan it. Then you do something like:

perl -MDBIx::Class::Schema::Loader=make_schema_at \
-e 'make_schema_at("MyApp::Schema", {dump_directory=>"schema_out"},' \
-e '["dbi:DBType:connstring", "user", "pass"]);'

(where "MyApp::Schema" is the package name you want the generated schema classes to share, and "schema_out" is the directory you want them to be generated in).

After this, you can either edit the generated schema classes, or, if you find that the loader does a good enough job (or at least a good enough job that you don't need to edit anything above the "DON'T EDIT ABOVE THIS LINE" line), you can decide that the schema in the DB is your primary source, and save the Schema::Loader script to be run again to re-generate the classes automatically if the DB changes.

Update

Parts of the above schema don't get processed correctly with DBIx::Class::Schema::Loader v0.05002 because Sinan managed to find a bug! Foreign key constraints weren't parsed correctly if the "references" part and the column name weren't on the same line.

The bug is fixed in DBICSL git, but since the fix isn't released yet, here's what the relations should look like (I'm leaving out the column definitions to save space; they should be just as the loader currently generates them).

EventAttendee.pm

__PACKAGE__->set_primary_key(qw(event_id person_id));

__PACKAGE__->belongs_to(
    "event" => "MyApp::Schema::Result::Event",
    { event_id => "event_id" },
    {}
);

__PACKAGE__->belongs_to(
    "person" => "MyApp::Schema::Result::Person",
    { person_id => "person_id" },
    {}
);

Event.pm

__PACKAGE__->set_primary_key("event_id");

__PACKAGE__->belongs_to(
    "event_creator" => "MyApp::Schema::Result::Person",
    { person_id => "event_creator" },
    { join_type => "LEFT" },
);

__PACKAGE__->has_many(
    "event_attendees" => "MyApp::Schema::Result::EventAttendee",
    { "foreign.event_id" => "self.event_id" },
);

# Not auto-generated, but you probably want to add it :)
__PACKAGE__->many_to_many(
    "people_attending" => "event_attendees" => "person"
);

People.pm

__PACKAGE__->has_many(
    # It might be wise to change this to "events_created"
    "events" => "MyApp::Schema::Result::Event",
    { "foreign.event_creator" => "self.person_id" },
);

__PACKAGE__->has_many(
    "event_attendees" => "MyApp::Schema::Result::EventAttendee",
    { "foreign.person_id" => "self.person_id" },
);

# Not auto-generated, but you probably want to add it :)
__PACKAGE__->many_to_many(
    "events_attending" => "event_attendees" => "event"
);
hobbs
@hobbs That is a useful module but it just alleviates the tedium out of writing column names and attributes. I am trying to figure out the exact number and nature of the `has_many` `might_have` etc calls I need to write.
Sinan Ünür
It writes those too, based on your foreign key constraints and linking tables.
hobbs
@hobbs For some reason, I am not able to get that either using your command line or with `dbicdump` using a SQLite database.
Sinan Ünür
@Sinan: It's due to a bug in Schema::Loader -- see above.
hobbs
Also a note, the LEFT JOIN annotation at one point there is because your `event_creator` is nullable. Not sure if you meant that. :)
hobbs
@hobbs Thank you very much. And, no, I had not meant `event_creator` to be nullable. Missed it when I was simplifying. Good catch.
Sinan Ünür