Mike BallRecent projects, blog, and informationhttp://www.mikeball.info2016-08-18T20:00:00-04:00Mike BallUsing the ConcourseCI pull request resource to verify Docker builds/blog/verify-docker-builds-in-concourse/2016-08-18T20:00:00-04:002016-08-18T20:00:00-04:00Mike Ball<p><a href="http://concourse.ci">Concourse.ci</a> offers a free, open source continuous integration and delivery tool through which software development teams can establish and manage delivery pipelines.</p>
<p><strong>Problem</strong>: <a href="https://travis-ci.org">TravisCI</a> can be configured to run CI against a docker image's <a href="https://travis-ci.org/mdb/docker-wct">source code...</a></p><p><a href="http://concourse.ci">Concourse.ci</a> offers a free, open source continuous integration and delivery tool through which software development teams can establish and manage delivery pipelines.</p>
<p><strong>Problem</strong>: <a href="https://travis-ci.org">TravisCI</a> can be configured to run CI against a docker image's <a href="https://travis-ci.org/mdb/docker-wct">source code repository</a>. But how can Concourse's <code>pull-request</code> resource be configured to test that <code>docker build</code> of a <code>Dockerfile</code> works as expected in a repo that houses such a <code>Dockerfile</code>?</p>
<p><strong>Solution</strong>: Configure the Concourse's pull request verification job to use the <code>docker-image</code> resource type, thus performing a <code>docker build</code> using the <code>Dockerfile</code> during a pull request's continuous integration. Note line 41 in the <code>pipeline.yml</code>.</p>
<h1 id="the-pipeline.yml">The pipeline.yml</h1>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65</pre></td><td class="code"><pre>resources:
# source code
- name: docker-foo
type: git
source:
branch: master
uri: git@github.com:username/docker-foo.git
# foo docker image
- name: foo-docker-image
type: docker-image
source:
repository: docker.your-company.com/username/foo
# docker-foo pull request resource
- name: docker-foo-pull-request
type: pull-request
source:
uri: git@github.comcast.com:aae/cloud-tools.git
repo: username/docker-foo
resource_types:
- name: pull-request
type: docker-image
source:
repository: jtarchie/pr
jobs:
# verify a pull request
- name: verify-pull-request
plan:
- get: docker-foo-pull-request
trigger: true
- put: docker-foo-pull-request
params:
path: docker-foo-pull-request
status: pending
# test in ConcourseCI that the PR's `Dockerfile` edits work as expected:
- put: foo-docker-image
params:
build: docker-foo-pull-request
on_success:
put: docker-foo-pull-request
params:
path: docker-foo-pull-request
status: success
on_failure:
put: docker-foo-pull-request
params:
path: docker-foo-pull-request
status: failure
# build foo docker image from `master`
- name: publish-docker-image
serial: true
plan:
- get: docker-foo
trigger: false
- put: foo-docker-image
params:
build: foo
tag: foo/version
</pre></td></tr></tbody></table>
</div>
Using OptionParser in Rake/blog/rake-option-parser/2016-04-14T20:00:00-04:002016-04-14T20:00:00-04:00Mike Ball<p>Problem: you'd like to leverage named arguments in your Ruby <code>Rake</code> task.</p>
<p>Solution: use <code>OptionParser</code> to parse the named arguments. Note the need to <em>also</em> call <code>#OptionParser#order!(ARGV)</code>, which is often absent from internet documentation.</p>
<p>This example...</p><p>Problem: you'd like to leverage named arguments in your Ruby <code>Rake</code> task.</p>
<p>Solution: use <code>OptionParser</code> to parse the named arguments. Note the need to <em>also</em> call <code>#OptionParser#order!(ARGV)</code>, which is often absent from internet documentation.</p>
<p>This example uses Ruby <code>2.2.2</code> and Rake <code>11.1.2</code>.</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21</pre></td><td class="code"><pre><span class="nb">require</span> <span class="s1">'optparse'</span>
<span class="n">task</span> <span class="ss">:hello</span> <span class="k">do</span>
<span class="n">options</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">name: </span><span class="s1">'world'</span>
<span class="p">}</span>
<span class="n">o</span> <span class="o">=</span> <span class="no">OptionParser</span><span class="p">.</span><span class="nf">new</span>
<span class="n">o</span><span class="p">.</span><span class="nf">banner</span> <span class="o">=</span> <span class="s2">"Usage: rake hello [options]"</span>
<span class="n">o</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="s1">'-n NAME'</span><span class="p">,</span> <span class="s1">'--name NAME'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="nb">name</span><span class="o">|</span>
<span class="n">options</span><span class="p">[</span><span class="ss">:name</span><span class="p">]</span> <span class="o">=</span> <span class="nb">name</span>
<span class="p">}</span>
<span class="c1"># return `ARGV` with the intended arguments</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">o</span><span class="p">.</span><span class="nf">order!</span><span class="p">(</span><span class="no">ARGV</span><span class="p">)</span> <span class="p">{}</span>
<span class="n">o</span><span class="p">.</span><span class="nf">parse!</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"hello </span><span class="si">#{</span><span class="n">options</span><span class="p">[</span><span class="ss">:name</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
</pre></td></tr></tbody></table>
</div>
<p>Usage:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2</pre></td><td class="code"><pre>rake hello -- --name=mike
hello mike
</pre></td></tr></tbody></table>
</div>
<p>Default behavior with no arguments:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2</pre></td><td class="code"><pre>$ rake hello
hello world
</pre></td></tr></tbody></table>
</div>
Using wget over Ansible's get_url/blog/ansible-wget/2015-12-22T19:00:00-05:002015-12-22T19:00:00-05:00Mike Ball<p>Problem: In provisioning a server, your Ansible playbook needs to download files from a URL behinded authentication, such as a private GitHub repository. In Ansible 2.0, Ansible's <a href="#">get_url</a> supports custom headers — such as <code>Authorization</code> — but pre-2...</p><p>Problem: In provisioning a server, your Ansible playbook needs to download files from a URL behinded authentication, such as a private GitHub repository. In Ansible 2.0, Ansible's <a href="#">get_url</a> supports custom headers — such as <code>Authorization</code> — but pre-2.0 Ansible does not.</p>
<p>Solution: Use <code>wget</code>, a <code>wgetrc</code>, and <code>ansible-vault</code>.</p>
<p>Step 1: Generate a <a href="https://github.com/settings/tokens">GitHub access token</a></p>
<p>Step 2: Store the token in an Ansible <code>group_var</code> at <code>your_playbook_dir/group_vars/all</code>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>github_token: "your access token value"
</pre></td></tr></tbody></table>
</div>
<p>Step 3: Use <code>ansible-vault</code> to encrypt your <code>github_token</code>; enter a password at the prompt:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4</pre></td><td class="code"><pre>$ ansible-vault encrypt your_playbook_dir/group_vars/all
Vault password:
Confirm Vault password:
Encryption successful
</pre></td></tr></tbody></table>
</div>
<p>Step 4: Create a <code>your_playbook_dir/tempaltes/wgetrc.j2</code> template to house <code>wgetrc</code> configuration. Specify the proper headers to authenticate against GitHub:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2</pre></td><td class="code"><pre>header = Authorization: token {{ github_token }}
header = Accept: application/vnd.github.v3.raw
</pre></td></tr></tbody></table>
</div>
<p>Step 5: Add a task to your playbook to lay down the <code>wgetrc</code> file:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4</pre></td><td class="code"><pre>- name: lay down /etc/wgetrc file
template:
src: wgetrc.j2
dest: /etc/wgetrc
</pre></td></tr></tbody></table>
</div>
<p>Step 6: Add a task to your playbook to download the file from a private GitHub repository:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2</pre></td><td class="code"><pre>- name: download some_service_def init.d script
shell: "wget -O /etc/init.d/some_service_def https://github.com/raw/user/repo/master/some_service_def"
</pre></td></tr></tbody></table>
</div>
<p>Note that, in Ansible 2.0, the use of <code>wget</code> can be replaced with <code>get_url</code>, replacing steps 4, 5, and 6 with the following:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5</pre></td><td class="code"><pre>- name: download some_service_def init.d script
get_url:
url: https://github.com/raw/user/repo/master/some_service_def
headers: "Authorization:token {{ github_token }},Accept:application/vnd.github.v3.raw"
dest: /etc/init.d/some_service_def
</pre></td></tr></tbody></table>
</div>
Apache Persistent Connection Problems/blog/apache-keep-alive/2015-10-17T20:00:00-04:002015-10-17T20:00:00-04:00Mike Ball<p>Problem: Apache worker thread pool is exhausted; server CPU consumption is high.</p>
<p>Solution: reduce the Apache <code>KeepAliveTimeout</code> from its 15 second default (disclaimer: maybe, depending on circumstances).</p>
<h2 id="background">Background</h2>
<p>HTTP keep-alive functionality seeks...</p><p>Problem: Apache worker thread pool is exhausted; server CPU consumption is high.</p>
<p>Solution: reduce the Apache <code>KeepAliveTimeout</code> from its 15 second default (disclaimer: maybe, depending on circumstances).</p>
<h2 id="background">Background</h2>
<p>HTTP keep-alive functionality seeks to improve efficiency. In effect, HTTP keep-alive — also referred to as <em>HTTP persistent connection</em> and <em>HTTP connection reuse</em> allows the use of a single TCP connection to send and receive multiple HTTP requests and responses.</p>
<p>Keep-alive reduces the latency associated with opening a new TCP connection for each HTTP request. It also reduces server CPU usage: by reusing an open connection, the server isn't required to utilize CPU resources necessary to open a new TCP connection (and handle HTTPS, if relevant).</p>
<p>Apache offers three settings through which HTTP keep-alive can be tuned:</p>
<ul>
<li><code>KeepAlive</code> — enables/disables HTTP keep-alive functionality</li>
<li><code>MaxKeepAliveRequests</code> — the maximum requests a single open connection can serve</li>
<li><code>KeepAliveTimeout</code> — the duration in seconds the server should wait for subsequent requests from a connected client before closing the connection. By default, 15 seconds.</li>
</ul>
<h2 id="the-apache-problem">The Apache problem</h2>
<p>The Apache web server offers a <code>KeepAliveTimeout</code> default of 15 seconds. However, when individual clients don't require 15 seconds of connection persistence, the lengthy timeout is unnecessarily memory-intensive for the server. In such a problematic scenario, the creation of many Apache processes — one per connection — occupies RAM waiting for subsequent client requests inside a too-generous 15 second window. This can result in Apache worker thread exhaustion under moderate traffic.</p>
<p>Access log analysis should offer insight. How frequently do individual clients perform requests? How much traffic is Apache serving? If worker thread usage consistently exceeds the request count inside a given window of time, <code>KeepAliveTimeout</code> may be too high. This is further substantiated if individual clients rarely perform multiple requests inside a 15 second window or if, for example, clients perform multiple requests inside an initial 5 seconds and don't perform subsequent requests for more than 10 seconds.</p>
Secure Session Cookie in Rails over HTTPS/blog/secure-session-cookie-in-rails/2015-09-23T20:00:00-04:002015-09-23T20:00:00-04:00Mike Ball<p>The secure flag can be set by an application server when sending a cookie within an HTTP response. By setting the secure flag, an HTTP client — such as a web browser — prevents cookie transmission unless the response is securely encrypted over HTTPS...</p><p>The secure flag can be set by an application server when sending a cookie within an HTTP response. By setting the secure flag, an HTTP client — such as a web browser — prevents cookie transmission unless the response is securely encrypted over HTTPS.</p>
<p>However, many web applications redirect <code>http://</code> to <code>https://</code>, and many Ruby on Rails applications are fronted by a web server such as Ngnix or Apache. Often, <code>HTTPS</code> is terminated at the Nginx/Apache layer. Given such an architecture, consider the following problematic behavior through which a Ruby on Rails session cookie could be transmitted insecurely in clear text:</p>
<ol>
<li>user types <code>http://example.com</code> into browser address bar</li>
<li>browser makes request to <code>http://example.com</code></li>
<li>browser receives 301 response w/ <code>https://example.com</code> specified as <code>Location</code> header; no session cookie is present/set</li>
<li>browser makes request to <code>https://example.com</code></li>
<li>browser receives response with session cookie present/set; the secure flag is absent from the cookie</li>
<li>user types <code>http://example.com</code> into browser address bar</li>
<li>browser makes request to <code>http://example.com</code></li>
<li>browser receives 301 response with <code>https://example.com</code> specified as <code>Location</code> header; step #5 session cookie is present/set was transmitted in clear text because the secure flag was absent in step #5.</li>
</ol>
<p>In Rails, calling <code>Rails.application.config.session_store</code> with <code>secure: true</code> in <code>config/initializers/session_store.rb</code> informs the Rails application to add the secure flag, but Rails will only do so if SSL is terminated at the application <em>or</em> if the Rails-fronting web server at which SSL is terminated – Nginx or Apache in the above example — adds an <code>X-Forwarded-Proto</code> header whose value is <code>https</code>.</p>
<p>For example, to do so in Apache, add the following to the Apache config file controlling your site:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>RequestHeader set X-Forwarded-Proto "https"
</pre></td></tr></tbody></table>
</div>
<p>And add the following to your Rails app's <code>config/initializers/session_store.rb</code>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3</pre></td><td class="code"><pre>Rails.application.config.session_store :cookie_store,
:key => '_your_app_name_session',
:secure => ENV['RAILS_ENV'] != 'development'
</pre></td></tr></tbody></table>
</div>
<p>Note that this requires an application restart to take effect.</p>
How to Test Google Polymer elements on Travis CI/blog/testing-polymer-on-travis/2015-06-05T20:00:00-04:002015-06-05T20:00:00-04:00Mike Ball<p>Problem: how do you design a cloud-based continuous integration pipeline if your automated application tests relies on technology that requires a GUI, or an actual web browser? <a href="https://www.polymer-project.org">Google Polymer</a> heavily leverages <a href="https://w3c.github.io/webcomponents/spec/shadow/">Shadow DOM</a>, a feature that's not currently...</p><p>Problem: how do you design a cloud-based continuous integration pipeline if your automated application tests relies on technology that requires a GUI, or an actual web browser? <a href="https://www.polymer-project.org">Google Polymer</a> heavily leverages <a href="https://w3c.github.io/webcomponents/spec/shadow/">Shadow DOM</a>, a feature that's not currently supported in headless JavaScript environments like <a href="http://phantomjs.org/">PhantomJS</a>. My <a href="http://github.com/mdb/polymer-testing-box">polymer-testing-box</a> demonstrate how to run such tests via <a href="http://en.wikipedia.org/wiki/Xvfb">Xvfb</a> on a headless Ubuntu VM. Can this technique be used on <a href="http://travis-ci.org">Travis CI</a>?</p>
<p>Travis CI supports Xvfb and Firefox. Travis offers <a href="http://docs.travis-ci.com/user/gui-and-headless-browsers/">documentation</a> on leveraging these technologies in its CI environment.</p>
<p>In short, Xvfb can be spun up in advance of test execution. web-component-tester tests can be run in Firefox.</p>
<p>Example <code>.travis.yml</code>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10</pre></td><td class="code"><pre>language: node_js
node_js:
- "0.12"
before_install:
- "npm install -g bower"
- "bower install"
- "npm install -g web-component-tester"
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
script: "wct"
</pre></td></tr></tbody></table>
</div>
<p>I created a <a href="http://github.com/kata-seeds/js-polymer-wct-seed">js-polymer-wct-seed</a> Kata Seed as a more robust example, somewhat based off the <a href="https://github.com/PolymerElements/polymer-starter-kit">Polymer Startup Kit</a>.</p>
JSConf 2015: Headless Testing Against Real Web Browsers/blog/jsconf-2015/2015-05-26T20:00:00-04:002015-05-26T20:00:00-04:00Mike Ball<p>I spoke at <a href="http://2015.jsconf.us">JSConf 2015</a> about how to test Google Polymer web components against real web browsers in a headless Linux using Xvfb.</p>
<p>My talk offered background on my philosophy, why some software requires tests be run against a real GUI, and also covered...</p><p>I spoke at <a href="http://2015.jsconf.us">JSConf 2015</a> about how to test Google Polymer web components against real web browsers in a headless Linux using Xvfb.</p>
<p>My talk offered background on my philosophy, why some software requires tests be run against a real GUI, and also covered a basic introduction to Xvfb, or X Virtual Frame Buffer. I also demo'd <a href="http://github.com/mdb/polymer-testing-box">polymer-testing-box</a>, a basic reference implementation illustrating how to provision an Ubuntu VM with all the prerequisites necessary to headlessly run Google Polymer web-component-tester tests against Chrome and Firefox.</p>
<p><a href="http://mdb.github.io/testing-with-xvfb/">View my slides »</a></p>
<p>Some related resources:</p>
<ul>
<li><a href="http://github.com/mdb/polymer-testing-box">polymer-testing-box</a> - a basic Vagrant box for testing Google Polymer elements headlessly against Firefox and Chrome</li>
<li><a href="http://github.com/mdb/nw-testing-box">nw-testing-box</a> - a basic Vagrant box for testing an NW.js application via Xvfb</li>
<li><a href="http://github.com/mdb/nw-app-testing">nw-app-testing</a> - a basic NW.js application demonstrating unit and end-to-end tests</li>
</ul>
NW.js Chromium data-path/blog/chromium-data-path/2015-04-08T20:00:00-04:002015-04-08T20:00:00-04:00Mike Ball<p><a href="http://nwjs.io/">NW.js</a> (formerly node-webkit) offers a platform through which desktop applications can be authored using Node.js and web technologies, like Chromium.</p>
<p><b>Problem</b>:</p>
<p>But how can it be ensured that user data doesn't persist between fresh installations of...</p><p><a href="http://nwjs.io/">NW.js</a> (formerly node-webkit) offers a platform through which desktop applications can be authored using Node.js and web technologies, like Chromium.</p>
<p><b>Problem</b>:</p>
<p>But how can it be ensured that user data doesn't persist between fresh installations of a NW.js application? For example, consider the following:</p>
<ol>
<li>user installs <code>SOME_NW_APP</code>.</li>
<li><code>SOME_NW_APP</code> leverages cookies and local storage.</li>
<li>NW.js writes the cookies/local storage data to <code>~/Library/Application\ Support/SOME_NW_APP</code> on Mac OS and <code>C:\Users\%USERNAME%\AppData\Local\Chromium\User Data\Default</code> on Windows 8.</li>
<li>user deletes the app by trashing the <code>/Applications/SOME_NW_APP.app</code> file in Mac OS or running the associated uninstaller in Windows 8, assuming the installation provided an uninstaller.</li>
<li>user re-installs <code>SOME_NW_APP</code></li>
<li><code>SOME_NW_APP</code> retains cookies and local storage data from its first installation.</li>
</ol>
<p><b>Solution</b>:</p>
<p>The NW.js <a href="https://github.com/nwjs/nw.js/wiki/Manifest-format">manifest</a> provides a mechanism through which configuration arguments can be passed to Chromium, including <code>--user-data</code>, which defines the path at which cookies and local storage data are stored. By explicitly setting this path, we can ensure that it's properly removed on uninstall.</p>
<p>Example manifest:</p>
<div class="highlight json"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7</pre></td><td class="code"><pre><span class="p">{</span><span class="w">
</span><span class="s2">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SOME_NW_APP"</span><span class="p">,</span><span class="w">
</span><span class="s2">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Some description"</span><span class="p">,</span><span class="w">
</span><span class="s2">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.0.3"</span><span class="p">,</span><span class="w">
</span><span class="s2">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src/index.html"</span><span class="p">,</span><span class="w">
</span><span class="s2">"chromium-args"</span><span class="p">:</span><span class="w"> </span><span class="s2">"--data-path='data/'"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table>
</div>
<h2 id="mac-os">Mac OS</h2>
<p>In Mac OS, an application built via the preceding manifest will store cookie/local storage data in <code>SOME_NW_APP.app/data</code>, thus ensuring that the <code>data</code> directory is deleted when <code>SOME_NW_APP.app</code> is trashed.</p>
<p>Note, though, this assumes the user running <code>SOME_NW_APP.app</code> has the necessary write permissions. Permissions can be a problem when <code>/Applications</code> is owned by root, <code>SOME_NW_APP.app</code> lives in <code>/Applications</code>, and the user running <code>SOME_NW_APP.app</code> does not have write permissions, as is often the case on Mac OS. Such a scenario prevents cookies and local storage items from being properly saved. See <a href="https://github.com/nwjs/nw.js/issues/1175#issuecomment-112122560">my comment here</a> for more information.</p>
<h2 id="windows">Windows</h2>
<p>In Windows, a tool such as <a href="/blog/node-webkit-app-windows-installer/">nsis</a> can be used to build an installer as well as an uninstaller; the uninstaller can ensure that all <code>data</code> files are deleted.</p>
<p>Example nsi uninstaller section:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14</pre></td><td class="code"><pre>Section "Uninstall"
RMDir '"$INSTDIR\Local Storage"'
RMDir "$INSTDIR\locales"
Delete "$INSTDIR\data\Cache\*"
Delete "$INSTDIR\data\Cache\index-dir\*"
RMDir "$INSTDIR\data\Cache\index-dir"
RMDir "$INSTDIR\data\Cache"
Delete "$INSTDIR\data\*"
RMDir "$INSTDIR\data\Local Storage"
RMDir "$INSTDIR\data"
SectionEnd
</pre></td></tr></tbody></table>
</div>
Creating a Windows Installer for a node-webkit App on Mac OS/blog/node-webkit-app-windows-installer/2014-09-24T20:00:00-04:002014-09-24T20:00:00-04:00Mike Ball<p><a href="https://github.com/rogerwang/node-webkit">node-webkit</a> provides a powerful path through which Linux, Windows, and Mac OS desktop applications can be authored using HTML5 and Node.js web technologies. <a href="https://github.com/mllrsohn/grunt-node-webkit-builder">grunt-node-webkit-builder</a> offers a tool through which a node-webkit application can be compiled...</p><p><a href="https://github.com/rogerwang/node-webkit">node-webkit</a> provides a powerful path through which Linux, Windows, and Mac OS desktop applications can be authored using HTML5 and Node.js web technologies. <a href="https://github.com/mllrsohn/grunt-node-webkit-builder">grunt-node-webkit-builder</a> offers a tool through which a node-webkit application can be compiled to distributable directories. But how can an automated build process bundle the MS Windows files in an application installer in a headless continuous integration environment, or from the command line?</p>
<p><a href="http://nsis.sourceforge.net/Main_Page">makensis</a> provides a solution. The tool can be used on Linux, though this overview focuses on Mac OS.</p>
<p>Install <code>makensis</code> on Mac OS using <a href="http://brew.sh/">homebrew</a>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>brew install nsis
</pre></td></tr></tbody></table>
</div>
<p>A basic <code>windows_installer.nsi</code> file:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48</pre></td><td class="code"><pre>!define PRODUCT_NAME "Your App Name"
Name "${PRODUCT_NAME}"
# define the resulting installer's name:
OutFile "your_app_installer.exe"
# default section start
Section
# define the path to which the installer should install
SetOutPath $INSTDIR
# specify the files to go in the output path
# these are the Windows files produced by grunt-node-webkit-builder
File path/to/build/win/your-app-name/ffmpegsumo.dll
File path/to/build/win/your-app-name/icudt.dll
File path/to/build/win/your-app-name/libEGL.dll
File path/to/build/win/your-app-name/libGLESv2.dll
File path/to/build/win/your-app-name/nw.pak
File path/to/build/win/your-app-name/your-app-name.exe
# define the uninstaller name
WriteUninstaller $INSTDIR\your_app_uninstaller.exe
# create a shortcut named 'your_app' in the start menu
# point the shortcute at your-app-name.exe
CreateShortCut "$SMPROGRAMS\your_app.lnk" "$INSTDIR\your-app-name.exe"
SectionEnd
# create a section to define what the uninstaller does
Section "Uninstall"
# delete the uninstaller
Delete $INSTDIR\your_app_uninstaller.exe
# delete the installed files
Delete $INSTDIR\ffmpegsumo.dll
Delete $INSTDIR\icudt.dll
Delete $INSTDIR\libEGL.dll
Delete $INSTDIR\libGLESv2.dll
Delete $INSTDIR\nw.pak
Delete $INSTDIR\your-app-name.exe
Delete $SMPROGRAMS\your_app.lnk
Delete $INSTDIR
SectionEnd
</pre></td></tr></tbody></table>
</div>
<p>And run the <code>nsi</code> file to generate the <code>your_app_installer.exe</code>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>$ makensis windows_installer.nsi
</pre></td></tr></tbody></table>
</div>
<p>Now, <code>your_app_installer.exe</code> can be distributed to users, thus offering a simple, stream-lined process through which <code>your-app</code> can be installed.</p>
All CSS Modal Dialogs/blog/all-css-modals/2014-08-07T20:00:00-04:002014-08-07T20:00:00-04:00Mike Ball<p>A simple technique for creating modal dialogs without the need for JavaScript.</p>
<p>The HTML:</p>
<div class="highlight html"><table style="border-spacing: 0"><tbody><tr>
<td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td>
<td class="code"><pre><span class="nt"><a</span> <span class="na">href=</span><span class="s">"#modal-1"</span><span class="nt">></span>Modal dialog opener<span class="nt"></a></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal"</span> <span class="na">id=</span><span class="s">"modal-1"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">'modal-content'</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">class=</span><span class="s">"close"</span> <span class="na">href=</span><span class="s">"#"</span><span class="nt">></span>close</pre></td>
</tr></tbody></table></div><p>A simple technique for creating modal dialogs without the need for JavaScript.</p>
<p>The HTML:</p>
<div class="highlight html"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td><td class="code"><pre><span class="nt"><a</span> <span class="na">href=</span><span class="s">"#modal-1"</span><span class="nt">></span>Modal dialog opener<span class="nt"></a></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal"</span> <span class="na">id=</span><span class="s">"modal-1"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">'modal-content'</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">class=</span><span class="s">"close"</span> <span class="na">href=</span><span class="s">"#"</span><span class="nt">></span>close<span class="nt"></a></span>
<span class="nt"><h2></span>Some Heading<span class="nt"></h2></span>
<span class="nt"><p></span>Some paragraph<span class="nt"></p></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
</pre></td></tr></tbody></table>
</div>
<p>The CSS:</p>
<div class="highlight css"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45</pre></td><td class="code"><pre><span class="nc">.modal</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">0</span><span class="p">;</span>
<span class="nl">right</span><span class="p">:</span> <span class="m">0</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">left</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0</span><span class="p">,</span><span class="m">0.7</span><span class="p">);</span>
<span class="nl">z-index</span><span class="p">:</span> <span class="m">99999</span><span class="p">;</span>
<span class="nl">opacity</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">-webkit-transition-property</span><span class="p">:</span> <span class="n">opacity</span><span class="p">;</span>
<span class="nl">-moz-transition-property</span><span class="p">:</span> <span class="n">opacity</span><span class="p">;</span>
<span class="nl">-o-transition-property</span><span class="p">:</span> <span class="n">opacity</span><span class="p">;</span>
<span class="nl">transition-property</span><span class="p">:</span> <span class="n">opacity</span><span class="p">;</span>
<span class="nl">-webkit-transition-duration</span><span class="p">:</span> <span class="m">400ms</span><span class="p">;</span>
<span class="nl">-moz-transition-duration</span><span class="p">:</span> <span class="m">400ms</span><span class="p">;</span>
<span class="nl">-o-transition-duration</span><span class="p">:</span> <span class="m">400ms</span><span class="p">;</span>
<span class="nl">transition-duration</span><span class="p">:</span> <span class="m">400ms</span><span class="p">;</span>
<span class="nl">-webkit-transition-timing-function</span><span class="p">:</span> <span class="n">ease-in</span><span class="p">;</span>
<span class="nl">-moz-transition-timing-function</span><span class="p">:</span> <span class="n">ease-in</span><span class="p">;</span>
<span class="nl">-o-transition-timing-function</span><span class="p">:</span> <span class="n">ease-in</span><span class="p">;</span>
<span class="nl">transition-timing-function</span><span class="p">:</span> <span class="n">ease-in</span><span class="p">;</span>
<span class="nl">pointer-events</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.modal</span><span class="nd">:target</span> <span class="p">{</span>
<span class="nl">opacity</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span>
<span class="nl">pointer-events</span><span class="p">:</span> <span class="nb">auto</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.modal</span> <span class="nt">a</span><span class="nc">.close</span> <span class="p">{</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span>
<span class="nl">right</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.modal</span> <span class="nt">div</span><span class="nc">.modal-content</span> <span class="p">{</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">relative</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">5%</span> <span class="nb">auto</span> <span class="m">0</span> <span class="nb">auto</span><span class="p">;</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">60%</span><span class="p">;</span>
<span class="nl">overflow</span><span class="p">:</span> <span class="nb">hidden</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table>
</div>
<p><a class="modal-trigger" href="#modal-1">View demo »</a></p>
<div class="modal" id="modal-1">
<div class='modal-content'>
<a class="close" href="#">close</a>
<h2>Some Heading</h2>
<p>Some paragraph</p>
</div>
</div>
Rails API Integration Tests/blog/rails-api-integration-tests/2014-04-16T20:00:00-04:002014-04-16T20:00:00-04:00Mike Ball<p>A web-services-oriented architecture encourages the development of multiple, modular applications over maintaining a single large, all-in-one monolithic piece of software. Often, the web services paradigm involves the development of clients apps that...</p><p>A web-services-oriented architecture encourages the development of multiple, modular applications over maintaining a single large, all-in-one monolithic piece of software. Often, the web services paradigm involves the development of clients apps that rely upon third party RESTful web services. Labor and responsibilities are divided and conquered across smaller, more manageable codebases and teams. But how can such a client application verify graceful integration? With a large user-base, such insight is increasingly critical.</p>
<p><b>Example</b>: You're deploying a Rails application that consumes a third party REST API, massages its data, and serves JSON. Unit tests stub HTTP requests with webmock; they verify that the application behaves as expected given prescribed data scenarios. But how do you ensure that both the upstream service, as well as your application, <i>actually</i> integrate as expected in a production scenario with real HTTP transactions, not just the stubbed responses you anticipate? How will you know in advance if your release candidate fails to gracefully handle an unnoticed third party API change? Or if you've introduced a bug in consuming third party data?</p>
<p>API versioning and hypermedia standards such as <a href="http://stateless.co/hal_specification.html">HAL</a> promise non-breaking changes. From this perspective such verification is arguably unnecessary. But what about human error and unanticipated problems? Mistakes happen. And what about services that don't promise non-breaking changes?</p>
<p><b>Solution</b>: Simple Rspec integration tests ensure your application appropriately handles real HTTP requests against the third party service. The following offers a basic pattern in Rails. I assume you're using <a href="http://rspec.info/">Rspec</a> and that your unit tests stub HTTP request with <a href="https://github.com/bblimke/webmock">webmock</a>.</p>
<p>Create a <code>config/environments/integration.rb</code> config file:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6</pre></td><td class="code"><pre><span class="c1"># inherit the test.rb config values</span>
<span class="nb">load</span><span class="p">(</span><span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"config"</span><span class="p">,</span> <span class="s2">"environments"</span><span class="p">,</span> <span class="s2">"test.rb"</span><span class="p">))</span>
<span class="no">YourApp</span><span class="o">::</span><span class="no">Application</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span>
<span class="c1"># integration-specific overrides can go here</span>
<span class="k">end</span>
</pre></td></tr></tbody></table>
</div>
<p>Create some conditional logic in your <code>spec_helper.rb</code> surrounding This excludes Rspec tests tagged <code>:integration</code> unless the <code>RAILS_ENV</code> environment variable is set to 'integration'.</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6</pre></td><td class="code"><pre><span class="no">RSpec</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">.</span><span class="nf">filter_run_excluding</span> <span class="ss">:integration</span> <span class="k">unless</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'RAILS_ENV'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'integration'</span>
<span class="k">end</span>
<span class="c1"># disables HTTP requests for all non-integration tests</span>
<span class="no">WebMock</span><span class="p">.</span><span class="nf">disable_net_connect!</span>
</pre></td></tr></tbody></table>
</div>
<p>Create an integration test file:</p>
<div class="highlight shell"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="gp">$ </span>touch spec/integration/api/user_spec.rb
</pre></td></tr></tbody></table>
</div>
<p>And add the following test code:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20</pre></td><td class="code"><pre><span class="nb">require</span> <span class="s1">'spec_helper'</span>
<span class="n">describe</span> <span class="s2">"/api/user"</span><span class="p">,</span> <span class="ss">:integration</span> <span class="o">=></span> <span class="kp">true</span> <span class="k">do</span>
<span class="n">subject</span> <span class="p">{</span> <span class="n">response</span> <span class="p">}</span>
<span class="n">before</span> <span class="ss">:each</span> <span class="k">do</span>
<span class="no">WebMock</span><span class="p">.</span><span class="nf">disable!</span>
<span class="n">get</span> <span class="s1">'api/user'</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"the API requests succeed as expected"</span> <span class="k">do</span>
<span class="n">its</span><span class="p">(</span><span class="ss">:status</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="mi">200</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"verifying its JSON attributes"</span> <span class="k">do</span>
<span class="n">subject</span> <span class="p">{</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">)</span> <span class="p">}</span>
<span class="n">its</span><span class="p">([</span><span class="s1">'username'</span><span class="p">])</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'mdb'</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table>
</div>
<p>Create a Rake task to run the integration tests:</p>
<div class="highlight shell"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="gp">$ </span>touch lib/tasks/integration.rake
</pre></td></tr></tbody></table>
</div>
<p>With the following code:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13</pre></td><td class="code"><pre><span class="nb">require</span> <span class="s1">'rspec/core/rake_task'</span>
<span class="n">namespace</span> <span class="ss">:integration</span> <span class="k">do</span>
<span class="n">desc</span> <span class="s2">"integration test the JSON API endpoints"</span>
<span class="no">RSpec</span><span class="o">::</span><span class="no">Core</span><span class="o">::</span><span class="no">RakeTask</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:test</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
<span class="c1"># set the RAILS_ENV such that :integration tagged</span>
<span class="c1"># specs are run</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'RAILS_ENV'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'integration'</span>
<span class="c1"># only run those files in the 'integration' directory</span>
<span class="n">t</span><span class="p">.</span><span class="nf">pattern</span> <span class="o">=</span> <span class="s2">"./spec/integration{,/*/**}/*_spec.rb"</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table>
</div>
<p>Run your integration tests:</p>
<div class="highlight shell"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="gp">$ </span>rake integration:test
</pre></td></tr></tbody></table>
</div>
<p>Integration tests can be run against each build. Such tests could also be run periodically against your production code, thus alerting the team should a breaking change our outtage occur.</p>
A Simple Ruby Class Example/blog/simple-ruby-class-example/2014-03-29T20:00:00-04:002014-03-29T20:00:00-04:00Mike Ball<p>Some colleagues asked about basic Ruby examples. The following RemoteConfig class makes an HTTP request and provides an object-oriented interface to XML served at the URL requested. The class serves as simple intro to some common needs and Ruby-oriented...</p><p>Some colleagues asked about basic Ruby examples. The following RemoteConfig class makes an HTTP request and provides an object-oriented interface to XML served at the URL requested. The class serves as simple intro to some common needs and Ruby-oriented language features:</p>
<ul>
<li>dynamic method definition</li>
<li>performing GET requests over HTTPS</li>
<li>creating a basic, object-oriented interface</li>
<li>parsing attribute-heavy XML with XPATH queries</li>
<li>testing with <a href="http://rspec.info/">Rspec</a> and <a href="https://github.com/bblimke/webmock">Webmock</a></li>
</ul>
<h2 id="the-xml">The XML</h2>
<p>Assume the following XML is hosted at <a href="https://somedomain.com/config.xml:">https://somedomain.com/config.xml:</a></p>
<div class="highlight xml"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7</pre></td><td class="code"><pre><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><configuration></span>
<span class="nt"><add</span> <span class="na">key=</span><span class="s">"first_key"</span> <span class="na">value=</span><span class="s">"first key value"</span> <span class="nt">/></span>
<span class="nt"><add</span> <span class="na">key=</span><span class="s">"second_key"</span> <span class="na">value=</span><span class="s">"second key value"</span> <span class="nt">/></span>
<span class="nt"><add</span> <span class="na">key=</span><span class="s">"third_key"</span> <span class="na">value=</span><span class="s">"third key value"</span> <span class="nt">/></span>
<span class="nt"><add</span> <span class="na">key=</span><span class="s">"fourth_key"</span> <span class="na">value=</span><span class="s">"fourth key value"</span> <span class="nt">/></span>
<span class="nt"></configuration></span>
</pre></td></tr></tbody></table>
</div>
<h2 id="the-ruby">The Ruby</h2>
<p>A Ruby class providing an interface to the above-cited XML could like like this:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75</pre></td><td class="code"><pre><span class="nb">require</span> <span class="s1">'net/http'</span>
<span class="nb">require</span> <span class="s1">'uri'</span>
<span class="nb">require</span> <span class="s1">'nokogiri'</span>
<span class="k">class</span> <span class="nc">RemoteConfig</span>
<span class="kp">attr_reader</span> <span class="ss">:xml</span>
<span class="c1"># On instantiation, perform an HTTP request to the XML</span>
<span class="c1"># config file, parse it with nokogiri, and define methods</span>
<span class="c1"># through which its values can be accessed:</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@xml</span> <span class="o">=</span> <span class="n">get_and_parse_config</span>
<span class="n">create_methods</span>
<span class="k">end</span>
<span class="c1"># A method to store the XML endpoint</span>
<span class="k">def</span> <span class="nf">url</span>
<span class="s2">"https://somedomain.com/config.xml"</span>
<span class="k">end</span>
<span class="c1"># private methods; not publicly exposed for use</span>
<span class="c1"># in an RemoteConfig instance</span>
<span class="kp">private</span>
<span class="c1"># Perform a GET request to retrieve the remote XML</span>
<span class="c1"># and parse the response with Nokogiri</span>
<span class="k">def</span> <span class="nf">get_and_parse_config</span>
<span class="no">Nokogiri</span><span class="o">::</span><span class="no">XML</span><span class="p">(</span><span class="n">get_remote_config</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Set up Net::HTTP to perform a GET request against</span>
<span class="c1"># the remote XML URL using HTTPS.</span>
<span class="c1"># Retrieve the response body.</span>
<span class="k">def</span> <span class="nf">get_remote_config</span>
<span class="n">http</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">host</span><span class="p">,</span> <span class="n">uri</span><span class="p">.</span><span class="nf">port</span><span class="p">)</span>
<span class="n">http</span><span class="p">.</span><span class="nf">use_ssl</span> <span class="o">=</span> <span class="kp">true</span>
<span class="n">http</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span><span class="n">request</span><span class="p">).</span><span class="nf">body</span>
<span class="k">end</span>
<span class="c1"># Perform the HTTP request to the XML file:</span>
<span class="k">def</span> <span class="nf">request</span>
<span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="no">Get</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">request_uri</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># parse the URL string as a URI</span>
<span class="k">def</span> <span class="nf">uri</span>
<span class="no">URI</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Use xpath to fetch the relevant attribute value:</span>
<span class="k">def</span> <span class="nf">fetch_value</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="vi">@xml</span><span class="p">.</span><span class="nf">xpath</span><span class="p">(</span><span class="s2">"//add[@key='</span><span class="si">#{</span><span class="n">value</span><span class="si">}</span><span class="s2">']/@value"</span><span class="p">).</span><span class="nf">text</span>
<span class="k">end</span>
<span class="c1"># An array of the methods we want a RemoteConfig</span>
<span class="c1"># instance to have:</span>
<span class="k">def</span> <span class="nf">available_methods</span>
<span class="p">[</span>
<span class="ss">:first_key</span><span class="p">,</span>
<span class="ss">:second_key</span><span class="p">,</span>
<span class="ss">:third_key</span><span class="p">,</span>
<span class="ss">:fourth_key</span>
<span class="p">]</span>
<span class="k">end</span>
<span class="c1"># Rather than repeat our method logic, define the public</span>
<span class="c1"># instance methods on instantantiation of the class:</span>
<span class="k">def</span> <span class="nf">create_methods</span>
<span class="n">available_methods</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="nb">method</span><span class="o">|</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:define_method</span><span class="p">,</span> <span class="nb">method</span><span class="p">)</span> <span class="p">{</span> <span class="n">fetch_value</span> <span class="nb">method</span><span class="p">.</span><span class="nf">to_s</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="usage">Usage</h2>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11</pre></td><td class="code"><pre><span class="c1"># instantiate an instance of RemoteConfig</span>
<span class="n">config</span> <span class="o">=</span> <span class="no">RemoteConfig</span><span class="p">.</span><span class="nf">new</span>
<span class="n">config</span><span class="p">.</span><span class="nf">first_key</span>
<span class="c1"># => 'first key value'</span>
<span class="n">config</span><span class="p">.</span><span class="nf">second_key</span>
<span class="c1"># => 'second key value'</span>
<span class="n">config</span><span class="p">.</span><span class="nf">url</span>
<span class="c1"># => 'https://somedomain.com/config.xml'</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="testing">Testing</h2>
<p>I'm assuming you're using Rspec and webmock, and that you have a spec_helper.rb file.</p>
<p>Your spec_helper.rb contains the following:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6</pre></td><td class="code"><pre><span class="nb">require</span> <span class="s1">'webmock/rspec'</span>
<span class="nb">require</span> <span class="s1">'remote_config'</span>
<span class="c1"># Disable real HTTP network requests when</span>
<span class="c1"># running our tests:</span>
<span class="no">WebMock</span><span class="p">.</span><span class="nf">disable_net_connect!</span>
</pre></td></tr></tbody></table>
</div>
<p>And the spec looks like this:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53</pre></td><td class="code"><pre><span class="nb">require</span> <span class="s1">'spec_helper'</span>
<span class="n">describe</span> <span class="no">RemoteConfig</span> <span class="k">do</span>
<span class="n">subject</span><span class="p">(</span><span class="ss">:remote_config</span><span class="p">)</span> <span class="p">{</span> <span class="n">described_class</span><span class="p">.</span><span class="nf">new</span> <span class="p">}</span>
<span class="n">before</span> <span class="ss">:each</span> <span class="k">do</span>
<span class="c1"># Use webmock to stub HTTP requests to return the value we expect:</span>
<span class="n">stub_request</span><span class="p">(</span><span class="ss">:get</span><span class="p">,</span> <span class="s1">'https://somedomain.com/config.xml'</span><span class="p">).</span><span class="nf">to_return</span><span class="p">(</span>
<span class="ss">:status</span> <span class="o">=></span> <span class="mi">200</span><span class="p">,</span>
<span class="ss">:body</span> <span class="o">=></span> <span class="s1">'<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<add key="first_key" value="first key value" />
<add key="second_key" value="second key value" />
<add key="third_key" value="third key value" />
<add key="fourth_key" value="fourth key value" />
</configuration>'</span>
<span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Test its public methods:</span>
<span class="n">describe</span> <span class="s2">"#initialize"</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"creates a Nokogiri XML document"</span> <span class="k">do</span>
<span class="n">expect</span><span class="p">(</span><span class="n">remote_config</span><span class="p">.</span><span class="nf">xml</span><span class="p">.</span><span class="nf">class</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="no">Nokogiri</span><span class="o">::</span><span class="no">XML</span><span class="o">::</span><span class="no">Document</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">describe</span> <span class="s2">"#first_key"</span> <span class="k">do</span>
<span class="n">subject</span> <span class="p">{</span> <span class="n">remote_config</span><span class="p">.</span><span class="nf">first_key</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'first key value'</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">describe</span> <span class="s2">"#second_key"</span> <span class="k">do</span>
<span class="n">subject</span> <span class="p">{</span> <span class="n">remote_config</span><span class="p">.</span><span class="nf">second_key</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'second key value'</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">describe</span> <span class="s2">"#third_key"</span> <span class="k">do</span>
<span class="n">subject</span> <span class="p">{</span> <span class="n">remote_config</span><span class="p">.</span><span class="nf">third_key</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'third key value'</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">describe</span> <span class="s2">"#url"</span> <span class="k">do</span>
<span class="n">subject</span> <span class="p">{</span> <span class="n">remote_config</span><span class="p">.</span><span class="nf">url</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">eq</span> <span class="s1">'https://somdomain.com/config.xml'</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></td></tr></tbody></table>
</div>
Automated Tests: What, Why, How?/blog/automated-testing/2014-02-07T19:00:00-05:002014-02-07T19:00:00-05:00Mike Ball<p>In recent years, automated testing has gained significant traction as a software development best practice. But not everyone's sold or fully understands where to begin in developing such tests.</p>
<p>Some teams fear test-writing may impact velocity. Some...</p><p>In recent years, automated testing has gained significant traction as a software development best practice. But not everyone's sold or fully understands where to begin in developing such tests.</p>
<p>Some teams fear test-writing may impact velocity. Some believe an ill-designed, legacy codebase makes such tests impossible. Others don't fully grasp the benefits. And others simply don't know where to begin.</p>
<p>I'm frequently asked about automated testing — managers, product owners, UX designers, junior developers, and the uninitiated want to know: Why write tests? Where can I learn more? How do I get started?</p>
<p>Recently, I ran into a colleague on the West Philly Route 34 trolley. We discussed automated testing; his questions inspired me to capture some of my answers in writing...</p>
<h2 id="high-level-definition">High level definition</h2>
<p>What do I have in mind when I say "automated testing"? At a high level, I'm referring to some process through which software's source code can be executed and its quality verified.</p>
<p>What does this look like? Generally, such tests take the form of what are called functional, integration, and/or unit tests and can be executed via a build tool, task manager, or some easy-to-start process. Popular build tools include <a href="http://rake.rubyforge.org/">Rake</a> in the Ruby world, <a href="http://gruntjs.com/">Grunt</a> and <a href="http://gulpjs.com/">Gulp</a> for Node.js JavaScript, and <a href="http://maven.apache.org/what-is-maven.html">Maven</a> in the Java world. Plain old <a href="http://en.wikipedia.org/wiki/Shell_script">shell scripts</a> are fine too. Ideally, the automated tests are run by individual developers in development, as well as in <a href="http://en.wikipedia.org/wiki/Continuous_integration">continuous integration</a>. See <a href="https://travis-ci.org/">Travis CI</a> and <a href="http://jenkins-ci.org/">Jenkins</a> for go-to continuous integration solutions.</p>
<h2 id="why-write-tests?">Why write tests?</h2>
<ul>
<li>Tests ensure that code works exactly as it should. A robust test suite can help identify the bugs an engineer failed to consider. How does the code perform if data is malformed? Is there a memory leak? Tests assist in identifying such problems before the code reaches users.</li>
<li>Tests serve to document the software's assumptions and intent, as well as its business logic and requirements. Well-written tests help explain code; tests facilitate better understanding across a team, now and in the future.</li>
<li>Tests make it easier to edit to the software's source code. Healthy software can easily accomodate rapid change to features and business requirements. When a source code edit produces test failures, an engineer is given the necessary insight to assess the edit's implications. Maybe the test failure prompts the engineer to reconsider her logic in making the edit, or maybe the test failure helps the developer identify exactly what additional edits must be made to fix the tests and accomodate the change.</li>
<li>Tests save time. Automated tests can rapidly assert software's health such that code can more confidently and more frequently be released to users.</li>
</ul>
<h2 id="where-can-i-learn-more?">Where can I learn more?</h2>
<p>Bob Martin's <a href="http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd">The Three Rules of TDD</a> is a helpful read.</p>
<p><a href="http://code.tutsplus.com/tutorials/deciphering-testing-jargon--net-27513">Deciphering Testing Jargon</a> is a good high-level overview of software testing lexis.</p>
<h2 id="how-do-i-get-started?">How do I get started?</h2>
<p>Disclaimer: these days, my primary languages are Ruby and JavaScript so therein lies my bias.</p>
<p>I like <a href="http://rspec.info/">Rspec</a> for testing Ruby; tools like <a href="https://www.relishapp.com/vcr/vcr/docs">VCR</a>, <a href="https://github.com/bblimke/webmock">webmock</a>, <a href="http://jnicklas.github.io/capybara/">capybara</a> help. <a href="https://github.com/colszowka/simplecov">SimpleCov</a> can measure the extent to which your code is fully tested ("code coverage").</p>
<p>To test JavaScript, I most often use <a href="http://jasmine.github.io/">Jasmine</a>, though I've also turned to <a href="http://visionmedia.github.io/mocha/">Mocha</a> and <a href="http://chaijs.com/">Chai</a>. <a href="http://phantomjs.org/">Phantomjs</a> is a headless webkit; it allows you to run your tests without a GUI web browser and works well with the aforementioned frameworks. If you're using Ruby on Rails, <a href="https://github.com/modeset/teaspoon">teaspoon</a> is a flexible test runner that elegantly integrates these tools with consideration for the Rails Asset Pipeline. To measure code coverage, <a href="http://gotwarlost.github.io/istanbul/">istanbul</a> leads the way.</p>
<p>Functional tests that simulate end-user behavior are a bit more tricky in my experience, though a combination of <a href="http://cukes.info/">Cucumber</a>, <a href="http://docs.seleniumhq.org/">Selenium</a>, and <a href="http://jnicklas.github.io/capybara/">Capybara</a> provides a powerful ecosystem. Here again, <a href="http://phantomjs.org/">Phantomjs</a> can be helpful in providing a headless environment. If you prefer to write such functional tests in JavaScript, <a href="http://zombie.labnotes.org/">Zombie</a> and similar tools seem promising.</p>
<h2 id="some-more-specific-resources">Some more specific resources</h2>
<p>The <a href="http://rubykoans.com">Ruby Koans</a> provide a great intro to TDD, as well as a good framework for learning Ruby.</p>
<p><a href="http://jasmine.github.io/">Jasmine</a>, the JavaScript testing framework, offers a helpful <a href="http://jasmine.github.io/2.0/introduction.html">online introduction</a>.</p>
<p>And <a href="http://code.tutsplus.com/search?utf8=%E2%9C%93&view=&search%5Bkeywords%5D=testing">Tuts+</a> hosts a large collection of strong, easy-to-follow testing-related tutorials.</p>
<p>Have some other resources to share? <a href="http://twitter.com/clapexcitement">Reach out</a>.</p>
Nicar 2013 Conference/blog/nicar-2013-conference/2013-03-03T19:00:00-05:002013-03-03T19:00:00-05:00Mike Ball<p><em>On February 27 – March 2, I attended NICAR 2013 in Louisville, Kentucky. My attendance was generously funded through a Temple University Center for Public Interest Journalism sponsorship.</em></p>
<h2 id="what-is-nicar?">What is NICAR?</h2>
<p>NICAR is the National Institute for Computer...</p><p><em>On February 27 – March 2, I attended NICAR 2013 in Louisville, Kentucky. My attendance was generously funded through a Temple University Center for Public Interest Journalism sponsorship.</em></p>
<h2 id="what-is-nicar?">What is NICAR?</h2>
<p>NICAR is the National Institute for Computer-Assisted Reporting, a program of Investigative Reporters and Editors, Inc. and the Missouri School of Journalism. In the institute’s <a href="http://www.ire.org/nicar/about/">own words</a>, “NICAR has trained thousands of journalists in the practical skills of finding, prying loose and analyzing electronic information.” Data discovery, data analysis, and news applications such as interactive maps and info-graphics offer common examples of the “CAR” in “NICAR.” Nate Silver’s <a href="http://fivethirtyeight.blogs.nytimes.com/">FiveThirtyEight blog</a> and its suite of analyses, maps, graphics, and interactive applications is another popular example. In the world of of quality public interest news — and the mission of helping people better understand its quantitative nuance — CAR boasts a potent mix of computer science, design, and journalism. Within this field, NICAR is the flagship conference.</p>
<h2 id="who-attends-the-nicar-conference?">Who attends the NICAR conference?</h2>
<p>This year’s attendees included investigative reporters, news graphics editors, interactive news teams, programmers, political scientists, and civic technologists. Organizations like NPR, Propublica, the New York Times, the Chicago Tribune, the Associated Press, and the Huffington Post were represented. Philadelphia attendees included folks from the University of Pennsylvania, AxisPhilly, Philadelphia City Paper, The Inquierer, and Mozilla.</p>
<h2 id="session-notes">Session notes</h2>
<p>As is evidenced by the <a href="http://www.ire.org/conferences/nicar-2013/sessions/">schedule</a>, a range of speakers addressed a diversity of topics.</p>
<p>A few personal takeaways:</p>
<p><a href="https://github.com/ashaw">Al Shaw</a> of <a href="http://propublica.org">Propublica</a> did a great job teaching journalists how to leverage Ruby in the newsroom. He also demonstrated some fun web scraping techniques, utilizing both Ruby and Node.js to retrieve political campaign donor data from the Federal Election Commission website. His slides are online: <a href="http://shaw.al.s3.amazonaws.com/nicar13/nicar-2013-ruby.html">Intro to Ruby</a> and <a href="http://shaw.al.s3.amazonaws.com/nicar13/nicar-2013-node.html">Web scraping with Node</a>.</p>
<p><a href="http://tasneemraja.com/">Tasneem Raja</a> offered insight on <a href="https://github.com/motherjones">news applications at Mother Jones</a> and how her team uses Google Spreadsheets and <a href="https://github.com/jsoma/tabletop">Tabletop.js</a> as an easy-to-edit back-end for many of their interactive features. See my <a href="http://www.mikeball.us/blog/using-google-spreadsheets-and-tabletop-js-as-a-web-application-back-end">overview and tutorial</a> for a demo of the technique.</p>
<p>NPR’s <a href="http://github.com/jeremyjbowers">Jeremy Bowers</a> and the New York Times’ <a href="https://twitter.com/jacqui">Jacqui Maher</a> spoke about serving high traffic, scalable web applications. Per Jeremy Bowers: why serve dynamic pages when your application is largely static? Instead of relying on application servers, Jeremy and the NPR apps team deploy static, JavaScript-driven applications to Amazon S3. Jacqui reflected on her team’s challenges during the 2012 presidential election, relaying anecdotes and advice on CloudFront, Capistrano, alerts, and melting load balancers.</p>
<p>The New York Times’ <a href="http://twitter.com/harrisj">Jacob Harris</a>, the Chicago Tribune’s <a href="http://twitter.com/hbillings">Heather Billings</a>, and Propublica’s <a href="http://twitter.com/A_L">Al Shaw</a> lead “Infect the CMS,” a panel about the challenges and shortcomings of content management systems in developing news applications. The conclusion: no CMS is perfect. APIs, JSON flat files, Google Drive, and <a href="https://gist.github.com/ashaw/5084087">tasks to sanitize Microsoft Office documents</a> help. Cross-team communication and skill-sharing is also critical.</p>
<p>Friday’s fast-paced Lightning Talks were a crowd-pleaser. A few highlights: Propublica’s Jeff Larson spoke on <a href="https://github.com/thejefflarson/nicar-nate-silver">algorithm design</a>. The University of Nebraska-Lincoln’s <a href="http://blog.mattwaite.com/">Matt Weite</a> argued the reporting potential of hardware hacks and demonstrated a tool he wired up to record the TSA’s handling of his luggage. Katie Park offered good tips in showing big data on small screens. The New York Times’ <a href="http://twitter.com/harrisj">Jacob Harris</a> bemoaned the squirrelly state-by-state data handling and business logic that’s necessary in developing elections applications. <a href="http://twitter.com/palewire">Ben Welsh</a> of the Los Angeles Times delivered an energetic, standup-comedy-reminiscent rant in which he challenged journalists to think more like developers and challenged developers to answer their phones. He titled the talk “Django Retrained: Five ways coding like a Web developer can make you a better investigative reporter.”</p>
<p>Other talks and workshops covered topics like R, building interactive maps, interacting with Census data, JavaScript best practices, Python, Django, data visualization, SQL, machine learning, statistics, and Fusion Tables.</p>
<h2 id="personal-highlights">Personal highlights</h2>
<p>The conference also provided the opportunity to further develop some Philly relationships. Discussing <a href="https://twitter.com/jduchneskie">John Duchneskie</a>‘s experience starting at The Inquirer a week after the <a href="http://en.wikipedia.org/wiki/MOVE">1985 MOVE bombing</a> was priceless. Learning more about <a href="http://staterep.me">StateRep.me</a> from Penn’s <a href="http://twitter.com/notthatbreezy">Chris Brown</a>, chatting about West Philly with City Paper’s <a href="http://twitter.com/rw_briggs">Ryan Briggs</a>, and meeting this year’s Knight-Mozilla OpenNews fellows through <a href="https://twitter.com/erika_owens">Erica Owens</a> were other highlights. I also spent a lot of time talking shop with my buddies <a href="https://twitter.com/caseypt">Casey Thomas</a> and <a href="https://twitter.com/pamasaur">Pam Selle</a> from <a href="http://axisphilly.org/">AxisPhilly</a>.</p>
<h2 id="summary">Summary</h2>
<p>Great conference, inspiring field — much gratitude to Temple’s Center for Public Interest Journalism for sending me. NICAR 2014 is in Baltimore; I expect more representation from Philly locals.</p>
Using Google Spreadsheets and Tabletop.js as a Web Application Back-end/blog/using-google-spreadsheets-and-tabletop-as-a-back-end/2013-03-01T19:00:00-05:002013-03-01T19:00:00-05:00Mike Ball<p>At <a href="http://ire.org/events-and-training/event/315">NICAR 2013</a>, <a href="http://tasneemraja.com">Tasneem Raja</a> spoke on <a href="http://ire.org/events-and-training/event/315/623">Smarter interactive Web projects with Google Spreadsheets and Tabletop.js</a>. Tasneem is <em>Mother Jones</em>‘s Interactive Editor; she outlined how <em>Mother Jones</em> uses Google Spreadsheets to power some of its interactive features...</p><p>At <a href="http://ire.org/events-and-training/event/315">NICAR 2013</a>, <a href="http://tasneemraja.com">Tasneem Raja</a> spoke on <a href="http://ire.org/events-and-training/event/315/623">Smarter interactive Web projects with Google Spreadsheets and Tabletop.js</a>. Tasneem is <em>Mother Jones</em>‘s Interactive Editor; she outlined how <em>Mother Jones</em> uses Google Spreadsheets to power some of its interactive features.</p>
<p>Beyond serving as a simple, easy-to-maintain datastore and CMS, Google Spreadsheets — used in concert with <a href="https://github.com/jsoma/tabletop">Tabletop.js</a> — allows for the creation of dynamic web content in absence of server-side processing, in effect empowering a highly scalable and simple architecture.</p>
<p>The following offers a basic example.</p>
<p><b>1.</b> Sign into <a href="https://drive.google.com">Google Drive</a> with your Google credentials and create a new Spreadsheet titled <code>tabletop_example</code> with the following content:</p></p>
<p><img src="http://www.mikeball.us/wp-content/uploads/2013/03/tabletop_spreadsheet.png" alt="tabletop_spreadsheet" /></p>
<p><b>2.</b> Click <code>File > Publish to the Web > Start Publishing</code> to publicly publish your spreadsheet.
<b>3.</b> Create a project directory with the following files and structure:</p></p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5</pre></td><td class="code"><pre>├── index.html
└── js
├── app.js
└── vendor
└── tabletop.js
</pre></td></tr></tbody></table>
</div>
<p>Grab a copy of <a href="https://github.com/jsoma/tabletop">Tabletop.js</a> to store in <code>js/vendor/tabletop.js</code>.</p>
<p>Set up the homepage by adding the following to index.html:</p>
<div class="highlight html"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15</pre></td><td class="code"><pre><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Some Site Title<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><header></span>
<span class="nt"><h1><a</span> <span class="na">href=</span><span class="s">"/"</span><span class="nt">></span>Some Site Title<span class="nt"></a></h1></span>
<span class="nt"></header></span>
<span class="nt"><ul</span> <span class="na">id=</span><span class="s">"politicians"</span><span class="nt">></ul></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"http://code.jquery.com/jquery-1.9.1.min.js"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"js/vendor/tabletop.js"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"js/app.js"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></td></tr></tbody></table>
</div>
<p>Retrieve the document key associated with your <code>tabletop_example</code> spreadsheet. This can be found in the document’s URL: docs.google.com/spreadsheet/ccc?key=<strong>YOUR_DOCUMENT_KEY_APPEARS_HERE</strong></p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18</pre></td><td class="code"><pre><span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">ready</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">Tabletop</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
<span class="na">key</span><span class="p">:</span> <span class="s1">'YOUR_DOCUMENT_KEY_GOES_HERE'</span><span class="p">,</span>
<span class="na">callback</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">tabletop</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">i</span><span class="p">,</span>
<span class="nx">dataLength</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&<</span><span class="nx">dataLength</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#politicians'</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'<li>'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">text</span><span class="p">:</span> <span class="nx">data</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">politician</span> <span class="o">+</span> <span class="s1">', '</span> <span class="o">+</span> <span class="nx">data</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">position</span>
<span class="p">})</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">simpleSheet</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">});</span>
<span class="p">});</span>
</pre></td></tr></tbody></table>
</div>
<p>Open <code>index.html</code> in your web browser.</p>
<p>While the above code outlines a fairly simple example, tools like <a href="http://backbonejs.org">Backbone.js</a> provide the opportunity to layer in functionality such as URL routing, filtering, and sorting. And again, because Tabletop.js requires no application server, this solution requires no technology beyond static HTML, CSS, and JavaScript. As such, it’s highly scalable when deployed to a web server like <a href="http://nginx.org">nginx</a> or <a href="http://httpd.apache.org">Apache</a>.</p>
Streamlined Rails Gem Updates on Gem in a Box/blog/streamlined-rails-gem-updates-on-gem-in-a-box/2013-02-10T19:00:00-05:002013-02-10T19:00:00-05:00Mike Ball<p><strong>Problem</strong>: Per recent Ruby on Rails security patches, you need to update your Rails applications. However, because you host Rails and its related gems on a private <a href="https://github.com/cwninja/geminabox">Gem in a Box</a> gem server, it’s a bit cumbersome to manually download the necessary gems...</p><p><strong>Problem</strong>: Per recent Ruby on Rails security patches, you need to update your Rails applications. However, because you host Rails and its related gems on a private <a href="https://github.com/cwninja/geminabox">Gem in a Box</a> gem server, it’s a bit cumbersome to manually download the necessary gems from <a href="http://rubygems.org">Rubygems.org</a>, and then upload them to your Gem in a Box gem server.</p>
<p><strong>Solution</strong>: bash and curl.</p>
<p><b>1.</b> List the necessary gems in a <code>gems.txt</code> file:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td><td class="code"><pre>rails
actionmailer
activemodel
actionview
actionpack
activerecord
activeresource
activesupport
railties
</pre></td></tr></tbody></table>
</div>
<p><b>2.</b> Create a bash script in a <code>get_gems.sh</code> file to retrieve the gems from Rubygems.org. Note that I’ve created a <code>VERSION</code> variable specifying the desired gems’ version.</p>
<div class="highlight shell"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7</pre></td><td class="code"><pre><span class="c">#!/bin/bash</span>
<span class="nv">VERSION</span><span class="o">=</span>4.1.8
<span class="k">for </span>ARG <span class="k">in</span> <span class="sb">`</span>cat <span class="nv">$1</span><span class="sb">`</span>; <span class="k">do
</span>curl -L <span class="s2">"http://rubygems.org/downloads/</span><span class="nv">$ARG</span><span class="s2">-</span><span class="nv">$VERSION</span><span class="s2">.gem"</span> > <span class="s2">"</span><span class="nv">$ARG</span><span class="s2">-</span><span class="nv">$VERSION</span><span class="s2">.gem"</span>
<span class="k">done</span>
</pre></td></tr></tbody></table>
</div>
<p><b>3.</b> Create a bash script in a <code>post_gems.sh</code> file to post the downloaded gems to your Gem in a Box. Note that I’ve created a <code>VERSION</code> variable specifying the desired gems’ version here too.</p>
<div class="highlight shell"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7</pre></td><td class="code"><pre><span class="c">#!/bin/bash</span>
<span class="nv">VERSION</span><span class="o">=</span>4.1.8
<span class="k">for </span>ARG <span class="k">in</span> <span class="sb">`</span>cat <span class="nv">$1</span><span class="sb">`</span>; <span class="k">do
</span>curl -v -X POST -F <span class="nv">file</span><span class="o">=</span>@<span class="nv">$ARG</span>-<span class="nv">$VERSION</span>.gem http://YOUR_GEM_IN_A_BOX_USERNAME:YOUR_PASSWORD@YOUR_GEM_IN_A_BOX.com/upload
<span class="k">done</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="usage">Usage</h2>
<p>Download gems:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>./get_gems.sh gems.txt
</pre></td></tr></tbody></table>
</div>
<p>Upload gems:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>./post_gems.sh gems.txt
</pre></td></tr></tbody></table>
</div>
Using the phl_geocode Ruby Gem/blog/using-the-phl-geocode-ruby-gem/2012-12-16T19:00:00-05:002012-12-16T19:00:00-05:00Mike Ball<p>I recently released <a href="http://github.com/mdb/phl_geocode.rb">phl_geocode</a>, a simple Ruby gem which gets latitude and longitude coordinates for a Philadelphia address.</p>
<h2 id="getting-started">Getting Started</h2>
<p><b>1.</b> Install phl_geocode:</p>
<div class="highlight plaintext">
<table style="border-spacing: 0"><tbody><tr>
<td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td>
<td class="code"><pre>gem install phl_geocode
</pre></td>
</tr></tbody></table>
</div>
<p><b>2.</b> Require phl_geocode:</p>
<div class="highlight plaintext">
<table style="border-spacing: 0"><tbody><tr>
<td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td>
<td class="code"><pre>require "phl_geocode"
</pre></td>
</tr></tbody></table>
</div>
<p><b>3.</b></p><p>I recently released <a href="http://github.com/mdb/phl_geocode.rb">phl_geocode</a>, a simple Ruby gem which gets latitude and longitude coordinates for a Philadelphia address.</p>
<h2 id="getting-started">Getting Started</h2>
<p><b>1.</b> Install phl_geocode:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>gem install phl_geocode
</pre></td></tr></tbody></table>
</div>
<p><b>2.</b> Require phl_geocode:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>require "phl_geocode"
</pre></td></tr></tbody></table>
</div>
<p><b>3.</b> Instantiate a PHLGeocode instance:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="n">phl</span> <span class="o">=</span> <span class="no">PHLGeocode</span><span class="p">.</span><span class="nf">new</span>
</pre></td></tr></tbody></table>
</div>
<p><b>4.</b> Get latitude/longitude coordinates for a philadelphia address:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="n">phl</span><span class="p">.</span><span class="nf">get_coordinates</span> <span class="s2">"1500 market street"</span>
</pre></td></tr></tbody></table>
</div>
<p>Example response:</p>
<div class="highlight ruby"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11</pre></td><td class="code"><pre><span class="p">[{</span>
<span class="ss">:address</span> <span class="o">=></span> <span class="s2">"1500 MARKET ST"</span><span class="p">,</span>
<span class="ss">:similarity</span> <span class="o">=></span> <span class="mi">100</span><span class="p">,</span>
<span class="ss">:latitude</span> <span class="o">=></span> <span class="mi">39</span><span class="o">.</span><span class="mi">9521740263203</span><span class="p">,</span>
<span class="ss">:longitude</span> <span class="o">=></span> <span class="o">-</span><span class="mi">75</span><span class="o">.</span><span class="mi">1661518986459</span>
<span class="p">},</span> <span class="p">{</span>
<span class="ss">:address</span> <span class="o">=></span> <span class="s2">"1500S MARKET ST"</span><span class="p">,</span>
<span class="ss">:similarity</span> <span class="o">=></span> <span class="mi">99</span><span class="p">,</span>
<span class="ss">:latitude</span> <span class="o">=></span> <span class="mi">39</span><span class="o">.</span><span class="mi">9521740263203</span><span class="p">,</span>
<span class="ss">:longitude</span> <span class="o">=></span> <span class="o">-</span><span class="mi">75</span><span class="o">.</span><span class="mi">1661518986459</span>
<span class="p">}]</span>
</pre></td></tr></tbody></table>
</div>
Using the civic-info Node.js Module to Get Voter and Election Info/blog/using-the-civic-info-node-module/2012-11-07T19:00:00-05:002012-11-07T19:00:00-05:00Mike Ball<p></p><p>Inspired by election apps like <a href="https://github.com/joannecheng/vote">vote</a>, I wrote <a href="http://github.com/mdb/civic-info.js">civic-info.js</a>, a simple Node.js module to interface with Google’s <a href="https://developers.google.com/civic-information">Civic Info API</a>.<br></p>
<h2 id="getting-started">Getting Started</h2>
<p><b>1.</b> Secure a Google API key.
<b>2.</b> Install civic-info:</p>
<div class="highlight plaintext">
<table style="border-spacing: 0"><tbody><tr>
<td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td>
<td class="code"><pre>npm install civic-info
</pre></td>
</tr></tbody></table>
</div>
<p><b>3.</b> Require and instantiate...</p><p><p>Inspired by election apps like <a href="https://github.com/joannecheng/vote">vote</a>, I wrote <a href="http://github.com/mdb/civic-info.js">civic-info.js</a>, a simple Node.js module to interface with Google’s <a href="https://developers.google.com/civic-information">Civic Info API</a>.<br /></p>
<h2 id="getting-started">Getting Started</h4></h2>
<p><b>1.</b> Secure a Google API key.
<b>2.</b> Install civic-info:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>npm install civic-info
</pre></td></tr></tbody></table>
</div>
<p><b>3.</b> Require and instantiate civic-info with your Google API key:</p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="kd">var</span> <span class="nx">civicInfo</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"civic-info"</span><span class="p">)({</span><span class="na">apiKey</span><span class="p">:</span> <span class="s2">"YOUR KEY"</span><span class="p">});</span>
</pre></td></tr></tbody></table>
</div>
<p>Alteratively, you can set a <code>GOOGLE_API_KEY</code> environment variable and instantiate like so:</p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="kd">var</span> <span class="nx">civicInfo</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"civic-info"</span><span class="p">)();</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="examples">Examples</h2>
<p>Get election info and election IDs:</p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3</pre></td><td class="code"><pre><span class="nx">civicInfo</span><span class="p">.</span><span class="nx">elections</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="p">});</span>
</pre></td></tr></tbody></table>
</div>
<p>Resulting response:</p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td><td class="code"><pre><span class="p">{</span>
<span class="nl">kind</span><span class="p">:</span> <span class="s1">'civicinfo#electionsQueryResponse'</span><span class="p">,</span>
<span class="nx">elections</span><span class="err">:</span>
<span class="p">[</span> <span class="p">{</span>
<span class="na">id</span><span class="p">:</span> <span class="s1">'2000'</span><span class="p">,</span>
<span class="na">name</span><span class="p">:</span> <span class="s1">'VIP Test Election'</span><span class="p">,</span>
<span class="na">electionDay</span><span class="p">:</span> <span class="s1">'2013-06-06'</span>
<span class="p">}</span> <span class="p">]</span>
<span class="p">}</span>
</pre></td></tr></tbody></table>
</div>
<p>Get voter information such as polling places, contests, candidates, etc. surrounding an election whose electionID is ’4000′ for a voter who lives at 1500 Market Street in Philadelphia:</p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3</pre></td><td class="code"><pre><span class="nx">civicInfo</span><span class="p">.</span><span class="nx">voterInfo</span><span class="p">({</span><span class="na">electionID</span><span class="p">:</span> <span class="s1">'4000'</span><span class="p">,</span> <span class="na">address</span><span class="p">:</span> <span class="s1">'1500 Market Street, Philadelphia, PA'</span><span class="p">},</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="p">});</span>
</pre></td></tr></tbody></table>
</div>
Deploying an Express App to Heroku/blog/deploying-an-express-app-to-heroku/2012-10-21T20:00:00-04:002012-10-21T20:00:00-04:00Mike Ball<p>Some coworkers expressed interest in deploying <a href="http://expressjs.com">Express</a> apps to <a href="http://heroku.com">Heroku</a>. These instructions seek to provide a basic overview, though Heroku offers much more robust documentation in its <a href="https://devcenter.heroku.com">dev center</a>.</p>
<p>Step 1: Create a <a href="https://api.heroku.com/signup">Heroku</a> account.</p>
<p>Step 2: Install the...</p><p>Some coworkers expressed interest in deploying <a href="http://expressjs.com">Express</a> apps to <a href="http://heroku.com">Heroku</a>. These instructions seek to provide a basic overview, though Heroku offers much more robust documentation in its <a href="https://devcenter.heroku.com">dev center</a>.</p>
<p>Step 1: Create a <a href="https://api.heroku.com/signup">Heroku</a> account.</p>
<p>Step 2: Install the <a href="https://toolbelt.herokuapp.com">Heroku Toolbelt</a>, which includes the Heroku command line client, Git, and Foreman.</li></p>
<p>Step 3: Log in by entering the following in the command line:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku login
</pre></td></tr></tbody></table>
</div>
<p>Step 4: Install <a href="http://nodejs.org">Node.js</a>.</p>
<p>Step 5: Create an Express app.</p>
<p>Let’s call it <code>heroku-demo</code>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>mkdir heroku-demo
</pre></td></tr></tbody></table>
</div>
<p>Define the application dependencies via a heroku-demo/package.json file:</p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td><td class="code"><pre><span class="p">{</span>
<span class="s2">"name"</span><span class="err">:</span> <span class="s2">"heroku-demo"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="err">:</span> <span class="s2">"Basic Express.js/Heroku demo"</span><span class="p">,</span>
<span class="s2">"version"</span><span class="err">:</span> <span class="s2">"0.0.1"</span><span class="p">,</span>
<span class="s2">"private"</span><span class="err">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="s2">"dependencies"</span><span class="err">:</span> <span class="p">{</span>
<span class="s2">"express"</span><span class="err">:</span> <span class="s2">"3.x"</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table>
</div>
<p>Install the package:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>npm install
</pre></td></tr></tbody></table>
</div>
<p>Set up the app via a <code>heroku-demo/app.js</code> file:</p>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14</pre></td><td class="code"><pre><span class="kd">var</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"express"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="c1">// Set up a URL route</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">"/"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="s2">"Heroku Demo!"</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// bind the app to listen for connections on a specified port</span>
<span class="kd">var</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span> <span class="o">||</span> <span class="mi">3000</span><span class="p">;</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">);</span>
<span class="c1">// Render some console log output</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Listening on port "</span> <span class="o">+</span> <span class="nx">port</span><span class="p">);</span>
</pre></td></tr></tbody></table>
</div>
<p>Now, you can run your app locally and view it in your browser at <code><a href="http://localhost:3000">http://localhost:3000</a></code>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>node app.js
</pre></td></tr></tbody></table>
</div>
<p>Step 6: Declare your app’s process types with a <code>heroku-demo/Procfile</code> so that it can run with <a href="https://github.com/ddollar/foreman">Foreman</a>:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>web: node app.js
</pre></td></tr></tbody></table>
</div>
<p>This declares a “web” process, as well as the command needed to run it. You can test that your Procfile/Foreman works:</p></p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>foreman start
</pre></td></tr></tbody></table>
</div>
<p>Step 7: Make <code>heroku-demo</code> a Git repository:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3</pre></td><td class="code"><pre>git init
git add .
git commit -m "Initial commit"
</pre></td></tr></tbody></table>
</div>
<p>Step 8: Deploy heroku-demo to Heroku.</p>
<p>Create a Heroku app:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku create
</pre></td></tr></tbody></table>
</div>
<p>Deploy the code to Heroku:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>git push heroku master
</pre></td></tr></tbody></table>
</div>
<p>Step 9: View your Heroku-hosted app in your web browser:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku open
</pre></td></tr></tbody></table>
</div>
<h2 id="some-additional-heroku-cli-tips">Some Additional Heroku CLI Tips</h2>
<p>Make your app available at a custom newname.herokuapp.com subdomain:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku apps:rename newname
</pre></td></tr></tbody></table>
</div>
<p>You can also use custom domains. See the <a href="https://devcenter.heroku.com/articles/custom-domains">Heroku documentation</a>.</p>
<p>View environment variables:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku config
</pre></td></tr></tbody></table>
</div>
<p><a href="https://devcenter.heroku.com/articles/config-vars">More Heroku environment variable documentation</a>.</p>
<p>Add environment variables:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku config:add NODE_ENV=production
</pre></td></tr></tbody></table>
</div>
<p>View logs:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku logs
</pre></td></tr></tbody></table>
</div>
<p>View running processes:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku ps
</pre></td></tr></tbody></table>
</div>
<p>Learn more about the Heroku command line client:</p>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>heroku help
</pre></td></tr></tbody></table>
</div>
Testing Node.js with Mocha, Expect.js, and Nock/blog/testing-node-with-mocha-expect-and-nock/2012-10-11T20:00:00-04:002012-10-11T20:00:00-04:00Mike Ball<p><strong>Problem</strong>: Your Node.js code uses <a href="https://github.com/voxpelli/node-request">request</a> or <a href="http://nodejs.org/api/http.html">http</a> to make http requests to URLs. You don’t want to make actual http calls, nor do you want to test request and/or http. How can you test that your code works as intended and interfaces properly with request...</p><p><strong>Problem</strong>: Your Node.js code uses <a href="https://github.com/voxpelli/node-request">request</a> or <a href="http://nodejs.org/api/http.html">http</a> to make http requests to URLs. You don’t want to make actual http calls, nor do you want to test request and/or http. How can you test that your code works as intended and interfaces properly with request and http?</p>
<p><strong>Solution</strong>: Use <a href="https://github.com/flatiron/nock">nock</a>. For the purposes of this example, I’ll also demonstrate how nock works in concert with <a href="http://visionmedia.github.com/mocha">mocha</a> and <a href="https://github.com/LearnBoost/expect.js">expect.js</a>.</p>
<h2 id="your-node-module">Your node module</h2>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16</pre></td><td class="code"><pre><span class="c1">// Let's call this file/module flickr-feeder.js</span>
<span class="kd">var</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"request"</span><span class="p">)</span>
<span class="p">,</span> <span class="nx">_</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"underscore"</span><span class="p">);</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">getFlickrJSON</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">params</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">url</span> <span class="o">=</span> <span class="s2">"http://api.flickr.com/services/feeds/photos_public.gne"</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">paramsObj</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'format'</span><span class="p">:</span> <span class="s1">'json'</span>
<span class="p">};</span>
<span class="nx">_</span><span class="p">.</span><span class="nx">extend</span><span class="p">(</span><span class="nx">paramsObj</span><span class="p">,</span> <span class="nx">params</span><span class="p">);</span>
<span class="nx">request</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span><span class="na">qs</span><span class="p">:</span> <span class="nx">paramsObj</span><span class="p">},</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">response</span><span class="p">,</span> <span class="nx">body</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">callback</span><span class="p">(</span><span class="nx">body</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">};</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="example-usage">Example Usage</h2>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5</pre></td><td class="code"><pre><span class="kd">var</span> <span class="nx">ff</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'flickr-feeder'</span><span class="p">);</span>
<span class="c1">// get JSON data from http://api.flickr.com/services/feeds/photos_public.gne?id=someFlickrID&#038;format=json</span>
<span class="nx">ff</span><span class="p">.</span><span class="nx">getFlickrJSON</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="s1">'someFlickrID'</span><span class="p">},</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="c1">// the JSON response from Flickr</span>
<span class="p">});</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="test-code">Test Code</h2>
<div class="highlight javascript"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27</pre></td><td class="code"><pre><span class="c1">// This file is lives in test/flickr-feeder.js</span>
<span class="kd">var</span> <span class="nx">flickrFeeder</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'../flickr-feeder.js'</span><span class="p">)</span>
<span class="p">,</span> <span class="nx">nock</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'nock'</span><span class="p">)</span>
<span class="p">,</span> <span class="nx">expect</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'expect.js'</span><span class="p">);</span>
<span class="nx">describe</span><span class="p">(</span><span class="s2">"flickrFeeder"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">describe</span><span class="p">(</span><span class="s2">"#getFlickrJSON"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// verify that the getFlickrJSON method exists</span>
<span class="nx">it</span><span class="p">(</span><span class="s2">"exists as a public method on flickrFeeder"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="k">typeof</span> <span class="nx">flickrFeeder</span><span class="p">.</span><span class="nx">getFlickrJSON</span><span class="p">).</span><span class="nx">to</span><span class="p">.</span><span class="nx">eql</span><span class="p">(</span><span class="s1">'function'</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// verify that the getFlickrJSON method calls the correct URL</span>
<span class="nx">it</span><span class="p">(</span><span class="s2">"makes the correct http call to Flickr's API based on the parameters it's passed"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// use nock</span>
<span class="nx">nock</span><span class="p">(</span><span class="s1">'http://api.flickr.com'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'/services/feeds/photos_public.gne?format=json&#038;id=someFlickrID'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">reply</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="p">{</span><span class="s1">'some_key'</span><span class="p">:</span><span class="s1">'some_value'</span><span class="p">});</span>
<span class="nx">flickrFeeder</span><span class="p">.</span><span class="nx">getFlickrJSON</span><span class="p">({</span><span class="na">id</span><span class="p">:</span> <span class="s1">'someFlickrID'</span><span class="p">},</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">data</span><span class="p">).</span><span class="nx">to</span><span class="p">.</span><span class="nx">eql</span><span class="p">({</span><span class="s1">'some_key'</span><span class="p">:</span><span class="s1">'some_value'</span><span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="run-your-test">Run your test</h2>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2</pre></td><td class="code"><pre>cd your_flickr_feeder_directory
mocha
</pre></td></tr></tbody></table>
</div>
<p>See the full example code on Github <a href="http://github.com/mdb/flickr-feeder">here</a>.</p>
Icon Sprites with Compass/blog/icon-sprites-with-compass/2012-10-09T20:00:00-04:002012-10-09T20:00:00-04:00Mike Ball<p>Manually creating sprite images is time-consuming and subject to human error. The corresponding CSS is often verbose and largely repetitive.</p>
<p><b>Solution</b>: Leverage <a href="http://compass-style.org/">Compass</a>'s spriting and looping features.</p>
<p>Example: You have an icon set of 20 10x10px individual...</p><p>Manually creating sprite images is time-consuming and subject to human error. The corresponding CSS is often verbose and largely repetitive.</p>
<p><b>Solution</b>: Leverage <a href="http://compass-style.org/">Compass</a>'s spriting and looping features.</p>
<p>Example: You have an icon set of 20 10x10px individual .png files. Their file names follow the convention icon_1.png, icon_2.png, and icon_3.png through icon_20.png.</p>
<h2 id="step-1:-install-compass">Step 1: Install compass</h2>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>gem install compass
</pre></td></tr></tbody></table>
</div>
<h2 id="step-2:-create-a-compass-project">Step 2: Create a compass project</h2>
<div class="highlight plaintext"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre>compass create your_project_name
</pre></td></tr></tbody></table>
</div>
<h2 id="step-3:-configure-compass-spriting">Step 3: Configure compass spriting</h2>
<p>In the case of the aforementioned, example, you’ll want to configure compass to build a sprite from the directory containing the icons. In this case, I created an <code>images/sprites/icons</code> directory.</p>
<p>Place the icons in an <code>images/sprites/icons/</code> directory within your compass project.</p>
<p>Create and/or edit your compass project’s <code>scss/_sprite.scss</code> file to build sprites from the newly created <code>images/sprites/icons/</code> directory. Add the following:</p>
<div class="highlight scss"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3</pre></td><td class="code"><pre><span class="nv">$bit24-layout</span><span class="p">:</span> <span class="n">smart</span>
<span class="nv">$bit24-sprite-dimensions</span><span class="o">:</span> <span class="bp">true</span>
<span class="o">@</span><span class="n">import</span> <span class="s2">"images/sprites/icons/*.png"</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="step-4:-write-scss">Step 4: Write SCSS</h2>
<p>The SCSS:</p>
<div class="highlight scss"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13</pre></td><td class="code"><pre><span class="k">@import</span> <span class="s2">"_sprite"</span><span class="p">;</span>
<span class="nc">.icon</span> <span class="p">{</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">height</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="nl">text-indent</span><span class="p">:</span> <span class="m">-5000px</span><span class="p">;</span>
<span class="k">@for</span> <span class="nv">$i</span> <span class="ow">from</span> <span class="m">0</span> <span class="ow">through</span> <span class="m">20</span> <span class="p">{</span>
<span class="k">&</span><span class="nc">.icon-</span><span class="si">#{</span><span class="nv">$i</span><span class="si">}</span> <span class="p">{</span>
<span class="k">@include</span> <span class="nd">icons-sprite</span><span class="p">(</span><span class="n">icon_</span><span class="si">#{</span><span class="nv">$i</span><span class="si">}</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table>
</div>
<p>The resultant compiled CSS:</p>
<div class="highlight css"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73</pre></td><td class="code"><pre><span class="o">//</span> <span class="nt">line</span> <span class="nt">418</span><span class="o">,</span> <span class="nt">sprites</span><span class="o">/</span><span class="nt">icons</span><span class="o">/*</span><span class="nc">.png</span>
<span class="nc">.icons-sprite</span><span class="o">,</span> <span class="nc">.icon.icon-0</span><span class="o">,</span> <span class="nc">.icon.icon-1</span><span class="o">,</span> <span class="nc">.icon.icon-2</span><span class="o">,</span> <span class="nc">.icon.icon-3</span><span class="o">,</span> <span class="nc">.icon.icon-4</span><span class="o">,</span> <span class="nc">.icon.icon-5</span><span class="o">,</span> <span class="nc">.icon.icon-6</span><span class="o">,</span> <span class="nc">.icon.icon-7</span><span class="o">,</span> <span class="nc">.icon.icon-8</span><span class="o">,</span> <span class="nc">.icon.icon-9</span><span class="o">,</span> <span class="nc">.icon.icon-10</span><span class="o">,</span> <span class="nc">.icon.icon-11</span><span class="o">,</span> <span class="nc">.icon.icon-12</span><span class="o">,</span> <span class="nc">.icon.icon-13</span><span class="o">,</span> <span class="nc">.icon.icon-14</span><span class="o">,</span> <span class="nc">.icon.icon-15</span><span class="o">,</span> <span class="nc">.icon.icon-16</span><span class="o">,</span> <span class="nc">.icon.icon-17</span><span class="o">,</span> <span class="nc">.icon.icon-18</span><span class="o">,</span> <span class="nc">.icon.icon-19</span><span class="o">,</span> <span class="nc">.icon.icon-20</span> <span class="p">{</span>
<span class="nl">background</span><span class="p">:</span> <span class="sx">url('/images/sprites/icons-sda260d590b.png')</span> <span class="nb">no-repeat</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon</span> <span class="p">{</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">height</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="nl">text-indent</span><span class="p">:</span> <span class="m">-5000px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-0</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-260px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-1</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-312px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-2</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-416px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-3</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-468px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-4</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-572px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-5</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-728px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-6</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-676px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-7</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-624px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-8</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1144px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-9</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1300px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-10</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-988px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-11</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1248px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-12</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1196px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-13</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1664px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-14</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1456px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-15</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-16</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-780px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-17</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-364px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-18</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1508px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-19</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1560px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.icon.icon-20</span> <span class="p">{</span>
<span class="nl">background-position</span><span class="p">:</span> <span class="m">0</span> <span class="m">-1716px</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table>
</div>
<h2 id="step-5:-write-html">Step 5: Write HTML</h2>
<p>Now, the icon sprite styles can be leveraged in HTML like so:</p>
<div class="highlight html"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="nt"><b</span> <span class="na">class=</span><span class="s">"icon icon-1"</span><span class="nt">></span>Some icon<span class="nt"></b></span>
</pre></td></tr></tbody></table>
</div>