<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Cameron Spear]]></title><description><![CDATA[Thoughts and stuff.]]></description><link>https://cameronspear.com/</link><image><url>https://cameronspear.com/favicon.png</url><title>Cameron Spear</title><link>https://cameronspear.com/</link></image><generator>Ghost 2.0</generator><lastBuildDate>Thu, 16 Apr 2026 00:55:47 GMT</lastBuildDate><atom:link href="https://cameronspear.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Developing with Docker: Getting Started]]></title><description><![CDATA[A quick guide on how to get set up locally to develop with Docker.]]></description><link>https://cameronspear.com/docker-getting-started/</link><guid isPermaLink="false">5b7c94f2608549006dd7df4a</guid><category><![CDATA[Docker]]></category><category><![CDATA[Adventures in Dockerland]]></category><category><![CDATA[Docker Toolbox]]></category><category><![CDATA[Docker Machine]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Fri, 29 Jan 2016 23:01:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>This is the first article in a series about Docker and Docker tooling with the aim to give a foundation of developing with Docker.</p>
</blockquote>
<p>This is a quick guide on how to get set up locally to develop with Docker. It assumes you have a basic understanding of what Docker and containerization is.</p>
<h3 id="conceptsforrunningdocker">Concepts for Running Docker</h3>
<p>There are a couple concepts important to understand that helped me a lot in getting up to speed:</p>
<p>Docker has two sides to it: a <strong>Docker client</strong> that can send commands to a <strong>Docker server</strong> (also referred to as the Docker host). The server is running the Docker daemon that will actually do the work. The daemon <em>cannot</em> run on Windows or Mac: it's Linux only. So to develop locally, you need a virtual machine (VM)<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> to run the daemon and act as the <strong>server</strong>, and your Mac (or Windows)<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> machine will act as the <strong>client</strong>, sending commands to build and run containers.</p>
<p>Here's an image taken from the <a href="https://docs.docker.com/engine/installation/mac/">Docker OSX installation docs</a> that illustrates it well:</p>
<p><img src="https://cameronspear.com/content/images/2016/01/mac_docker_host.svg" alt="Docker VM diagram"></p>
<h3 id="thesetup">The Setup</h3>
<p>So for development, you need Docker (the client) installed on your computer and a VM to act as the server. Then you set some environment variables to point to the Docker server (the daemon runs on TCP port 2376 by default), and commands run on the client will be sent to the server. When you build, the build context is sent to the server, and then the server does all the building.</p>
<h3 id="dockertoolbox">Docker Toolbox</h3>
<p>Fortunately, Docker has provided some tools to help you do all this: <a href="https://docs.docker.com/machine/">Docker Machine</a> can provision a VM for you, and <a href="https://www.docker.com/products/docker-toolbox">Docker Toolbox</a> will install Docker, Docker Machine, VirtualBox (which can run VMs) and a few other tools.</p>
<p>After you download and install all those tools with the Toolbox, you can create a VM with Machine:</p>
<pre><code>docker-machine -d virtualbox create default
</code></pre>
<p>(Where <code>default</code> is the name you want to give the VM, and can be anything you want.)</p>
<p>That will set up a VM appropriate for Docker development and then you can run a command to set up your environment so the client will use the VM as the server:</p>
<pre><code>eval $(docker-machine env default)
</code></pre>
<p>To test that it's working correctly, you can run the Docker <code>hello world</code>:</p>
<pre><code>docker run hello-world
</code></pre>
<p>It will pull (download) the <code>hello world</code> image if it isn't already downloaded on the server (which if you just set up a new Machine, it won't be) and if everything's working, print something like:</p>
<pre><code>Hello from Docker.
This message shows that your installation appears to be working correctly.

