Thursday, November 3, 2011

Format My Source Code for Blogging

This is very useful for blogging source code.

Format My Source Code for Blogging

I'll go through and fix some previous posts soon.

Notes on Apache Ant

I'm an Apache Ant newbie. I've known for years that it exists, but I've never used it. I just installed it, today, and now I'm trying to understand what it does and how to use it. Here are some notes:


  • An Ant build contains one project
  • That project has at least one target
  • Each target contains tasks


The build file defines these things and is written in XML and is called build.xml. It has to define at least one target. The target defines one or more tasks to perform when it is run. Here is my first build file:

<?xml version="1.0" encoding="UTF-8"?>  
<project name="helloworld" default="init" basedir=".">  
    <description>  
    Build file for the Hello World application.  
    </description>  
    <target name="init" description="Initialize the build.">
        <touch file="testinit" />
    </target>
</project>

It includes a description (of the project) and one target. The target is simply a set of tasks you want to run. It can be called whatever you want, really. The target name attribute is what you will call from the command line when you want to run those tasks. In this example, my target has the name attribute "init". In that target, the only task I've included is the "touch" task. This will execute the *nix "touch" command. The "file" attribute within the "touch" task specifies which file to touch. (See the Ant manual for more available tasks. They are legion.)

Now, to run this, I go to the directory containing the build.xml file and issue the following command:

ant init

Notice that I didn't say "ant build.xml" or "ant build." This is because I'm telling ant to run a specific target. Also, if I change the name of the file from build.xml to moo.xml, it will break. Ant expects the build file to be called build.xml.

Wasn't that fun?

Continuous Integration Part I

I'm setting up a suite of continuous integration tools for my current job. We are a PHP shop using Mercurial for version control. My catalogue of tools is as follows (so far):

  • Ant (automated build tool)
  • PHPUnit (unit testing)
  • Selenium (functional testing)


I haven't decided on a CI server, yet. I'm looking at Hudson, as it has an extension to support Mercurial. Eventually, I'm hoping we'll use a service, like SauceLabs' Sauce OnDemand to perform our functional tests, since I'd like to take advantage of their cross-browser tests. They use Selenium RC, so we'll want to get started using that, ourselves. As of version 3, PHPUnit has support for writing tests for Selenium. I don't know, yet, whether those would work with SauceLabs, nor do I know whether we'll use those or write our scripts the traditional way, as per the documentation on the Selenium site. Another thing to note is that Selenium RC (Selenium 1) is officially deprecated in favor of Selenium Webdriver (Selenium 2), but they're going to support Selenium RC, doing bug fixes and the like, as it has more features than Webdriver (at least for now).

To get Ant to work with Mercurial, I'm going to need ANT4HG.

I still need to ascertain what all, exactly, will go into my build process. I want it to represent a complete rebuild of our entire Web app from scratch, complete with a set of clean test data and the application code, as well as all tests. I'm suspicious, however, that there will be more to it than doing a clean check out (in hg terms, a clone) of our code base and grabbing a db schema from somewhere, but we'll see.

Tasks done so far:

  1. I've installed PHPUnit using Pear (which I had already installed through Macports when I installed php-5). 
  2. I have installed Ant (an Apache project) using Macports.
  3. Installed ANT4HG as follows:
To install ANT4HG, I downloaded the binary from the SourceForge downloads. I extracted the zip file and ended up with a jar file. I then moved the jar file to the ant lib directory. On my system, that is located here: /opt/local/share/java/apache-ant/lib. It should now be available for Ant to use.


OK, now for Selenium (deep breath). I need the Selenium standalone server. Eventually, I won't need this (I hope), since we'll rely on SauceLabs for this service. But for the purposes of setting up a test of our chosen tools - and also because we're not going to subscribe to a service, yet - I need to have it up and running locally. I'm following instructions I found here, with the exception that I put my selenium jar file in /opt/local/share/java/selenium instead of /usr/lib/selenium. (Either way, I needed to create the selenium directory.)

