Archive of January 2009

Mac Owners, Freak Out Someone You Love

My girlfriend works from home on Fridays. I do not. I do, however, leave my Mac powered on and active during the day so that I can SSH in and access files from the office if I need to do so. Last week, knowing that she would be sitting near said computer, I decided to mess with her just a little bit. To do so, I SSH’d into the Mac and used the say command:

$ say "Hello Tricia, this is God. Have you been touching yourself again?"

I personalized the line from Real Genius, but you get the idea. A very nice, disembodied voice that goes by the name of Alex spoke the words aloud as if there was someone in the room with her. She said she jumped about a six feet and had to crawl back into her own skin. She also said that she found Alex’s voice disturbingly sexy. Disturbing to whom?

Learning CakePHP: Foreign Key Constraint Violation

In the course of building forms with CakePHP’s form helper, I kept running into problems with the foreign key constraints in my database. I’d get an error if I didn’t enter a value in the form even though a NULL value was allowed at the database level. A little debugging indicated that the cause was CakePHP trying to insert an empty string into the foreign key field. Since there was no record in the parent table with an empty string id value, the database had no choice but to reject the insert request:

SQL Error: 1452: Cannot add or update a child row: a foreign key constraint fails (`mydatabase/events`, CONSTRAINT `events_ibfk_1` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE)

I will confess right now that I didn’t research this extensively and I don’t know whether this is something that’s easily avoidable using existing techniques or resources offered by CakePHP. It could also be that I’ve coded something incorrectly. What I saw, though, was an opportunity to learn. I saw an opportunity to create a behavior – something I hadn’t done yet – that would detect fields where a NULL value was acceptable and replace empty string values accordingly.

My project has events and events have locations. The relevant components of my database schema look like this:

CREATE TABLE locations (
   id            VARCHAR(255)   NOT NULL,
   name          VARCHAR(255)   NOT NULL,
   description   VARCHAR(255)   NULL,
   PRIMARY KEY ( id )
);
CREATE TABLE events (
   id            CHAR(36)       NOT NULL,
   location_id   VARCHAR(255)   NULL,
   name          VARCHAR(255)   NOT NULL,
   start_time    DATETIME       NOT NULL,
   end_time      DATETIME       NULL,
   PRIMARY KEY ( id ),
   FOREIGN KEY ( location_id )
      REFERENCES locations ( id )
         ON UPDATE CASCADE
         ON DELETE SET NULL
);

Using those tables are my Event and Location models:

class Event extends AppModel {
   public $name      = ‘Event’;
   public $belongsTo = array ( ‘Location’ );

/** content */ } class Location extends AppModel { public $name = ‘Location’; public $hasMany = ‘Event’; /** content */ }

In my event form, I have a dropdown list by which a user can select a location where the event will occur. Since not all events have a specific location, I’ve set the empty option to true so that the user can choose no association:

echo $form->input (
     'location_id',
     array (
        'div'      => 'input select',
        'selected' => $this->data['Location']['id'],
        'empty'    => true
     )
);

Unfortunately, my foreign key constraint didn’t like that empty option. My solution was to create a new Nullable behavior. All the behavior does is inspect a model’s schema and the data being saved. If a field is nullable and the data for that field is empty, the data values empty string is replaced with a null value. It does this in the beforeSave() callback function.

class NullableBehavior extends ModelBehavior {
   /**
    * function beforeSave
    * Looks for nullable fields in the schema and replaces empty string values for those fields
    * with NULL values.
    */
   function beforeSave ( $model ) {
      $schema = $model->schema();

foreach ( $schema as $field => $metadata ) { if ( $metadata[‘null’] ) { if ( isset ( $model->data[$model->name][$field] ) && $model->data[$model->name][$field] === ‘’ ) { $model->data[$model->name][$field] = null; } } } } }

Because I really like the technical accuracy that results from this solution even when constraint errors aren’t being thrown, I wanted to apply it to all of my models. Because I’m lazy, I didn’t want to write the code to add the behavior to all of those models. What I did instead was add it to my applications AppModel in the app/ directory.

class AppModel extends Model{
   public $actsAs = array ( 'Nullable' );
}

It solved my problem, but it may not be perfect or even necessary. Any feedback on my approach or the behavior itself would be appreciated.

Tab Completion for CDPATH on Mac

Though I’ve written several shell-related posts, I’m not a shell geek. Let’s get that out of the way right now. The shell is a tool for me and nothing more. I had used tcsh for years and years and years for no other reason than someone told me it was better than bash when I first started doing any “real” Unix work. I recently moved to bash because, frankly, I was tired of having to custom-configure every system I login to on a regular basis. Bash is the default shell for most Unix flavors and since the shell is only a tool, I saw no reason to continue spending time on customizations.