(more content)...
</code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>Hopefully this will serve as a foundation to understand what's going on in subsequent articles, and to hopefully better follow along.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>You don't <em>have</em> to use a locally running VM. You can point the client to use a Docker server running remotely. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>If you develop on Linux, your machine can act as both the client and the server, and you don't need to run a VM. <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[Adventures in Dockerland]]></title><description><![CDATA[<p>I've been using Docker a <em>lot</em> lately. And I absolutely love it. I have tons to share, and I want to start blogging again (he says every week for a year without doing anything), and this seems like a great topic to get started (up again) with.</p>
<p><img src="https://cameronspear.com/content/images/2016/01/Docker_-container_engine-_logo-1.png" alt="Docker is Cool"></p>
<p>A few things</p>]]></description><link>https://cameronspear.com/adventures-in-dockerland/</link><guid isPermaLink="false">5b7c94f2608549006dd7df48</guid><category><![CDATA[Docker]]></category><category><![CDATA[Adventures in Dockerland]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Tue, 26 Jan 2016 04:06:48 GMT</pubDate><media:content url="https://cameronspear.com/content/images/2016/01/product---engine.png" medium="image"/><content:encoded><![CDATA[<img src="https://cameronspear.com/content/images/2016/01/product---engine.png" alt="Adventures in Dockerland"><p>I've been using Docker a <em>lot</em> lately. And I absolutely love it. I have tons to share, and I want to start blogging again (he says every week for a year without doing anything), and this seems like a great topic to get started (up again) with.</p>
<p><img src="https://cameronspear.com/content/images/2016/01/Docker_-container_engine-_logo-1.png" alt="Adventures in Dockerland"></p>
<p>A few things I've done lately that I wanted to share:</p>
<ul>
<li>Use lots of CLI tools via Docker (it's great for that kind of thing).</li>
<li>Moved this site to using Docker.</li>
<li>Develop locally and push to production, all using only Docker, Docker Compose, and Docker Machine.</li>
<li>Migrated this site to a CoreOS VM.</li>
<li>Published multiple Docker sites to a single physical machine (think Docker virtual hosts).</li>
</ul>
<p>I've also worked on some <em>massive</em> projects exploring Docker at scale, but I want this series to mostly focus on Docker for the &quot;day to day&quot; and the average developer (I'm on a mission to prove it doesn't need scale massively (or even at all) to find Docker <em>super</em> useful).</p>
<p>I'm hoping to have a new post every week or three, with the first later this week. I'll update this post as a sort of table of contents. Stay tuned for exciting stories of Adventures in Dockerland!</p>
]]></content:encoded></item><item><title><![CDATA[Amazon's Gimmicky Guarantee]]></title><description><![CDATA[<p>I'm actually quite happy with Amazon, but there's one thing that <em>really</em> bothers me:</p>
<p><img src="https://cameronspear.com/content/images/2015/06/guaranteed.png" alt=""></p>
<p>They <em>say</em> they guarantee their Prime shipments<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> to be delivered by a particular date, but what do they do when that date is pushed back (as was the case with one of my shipments recently)</p>]]></description><link>https://cameronspear.com/amazons-gimmicky-guarantee/</link><guid isPermaLink="false">5b7c94f2608549006dd7df47</guid><category><![CDATA[amazon]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Thu, 04 Jun 2015 01:44:47 GMT</pubDate><content:encoded><![CDATA[<p>I'm actually quite happy with Amazon, but there's one thing that <em>really</em> bothers me:</p>
<p><img src="https://cameronspear.com/content/images/2015/06/guaranteed.png" alt=""></p>
<p>They <em>say</em> they guarantee their Prime shipments<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> to be delivered by a particular date, but what do they do when that date is pushed back (as was the case with one of my shipments recently)?</p>
<p>Nothing.</p>
<p>I even opened a ticket to complain. They didn't even take responsibility, just blamed the carrier (perhaps it was the carrier's fault, but at no time did I receive a guarantee from UPS or FedEx, etc).</p>
<p>To be clear: I'm bothered that they slap that &quot;guarantee&quot; on it and it means nothing. They just do it to make you feel warm and fuzzy, but they're not going to do anything about it unless you make a real stink. As seen in the conversation below, I did end up getting a $5 credit, but I didn't want it that way.</p>
<p>I either want them to stop giving me empty guarantees, or have some sort of blanket policy to give a small credit when the ball gets dropped. The latter seems unrealistic since they don't have control over a lot of what it takes to deliver a package, and I would be satisfied with the former. Just call it what it is: an estimated date of delivery.</p>
<p>If you're curious, here's a <a href="http://droplr.cwspear.ninja/192aV">screenshot of the original conversation</a> and the entire conversation in text form is below:</p>
<blockquote>
<p><strong>Me:</strong> Hi, I got a shipping notice that an item was shipped with a &quot;Guaranteed delivery date: Tuesday, June 2, 2015&quot; and I was notified on the day it was supposed to be deliver that it won't be here (estimated) until &quot;Monday, June 8, 2015&quot;</p>
<p><strong>Amazon:</strong> Hello, my name is <s>Redacted</s>. I'm here to help you today.</p>
<p><strong>Me:</strong> Hello</p>
<p><strong>Amazon:</strong> I'm sorry to hear that you haven't received the item as promised.<br>
Just to make sure, are you referring to the item &quot;Canon CA-110 CA110 Replacement AC Power Supply Adapter Charger for VIXIA HF R200 R20 R21 M500 M50 M52 R300 R30 R32 LEGRIA HF R26 R28 R206 Camcorders&quot;?</p>
<p><strong>Me:</strong> Correct</p>
<p><strong>Amazon:</strong> Thanks for confirming.<br>
It seems that the carrier delayed the package in transit. In this case, I'll contact the carrier on behalf of you and instruct to deliver your package with high priority.<br>
Will that work for you?</p>
<p><strong>Me:</strong> Well, what exactly is meant by &quot;Guaranteed delivery date&quot;<br>
?</p>
<p><strong>Amazon:</strong> I understand your concern, Cameron. the carrier delayed the package in transit. I'll also forward this to our shipping team to check the issue with carrier.<br>
In addition to our large selection, one of the benefits we try very hard to offer our customers is convenience. I'm very sorry for the inconvenience you experienced in this case.</p>
<p><strong>Me:</strong> You didn't really answer the question</p>
<p><strong>Amazon:</strong> I understand your concern that we've promised you to deliver your package on guaranteed delivery date and the package was not delivered as promised.<br>
To help compensate any inconvenience, I'll help you with one month prime extension. In this case, are you able to wait until June 5, 2015 to get the package?</p>
<p><strong>Me:</strong> So... the guarantee means nothing? It's just marketing speak?<br>
A false assurance?</p>
<p><strong>Amazon:</strong> No, Cameron. I'm very sorry about all of this. I hope you'll consider this an isolated incident and give us another chance in the future.</p>
<p><strong>Me:</strong> Well, maybe you should change the text to always say &quot;estimated&quot; instead of &quot;guaranteed&quot; if you're not going to do anything when it falls short.</p>
<p><strong>Amazon:</strong> I understand your concern completely. I'm really sorry for the delay in delivering the package. If you prefer I'll help you with full refund and please refuse the package while delivery. I'll also forward this to our shipping team to check the issue with carrier and this will not happen in future.</p>
<p><strong>Me:</strong> No, it's okay. That's not what I want</p>
<p><strong>Amazon:</strong> To help you, I'll contact the carrier on behalf of you and instruct to deliver your package with high priority.</p>
<p><strong>Me:</strong> That's not really what I want either, and you've said that at least 3 times now.</p>
<p><strong>Amazon:</strong> To help compensate the inconvenience I'll help you with $5 promotional credit to your account.<br>
Will that work for you?</p>
<p><strong>Me:</strong> I guess, but ultimately, I would like for Amazon not to say they guarantee deliveries when they don't typically do anything about it. Either have a policy to give something like $5 credit if something goes wrong, or don't claim a &quot;guaranteed delivery.&quot; It's very confusing and misleading.</p>
<p><strong>Amazon:</strong> I understand your concern. I'll forward this to our appropriate team.</p>
<p><strong>Me:</strong> Thank you</p>
<p><strong>Amazon:</strong> I've issued $5 promotional credit to your account.</p>
<p><strong>Me:</strong> Thanks</p>
<p><strong>Amazon:</strong> You're welcome.<br>
Thanks for your understanding and patience!<br>
Is there anything else I can assist you with today?</p>
<p><strong>Me:</strong> Nope</p>
<p><strong>Amazon:</strong> Thanks for shopping at Amazon.com. We appreciate your business and look forward to serving you again in the near future.<br>
Have a nice day!<br>
Take care, bye!<br>
Please click the &quot;End Chat&quot; link to close this window.</p>
<p><strong>Me:</strong> You, too! Bye</p>
</blockquote>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>I couldn't find any official agreement from Amazon as to what they mean by &quot;Guaranteed Prime Deliveries,&quot; but if you <a href="https://www.google.com/search?q=amazon+prime+guaranteed+delivery">do a search for it</a>, you'll find a lot of other people that have run into the same issue. They seem to have better luck getting Amazon to do something more tangible to make up for it than me, but I find it very frustrating either way! <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[Embracing the npm in All of Us]]></title><description><![CDATA[Ghost is now running as an npm module!]]></description><link>https://cameronspear.com/embracing-the-npm-in-all-of-us/</link><guid isPermaLink="false">5b7c94f2608549006dd7df46</guid><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Mon, 30 Mar 2015 03:07:04 GMT</pubDate><content:encoded><![CDATA[<p>The site may only look slightly different, but I've completely refactored the backend for the blog to use <a href="https://github.com/TryGhost/Ghost/wiki/Using-Ghost-as-an-npm-module">Ghost as <code>npm</code> module</a>. As part of this, I wrapped the site in a custom express server to handle some other functionality from the same Node app, and converted all the legacy PHP content to use Node! Just to make sure it was all good and gone, I even deleted PHP from the server =)</p>
<p>This will make it a lot easier to keep Ghost up to date, and again, hopefully set the stage for bigger change that will probably come in 5-10 years at this rate...</p>
<p>I also updated to Node <code>0.12.1</code>. I would have updated to io.js, but that would have taken more effort.</p>
<p>Most of the site is still intact. I completely removed a couple pages that had <em>very</em> few views and have been &quot;deprecated&quot; for over a year. So no one should even really notice anything other than the fact that the old home page is now gone (it's now the list of blog posts). I was never a fan of that page, so I'm not sad to see it gone.</p>
<p>Anyway, now all it takes to update Ghost is an <code>npm update</code> and we're off! I recommend you self-hosters give it a try. It's actually pretty slick and converting the blog itself was the easy part (updating the legacy content was less-so, but I think we're all good now!).</p>
]]></content:encoded></item><item><title><![CDATA[HTTPS 100% of the Time]]></title><description><![CDATA[<p>In a long and (hopefully) interesting series of changes coming to my site (a lot technical, but many things will be visual, too), I have made the switch to use <code>https</code> <em>everywhere</em> and not just on select pages. All pages should now be protected by SSL/TLS/whatever it is</p>]]></description><link>https://cameronspear.com/https-100-of-the-time/</link><guid isPermaLink="false">5b7c94f2608549006dd7df44</guid><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Tue, 23 Dec 2014 19:45:22 GMT</pubDate><content:encoded><![CDATA[<p>In a long and (hopefully) interesting series of changes coming to my site (a lot technical, but many things will be visual, too), I have made the switch to use <code>https</code> <em>everywhere</em> and not just on select pages. All pages should now be protected by SSL/TLS/whatever it is these days.</p>
<p>This is <em>mostly</em> a benefit to me (or I should say I mostly did it for me), but there are a number of benefits for you, the user, too.</p>
<p>Please report any issues you spot that might be related to this change so I can fix them.</p>
]]></content:encoded></item><item><title><![CDATA[A DIFFerent, Better Approach to Database Migrations in PHP]]></title><description><![CDATA[<p>I've been giving a lot of thought lately to how I don't particularly like how most PHP frameworks/tools/ORMs handle migrations and how I think we, as a community, can do better.</p>
<p>This article assumes we're all on the same page as to <em>why</em> we should be using migrations.</p>]]></description><link>https://cameronspear.com/a-different-and-better-approach-to-database-migrations-in-php/</link><guid isPermaLink="false">5b7c94f2608549006dd7df43</guid><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Tue, 16 Dec 2014 07:58:00 GMT</pubDate><content:encoded><![CDATA[<p>I've been giving a lot of thought lately to how I don't particularly like how most PHP frameworks/tools/ORMs handle migrations and how I think we, as a community, can do better.</p>
<p>This article assumes we're all on the same page as to <em>why</em> we should be using migrations. This article is to dissect the <em>how</em>.</p>
<h3 id="thecurrentstate">The Current State</h3>
<p>I tried to research as many different migration tools as I could before writing this article. I even pulled down code for a few of them to get a more hands-on feel for some of them.</p>
<p><strong>Full Disclosure</strong>: I haven't used very many of these in a real-world application, and some I haven't used at all, just perused the docs. Please let me know if I got something wrong.</p>
<p>Four of the six migration tools on the first page of a Google Search for <strong>php database migrations</strong> all have similar approaches (using Phinx as an example, as it's mentioned <em>four</em> times on the first page and it's framework/ORM agnostic):</p>
<pre><code class="language-php">&lt;?php

use Phinx\Migration\AbstractMigration;

class CreateUserLoginsTable extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        // create the table
        $table = $this-&gt;table('user_logins');
        $table-&gt;addColumn('user_id', 'integer')
              -&gt;addColumn('created', 'datetime')
              -&gt;create();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
		$this-&gt;dropTable('user_logins');
    }
}
</code></pre>
<p>This is a incremental snapshot of what you want to have done in the database. If at a later time, you need to add an <strong>updated</strong> field to the database, you must create a new migration:</p>
<pre><code class="language-php">&lt;?php

