Getting into the code

So it has been a couple of weeks since I have posted. But now we need to set up some base code before we can go forward with the details and then adding in the Facebook Graph API. In the last post, the Data model was set up. We have skills and certifications as standalone tables. Skills with the levels and areas tables connected together. We also have the main glue of resumes, connected to covers and tasks which itself is connected to jobs. A lot of tables to create the resume section, but will keep some of this information all together. We need to create some code so that we can get all this information.

If you Baked each object, and used the Bake methods to create the model, and associations, as well as the controllers an views, you will have some code ready to use and ready to go. After you have Baked these items, the sample code that is created is ok to begin with. However, we want to take advantage of a very important technique, and the is the centralization of code, and prevent code duplication. There is one other thing that gets to me, and this is more of an OCD thing for me in code, and that is the way that Cake does the edit check in the controller. In the base created code, it creates a section of code that checks for an ID. If it is not passed, then it redirects the page elsewhere. Like so:

function edit($id = null) {
    if (!$id && empty($this->data)) {
        $this->Session->setFlash(__('Invalid resume', true));
        $this->redirect(array('action' => 'index'));
    }
    . . . . 
}

So in this code segment, if one gets to the edit form, and an ID is passed, and the form is not filled in, then it will display the actual form. And if the ID is not passed in, then it redirects to the Index page. For examples, if the site name was test.com:
www.test.com/resume/edit/2 – will result in the form being shown
www.test.com/resume/edit – will result in a redirect and the error message

Now here is where my OCD kicks in a little. . . .

If the table is housing 3 resumes, IDs of 1,2, and 3 and you go to the edit page for those resumes, it will display the resume content. But, for kicks and giggles, what if we passed in this as the URL:
www.test.com/resume/edit/350

This will display a blank form and even allow one to input data, fill it out, and submit it. Now granted, the processing of it alone will provide a new resume. However, that is why we have an “Add” function. Why not create some logic to check to see if the ID is valid to begin with? This will help on both the edit, and view methods in the controller. (as the View function will behave the same way as the edit function on display). And it is not just for this controller, but it will likely be an issue in all controllers. What I did in this case, is provide a function in the App Model code so that it can check those. Here is one way to solve this. And there can be multiple ways to resolve this issue, this is just one method I have done.
In the app/app_model.php code:

function ifExists($model = null, $id){
    if ( ($model == null) || ($model == '') ||(is_null($id)) ||($id == '') ){
        return 0; 
    }
    $check = $this->find('count', array('conditions' => array($model . "." . strtolower($model) . '_id' => $id)));
    
    return $check; 
}

The way I set up my models for the primary key is tableName_id. So for the resume table, the primary key is “resume_id”, and for the skills table it is “skill_id” and so forth. You can always modify this to use whatever naming convention you use. In this function, it checks to see if the model is passed. If it is not, returns a false. If a model is passed, then it runs a quick find query to see if that ID exists in the table. If it finds anything, then it returns a “1” (or true), if not, then a “0” (or false). By sticking this in the app_model.php file, it is now accessible by all models. To use this, in the edit function, for example, in the resume controller:

function edit($id = null) {
    if ( ((!$id) || (!$this->Resume->ifExists('Resume', $id))) && empty($this->data)) {
        $this->Session->setFlash(__('That Resume does not seem to exist, please try this again.', true));
        $this->redirect(array('action' => 'index'));
    }
    . . . 
}

This will check for an ID, checks to see if that resume exists, and that the form is not filled in yet. If the ID does not exist in the table, then it redirects to the Index page, with a customized message. I could also redirect to the add page with no message, and this is entirely up to you on how you want to do this.

For the View function, it is very similar:

function view($id = null) {
    if ( (!$id) || (!$this->Resume->ifExists('Resume', $id)) ) {
        $this->Session->setFlash(__('That Resume ID does not seem to exist. Please try again', true));
        $this->redirect(array('action' => 'index'));
    }
    . . . 
}

Again, check for an ID, and then make sure it exists in the table. If not, then redirect. A small bit of code to help keep the application from having odd behavior and keep it clean.

