CodeIgniter Forums
DataMapper 1.6.0 - Printable Version

+- CodeIgniter Forums (https://forum.codeigniter.com)
+-- Forum: Archived Discussions (https://forum.codeigniter.com/forumdisplay.php?fid=20)
+--- Forum: Archived Libraries & Helpers (https://forum.codeigniter.com/forumdisplay.php?fid=22)
+--- Thread: DataMapper 1.6.0 (/showthread.php?tid=11358)



DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]MikeHibbert[/eluser]
I dont suppose anyone has a copy of datamapper 1.3.3 hanging around?!

I need to run this on a PHP4 server and it appears that the autoload call used in later versions if PHP5 only!

Mike


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]bEz[/eluser]
I am having a problem trying to handle an Duplicatation error, based on the Table Schema below:
Code:
CREATE TABLE IF NOT EXISTS `seasons` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `zone_id` mediumint(9) NOT NULL,
  `seq` smallint(5) unsigned NOT NULL,
  `gametitle_id` mediumint(9) NOT NULL,
  `seamode_id` tinyint(4) NOT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `zoneseq` (`zone_id`,`seq`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT AUTO_INCREMENT=4 ;

--
-- Dumping data for table `seasons`
--

INSERT INTO `seasons` (`id`, `zone_id`, `seq`, `gametitle_id`, `seamode_id`, `created`, `updated`) VALUES
(1, 1, 1, 1, 0, '2009-04-15 19:15:10', '2009-04-15 19:15:10'),
(2, 2, 1, 1, 0, '2009-04-15 19:16:09', '2009-04-15 19:16:09');
The duplication keys around:
Quote:UNIQUE KEY `zoneseq` (`zone_id`,`seq`)
db_debug has been set to FALSE
and I'm using the following method in a model (Season) to attempt custom error handling.

Code:
class Season extends Datamapper {
   var $table = 'seasons';
   var $has_many = array('commissioner', 'registration', 'conference');
   var $has_one = array('zone');
  
  function Season()
  {
    parent::Datamapper();
  }

  function update($data=NULL)
  {
    $ds = new Season();
    $ds = $this->get_by_id($data['id']);
    if ( $ds->exists() )
    {
      $zone_id = $ds->zone_id;
      $seq = $ds->seq;
      $dup_zone_id = ($zone_id == (integer) $data['zone_id']);
      $dup_seq = ($seq == (integer) $data['seq']);
      if ( $dup_zone_id == $dup_seq )
        return array('success' => FALSE, 'message' => "Record already exist for Zone ID: $zone_id and Season #: $seq");

      $s = $this->get_by_id($data['id']);
      $s->zone_id = $data['zone_id'];
      $s->seq = $data['seq'];
      $s->gametitle_id = $data['gametitle_id'];
      if ( $s->save() )
        return array('success' => TRUE, 'message' => "Record successfully updated.");
      else
        return array('success' => FALSE, 'message' => "Unable to update record at this time.");
    }
    /* else
    {
      return $this->create($data);
    } */
  }
}


Is there a better method of handling duplication on indexes, and reporting this back to the user?
The array that is returned is processed by a controller which pushes it into the appropriate view.


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]OverZealous[/eluser]
DM has a built-in unique rule. You should use that, which will check for uniqueness on a column before attempting to save. It is explained in the docs.

[FYI: If you are using DMZ, then you can't have NOT NULL for "<model>_id" columns, because the model needs to be saved before the relationship. Also, instead of hand-setting the <model>_ids, why don't you use the normal relationship saving methods?]


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]bEz[/eluser]
[quote author="OverZealous.com" date="1239842701"]DM has a built-in unique rule. You should use that, which will check for uniqueness on a column before attempting to save. It is explained in the docs.[/quote]
Well, I am not looking for a unique column individually, but unique combination of the two columns.
In other words, you cannot have a duplicate season with the same zone and seq value. But I will re-check the docs if this is the case.