The other day when @patrick_mc dropped a link to a bash mastery article in a tweet, I saw the opportunity to learn something. Though it was hardly a masterful type article, I did learn about one thing that I didn’t know about before: CDPATH.

The details of CDPATH are outside of the scope of this post, but the gist is that it works the way that the PATH works. Type a directory and if it’s in your CDPATH, the OS will find it and change to that directory by name alone – even if the directory name isn’t in your current working directory. For example, if I’m in my home directory, I can type www and be delivered directly to /Users/me/Development/www as long as /Users/me/Development is in my CDPATH.

When I first read the article, I was at work and on my Ubuntu machine so I tried it there first by adding the following line to my /home/me/.bash_profile:

export CDPATH=.:~:~/Development

After sourcing ($ source .profile) my updated profile, I was able to jump to directories exactly as advertised. Moreover, tab completion worked brilliantly so, from my home directory, I could type cd w[TAB] and it would complete to cd www. Brilliant.

It wasn’t quite so easy on the Mac, though. I updated my .profile, but couldn’t get it to work. I thought I had a syntax error in my .profile, but after hours of trial and error, I was no closer. I pinged the #lazyweb on Twitter to ask whether it worked for others on OS X and got more than a few affirmative responses which just confused me even more. A quick chat with Brad Greenlee, though, pointed out the error of my ways. It turned out that CDPATH was working great, but I was assuming (yeah, I know) that tab completion would also work. It didn’t.

For me, something like CDPATH is only useful with tab completion because otherwise, I can tab complete the full path to the file before I can type the entire directory name (for all but the shortest directory names). Besides, the Tab key is completely woven into the fabric of my muscle memory now. I can’t give it up when I’m in the shell. After a little digging, I found the answer:

$ sudo port install bash-completion

Yep, that’s it (assuming you have MacPorts installed). Well, almost. You do have to update your .profile (or .bash_profile, .bashrc, etc.) to source the installed file at /opt/local/etc/bash_completion. Instructions for doing so are printed in the output of the installation. In case you close your window too early, though:

if [ -f /opt/local/etc/bash_completion ]; then
    . /opt/local/etc/bash_completion
fi

Happy completing.

SSH Host Key Checking

One of the things that’s always annoyed me, but not to such a degree that I’ve felt compelled to expend any effort “fixing” it, is the prompt that I get every time I SSH to a machine that I haven’t connected to before. It looks a little something like this:

The authenticity of host 'host.domain.tld (192.168.1.16)' can't be established.
RSA key fingerprint is 58:3d:dc:39:b3:5c:44:0b:ah:9b:7d:01:8e:f2:f8:77.
Are you sure you want to continue connecting (yes/no)?

Because it’s not a huge deal to type “yes” to create the connection, I’ve never cared enough to really look into it.

This morning, though, I was trying to do a pull from one of my git repositories only to find that the server signature had changed and my connection was terminated. As far as I know, the only way to re-establish a connection terminated for this reason is to remove that server from my list of known hosts. To do so I usually crack open my ~/.ssh/known_hosts file, find the line that begins with the host name of the server I’m trying to connect to and delete that line. The next time I try to connect, the server is added back to the file (after typing “yes” again).

Unfortunately, I was working on my Linux machine this morning and I got an unpleasant surprise when I opened my ~/.ssh/known_hosts file. Ubuntu, unlike every other Unix flavor I’ve worked with/in, encrypts the contents of that file. That meant I couldn’t find the line for the particular server whose signature changed for the purpose of surgical deletion. To reset that server as a known host, I’d have to delete the entire contents of the file. I connect to a number of servers and this has become a big file; I didn’t want to have to type “yes” that many times so a “fixing” the annoyance took on a greater urgency.

This is when it’s really handy to work with a Linux sys admin. He heard me expressing my annoyance in a semi-colorful manner and told me to just shut off the authentication prompt. Hearing the ability to kill two birds with one stone, I did a quick search, then cracked open my SSH config file (/etc/ssh/ssh_config) and edited the following line:

# StrictHostKeyChecking ask

I uncommented the line and changed the value to “no” and I’m no longer prompted when connecting to new machines. Now it’s safe to clear my known_hosts file without bother. I do still get a warning that a new host has been added to my file (which is nice), but no interaction is required.

Defensio False Positives

In addition to the random false negative, I’ve had a number of folks report that comments they’ve left on various posts have been marked as spam. Defensio, like most spam filters, isn’t perfect. If this happens to you, assuming that you’re human and not a spamming jackhole, please drop me a line via the contact form and I’ll make sure that your comment gets added appropriately.

← Earlier Posts Page 1 of 2