I then created a launch file so that Selenium would be launched on system startup. The file is copied from the instructions I used on Dan Straw's site. Since my "selenium" directory is in a different location than his, I had to modify my launch file to reflect the proper location. I also had to change the name of the jar file, as he mentions in his write-up. I loaded the plist file and started the service. Using "ps auxwww | grep -i selenium" I learned that it's up and running. Good.

Everything is installed, now, so I just need to get my feet wet and play with it. I'll save that for another post. In the meantime, I'll be looking this.

Monday, October 17, 2011

Searching Relations in Yii's CGridview

[NOTE: I'm typing this pretty frantically, just to get it out there and then move on to my next task! So, it might be a bit confusing. You should familiarize yourself with Yii's CGridView widget before trying to understand what the heck I'm rambling on about below.]

In the default grid view widget usage in admin.php (when you use gii to generate it), all of the fields displayed are directly tied to the model you're viewing. Many times, you don't want to see the value of a foreign key but rather the actual string that would be associated with that particular foreign element. For example, if I have a grid view to display a series of Company models, I don't want the "state" column to display "2" if it really could display "California." That's easy enough to fix by providing some additional information in the CGridView widget definition. Instead of including the state column as "stateId", include an array that specifies the column in the Company table ("stateId"), a header to display at the top of the grid column ("State"), and the value that should actually be shown ("$data->state->name" which represents the "name" column from the related model, "state," for the current data element - $data - of the many Company models returned):


<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'company-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'selectableRows'=>2,
'columns'=>array(
'name',
'street',
'street2',
'city',
array(
'name'=>'stateId',
'header'=>'State',
'value'=>'$data->state->name',
),
'zipCode',
array(
'class'=>'CButtonColumn',
),
),
)); ?>

The problem, however, is that you lose the search field at the top of the column that is used for filtering the result set shown in the grid! To get that to show up again, you'll need to do a couple of other things. Both steps involve the model itself. In my case, I'm dealing with my Company model. Here goes.

First, you need to change the way the comparison is handled in the search() function when it comes to the model's state information. By default, you probably would have something like this:

$criteria->compare('stateId', $this->stateId);

But this doesn't get you what you want, because we're not really comparing the value of a foreign key with an integer that has been entered. (I mean, who's gonna enter an integer when they're searching for Georgia?) So, make it look like this:

$criteria->compare('state.name',$this->stateId, true);

This tells the comparison to compare the name of the state with the value returned from the incoming 'stateId' field (which is now going to be a textual representation of the state's name).

Notice that I'm using the "dot" notation, as in "state.name". This is the syntax for querying a table with the alias "state" for the value of it's "name" column, of course. The reason I can do this, here, is that I've already made sure that

  1. I have a relation in Company with the State table that is referenced with the alias "state" (part of the Company model's relations() function that returns an array of relations).
  2. I have included "with" in my search() function's criteria to let Yii know to join the State table when it selects the Company models:  $criteria->with=array('user','image','video','state','approvedBy0','deletedBy0');
  3. I have included "together" in the criteria and set it to true: $criteria->together=true;
These are important for defining for Yii how to retrieve the related states information in a way that makes it available to search().

The other piece of the puzzle involves the rules() function of the Company model. You have to declare that the "name" column of the State table is safe on search. Most likely, you already have a rule for searching that looks something like this:

array('name, street, street2, city', 'safe', 'on'=>'search')

This means that the columns, "name," "street," and "street2" are safe to include for the search scenario. But we also want the "name" column from our State table to be safe. The way we add this is to take advantage of our relation (which has provided us with the alias, "state," for the State table). Because states are retrieved actively - since "with" includes the state alias and "together" is set to true - we can refer to it's member variables. To include the State tables "name" column in the search, then, just add it to the array:

array('name, street, street2, city, state.name', 'safe', 'on'=>'search')

That's all there is to it! You should now have:


  • A column in your grid that displays the state's actual name, rather than the foreign key for the state
  • A header at the top of the column that reads "State" instead of "stateId" (how gauche)
  • A text field for searching on states that compares what you've typed in with the actual state name values from the available models.

I've seen examples of how to make the search field a pull down menu rather than a text field (which makes sense for states), but I'm not going to post on that, at the moment. I would point out, however, that you can also now sort the state names by clicking the "State" header. Sweet!