[quote author="OverZealous.com" date="1239842701"][FYI: If you are using DMZ, then you can't have NOT NULL for "<model>_id" columns, because the model needs to be saved before the relationship. Also, instead of hand-setting the <model>_ids, why don't you use the normal relationship saving methods?][/quote]
Yes, I am using DMZ, however... the Zones are it's own table of UNIQUE Values, which is related to a Sport (also it's own table of UNIQUE values).
And yes, Season is related to Zone, however, I am using a form with a drop-down of Zone Data when creating a new Season, hence the reason I'm setting the zone_id.

What would be the "normal" method in this case?

below are the other two Table Schemas:
Code:
CREATE TABLE IF NOT EXISTS `zones` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `sport_id` mediumint(9) NOT NULL,
  `acro` varchar(10) NOT NULL,
  `name` varchar(35) NOT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `acro` (`acro`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT AUTO_INCREMENT=4 ;

--
-- Dumping data for table `zones`
--

INSERT INTO `zones` (`id`, `sport_id`, `acro`, `name`, `created`, `updated`) VALUES
(1, 1, 'EGC', 'Elite Gridiron Champion', '2009-03-23 23:28:43', '2009-03-23 23:28:43'),
(2, 1, 'NGC', 'National Gridiron Challenge', '2009-03-23 23:28:43', '0000-00-00 00:00:00'),
(3, 1, 'FFL', 'Furious Football League', '2009-03-23 23:28:43', '0000-00-00 00:00:00');

Code:
CREATE TABLE IF NOT EXISTS `sports` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `seq` smallint(5) unsigned NOT NULL,
  `acro` varchar(10) NOT NULL,
  `name` varchar(35) NOT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT AUTO_INCREMENT=2 ;

--
-- Dumping data for table `sports`
--

INSERT INTO `sports` (`id`, `seq`, `acro`, `name`, `created`, `updated`) VALUES
(1, 1, 'FTBL', 'Football', '2009-03-23 23:28:43', '2009-03-23 23:28:43');



DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]bEz[/eluser]
to add to the above.

You can have a Season with Duplicate Zone (zone_id), but offset by the Season # (seq).


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]OverZealous[/eluser]
One simple solution is to make your own validation rule. There are three steps:

First, add a method like _unique_zone_seq($field). This method can check to see that the two fields are unique. If they aren't, return FALSE, otherwise return TRUE.

Second, add a line to a language file (you need to load this in, but you can do it in the rule itself) called 'unique_zone_seq'.

Finally, add 'unique_zone_seq' to one or both of the fields.

Code:
$validation = array(
    array('field' => 'seq', 'rules' => array('unique_zone_seq'))
);

...

function _unique_zone_seq($field) {
    $unique = ... // query the DB for uniqueness
    if(!$unique) {
        $CI =& get_instance();
        $CI->load->lang('season_errors'); // error message is in season_errors_lang.php
    }
    return $unique;
}

See Validation in the docs for more explicit information.

update: There also is a unique_pair validation rule.


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]bEz[/eluser]
Thanks OverZealous!

I will try the "unique_pair" rule first.

As for the 'hand-setting' of the zone_id, since I'm getting the value from a form drop_down anyhow, is this still not the appropriate method?


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]camporter1[/eluser]
So, in order to get self-referencing for say, a user, I would just go about doing it this way:

Code:
class User extends DataMapper {
    var $has_many = array(
        'user' => array(
            'join_self_as' => 'user'
        )
    );
}

Otherwise, I'm confused. I'm not sure if I'm allowed to do a normal relationship, or something else. I would use a complicated 3 table relationship to allow users to be related to eachother, but that seems silly and redundant. I also don't know what the tables need to be named for the relationships in that situation.

Has anyone else tried this?

Thanks.


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]bEz[/eluser]
[quote author="bEz" date="1239845560"]Thanks OverZealous!

I will try the "unique_pair" rule first.