use Phinx\Migration\AbstractMigration;

class AddUpdatedColumnToUserLoginsTable extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $table = $this-&gt;table('user_logins');
        $table-&gt;addColumn('updated', 'datetime')
              -&gt;save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
        $table = $this-&gt;table('user_logins');
		$table-&gt;removeColumn('updated')
              -&gt;save();
    }
}
</code></pre>
<p>The syntax is different, but for the most part, the approach is the same.</p>
<p>From here, you just run some sort of command to run the pending <code>up</code> commands, and if there is an issue, you can rollback which runs the <code>down</code> commands. The idea here is that you commit these migrations with your code, and if so if you pull down any particular commit of the code, you can either move forward or back, going through each migration in order, to arrive at the state the database was in at that particular commit.</p>
<h3 id="whatswrongwiththisapproach">What's Wrong with this Approach?</h3>
<p>First of all, It's <em>so very tedious</em> to make minor changes to your database. If you're still in development and haven't pushed your code, you can just rollback, make a change to the migration, and then re-run the migration. This is a bad idea to do if you've already pushed the migration, <em>especially</em> if you've already pushed it to production. Changing migrations after the fact can (and often do) cause systems that have already run those migrations to not pick up those changes and thus the database is no longer in sync.</p>
<p>For this second reason, I feel that it's <em>so</em> obvious that this is an issue, it's easily overlooked—including me—until the glass was shattered by looking at how other languages do it: <em>no where in the code is there an absolute list of the fields on the models/fields in the database</em>.</p>
<p>Okay, okay, I guess that point really does depend on the code you're using, i.e. the framework may or may not have a way to support this. In my research, I found Laravel to be the most popular PHP framework this year. Laravel does not use property names on the model, and thus you can't really go to the model to see what fields belong there. On the other hand, Phalcon, another popular PHP framework, does. This is less of an issue in frameworks Phalcon, but Phalcon doesn't support the &quot;easier way&quot; of doing migrations that I will discuss later.</p>
<h3 id="othercontenders">Other Contenders</h3>
<p>Doctrine and Propel are both under the Symfony umbrella, and that may explain why they have some similar functionality. They both allow for a configuration file definition of models (Doctrine actually has a bunch of different ways you can define the models).</p>
<p>I find this approach to be <em>a lot better</em>, but the way these ORMs do it still have their issues. Let's first explain how they approach it:</p>
<p>As an example (taken from the Propel docs), you would create an XML file:</p>
<pre><code class="language-markup">&lt;database name=&quot;bookstore&quot; defaultIdMethod=&quot;native&quot;&gt;
  &lt;table name=&quot;book&quot; description=&quot;Book Table&quot;&gt;
    &lt;column name=&quot;id&quot; type=&quot;integer&quot; primaryKey=&quot;true&quot; autoIncrement=&quot;true&quot; /&gt;
    &lt;column name=&quot;title&quot; type=&quot;varchar&quot; required=&quot;true&quot; primaryString=&quot;true&quot; /&gt;
    &lt;column name=&quot;isbn&quot; required=&quot;true&quot; type=&quot;varchar&quot; size=&quot;24&quot; phpName=&quot;ISBN&quot; /&gt;
    &lt;column name=&quot;author_id&quot; type=&quot;integer&quot; /&gt;
    &lt;foreign-key foreignTable=&quot;author&quot; onDelete=&quot;setnull&quot; onUpdate=&quot;cascade&quot;&gt;
      &lt;reference local=&quot;author_id&quot; foreign=&quot;id&quot; /&gt;
    &lt;/foreign-key&gt;
  &lt;/table&gt;
  &lt;table name=&quot;author&quot;&gt;
    &lt;column name=&quot;id&quot; type=&quot;integer&quot; primaryKey=&quot;true&quot; autoIncrement=&quot;true&quot; /&gt;
    &lt;column name=&quot;first_name&quot; type=&quot;varchar&quot; /&gt;
    &lt;column name=&quot;last_name&quot; type=&quot;varchar&quot; /&gt;
  &lt;/table&gt;
&lt;/database&gt;
</code></pre>
<p>You would then execute a <code>diff</code> command, that will <em>automatically create</em> a migration for you (both Doctrine and Propel create code that executes raw-SQL migrations on the high level, i.e. <code>DB::exec('CREATE table ...');</code>).</p>
<p>It looks at the current schema in the database and creates a diff based on the current state of the XML config (you would then run that migration to actually change the database). The first time you run that <code>diff</code> command, it would create the table and all the columns. Then, if you want to add another column after the fact, you edit the schema XML, adding the necessarily XML, i.e. in the <code>author</code> table node in the example, you could add</p>
<pre><code class="language-markup">&lt;column name=&quot;middle_name&quot; type=&quot;varchar&quot; /&gt;
</code></pre>
<p>Then you run another <code>diff</code> command, and it would create and <code>up</code> and <code>down</code> migration that would, respectively, add and remove the <code>middle_name</code> column to the <code>authors</code> table.</p>
<h3 id="thecomparison">The Comparison</h3>
<p>I find this 2nd approach to be <em>vastly</em> more simple. You only have to change the schema in one place, and the tedious part is done for your automagically. You have an absolute definition of the schema (which maps to the models) for quick reference.</p>
<p>The only downside I can think of: you could only have one pending migration at a time. You'd have to apply the last migration before you could do another diff, otherwise the diff wouldn't work, because it'd be based off an old version of the schema. You <em>could</em> get around it with comparing the schema that <em>would</em> be if you ran the first migration before creating a second.</p>
<p>I don't find this necessary. If you've pushed, you should have executing that migration anyway. If you haven't pushed, you can just undo that migration and create a new one. i.e. instead of having 2 migrations: <code>A -&gt; B -&gt; C</code>, you just have one: <code>A -&gt; C</code>.</p>
<p>Now, while I think the approach is superior, the only two stable, in-common-use migration tools I could find were wrapped behind huge ORMs. I've played with Doctrine migrations a lot, but a ton of the configuration defines behavior of the model that has nothing to do with the migrations, but with how Doctrine behaves. Basically: it's tied too tightly to the Doctrine ORM and isn't appropriate to use with other ORMs/frameworks.</p>
<h3 id="thesolutionaproposal">The Solution: A Proposal</h3>
<p>The solution is to start with an existing tool that can handle all the abstraction away to make migrations easier to handle. That is, I think Phinx is the perfect place to start. Rather than having to re-invent the wheel and figure out how to write raw SQL to support different database vendors, we take what we know <em>does work</em> as a starting point.</p>
<p>Phinx is also ORM/framework agnostic, and building a tool to extend from Phinx would wisely also be ORM/framework agnostic, and should be able to replace another ORM/framework migration approach.</p>
<p>From there, we support schema definitions like Propel. But we don't <em>only</em> offer XML. We offer basically any transactional data format that can be mapped to a PHP array that would look something like this:</p>
<pre><code class="language-php">[
	'table' =&gt; 'users',
    'columns' =&gt; [
    	'name' =&gt; [
        	'type' =&gt; 'string',
            'length' =&gt; 100,
        ],
        'email' =&gt; [
        	'type' =&gt; 'string',
        ]
        ...
    ]
]
</code></pre>
<p>It'd be easy to support XML, YAML, JSON, obviously a PHP array.</p>
<p>If you were to run a diff on this schema for the first time, you'd end up with this migration in Phinx, automatically generated:</p>
<pre><code class="language-php">&lt;?php

