tpitalehttp://tpitale.com/2020-11-29T15:45:50+00:00Tony Pitaletpitale@gmail.comhttp://tpitale.com/aliasing-fields-on-ecto-associations-in-absinthe-for-graphqlAliasing Fields on Ecto Associations in Absinthe for GraphQL2019-02-01T17:10:24+00:00
<p>How do we maintain the consistency of our GraphQL APIs over time, while the underlying data structures change? This is a common question we face building software on the web. We have mobile and react applications, or third party consumers, that rely upon the structure and data provided our APIs. The truth of our data storage may be much different than the API presents. Let’s adapt a few common scenarios using features available from Absinthe in Elixir/Phoenix/Ecto.</p>
<p>One technique for handling this is to continue presenting the original field, but using data from another source. I would refer to this as “aliasing” or mapping one field’s data into another. The fundamental idea in Absinthe is that the name of the field presented in the API is not required to be the key for the source of data we use.</p>
<p>I’ll cover two scenarios of database refactorings I went through recently, while keeping the API backward compatible:</p>
<ol>
<li>Renaming a column</li>
<li>Moving a column into another table</li>
</ol>
<h2 id="renaming-a-column">Renaming a Column</h2>
<p>This is a simple example to get us warmed up, but we’ll use this technique in combination with another in our next example.</p>
<p>We’ll start with a table and a column (example from Postgres):</p>
<pre><code class="language-sql">
CREATE TABLE firmware_versions (
id SERIAL,
semantic_version_number text
);
CREATE TABLE devices (
id SERIAL,
firmware_version_id integer REFERENCES firmware_versions(id),
desired_firmware_version_id integer REFERENCES firmware_versions(id)
);
</code></pre>
<p>And the Absinthe object type would look something like (I’ve omitted data loaders and resolvers):</p>
<pre><code class="language-elixir">
@desc "Firmware version"
object :firmware_version do
@desc "Database ID of the firmware version"
field(:id, :integer)
@desc "Firmware version expressed as a semantic version number"
field(:semantic_version_number, :string)
end
@desc "A network-connected device"
object :device do
…
@desc "Current firmware version of the device"
field(:firmware_version, :firmware_version, resolve: dataloader(Device))
@desc “Firmware version we are attempting to update to"
field(:desired_firmware_version, :firmware_version, resolve: dataloader(Device))
end
</code></pre>
<h3 id="changing-the-object_type">Changing the object_type</h3>
<p>What happens when we change the column name from <code class="language-plaintext highlighter-rouge">desired_firmware_version_id</code> to <code class="language-plaintext highlighter-rouge">requested_firmware_version_id</code>? This is an admittedly contrived example, meant to highlight a point. Aside from the normal database migration to alter the table, we need only make a straightforward change in our GraphQL type for the device to maintain the interface for our users.</p>
<pre><code class="language-elixir">
field(:requested_firmware_version, :firmware_version, resolve: dataloader(Device), name: "desired_firmware_version")
</code></pre>
<p>Two changes are required. To start, we update the first argument in <code class="language-plaintext highlighter-rouge">field</code> to match the new name of the column (in this case, an association). Then, we add the option for <code class="language-plaintext highlighter-rouge">name</code> and pass in a string (binary) to match the previous column’s name in the API.</p>
<p>If you are planning to deprecate the old field name over time, add the <code class="language-plaintext highlighter-rouge">deprecate</code> option available on <code class="language-plaintext highlighter-rouge">field</code> and pass it a string with the reason for the deprecation. Absinthe will handle the rest.</p>
<h2 id="moving-to-a-new-table">Moving to a New Table</h2>
<p>What happens if we make a more drastic change to the structure of our data by adding a relation to the mix? Whether this is a new table or an existing table that better suits our data, our structures may change in more substantial ways. In this example, we’ll start from the same initial structure of the <code class="language-plaintext highlighter-rouge">devices</code> table from above. Instead, we’ll add a new table rather than renaming our column.</p>
<pre><code class="language-sql">
CREATE TABLE firmware_update_requests (
id SERIAL,
pending boolean DEFAULT true,
device_id integer REFERENCES devices(id),
firmware_version_id integer REFERENCES firmware_versions(id)
);
CREATE UNIQUE INDEX pending_request_index ON firmware_update_requests(device_id, pending);
</code></pre>
<p>This change will necessitate more changes than our previous example, but fundamentally, the technique is the same.</p>
<h3 id="add-an-ecto-virtual-field-and-association">Add an Ecto Virtual Field and Association</h3>
<p>To make it work, we just need to get the data from this new table’s <code class="language-plaintext highlighter-rouge">firmware_version_id</code> into a place on the device record where our alias Absinthe <code class="language-plaintext highlighter-rouge">field</code> can get to it.</p>
<p>We’ll use a combination of two Ecto features: we’ll make space for the <code class="language-plaintext highlighter-rouge">firmware_version_id</code> using a <code class="language-plaintext highlighter-rouge">virtual</code> field in the schema and add our association without defining a field (since we already did it in the virtual field).</p>
<pre><code class="language-elixir">
# in MyApp.Device module
schema :devices do
# snip …
field(:requested_firmware_version_id, :integer, virtual: true)
belongs_to(:requested_firmware_version, MyApp.FirmwareVersion, define_field: false)
end
</code></pre>
<p>This creates a place for us to join data into the device, and allows us to look it up as an association.</p>
<h3 id="update-the-absinthe-resolver">Update the Absinthe Resolver</h3>
<p>Now, we can change our query to load this data. For Absinthe, we do this in the resolver:</p>
<pre><code class="language-elixir">
defp with_pending_request(query \\ MyApp.Device) do
from(
d in query,
left_join: r in MyApp.FirmwareUpdateRequest,
on: d.id == r.device_id,
on: r.pending == true,
select: %{d | requested_firmware_version_id: r.firmware_version_id}
)
end
</code></pre>
<p>Let’s break this down. We’ll start with the <code class="language-plaintext highlighter-rouge">query</code> argument. This is an <code class="language-plaintext highlighter-rouge">Ecto.Query</code> for records from <code class="language-plaintext highlighter-rouge">MyApp.Device</code>. Next, we <code class="language-plaintext highlighter-rouge">left_join</code> our <code class="language-plaintext highlighter-rouge">firmware_update_request</code> record on the matching <code class="language-plaintext highlighter-rouge">device_id</code> and we restrict it to <code class="language-plaintext highlighter-rouge">pending</code> requests so that we only join, at most, a single record for each device.</p>
<p>Lastly, my favorite bit, is the <code class="language-plaintext highlighter-rouge">select</code>. It merges the <code class="language-plaintext highlighter-rouge">firmware_version_id</code> into the new virtual field we added to the <code class="language-plaintext highlighter-rouge">MyApp.Device</code> schema. And with that data in place, we can use our previous change to the Absinthe object type <code class="language-plaintext highlighter-rouge">field</code> for the <code class="language-plaintext highlighter-rouge">name</code> option and everything will work.</p>
<h2 id="all-together-now">All Together Now</h2>
<p>With all of these changes: using the resolver to load the firmware version from the <code class="language-plaintext highlighter-rouge">firmware_update_request</code> association into an Ecto virtual field and aliasing the field in Absinthe we have successfully mapped a column from an associated table into place on the original field in our GraphQL API. Thus, we have retained the external structure and data of our API while modifying the underlying data store.</p>
<p>While this is how I solved this particular problem it is, of course, not the only way to solve this problem. Other solutions might involve resolver callbacks (coming in the next version of Absinthe) or simply a custom resolver that returns a properly formed map. Whatever path you choose, I hope this provides you some new info on both Absinthe and Ecto and their function.</p>
<p>This post was original published on the <a href="https://tech.offgrid-electric.com/aliasing-fields-on-ecto-associations-in-absinthe-for-graphql-abcaad92b9b4">Power the People</a> blog.</p>
http://tpitale.com/activesupport-notifications-to-influxdb-with-tremoloActiveSupport::Notifications to InfluxDB, with Tremolo2015-10-20T01:56:19+00:00
<p>If you’ve ever used stat-tracking software like StatsD and Graphite, you may have already heard of <a href="https://influxdb.com/">InfluxDB</a>. For those who have not, it is a time-series database, useful for tracking data to be aggregated over a period of time.</p>
<p>I’ve been using InfluxDB for some time to track a variety of data in my Rails applications. InfluxDB caught my eye because it not only tracked data, but <em>metadata</em>, too. Metadata is anything you might want to use to “scope” your query (that is not time-related). For me, this is the ID of a client in a multi-tenant application for background processing work.</p>
<p>To make it easier to use in my Rails applications I wrote a little library called <a href="https://github.com/tpitale/tremolo">Tremolo</a> which has similar functionality to the Ruby StatsD library.</p>
<p>By way of introduction, I would like to show how I leveraged ActiveSupport::Notifications to send my data to InfluxDB using Tremolo. I’ll do this in three parts.</p>
<ul>
<li>Basics of Tremolo</li>
<li>Setting up ActiveSupport::Notifications</li>
<li>Tie the two together</li>
</ul>
<p>As a bonus, I’ll also give you a peek at what the data looks like graphed on my dashboard, which uses <a href="http://grafana.org/">Grafana</a>.</p>
<h2 id="intro-to-tremolo">Intro to Tremolo</h2>
<p>Tremolo is a library to send data in InfluxDB’s wireline protocol over UDP, built using Celluloid. Using it is very straightforward. First, we make ourselves a tracker, pointing it at our InfluxDB instance.</p>
<pre><code class="language-ruby">
Tremolo.supervised_tracker(:tracker, '0.0.0.0', 4444)
</code></pre>
<p>To send InfluxDB some stats, we use our new tracker.</p>
<pre><code class="language-ruby">
tracker = Tremolo.fetch(:tracker)
# increment a stat by 1
tracker.increment('count.series-name')
</code></pre>
<p>Tremolo also supports <code class="language-plaintext highlighter-rouge">decrement</code>, <code class="language-plaintext highlighter-rouge">timing</code> with an integer for milliseconds, and most importantly for my examples: <code class="language-plaintext highlighter-rouge">time</code> with a block.</p>
<pre><code class="language-ruby">
tracker = Tremolo.fetch(:tracker)
# increment a stat by 1
tracker.time('timing.series-name') do
# do something that takes time, like making an API call
Net::HTTP.get(URI('http://example.com/api/v1/things'))
end
</code></pre>
<p>Now that we’ve got the basics of Tremolo, let’s take a look at what Rails has to offer to make our tracking a bit easier.</p>
<h2 id="activesupportnotifications">ActiveSupport::Notifications</h2>
<p>Rails has some very simple, but handy, functionality to allow us to <code class="language-plaintext highlighter-rouge">instrument</code> our code. Elsewhere, we can <code class="language-plaintext highlighter-rouge">subscribe</code> to get notifications of when our instrumented code is run. Rails uses this to instrument requests and calls to the database, while also enabling third-party libraries to hook into the system at both ends.</p>
<p><code class="language-plaintext highlighter-rouge">ActiveSupport::Notifications</code> has some key features that make it especially well-suited to tie into a stats-tracking library like Tremolo.</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">instrument</code> accepts a payload of extra information</li>
<li><code class="language-plaintext highlighter-rouge">instrument</code> can handle timing a block of code</li>
<li>We can <code class="language-plaintext highlighter-rouge">subscribe</code> to a simple regex pattern to capture similar notifications</li>
</ol>
<p>Let’s take a look at a simple example, making an API call:
<strong>Note:</strong> I’m going to alias ActiveSupport::Notifications as ASN for brevity.</p>
<pre><code class="language-ruby">
ASN.instrument('timing.external_requests', customer_id: customer.id) do
ApiClient.get("/api/customers/#{customer_id}")
end
</code></pre>
<p>And to subscribe to this:</p>
<pre><code class="language-ruby">
ASN.subscribe('timing.external_requests') do |*args|
event = ASN::Event.new(*args)
# event contains lots of data, including:
event.name # the full name of the instrument, 'timing.external_requests'
event.duration # timing for the block
event.payload # extra hash we passed to `instrument`
end
</code></pre>
<p>Straightforward, and powerful. Let’s make use of this to build some flexible tracking in our application.</p>
<h2 id="and-put-em-together">And Put ‘em Together</h2>
<h3 id="subscriptions">Subscriptions</h3>
<p>Let’s start by setting up some broad subscriptions using regex patterns:</p>
<pre><code class="language-ruby">
ASN.subscribe(/\Atiming/) do |*args|
# fill in here
end
</code></pre>
<p>Now, let’s fill in some tracking with Tremolo:</p>
<pre><code class="language-ruby">
ASN.subscribe(/\Atiming/) do |*args|
event = ASN::Event.new(*args)
tracker = Tremolo.fetch(:tracker)
tracker.timing(event.name, event.duration, event.payload)
end
</code></pre>
<p>Inside our subscribe block, we make an event from the <code class="language-plaintext highlighter-rouge">args</code>, then get our <code class="language-plaintext highlighter-rouge">tracker</code>. Once we have those, we track the <code class="language-plaintext highlighter-rouge">timing</code> from our event, using the full name and duration, passing the payload as metadata last.</p>
<p>I also like to set up a <code class="language-plaintext highlighter-rouge">count</code> subscription, like so:</p>
<pre><code class="language-ruby">
ASN.subscribe(/\Atiming/) do |*args|
event = ASN::Event.new(*args)
tracker = Tremolo.fetch(:tracker)
data = event.payload
value = data.delete(:value) || 1
tracker.write_point(event.name, {value: value}, data)
end
</code></pre>
<p>There’s a little more to this one than our timing. First difference is we want to get a value for the count. If none is provided use 1 as a default. We use <code class="language-plaintext highlighter-rouge">delete</code> because we don’t want this value tracked into the metadata in InfluxDB.</p>
<p>Next we use <code class="language-plaintext highlighter-rouge">write_point</code> on our <code class="language-plaintext highlighter-rouge">tracker</code> because we’ve got our own <code class="language-plaintext highlighter-rouge">value</code> to track.</p>
<h3 id="instrument-our-code">Instrument Our Code</h3>
<p>Now that we have our subscriptions for both <code class="language-plaintext highlighter-rouge">timing</code> and <code class="language-plaintext highlighter-rouge">count</code>, we can start to <code class="language-plaintext highlighter-rouge">instrument</code> our code throughout. What’s great is we just use the code from earlier:</p>
<pre><code class="language-ruby">
ASN.instrument('timing.external_requests', customer_id: customer.id) do
ApiClient.get("/api/customers/#{customer_id}")
end
</code></pre>
<p>To track to a count series:</p>
<pre><code class="language-ruby">
ASN.instrument('count.external_requests', {customer_id: customer.id})
</code></pre>
<h2 id="what-to-track">What to Track</h2>
<p>Here are some examples of the things I instrument in my own applications.</p>
<ul>
<li>timing of external API calls</li>
<li>timing of background work</li>
<li>counts of calls to deprecated methods</li>
<li>business statistics like user logins/registrations</li>
<li>cache misses</li>
</ul>
<p>Though, if it takes time in code, or is of any importance to your business or the functioning of other code, track it!</p>
<h2 id="bonus-round-grafana">Bonus Round: Grafana</h2>
<p>InfluxDB is extremely easy to get installed and running. If you’re on a Mac <code class="language-plaintext highlighter-rouge">brew install</code> will get you running, and a simple configuration change will set up a UDP listener.</p>
<pre><code class="language-ini">
[udp]
enabled = true # make sure this is true
bind-address = ":4444" # pick your own port here, match to Tremolo's config
database = "your-database-here"
batch-size = 1000
batch-timeout = "1s"
</code></pre>
<p>InfluxDB does not, however, have a UI for anything other than querying databases/series with a SQL-esque query language. This is where Grafana comes in.</p>
<p><img src="/images/posts/grafana.png" alt="Grafana Dashboard" title="Grafana Dashboard" /></p>
<p>Grafana has <a href="http://docs.grafana.org/installation/">installation</a> packages for most linux platforms, but for Mac OS X you’ll need to <a href="http://docs.grafana.org/installation/mac/">install from source</a>. This was quick and easy on my system, and required no further (backend) configuration to get running. Just set your data source (influxdb) and go.</p>
<h2 id="wrap-it-up">Wrap it Up</h2>
<p>I’ve found ActiveSupport::Notifications and Tremolo to be a fantastic combo. I hope this article has made it easier for you to get into tracking important data in your own applications.</p>
<p>Read more here about <a href="http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html">ActiveSupport::Notifications</a> and more about <a href="https://github.com/tpitale/tremolo">Tremolo</a>.</p>
<p>If you’re wondering about the name Tremolo, I have a whole series of libraries for integrating stats in a variety of ways, all named for different musical terms. <a href="https://github.com/tpitale/legato">Legato</a> pulls data from Google Analytics, and <a href="https://github.com/tpitale/staccato">Staccato</a> sends data to GA using the official tracking API. Tremolo is just the latest in this collection. Check ‘em all out!</p>
http://tpitale.com/mailroom-now-with-sidekiq-and-que-supportMailRoom, Now With Sidekiq and Que Support2015-09-09T01:52:09+00:00
<p>Before today, MailRoom primarily delivered mail messages from IMAP via a POST request to an API you configure. Now, thanks to the work of <a href="https://github.com/douwem">Douwe Maan</a> (for a <a href="https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1173">GitLab feature</a>), there are delivery methods for enqueuing work into redis or postgresql to be handled by your own Sidekiq or Que workers!</p>
<p>Along with these brand new strategies, we have lots of bug fixes and new features in MailRoom:</p>
<ul>
<li>Mailboxes can be <a href="https://github.com/tpitale/mail_room#configuration">configured to DELETE</a> a message after receiving it.</li>
<li>The mailbox will be checked immediately upon startup for any unread messages.</li>
<li>All IOErrors will be handled, not just EOFErrors.</li>
<li>Brand new configuration, due to expanded options for Sidekiq and Que delivery.</li>
</ul>
<p>Configuration new looks more like this:</p>
<pre><code class="language-ruby">
:mailboxes:
-
:email: "user1@gmail.com"
:password: "password"
:name: "inbox"
:search_command: 'NEW'
:delivery_method: que
:delivery_options:
:host: localhost
:port: 5432
:database: mail_room_development
:username: username
:password: password
:queue: default
:priority: 5
:job_class: EmailParseJob
</code></pre>
<p>The <code class="language-plaintext highlighter-rouge">delivery_options</code> section is new, and holds all options specific to the chosen delivery method. Check out the <a href="https://github.com/tpitale/mail_room/blob/master/README.md#sidekiq">README for Sidekiq</a> and <a href="https://github.com/tpitale/mail_room/blob/master/README.md#que">Que</a> to see all the new options.</p>
<p>All existing configuration will continue to work for the foreseeable future, so have no fear when upgrading to the latest version.</p>
<p>I hope this makes using MailRoom even easier. Please help me in thanking Douwe Maan for his excellent work on the Sidekiq delivery method! I’m very excited to see <a href="https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile#L272">GitLab using MailRoom</a>.</p>
<p>If you have any questions or requests, please <a href="https://github.com/tpitale/mail_room/issues">open an issue</a>, or chat with me <a href="https://twitter.com/tpitale">on Twitter</a>.</p>
http://tpitale.com/choosing-the-pathChoosing the Path2015-02-12T22:05:32+00:00
<p>There is rarely one right solution to a problem. As <a href="http://therealadam.com/2014/09/23/well-tuned-judgement/">Adam Keys describes in a recent post</a>, the path to improvement comes (at some point) to be our ability to identify the paths we might take, to understand the tradeoffs between each path, and to choose the “right” path given the constraints, context, and goals we’re working with.</p>
<p>I’m striving to learn some of the ways to identify which path is the right one. The aim is to have a rough idea which path suits best. Here are a few of the questions I might ask myself before choosing the next path I take.</p>
<h2 id="constraints-and-context">Constraints and Context</h2>
<p>What can be changed in your system and environment, what can’t or shouldn’t? How much time do you have? Who do you need to communicate with, either to ask for their help or to make them aware of the changes coming? Will customers be impacted? Will internal teams have to change the way they work, or be prepared to offer increased support communication?</p>
<h2 id="consistency">Consistency</h2>
<p>Provided by the environment, the team, and the company, in which we operate. Starting simply, stick with the <strong>language</strong>, <strong>tools</strong>, and other <strong>moving parts</strong>, that your environment already supports. Consistency is valuable. The right tool for the job may very well be the tool you already have in your hand.</p>
<p>The counter to this is also important. Consider whether or not what you’re trying to hammer is a nail, or if it is something entirely different. Maybe it is time to use a new tool.</p>
<h2 id="pain">Pain</h2>
<p>Identify points of friction. Which path will be challenging, and at what points? Will one path take longer than another and does that matter? Can some of the pain be deferred?</p>
<h2 id="change">Change</h2>
<p>How will your solution cope with change? How likely is change to occur? Be cautious of this particular question. The future is mostly unknowable. However, “change is the only constant” is more than just a platitude.</p>
<p>What is the collateral effect (or damage) of your change? What other pieces in the system will fail or require alteration? Can they be altered in advance?</p>
<h2 id="surprise">Surprise</h2>
<p>Will the path lead to a solution that is surprising? Or, will you have to do something surprising along the way? If someone (or you) comes along in a few days or weeks, what will happen?</p>
<h2 id="introspection">Introspection</h2>
<p>What insights might be useful to have now, or after your solution is added? What logging, stats, or other instrumentation should be put in place?</p>
<p>These are just some of the things I try to mull over in my head, or with folks on my team, when weighing different paths to a solution. Foster a culture where the choice of solutions is easy for anyone in the team to discuss.</p>
http://tpitale.com/2014-retrospective2014 Retrospective2015-01-02T19:59:17+00:00
<blockquote>
<p>The problem with the future is that it keeps turning into the present.
– Hobbes</p>
</blockquote>
<p>2014 was certainly a mixed bag. For now, I’d only like to add my brief perspective on what <a href="http://sixtwothree.org/posts/looking-back-at-2014">others</a> have <a href="https://adactio.com/journal/8079">covered</a> more <a href="https://twitter.com/importantshock/status/550406163226177536">eloquently</a>.</p>
<p>We lost (and watched as others lost) those dear to us. Robin Williams hit me especially hard. The loss of our colleagues and friends was heartbreaking, including James Golick and Ezra Zygmuntowicz very recently. Ezra was so influential to me (and many others) in my current career path.</p>
<p>Despite how it felt much of the time 2014 was actually very good to me. While none of what follows are strictly resolutions, they are things I feel are valuable to reflect upon and appreciate.</p>
<h2 id="accomplished">Accomplished</h2>
<h3 id="personal">Personal</h3>
<p>I’ve been on a diet and exercise regimen for a little over a month now. The plan I’m following is a selection from the Four Hour Body. While I’m not seeing enormous weight loss, I am seeing a steady downward trend. It has also been a very manageable, consistent plan.</p>
<p>I’ve also been able to save much more this year than ever before. This has been almost entirely through reduced spending. I cook at home much more (see diet, too) and I have not made any large technology purchases this year. No new computer, no new camera or lenses. My spending was down 20% this year, and almost all of that went to savings.</p>
<h3 id="professional">Professional</h3>
<ul>
<li>Spoke locally at <a href="http://refresh-dc.org">Refresh DC</a> on using <a href="http://jekyllrb.com/">Jekyll</a> for prototyping</li>
<li><a href="http://growingdevs.com">GrowingDevs</a> continues thanks to some great articles written this year</li>
<li>Maintained average of one contribution a day in my <a href="https://github.com/tpitale">public graph</a></li>
<li>Started maintaining a series of code “lab” journals</li>
<li>Submitted to conferences (<a href="http://tropicalrb.com">Tropical Ruby</a> and <a href="http://www.ancientcityruby.com/">Ancient City Ruby</a>)</li>
</ul>
<h3 id="hobbies">Hobbies</h3>
<p>I got to do something this year that I’ve wanted to try since I first got my license: drive on a track. This year I was fortunate enough to go with my father to drive a series of new Porsche cars on a track. While it was in a controlled arrangement (follow-the-leader behind a pro/teacher), we were able to perform a few laps at full throttle. I was later able to join the same group, this time with my friend Chris, at a different track.</p>
<p>I also got to work on my own car in preparation for maybe doing some track driving this year. New downpipes were installed (by a shop, I don’t have a lift, yet) along with a number of suspension and chasis components.</p>
<p>Even better, my father and I installed all new front brakes. It was a challenging but hugely rewarding process. I’m making plans to do more work myself going forward!</p>
<p><img src="/images/posts/stoptech-brakes-s4.jpg" alt="Stoptech Brakes on my S4" class="twoup" />
<img src="/images/posts/old-brakes-s4.jpg" alt="Me holding an old brake from my S4" class="twoup" /></p>
<p>In photography, I hit a landmark (for me at least) when I gave prints of my photography as gifts. To my sister, I gave a series of photos from the beach where she and her husband were engaged. To my mother, I gave a photo of the Cliffs of Moher in Ireland, where we recently vacationed as a family for her birthday.</p>
<p>Lastly, thanks to Veronica, and the collection of ukuleles in our apartment, I’ve learned to play a few songs on uke. I’ve even played for some friends and coworkers on hangouts!</p>
<h3 id="travel">Travel</h3>
<ul>
<li>Ireland with my family and Veronica</li>
<li>Portland/Seattle with Veronica</li>
<li>NYC 3x! (Velocity Conf, GoRuCo, teaching with CodeNow)</li>
</ul>
<p>Lastly, and best of all, <strong>Veronica and I are engaged!</strong></p>
<h2 id="misses">Misses</h2>
<h3 id="recaps">Recaps</h3>
<p>I started the year writing recaps for each month. That fell off quickly. I tried quarterly, but only did one since it was nearly the end of the year.</p>
<p>I transitioned very early on to using iDoneThis in combination with Day One. Both are designed around regular reminders, and allow me to write brief journals more frequently. Day One I use for photo memory on my iPhone. This combination has been much more effective for me to externalize some memories.</p>
<h3 id="reading-and-writing">Reading and Writing</h3>
<p>My reading and writing this year were generally inconsistent, but no worse than other years. I only finished reading a handful of fiction and a few tech books, with almost zero non-fiction in the mix.</p>
<h3 id="programming">Programming</h3>
<p>I learned a bit of Elixir and Rust, but did not build anything substantial this year with either. Nor did I create any new OSS projects. And my biggest programming disappointment was not launching an iOS app I’ve been working on. However, I have reasons for why it was not launched.</p>
<h2 id="plans-for-2015">Plans for 2015</h2>
<p>I have a big list planned for 2015, but mostly I’ll be focusing on three areas:</p>
<ol>
<li>My relationships with family and friends</li>
<li>Reading (already planned on Goodreads!)</li>
<li>Non-programming hobbies (e.g., travel, music, and working on/driving my car)</li>
</ol>
<h2 id="altogether-now">Altogether Now</h2>
<p>What a year it has been! 2014 was wonderful and difficult. I am thankful for all the family and friends that helped make it possible, and who helped me get through those days that weren’t as wonderful. I can’t be anything but excited for 2015!</p>
http://tpitale.com/loading-animations-in-ember-js-with-svg-and-cssLoading Animations in Ember.js with SVG and CSS2014-10-03T16:11:35+00:00
<p>From a usability perspective, it’s rare for “put a spinner on it” to be the right option. But, it can be a useful technique in some instances.</p>
<p>One such example is communicating network activity in an Ember.js application to users. Ember data uses jQuery’s AJAX functionality, which has some really wonderful hooks into the operations it performs.</p>
<p>There are two types of triggers: local and global. For my purposes, I used the global triggers on <code class="language-plaintext highlighter-rouge">ajaxSend</code> and <code class="language-plaintext highlighter-rouge">ajaxComplete</code>. jQuery’s recommendation is to bind these to $(document). You can read more on <a href="http://api.jquery.com/Ajax_Events/">local triggers in their docs</a>.</p>
<pre><code class="language-javascript">
$(document).bind("ajaxSend", show_loader_function);
$(document).bind("ajaxComplete", hide_loader_function);
</code></pre>
<p>I use these triggers to show/hide (or fadeIn/fadeOut) an SVG loading “spinner”, placed discretely in the header.</p>
<p>The <a href="http://codepen.io/aurer/pen/jEGbA/?editors=110">original source for that SVG is here</a>. Unfortunately, I found that there was something delaying the start of the animation embedded in the SVG. So, I turned to CSS3 (with SCSS, naturally) to generate a smoother animation. Compass has a simple mixin use creating a named keyframe and including it in the selector.</p>
<p>The jQuery AJAX triggers, SVG, and animation SCSS ended up looking like this when complete:</p>
<h3 id="triggers">Triggers</h3>
<pre><code class="language-javascript">APP.show_loader = function() {
$('.header .loader').fadeIn(50);
}
APP.hide_loader = function() {
$('.header .loader').fadeOut(100);
}
$(document).bind("ajaxSend", APP.show_loader).bind("ajaxComplete", APP.hide_loader);
</code></pre>
<h3 id="svg">SVG</h3>
<pre><code class="language-markup">
<div class="loader" title="loading">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="30px" height="30px" viewBox="0 0 50 50" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path fill="#000" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"></path>
</svg>
</div>
</code></pre>
<h3 id="css-animation">CSS Animation</h3>
<pre><code class="language-css">@include keyframes(spin) { // uses compass for keyframe mixin
100% { transform: rotate(360deg); }
}
.loader {
svg {
display: inline-block;
@include animation(spin 0.6s infinite);
@include animation-timing-function(linear);
path {
fill: #FFFFFF;
}
}
}
</code></pre>
<p>This combination of jQuery’s global AJAX triggers and SVG+CSS animations is flexible. It is easy to adjust the size of the SVG, and the rotation speed and color of the animation with CSS. It’s also more performant than the common animated gif spinner (fewer requests, smaller size). A better way to “put a spinner on it.”</p>
http://tpitale.com/april-may-june-recapApril/May/June Recap2014-07-02T14:36:42+00:00
<p>Another quarter of the year has gone by now! So, I decided to switch to doing quarterly recaps. It feels a little too long between reviews, but better than monthly.</p>
<h2 id="coding--oss-contributions">Coding & OSS Contributions</h2>
<p>I’ve contributed in small ways to a wide variety of new projects this month. Very exciting stuff for me. Most exciting was adding some code written in Go to the influxdb project.</p>
<p>At the same time, I continue to work on Legato, and had even more time to work on my side projects. Honestly, I mostly need to spend more time explaining and promoting their usage.</p>
<p>I think I need to start a project in Elixir or Rust soon. Given how much I enjoyed the work I was doing on Wisp (protocols) and contributing in Go, I think I can find something fun to do in either of those languages.</p>
<p>Lastly, I taught a round of classes with CodeNow. It was a stark reminder of how abstract programming concepts are to those with no experience, and how difficult it can be to explain some of those abstract ideas. A challenging, and rewarding, experience for sure!</p>
<h2 id="writing">Writing</h2>
<p>Not much writing. I’ve been trying to make GrowingDevs a safe place to write and share. A platform for some folks to stand on who may not have a platform of their own. I’m disappointed in myself because, so far, all of the authors have been from the most empowered groups of developers. I’m trying to give time and space to the people I’ve asked to share something on GrowingDevs. I know how precious their time is, and how scary and even dangerous it can be to speak publicly.</p>
<h2 id="photography">Photography</h2>
<p>Part of the reason for the 3-month recap post is because of a vacation I took to Ireland. I am very happy with the photos that came out of that trip; they were amazing because the trip was so amazing. Now, I’m struggling with the post-processing work. I think this means I need to get better at not expecting to post-process images much. And, I probably need to find a baseline automation I can add in Lightroom to take my photos from RAW and get them to a simple, shareable state.</p>
<h2 id="travel">Travel</h2>
<p>Lots of travel! Between the trip to Ireland, many trips to NJ and NYC for CodeNow, and a trip a bit west to St. Louis. The past 3 months have been quite an adventure. Very much worth it, but I’m also enjoying being back home in DC.</p>
<h2 id="until-next-time">Until Next Time</h2>
<p>Cheers!</p>
http://tpitale.com/march-recapMarch 2014, Recap2014-04-04T04:53:25+00:00
<p>Three months, one quarter of the year, has gone by already! I’m only a few days late with this post.</p>
<h2 id="coding--oss-contributions">Coding & OSS Contributions</h2>
<p>I’ve done more work on my side projects this month, and that makes me happy. After things didn’t really pan out with one project in February, it felt really good to dig back into others.</p>
<p>Nothing new on the OSS front. Continuing to try to foster a good community around Legato. Staccato has gotten a boost since the Universal Analytics features of Google Analytics have been released out of beta. Planning to write something about that, maybe on Growing Devs.</p>
<p>Continued to work on Wisp. It has me thinking in new ways and learning a bit more about Python in the process.</p>
<h2 id="writing">Writing</h2>
<p>I didn’t really write much in March. I was trying to reach out to as many people as possible about contributing to Growing Devs. Nobody came through in the end, so I chose to not write anything in March, rather than make Growing Devs just a second blog for my writing.</p>
<p>I can’t say I’m not disappointed, but I’ll keep trying to spread the word since everyone seems very positive about the general idea. I understand it’s hard to find the motivation to write. I’m hoping my offer to help people edit and grow in their writing will attract some folks.</p>
<p>I did make one addition to Growing Devs, and that was a Code of Conduct. With the help of some fantastic people on twitter, I was able to find a solid Code. I’m hoping this will make everyone feel safer and make the future community much more welcoming.</p>
<h2 id="photography">Photography</h2>
<p>Nothing new on this front. March was very strange, weather-wise. April has been much better, and I’m hoping to get out early in the morning for the Cherry Blossom Festival. Hopefully I’ll more to share on this in my next entry.</p>
<h2 id="until-next-time">Until Next Time</h2>
<p>Cheers!</p>
http://tpitale.com/february-recapFebruary 2014, Recap2014-03-13T05:25:06+00:00
<p>This post is a little bit late since we’re half-way into March, but I felt it was important to share how my February went. So, here goes.</p>
<h2 id="coding--oss-contributions">Coding & OSS Contributions</h2>
<p>I’ve been pretty good with sticking to my plan of having an average of one public contribution a day. I’ve also kept up my private, side-project contributions as well.</p>
<p>I was also a little distracted by an experiment I tried this month. While it didn’t quite work out (yet) it was a fantastic experience. I learned much. Though, that endeavor took some time from my potential OSS work.</p>
<h2 id="monthly-reviews">Monthly Reviews</h2>
<p>At first, I was itching to write my first review. As my contributions have slowed, so too has my desire to write reviews every other week. Monthly will do nicely. Now to just write them on time. This one is a little late.</p>
<h2 id="writing">Writing</h2>
<p>In general, I’ve continued to write. I wrote a post on <a href="http://growingdevs.com">growingdevs.com</a> for February. I’m still looking for contributors for March, so please get in touch!</p>
<h2 id="photography">Photography</h2>
<p>My photography has been lacking. However, my motivation is renewed having received my new Mac Pro. I’ve worked through a large backlog of unprocessed photos. I’m now ready to take even more. I’m still working out exactly how I want to handle publishing, and where.</p>
<h2 id="helping-others">Helping Others</h2>
<p>I’ve been sticking close to home on this one so not much to report. I attempted to reach out to the folks at CodeNow but the timing just didn’t work out for me to help with the next round of their classes in NYC. Next time, I hope to be able assist.</p>
<h2 id="until-next-time">Until Next Time</h2>
<p>I’m pretty pleased with how this is going. Even while writing this, I’m inspired to get back into my photography, and I’m really excited to take the next big steps on some of my side projects. March should be exciting! Cheers!</p>
http://tpitale.com/january-recapJanuary 2014, Recap2014-02-01T13:36:27+00:00
<p>Like many people I took advantage of the New Year as a time to reflect. This year I took special care in my reflection because January marked my 30th birthday.</p>
<p>With refreshed goals in mind I set out to create the systems and, hopefully, habits that will help me to live each goal going forward.</p>
<p>The best way that I’ve found to grow is through deliberate practice. Review and feedback are critical components of this. What follows is my self-review so far for January 2014.</p>
<h3 id="coding">Coding</h3>
<p>My first goal, to code more on OSS, lead me to attempt to build the habit of contributing daily. For the entire month I have done just that, by the standards of the Github contribution graph. I feel pretty amazing about this achievement.</p>
<p>I’ll soon be writing a post on the numerous benefits I’ve found when contributing daily and how I managed to keep it up for even one month.</p>
<h3 id="writing">Writing</h3>
<p>I’ve always wanted to write more. And while I wasn’t as bold to say I would write every day, I did decide I wanted to make it easier to write. My system was to reduce my backlog of blog posts I’ve been collecting in Evernote. I plan to finish these regularly.</p>
<p>Also, I started GrowingDevs in 2013 with the help of a few friends. This month, I posted our fourth article. I’m hoping that the site as a whole will keep to a monthly posting schedule. So, if you have something you would like to share, please send me a message, or just a PR! I am excited to help anyone interested in getting started.</p>
<p>Lastly, my ideas for a book also continue to evolve, but that is not yet an active goal. The system is little work at a time to continue to develop these ideas, ask friends, and fill out outlines and research notes.</p>
<h3 id="helping-others">Helping Others</h3>
<p>I haven’t done as much with this. But I believe I’ve developed some ideas for a system that I want. So far I have reached out to the team at <a href="http://codenow.org">CodeNow.org</a>. I also plan to:</p>
<ol>
<li>Offer to help with new developers starting on their own OSS projects</li>
<li>Publicize my availability as a mentor/speaking coach</li>
</ol>
<h3 id="photography">Photography</h3>
<p>My plan was to do at least one photowalk a month. While I did not do a photowalk in January, I did have a “product” photo shoot. I was able to help a friend take photos for a product review she’ll be posting on her own blog. I was able to get some practice in for my off-camera flash technique.</p>
<h3 id="finish-what-i-start">Finish What I Start</h3>
<p>One of my biggest challenges in 2013 was finishing what I started. There’s no real system for doing this, other than to get feedback and do regular reviews.</p>
<p>The first week of this month, I finally finished a project that has been hanging around for 6+ months.</p>
<p>My OSS has also been brought back into a state of repair after my month of daily OSS coding. While not exactly finishing what I start, it is taking responsibility for the open issues and pull requests that I’ve been neglecting.</p>
<p>This all feels great, and helps to motivate me to finish more of what I start. It also frees me up to start new projects, which I’m pretty jazzed about!</p>
<h3 id="monthly-reviews">Monthly Reviews</h3>
<p>To wrap up this review, I wanted to point out again that one of my systems is to write a review at least monthly. Here you have it. Check!</p>
<h3 id="changes-to-the-systems">Changes to the Systems</h3>
<p>After one month, I already have some ideas for changes I’d like to make in my systems going forward.</p>
<p>I’m hoping to switch to doing shorter, biweekly reviews. Close feedback and introspection is important. Doing this after a whole month has felt too long.</p>
<p>Less coding. I’m going to try to keep an <em>average</em> above one OSS contribution per day. My count on Github should stay at or above 365. For now, I’ll keep contributing daily. My current count for OSS contributions, as of this writing, is 346.</p>
<p>I’d like to read and meditate more. Look for that in my next recap.</p>
<p>There you have it, my January 2014 Recap! I hope this helps you feel inspired to build systems for your own goals, and to write out regular reviews of the progress you’ve made.</p>
<p>Thanks for reading. It certainly helps to have all of you to keep me accountable and to really motivate me. So, thanks!</p>