Next is to process the forms. By using Bake, it creates two functions, add and edit. Both process the forms as needed. Which there is nothing we can do to clean that up. However, some basic pre-commit tasks are still to be done. One thing I am highly in favor of is sanitizing the data. If there is some basic checks we need to do on the data, then we must also do that as well. For this we, will use the jobs controller/model for the example.

In each row of the job, we have a start date and an end date. In the model, we will validate these fields to be a “date”. We also need to validate these dates in another way. The end date can not be earlier than the start date. It is quite impossible to end a job three months before you even start it. So we want to keep that in check as well. And let’s say we want to do that in the controller. So now we need to sanitize the data, and check the dates. This can be redundant in the code, as it will exist in the edit and add functions. And if we need to make a change to the logic, it will cause an issue in two places. So, we can take that and put this in its own function. First we start with the sanitation of the data. At the top of the file, place

App::import('Sanitize');

Then create a new function and place the sanitation in this function:

function validateForm($data){
    $san = new Sanitize();
    // Clean the data
    $this->data = $san->clean($this->data); 
    $this->data['Job']['company_name'] = $san->paranoid($this->data['Job']['company_name'], array(' ', '.', '/', '-', '*', '&', '(', ')',','));
    $this->data['Job']['via_company']  = $san->paranoid($this->data['Job']['via_company'], array(' ', '.', '/', '-', '*', '&', '(', ')',','));
    $this->data['Job']['location']     = $san->paranoid($this->data['Job']['location'], array(' ', '.', '/', '-', '*', '&', '(', ')',','));
     . . .     
}

In this, I am cleaning the Company Name, the Via Company (in case it was a contract job), and Location. The next thing we need to do is create the logic to test the dates.I am just going to create a simple number using the fields from the form. Then I will compare the 2 numbers, and if the end is less than the start, then it is an error. So the entire function is:

function validateForm($data){
    $san = new Sanitize();
    // Clean the data
    $this->data = $san->clean($this->data); 
    $this->data['Job']['company_name'] = $san->paranoid($this->data['Job']['company_name'], array(' ', '.', '/', '-', '*', '&', '(', ')',','));
    $this->data['Job']['via_company']  = $san->paranoid($this->data['Job']['via_company'], array(' ', '.', '/', '-', '*', '&', '(', ')',','));
    $this->data['Job']['location']     = $san->paranoid($this->data['Job']['location'], array(' ', '.', '/', '-', '*', '&', '(', ')',','));
     . . .   
     
    $errors = array();
    
    // Set the dates in a usuable format
    $start = $data['Job']['start_date']['year'] . $data['Job']['start_date']['month'] . $data['Job']['start_date']['day'];
    $end   = $data['Job']['end_date']['year'] . $data['Job']['end_date']['month'] . $data['Job']['end_date']['day'];
    $today = date('Ymd'); 

    // Check to make sure the start date is not later than the end date
    if ( $start >= $end ){
        // $this->Job->invalidate('start_date', "The Start Date must be earlier than then End Date");
        $error['err']['field'] = 'start_date';
        $error['err']['messg'] = 'The Start Date must be earlier than then End Date';
    }
    
    // Check to see whether or not to include the end date as part of the insert            
    if ( $end == $today ){
        $error['end'] = 0;
    } else {
        $error['end'] = 1;
    }
    $data[error] = $error;
    return $data;
}

Now we need to be able to use that in the actual form. And we need to invoke an error in case there is an issue. If an error is found, we need to invalidate the field and provide the error message. That could be done in the following way:

function edit($id = null) {
    . . . 
    if (!empty($this->data)) {
        $check = $this->validateForm($this->data);
        // Check to make sure the start date is not later than the end date
        if ( isset($check['Error']['err']) ){
            $this->Job->invalidate($check['Error']['err']['field'], $check['Error']['err']['messg']);
        }
        
        // Set the data to the model now
        $this->Job->set( $check['Job'] );
        
        if ( $this->Job->validates() ) {
            // Do the normal process/save stuff
            . . . 
        }
         . . . 
    }
    . . . 
}

In this example, it shows how to find ways to cut down code repetition. Now I am sure we could even set this validation in the model as well. I am just giving an example of what we can do. Now using this, we can set up all the different functionality of the resume application.

In the next post, I will go through how to secure the Resume so only parts are seen by the world, and which parts you would have to log in for, and why. I will also go into a centralized query for all resume elements and be able to display.