How should I set up my DBIx::Class result classes in this simple case?
Asked Answered
M

1

7

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?

Mehalek answered 18/2, 2010 at 18:7 Comment(3)
A new release of DBIx::Class::Schema::Loader that handles your sample schema should be posted within the next day. As you work on the real thing it would really be great if you could continue testing Schema::Loader against it and report any bugs you find (or supply failing tests), as there's still plenty of room for improvement in Schema::Loader's SQLite support.Caporal
Schema::Loader 0.05003 which fixes this bug is uploaded: pause.perl.org:81/incoming/…Spurrier
@Rafael Thanks for the heads up. I downloaded it. Will check it out this weekend.Pronunciamento
C
6

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"
);
Caporal answered 18/2, 2010 at 19:4 Comment(5)
It writes those too, based on your foreign key constraints and linking tables.Caporal
For some reason, I am not able to get that either using your command line or with dbicdump using a SQLite database.Pronunciamento
@Sinan: It's due to a bug in Schema::Loader -- see above.Caporal
Also a note, the LEFT JOIN annotation at one point there is because your event_creator is nullable. Not sure if you meant that. :)Caporal
Thank you very much. And, no, I had not meant event_creator to be nullable. Missed it when I was simplifying. Good catch.Pronunciamento

© 2022 - 2024 — McMap. All rights reserved.