use Phinx\Migration\AbstractMigration;

class AutoCreateUserTable extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
    	// IDs are automatically added in Phinx
        // (IDs can be changed/overridden)
        $table = $this-&gt;table('users');
        $table-&gt;addColumn('name', 'string', ['length' =&gt; 100])
              -&gt;addColumn('email', 'string')
              -&gt;create();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {
		$this-&gt;dropTable('users');
    }
}
</code></pre>
<p>Bam. Best of both worlds. We still have incremental migrations, but it's done automatically. Before committing this migration, we could make tweaks (just as long as the final schema is the same). For example, if the auto-migration generated dropped and added a column when what you wanted was to rename the column, you could tweak the migration to rename the column instead. Just as long as the final state of the database is same as the current schema.</p>
<p>Or, better yet, the accompanying command-line tool would detect those types of discrepancies, and <em>ask</em> you if you want to drop/add or rename. (This may be a v2 type of thing.)</p>
<p>As a last thought, there's something else I want to add... I know this will be controversial, I <em>strongly</em> believe it should support PHP annotations. Which don't technically exist, I know (I think they should. Maybe a blog post for another day). While I think PHPdoc annotations aren't great... I think it's worth the &quot;not so greatness.&quot;</p>
<p>The rationale is that if you have a framework that has the field names in the models like Phalcon, you wouldn't want to have to update your schema in <em>two</em> places (the schema definition <em>and</em> the model definition). For example:</p>
<pre><code class="language-php">&lt;?php namespace App;

use \Phalcon\Mvc\Model;

/**
 * @Table('Users')
 */
class Users extends Model
{
	/**
	 * @Type('integer')
     * @Signed(false)
     * @PrimaryKey
	 */
    public $id;

	/**
	 * @Type('string')
     * @Length(100)
	 */
    public $name;