As for the 'hand-setting' of the zone_id, since I'm getting the value from a form drop_down anyhow, is this still not the appropriate method?[/quote]
Well, as per suggestion, "unique_pair" did the trick without need for custom validation routine (although I'm sure I will need that approach for another project).
Here is the updated model (Season).
Code:
&lt;?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Season extends DataMapper {
  
   var $table = 'seasons';
   var $has_many = array('commissioner', 'registration', 'conference');
   var $has_one = array('zone');
   var $validation = array(
          array(
            'field' => 'zone_id',
            'label' => 'Zone ID',
            'rules' => array('required', 'trim', 'unique_pair' => 'seq')
          ),
          array(
            'field' => 'seq',
            'label' => 'Season #',
            'rules' => array('required', 'trim', 'unique_pair' => 'zone_id')
          ),
        );

    function Season()
    {
        parent::DataMapper();
    }
    
    function create($data=NULL)
    {
       $zone_id = $data['zone_id'];
       $seq = $data['seq'];
       $this->zone_id = $zone_id;
       $this->seq = $seq;
       $this->gametitle_id = $data['gametitle_id'];
       if ( $this->save() )
         return array('success' => TRUE, 'message' => "Record successfully created.");
       else
       {
         $return_msg = $this->error->seq . 'Zone: ' . $zone_id . br() . ' Season #: ' . $seq;
         return array('success' => FALSE, 'message' => $return_msg);
       }
    }
    
    function update($data=NULL)
    {
       $zone_id = $data['zone_id'];
       $seq = $data['seq'];

       $s = $this->get_by_id($data['id']);
       if ( $s->exists() )
       {
         $s->zone_id = $zone_id;
         $s->seq = $seq;
         $s->gametitle_id = $data['gametitle_id'];
         if ( $s->save() )
           return array('success' => TRUE, 'message' => "Record successfully updated.");
         else
         {
           $return_msg = $this->error->seq . 'Zone: ' . $zone_id . br() . ' Season # ' . $seq;
           return array('success' => FALSE, 'message' => $return_msg);
         }
       } else
       {
         $return_msg = $this->error->seq . 'Zone: ' . $zone_id . ' Season #: ' . $seq;
         return array('success' => FALSE, 'message' => $return_msg);
       }
    }
    
}

/* End of file season.php */

1) Was 'unique_pair' required on both columns of the validation?

2) Still awaiting the verdict on whether my "hand-setting" of the zone_id is still considered in-appropriate in this case.
And what the resolution would be. In the meantime, I will comb the forums and "docs" for what may be the cause of my ways.


DataMapper 1.6.0 - El Forum - 04-15-2009

[eluser]OverZealous[/eluser]
@camporter1
You will always have to "name" both sides of the relationship. In other words, to have User related to User, you need to define a "parent" and "child" relationship. (Note: I've never tried to work with recursive self-references, meaning User1 is related to himself, so no guarantees that will work at all.)
Code:
class User extends DataMapper {
    var $has_many = array(
        'parent' => array(
            'class' => 'user',
            'other_field' => 'child'
        ),
        'child' => array(
            'class' => 'user',
            'other_field' => 'parent'
        )
    );
}

Then your table (in a many-to-many relationship) would be child_parent with the columns child_id and parent_id. You can also leave one side of the relationship as simply user, but that can sometimes be confusing.

Finally, to make changes, you'll have to specify the field to save on when saving:
Code:
$user = ... // look up user
$parent = ... // look up parent
$user->save($parent, 'parent');
// or $user->save(array('parent' => $parent));
// or $parent->save($user, 'child');
// etc.

@bEz
There's nothing "wrong" with saving to the id's directly, I guess I should have made that clear. It can even save a lot of time on the DB, if you trust the users.

However, there's no validation being done on that id, so someone could send the server an invalid id, and really break your application, possibly even getting access to someone else's data. I always recommend writing code as if every user was trying to break the server.

In other words, I would look up the related object by id, and then save that object using the normal $object->save($related). Of course, if you decide to do this, you'll have to enable NULL values for the related columns, because DM doesn't save relationships until after saving $object.

As for needing the rule on both, it probably isn't necessary. The rule runs either way, and checking both just doubles the queries. The only drawback to running on just one is that you won't get an error message on the other field.