[system] / branches / rel-2-2-dev / webwork2 / lib / WeBWorK / DBv3.pm Repository:
ViewVC logotype

View of /branches/rel-2-2-dev/webwork2/lib/WeBWorK/DBv3.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3972 - (download) (as text) (annotate)
Wed Jan 25 23:12:05 2006 UTC (7 years, 4 months ago) by sh002i
File size: 27755 byte(s)
update copyright date range -- 2000-2006. this is probably overkill,
since there are some files that were created after 2000 and some files
that were last modified before 2006.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/DBv3.pm,v 1.9 2005/01/06 21:04:40 sh002i Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::DBv3;
   18 use base 'Class::DBI';
   19 use WeBWorK::DBv3::NormalizerMixin;
   20 use Class::DBI::Plugin::AbstractCount;
   21 
   22 =head1 NAME
   23 
   24 WeBWorK::DBv3 - Class::DBI interface to WWDBv3.
   25 
   26 =head1 SYNOPSIS
   27 
   28  use WeBWorK::DBv3;
   29 
   30  my $wwdbv3_settings = {
   31   dsn          => "dbi:mysql:wwdbv3",
   32   user         => "wwdbv3",
   33   pass         => "SeCrEt",
   34   attr         => {  }, # optional
   35   upgrade_lock => "/path/to/wwdbv3_upgrade.lock", # prevent concurrent schema upgrades
   36  };
   37 
   38  WeBWorK::DBv3::init($wwdbv3_settings);
   39 
   40  # --- any time after init() as been called... ---
   41 
   42  my $course = WeBWorK::DBv3::Course->find({name => "Sam's course"})
   43   or die "course not found!";
   44 
   45  my @participants = $course->participants;
   46 
   47  my $participant = WeBWorK::DBv3::Participant->find({login_name => "sam.hathaway"});
   48  $participant->first_name("Sam");
   49  $participant->last_name("Hathaway");
   50  $participant->update;
   51 
   52  my @set_assignments = $participant->set_assignments;
   53 
   54 =head1 DESCRIPTION
   55 
   56 WeBWorK::DBv3 provides a Class::DBI-based interface to the third-generation
   57 WeBWorK database. (WeBWorK::DB provided an interface to the second-generation
   58 database, the first-generation database was that used by WeBWorK 1.x.)
   59 
   60 The database schema is described at
   61 <http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/DatabaseSchemaV3>.
   62 
   63 WeBWorK::DBv3 supports automatic schema upgrades by checking the value of the
   64 C<db_version> record in the C<setting> table and applying SQL deltas to the
   65 database.
   66 
   67 =cut
   68 
   69 use strict;
   70 use warnings;
   71 use vars qw/$dbh $dt_format/;
   72 use Data::Dumper;
   73 use DateTime::Format::DBI;
   74 use WeBWorK::DBv3::Utils;
   75 use WeBWorK::Utils qw/fisher_yates_shuffle/;
   76 
   77 =head1 INITIALIZATION
   78 
   79 The init($wwdbv3_settings) function allows the user to set up the details of the
   80 database connection at runtime rather than at compile time. This lets us use
   81 values from the WeBWorK::CourseEnvironment (loaded at runtime) in specifying the
   82 database connection.
   83 
   84 $wwdbv3_settings is a reference to a hash containing the following values:
   85 
   86  dsn          => The DBI data source, e.g. "dbi:mysql:wwdbv3"
   87  user         => The user name with which to connect.
   88  pass         => The password to supply to connect.
   89  attr         => A reference to a hash containing DBI attributes.
   90                  See L<DBI> for more information.
   91  upgrade_lock => Path to a file which is flock()'d while performing
   92                  database upgrades.
   93 
   94 =cut
   95 
   96 sub init {
   97   my ($wwdbv3_settings) = @_;
   98 
   99   my $dsn  = $wwdbv3_settings->{dsn};
  100   my $user = $wwdbv3_settings->{user};
  101   my $pass = $wwdbv3_settings->{pass};
  102   my %attr = (
  103     RootClass => "DBIx::ContextualFetch", # this is supposedly important to Class::DBI
  104     RaiseError => 1, # we don't want to have to test return values
  105     %{ $wwdbv3_settings->{attr} }, # allow user-specified attributes to override
  106   );
  107 
  108   $dbh = DBI->connect_cached($dsn, $user, $pass, \%attr);
  109 
  110   $dt_format = new DateTime::Format::DBI($dbh);
  111 
  112   my $lockfile = $wwdbv3_settings->{upgrade_lock};
  113   upgrade_schema($dbh, $lockfile);
  114 }
  115 
  116 # override db_Main to get database handle initialized in init() above. note that
  117 # Class::DBI->connection() is never called.
  118 sub db_Main {
  119   return $dbh;
  120 }
  121 
  122 ################################################################################
  123 
  124 =head1 PUBLIC CLASS::DBI EXTENSIONS
  125 
  126 WeBWorK::DBv3 extends Class::DBI to provide several features useful to users to
  127 the WWDBv3 system.
  128 
  129 =head2 Table locking
  130 
  131 When using a table type that doesn't support transactions, we need to be able to
  132 do table-level locks. The currently implementations are from
  133 Class::DBI::Extension and are MySQL-specific.
  134 
  135 =over
  136 
  137 =item lock_table()
  138 
  139 Write-lock the current table.
  140 
  141 =cut
  142 
  143 __PACKAGE__->set_sql(LockTable => "LOCK TABLES %s WRITE");
  144 
  145 sub lock_table {
  146     my $class = shift;
  147     $class->sql_LockTable($class->table)->execute;
  148 }
  149 
  150 =item unlock_table()
  151 
  152 Unlock I<all> locked tables.
  153 
  154 =cut
  155 
  156 __PACKAGE__->set_sql(UnlockTable => "UNLOCK TABLES");
  157 
  158 sub unlock_table {
  159     my $class = shift;
  160     $class->sql_UnlockTable->execute;
  161 }
  162 
  163 =back
  164 
  165 =cut
  166 
  167 ################################################################################
  168 
  169 =head1 INTERNAL IMPROVEMENTS
  170 
  171 WeBWorK::DBv3 extends Class::DBI to provide several features useful in the
  172 definition of table classes.
  173 
  174 =head2 DateTime support
  175 
  176 The method has_a_datetime() has been defined as a shortcut to specifying that a
  177 DATETIME column should be inflated to and deflated from a DateTime.pm object.
  178 
  179  __PACKAGE__->has_a_datetime("open_date");
  180 
  181 =cut
  182 
  183 sub _datetime_inflate {
  184   my $dt = $dt_format->parse_datetime($_[0]) or _croak("invalid date: '$_[0]'");
  185   return $dt->set_time_zone("UTC");
  186 }
  187 
  188 sub _datetime_deflate {
  189   my $dt = $_[0]->clone->set_time_zone("UTC"); # clone to avoid changing timezone of original object
  190   return $dt_format->format_datetime($dt);
  191 }
  192 
  193 # this declares a column to be of type DateTime and defines inflation/deflation
  194 # subroutines for it
  195 sub has_a_datetime {
  196   my ($class, $field) = @_;
  197   return unless $field;
  198 
  199   $class->has_a(
  200     $field  => "DateTime",
  201     inflate => \&_datetime_inflate,
  202     deflate => \&_datetime_deflate,
  203   );
  204 }
  205 
  206 =head2 Per-column normalization support
  207 
  208 WeBWorK::DBv3 adds per-column normalization support to Class::DBI via
  209 WeBWorK::DBv3::NormalizerMixin. The interface and implementation are similar to
  210 that of Class::DBI triggers (via Class::Trigger).
  211 
  212 To add a normalizer to a field:
  213 
  214  __PACKAGE__->add_normalizer(field => \&normalizer_sub);
  215 
  216 &normalizer_sub takes one argument, the value to be normalized. It should return
  217 the normalized value. For example:
  218 
  219  sub _bool_normalizer { $_[0] ? 1 : 0 }
  220  WeBWorK::DBv3::Course->add_normalizer(visible => \&_bool_normalizer);
  221 
  222 Like triggers, multiple normalizers can be added for a single field. However,
  223 you cannot specify the order in which they will be run.
  224 
  225 =cut
  226 
  227 sub normalize_column_values {
  228   my ($self, $column_values) = @_;
  229 
  230   my @errors;
  231 
  232   foreach my $column (keys %$column_values) {
  233     #warn "callig normalizers for column '$column'.\n";
  234     eval { $self->call_normalizer($column_values, $column) };
  235     push @errors, $column => $@ if $@;
  236   }
  237 
  238   return unless @errors;
  239   $self->_croak(
  240     "normalize_column_values error: " . join(" ", @errors),
  241     method => "normalize_column_values",
  242     data => { @errors },
  243   );
  244 }
  245 
  246 =head3 Predefined normalizers
  247 
  248 Several normalizers are conveniently predefined using a syntax similar to that
  249 of has_a() relationship declarations.
  250 
  251 =over
  252 
  253 =item has_a_boolean($field)
  254 
  255 True values will be normalized to C<1>, false values to C<0>.
  256 
  257 =cut
  258 
  259 sub _bool_normalizer { $_[0] ? 1 : 0 }
  260 sub has_a_boolean {
  261   my ($class, $field) = @_;
  262   return unless $field;
  263 
  264   $class->add_normalizer($field => \&_bool_normalizer);
  265 }
  266 
  267 =back
  268 
  269 =head2 set_creation_date()
  270 
  271 The subroutine set_creation_date() is available for use in setting the
  272 C<creation_date> in newly created records using a C<before_create> trigger.
  273 
  274  __PACKAGE__->add_trigger(before_create => \&WeBWorK::DBv3::set_creation_date);
  275 
  276 set_creation_date() uses the DateTime->now() constructor.
  277 
  278 set_creation_date() should probably not be used when defining table classes or
  279 in client code. Instead, only refer to it when adding triggers.
  280 
  281 =cut
  282 
  283 sub set_creation_date {
  284   $_[0]->creation_date(DateTime->now);
  285 }
  286 
  287 #=head2 Macros for uniqueness constraints
  288 #
  289 #has_unique_columns() allows you do define uniqueness constraints by listing the
  290 #fields that must be unique:
  291 #
  292 # __PACKAGE__->has_unique_columns($name => qw/field1 field2 field3/);
  293 #
  294 #$name gives a name to this constraint, which is included in the error message
  295 #given when the conditions of the constraint are not met.
  296 #
  297 #=cut
  298 #
  299 ## FIXME this is broken -- it doesn't allow multiple NULL values! I'd rather just
  300 ## catch the DBI uniqneness violation errors and munge them in some way to get a
  301 ## useful error message out. Is there some way to do that? Would it me MySQL
  302 ## specific?
  303 ##
  304 ## <NULL> and <NULL> - OK
  305 ## <NULL, foo> and <NULL, foo> - not OK
  306 #
  307 #sub has_unique_columns {
  308 # my ($class, $name, @columns) = @_;
  309 #
  310 # $class->_invalid_object_method('has_unique_columns()') if ref $class;
  311 # $name or $class->_croak("has_unique_columns needs a name");
  312 #
  313 # foreach my $column (@columns) {
  314 #   # normalize columns, and croak on any invalid columns
  315 #   my $normalized_column = $class->find_column($column)
  316 #     or $class->_croak("has_unique_columns: '$column' is not a valid column");
  317 #   $column = $normalized_column;
  318 # }
  319 #
  320 # # closure over @columns, $name
  321 # my $unique_columns = sub {
  322 #   my ($self) = @_;
  323 #   my %search_spec = map { $_ => $self->$_ } @columns;
  324 #   $search_spec{id} = { '!=', $self->id };
  325 #   unless ($self->count_search_where(%search_spec) == 0) {
  326 #     my $columns = join(",", @columns);
  327 #     my $values = join(",", map "'$search_spec{$_}'", @columns);
  328 #     my $fail = @columns == 1 ? "fails" : "fail";
  329 #     return $self->_croak("$class ($columns) $fail uniqueness constraint '$name' with ($values)");
  330 #   }
  331 # };
  332 #
  333 # $class->add_trigger(before_create => $unique_columns);
  334 # $class->add_trigger(before_update => $unique_columns);
  335 #}
  336 
  337 =head2 Comma-separated list handling
  338 
  339 has_a_list() allows you to define a column as containing a comma-separated list
  340 of values. It will add an accessor/modifier to the invocant's class with the
  341 suffix C<_list>.
  342 
  343 Note that this handling is pretty dumb, and cannot deal with embedded commas.
  344 This is typically OK, since list fields are usually used to store list of record
  345 IDs or strings that are valid identifiers. (If you really need to store strings
  346 with embedded commas, you may URL-encode them or whatever you like. Just make
  347 sure you decode them on the way out!)
  348 
  349  __PACKAGE__->has_a_list("problem_order");
  350 
  351  # results in this method being added to __PACKAGE__
  352  sub problem_order_list {
  353   my ($self, @list) = @_;
  354   if (@list) {
  355     return $self->problem_order(join(",", @list));
  356   } else {
  357     return split(",", $self->problem_order);
  358   }
  359  }
  360 
  361 =cut
  362 
  363 sub has_a_list {
  364   my ($class, $field) = @_;
  365   return unless $field;
  366 
  367   # closure over $field
  368   my $cs_list = sub {
  369     my ($self, @list) = @_;
  370     if (@list) {
  371       return $self->$field(join(",", @list));
  372     } else {
  373       return split(",", $self->$field);
  374     }
  375   };
  376 
  377   my $method_name = "${class}::${field}_list";
  378 
  379   no strict 'refs';
  380   *$method_name = $cs_list;
  381 }
  382 
  383 =head2 Ordered has_many() relationships
  384 
  385 has_ordering() allows you to impose a constant ordering on records returned by a
  386 has_many() accessor. The ordering can be specified by a cs_list field accessor
  387 (i.e. C<problem_order_list>). This accessor must contain a list of IDs that can
  388 be used to order the
  389 
  390  __PACKAGE__->has_many(abstract_problems => "WeBWorK::DBv3::AbstractProblem");
  391  __PACKAGE__->has_a_list("problem_order");
  392  __PACKAGE__->has_explicit_order(abstract_problems => "problem_order_list");
  393 
  394 The above example will add an accessor ordered_abstract_problems() to the class
  395 that uses the method problem_order_list() to order the records returned by the
  396 abstract_problems() accessor.
  397 
  398 The records reference by the has_many relationship and the cs_list field must be
  399 kept in sync, or a warning is emitted.
  400 
  401 =cut
  402 
  403 sub has_explicit_order {
  404   my ($class, $relation, $cs_list) = @_;
  405   return unless $relation && $cs_list;
  406 
  407   #warn "$class->has_explicit_order($relation => $cs_list)\n";
  408 
  409   my $cs_list_method = "${cs_list}_list";
  410   $class->_croak("has_explicit_order() needs a cs_list field.")
  411     unless $class->can($cs_list_method);
  412 
  413   # closure over: $class, $relation, $cs_list, $cs_list_method
  414   my $ordered = sub {
  415     my ($self, @args) = @_;
  416     my @order = $self->$cs_list_method;
  417     my %data_by_id;
  418 
  419     my $iter;
  420     # set up %data_by_ids to map IDs to data
  421     if (wantarray) {
  422       # if we're dealing with real objects, the data is objects.
  423       my @Objs = $self->$relation;
  424       my @ids = map { $_->id } @Objs;
  425       @data_by_id{@ids} = @Objs;
  426     } else {
  427       # if we're dealing with an iterator, the data is a field inside
  428       # Class::DBI::Iterator. messing with this data structure is probably not
  429       # a good idea. Instead, we should probably be creating our own
  430       # relationship type, OrderedHasMany, which would use a specified field
  431       # to order the related records.
  432       $iter = $self->$relation;
  433       my @data = @{$iter->{_data}};
  434       my @ids = map { $_->{id} } @data;
  435       @data_by_id{@ids} = @data;
  436     }
  437 
  438     # populate @sorted_data by taking the values of %data_by_id in order by @order
  439     my @sorted_data;
  440     foreach my $id (@order) {
  441       if (exists $data_by_id{$id}) {
  442         push @sorted_data, $data_by_id{$id};
  443         delete $data_by_id{$id};
  444       } else {
  445         my $this_class = ref $class || $class;
  446         my $f_class = "[foreign class]";
  447         $self->_carp("Warning: $f_class ID '$id' exists in $cs_list,"
  448           . " but it does not correspond to an $f_class of $this_class"
  449           . " '" . $self->name . "'. Probably $cs_list was not updated"
  450           . " when this problem was deleted.");
  451       }
  452     }
  453     if (my @unlisted_ids = keys %data_by_id) {
  454       my $this_class = ref $class || $class;
  455       my $f_class = "[foreign class]";
  456       $self->_carp("Warning: $this_class '" . $self->name . "' is referenced by"
  457         . " $f_class that do not appear in $cs_list. Probably"
  458         . " $cs_list was not updated when the following $f_class"
  459         . " were added: '@unlisted_ids'.");
  460     }
  461 
  462     # now dispose of the data appropriately
  463     if (wantarray) {
  464       # @sorted_data contains real objects, so we just return it here
  465       return @sorted_data;
  466     } else {
  467       # store new order for _data field into iterator, and return iterator
  468       $iter->{_data} = \@sorted_data;
  469       return $iter;
  470     }
  471   };
  472 
  473   my $method_name = "${class}::ordered_$relation";
  474 
  475   no strict 'refs';
  476   *$method_name = $ordered;
  477 }
  478 
  479 ################################################################################
  480 # Table classes: each table in the database is a subclass of WeBWorK::DBv3.
  481 # (http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/DatabaseSchemaV3)
  482 #
  483 # These are in the reverse order from the order in DatabaseSchemaV3, to ensure
  484 # that the has_a() part of a relationship occurs before the has_many() part.
  485 #
  486 # From C<Class::DBI/has_many>:
  487 #
  488 # When setting up the relationship we examine the foreign class's has_a()
  489 # declarations to discover which of its columns reference our class. (Note that
  490 # because this happens at compile time, if the foreign class is defined in the
  491 # same file, the class with the has_a() must be defined earlier than the class
  492 # with the has_many(). If the classes are in different files, Class::DBI should
  493 # be able to do the right thing).
  494 ################################################################################
  495 
  496 package WeBWorK::DBv3::ProblemAttempt;
  497 use base 'WeBWorK::DBv3';
  498 
  499 __PACKAGE__->table("problem_attempt");
  500 __PACKAGE__->columns(All => qw/id problem_version creation_date score data/);
  501 
  502 __PACKAGE__->has_a(problem_version => "WeBWorK::DBv3::ProblemVersion");
  503 __PACKAGE__->has_a_datetime("creation_date");
  504 
  505 __PACKAGE__->add_trigger(before_create => \&WeBWorK::DBv3::set_creation_date);
  506 
  507 ################################################################################
  508 
  509 package WeBWorK::DBv3::ProblemVersion;
  510 use base 'WeBWorK::DBv3';
  511 
  512 __PACKAGE__->table("problem_version");
  513 __PACKAGE__->columns(All => qw/id set_version problem_assignment creation_date
  514 source_file seed/);
  515 
  516 __PACKAGE__->has_a(set_version => "WeBWorK::DBv3::SetVersion");
  517 __PACKAGE__->has_a(problem_assignment => "WeBWorK::DBv3::ProblemAssignment");
  518 __PACKAGE__->has_a_datetime("creation_date");
  519 
  520 __PACKAGE__->has_many(problem_attempts => "WeBWorK::DBv3::ProblemAttempt");
  521 
  522 __PACKAGE__->add_trigger(before_create => &WeBWorK::DBv3::set_creation_date);
  523 
  524 ################################################################################
  525 
  526 package WeBWorK::DBv3::SetVersion;
  527 use base 'WeBWorK::DBv3';
  528 
  529 __PACKAGE__->table("set_version");
  530 __PACKAGE__->columns(All => qw/id set_assignment problem_order creation_date/);
  531 
  532 __PACKAGE__->has_a(set_assignment => "WeBWorK::DBv3::SetAssignment");
  533 __PACKAGE__->has_a_datetime("creation_date");
  534 __PACKAGE__->has_a_list("problem_order");
  535 
  536 __PACKAGE__->has_many(problem_versions => "WeBWorK::DBv3::ProblemVersion");
  537 
  538 __PACKAGE__->add_trigger(before_create => &WeBWorK::DBv3::set_creation_date);
  539 
  540 ################################################################################
  541 
  542 package WeBWorK::DBv3::ProblemOverride;
  543 use base 'WeBWorK::DBv3';
  544 
  545 __PACKAGE__->table("problem_override");
  546 __PACKAGE__->columns(All => qw/id abstract_problem section recitation
  547 participant source_type source_file source_group_set_id weight
  548 max_attempts_per_version version_creation_interval versions_per_interval
  549 version_due_date_offset version_answer_date_offset/);
  550 
  551 __PACKAGE__->has_a(abstract_problem => "WeBWorK::DBv3::AbstractProblem");
  552 __PACKAGE__->has_a(section => "WeBWorK::DBv3::Section");
  553 __PACKAGE__->has_a(recitation => "WeBWorK::DBv3::Recitation");
  554 __PACKAGE__->has_a(participant => "WeBWorK::DBv3::Participant");
  555 
  556 # FIXME need to make version_due_date_offset/version_answer_date_offset
  557 # DateTime::Offset objects
  558 
  559 ################################################################################
  560 
  561 package WeBWorK::DBv3::SetOverride;
  562 use base 'WeBWorK::DBv3';
  563 
  564 __PACKAGE__->table("set_override");
  565 __PACKAGE__->columns(All => qw/id abstract_set section recitation participant
  566 set_header hardcopy_header open_date due_date answer_date published
  567 problem_order reorder_type reorder_subset_size atomicity
  568 max_attempts_per_version version_creation_interval versions_per_interval
  569 version_due_date_offset version_answer_date_offset/);
  570 
  571 __PACKAGE__->has_a(abstract_set => "WeBWorK::DBv3::AbstractSet");
  572 __PACKAGE__->has_a(section => "WeBWorK::DBv3::Section");
  573 __PACKAGE__->has_a(recitation => "WeBWorK::DBv3::Recitation");
  574 __PACKAGE__->has_a(participant => "WeBWorK::DBv3::Participant");
  575 __PACKAGE__->has_a_datetime("open_date");
  576 __PACKAGE__->has_a_datetime("due_date");
  577 __PACKAGE__->has_a_datetime("answer_date");
  578 __PACKAGE__->has_a_list("problem_order");
  579 
  580 # FIXME need to make version_due_date_offset/version_answer_date_offset
  581 # DateTime::Offset objects
  582 
  583 ################################################################################
  584 
  585 package WeBWorK::DBv3::ProblemAssignment;
  586 use base 'WeBWorK::DBv3';
  587 
  588 __PACKAGE__->table("problem_assignment");
  589 __PACKAGE__->columns(All => qw/id set_assignment abstract_problem source_file/);
  590 
  591 __PACKAGE__->has_a(set_assignment => "WeBWorK::DBv3::SetAssignment");
  592 __PACKAGE__->has_a(abstract_problem => "WeBWorK::DBv3::AbstractProblem");
  593 
  594 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride");
  595 __PACKAGE__->has_many(problem_versions => "WeBWorK::DBv3::ProblemVersion");
  596 
  597 ################################################################################
  598 
  599 package WeBWorK::DBv3::SetAssignment;
  600 use base 'WeBWorK::DBv3';
  601 
  602 __PACKAGE__->table("set_assignment");
  603 __PACKAGE__->columns(All => qw/id abstract_set participant problem_order/);
  604 
  605 __PACKAGE__->has_a(abstract_set => "WeBWorK::DBv3::AbstractSet");
  606 __PACKAGE__->has_a(participant => "WeBWorK::DBv3::Participant");
  607 __PACKAGE__->has_a_list("problem_order");
  608 
  609 __PACKAGE__->has_many(problem_assignments => "WeBWorK::DBv3::ProblemAssignment");
  610 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride");
  611 __PACKAGE__->has_many(set_versions => "WeBWorK::DBv3::SetVersion");
  612 
  613 __PACKAGE__->has_explicit_order(problem_assignments => "problem_order");
  614 
  615 ################################################################################
  616 
  617 package WeBWorK::DBv3::AbstractProblem;
  618 use base 'WeBWorK::DBv3';
  619 
  620 __PACKAGE__->table("abstract_problem");
  621 __PACKAGE__->columns(All => qw/id abstract_set name source_type source_file
  622 source_group_set_id source_group_select_time weight max_attempts_per_version
  623 version_creation_interval versions_per_interval version_due_date_offset
  624 version_answer_date_offset/);
  625 
  626 __PACKAGE__->has_a(abstract_set => "WeBWorK::DBv3::AbstractSet");
  627 
  628 __PACKAGE__->has_many(problem_assignments => "WeBWorK::DBv3::ProblemAssignment");
  629 
  630 # FIXME need to make version_due_date_offset/version_answer_date_offset
  631 # DateTime::Offset objects
  632 
  633 ################################################################################
  634 
  635 package WeBWorK::DBv3::AbstractSet;
  636 use base 'WeBWorK::DBv3';
  637 
  638 __PACKAGE__->table("abstract_set");
  639 __PACKAGE__->columns(All => qw/id course name set_header hardcopy_header
  640 open_date due_date answer_date published problem_order reorder_type
  641 reorder_subset_size reorder_time atomicity max_attempts_per_version
  642 version_creation_interval versions_per_interval version_due_date_offset
  643 version_answer_date_offset/);
  644 
  645 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course");
  646 __PACKAGE__->has_a_datetime("open_date");
  647 __PACKAGE__->has_a_datetime("due_date");
  648 __PACKAGE__->has_a_datetime("answer_date");
  649 __PACKAGE__->has_a_boolean("published");
  650 __PACKAGE__->has_a_list("problem_order");
  651 
  652 __PACKAGE__->has_many(abstract_problems => "WeBWorK::DBv3::AbstractProblem");
  653 __PACKAGE__->has_many(set_assignments => "WeBWorK::DBv3::SetAssignment");
  654 
  655 __PACKAGE__->has_explicit_order(abstract_problems => "problem_order");
  656 
  657 # FIXME need to make version_due_date_offset/version_answer_date_offset
  658 # DateTime::Offset objects
  659 
  660 sub assign_to_participant {
  661   my ($self, $Participant) = @_;
  662 
  663   my $SetAssignment = eval {
  664     create WeBWorK::DBv3::SetAssignment({
  665       abstract_set => $self,
  666       participant => $Participant,
  667     })
  668   };
  669   $@ =~ /Duplicate entry/ and die "Abstract set '", $self->name,
  670     "' is already assigned to participant '", $Participant->user->name, "'.\n";
  671   $@ and die $@;
  672 
  673   # order in which to assign abstract problems to participant
  674   my @problem_assignment_order = $self->problem_order_list;
  675 
  676   # if it's time to reorder, we do that now
  677   if ($self->reorder_time eq "assignment") {
  678 
  679     # randomize?
  680     if ($self->reorder_type eq "none") {
  681       # leave order alone
  682     } elsif ($self->reorder_type eq "random") {
  683       # randomize
  684       fisher_yates_shuffle(\@problem_assignment_order);
  685     }
  686 
  687     # take a subset?
  688     if (defined $self->reorder_subset_size) {
  689       my $range = $self->reorder_subset_size;
  690       if ($range < @problem_assignment_order) {
  691         @problem_assignment_order = @problem_assignment_order[0 .. $range-1];
  692       }
  693     }
  694   }
  695 
  696   # order of assigned problems
  697   my @assigned_problem_order;
  698 
  699   foreach my $abs_prob_id (@problem_assignment_order) {
  700 
  701   }
  702 }
  703 
  704 ################################################################################
  705 
  706 package WeBWorK::DBv3::Participant;
  707 use base 'WeBWorK::DBv3';
  708 
  709 __PACKAGE__->table("participant");
  710 __PACKAGE__->columns(All => qw/id course user status role section recitation
  711 last_access comment/);
  712 
  713 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course");
  714 __PACKAGE__->has_a(user => "WeBWorK::DBv3::User");
  715 __PACKAGE__->has_a(status => "WeBWorK::DBv3::Status");
  716 __PACKAGE__->has_a(role => "WeBWorK::DBv3::Role");
  717 __PACKAGE__->has_a(section => "WeBWorK::DBv3::Section");
  718 __PACKAGE__->has_a(recitation => "WeBWorK::DBv3::Recitation");
  719 
  720 __PACKAGE__->has_many(set_assignments => "WeBWorK::DBv3::SetAssignment");
  721 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride");
  722 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride");
  723 
  724 ################################################################################
  725 
  726 package WeBWorK::DBv3::Recitation;
  727 use base 'WeBWorK::DBv3';
  728 
  729 __PACKAGE__->table("recitation");
  730 __PACKAGE__->columns(All => qw/id course name/);
  731 
  732 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course");
  733 
  734 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant");
  735 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride");
  736 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride");
  737 
  738 ################################################################################
  739 
  740 package WeBWorK::DBv3::Section;
  741 use base 'WeBWorK::DBv3';
  742 
  743 __PACKAGE__->table("section");
  744 __PACKAGE__->columns(All => qw/id course name/);
  745 
  746 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course");
  747 
  748 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant");
  749 __PACKAGE__->has_many(set_overrides => "WeBWorK::DBv3::SetOverride");
  750 __PACKAGE__->has_many(problem_overrides => "WeBWorK::DBv3::ProblemOverride");
  751 
  752 ################################################################################
  753 
  754 package WeBWorK::DBv3::Role;
  755 use base 'WeBWorK::DBv3';
  756 
  757 __PACKAGE__->table("role");
  758 __PACKAGE__->columns(All => qw/id course name privs/);
  759 
  760 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course");
  761 __PACKAGE__->has_a_list("privs");
  762 
  763 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant");
  764 
  765 ################################################################################
  766 
  767 package WeBWorK::DBv3::Status;
  768 use base 'WeBWorK::DBv3';
  769 
  770 __PACKAGE__->table("status");
  771 __PACKAGE__->columns(All => qw/id course name allow_course_access
  772 include_in_assignment include_in_stats include_in_scoring/);
  773 
  774 __PACKAGE__->has_a(course => "WeBWorK::DBv3::Course");
  775 __PACKAGE__->has_a_boolean("allow_course_access");
  776 __PACKAGE__->has_a_boolean("include_in_assignment");
  777 __PACKAGE__->has_a_boolean("include_in_stats");
  778 __PACKAGE__->has_a_boolean("include_in_scoring");
  779 
  780 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant");
  781 
  782 ################################################################################
  783 
  784 package WeBWorK::DBv3::User;
  785 use base 'WeBWorK::DBv3';
  786 
  787 __PACKAGE__->table("user");
  788 __PACKAGE__->columns(All => qw/id first_name last_name email_address student_id
  789 login_id password display_mode show_old_answers/);
  790 
  791 __PACKAGE__->has_a_boolean("show_old_answers");
  792 
  793 __PACKAGE__->has_many(participants => "WeBWorK::DBv3::Participant");
  794 
  795 sub name {
  796   return $_[0]->first_name . " " . $_[0]->last_name;
  797 }
  798 
  799 ################################################################################
  800 
  801 package WeBWorK::DBv3::Course;
  802 use base 'WeBWorK::DBv3';
  803 
  804 __PACKAGE__->table("course");
  805 __PACKAGE__->columns(All => qw/id name visible locked archived/);
  806 
  807 __PACKAGE__->has_a_boolean("visible");
  808 __PACKAGE__->has_a_boolean("locked");
  809 __PACKAGE__->has_a_boolean("archived");
  810 
  811 __PACKAGE__->has_many(statuses => "WeBWorK::DBv3::Status");
  812 __PACKAGE__->has_many(roles => "WeBWorK::DBv3::Role");
  813 __PACKAGE__->has_many(sections => "WeBWorK::DBv3::Section");
  814 __PACKAGE__->has_many(recitations => "WeBWorK::DBv3::Recitation");
  815 __PACKAGE__->has_many(participants  => "WeBWorK::DBv3::Participant");
  816 __PACKAGE__->has_many(abstract_sets => "WeBWorK::DBv3::AbstractSet");
  817 
  818 ################################################################################
  819 
  820 package WeBWorK::DBv3::EquationCache;
  821 use base 'WeBWorK::DBv3';
  822 
  823 __PACKAGE__->table("equation_cache");
  824 __PACKAGE__->columns(All => qw/id tex width height depth/);
  825 
  826 ################################################################################
  827 
  828 package WeBWorK::DBv3::Setting;
  829 use base 'WeBWorK::DBv3';
  830 
  831 __PACKAGE__->table("setting");
  832 __PACKAGE__->columns(All => qw/name val/);
  833 
  834 ################################################################################
  835 
  836 =head1 AUTHOR
  837 
  838 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
  839 
  840 =cut
  841 
  842 1;
  843 
  844 __END__
  845 

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9