	/**
	 * @Type('string')
	 */
    public $email;
}
</code></pre>
<p>This way, you still have your schema and your model definition and no redundant code.</p>
<h3 id="conclusion">Conclusion</h3>
<p>I hope you see the potential as I do. I'm okay if you think this idea is stupid. You don't have to use it. I obviously think you should. I think it greatly simpliefies the process and lowers the barrier of entry. As far as I can find, this would be the first and only not-tied-to-an-ORM/framework implementation in PHP out there.</p>
<p>I hope to have a working proof-of-concept prototype up by the end of the year with at least MySQL support for basic stuff.</p>
<p>Please, if you have any questions, comments, suggestions, corrections, personal crises, or you just wanna call me out on something, please feel free to leave a comment, <a href="https://cameronspear.com/contact/">contact me</a>, or <a href="https://twitter.com/CWSpear">reach out to me on Twitter</a>.</p>
<hr>
<p><strong>Appendix A</strong>: Because Node is often used with noSQL databases, it is more common to have a schema defintion approach, and a few ORMs I've used in Node (<a href="https://github.com/balderdashy/waterline">Waterline</a>, <a href="http://sequelizejs.com/">Sequelize</a>) have support for SQL database vendors and thus provide automatic database syncing based on schema.</p>
<p><strong>Appendix B</strong>: .NET supports a couple different ways to do <a href="http://blogs.msdn.com/b/adonet/archive/2012/02/09/ef-4-3-automatic-migrations-walkthrough.aspx">automatic migrations</a>. It's pretty powerful and in the .NET world, it's hard to say something like this is &quot;only for small, simple projects.&quot;</p>
]]></content:encoded></item><item><title><![CDATA[Year in Review]]></title><description><![CDATA[<p>I set out with a goal of writing at least twice per month for a full year (and hopefully to continue that pace beyond that year, but at least for one year!).</p>
<p>I started out going good in the first half of the year. But then I sort of fell</p>]]></description><link>https://cameronspear.com/year-in-review/</link><guid isPermaLink="false">5b7c94f2608549006dd7df42</guid><category><![CDATA[family]]></category><category><![CDATA[personal]]></category><category><![CDATA[review]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Mon, 15 Dec 2014 06:56:03 GMT</pubDate><media:content url="/blog/content/images/2014/12/The-Twins--1.jpg" medium="image"/><content:encoded><![CDATA[<img src="/blog/content/images/2014/12/The-Twins--1.jpg" alt="Year in Review"><p>I set out with a goal of writing at least twice per month for a full year (and hopefully to continue that pace beyond that year, but at least for one year!).</p>
<p>I started out going good in the first half of the year. But then I sort of fell apart!</p>
<p>I'm redoubling my efforts starting now, and hope to resume my pace of earlier in the year. I want my blog to primarily be technical, and I will probably continue to write just for my own sake. To the last point, I wanted to write a bit about my year. This will be a personal post, and not have <em>any</em> code in it, so if you're bothered by that, stop reading now. =)</p>
<p>Think of this as the Spear Family Christmas Card.</p>
<h3 id="itbegins">It Begins</h3>
<p>This is easily the most exciting year of my life. My wife, Rachelle and I started off the year finding out we were pregnant! It took us 2 years, but we were finally going to have a child. Actually, that's not entirely true. At the 2nd ultrasound, we found out we were going to have <em>children</em>. That's right! We were going to have <em>twins</em>.</p>
<p>Thus also began probably one of the toughest years of my wife's life! It wasn't enough to be pregnant with twins, but we had a number of other major life-altering things happen to us during this time.</p>
<h3 id="dogdays">Dog Days</h3>
<p>Not the least of which, was getting a second dog! Our friends were breeding their Great Dane and we expressed interest in one of the puppies. We came to an agreement to take one of the puppies just days before discovering we were having twins. Crazy as we are (I am), we decided to move forward, and a few months later, this gorgeous little gal moved in with us:</p>
<p><img src="https://cameronspear.com/blog/content/images/2014/12/Cinders.jpg" alt="Year in Review"></p>
<p>Her name is Cinders, and she wasn't this little for long.</p>
<p>Just so Bruno doesn't feel left out, here's the pic of the two of them with her a little more grown up:</p>
<p><img src="https://cameronspear.com/blog/content/images/2014/12/Bruno-and-Cinders.jpg" alt="Year in Review"></p>
<h3 id="movingon">Moving On</h3>
<p>Second to the birth of the babies, the biggest thing to happen this year was that we built a house! I mean, we didn't build it ourselves, we had it built. Building is definitely pretty stressful (and again, doubly so if you're pregnant with twins), but it was definitely worth it. We love our house and it definitely feels like home.</p>
<p><img src="https://cameronspear.com/blog/content/images/2014/12/the-house.jpg" alt="Year in Review"></p>
<p>Packing and moving was crazy. Fortunately, we had lots of help from friends, family and church. Rachelle was put on temporary bed rest shortly before the move, and those close to us really came through in making sure we got all packed while Rachelle was out of commission.</p>
<p>Once we moved, and got things settled down, one would think things would be easier. But alas, Rachelle had a pretty rough 3rd trimester and we had a lot of day trips to the hospital.</p>
<p>Just as we're getting settled in the new house...</p>
<h3 id="doubletrouble">Double Trouble!!</h3>
<p>Our beautiful twins, Madelyn Adelaide and Oliver &quot;Ollie&quot; Wiley were born September 18, 2014.</p>
<p><img src="https://cameronspear.com/blog/content/images/2014/12/The-Twins-Newborn.jpg" alt="Year in Review"></p>
<p>They had to stay about 10 days in the NICU, which was pretty stressful. Rachelle spent a lot of time in the NICU with the babies. We were very grateful that friends and family could help with rides when Rachelle and I's schedules didn't sync up.</p>
<p>Speaking of rides... before the babies were born, our only modes of transportation were either a way too small Dodge Neon, or a 1994 Ford Explorer that I am always very relieved when it turns on. We felt that it was time for a new car! Not even close to the most exciting thing to happen this year, but it is nice to finally own a car that isn't old enough to drive its own car.</p>
<h3 id="moreofthetwinsalready">More of the Twins, Already!</h3>
<p>Ollie was 4 lb 14 oz and Madelyn was 4 lb 10 oz. They had low blood sugars and had a hard time keeping their temperatures, which is why they took them to the NICU. It was about as &quot;unserious&quot; as it gets for having to go the NICU, but they had high risk for (and indeed got) jaundince, so it was good that they were in a place where they could get the care they needed.</p>
<p>Other than that, they were very healthy and undeniably adorable.</p>
<p><img src="https://cameronspear.com/blog/content/images/2014/12/First-Day-Out-of-the-Hospital.jpg" alt="Year in Review"></p>
<p>They wanted to moniter their weights, and at their first few doctors visits, they tracked their growth. It was off the charts! These guys were ready to make up for lost weight and were growing at a much faster rate than expected, which made the doctors (and thus us) happy.</p>
<h3 id="3monthslater">3 Months Later</h3>
<p>We're tired. But we're happy and we're surviving. It wasn't that long ago that it was just the two of us. Now it's the six of us! Our house is so noisy and bursting with life.</p>
<p><img src="https://cameronspear.com/blog/content/images/2014/12/The-Twins-.jpg" alt="Year in Review"></p>
<p>It's been a crazy year. Work's been going well for me, and Rachelle's been an amazing mother, taking great care of all of us and the house. What more could a guy ask for? Triplets next year?</p>
]]></content:encoded></item><item><title><![CDATA[How I Feel About Semicolons in JavaScript]]></title><description><![CDATA[<p>&quot;Hey, what's your policy on using semicolons in JavaScript?&quot;</p>
<p>&quot;Never. You don't need'em. Well, <code>for</code> loops. They're required there.</p>
<p>&quot;And null loops such as: <code>while (something);</code>. But you probably shouldn't be using those anyway, right?</p>
<p>&quot;Oh, and <code>case &quot;foo&quot;: doSomething(); break</code></p>
<p>&quot;Er,</p>]]></description><link>https://cameronspear.com/how-i-feel-about-semicolons-in-javascript/</link><guid isPermaLink="false">5b7c94f2608549006dd7df41</guid><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Sat, 11 Oct 2014 19:58:12 GMT</pubDate><content:encoded><![CDATA[<p>&quot;Hey, what's your policy on using semicolons in JavaScript?&quot;</p>
<p>&quot;Never. You don't need'em. Well, <code>for</code> loops. They're required there.</p>
<p>&quot;And null loops such as: <code>while (something);</code>. But you probably shouldn't be using those anyway, right?</p>
<p>&quot;Oh, and <code>case &quot;foo&quot;: doSomething(); break</code></p>
<p>&quot;Er, and in front of a leading <code>(</code> or <code>[</code> at the start of the line. Can't forget that. Otherwise the expression could be interpreted as a function call or property access, respectively.</p>
<p>&quot;Well, and I guess lines starting with <code>-</code> and <code>+</code> need to be prefixed with a semicolon, but that'll probably like <em>never</em> come up.</p>
<p>&quot;But yeah, just don't forget those simple edge cases and you'll be fine. It's really pretty simple. You'll probably only waste a few hours occasionally on stupid little things you forgot.&quot;</p>
<p>&quot;Simple, eh? I have a pretty simple rule when to use them, too: <em>always</em>. Much easier to remember!&quot;</p>
<hr>
<p>Thanks to <code>npm</code>'s <a href="https://docs.npmjs.com/misc/coding-style#semicolons">style guide</a> that helped me write this article by showing me <em>just</em> how foolish the <strong>don't use them</strong> policy is for semicolons in JavaScript.</p>
]]></content:encoded></item><item><title><![CDATA[Vagrant SSH Command Shortcut]]></title><description><![CDATA[<p><a href="http://www.vagrantup.com/">Vagrant</a> is a tool that is all about distributing a development Virtual Machine (VM) that all the devs on a project can easily set up and so all devs can be working in the same environment.</p>
<p>It has a neat little feature that will sync your folders on the host</p>]]></description><link>https://cameronspear.com/vagrant-ssh-command-shortcut/</link><guid isPermaLink="false">5b7c94f2608549006dd7df40</guid><category><![CDATA[zsh]]></category><category><![CDATA[bash]]></category><category><![CDATA[ssh]]></category><category><![CDATA[vagrant]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Tue, 08 Jul 2014 03:37:52 GMT</pubDate><content:encoded><![CDATA[<p><a href="http://www.vagrantup.com/">Vagrant</a> is a tool that is all about distributing a development Virtual Machine (VM) that all the devs on a project can easily set up and so all devs can be working in the same environment.</p>
<p>It has a neat little feature that will sync your folders on the host to the guest VM, so you don't even have to SSH into the guest VM do most actions.</p>
<p>Occassionally, however, you need to run a command directly on the guest VM, such as a migration. Normally, you have to either run <code>vagrant ssh</code> and then change the directory to where the project is (it's very common to create an alias on the guest that points to the project root in the form of <code>/vagrant/</code>), run the command and then exit to get back to your normal shell.</p>
<p>Or, you can combine that into one command:</p>
<pre><code class="language-bash">vagrant ssh -c &quot;cd /vagrant &amp;&amp; command to run&quot;
</code></pre>
<p>But that is a lot to type out every time!</p>
<p>So I created a little &quot;Vagrant user do&quot; <code>bash</code> function (just place this in your <code>.bashrc</code> (or, if you're like me, your <code>.zshrc</code>)):</p>
<pre><code class="language-bash">function vudo() {
  eval &quot;vagrant ssh -c \&quot;cd /vagrant &amp;&amp; $@\&quot;&quot;
}
</code></pre>
<p><strong>Usage:</strong></p>
<pre><code class="language-bash">$ vudo composer update
$ vudo php artisan migrate
$ vudo anycommandyouwanttorunonyourvmfromyourvagrantfolder
</code></pre>
<p>Now you can quickly and easily run one-off commands in your VM with very little effort.</p>
]]></content:encoded></item><item><title><![CDATA[Server Grab Pattern]]></title><description><![CDATA[<p>Terrible name, I know. But I've been digging this pattern for succinctly and simultaneously getting data from the server and putting it on the <code>$scope</code>.</p>
<p>I present the <strong>Server Grab Pattern</strong>:</p>
<pre><code class="language-javascript">// inside a controller (or whatever. you get the idea):
$q.all({
    users:   api.get('users'),
    books:   api.get('books')</code></pre>]]></description><link>https://cameronspear.com/server-grab-pattern/</link><guid isPermaLink="false">5b7c94f2608549006dd7df3f</guid><category><![CDATA[angular]]></category><category><![CDATA[$q]]></category><category><![CDATA[patterns]]></category><category><![CDATA[promises]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Mon, 30 Jun 2014 23:38:00 GMT</pubDate><content:encoded><![CDATA[<p>Terrible name, I know. But I've been digging this pattern for succinctly and simultaneously getting data from the server and putting it on the <code>$scope</code>.</p>
<p>I present the <strong>Server Grab Pattern</strong>:</p>
<pre><code class="language-javascript">// inside a controller (or whatever. you get the idea):
$q.all({
    users:   api.get('users'),
    books:   api.get('books'),
    bananas: api.get('fruit').then(function (fruit) {
        return _.pluck(fruit, 'banana');
    }
}).then(function (responses) {
	angular.extend($scope, responses);
}).catch(errorHandler);
</code></pre>
<p>The idea here is that it's a very clear way to simultaneously grab a bunch of stuff from the server, maybe do individual manipulations before attaching to the <code>$scope</code> where it's clear where each piece goes, etc, etc.</p>
<p>And then in one line in the response, you put everything on the <code>$scope</code> and you can handle all the errors at once. Once I started using this, I cut down on a lot of lines of code and I personally find it easier to read than what I was using before, too.</p>
<h3 id="whatshappening">What's Happening</h3>
<p><code>$q.all</code> can take an array or map of promises (they don't even actually have to be promises!) and runs them all asyncronously and once <em>all</em> of those promises are returned, then it passes the array/map with the resolved values to the <code>then</code> handler. Since you can chain promises, you can arbitrarily do something with just certain responses and they could even return promises (or not, like in this example, as this example makes it clearer that we're manipulating that data straight from the server).</p>
<p>Yeah, I think promises are pretty dang awesome, too.</p>
<p>It's just a cool little pattern I've been using and wanted to share. Ya know. Since it's 11:37pm on June 30th and my goal was to write 2 posts every month this year...</p>
]]></content:encoded></item><item><title><![CDATA[How cool are $formatters and $parsers?]]></title><description><![CDATA[<p>Angular has this concept in directives called <code>$parsers</code> and <code>$formatters</code>. It's mechanism to be able to format data from model to view so that it's presented in a more human-friendly way to the user, and when they modify it, it can be translated back to a more computer-friendly mode. It</p>]]></description><link>https://cameronspear.com/how-cool-are-formatters-and-parsers/</link><guid isPermaLink="false">5b7c94f2608549006dd7df36</guid><category><![CDATA[angular]]></category><category><![CDATA[directive]]></category><category><![CDATA[$parsers]]></category><category><![CDATA[$formatters]]></category><category><![CDATA[ngModel]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Fri, 20 Jun 2014 16:06:08 GMT</pubDate><content:encoded><![CDATA[<p>Angular has this concept in directives called <code>$parsers</code> and <code>$formatters</code>. It's mechanism to be able to format data from model to view so that it's presented in a more human-friendly way to the user, and when they modify it, it can be translated back to a more computer-friendly mode. It leverages the <code>ngModel</code> controller to do this.</p>
<p><code>$parsers</code>/<code>$formatters</code> are an array of actions that take in input, translate it to either the computer-friendly or human-friendly format respectively and return the translated value.</p>
<h3 id="thecode">The Code</h3>
<pre><code class="language-javascript">angular.module('myApp')
.directive('myDirective', function () {
	return {
    	// $parsers/$formatters live on the
        // ngModel controller, so we need this!
    	require: 'ngModel',
        link: function (scope, elem, attrs, ngModel) {
        	ngModel.$parsers.push(function toModel(input) {
            	// do something to format user input
                // to be more &quot;computer-friendly&quot;
                return modifiedInput;
            });
            
            ngModel.$formatters.push(function toView(input) {
            	// do something to format user input
                // to be more &quot;human-friendly&quot;
                return modifiedInput;
            });
        }
    };
});
</code></pre>
<p>Your <code>$parsers</code>/<code>$formatters</code> can do just about anything, and you don't <em>strictly</em> need to have one of each, but it usually makes sense that if you take an input and apply a matching <code>$parser</code> and <code>$formatter</code> (or vice versa), you would get the same original input. This is just probably the most common use-case, but a hard and fast <em>rule</em> or anything.</p>
<p>Note that we're <strong>pushing</strong> (you can also <strong>unshift</strong>) functions onto our <code>$formatters</code>/<code>$parsers</code>. Remember that inputs can have multiple directives on them, and thus can have multiple <code>$formatters</code>/<code>$parsers</code>.</p>
<h3 id="exampletaglist">Example: Tag List</h3>
<p>In this example, you can enter in a string of comma separated values, and the model will be an actual array of values split by the commas. Conversely, you can set the model to an array and the input will appear as a string of comma separated values.</p>
<p data-height="195" data-theme-id="1909" data-slug-hash="hJuCq" data-default-tab="result" class="codepen">See the Pen <a href="https://codepen.io/CWSpear/pen/hJuCq/">$parsers/$formatters demo</a> by Cameron Spear (<a href="http://codepen.io/CWSpear">@CWSpear</a>) on <a href="http://codepen.io">CodePen</a>.</p>
<script async src="//codepen.io/assets/embed/ei.js"></script>]]></content:encoded></item><item><title><![CDATA[Handle AngularJS Form Errors with Ease]]></title><description><![CDATA[<p>Angular 1.3 is just around the corner and with it comes <a href="https://docs.angularjs.org/api/ngMessages/directive/ngMessages"><code>ngMessages</code></a>. In a lot of ways, it is good and brings a lot to the table in bringing an alternative way of handling errors in a form.</p>
<p>But it's still not simple enough! And I like simple. I</p>]]></description><link>https://cameronspear.com/handle-angularjs-form-errors-with-ease/</link><guid isPermaLink="false">5b7c94f2608549006dd7df3e</guid><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Thu, 29 May 2014 05:53:32 GMT</pubDate><content:encoded><![CDATA[<p>Angular 1.3 is just around the corner and with it comes <a href="https://docs.angularjs.org/api/ngMessages/directive/ngMessages"><code>ngMessages</code></a>. In a lot of ways, it is good and brings a lot to the table in bringing an alternative way of handling errors in a form.</p>
<p>But it's still not simple enough! And I like simple. I work on all sorts of projects, and while I often like the fine-tuning of error handling that Angular affords me, sometimes, I just need something simpler that Just Works™ with minimal effort and configuration.</p>
<p>That's why I built the <a href="https://github.com/CWSpear/angular-form-errors-directive">Form Errors directive</a>. (Catchy name, eh?) It couldn't be simpler to use and the default configuration works 99% of the time (or at least it does for me).</p>
<h3 id="letsseeitinaction">Let's see it in action</h3>
<pre><code class="language-markup">&lt;form name=&quot;theForm&quot;&gt;
  &lt;input type=&quot;text&quot; name=&quot;name&quot; ng-model=&quot;user.name&quot; required&gt;
  &lt;input type=&quot;password&quot; &quot;password&quot; ng-model=&quot;user.name&quot; required ng-minlength=&quot;8&quot;&gt;
  &lt;form-errors&gt;&lt;/form-errors&gt;
  &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
&lt;form&gt;
</code></pre>
<p>That's it. Really. By putting <code>&lt;form-errors&gt;&lt;/form-errors&gt;</code> inside a form, it will display a list errors as we type. We can conditionally show the list if we want (see <a href="http://cwspear.github.io/angular-form-errors-directive/">demo</a>), only after they try to submit it, or we can just show it to them as they type.</p>
<p>In this example, <code>&lt;form-errors&gt;&lt;/form-errors&gt;</code> would display this markup for this form when it's blank:</p>
<pre><code class="language-markup">&lt;ul class=&quot;form-errors&quot;&gt;
  &lt;li class=&quot;form-error&quot;&gt;Name is required.&lt;/li&gt;
  &lt;li class=&quot;form-error&quot;&gt;Password is required.&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>It automagically gets the name to use from the name attribute of the input and <strong>humanizes</strong> it, meaning it turns <code>camelCase</code> into <code>Camel Case</code>, <code>dash-case</code> into <code>Dash Case</code> and <code>snake_case</code> into <code>Snake Case</code>. You can override this to be anything you want with the <code>nice-name</code> attribute on the input. (See <a href="https://github.com/CWSpear/angular-form-errors-directive#custom-names-and-messages">docs</a> for how to do this and also how to display custom messages.)</p>
<p>If you start typing in the Name field in the example above, the <strong>Name is required</strong> message goes away. If you start typing in the Password field, the message is changed to <strong>Password is too short</strong> until the password is at least 8 characters long, then that disappears, too.</p>
<h3 id="easeefficiencytimemoney">Ease = Efficiency = Time = Money</h3>
<p>This thing really has saved me a lot of time because I can be more efficient about how I do the markup and handle the form errors. It may not be the best UX, but when you're doing a ton of forms with a ton of inputs each, this can save you a lot of time.</p>
<p>You can combine this with the <code>ng-invalid</code> classes Angular adds on inputs to get the best of both worlds: immediately drawing them to the error and displaying a message as to what went wrong, all with the minimal amount of effor.</p>
<p>I hope the directive can help you as much as it's helped me. Check out the project on <a href="https://github.com/CWSpear/angular-form-errors-directive">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Two New Site Launches]]></title><description><![CDATA[<p>It's been a busy, <em>busy</em> week. <a href="http://mavrx.io/">Mavrx</a> launched not one, but <em>two</em> ecommerce websites this week. It wasn't planned that way, but just sorta happened. Launching sites—especially ones like ecommerce sites—is always hectic, and it was especially hectic trying to do all this on top of my normal</p>]]></description><link>https://cameronspear.com/two-new-site-launches/</link><guid isPermaLink="false">5b7c94f2608549006dd7df3d</guid><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Sat, 03 May 2014 17:13:00 GMT</pubDate><content:encoded><![CDATA[<p>It's been a busy, <em>busy</em> week. <a href="http://mavrx.io/">Mavrx</a> launched not one, but <em>two</em> ecommerce websites this week. It wasn't planned that way, but just sorta happened. Launching sites—especially ones like ecommerce sites—is always hectic, and it was especially hectic trying to do all this on top of my normal workload.</p>
<p>But we did it! Big thanks to <a href="http://lawrenceallen.com/">Lawrence Allen</a> for desiging these awesome sites and helping me get everything ready to launch.</p>
<h3 id="millenniumsporttechnologies">Millennium Sport Technologies</h3>
<p><a href="https://millenniumsport.net/">Millennium Sport Technologies</a> (MST) sells quality sport supplements. They wanted a quality responsive ecommerce site that they could manage the products themselves (something that they couldn't do on their old site).</p>
<p><a href="https://cameronspear.com/blog/content/images/2014/May/millennium-sport-technologies.png"><img src="https://cameronspear.com/blog/content/images/2014/May/millennium-sport-technologies-scaled.jpg" alt="Millennium Sport Technologies' Home Page"></a></p>
<h3 id="roasthousecoffee">Roast House Coffee</h3>
<p><a href="https://roasthousecoffee.com/">Roast House Coffee</a> (RHC) sells coffee. <a href="https://roasthousecoffee.com/blog/we-did-it-we-won">Really good, high quality coffee</a>. We got the awesome opportunity to not only build them a new website, but we (i.e. Lawrence) redid all their branding and labels, too, which is really exciting!</p>
<p><a href="https://cameronspear.com/blog/content/images/2014/May/roast-house-coffee.png"><img src="https://cameronspear.com/blog/content/images/2014/May/roast-house-coffee-scaled.jpg" alt="Roast House Coffee's Home Page"></a></p>
<h3 id="needawebsite">Need a website?</h3>
<p>If you like what you see and are in need of a new website, please contact us on the <a href="http://mavrx.io/">Mavrx</a> site and we can build something awesome together!</p>
]]></content:encoded></item><item><title><![CDATA[Craft CMS Twig Filter Extension]]></title><description><![CDATA[<p><a href="https://buildwithcraft.com/">Craft</a> is an awesome CMS that if you haven't heard of, you should. It's built on the <a href="http://www.yiiframework.com/">Yii</a> framework (which I haven't really used beyond dabbling with Craft, but I've heard good things) and uses <a href="http://twig.sensiolabs.org/">Twig</a> for templates.</p>
<p>Anyway, this post isn't about how awesome Craft is (which it is)</p>]]></description><link>https://cameronspear.com/craft-cms-twig-filter-extension/</link><guid isPermaLink="false">5b7c94f2608549006dd7df3c</guid><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Fri, 18 Apr 2014 23:47:06 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://buildwithcraft.com/">Craft</a> is an awesome CMS that if you haven't heard of, you should. It's built on the <a href="http://www.yiiframework.com/">Yii</a> framework (which I haven't really used beyond dabbling with Craft, but I've heard good things) and uses <a href="http://twig.sensiolabs.org/">Twig</a> for templates.</p>
<p>Anyway, this post isn't about how awesome Craft is (which it is), it's about a piece of auspiciously missing documents: how to create your own Twig <a href="http://buildwithcraft.com/docs/twig-primer#filters">filter</a> inside of craft!</p>
<h3 id="worditup">Word It Up</h3>
<p>For demonstration purposes, we're going to create a simple &quot;Word It Up&quot; filter that takes a number and converts each letter into its corresponding words (just on a per-digit basis).</p>
<p>For example, if we were to put this in our template:</p>
<pre><code class="language-markup">&lt;p&gt;{{ 123 | wordItUp }}&lt;/p&gt;
</code></pre>
<p>it would output:</p>
<pre><code class="language-markup">&lt;p&gt;one two three&lt;/p&gt;
</code></pre>
<h3 id="buildingthetwigextension">Building the Twig Extension</h3>
<blockquote>
<p>This guide will assume you're familiar with building Craft Plugins. (The full code used in this example is up on <a href="https://github.com/Mavrx/word-it-up">GitHub</a> as a template.)</p>
</blockquote>
<p>Your Twig Extension files will go within a your plugin directory in a directory called <code>twigextensions</code>. So assuming our plugin is named <code>Word It Up</code> (which is it is in this example), our directory structure would look like this:</p>
<pre><code class="language-directory-tree">craft
└── plugins
    └── worditup
        ├── WordItUpPlugin.php
        └── twigextensions
            └── WordItUpTwigExtension.php
</code></pre>
<p>(As an aside, you should definitely checkout the <a href="http://mama.indstate.edu/users/ice/tree/"><code>tree</code></a> command line tool (i.e. <code>brew install tree</code>).)</p>
<p>In our <code>WordItUpPlugin.php</code> file, the only thing special we need to do is add an <code>addTwigExtension</code> function and return an instance of our Twig Extension:</p>
<pre><code class="language-php">public function addTwigExtension()
{
    Craft::import('plugins.worditup.twigextensions.WordItUpTwigExtension');

    return new WordItUpTwigExtension();
}
</code></pre>
<p>Now we go into the <code>WordItUpTwigExtension.php</code> file itself. We need to include use some Twig stuff at the top of our file (along with the Craft namespace):</p>
<pre><code class="language-php">namespace Craft;

use Twig_Extension;
use Twig_Filter_Method;

class WordItUpTwigExtension extends \Twig_Extension
{
	...
</code></pre>
<p>The only other &quot;special&quot; stuff we'd need is a <code>getFilters</code> function that returns Twig-wrappered methods referenced in your class:</p>
<pre><code class="language-php">public function getFilters()
{
    return array(
        'wordItUp' =&gt; new Twig_Filter_Method($this, 'wordItUp'),
    );
}
</code></pre>
<p>All this says is that if we use a <code>wordItUp</code> filter in the Twig template, it will pass the input to your <code>wordItUp</code> function in from the current (<code>$this</code>) class.</p>
<h3 id="extraparameters">Extra Parameters</h3>
<p>For completeness sake, the way filter parameters get mapped from Twig template to filter function is like so:</p>
<pre><code class="language-markup">{% set red = 'red' %}
{% set blue = 'blue' %}
{% set green = 'green' %}
{{ red | someFilter(blue, green) }}
</code></pre>
<p>maps to</p>
<pre><code class="language-php">public function someFilter($one, $two, $three) 
{
	echo $one; // 'red'
	echo $two; // 'blue'
	echo $three; // 'green'
}
</code></pre>
<p>so you can pass in as many extra parameters as you need.</p>
<h3 id="fullexample">Full Example</h3>
<p>Here's the full <code>WordItUpTwigExtension.php</code> file:</p>
<pre><code class="language-php">&lt;?php 
namespace Craft;

use Twig_Extension;
use Twig_Filter_Method;

class WordItUpTwigExtension extends \Twig_Extension
{
    private $digitToWord = array(
        0 =&gt; 'zero',
        1 =&gt; 'one',
        2 =&gt; 'two',
        3 =&gt; 'three',
        4 =&gt; 'four',
        5 =&gt; 'five',
        6 =&gt; 'six',
        7 =&gt; 'seven',
        8 =&gt; 'eight',
        9 =&gt; 'nine',
    );

    public function getName()
    {
        return 'WordItUp';
    }

    public function getFilters()
    {
        return array(
            'wordItUp' =&gt; new Twig_Filter_Method($this, 'wordItUp'),
        );
    }

    public function wordItUp($number)
    {
        $output = array();
        
        foreach (str_split(intval($number)) as $digit) {
            $output[] = $this-&gt;digitToWord[$digit];
        }

        return implode(' ', $output);
    }
}
</code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>Now you can go and install your plugin via the Craft dashboard and use your new filter in a plugin, and voila! You &quot;Worded It Up!&quot;</p>
<p>You can now turn</p>
<pre><code class="language-markup">&lt;p&gt;{{ 123 | wordItUp }}&lt;/p&gt;
</code></pre>
<p>into:</p>
<pre><code class="language-markup">&lt;p&gt;one two three&lt;/p&gt;
</code></pre>
<p>View full plugin on <a href="https://github.com/Mavrx/word-it-up">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Streams in Wiredep]]></title><description><![CDATA[<p><a href="https://github.com/taptapship/wiredep"><code>wiredep</code></a> is a neat little Node package that does one pretty awesome thing: it automatically wires up your Bower components to your HTML/(S)CSS based on dependencies (i.e. it automatically includes them in the correct order!).</p>
<p>You may not have heard of <code>wiredep</code> before, but it's likely you've</p>]]></description><link>https://cameronspear.com/streams-in-wiredep/</link><guid isPermaLink="false">5b7c94f2608549006dd7df3b</guid><category><![CDATA[gulpjs]]></category><category><![CDATA[gruntjs]]></category><category><![CDATA[bower]]></category><category><![CDATA[wiredep]]></category><category><![CDATA[yeoman]]></category><dc:creator><![CDATA[Cameron Spear]]></dc:creator><pubDate>Fri, 04 Apr 2014 21:16:56 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://github.com/taptapship/wiredep"><code>wiredep</code></a> is a neat little Node package that does one pretty awesome thing: it automatically wires up your Bower components to your HTML/(S)CSS based on dependencies (i.e. it automatically includes them in the correct order!).</p>
<p>You may not have heard of <code>wiredep</code> before, but it's likely you've used it if you've used the <a href="https://github.com/stephenplusplus/grunt-wiredep"><code>grunt-wiredep</code></a> (formally <code>grunt-bower-install</code>) <a href="http://gruntjs.com/">Grunt</a> plugin which is a popular one that uses it, and was a precursor to <code>wiredep</code>.</p>
<p>Both the Node package and the Grunt plugin were built by <a href="https://github.com/stephenplusplus">Stephen Sawchuk</a>, a friendly, talented developer from Michighan. I don't know very much about him, even though he has added me to the team that manages <code>wiredep</code>. He works on the Yeoman team and built <code>grunt-wiredep</code> for use in the awesome build systems that come with many Yeoman generators, and eventually extracted the parts out to create what is now known as <code>wiredep</code> and modified the original Grunt plugin to be a wrapper around <code>wiredep</code>.</p>
<p>Anyway, introduction done. On to the meat of the article:</p>
<h3 id="wiredepstreamsandgulpohmy">Wiredep, Streams and Gulp, oh my!</h3>
<p>I've been talking about streams a little bit lately and how it relates to <a href="http://gulpjs.com/">Gulp</a>. Well, streams can be pretty cool, and are a fundamental part of how Gulp works.</p>
<p>You might ask yourself, &quot;Why isn't there a <code>gulp-wiredep</code> pulgin?&quot; Before I answer, I'd liket to remind you of one of Gulp's tenents, taken from their website:</p>
<blockquote>
<p>By preferring code over configuration, gulp keeps simple things simple and makes complex tasks manageable.</p>
</blockquote>
<p>In other words: why create a dedicated <code>gulp-wiredep</code> plugin when you can just use <code>wiredep</code> directly in your Gulp build? Indeed, you can just use <code>wiredep</code> straight up in your Gulp build in its own task. But once you try to do more complex things, you may run into some issues in not being able to fully integrate <code>wiredep</code> with the other streams from Gulp.</p>
<p>That is, until you realize that <code>wiredep</code> supports streams! It wasn't documented until only recently (in part, thanks to my efforts, I might add ;-)), but I was digging into the source code of <code>wiredep</code>, trying to figure out something else, and I noticed that it had some code related to streams. I dug a little deeper, and indeed, <code>wiredep</code> has stream support for perfect integration with Gulp (and anything else that would use a stream).</p>
<p>For example, I <em>had</em> been using <code>wiredep</code> <em>without</em> streams like this:</p>
<pre><code class="language-javascript">/////////////////////
// Without Streams //
/////////////////////

var gulp    = require('gulp');
var wiredep = require('wiredep');

gulp.task('bower', function () {
  wiredep({
    src: './src/footer.html',
    directory: './bower_componets/',
    bowerJson: require('./bower.json'),
  });
});
</code></pre>
<p>which was all well and good until I needed another task that depended on the <code>bower</code> task from being completed. The simplest <a href="https://cameronspear.com/blog/handling-sync-tasks-with-gulp-js/">way to do that</a> was to return a stream. Well, good thing <code>wiredep</code> support streams! Change the above code to this and then we're all good:</p>
<pre><code class="language-javascript">//////////////////
// With Streams //
//////////////////

var gulp    = require('gulp');
// note that we're grabbing the stream function
var wiredep = require('wiredep').stream;

gulp.task('bower', function () {
  gulp.src('./src/footer.html')
    .pipe(wiredep({
      directory: './bower_componets/',
      bowerJson: require('./bower.json'),
    }))
    .pipe(gulp.dest('./dest'));
});
</code></pre>
<p>BAM, just like that, we have full Gulp integration!</p>
<h3 id="conclusion">Conclusion</h3>
<p>As many of you that have been getting into Gulp more have noticed, the solution for a lot of your Gulp needs aren't always a Gulp specific plugin. That's one of the cool things about Gulp: it's just code.</p>
<p><code>wiredep</code> is one of those tools that has everything you need out of the box and there's no need for a Gulp specific plugin. Employ some of the techniques in this post and you too can benefit from the Wiredep magic of simple, auto-linking, Bower dependency management.</p>
]]></content:encoded></item></channel></rss>