Matt BertherJekyll2023-08-06T17:48:20+00:00https://matt.berther.io/Matt Bertherhttps://matt.berther.io/matt@berther.iohttps://matt.berther.io/2016/02/25/continuous-deployment-to-cloudfront-using-circleci2016-02-25T00:00:00+00:002016-02-25T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>Historically, I used a dynamic publishing engine to host this site. The engine would reside on a server that I was responsible for maintaining – both security patches and uptime. Recently though, I made the switch to using the <a href="https://aws.amazon.com/cloudfront/">AWS CloudFront</a> platform to make this site available on the Internet. Key benefits from this approach include:</p>
<ul>
<li><strong>Edge locations around the world.</strong> When someone requests a file from this site, the request is automatically routed to a copy of the file at a data center nearest the visitor, which results in faster download times.</li>
<li><strong>Availability.</strong> When <a href="https://mattberther.io">https://matt.berther.io</a> goes down, a massive team of people from AWS are working on correcting the problem.</li>
<li><strong>Cost.</strong> I pay only for the S3 storage and CloudFront bandwidth I use. Last month, my spend to provide this site’s content in data centers around the world was a whopping $0.64.</li>
</ul>
<p>As I made the transition, I created this site using Jekyll and host the repository on <a href="https://github.com/mattberther/berther.io">github</a>. I wanted to make sure that when I update the github repository that changes are automatically pushed out so visitors receive the most up-to-date version of the site. To do this, I leverage <a href="https://circleci.com">circleci</a> which is a continuous integration platform that follows my github repository and performs actions when changes are pushed.</p>
<p>This post describes the steps I took to configure circleci to send my changes out to CloudFront.</p>
<ol>
<li>Install and configure the s3_website gem</li>
<li>Follow your github project with circleci</li>
<li>Configure the test and deployment commands</li>
<li>Push your project to github</li>
</ol>
<h4 id="install-and-configure-the-s3_website-gem">Install and Configure the s3_website gem</h4>
<p>The <code class="language-plaintext highlighter-rouge">s3_website</code> gem is available on <a href="https://github.com/laurilehmijoki/s3_website">github</a>. Add it to your project’s Gemfile with <code class="language-plaintext highlighter-rouge">gem 's3_website'</code>. The documentation page for the gem is pretty comprehensive and describes the various configuration options. At minimum, you’ll need to perform the following steps:</p>
<ul>
<li>Run <code class="language-plaintext highlighter-rouge">s3_website cfg create</code> to generate the <code class="language-plaintext highlighter-rouge">s3_website.yml</code> file</li>
<li>Modify the <code class="language-plaintext highlighter-rouge">s3_website.yml</code> file to suit your configuration</li>
<li>Run <code class="language-plaintext highlighter-rouge">s3_website cfg apply</code> to configure your S3 bucket to function as a website</li>
</ul>
<p>At minimum, your configuration file will need to have the following elements to support a CloudFront distribution.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">s3_id</span><span class="pi">:</span> <span class="s"><%= ENV['S3_ACCESS_KEY'] %></span>
<span class="na">s3_secret</span><span class="pi">:</span> <span class="s"><%= ENV['S3_SECRET_KEY'] %></span>
<span class="na">s3_bucket</span><span class="pi">:</span> <span class="s"><%= ENV['S3_BUCKET'] %></span>
<span class="na">cloudfront_distribution_id</span><span class="pi">:</span> <span class="s"><%= ENV['AWS_CLOUDFRONT_ID'] %></span>
</code></pre></div></div>
<p>Note that you can use ERB in your yaml file, so <em>please</em> make sure you’re using environment variables to store sensitive information. You’ll want to make sure that these items don’t end up available in your github repository. For a more comprehensive configuration, you can view <a href="https://github.com/mattberther/berther.io/blob/master/s3_website.yml">the file this site uses</a>.</p>
<h4 id="follow-the-github-project-with-circleci">Follow the github project with circleci</h4>
<p>To have circleci listen for changes to your github repository, login to your circleci account and select ‘Add Projects’ from the left sidebar. Select the GitHub account and then the repository you’re interested in following. This should start the project building. However, before the build succeeds, we’ll need to configure the test and deployment commands.</p>
<h4 id="configure-the-test-and-deployment-commands">Configure the test and deployment commands</h4>
<p>Within your circleci project, select settings and <code class="language-plaintext highlighter-rouge">Test Commands</code>. In the post-test commands section, type <code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code>. This tells the circleci environment to build your jekyll site.</p>
<p>Via the website, you can configure deployments using Heroku or AWS CodeDeploy. However, since we’ll be deploying to S3 and CloudFront, we need to configure a custom deployment to use the s3_website gem we configured earlier. To tell circleci to use this command, create a <code class="language-plaintext highlighter-rouge">circle.yml</code> file in the root of your github project with the following contents.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">deployment</span><span class="pi">:</span>
<span class="na">aws</span><span class="pi">:</span>
<span class="na">branch</span><span class="pi">:</span> <span class="s">master</span>
<span class="na">commands</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">bundle exec s3_website push</span>
</code></pre></div></div>
<h4 id="push-your-project-to-github">Push your project to github</h4>
<p>Using your preferred technique, push your repository to github. In under a couple minutes from pushing the changes to github, your changes are live on the interwebs.</p>
<p><a href="https://matt.berther.io/2016/02/25/continuous-deployment-to-cloudfront-using-circleci/">Continuous Deployment to CloudFront Using circleci</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on February 25, 2016.</p>https://matt.berther.io/2016/02/13/exporting-a-jsonresume-with-gulp2016-02-13T00:00:00+00:002016-02-13T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>Ive received questions from readers wondering about the résumé link on my page. I created the résumé using <a href="http://jsonresume.org">JSON Resume</a> with the hope that it would make it easier to keep it up-to-date. I also love the idea of an open standard for résumés. So far, I have been very happy with the project and I am very grateful to the awesome team behind JSON Resume.</p>
<p>However, re-publishing the résumé became a manual step that I would sometimes forget when I rebuilt this site. This site is powered by <a href="https://jekyllrb.com">Jekyll</a> and <a href="http://gulpjs.com">Gulp</a> helps me with some of the other asset related things.</p>
<p>I looked for an existing gulp plugin that would allow me to create HTML from my JSON Resume file. Since nothing existed, I did what anyone who had a few hours to kill would do… I made my own.</p>
<h2 id="introducing-gulp-resume">Introducing gulp-resume</h2>
<p>The usage of this plugin is what you would expect. Make sure you <code class="language-plaintext highlighter-rouge">npm install --save-dev gulp-resume</code> and then add the following to your <code class="language-plaintext highlighter-rouge">Gulpfile.js</code>:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">resume</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp-resume</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">rename</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp-rename</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">resume</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">src</span><span class="p">(</span><span class="dl">'</span><span class="s1">resume.json</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">resume</span><span class="p">({</span>
<span class="na">format</span><span class="p">:</span> <span class="dl">'</span><span class="s1">html</span><span class="dl">'</span><span class="p">,</span>
<span class="na">theme</span><span class="p">:</span> <span class="dl">'</span><span class="s1">elegant</span><span class="dl">'</span>
<span class="p">}))</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">rename</span><span class="p">(</span><span class="dl">'</span><span class="s1">resume.html</span><span class="dl">'</span><span class="p">))</span>
<span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">));</span>
<span class="p">});</span>
</code></pre></div></div>
<h2 id="download-links">Download links</h2>
<p><a href="https://github.com/mattberther/gulp-resume">gulp-resume on GitHub</a> |
<a href="https://www.npmjs.com/package/gulp-resume">gulp-resume on npm</a></p>
<p><a href="https://matt.berther.io/2016/02/13/exporting-a-jsonresume-with-gulp/">Exporting a JSON Resume with gulp</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on February 13, 2016.</p>https://matt.berther.io/2015/02/03/how-to-resize-aws-ec2-ebs-volumes2015-02-03T00:00:00+00:002015-02-03T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>It is impossible to resize an EBS volume. However, by creating a copy of the volume that is either larger or smaller, you can simulate a resize. Doing this with EBS volumes can be challenging, especially when they are mounted as the root device on an EC2 instance. This post is intended to provide step-by-step directions on how to either expand or shrink the size of an EBS volume.</p>
<p><strong>Before</strong> you start the steps below to resize your volume, please make sure that you have a backup. This means, shutting down your EC2 instance and taking a snapshot of the root volume. This will allow you to come back should things go horribly wrong.</p>
<h2 id="shrinking-an-ebs-volume">Shrinking an EBS Volume</h2>
<p>When you wish to shrink an EBS root volume, you will need to start a new, small EC2 instance that you can attach the volume you wish to resize. A t2.micro instance should be more than sufficient for this task. Once you have this instance created, proceed with the following steps.</p>
<ol>
<li>You took a backup, right? If you did not, stop your EC2 instance and take a snapshot now. Seriously, I’ll wait</li>
<li>Create a new EBS volume that is the size you wish to shrink to</li>
<li>Detach the volume you wish to resize from the current EC2 instance and attach both volumes to the new, small EC2 instance you created
<ul>
<li>Mount the old volume as /dev/sdf (this becomes /dev/xvdf)</li>
<li>Mount the new volume as /dev/sdg (this becomes /dev/xvdg)</li>
</ul>
</li>
<li>Power on the new, small instance and wait for it to come online</li>
<li>SSH into the instance and run the following commands</li>
<li>To ensure that the file system is in order, run <code class="language-plaintext highlighter-rouge">sudo e2fsck -f /dev/xvdf1</code>. If you’re resizing a different partition on the drive, change the number 1 to the partition number you wish to resize.</li>
<li>If the e2fsck command ran without errors, now run <code class="language-plaintext highlighter-rouge">sudo resize2fs -M -p /dev/xvdf1</code>. Again, change the 1 to the partition number you wish to resize if you’re not resizing the first one.</li>
<li>The last line from the resize2fs command should tell you how many 4k blocks the filesystem now is. To calculate the number of 16MB blocks you need, use the following formula: blockcount * 4 / (16 * 1024). Round this number up to give yourself a little buffer.</li>
<li>If you dont yet have a partition on your new volume (/dev/xvdg1), use <a href="http://www.howtogeek.com/106873/how-to-use-fdisk-to-manage-partitions-on-linux/">fdisk to create one</a>.</li>
<li>Execute the following command, using the number you came up with in the previous step. <code class="language-plaintext highlighter-rouge">sudo dd bs=16M if=/dev/xvdf1 of=/dev/xvdg1 count=numberfrompreviousstep</code>. Depending on how large your volume is this may take several minutes to run – let it finish.</li>
<li>After the copy finishes, resize and check and make sure that everything is in order with the new filesystem by running <code class="language-plaintext highlighter-rouge">sudo resize2fs -p /dev/xvdg1</code> followed by <code class="language-plaintext highlighter-rouge">sudo e2fsck -f /dev/xvdg1</code>.</li>
<li>After this step is complete, detach both volumes from the new instance you created. Attach the shrunken volume to the old EC2 instance as /dev/sda1 (your boot device) and restart your old instance. Save the previous, larger volume until you’ve validated that everything is working properly. When you’ve verified things are working well, feel free to delete the new EC2 instance you created, plus the larger volume and snapshot.</li>
</ol>
<h2 id="expanding-an-ebs-volume">Expanding an EBS Volume</h2>
<p>Expanding the size of an EBS volume is a bit easier, since we dont have to execute a disk-disk copy. To expand the size of the volume, execute the following steps:</p>
<ol>
<li>You took a backup, right? If you did not, stop your EC2 instance and take a snapshot now. Seriously, I’ll wait</li>
<li>Create a new EBS volume from the snapshot specifying the new, larger size</li>
<li>Attach the new EBS volume to your existing EC2 instance, as /dev/sda1 if this is the root volume</li>
<li>Power on your existing instance and wait for it to come online</li>
<li>SSH into the instance and run the following commands</li>
<li>To ensure that the file system is in order, run <code class="language-plaintext highlighter-rouge">sudo e2fsck -f /dev/xvda1</code>. If you’re resizing a different partition on the drive, change the number 1 to the partition number you wish to resize.</li>
<li>If the e2fsck command ran without errors, now run <code class="language-plaintext highlighter-rouge">sudo resize2fs -p /dev/xvda1</code>. Again, change the 1 to the partition number you wish to resize if you’re not resizing the first one.</li>
<li>Save the previous, smaller volume and the snapshot until you’ve validated that everything is working properly. When you’ve verified things are working well, feel free to delete the original volume and snapshot.</li>
</ol>
<p>I hope these instructions were able to help you. If you need some extra help, please feel free to shout out in the comments.</p>
<p><a href="https://matt.berther.io/2015/02/03/how-to-resize-aws-ec2-ebs-volumes/">How to Resize AWS EC2 EBS Volumes</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on February 03, 2015.</p>https://matt.berther.io/2015/02/01/good-fast-cheap-pick-two2015-02-01T00:00:00+00:002015-02-01T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>Excellent Venn diagram describing the tradeoffs of the classic project management dilemma (author unknown).</p>
<p><img src="/uploads/2015/02/project_management.jpg" alt="project management dilemma" /></p>
<p><a href="https://matt.berther.io/2015/02/01/good-fast-cheap-pick-two/">Good, Fast, Cheap. Pick Two.</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on February 01, 2015.</p>https://matt.berther.io/2014/08/13/digital-ocean-dokku-and-ssltls2014-08-13T00:00:00+00:002014-08-13T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<h3 id="background">Background</h3>
<p>Heroku powered this website for the past several years. I had no problems with the platform. Deployments were reliable and fast. However, when the time came to add SSL support to the site, Heroku became cost-prohibitive for the volume of traffic this site generates. Protecting the privacy of my readers led me to explore new ways to host this site.</p>
<h3 id="requirements">Requirements</h3>
<p>I had a small set of requirements from a hosting platform. It needs to support SSL/TLS, it needs to be fast, and it needs to be inexpensive. I required SSL/TLS support because I wanted to to my part to secure the privacy of visitors. This site runs on a small, <a href="https://sinatrarb.com">Sinatra</a>-based application. While speed was important, I think that most any host wouldve been able to meet the speed requirement. Last, it needed to be inexpensive – this site is a hobby for me and I wanted to limit my costs to less than $10 per month.</p>
<p>After doing some exploring and research, I found Digital Ocean (<a href="https://www.digitalocean.com/?refcode=bf3672992ae5">referral link</a>). Digital Ocean allowed me to configure a 512mb/1cpu instance with 20gb of ssd storage for $5.00/month ($0.007/hour). As this is a full VPS instance, I had the capability of installing whatever I wanted on it (including my SSL certificate). One of the nice things about Digital Ocean is that you can quickly bring up many different applications on your droplet. One of the applications you can install is <a href="https://github.com/progrium/dokku">Dokku</a>, which advertises itself as a mini-Heroku powered by Docker written in less than 100 lines of BASH script.</p>
<h3 id="setup">Setup</h3>
<p>The setup of Dokku and DigitalOcean is straightforward. There is a page on the DigitalOcean site that describes exactly <a href="https://www.digitalocean.com/community/tutorials/how-to-use-the-digitalocean-dokku-application">what you need to do</a> to set up the infrastructure. After I installed the Dokku application on the DigitialOcean droplet, I needed to set up a remote to the website’s git repository. Because I want to deploy to a naked domain (ie: berther.io vs www.berther.io), I needed to name my dokku application the full domain.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git remote add dokku dokku@berther.io:berther.io
</code></pre></div></div>
<p>Now, whenever I want to publish this website, I push to that new remote with</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git push dokku master
</code></pre></div></div>
<p>The last remaining piece was to add SSL support. I ordered a wildcard certificate from DNSimple (<a href="https://dnsimple.com/r/075fa436681f4a">referral link</a>). After I received the certificate, I copied the key and the certificate to my DigitalOcean droplet and copied them into my application’s tls folder. In this case, that was <code class="language-plaintext highlighter-rouge">~/dokku/berther.io/tls</code>. Once the files were in place, I redeployed the app. When I redeployed the app, Dokku detected the certificate and set up nginx to accept traffic on the HTTPS (443) port.</p>
<h3 id="wrapping-up">Wrapping Up</h3>
<p>At the end of all this, I have my own mini-Heroku with full SSL support for $5/month. This made it easy to secure my little corner of the interwebs. It also makes it easy for you to do – so what are you waiting for?</p>
<p><a href="https://matt.berther.io/2014/08/13/digital-ocean-dokku-and-ssltls/">Digital Ocean, Dokku, and SSL/TLS</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on August 13, 2014.</p>https://matt.berther.io/2014/08/11/most-popular-articles-from-google-analytics-api-part-22014-08-11T00:00:00+00:002014-08-11T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>In <a href="/2014/08/08/most-popular-articles-from-google-analytics-api-part-1">part one</a> of this series, we set up the Google API and created a simple class that interacts with the API to return the most popular articles from Google Analytics. We can make a few enhancements to the class from the previous series to make things a little better. These enhancements include:</p>
<ul>
<li>filtering and limiting the number of results that come back</li>
<li>caching the discovered API so we dont have a round trip on every access</li>
<li>security enhancements to avoid storing credentials in your source code repository</li>
</ul>
<h3 id="filtering-and-limiting">Filtering and limiting</h3>
<p>By default, the Google API client returns a large number of resources. For this website, I wanted to only return the ten most popular articles. I also wanted to filter certain pages from the result set. For example, I did not want the home page showing up in the list of most popular articles. Fortunately, accomplishing both of those goals was as easy as adding some parameters to the api call.</p>
<p>Since we know that the articles on the site follow a certain naming convention (YYYY/MM/DD/slug), we can put together a simple regular expression to pass in to the filters parameter.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="c1"># previous methods omitted</span>
<span class="k">def</span> <span class="nf">most_popular</span>
<span class="n">authorize!</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="ss">api_method: </span><span class="n">api</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">ga</span><span class="p">.</span><span class="nf">get</span><span class="p">,</span>
<span class="ss">parameters: </span><span class="p">{</span>
<span class="s1">'ids'</span> <span class="o">=></span> <span class="s2">"ga:</span><span class="si">#{</span><span class="n">profile_id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span>
<span class="s1">'start-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">prev_month</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'end-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'dimensions'</span> <span class="o">=></span> <span class="s1">'ga:pagePath,ga:pageTitle'</span><span class="p">,</span>
<span class="s1">'metrics'</span> <span class="o">=></span> <span class="s1">'ga:pageviews'</span><span class="p">,</span>
<span class="s1">'sort'</span> <span class="o">=></span> <span class="s1">'-ga:pageviews'</span><span class="p">,</span>
<span class="s1">'max-results'</span> <span class="o">=></span> <span class="mi">10</span><span class="p">,</span>
<span class="s1">'filters'</span> <span class="o">=></span> <span class="s1">'ga:pagePath=~^\/\d{4}\/\d{2}\/\d{2}[^/]*'</span>
<span class="p">}).</span><span class="nf">data</span><span class="p">.</span><span class="nf">rows</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This is much better – now we’re only getting a small number of articles that we actually want to list on our most popular articles page.</p>
<h3 id="caching">Caching</h3>
<p>In the current implementation, the api method attempts to discover the Google API every time it is accessed. We can cache this non-changing data onto the file system and load from there, rather than over the network. Implementing the api caching is also easy to do. Update the api method this way:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="c1"># previous methods omitted</span>
<span class="k">def</span> <span class="nf">api</span>
<span class="n">api_version</span> <span class="o">=</span> <span class="s1">'v3'</span>
<span class="n">cached_api_file</span> <span class="o">=</span> <span class="s2">"analytics-</span><span class="si">#{</span><span class="n">api_version</span><span class="si">}</span><span class="s2">.cache"</span>
<span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">exist?</span><span class="p">(</span><span class="n">cached_api_file</span><span class="p">)</span>
<span class="vi">@api</span> <span class="o">=</span> <span class="vi">@client</span><span class="p">.</span><span class="nf">discovered_api</span><span class="p">(</span><span class="s1">'analytics'</span><span class="p">,</span> <span class="n">api_version</span><span class="p">)</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">cached_api_file</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">dump</span><span class="p">(</span><span class="n">api</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="vi">@api</span> <span class="o">||=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">cached_api_file</span><span class="p">))</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This is also a nice improvement, especially for cases where the class is reused. The api discovery is now stored as an instance variable and loaded from file if the api has already been discovered.</p>
<h3 id="security">Security</h3>
<p>The last piece to address is mostly an enhancement in the interest of security. As most of you probably know, a generally accepted way of storing secrets is to add them to the environment, rather than hardcoding them in your source code. To that end, we just need to update our four helper methods to read their values from the environment.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="c1"># previous methods omitted</span>
<span class="k">def</span> <span class="nf">key_file</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'KEY_FILE'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">key_password</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'KEY_PASSWORD'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">service_account</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'SERVICE_ACCOUNT'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">profile_id</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'PROFILE_ID'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This concludes the enhancements for the AnalyticsAPI class – and also this series. I hope that you learned how to activate and interact with the Google APIs in this series. For your reference, the entire AnalyticsAPI class is included below.</p>
<h3 id="final-class">Final class</h3>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">app_name</span><span class="p">,</span> <span class="n">app_version</span><span class="p">)</span>
<span class="vi">@client</span> <span class="o">=</span> <span class="no">Google</span><span class="o">::</span><span class="no">APIClient</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">application_name: </span><span class="n">app_name</span><span class="p">,</span>
<span class="ss">application_version: </span><span class="n">app_version</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">most_popular</span>
<span class="n">authorize!</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="ss">api_method: </span><span class="n">api</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">ga</span><span class="p">.</span><span class="nf">get</span><span class="p">,</span>
<span class="ss">parameters: </span><span class="p">{</span>
<span class="s1">'ids'</span> <span class="o">=></span> <span class="s2">"ga:</span><span class="si">#{</span><span class="n">profile_id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span>
<span class="s1">'start-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">prev_month</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'end-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'dimensions'</span> <span class="o">=></span> <span class="s1">'ga:pagePath,ga:pageTitle'</span><span class="p">,</span>
<span class="s1">'metrics'</span> <span class="o">=></span> <span class="s1">'ga:pageviews'</span><span class="p">,</span>
<span class="s1">'sort'</span> <span class="o">=></span> <span class="s1">'-ga:pageviews'</span><span class="p">,</span>
<span class="s1">'max-results'</span> <span class="o">=></span> <span class="mi">10</span><span class="p">,</span>
<span class="s1">'filters'</span> <span class="o">=></span> <span class="s1">'ga:pagePath=~^\/\d{4}\/\d{2}\/\d{2}[^/]*'</span>
<span class="p">}).</span><span class="nf">data</span><span class="p">.</span><span class="nf">rows</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">authorize!</span>
<span class="n">key</span> <span class="o">=</span> <span class="no">Google</span><span class="o">::</span><span class="no">APIClient</span><span class="o">::</span><span class="no">KeyUtils</span><span class="p">.</span><span class="nf">load_from_pkcs12</span><span class="p">(</span><span class="n">key_file</span><span class="p">,</span> <span class="n">key_password</span><span class="p">)</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">authorization</span> <span class="o">=</span> <span class="no">Signet</span><span class="o">::</span><span class="no">OAuth2</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">token_credential_uri: </span><span class="s1">'https://accounts.google.com/o/oauth2/token'</span><span class="p">,</span>
<span class="ss">audience: </span><span class="s1">'https://accounts.google.com/o/oauth2/token'</span><span class="p">,</span>
<span class="ss">scope: </span><span class="s1">'https://www.googleapis.com/auth/analytics.readonly'</span><span class="p">,</span>
<span class="ss">issuer: </span><span class="n">service_account</span><span class="p">,</span>
<span class="ss">signing_key: </span><span class="n">key</span><span class="p">)</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">authorization</span><span class="p">.</span><span class="nf">fetch_access_token!</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">api</span>
<span class="n">api_version</span> <span class="o">=</span> <span class="s1">'v3'</span>
<span class="n">cached_api_file</span> <span class="o">=</span> <span class="s2">"analytics-</span><span class="si">#{</span><span class="n">api_version</span><span class="si">}</span><span class="s2">.cache"</span>
<span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">exist?</span><span class="p">(</span><span class="n">cached_api_file</span><span class="p">)</span>
<span class="vi">@api</span> <span class="o">=</span> <span class="vi">@client</span><span class="p">.</span><span class="nf">discovered_api</span><span class="p">(</span><span class="s1">'analytics'</span><span class="p">,</span> <span class="n">api_version</span><span class="p">)</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">cached_api_file</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">dump</span><span class="p">(</span><span class="n">api</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="vi">@api</span> <span class="o">||=</span> <span class="no">Marshal</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">cached_api_file</span><span class="p">))</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">key_file</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'KEY_FILE'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">key_password</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'KEY_PASSWORD'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">service_account</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'SERVICE_ACCOUNT'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">profile_id</span><span class="p">;</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'PROFILE_ID'</span><span class="p">];</span> <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p><a href="https://matt.berther.io/2014/08/11/most-popular-articles-from-google-analytics-api-part-2/">Most Popular articles from Google Analytics API, Part 2</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on August 11, 2014.</p>https://matt.berther.io/2014/08/08/most-popular-articles-from-google-analytics-api-part-12014-08-08T00:00:00+00:002014-08-08T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>Adding a page that lists the most popular articles from your website using data from Google Analytics is a relatively straightforward process. Recently, I added <a href="/popular">this feature</a> to this site. This article intends to walk you through how to set up your application to access data from the Google API.</p>
<h2 id="enabling-api-access">Enabling API access</h2>
<p>Before we are able to query the Google API, we will need to visit the <a href="https://console.developers.google.com">Google Developers Console</a>. Once there, click Create Project to create a new project that will be used to control the APIs you will interact with.</p>
<p>After the project has been created, select apis under the apis & auth section in the left navigation. This will bring a list of all the Google APIs that your project can access. For this feature, we are interested in activating the Analytics API, so go ahead do do that.</p>
<p><img src="/uploads/2014/08/analytics_api.png" alt="Analytics API turned on" title="Turning on Analytics API" /></p>
<p>After you enable the Analytics API, select the Credentials link in the left navigation and then click on Create New Client ID. Since we’re calling the API on behalf of our application, create a new Service Account. After the service account is created, select Generate new P12 key and make note of the private key’s password. This step will also download a key file to your computer. You’ll need to copy this key file to a location that your application can access.</p>
<p>To move on to the next step, you will need the following items. Make sure you have all of these before you move on.</p>
<ul>
<li>The private key password</li>
<li>The p12 file</li>
<li>The service account’s email address</li>
</ul>
<p>Now, we need to enable the Google service account to access our analytics data. To do this, log in to Google Analytics and select the Admin tab. Under the View section, select User Management and add permissions for the Google service account email address to read and analyze your analytics data.</p>
<p>To query the API, you will also need your Google profile ID, which can be found under View Settings in the Admin section.</p>
<p><img src="/uploads/2014/08/view_settings.png" alt="Finding your profile ID" title="Finding your Profile ID" /></p>
<h2 id="writing-the-code">Writing the Code</h2>
<p>From the previous step, you should now have four items:</p>
<ul>
<li>The private key password</li>
<li>The p12 file</li>
<li>The email address of the service account</li>
<li>The profile ID of your Google Analytics property</li>
</ul>
<p>In this section, we will use the google-api-client Ruby gem to interact with the Analytics API and return a list of most popular posts.</p>
<p>Before we go any further, we’ll need to add the google-api-client to our Gemfile:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">gem</span> <span class="s1">'google-api-client'</span>
</code></pre></div></div>
<p>After we bundle our app, we can then start creating a class to interact with the API. This class needs to do a couple of things:</p>
<ol>
<li>Initialize the Google::APIClient</li>
<li>Authorize the client using the keys and account information from the previous step</li>
<li>Discover the API</li>
<li>Execute the API method</li>
</ol>
<p>To get started, let’s initialize the Google APIClient. The constructor allows you to specify an application name and application version. To make this generic, let’s allow our wrapper class to accept these parameters.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'google/api_client'</span>
<span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">app_name</span><span class="p">,</span> <span class="n">app_version</span><span class="p">)</span>
<span class="vi">@client</span> <span class="o">=</span> <span class="no">Google</span><span class="o">::</span><span class="no">APIClient</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">application_name: </span><span class="n">app_name</span><span class="p">,</span>
<span class="ss">application_version: </span><span class="n">app_version</span><span class="p">)</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">key_file</span><span class="p">;</span> <span class="s1">'/path/to/key.p12'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">key_password</span><span class="p">;</span> <span class="s1">'privatekeypassword'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">service_account</span><span class="p">;</span> <span class="s1">'serviceaccount@email.address'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">profile_id</span><span class="p">;</span> <span class="s1">'123456'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Now that we have a Google APIClient instantiated, let’s build the authorize method. Authorization uses OAuth2 and is signed with the p12 key downloaded from the previous step. The method looks like this:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="c1"># previous methods omitted</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">authorize!</span>
<span class="n">key</span> <span class="o">=</span> <span class="no">Google</span><span class="o">::</span><span class="no">APIClient</span><span class="o">::</span><span class="no">KeyUtils</span><span class="p">.</span><span class="nf">load_from_pkcs12</span><span class="p">(</span><span class="n">key_file</span><span class="p">,</span> <span class="n">key_password</span><span class="p">)</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">authorization</span> <span class="o">=</span> <span class="no">Signet</span><span class="o">::</span><span class="no">OAuth2</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">token_credential_uri: </span><span class="s1">'https://accounts.google.com/o/oauth2/token'</span><span class="p">,</span>
<span class="ss">audience: </span><span class="s1">'https://accounts.google.com/o/oauth2/token'</span><span class="p">,</span>
<span class="ss">scope: </span><span class="s1">'https://www.googleapis.com/auth/analytics.readonly'</span><span class="p">,</span>
<span class="ss">issuer: </span><span class="n">service_account</span><span class="p">,</span>
<span class="ss">signing_key: </span><span class="n">key</span><span class="p">)</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">authorization</span><span class="p">.</span><span class="nf">fetch_access_token!</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>The Google APIClient allows us to discover the API, so we’ll create a helper method that returns the discovered API which our method can then use to interact with the API.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="c1"># previous methods omitted</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">api</span>
<span class="n">api_version</span> <span class="o">=</span> <span class="s1">'v3'</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">discovered_api</span><span class="p">(</span><span class="s1">'analytics'</span><span class="p">,</span> <span class="n">api_version</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Now, we’ve got the infrastructure in place to query the analytics api. We’ll do that with the following method:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="c1"># previous methods omitted</span>
<span class="k">def</span> <span class="nf">most_popular</span>
<span class="n">authorize!</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="ss">api_method: </span><span class="n">api</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">ga</span><span class="p">.</span><span class="nf">get</span><span class="p">,</span>
<span class="ss">parameters: </span><span class="p">{</span>
<span class="s1">'ids'</span> <span class="o">=></span> <span class="s2">"ga:</span><span class="si">#{</span><span class="n">profile_id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span>
<span class="s1">'start-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">prev_month</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'end-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'dimensions'</span> <span class="o">=></span> <span class="s1">'ga:pagePath,ga:pageTitle'</span><span class="p">,</span>
<span class="s1">'metrics'</span> <span class="o">=></span> <span class="s1">'ga:pageviews'</span><span class="p">,</span>
<span class="s1">'sort'</span> <span class="o">=></span> <span class="s1">'-ga:pageviews'</span>
<span class="p">}).</span><span class="nf">data</span><span class="p">.</span><span class="nf">rows</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>We now have a class that can query the Analytics API and return a list of the URLs and titles of our most popular articles for the previous month sorted in descending order by the number of page views. Now, lets look at some enhancements we can make to the class to make it more flexible. We’ll look at that in <a href="/2014/08/11/most-popular-articles-from-google-analytics-api-part-2">part two</a>, coming soon. For now, the class we have created is presented in its entirety below:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AnalyticsAPI</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">app_name</span><span class="p">,</span> <span class="n">app_version</span><span class="p">)</span>
<span class="vi">@client</span> <span class="o">=</span> <span class="no">Google</span><span class="o">::</span><span class="no">APIClient</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">application_name: </span><span class="n">app_name</span><span class="p">,</span>
<span class="ss">application_version: </span><span class="n">app_version</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">most_popular</span>
<span class="n">authorize!</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="ss">api_method: </span><span class="n">api</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">ga</span><span class="p">.</span><span class="nf">get</span><span class="p">,</span>
<span class="ss">parameters: </span><span class="p">{</span>
<span class="s1">'ids'</span> <span class="o">=></span> <span class="s2">"ga:</span><span class="si">#{</span><span class="n">profile_id</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span>
<span class="s1">'start-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">prev_month</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'end-date'</span> <span class="o">=></span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s1">'%Y-%m-%d'</span><span class="p">),</span>
<span class="s1">'dimensions'</span> <span class="o">=></span> <span class="s1">'ga:pagePath,ga:pageTitle'</span><span class="p">,</span>
<span class="s1">'metrics'</span> <span class="o">=></span> <span class="s1">'ga:pageviews'</span><span class="p">,</span>
<span class="s1">'sort'</span> <span class="o">=></span> <span class="s1">'-ga:pageviews'</span>
<span class="p">}).</span><span class="nf">data</span><span class="p">.</span><span class="nf">rows</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">authorize!</span>
<span class="n">key</span> <span class="o">=</span> <span class="no">Google</span><span class="o">::</span><span class="no">APIClient</span><span class="o">::</span><span class="no">KeyUtils</span><span class="p">.</span><span class="nf">load_from_pkcs12</span><span class="p">(</span><span class="n">key_file</span><span class="p">,</span> <span class="n">key_password</span><span class="p">)</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">authorization</span> <span class="o">=</span> <span class="no">Signet</span><span class="o">::</span><span class="no">OAuth2</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">token_credential_uri: </span><span class="s1">'https://accounts.google.com/o/oauth2/token'</span><span class="p">,</span>
<span class="ss">audience: </span><span class="s1">'https://accounts.google.com/o/oauth2/token'</span><span class="p">,</span>
<span class="ss">scope: </span><span class="s1">'https://www.googleapis.com/auth/analytics.readonly'</span><span class="p">,</span>
<span class="ss">issuer: </span><span class="n">service_account</span><span class="p">,</span>
<span class="ss">signing_key: </span><span class="n">key</span><span class="p">)</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">authorization</span><span class="p">.</span><span class="nf">fetch_access_token!</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">api</span>
<span class="n">api_version</span> <span class="o">=</span> <span class="s1">'v3'</span>
<span class="vi">@client</span><span class="p">.</span><span class="nf">discovered_api</span><span class="p">(</span><span class="s1">'analytics'</span><span class="p">,</span> <span class="n">api_version</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">key_file</span><span class="p">;</span> <span class="s1">'/path/to/key.p12'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">key_password</span><span class="p">;</span> <span class="s1">'privatekeypassword'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">service_account</span><span class="p">;</span> <span class="s1">'serviceaccount@email.address'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">def</span> <span class="nf">profile_id</span><span class="p">;</span> <span class="s1">'123456'</span><span class="p">;</span> <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p><a href="https://matt.berther.io/2014/08/08/most-popular-articles-from-google-analytics-api-part-1/">Most Popular articles from Google Analytics API, Part 1</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on August 08, 2014.</p>https://matt.berther.io/2014/07/26/running-gulpjs-tasks-in-order2014-07-26T00:00:00+00:002014-07-26T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>When I did the redesign of this site, I wanted to also do some things that had been on my mind for a while, including combining and minifying my javascript and css resources. By doing so, I hoped that pages would load even faster than they already did.</p>
<p>As I looked at how best to do this, I saw that there were several tools available to do this. One evening, I was part of a Twitter conversation between <a href="http://www.hanselman.com/">Scott Hanselman</a> and <a href="http://jden.us/">Jason Denizac</a>. During this conversation, I learned of a couple of tools called <a href="http://gruntjs.com/">Grunt</a> and <a href="gulpjs.com">GulpJS</a>. These two tools are part of an ecosystem of tools called client-side task runners that run on the nodejs platform.</p>
<p>At the time of this writing, GulpJS seems to be the new kid on the block. Its primary advantage is a much terser syntax and a stream-based processing model. Based on this, it was the one I chose for the build system for this site. There are many sites out there that describe exactly how to get Gulp and this article will not dive into those.</p>
<p>Due to the asynchronous processing of the gulp file, some resources would complete before others. This sometimes caused confusion and unexpected output. For example, the clean task might take longer to run than the compliation tasks. As a result, that task might incorrectly delete items after compilation.</p>
<p>There is a way to run gulpjs tasks in order – using a plugin called <a href="https://github.com/OverZealous/run-sequence">run-sequence</a>. You install the run-sequence plugin in the same way that you install any other gulp plugin: <code class="language-plaintext highlighter-rouge">npm install run-sequence --save-dev</code>.</p>
<p>Once you install the plugin, you can define a task that has several task steps. Within this task, you can use the run-sequence plugin to define which tasks need to wait on others before completing. You can pass two kinds of parameters to the plugin, either a single task name or an array of several task names.</p>
<p>When passing a single task name that task must complete before the next task(s) begin. When passing several task names as an array, all the tasks in that array will run in parallel. However, all the tasks in the array must complete before the next task starts. This allows the developer to be specific with task dependencies.</p>
<p>For an example, I will show an abbreviated version of the gulpfile.js used for this site below.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">runSequence</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">run-sequence</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// several task definitions removed for brevity</span>
<span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">runSequence</span><span class="p">(</span><span class="dl">'</span><span class="s1">clean</span><span class="dl">'</span><span class="p">,</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">lint</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">less</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">scripts</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">vendor</span><span class="dl">'</span><span class="p">],</span>
<span class="dl">'</span><span class="s1">watch</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">callback</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>In the ‘default’ gulp task defined above, the clean task will run. After it completes, the lint, less, scripts, and vendor tasks all run in parallel. When they have <em>all</em> finished, the watch task runs.</p>
<p><a href="https://matt.berther.io/2014/07/26/running-gulpjs-tasks-in-order/">Running gulpjs tasks in order</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on July 26, 2014.</p>https://matt.berther.io/2014/07/25/15-styles-of-distorted-thinking2014-07-25T00:00:00+00:002014-07-25T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>I encountered this list some time ago and thought it would be great to repost here as a reminder of how sometimes our thinking can be distorted.</p>
<p><strong>Filtering</strong>: You take the negative details and magnify them while filtering out all positive aspects of a situation.</p>
<p><strong>Polarized Thinking</strong>: Things are black or white, good or bad. You have to be perfect or you’re a failure. There is no middle ground.</p>
<p><strong>Overgeneralization</strong>: You come to a general conclusion based on a single incident or piece of evidence. If something bad happens once, you expect it to happen over and over again.</p>
<p><strong>Mind Reading</strong>: Without their saying so, you know what people are feeling and why they act the way they do. In particular, you are able to divine how people are feeling toward you.</p>
<p><strong>Catastrophizing</strong>: You expect disaster. You notice or hear about a problem and start “what ifs”. What if tragedy strikes? What if it happens to you?</p>
<p><strong>Personalization</strong>: Thinking that everything people do or say is some kind of reaction to you. You also compare yourself to others, trying to determine who’s smarter, better looking, etc.</p>
<p><strong>Control Fallacies</strong>: If you feel externally controlled, you see yourself as helpless, a victim of fate. The fallacy of internal control has you responsibile for the pain and happiness of everyone around you.</p>
<p><strong>Fallacy of Fairness</strong>: You feel resentful because you think you know what’s fair but other people wont agree with you.</p>
<p><strong>Blaming</strong>: You hold other people responsible for your pain, or take the other tack and blame yourself for every problem or reversal.</p>
<p><strong>Should</strong>: You have a list of iron clad rules about how you and other people should act. People who break the rules anger you and you feel guilty if you violate the rules.</p>
<p><strong>Emotional Reasoning</strong>: You believe that what you feel must be true - automatically. If you feel stupid and boring, then you must be stupid and boring.</p>
<p><strong>Fallacy of Change</strong>: You expect that other people will change to suit you if you just pressure or cajole them enough. You need to change people because your hope for happiness seems to depend entirely on them.</p>
<p><strong>Global Labeling</strong>: You generalize one or two qualities into a negative global judgment.</p>
<p><strong>Being Right</strong>: You are continually on trail to prove that your opinions and actions are correct. Being wrong is unthinkable and you will go to any length to demonstrate your rightness.</p>
<p><strong>Heaven’s Reward Fallacy</strong>: You expect all your sacrifice and self-denial to pay off, as if there were someone keeping score. You feel bitter when the reward doesnt come.</p>
<p><a href="https://matt.berther.io/2014/07/25/15-styles-of-distorted-thinking/">15 Styles of Distorted Thinking</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on July 25, 2014.</p>https://matt.berther.io/2014/03/05/finding-all-sql-server-databases-for-a-login2014-03-05T00:00:00+00:002014-03-05T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>Earlier today, I had a need to deactivate a SQL Server login. Before I did that, I wanted to find out which databases the user was allowed to access. Rather than opening each of the 35 databases on the SQL Server in SSMS and looking to see whether or not the login was a user in the database, I wanted to create a query that would do this all in one fell swoop for me.</p>
<p>I learned a little more about <code class="language-plaintext highlighter-rouge">sp_MSforeachdb</code>, which is a stored procedure that executes the parameter against every database on the SQL Server. There are several documented problems with this stored procedure, but for what I needed to do, it was a great way to get the job done.</p>
<p>The command I used was:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">exec</span> <span class="n">sp_MSforeachdb</span> <span class="s1">'if (select count(*) from [?].sys.sysusers where
name = "usernametosearch") > 0 select "?"'</span>
</code></pre></div></div>
<p>In the above command, you’ll notice the ‘?’, which is substituted for the database name on every iteration of the loop.</p>
<p>Again, not the right tool for every job… but it served well here.</p>
<p><a href="https://matt.berther.io/2014/03/05/finding-all-sql-server-databases-for-a-login/">Finding all SQL Server databases for a login</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on March 05, 2014.</p>https://matt.berther.io/2013/12/29/pushing-large-git-repos-with-ssh2013-12-29T00:00:00+00:002013-12-29T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>For various reasons, we have a <strong>MASSIVE</strong> (14gb) git repository that we work with. We have a clone of this repository out in the cloud behind an SSH server. Recently, when I would attempt to push the repository, I would end up with failures while compressing the objects.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git push aws master
<span class="go">Counting objects: 4456610, done.
Read from remote host my.gitserver: Connection reset by peer
fatal: The remote end hung up unexpectedly
Compressing objects: 100% (1984267/1984267), done.
</span><span class="gp">fatal: sha1 file '<stdout></span><span class="s1">' write error: Invalid argument
</span><span class="go">error: failed to push some refs to 'git@my.gitserver:repo.git'
</span></code></pre></div></div>
<p>On a hunch, I thought that the connection to the SSH server was timing out. I didnt know why the SSH connection would be opened before it was needed. However, the message seemed to indicate that some connection was being dropped by the server. To keep the SSH session alive so that the object compression could complete, I needed to update my ~/.ssh/config file and add an entry for my git remote.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">Host my.gitserver
ServerAliveInterval 60
</span></code></pre></div></div>
<p>When no data has been received from the server, the setting specified in ServerAliveInterval will determine the number of seconds after which a null packet will be sent to the server. The default setting is 0 which means that no keep alive packets are sent. This setting can be combined with ServerAliveCountMax which is the maximum number of ServerAlive messages that will be sent without response before the connection is terminated. The default value for ServerAliveCountMax is 3, which is good enough for what I need.</p>
<p><a href="https://matt.berther.io/2013/12/29/pushing-large-git-repos-with-ssh/">Pushing large git repos with SSH</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on December 29, 2013.</p>https://matt.berther.io/2012/09/20/removing-duplicate-messages-from-outlook2012-09-20T00:00:00+00:002012-09-20T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>I recently learned that Outlook for Mac had been uploading multiple copies of the same message to the Exchange server. At final count, I had approximately 280,000 email messages sitting in my “Archive” folder on the server. As you can imagine, this caused tremendous download times for resynchronizing my folders.</p>
<p>I looked for tools that could purge the duplicates for me, but had a tough time getting most of them to work on Microsoft Outlook 2010. I set out to try and solve this problem by creating a simple C# app that would iterate through my archive folder and identify and remove duplicate items.</p>
<p>I chose to parse the messages in two different ways to make sure that I was able to remove as many duplicates as possible. The first scan removed every message that had a duplicate <a href="http://en.wikipedia.org/wiki/Message-ID">message id</a>. The second scan removed every message that had the same sender email, subject, and sent time.</p>
<p>This technique worked remarkably well. My archive folder now has less than 70,000 messages in it, which means that approximately 75% of the messages in that folder were deleted as duplicates.</p>
<p>I’ve made my source code available at <a href="https://github.com/mattberther/duplicate_email_remover">github</a> for anyone that is interested in using and/or forking the project.</p>
<p>Please keep in mind that there are no warranties with the code. It worked well for me; your mileage may vary.</p>
<p><a href="https://matt.berther.io/2012/09/20/removing-duplicate-messages-from-outlook/">Removing duplicate messages from Outlook</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on September 20, 2012.</p>https://matt.berther.io/2012/09/14/chrome-extension-for-instapaper2012-09-14T00:00:00+00:002012-09-14T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>I use instapaper.com as my read later service. I have installed the Chrome add-on to allow me to quickly tag an article to read later. Also, I have configured it as my read later service in Tweetbot, which allows me to quickly send articles to it for later reading.</p>
<p>The one thing that has always bugged me about the instapaper website is that it does not open links in a new tab/window. To get around this, I set out to create a Chrome extension. This is what I came up with.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ==UserScript==</span>
<span class="c1">// @name Instapaper New Windows</span>
<span class="c1">// @namespace http://mattberther.com</span>
<span class="c1">// @description Open Instapaper links in a new window</span>
<span class="c1">// @include http://www.instapaper.com/*</span>
<span class="c1">// @require http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js</span>
<span class="c1">// ==/UserScript==</span>
<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">function</span> <span class="nx">loadJQuery</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">script</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">script</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">script</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">src</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">script</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">load</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">script</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">script</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">script</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">(</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">callback</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">)();</span><span class="dl">"</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">script</span><span class="p">);</span>
<span class="p">},</span> <span class="kc">false</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">script</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">a.tableViewCellTitleLink</span><span class="dl">"</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="dl">'</span><span class="s1">target</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">_blank</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">loadJQuery</span><span class="p">(</span><span class="nx">main</span><span class="p">);</span>
<span class="p">})();</span>
</code></pre></div></div>
<p>Copy the code above and save it to a location on your computer; I called mine instapaper.js. The latest versions of Google Chrome no longer allow you to add extensions from a third party source (like your own computer) by simply clicking on the javascript file. To install the extension, open the extensions window in Chrome and then dragging the file you created onto the window.</p>
<p>Once the extension is activated, any links from your unread list in instapaper.com will open in a new window.</p>
<p><a href="https://matt.berther.io/2012/09/14/chrome-extension-for-instapaper/">Chrome extension for instapaper</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on September 14, 2012.</p>https://matt.berther.io/2012/09/09/validating-habtm-relationships-with-rails-3x2012-09-09T00:00:00+00:002012-09-09T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>There comes a time as you build up a rails application that you end up using the has_and_belongs_to_many (HABTM) macro. This macro is an easy way to create a many-to-many relationship between two of your ActiveRecord models.</p>
<p>In some cases you may want to validate that association. However, the traditional methods for validating rails models do not work.</p>
<p>The unit tests below described how I wanted the relationship to function.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ProjectTest</span> <span class="o"><</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span>
<span class="n">setup</span> <span class="k">do</span>
<span class="vi">@project</span> <span class="o">=</span> <span class="no">Project</span><span class="p">.</span><span class="nf">new</span><span class="p">()</span>
<span class="k">end</span>
<span class="nb">test</span> <span class="s2">"may have many developers"</span> <span class="k">do</span>
<span class="mi">4</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="vi">@project</span><span class="p">.</span><span class="nf">developers</span> <span class="o"><<</span> <span class="no">FactoryGirl</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">:developer</span><span class="p">)</span> <span class="p">}</span>
<span class="n">assert</span> <span class="vi">@project</span><span class="p">.</span><span class="nf">save</span>
<span class="k">end</span>
<span class="nb">test</span> <span class="s2">"must have at least one developer"</span> <span class="k">do</span>
<span class="vi">@project</span><span class="p">.</span><span class="nf">save</span>
<span class="n">assert_equal</span> <span class="mi">1</span><span class="p">,</span> <span class="vi">@project</span><span class="p">.</span><span class="nf">errors</span><span class="p">.</span><span class="nf">count</span>
<span class="n">assert_not_nil</span> <span class="vi">@project</span><span class="p">.</span><span class="nf">errors</span><span class="p">[</span><span class="ss">:developers</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>In my case, I was hoping to validate that each project had at least one developer associated to it. Initially, I coded my models to make the first test pass.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Developer</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Project</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">has_and_belongs_to_many</span> <span class="ss">:developers</span>
<span class="k">end</span>
</code></pre></div></div>
<p>To make the second test pass, I tried to implement a custom active record validator.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Project</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">has_and_belongs_to_many</span> <span class="ss">:developers</span>
<span class="n">validate</span> <span class="ss">:minimum_number_of_developers</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">minimum_number_of_developers</span>
<span class="n">errors</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="ss">:developers</span><span class="p">,</span> <span class="s2">"must have at least on developer"</span><span class="p">)</span> <span class="k">if</span> <span class="n">developers</span><span class="p">.</span><span class="nf">count</span> <span class="o"><</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This, however, does NOT work with HABTM relationships. The way that these relationships work is that the associated property is not available until after the record is saved.</p>
<p>To get around this, we can validate as part of the after_save callback. Validating here and returning false from the callback will rollback the entire transaction.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Project</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">has_and_belongs_to_many</span> <span class="ss">:developers</span>
<span class="n">after_save</span> <span class="ss">:validate_minimum_number_of_developers</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">validate_minimum_number_of_developers</span>
<span class="k">if</span> <span class="n">developers</span><span class="p">.</span><span class="nf">count</span> <span class="o"><</span> <span class="mi">1</span>
<span class="n">errors</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="ss">:developers</span><span class="p">,</span> <span class="s2">"must have at least on developer"</span><span class="p">)</span>
<span class="k">return</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>The test passes with the code above.</p>
<p><a href="https://matt.berther.io/2012/09/09/validating-habtm-relationships-with-rails-3x/">Validating HABTM relationships with Rails 3.x</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on September 09, 2012.</p>https://matt.berther.io/2011/12/08/fixed-position-footers2011-12-08T00:00:00+00:002011-12-08T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>Posting mostly for my own reference…</p>
<p>One thing I find that I need to do a lot is position a footer bar across the bottom of the page. The most common way to do this is to set a fixed position on the element and anchor it to the bottom using this css:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">#footer</span> <span class="p">{</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">fixed</span><span class="p">;</span>
<span class="nl">bottom</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">height</span><span class="p">:</span> <span class="m">75px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Unfortunately, this doesnt work quite right in IE. When using this style definition in IE, the footer gets locked into a specific position in the viewport and when you resize from the corner anchor the footer does not move with the window.</p>
<p>The <strong>proper</strong> cross-browser way to declare a fixed position footer is to use negative margins, like this:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">#footer</span> <span class="p">{</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">fixed</span><span class="p">;</span>
<span class="nl">top</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="nl">margin-top</span><span class="p">:</span> <span class="m">-75px</span><span class="p">;</span>
<span class="nl">height</span><span class="p">:</span> <span class="m">75px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This appears to function properly in every browser I’ve looked at so far.</p>
<p><a href="https://matt.berther.io/2011/12/08/fixed-position-footers/">Fixed Position Footers</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on December 08, 2011.</p>https://matt.berther.io/2011/12/06/the-software-behind-the-site2011-12-06T00:00:00+00:002011-12-06T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>Several times over the past months, I’ve received questions about the software and setup that I use to run the mattberther.com blog and related pages. Since the site recently underwent a dramatic change in tooling, I want to detail what I chose and why.</p>
<p>Before the change, the site was powered by wordpress. I had my own rackspace virtual server that I was using to host the apache and mysql server servers required by wordpress. Wordpress is not a bad piece of software, but what I realized is that it tries very hard to be all things to all people.</p>
<p>Enter the blog engine Im using now: <a href="http://cloudhead.io/toto">toto</a></p>
<p>Toto’s philosophy is akin to mine: use the best tool for the job. Toto’s website states that “everything that can be done better with another tool should be”. To that end, toto doesn’t use complicated web frameworks. It doesn’t use a database. There’s no built-in commenting support. If you want comments, you’ll use <a href="http://disqus.com">disqus</a>. It also relies on git for version control. When you combine toto with a Heroku account, you also use git to deploy the site. Its designed to be used with a proxy cache for high availability and fast response times.</p>
<p>Migrating the posts from the mysql database to the text files proved to be relatively straightforward, using a simple ruby script to iterate over the rows and format a text file that matched toto’s expectations. Importing the comments into the disqus platform was equally straightforward using their JSON API.</p>
<p>I much prefer the simplicity of this new setup. The entire blog engine weighs in at about 300 lines of code. If running a blog without putting your hands on the metal and being able to control every nuance of your blog platform appeals to you, then certainly take a look at the toto/heroku combination. If not, then I believe that wordpress is a fine solution.</p>
<p><a href="https://matt.berther.io/2011/12/06/the-software-behind-the-site/">The Software Behind the Site</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on December 06, 2011.</p>https://matt.berther.io/2011/11/29/gitting-tfs-out-of-your-way2011-11-29T00:00:00+00:002011-11-29T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>More and more, I have developed a passion for the Git source control system. I love how Git stays out of my way, until I need to use it. Git offers a very easy way to test things out, whilst utilizing the benefits of source control. In the traditional, connected model of source control, experimentation proves to be somewhat difficult because you don’t want to corrupt your main development line. Branching and merging with most other systems is a nightmare at best. With Git, branches are very cheap and merging is virtually painless.</p>
<p>I use Git for side projects as well as a few of the open source projects I am a part of. However, during the day, my organization uses Microsoft’s Team Foundation Server for source control. I find that in a connected source control model, I am constantly waiting on TFS to catch up. Either it’s out too lunch, or it needs to download and checkout a file before I can edit it. Experimentation is tough, for the reasons I mentioned earlier. Surely, there has to be something better, while still keeping TFS in the organization.</p>
<p>Enter Git-TFS… Git-tfs is a two-way bridge between Git and TFS created by <a href="http://mattonrails.wordpress.com">Matt Burke</a>. It allows me to clone a TFS repository and use Git for source control (without the hassles of being tied to a TFS server). This means: 1) no more waiting on TFS, 2) no more locked files, 3) no more requiring network connectivity to work on a project. At a time that I believe a development effort is ready to be committed to the main line, I use git-tfs to push my changes to the TFS server so that the rest of the team can get them.</p>
<p>Sound interesting? Here’s how to get started:</p>
<p>First step’s first. You need to have a Git installation on your machine. I use <a href="http://code.google.com/p/msysgit/">msysGit</a> 1.7.6 preview 20110708, but this should work with newer versions as well. I just used the default installation options, specifically, I chose to use the Git Bash only when prompted about how to use Git from the command line. I chose this option because it did not otherwise modify my system.</p>
<table>
<tbody>
<tr>
<td>Once you have a Git installation, you need to install the git-tfs plugin, which is available on github at <a href="https://github.com/spraints/git-tfs">https://github.com/spraints/git-tfs</a>. Installing the plugin is pretty straightforward. You can choose to either download or build it yourself. I went with the download option and extracted the zip file to c:\git-tfs. Once the files are in place, git has to know how to find them. The easiest way to do this is to add c:\git-tfs to your path (Advanced System Settings</td>
<td>Environment Variables).</td>
</tr>
</tbody>
</table>
<p>At this point, you are ready to clone a TFS repository using git-tfs. You can do this one of two ways:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">git tfs clone http://tfs:8080/ $</span>/TeamProject/folder
</code></pre></div></div>
<p>or</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">git tfs quick-clone http://tfs:8080/ $</span>/TeamProject/folder
</code></pre></div></div>
<p>At this point, you are ready to open your project. If you’re using Visual Studio (which you likely are), you may still have the TFS source control bindings in place. There’s several ways to handle this. You could 1) disconnect your network cable while starting the project, 2) update all of the projects to remove the source control bindings, or 3) install <a href="http://visualstudiogallery.msdn.microsoft.com/425f09d8-d070-4ab1-84c1-68fa326190f4?SRC=Home">GoOffline</a>.</p>
<p>Option one is obviously less than desirable. Option two is reasonable, however, we are assuming that others on the team continue to use TFS and need to have the bindings in place. That leaves the GoOffline solution. GoOffline is a free Visual Studio add-in that adds a Go Offline button to the Source Control menu. When the solution is in offline mode, any file renames or moves happen without communicating with the TFS server. Perfect!</p>
<p>I prefer to work in feature branches (sometimes also referred to as topic branches). Again, branches are cheap in Git and this allows me the ability to experiment while enjoying the comforts of source control and not forcing experimental changes on my team. My typical workflow with git-tfs looks something like this:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>create and checkout a new branch <span class="k">for </span>feature
<span class="go">git checkout -b feature_name
</span><span class="gp">#</span><span class="w"> </span>write tests and code
<span class="go">
</span><span class="gp">#</span><span class="w"> </span>commit my changes with a meaningful commit message
<span class="go">git commit -am "meaningful commit message"
</span><span class="gp">#</span><span class="w"> </span>repeat the code/commit process <span class="k">until </span>the feature is <span class="nb">complete</span>
<span class="go">
</span><span class="gp">#</span><span class="w"> </span>when ready to commit the changes to the TFS repository, first <span class="nb">sync </span>any newer changes from the master
<span class="go">
</span><span class="gp">#</span><span class="w"> </span>switch to the master branch
<span class="go">git checkout master
</span><span class="gp">#</span><span class="w"> </span>pull new changes from the TFS server
<span class="go">git tfs pull
</span><span class="gp">#</span><span class="w"> </span>switch back to the feature branch
<span class="go">git checkout feature_name
</span><span class="gp">#</span><span class="w"> </span>import the changes from the master branch onto our feature branch
<span class="go">git rebase master
</span><span class="gp">#</span><span class="w"> </span>last but not least, push to TFS
<span class="gp">#</span><span class="w"> </span>optionally, add <span class="nt">--build-default-comment</span> which will create a default commit message
<span class="go">git tfs checkintool
</span></code></pre></div></div>
<p>The checkintool command brings up the TFS commit dialog which allows you to associate checkins with work items and modify the commit message. Importantly, the push to TFS is done as a single commit, so regardless of how many times you commit to your git repository, the change pushed to TFS reflects only the final result and not the journey it took to get there.</p>
<p>I have only been using this workflow for a little while now, but for now, I am <em>very</em> happy with it. Your mileage may vary. Please let me know in the comments if you’re using this and how it’s working for you.</p>
<p><a href="https://matt.berther.io/2011/11/29/gitting-tfs-out-of-your-way/">"Gitting" TFS out of your way</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on November 29, 2011.</p>https://matt.berther.io/2011/11/27/goals-and-goal-setting2011-11-27T00:00:00+00:002011-11-27T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p><strong>Goals and Goal Setting:</strong></p>
<p>In the book <em>What They Don’t Teach you at Harvard Business School</em>, Mark McCormak discusses the importance of setting goals. The students in the 1979 Harvard MBA program were surveyed and asked “Have you set clear, written goals for your future and made plans to accomplish them?” Only three percent of the graduates had written goals and plans; 13 percent had goals, but they were not in writing; and 84 percent had no specific goals at all.</p>
<p>Ten years later, the members of the class were surveyed again. The 13 percent of the class that had goals were earning an average of twice as much as the 84 percent who had no goals at all. However, the three percent that had clear, written goals were earning on average, <strong>ten times the other 97 percent combined</strong>.</p>
<p>For many of us, during this time of year, we begin to look at performance reviews for our team members. An important part of a good performance review are effective goals for the coming year. I am certainly not a management expert and most of the ideas that I will share with you today are not my own. However, I do see value in each of the ideas. For me, these ideas make the goal setting process easier. I certainly hope that they can do the same for you.</p>
<p><strong>Top Tips:</strong></p>
<ul>
<li>Strong relationships with directs is paramount
<ul>
<li>Many management leaders propose that having weekly one-on-ones is the single most important way to cultivate a positive relationship with your direct reports.</li>
<li>10/10/10 agenda
<ul>
<li>10 minutes for the direct to discuss whatever they want</li>
<li>10 minutes for you to discuss whatever you would like with the direct</li>
<li>10 minutes to discuss business and progress toward goals</li>
</ul>
</li>
</ul>
</li>
<li>Create MT goals (measurable and timely) and use SMART to validate them
<ul>
<li>Examples:
<ul>
<li>By April 30, 2011, consolidate the presentation models used by the company’s applications.</li>
<li>Integrate the new ERP system with the CRM system by October 31, 2010.</li>
</ul>
</li>
</ul>
</li>
<li>Create goals that intersect business and individual objectives
<ul>
<li>Examples:
<ul>
<li>By April 30, 2011, combine multiple automated test frameworks into a single framework and increase coverage of automated tests by 20% using the consolidated framework.</li>
<li>Complete Microsoft developer certification 70-515 by April 30, 2011.</li>
</ul>
</li>
</ul>
</li>
<li>Emphasize cross-team collaboration
<ul>
<li>Examples:
<ul>
<li>Participate in a mentorship program to grow understanding of our organization’s business by April 30, 2011.</li>
<li>Participate in a mentoring program to enhance leadership and communication skills by December 31, 2009.</li>
</ul>
</li>
</ul>
</li>
<li>Create personal goals
<ul>
<li>Examples:
<ul>
<li>Run a 5K race in less than 32 minutes by April 30, 2010</li>
<li>Receive a belt promotion in my chosen martial art by December 31, 2011</li>
</ul>
</li>
</ul>
</li>
<li>Be flexible</li>
</ul>
<blockquote>
<p>“I’m much more likely to accomplish something if I write my goals down and share my goals with others. Recruiting the support of other people also helps keep me on track.”</p>
</blockquote>
<blockquote>
<p>“What was/is valuable to me is taking into account what I want to do when setting goals. That results in my engagement right from the start.”</p>
</blockquote>
<blockquote>
<p>“I think the addition of a personal goal is a great idea. It’s good for promoting personal improvement and makes the goal setting process less painful.”</p>
</blockquote>
<p>I have used this model for a number of years. The quotes above are a few pieces of feedback that I received from my team regarding these ideas.</p>
<p><a href="https://matt.berther.io/2011/11/27/goals-and-goal-setting/">Goals and Goal Setting</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on November 27, 2011.</p>https://matt.berther.io/2011/11/22/cygwin---unable-to-remap-to-same-address-as-parent2011-11-22T00:00:00+00:002011-11-22T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>I find the windows command prompt somewhat limiting and have never really been able to make the leap to Powershell. Personally, I like to use a Cygwin shell for command line work. I am comfortable in a unix shell with previous Linux and Mac experience. I’m not an expert by any means, but I can get by.</p>
<p>Recently, I installed the Cygwin shell on my 64bit Windows 7 system. After installing the developer tools and trying to compile ruby 1.9.2, I found that I had a tremendous amount of problems building. Id see numerous errors that stated:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">unable to remap file to same address as parent ruby #</span><span class="c">## fork: child XXX - died waiting for dll</span>
</code></pre></div></div>
<p>The most commonly referenced <a href="http://www.garethhunt.com/2008/02/11/cygwin-died-waiting-for-dll-loading/">solution</a> was to rebase by doing the following:</p>
<ol>
<li>Start -> Run</li>
<li>cmd.exe</li>
<li>cd c:\path\to\cygwin\bin\</li>
<li>ash.exe</li>
<li>./bin/rebaseall</li>
<li>Reboot</li>
</ol>
<p>However, this did not work for me. I found a <a href="http://cygwin.com/ml/cygwin/2011-04/msg00075.html">message on the cygwin mailing list</a> stating that the gems may not add information to the proper paths and that we must create the list manually.</p>
<p>To do this, first from your cygwin shell do:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">find /lib/ruby/gems -name '*.so' ></span><span class="w"> </span>/tmp/ruby.gems.local.so.lst
</code></pre></div></div>
<p>Then, follow steps the steps above, replacing step five with:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">./bin/rebaseall -T /tmp/ruby.gems.local.so.lst
</span></code></pre></div></div>
<p>Since performing this, I have not seen any more of the remap file errors. Your mileage may vary though.</p>
<p><a href="https://matt.berther.io/2011/11/22/cygwin-unable-to-remap-to-same-address-as-parent/">Cygwin - unable to remap to same address as parent</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on November 22, 2011.</p>https://matt.berther.io/2011/01/26/hidden-gems-in-code2011-01-26T00:00:00+00:002011-01-26T00:00:00+00:00Matt Bertherhttps://matt.berther.iomatt@berther.io<p>This gem was spotted in some code that my team was working on today. It made us chuckle anyway. :)</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">GetAgeOptions</span><span class="p">(</span><span class="nx">localization</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">ageArray</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">();</span>
<span class="nx">ageArray</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Nick Rocks</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">ageArray</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Ben Rocks too</span><span class="dl">"</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">ageArray</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p><a href="https://matt.berther.io/2011/01/26/hidden-gems-in-code/">Hidden Gems in Code</a> was originally published by Matt Berther at <a href="https://matt.berther.io">Matt Berther</a> on January 26, 2011.</p>