<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Abletech Blog</title>
  <subtitle>A feed of the latest posts from Abletech blog.</subtitle>
  <link href="https://abletech.nz/feed.xml" rel="self"/>
  <link href="https://abletech.nz/"/>
  
  
  <id>https://abletech.nz</id>
  <author>
    <name>Abletech</name>
    <email>hello@abletech.nz</email>
  </author>
  
    
    <entry>
      <title>Mission-critical app gets a refresh</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>At Abletech, we are proud to maintain the AED Locations app for <a href="https://apps.apple.com/app/apple-store/id424094430?pt=517613&amp;ct=abletech-blog&amp;mt=8" target="_blank" rel="noopener noreferrer">iOS</a> and <a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations&amp;referrer=utm_source%3Dabletech-blog" target="_blank" rel="noopener noreferrer">Android</a>. AEDs (Automated External Defibrillator) are life-saving machines that <a href="https://www.redcross.org/take-a-class/aed/using-an-aed/what-is-aed" target="_blank" rel="noopener noreferrer">help to treat Sudden Cardiac Arrest (SCA)</a>, and the AED Locations app makes it easy for you to find these when you need them. You may have seen the AED symbol while out-and-about across the motu, indicating that one is nearby, available 24/7 or during specific hours. We also maintain the <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">AED Locations website</a>, providing access to these locations from your mobile or desktop browser.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="gyngju62xan0osnc64tqw2fq" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_AED_Locations_48053ee054.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/AED_Locations_48053ee054.png" srcset="https://cms-assets.abletech.nz/thumbnail_AED_Locations_48053ee054.png 245w" data-zooming-width="245" data-zooming-height="109" loading="lazy" width="245" height="109"></figure>
</div>
<h3>A mission-critical app</h3>
<p>The AED Locations app shows you the location of your nearest AED, allows you to search for AEDs, and displays the details of an AED, including its hours of availability, physical address and instructions on how to access it. This experience is bundled up into native apps for iOS and Android. The purpose of AED Locations is twofold - to bring awareness to the availability of these AEDs across New Zealand, and to provide you with an essential resource to find your nearest AED in the event that you, or someone else, needs it.</p>
<p>Because of this, an app like AED Locations needs to be readily accessible, available and resilient. Once a user downloads the app to their device, they might only open it at a critical moment - when it needs to work, and fast. When they open it, they could be in an area with poor or no internet connectivity, not have data available on their mobile, or be running low on battery.</p>
<h3>The 2025 refresh</h3>
<p>To further improve your experience with the AED mobile apps, and make them even more resilient, we updated them in late 2025, bringing performance and stability improvements, as well as a refreshed user interface.</p>
<p>To ensure a consistent experience, regardless of your choice of mobile platform, we wanted to bring the functionality of both apps into alignment. We took the opportunity to look at how each app behaved, taking the best of each, while still respecting platform conventions. For example, the Android app had the ability to center on an AED location once selected, so we brought that behaviour to the iOS app. The iOS app showed a “Report a problem” button after selecting an AED, which we added on Android.</p>
<p>Accessibility was another important factor in this refresh. Screen readers will now have an easier time understanding screen content and navigating around the app, and it is now possible to open and close bottom sheets in the app using accessibility actions. Bottom sheets were given explicit close buttons, providing an escape hatch when gestures are difficult. Both apps now support dynamic font sizing, so if you set a particular font size preference on your device, the apps will respect that and increase (or decrease) the size of text on the screen.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mwwgkxc4v4ybxgytob814fve" alt="" data-big=https://cms-assets.abletech.nz/large_AED_Mobile_eb7ef6a685.png sizes="(min-width: 1280px) 801px, (min-width: 768px) 401px, (min-width: 1024px) 601px, (min-width: 640px) 125px" src="https://cms-assets.abletech.nz/AED_Mobile_eb7ef6a685.png" srcset="https://cms-assets.abletech.nz/large_AED_Mobile_eb7ef6a685.png 801w, https://cms-assets.abletech.nz/small_AED_Mobile_eb7ef6a685.png 401w, https://cms-assets.abletech.nz/medium_AED_Mobile_eb7ef6a685.png 601w, https://cms-assets.abletech.nz/thumbnail_AED_Mobile_eb7ef6a685.png 125w" data-zooming-width="801" data-zooming-height="1000" loading="lazy" width="801" height="1000"></figure>
</div>
<p>We’ve improved the behind-the-scenes behaviour - both apps now check for new locations in the background every 24 hours, helping to keep the list of locations up-to-date as much as possible. Even if you install the app but never open it, or have no internet connection, the app is pre-bundled with a list of known locations at the time of release - meaning you’re never without a list of locations to refer to. Operations to fetch and update data in the local database were rewritten and optimised, to make them more efficient and reliable.</p>
<p>The tech stack of both apps was also modernised. The Android app now uses Kotlin Flows for data exchange, and native layouts for all screens. This resulted in a significant reduction in the amount of memory and battery the app was using. The iOS app now uses SwiftUI and Combine, and as a result, we use system UI elements which makes the app familiar and easy to use. Gestures that you would use in other native apps are also supported, such as the ability to swipe sheets up and down, adding to the consistent experience.</p>
<h3>Conclusion</h3>
<p>The mission-critical nature of the AED Locations apps on mobile means that users need to be able to use the app in a variety of conditions, at the time when they need it most. It needs to be reliable, resilient and fast. By refreshing the apps in late 2025, we improved performance, aligned features across platforms, and enhanced the accessibility of the app, ensuring it is available to, and usable by, as many users as possible across Aotearoa.</p>
<p>If you haven’t already, download the app for <a href="https://apps.apple.com/app/apple-store/id424094430?pt=517613&amp;ct=abletech-blog&amp;mt=8" target="_blank" rel="noopener noreferrer">iOS</a> and <a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations&amp;referrer=utm_source%3Dabletech-blog" target="_blank" rel="noopener noreferrer">Android</a>, or <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">bookmark the site</a> on your device. Is there an AED location not present in the app? <a href="https://aedlocations.co.nz/form/" target="_blank" rel="noopener noreferrer">Use this form</a> to suggest it!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Goodbye Manual Markup: How Claude Code Automates the Figma-to-Code Workflow</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>What if we could take the designs provided to us by our clients and instead of spending hours measuring spacing, and recreating layouts pixel by pixel, we could use AI tooling, and skip the manual translation entirely?</p>
<p>Using Claude Code’s Figma integration, we decided to try exactly that.</p>
<h3>How It Works</h3>
<p>Claude Code connects directly to Figma through the Model Context Protocol (MCP), allowing it to read your design files and generate functional code automatically. Here's the typical workflow:</p>
<h4>1. Design in Figma</h4>
<p>Create your website design in Figma as you normally would. Design your pages, components, and interactions using Figma's familiar interface.</p>
<h4>2. Get Your Figma URL</h4>
<p>Open your design in Figma and copy the URL. It should look something like:
<code>https://figma.com/design/abc123/MyWebsite?node-id=1-2</code>
<br></p>
<h4>3. Generate Code with Claude Code</h4>
<p>In your terminal, paste your Figma URL and ask Claude Code to implement the component:</p>
<p><code>&gt; https://figma.com/design/abc123/MyWebsite?node-id=1-2</code></p>
<p><code>&gt; get_code can you implement the button component from the designs?</code></p>
<p>The <code>get_code</code> command tells Claude Code to fetch the design context from Figma, then it will:</p>
<ul>
<li>Extract the design specifications including all variables and tokens</li>
<li>Generate semantic HTML/JSX structure</li>
<li>Create matching styles with exact colours, spacing, and typography from your Figma variables</li>
<li>Download and reference any image assets</li>
<li>Produce clean, readable code following best practices</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dauqfcertvwltxycfl8g6fsl" alt="" data-big=https://cms-assets.abletech.nz/medium_Comparison_c4de04cf8a.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Comparison_c4de04cf8a.png" srcset="https://cms-assets.abletech.nz/small_Comparison_c4de04cf8a.png 500w, https://cms-assets.abletech.nz/medium_Comparison_c4de04cf8a.png 750w, https://cms-assets.abletech.nz/thumbnail_Comparison_c4de04cf8a.png 245w" data-zooming-width="750" data-zooming-height="291" loading="lazy" width="750" height="291"></figure>
</div>
<h4>4. Refine and Iterate</h4>
<p>Review the generated component, and make any adjustments. You can iterate directly with Claude Code:</p>
<p><code>&gt; That button component doesn't have the same padding as the designs, could you use the padding specified in the designs</code></p>
<p><code>&gt; the designs have the background colour as &lt;variable-name&gt; could you alter the component to use that variable</code></p>
<p>Claude Code will adjust the implementation to match your Figma design specifications exactly.
Claude Code understands your existing codebase context, so it can match your coding style, use your design system, and integrate with your component library. Once satisfied, integrate the component into your project.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="aw6ojo2fs729hk5x065jx84p" alt="" data-big=https://cms-assets.abletech.nz/large_Figma_to_Claude_to_Code_05e0c05ef3.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 195px" src="https://cms-assets.abletech.nz/Figma_to_Claude_to_Code_05e0c05ef3.png" srcset="https://cms-assets.abletech.nz/large_Figma_to_Claude_to_Code_05e0c05ef3.png 1000w, https://cms-assets.abletech.nz/small_Figma_to_Claude_to_Code_05e0c05ef3.png 500w, https://cms-assets.abletech.nz/medium_Figma_to_Claude_to_Code_05e0c05ef3.png 750w, https://cms-assets.abletech.nz/thumbnail_Figma_to_Claude_to_Code_05e0c05ef3.png 195w" data-zooming-width="1000" data-zooming-height="801" loading="lazy" width="1000" height="801"></figure>
</div>
<h3>Real-World Example</h3>
<p>Let's say you've designed a hero section in Figma. Instead of manually coding:</p>
<p><code>.hero {  padding: 80px 24px;  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);  }</code></p>
<p>You simply provide the Figma URL, and Claude Code generates the complete component with:</p>
<ul>
<li>Exact gradient values</li>
<li>Exact design token values (colours, spacing, borders)</li>
<li>Proper component variants and states</li>
<li>Responsive behaviour</li>
<li>Typography that matches your design</li>
<li>Grid layouts and flexbox configurations</li>
<li>Accessibility features</li>
</ul>
<br>
<h3>Advanced Capabilities</h3>
<br>
<h4>Design System Integration</h4>
<p>If you're using a design system, Claude Code can map Figma components to your existing component library:</p>
<p><code>&gt; Use our Button component from components/ui/Button.tsx when generating code from this Figma design</code>
<br></p>
<h4>Stateful Components</h4>
<p>Claude Code can generate components with interactive states:</p>
<p><code>&gt; get_code can you update the Navigation Menu component to have an open and closed state for each menu item and make it reflect this design?</code></p>
<p>The generated code will include proper state management and transitions matching your Figma prototypes.</p>
<h4>Multiple Pages</h4>
<p>Building a multi-page website? Claude Code can process entire page designs:</p>
<p><code>&gt; Generate components for all pages in this Figma file: https://figma.com/design/abc123/MyWebsite</code>
<br></p>
<h4>Responsive Design</h4>
<p>Claude Code analyses your Figma frames and auto-generates responsive CSS with appropriate breakpoints, ensuring your site looks great on all devices.</p>
<h4>Design Variables and Tokens</h4>
<p>Claude Code automatically extracts and uses Figma variables (colours, spacing, typography) to ensure perfect consistency:</p>
<ul>
<li>Colour tokens: <code>accent-75</code>, <code>background</code>, <code>border</code></li>
<li>Spacing values: Exact padding and margin from designs</li>
<li>Typography: Font families, sizes, weights, and line heights</li>
</ul>
<br>
<h3>Benefits</h3>
<p><strong>Speed</strong>: What used to take hours now takes minutes. Generate multiple components in a single conversation.</p>
<p><strong>Accuracy</strong>: Extract exact values from Figma variables. No more guessing if that padding is 16px or 20px, or which shade of blue to use.</p>
<p><strong>Iteration</strong>: Catch design discrepancies immediately. Claude Code can compare generated code against design specs and fix mismatches on the fly.</p>
<p><strong>Consistency</strong>: Maintain perfect design-code consistency across your entire website by using Figma variables as the single source of truth.</p>
<h3>Tips for Best Results</h3>
<br>
<ul>
<li><strong>Use Figma variables</strong>: Define your design tokens (colours, spacing, typography) as Figma variables for automatic extraction</li>
<li><strong>Organise your Figma layers</strong>: Use clear, semantic names for layers and frames</li>
<li><strong>Use Figma components</strong>: Claude Code recognises reusable components</li>
<li><strong>Use get_code prefix</strong>: This tells Claude Code to fetch design context before generating code</li>
<li><strong>Define your tech stack</strong>: Tell Claude Code what framework you're using (React, Vue, vanilla HTML/CSS)</li>
<li><strong>Specify conventions</strong>: Mention any naming conventions or styling approaches you prefer</li>
<li><strong>Establish clear conventions</strong>: This will help prevent some discrepancies (e.g., &quot;always use the exact Figma variable names&quot;)</li>
<li><strong>Verify against designs</strong>: Claude Code can fix spacing, colours, and other values when you point out mismatches</li>
<li><strong>Be specific about discrepancies</strong>: If something doesn't match, reference the specific variable or value (e.g., &quot;the designs have the background colour as <variable-name>&quot;)</li>
<li><strong>Iterate component by component</strong>: Start with core components (buttons, inputs), then build more complex ones (navigation, forms)</li>
</ul>
<br>
<h3>Conclusion</h3>
<p>The Figma-to-code workflow eliminates much of the manual translation work between design and implementation. By automating the extraction of design tokens and component structure, you can focus more on building features and less on pixel-pushing.</p>
<p>This approach works particularly well for:</p>
<ul>
<li>Component libraries and design systems</li>
<li>Rapid prototyping and iteration</li>
<li>Maintaining consistency between design and production code</li>
</ul>
<p>The tooling still requires iteration—you'll often need to refine spacing, colours, or component structure—but it significantly reduces the initial implementation time and helps catch discrepancies early.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Are Local LLMs the solution to all our problems?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rrzkhmp5f2l6v4qiwlc2iwnk" alt="" data-big=https://cms-assets.abletech.nz/large_Untitled_design_f2de47a3be.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/Untitled_design_f2de47a3be.png" srcset="https://cms-assets.abletech.nz/large_Untitled_design_f2de47a3be.png 1000w, https://cms-assets.abletech.nz/small_Untitled_design_f2de47a3be.png 500w, https://cms-assets.abletech.nz/medium_Untitled_design_f2de47a3be.png 750w, https://cms-assets.abletech.nz/thumbnail_Untitled_design_f2de47a3be.png 156w" data-zooming-width="1000" data-zooming-height="1000" loading="lazy" width="1000" height="1000"></figure>
</div>
<p>It feels like there is a growing gap between people who are deep in technology/AI space (like myself) and everyone else about whether AI is good or bad. Take, for example, this study [1] done in the US on whether people think AI will have a positive or negative effect on the US over the next 20 years.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="eeesqyoymd7q2os8zzdygh39" alt="" data-big=https://cms-assets.abletech.nz/medium_pi_2025_04_03_us_public_and_ai_experts_0_01_2b551e5984.webp sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 184px" src="https://cms-assets.abletech.nz/pi_2025_04_03_us_public_and_ai_experts_0_01_2b551e5984.webp" srcset="https://cms-assets.abletech.nz/small_pi_2025_04_03_us_public_and_ai_experts_0_01_2b551e5984.webp 500w, https://cms-assets.abletech.nz/medium_pi_2025_04_03_us_public_and_ai_experts_0_01_2b551e5984.webp 750w, https://cms-assets.abletech.nz/thumbnail_pi_2025_04_03_us_public_and_ai_experts_0_01_2b551e5984.webp 184w" data-zooming-width="750" data-zooming-height="636" loading="lazy" width="750" height="636"></figure>
</div>
<p>[1]</p>
<p>The difference here is quite staggering, and is certainly not the vibe you get in tech-focused spaces. It seems like every tech company is integrating AI into their products as if their lives depend on it, and Nvidia stock seems to be infinitely growing in value. Specifically in the developer space (where I live) it feels like everyone is using AI to speed up their development workflow and are producing apps faster than they can think of catchy domain names to deploy them to!</p>
<p>Whereas, in my personal life, most people I talk to really don’t like AI. When companies signal their move toward using more AI, there is sometimes quite a strong backlash from the public. For example, the social media backlash around Duolingo[2]. There are a few main concerns that the general public have about AI: its impact on the environment, copyright concerns around training data, privacy concerns, and the threat of replacing human workers. To focus primarily on the environmental impact: AI models, more specifically Large Language Models (LLMs) take a whole lot of computing power to train and also to run day-to-day. This has driven large increases in demand for data centers and, as a result, electricity. Well, what if we didn’t need all that data center grunt, can we just run LLMs on our existing hardware? Turns out we can! Enter open-weight LLMs.
Open-weight LLMs are effectively open source (they’re technically not actually open source which is why we call them open-weight) LLMs that you can download and run on whatever hardware you like, including your laptop! So, one can imagine a world where instead of getting ‘bleeding-edge’ performance from the very best models running in the cloud, we can get ‘good enough’ performance at the cost of your laptop battery draining a bit faster. Is such a world possible today?</p>
<p><strong>Note:</strong> Literally the day that I am writing this article, OpenAI has just released its first-ever set of open-weight models. They look very promising and would potentially have changed the conclusions I made here.</p>
<p>In my best attempt to answer this question,  I got a set up working really quickly using LM Studio and Aider.  On my 2021 M1 Pro Macbook Pro (with 32GB of RAM), the best models I have found for my use case are qwen2.5-coder-14b  and qwen3-30b-a3b which use ~8GB and an eye-watering 18GB of RAM, respectively. While I did get slightly better results with the larger model (it even seemed to work faster, due to whatever magic allows it to only have 3B active parameters), the more-than-doubling of RAM usage was unacceptable. The results I was able to get out  of qwen2.5-coder-14b were pretty good. It's able to refactor bloated types for me, as well as fix tests that are failing due to missing React contexts. Though, it is immediately obvious that it is running on my laptop and not some GPU compute cluster somewhere in the cloud, when the first token takes around 15-20 seconds to appear. The code it generates does sometimes need some tweaking, though the process is certainly faster than having to read documentation to refresh my memory on seldom-used syntax.
Now, looked at in isolation, these results are pretty good. I can effectively work on multiple issues at the same time, firing off a prompt about a simple-looking bug that’s cropped up. Then, while I wait for the LLM to get back to me, I can go and fix another one that requires a bit more contextual knowledge. By the time I'm done, there should be a maybe 80-90% ready solution for the bug that I can quickly tweak and commit right after.</p>
<p>Well, they say 'Comparison is the thief of joy', and here it is: Github Copilot. State-of-the-art models, running on whatever hardware necessary. How many gigabytes of RAM it takes to run is not my problem. And all for just $10USD/Mo. And with great computing power comes some pretty great results: Using the same prompt as I did for my local model, I got it to try and fix the same test that was failing (from the missing context). To solve my problem, Copilot generated a first draft of a mock context. It then realised that this mock was throwing a type error. So it searched the codebase for the type definition, figured out what properties it was missing, and added them. Now that it knew it had correctly-compiling code, it asked if it could run the failing test, which passed, it then knew to run the entire test suite to ensure it hadn't broken anything anywhere else.
And it did all the above in the time the local model took to just generate its best guess at a fix. A lot of the gains here come from the tool calls that Copliot does, and I know that I theoretically could get a similar setup with the Qwen model running locally. But as far as I understand, that requires a whole lot of manual setup, and Qwen2.5-coder was not trained for tool calls.</p>
<p>Let’s loop back to why we’re looking at locally-running LLMs in the first place: energy usage. Estimating energy usage for AI, especially for something like Copilot, is difficult for many reasons. 1. We don't know how big models like Sonnet 4 and GPT 4.1 actually are. 2. LLMs for this use-case have very large context windows and are doing specialised tool-calls, and I can only <em>guess</em> that this has a higher electricity cost than your typical ChatGPT request. 3. Even if the first two things are known, LLM electricity usage is hard to quantify anyway, even small variations in more 'standard' prompting can make the LLM work much harder. All that being said, here is a <strong>very</strong> rough estimate: An article from MIT Technology Review[3] estimates that Llama 3.1 (405B parameters) uses 6706 joules, on average, per response. For reference, it is estimated that GPT-4 could have over 1T parameters. So lets say that the average prompt for Copilot running GPT 4.1 is 5 times more expensive than that, 33,530 joules. Lets say that our 7 Abletech devs are doing 30 prompts a day, 5 days a week: 33530 * 7 * 30 * 5  = 35,206,500 joules/week, which converts to roughly 9.78 kWh/week. For reference, the maybe 10-year-old fridge that came with my apartment is rated to use 10.58 kWh/week.
I think the question around water usage falls into a similar category as electricity usage. Yes, generative AI is a GPU-intensive task, and GPUs run hotter than CPUs, so it does create a higher demand for cool water. But as shown by the above estimations, the extra demand that we, as an organisation, create is but a drop in the pond.
That’s just the energy required to run the model on an ongoing basis. The other component of this is the energy used to train these models. This is massive, an MIT News article[4] states that for GPT-3 (from 2020) 'the training process alone consumed 1,287 megawatt hours of electricity (enough to power about 120 average U.S. homes for a year)'. I am sure that something like GPT4.1 or Sonnet4 used significantly more than that. And, to circle back to my point, the local LLM models will have consumed similar amounts of energy to be trained, so by using them we're not really 'saving' on that front.</p>
<p>To briefly address the ethical concerns around training data. Unfortunately, local/open-source LLMs are trained on just-as-sketchy data. Meta's open-weight LLaMa 3 was trained on Books3 which contains hundreds of thousands of pirated books and was also the subject of a lawsuit from many of the authors of said books[5].</p>
<p>This wraps around to the more general question: How much impact can one small organisation even have here? It is unquestionable that the general trend of AI and the industry-at-large has significant effects on the environment. Quoting again from MIT Technology Review[3]: 'AI-specific servers [...] are estimated to have used between 53 and 76 terawatt-hours of electricity. On the high end, this is enough to power more than 7.2 million US homes for a year.'. And the ethical questions around training data are unavoidable if you are using generative AI in any capacity. So, will moving our handful of devs off of Github Copilot and onto some combination of Qwen models and Aider have a measurable impact on the environmental effects of AI-at-large? No. Will it even have a significant impact on the carbon footprint of our Organisation? Not really. But is taking a step in the 'right direction' here worth the trade-offs?
With the current state of the technology, and in this particular context I would lean towards no. Though in just 12 months this whole thing might flip on its head. We might be able to run models with big contexts and tool-usage baked in directly off of our laptops, merely sipping at the RAM and GPU. At which point this question will be worth revisiting.</p>
<p>You may have noticed that completely dodged talking about privacy and replacing human workers. I am not even close to being qualified to talk about the latter, so I will leave that one alone. However,  I do plan on doing a follow-up article about privacy and that will likely be through the lens of a more general user compared to a developer. That might be a situation where the local model is 'good enough' and the other contributing factors outweigh the performance left on the table. Stay tuned!</p>
<p><strong>Sources</strong><br>
[1] <a href="https://www.pewresearch.org/internet/2025/04/03/how-the-us-public-and-ai-experts-view-artificial-intelligence/#who-did-we-define-as-ai-experts-and-how-did-we-identify-them" target="_blank" rel="noopener noreferrer">pewresearch.org</a><br>
[2] <a href="https://www.wired.com/story/generative-ai-backlash/" target="_blank" rel="noopener noreferrer">wired.com</a><br>
[3] <a href="https://www.technologyreview.com/2025/05/20/1116327/ai-energy-usage-climate-footprint-big-tech/" target="_blank" rel="noopener noreferrer">technologyreview.com</a><br>
[4] <a href="https://www.news.mit.edu/2025/explained-generative-ai-environmental-impact-0117" target="_blank" rel="noopener noreferrer">news.mit.edu</a><br>
[5] <a href="https://www.theatlantic.com/technology/archive/2023/08/books3-ai-meta-llama-pirated-books/675063/" target="_blank" rel="noopener noreferrer">theatlantic.com</a></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>OWASP Top 10: Key Takeaways from a Secure Code Training Course</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wm4w18v6jswobphymc5tfsbk" alt="A steampunk-style IT security guardian standing in front of a server, holding a mechanical staff prominently in front of it. The guardian wears brass" data-big=https://cms-assets.abletech.nz/large_j_L_Hn_S_Rft_Ws_1024_9bfe13db26.webp sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/j_L_Hn_S_Rft_Ws_1024_9bfe13db26.webp" srcset="https://cms-assets.abletech.nz/large_j_L_Hn_S_Rft_Ws_1024_9bfe13db26.webp 1000w, https://cms-assets.abletech.nz/small_j_L_Hn_S_Rft_Ws_1024_9bfe13db26.webp 500w, https://cms-assets.abletech.nz/medium_j_L_Hn_S_Rft_Ws_1024_9bfe13db26.webp 750w, https://cms-assets.abletech.nz/thumbnail_j_L_Hn_S_Rft_Ws_1024_9bfe13db26.webp 156w" data-zooming-width="1000" data-zooming-height="1000" loading="lazy" width="1000" height="1000"></figure>
</div>
<p>I recently attended a <a href="https://www.privsec.nz/security-awareness-and-training.html" target="_blank" rel="noopener noreferrer">Secure Code Training</a> course hosted by PrivSec Consulting. The course broadly followed the <a href="https://owasp.org/www-project-top-ten/" target="_blank" rel="noopener noreferrer">OWASP Top Ten</a>, representing the most critical security risks to web applications.</p>
<p>The course highlighted how easy it is to miss small issues with significant security implications and the importance of understanding and mitigating these vulnerabilities. The course goes much more in-depth about each vulnerability, known exploits, and hands-on exercises to exploit them yourself which were particularly helpful in understanding each vulnerability's anatomy. I highly recommend this course to anyone interested in application security or penetration testing.</p>
<p>This article summarizes the key takeaways and mitigations discussed in the course.</p>
<p>The list is in the same order as the <a href="https://owasp.org/www-project-top-ten/" target="_blank" rel="noopener noreferrer">OWASP Top 10</a>, which is sorted from most to least impactful. This isn't to say that number 10 on the list can't be just as devastating as number 1; it just outlines the broader risk as identified by OWASP.</p>
<p>If you want to learn more, hit us up at <a href="www.abletech.nz" target="_blank" rel="noopener noreferrer">Abletech</a>!</p>
<h2>1. <a href="https://owasp.org/Top10/A01_2021-Broken_Access_Control/" target="_blank" rel="noopener noreferrer">Broken Access Control</a></h2>
<p>Being able to access things you <strong>shouldn't</strong> be able to can result in users being able to edit another user's account data, access restricted pages, perform actions while unauthenticated, or read arbitrary files via path traversal.</p>
<p><strong>Exploit example:</strong> If relying purely on client-side validation, a malicious user might change information in the payload sent to the server, such as making changes to another user's data by changing the user ID in the request body.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Deny access by default unless the resource is public.</li>
<li>Ensure validations are also performed on the server, including checking that the user requesting to view or change information has access to the resource, is able to perform the specific action, and is only able to provide a list of permitted values.</li>
<li>Any information that doesn't <strong>need</strong> to be provided by the user shouldn't be.</li>
<li>Lock down resource handlers to specific public directories so path traversal is not possible.</li>
<li>Only return as much data as needed.</li>
<li>Implement alerting for large influxes of requests, specifically 401/403, which might indicate a malicious party is attempting to see what they can and can't access.</li>
<li>Embody common vulnerabilities into unit tests so you will be aware if code changes introduce vulnerabilities.</li>
</ul>
<h2>2. <a href="https://owasp.org/Top10/A02_2021-Cryptographic_Failures/" target="_blank" rel="noopener noreferrer">Cryptographic Failure</a></h2>
<p>A breakdown in encryption/security that leads to exposing sensitive data.</p>
<p><strong>Examples include:</strong></p>
<ul>
<li>Using a weak password hashing function.</li>
<li>Using passwords as cryptographic keys.</li>
<li>Accidentally checking secrets or keys into git.</li>
<li>Implementing your own insecure algorithms.</li>
<li>Presence of Cryptographic Oracle (an oracle is any system that can give some extra information on a system, which otherwise would not be available).</li>
</ul>
<p><strong>Exploit example:</strong> A malicious user could find a secret key hard-coded into JavaScript and use it to impersonate other users.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>If you don't need it, don't store it.</li>
<li>Encrypt all data in transit and at rest.</li>
<li>Disable caching for any sensitive data.</li>
<li>Don't roll your own crypto.</li>
<li>Ensure error messages don't give any insight into the system.</li>
</ul>
<h2>3. <a href="https://owasp.org/Top10/A03_2021-Injection/" target="_blank" rel="noopener noreferrer">Injection</a></h2>
<p>When a client is providing data, there is a possibility of injection where the user can cause some unexpected behavior with their input.</p>
<p><strong>Examples include:</strong></p>
<ul>
<li>SQL Injection</li>
<li>Cross-Site Scripting (XSS)</li>
<li>Command Injection</li>
<li>Template Injection</li>
</ul>
<p><strong>Exploit example:</strong> If an application is sorting items on a page using query params with keywords like DESC or ASC, they might be feeding these user inputs straight into the SQL query. We can send another malicious query instead and potentially get information about the structure of the database or users' password hashes.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>User-provided data should be untrusted.</li>
<li>Any client input should be sanitized before usage.</li>
<li>Use prepared statements and parameterized queries to handle data inputs.</li>
</ul>
<h2>4. <a href="https://owasp.org/Top10/A04_2021-Insecure_Design/" target="_blank" rel="noopener noreferrer">Insecure Design</a></h2>
<p>This refers to design flaws in application development that expose security risks and may be consequences of application architecture.</p>
<p><strong>Examples include:</strong></p>
<ul>
<li>Actions that use unnecessary user input.</li>
<li>Returning whether an account with a provided email exists on login/reset.</li>
<li>Leaving deployment artifacts such as <code>.git</code> in the web root.</li>
</ul>
<p><strong>Exploit example:</strong> If your application returns an error saying this user doesn't have an account on the login or reset flow for provided email addresses, a malicious user can use this to determine which accounts exist and use this information in further vulnerabilities.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Use secure and established design patterns.</li>
<li>Use threat modeling to identify potential security issues.</li>
</ul>
<h2>5. <a href="https://owasp.org/Top10/A05_2021-Security_Misconfiguration/" target="_blank" rel="noopener noreferrer">Security Misconfiguration</a></h2>
<p>This refers to security settings or configurations that may be implemented or set improperly.</p>
<p><strong>Examples include:</strong></p>
<ul>
<li>Default username/password.</li>
<li>Bad error handling that gives information about the underlying system.</li>
<li>Intentionally disabling security features.</li>
</ul>
<p><strong>Exploit example:</strong> A default admin account exists for some applications. If the password for this account hasn't been changed, malicious users may be able to access the application's admin functionality with a known default username/password.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Regular audits of configuration.</li>
<li>Automate the process of deploying configuration to avoid human error.</li>
<li>Use Infrastructure as Code (IaC) to store configuration in repeatable and reviewed code.</li>
</ul>
<h2>6. <a href="https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/" target="_blank" rel="noopener noreferrer">Vulnerable and Outdated Components</a></h2>
<p>Components are constantly being updated and improved as new vulnerabilities are discovered. It is important to keep them up to date as older versions may be susceptible to other vulnerabilities on this list.</p>
<p><strong>Exploit example:</strong> A malicious user is able to use a known flaw in a dependency to compromise your application.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Keep software up to date.</li>
<li>Use dependency checking tools like Dependabot to alert you to vulnerabilities.</li>
<li>Avoid using unmaintained components that aren't getting security updates.</li>
<li>Make sure you are downloading components from a reputable source.</li>
<li>Remove any unused dependencies.</li>
</ul>
<h2>7. <a href="https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/" target="_blank" rel="noopener noreferrer">Identification and Authentication Failures</a></h2>
<p>Security weaknesses in the application's identification and authentication processes that lead to unauthorized access.</p>
<p><strong>Exploit example:</strong> If you aren't prompting for the current password when changing a user's password, a malicious user can leverage this to use cross-site scripting to take over another user's account.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Implement Multi-Factor Authentication (MFA).</li>
<li>Use a strong hashing algorithm.</li>
<li>Lock accounts after failed login attempts.</li>
<li>Use established design patterns.</li>
<li>Implement strong password requirements.</li>
</ul>
<h2>8. <a href="https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/" target="_blank" rel="noopener noreferrer">Software and Data Integrity Failures</a></h2>
<p>Code and infrastructure that doesn't protect against integrity violations can lead to unauthorized software modification and malicious code execution.</p>
<p><strong>Exploit example:</strong> If a malicious user is able to replace a package your application depends on, or if you are using an untrusted package in the first place, they might be able to compromise your application without you knowing.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Ensure your libraries and dependencies are coming from trusted sources.</li>
<li>Ensure your CI/CD has good segregation and access control.</li>
<li>Use dependency checkers.</li>
</ul>
<h2>9. <a href="https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/" target="_blank" rel="noopener noreferrer">Security Logging and Monitoring Failures</a></h2>
<p>This occurs when there is insufficient logging or monitoring which might leave you unaware of suspicious activities or when security breaches may have occurred.</p>
<p><strong>Exploit example:</strong> If a malicious user is attempting to brute force a bunch of different path combinations in the hopes of gaining some information about the system and you aren't being alerted, this may go unnoticed and you won't have the chance to stop them.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Persist logs somewhere that is not locally on the server.</li>
<li>Monitor logs in real-time to look for suspicious activity.</li>
<li>Set up alerts if certain conditions are met (e.g., failed login or 403 responses).</li>
</ul>
<h2>10. <a href="https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/" target="_blank" rel="noopener noreferrer">Server-Side Request Forgery (SSRF)</a></h2>
<p>This is a vulnerability that allows an attacker to cause the server-side application to make requests to an unintended location, often bypassing security controls.</p>
<p><strong>Exploit example:</strong> A malicious user may upload an SVG image that contains a URL to some domain they control. If this is executed, it may be used to gain information from the server.</p>
<p><strong>Mitigations:</strong></p>
<ul>
<li>Validate and sanitize user input.</li>
<li>Implement firewall rules and network segmentation to keep servers contained.</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>The Myth of Icarus and the Modern Dilemma of AI-Augmented Coding</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>Once upon a time—well, in a land of myth and melodrama—a young chap named Icarus got the ultimate upgrade: wings. Handcrafted by his ever-wise (and occasionally overly ambitious) father, Daedalus, these wings were a marvel of ancient engineering, crafted from feathers and wax. Icarus, thrilled by his newfound ability to defy gravity, soared into the sky with the enthusiasm of a kid on a sugar rush.</p>
<p>But alas, our feathered hero forgot one crucial detail: even wax has its melting point. With a misplaced sense of invincibility and a blind spot for caution, Icarus zoomed too high, flirting with the sun until—sizzle!—his wings melted, and he made a spectacular, if not tragic, plunge back to Earth.</p>
<p>Fast forward to our modern era of code and coffee, and a similar spectacle is emerging—except instead of wings, junior developers are being handed shiny new AI tools and coding assistants. These digital marvels promise superhuman speed and efficiency, much like Icarus’s wings promised the thrill of flight. But here’s the rub: without a solid grounding in the fundamentals of coding, these promising tools might just lead to a modern-day Icarus moment. Imagine a junior developer, all bright-eyed and armed with an AI co-pilot, ready to take on the tech world—only to crash-land when the code doesn’t quite work as expected.</p>
<p>This <strong>isn't</strong> a call to banish AI to the realm of Icarus’ ill-fated wings; rather, it’s a wake-up call for business leaders who depend on development teams to drive innovation. The challenge lies in ensuring that these budding developers evolve into knowledgeable, reliable professionals—adept at harnessing AI, yet wise enough to know when to debug it, question it, and sometimes even ignore it.</p>
<h3>The New Era of AI-Augmented Coding</h3>
<p>AI agents have rapidly become part of every coder's toolkit, offering everything from auto-completion to full-on code generation. Senior developers are already leveraging these capabilities to achieve near-miraculous levels of productivity. Meanwhile, junior developers might find themselves watching from the sidelines, tasked with less challenging work. It’s a bit like getting a jetpack but being told to walk the dog with it—exciting in theory, but a bit of a letdown in practice.</p>
<h4>Impacts on the Junior Developer Experience</h4>
<p>Limited Exposure to Complex Challenges</p>
<p>When seniors use AI to solve the big, tough problems, juniors can end up with only the easy bits. This might seem like a safe way to ease them into the world of coding, but without those high-stakes challenges, how will they ever learn to truly debug a mysterious error or craft elegant, efficient solutions?</p>
<p>Rising Opportunity Costs in Mentorship</p>
<p>Mentoring is the secret sauce that transforms a rookie into a master. But as AI takes over more of the heavy lifting, senior developers find themselves with less time to pass on their hard-earned wisdom. Every minute spent on mentorship is a minute not spent on high-impact tasks, and in a fast-paced world where productivity is king, that’s a tough trade-off for any business leader to ignore.</p>
<p>Dependence on AI’s “Best Practices”</p>
<p>There’s a danger in taking AI suggestions at face value. Junior developers might start relying too heavily on AI-generated code, adopting practices without fully understanding the context behind them. It’s like learning to drive solely in a simulator—fun and efficient, but not exactly the same as navigating a real, unpredictable highway.</p>
<h4>Business Implications: A Supply Chain at Risk</h4>
<p>For business leaders, the stakes are high. The future of your innovation pipeline depends on a steady stream of skilled developers who can think critically and solve complex problems. If junior developers miss out on the hands-on, sometimes messy, but ultimately enriching process of learning through challenges, your team might end up with a shiny facade of productivity but a shaky foundation of talent.</p>
<p>Investing in proper training isn’t just a “nice-to-have”—it’s a strategic imperative. Neglecting this could lead to a talent drought, where the next generation of developers might be adept at wielding AI like a magic wand but lack the deep understanding necessary to drive true innovation.</p>
<h4>Insights from the AI &amp; Coding Community</h4>
<p>The chatter among developers is as lively as a coding hackathon. Experts are buzzing about the double-edged sword of AI: while these tools boost efficiency, they also risk creating an echo chamber of conventional practices.</p>
<p>The consensus?</p>
<p>Embrace AI, but don’t let it replace the rigorous, foundational training that transforms a good developer into a great one. After all, no one wants a generation of “AI whisperers” who can’t decipher the mystery behind that one pesky bug.</p>
<h4>Strategies to Ensure Sustainable Developer Growth</h4>
<p>How can we avoid turning our junior developers into modern-day Icaruses? Here are a few strategic moves:</p>
<ul>
<li>
<p>Rethink Mentorship Models: Integrate AI as an assistant rather than a crutch. Encourage mentorship that delves into the “why” behind the code, ensuring that juniors aren’t just copying and pasting but are truly understanding the craft.</p>
</li>
<li>
<p>Invest in Foundational Learning: Balance the allure of quick fixes with robust instruction in core coding principles. This way, even when the AI suggests a shortcut, developers can evaluate whether it’s really the best route.</p>
</li>
<li>
<p>Cultivate a Culture of Curiosity: Foster an environment where asking “Why did the AI suggest that?” is as common as debugging. Celebrate questions and encourage exploration—after all, every great coder started by wondering what makes the code tick.</p>
</li>
</ul>
<h2>Conclusion: Soar Wisely, But Not Too High</h2>
<p>Just as Icarus learned the hard way that wings require more than just wax and feathers, our developers need more than just flashy AI tools to succeed. The story of Icarus is a playful yet poignant reminder: wield great power responsibly, or risk a spectacular fall.</p>
<p>For business leaders, the message is clear: <strong>invest in nurturing talent.</strong>
By balancing the efficiency gains from AI with comprehensive, hands-on training, you can ensure that your developers evolve into wise, capable professionals. Let’s aim to produce not just coders, but true artisans of software—ones who can harness AI’s power without flying too close to the sun.</p>
<p>So, here’s to a future where our developers soar gracefully—leveraging AI with the savvy of a seasoned pilot and the humility of one who’s learned that sometimes, a little caution goes a long way.</p>
<p>Let’s make sure that our tech teams can not only reach for the stars but also know how to navigate back down to Earth.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>End of Year Reflection - of a sort</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bovfskpvbv8zstffu2dqmuxo" alt="" data-big=https://cms-assets.abletech.nz/large_60_IL_Ec_Y_e1_1280_801e14c817.webp sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/60_IL_Ec_Y_e1_1280_801e14c817.webp" srcset="https://cms-assets.abletech.nz/large_60_IL_Ec_Y_e1_1280_801e14c817.webp 1000w, https://cms-assets.abletech.nz/small_60_IL_Ec_Y_e1_1280_801e14c817.webp 500w, https://cms-assets.abletech.nz/medium_60_IL_Ec_Y_e1_1280_801e14c817.webp 750w, https://cms-assets.abletech.nz/thumbnail_60_IL_Ec_Y_e1_1280_801e14c817.webp 245w" data-zooming-width="1000" data-zooming-height="562" loading="lazy" width="1000" height="562"></figure>
</div>
<p>Recently, during a casual conversation over drinkies, I was discussing our company's plans to continue working with <a href="https://www.linkedin.com/company/riki-consultancy-ltd/" target="_blank" rel="noopener noreferrer">Riki Consultancy Ltd</a> next year as part of our Te Tiriti strategy. Someone asked why we were still bothering, especially since under the current government it was no longer an edict—in fact, quite the opposite. My response was simple: we never undertook this journey because of an edict from anyone; we undertook it because it aligned with our values as a company. It was, and remains, the right thing to do.</p>
<p>Eru Kapa-Kingi said, &quot;The difference between a radical thought and a normal one is timing and familiarity.&quot;</p>
<p>During the Hikoi, I observed many people who, ten years ago, might have considered participating in such an event as being a radical act.</p>
<p>On the day, however, they seemed to find it the most normal, natural thing to be doing. These individuals had clearly looked past the reductive and dismissive rhetoric that was flung at the peaceful but determined protest the moment it began. I suspect they also had a pretty good understanding that &quot;equality&quot; doesn’t necessarily translate into &quot;equity.&quot;</p>
<p>Our partner at <a href="http://koha.kiwi/" target="_blank" rel="noopener noreferrer">Koha.kiwi</a>, <a href="https://www.linkedin.com/in/te-awanui-reeder-22298929/" target="_blank" rel="noopener noreferrer">Te Awanui Reeder</a>, recently wrote about the exhaustion he feels due the constant battle he faces as tangata whenua, especially in recent times.</p>
<p>Reading his words left me feeling embarrassed. Not embarrassed by his vulnerability, but by the fact that I could go on the Hikoi, feeling invigorated and proud to have been part of something magical, and then simply return to other things. My participation in this battle is, if i'm honest, a choice. That realisation shifted my embarrassment into something deeper: I felt a bit ashamed.</p>
<h3>Moving Forward</h3>
<p>So, whanau, as the Hikoi transitions from a powerful moment into a cherished memory, I reckon the best thing that tangata tiriti can do is to help in the battle, lets carry some of the load. So over the break, here are a few things I am going to do:</p>
<ul>
<li>
<p>Make a submission!</p>
</li>
<li>
<p>Celebrate and elevate tangata whenua! - Proudly and loudly acknowledge the amazing contributions of tangata whenua. Through the socials, with mates, supporting events.</p>
</li>
<li>
<p>Immerse myself further in Matauranga Māori. - This knowledge is graciously shared, I am going to gratefully embrace it.</p>
</li>
<li>
<p>Learn even more about the history of this country - I loved Social Studies and History at High School, but hell I had completely forgotten about the legislative genocide that happened in this country. Thanks <a href="https://www.linkedin.com/company/riki-consultancy-ltd/" target="_blank" rel="noopener noreferrer">Riki Consultancy Ltd</a></p>
</li>
<li>
<p>Speak up - At that Christmas BBQ, when the racist second cousin talks about &quot;bloody Maoris,&quot; rather than rolling my eyeballs loudly, I will gently but firmly correct them: &quot;There is no 'S' in Māori, so it’s 'Bloody Māori.&quot;</p>
</li>
<li>
<p>Then, as they rapidly try to change the subject, I will push on and explain that: instead of fearing what they think will be taken from them, they should try and celebrate the richness and beauty that comes through a Te Ao Māori lens, appreciate the taonga that tangata whenua give to this stunning country, appreciate the gift that tangata whenua are and appreciate what a privilege it is to be able to march along side our partners - yesterday, today and tomorrow.</p>
</li>
</ul>
<p>So endeth the rant! 😊</p>
<p>May you have a safe and restful break.</p>
<p><strong><em>Ka ara ake tētahi, ka ara ake te katoa - One lifts, we all lift</em></strong></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Navigating the Exciting Yet Complex World of Software Development</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="v7ubehenywymbt01lpyhkx9m" alt="" data-big=https://cms-assets.abletech.nz/large_1732846237626_6b75e72345.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1732846237626_6b75e72345.png" srcset="https://cms-assets.abletech.nz/large_1732846237626_6b75e72345.png 1000w, https://cms-assets.abletech.nz/small_1732846237626_6b75e72345.png 500w, https://cms-assets.abletech.nz/medium_1732846237626_6b75e72345.png 750w, https://cms-assets.abletech.nz/thumbnail_1732846237626_6b75e72345.png 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<h3><em>Why Funding Models Matter More Than Ever</em></h3>
<p>It's an incredibly exciting time to be creating software. Never before has it been faster to produce mockups or cheaper to build prototypes. If you have software you want to build, there's no better time than now. The accessibility of development tools and resources has lowered barriers, empowering both newcomers and seasoned entrepreneurs to bring innovative ideas to life swiftly.</p>
<p>However, amidst this enthusiasm, it's crucial not to overlook a fundamental reality: a software business is still a business. It requires customers, revenue, and a sustainable funding model. The ease of creating software can sometimes mask the challenges of turning a prototype into a profitable enterprise.</p>
<h3>The Allure of Easy Entry</h3>
<p>The rise of AI, no-code platforms, open-source libraries, and affordable cloud services has democratised software development. Entrepreneurs can now validate ideas and develop minimum viable products (MVPs) with minimal upfront costs. This environment fosters innovation and allows for rapid iteration, which is fantastic for testing concepts and responding to market feedback.</p>
<h3>The Hidden Costs</h3>
<p>Despite the reduced costs of initial development, scaling a software product demands significant investment. Marketing, customer support, infrastructure, and ongoing development require resources that go beyond what initial prototypes might suggest. The illusion of low-cost entry can lead founders to underestimate these expenses, potentially jeopardising the long-term viability of their ventures.</p>
<h3>The Grant Trap</h3>
<p>Grants can be a double-edged sword. While they provide much-needed capital without diluting ownership, they can also create a false sense of security. Relying heavily on grants may delay the pursuit of a viable business model, as the immediate pressure to generate revenue is alleviated. This can lead to a focus on research and development at the expense of market validation and profitability.</p>
<h3>Venture Capital: Friend or Foe?</h3>
<p>Venture capital (VC) funding offers the allure of substantial resources to accelerate growth. However, initial cash injections can sometimes mask poor business models. With significant funding at their disposal, startups might overlook fundamental flaws in their revenue strategies or market fit, assuming that ample capital can compensate for underlying issues.</p>
<p>Moreover, it's essential to recognise that the goals of VC investors may not always align with building a sustainable business. Sometimes, the primary objective isn't to develop a viable, profitable company but to acquire a large user base rapidly. In the ever-exciting data play, acquiring customers becomes the focal point. Startups are pushed to prioritise growth and data collection over profitability, banking on the potential value of user data for future monetisation.</p>
<p>This approach can be risky. By concentrating on customer acquisition and data gathering, companies may neglect the development of a solid business model. If the anticipated data-driven revenue streams don't materialise, the startup could find itself without a sustainable path forward.</p>
<p>The really important thing to keep in mind is that though VC funding can kickstart your dream, it is a business model in its own right and a very different one to your business. Like anything, you have to know how the game is played if you want to win. Do you have the time, space and energy to play your game and theirs?</p>
<h3>Balancing Product Development and Profitability</h3>
<p>There's an inherent tension between perfecting your product and building a sustainable business. While it's useful to delay monetisation until your offering is refined, it's essential to find ways to validate your business model early on. Holding off on charging customers can hinder your ability to test price sensitivity, understand customer value perception, and establish reliable revenue streams. On top of that, you can have a solid business model but still struggle with cashflow. If sales take too long to catch up to costs, you risk everything you’ve worked for each time you try to make payroll -let along paying those cloud hosting invoices!</p>
<h3>Strategies for Success</h3>
<p><strong>Early Market Engagement:</strong>  Involve potential customers from the outset. Their feedback can guide development and ensure the product meets real needs.</p>
<p><strong>Test Monetisation Models:</strong>  Experiment with different pricing strategies early. This helps identify what customers are willing to pay and adjusts your value proposition accordingly.</p>
<p><strong>Sustainable Funding Mix:</strong>  Diversify your funding sources. Combine personal investment, customer revenue, and external funding to balance control and resource availability.</p>
<p><strong>Focus on Profitability:</strong>  Keep a close eye on the path to profitability. Even if growth is the current focus, have a clear plan for how and when the business will become profitable.</p>
<p>The current landscape offers unprecedented opportunities to bring software ideas to life. However, the fundamentals of building a successful business haven't changed. Revenue, customers, and a solid funding model are as critical as ever. By acknowledging the need for profitability early and balancing product development with business strategy, founders can turn exciting prototypes into sustainable enterprises.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="joxd3elxrd3w5gctqs90wcdk" alt="" data-big=https://cms-assets.abletech.nz/small_1732846298088_d6799abe3e.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 180px" src="https://cms-assets.abletech.nz/1732846298088_d6799abe3e.jpeg" srcset="https://cms-assets.abletech.nz/small_1732846298088_d6799abe3e.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1732846298088_d6799abe3e.jpeg 180w" data-zooming-width="500" data-zooming-height="434" loading="lazy" width="500" height="434"></figure>
</div>
<p>If you're embarking on the journey of building a software business, remember to keep one eye on innovation and the other on sustainability. Engage with mentors, seek diverse funding sources, and never lose sight of the importance of a solid business model.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>How did we get here?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zvan190i9zxuxrjygbo3p3no" alt="" data-big=https://cms-assets.abletech.nz/small_1730497899951_dbe9d56222.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1730497899951_dbe9d56222.jpeg" srcset="https://cms-assets.abletech.nz/small_1730497899951_dbe9d56222.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1730497899951_dbe9d56222.jpeg 245w" data-zooming-width="500" data-zooming-height="281" loading="lazy" width="500" height="281"></figure>
</div>
<p>In my role <a href="https://abletech.nz/">Abletech</a> I've spent the last few months preparing software proposals, RFIs and RFPs and I'm left feeling reflective about the state of the software market. My overwhelming urge is to do the rounds of offices of tech purchasers giving out hugs.</p>
<p>Who hurt you?!</p>
<p>Software development has matured dramatically over the years. We've embraced agile methodologies, experimented with lean thinking from manufacturing, dabbled in Kanban, and even birthed entire disciplines like product development. We’ve seen firsthand the dangers of rigid, sequential &quot;waterfall&quot; models, and while we haven’t solved every challenge in software development, we’ve made extraordinary progress. Productivity has soared, time to market has shortened, and we’ve become much better at building products that actually fit the needs of the market, all while embracing the rapid feedback loops that are crucial for success today.</p>
<p>Yet, despite all of these advances, procurement seems to be stuck in a different era. We’re still seeing multimillion-dollar contracts tied up in exhaustive requirements documents, where every feature and interaction is meticulously defined upfront—as if locking everything down early will somehow eliminate the risk.</p>
<p>It doesn’t.</p>
<p>It just shifts the risk to later stages, when adapting to real-world changes becomes exponentially harder and costlier. Incremental delivery—the very thing that has transformed the rest of the industry—is somehow still out of reach for these large, structured organisations.</p>
<p>And so, I offer this apology. I’m sorry my industry has led us here.</p>
<p>For years, we—the software industry—have worked to deliver systems exactly to spec. We’ve played the game of exhaustive requirements gathering, painted a picture of the future that was, frankly, based on nothing more than educated guesses. The truth is, we didn't know what the system would truly need to do any more than you did. But we soldiered on, delivering software that met all the requirements...while often missing the point.</p>
<p>The world has changed. It’s no longer enough to deliver a system that simply matches what was written down months or years ago. Why? Because by the time we get there, the world—and your organisation—has evolved. Markets shift, technologies advance, and user needs transform. Working software delivered quickly, and that can be adapted quickly, is far more valuable than a solution that ticks every box on a static spec sheet.</p>
<p>This is why I’m here, with open arms, inviting us to move forward together.</p>
<p>Imagine a procurement process focused on outcomes, not a rigid list of requirements. Imagine starting small—delivering incrementally, reducing the risk for both sides. Each small delivery is a chance to learn, to adjust, to refine. The goal isn’t to create a product that was once envisioned; it’s to build a product that works in today’s world and for tomorrow’s challenges.</p>
<p>The measure of quality should no longer be &quot;built to spec&quot; but &quot;cheap to change.&quot; Software that is adaptable, flexible, and ready to evolve is the hallmark of successful projects in this rapidly changing world. When we embrace incremental delivery, we enable continuous discovery. We create space for innovation. And we reduce the enormous waste that comes from managing risk by locking down every detail too early in the process.</p>
<p>So, let's rethink this approach together. Yes, we’ve modelled it poorly in the past, but we’ve learned. Now, it’s time for private businesses, startups, government departments, universities, councils—everyone involved in procurement—to join us in a new journey. A journey where we focus on outcomes, on delivering value incrementally, and on embracing the fact that in today’s world, the only constant is change.</p>
<p>Let’s write a new chapter—one that allows us to work together to build not just what was asked for, but what is truly needed. Are you up for it?</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Gen X meets AI</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lqkacxzmkdcy8mgavz82ov0j" alt="" data-big=https://cms-assets.abletech.nz/large_1728771625815_823c4531d3.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1728771625815_823c4531d3.jpeg" srcset="https://cms-assets.abletech.nz/large_1728771625815_823c4531d3.jpeg 1000w, https://cms-assets.abletech.nz/small_1728771625815_823c4531d3.jpeg 500w, https://cms-assets.abletech.nz/medium_1728771625815_823c4531d3.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1728771625815_823c4531d3.jpeg 245w" data-zooming-width="1000" data-zooming-height="562" loading="lazy" width="1000" height="562"></figure>
</div>
<p>I've launched a podcast! Well, kind of...</p>
<p>I am of a certain age that means I knew life before the interwebs.</p>
<p>Cutting edge technology in my childhood was a wee bell on a 45 record telling me it was time to turn the page of the accompanying book. (The Ugly Duckling, The Legend of Finn McCool!)</p>
<p>But over the years I have been, not an early adopter, but certainly an early majority adopter of new innovations, followed by an evangelism on their behalf.</p>
<p>The number of people I have shared my story with about buying my first ever US stock market share, on my phone, on the backseat of the bus, on the way to work, because Nat Fergusson had finally convinced me it was dead easy. And she was right!</p>
<p>That first stock was Tesla and the bus was the #14.</p>
<p>Whilst Tesla hasn't been an easy hold for many reasons, one beginning with E being the main culprit, nevertheless it started a love affair with DIY portfolio management, one that still excites me as recently as 3am Saturday morning. (buying the inevitable Tesla dip, post a Tesla product event..not financial advice!)</p>
<p>One of my <a href="https://www.linkedin.com/company/able-technology/" target="_blank" rel="noopener noreferrer">Abletech</a> colleagues is founder and director Nigel.</p>
<p>Nigel is an early adopter and the world so needs people like Nigel.</p>
<p>As he was leaving one night, we did our usual &quot;solve the problems of the world in 5 mins&quot; chat, within which he mentioned Google's NotebookLM.</p>
<p>Nigel's 3 word description of NotebookLM &quot;it's just amazing&quot;. This was enough to pique some interest.</p>
<p>We had just had a heavy run of RFPs where each was different enough to require different write ups for the company profile and approach. As painful as it often is, with each new RFP we do end up with new assets. The challenge of course is how to catalogue, tag and summarise them to ensure we don't lose them and can re-use it all.</p>
<p>Faced with the labourious task of having to make sense of and catalog our genius, the delivery team decided to have a wee crack using NotebookLM.</p>
<p>So we loaded in 3 RFPs, asked some questions and boom, within seconds, out came some beautifully summarised notebooks, each with citations back to the source documents.</p>
<p>Buoyed with excitement, we loaded a few more in to answer a few more questions about ourselves and more nuggets of gold came to the surface. All our own words, but beautifully summarised, cataloged and all with citations.</p>
<p>I cannot begin to explain the time this has saved and how friggin beautifully organised it all is.</p>
<p>Fast forward to Saturday arvo. I was in the sun room at home supping on a beer of danger. I happened across an article by Mike &quot;MOD&quot; O'Donnell about the huge popularity of NotebookLM, especially the podcast feature. Its actual title is &quot;Google kills the podcast industry as the world stampedes to NotebookLM | The Post &quot;.</p>
<p>He was talking about the rapid uptake of NotebookLM, especially with peeps creating podcasts. A simple loading of text based content can result in a Podcast that has two &quot;hosts&quot; talking about the content.</p>
<p>So curious as I loaded my Linkedin post from a few weeks ago in, I waited 5 mins max and boom, a podcast was delivered with my Linkedin post the theme of the conversation.</p>
<p>Knowing it's made up didn't dim the excitement of having my thoughts and theories spoken back to me - it was a weird kind of ego trip to be honest!</p>
<p>But, they got a few things wrong:</p>
<ul>
<li>
<p>I wasn't young when I became a junior BA - it was my second career</p>
</li>
<li>
<p>I didn't go to university - my highest formal educational achievement is the certificate I got at Stratford Primary School for the 50 meters flutterboard</p>
</li>
<li>
<p>I had to listen twice to understand they were referring to Te Awanui Reeder</p>
</li>
<li>
<p>And don't get me started on the mangling of very basic Te Reo</p>
</li>
</ul>
<p>And it was a wee but cringey as they attempted humour in that very American way.</p>
<p>Whilst I'm sure I can edit the factual errors, if I don't, will these become the interwebs source of truth about me? Do I really want to be represented by two made up people, just because I find myself interesting and funny, will others, why is the word Māorii such a challenge still to such amazing technology.</p>
<p>But still - it was fascinating and the way it was able to create a conversation and interpret my post was, as Nigel said, just amazing.</p>
<p>The speed is just mind boggling.</p>
<p>For all of Elon's very many faults, he is right in that technology should be giving us time back.</p>
<p>But as MOD says in his article, this is another example of a fascinating but slippery slope we are needing to make sense of.</p>
<p>So have a listen if you are so inclined - no pressure!</p>
<p><a href="https://soundcloud.com/sooz_j/musings-of-a-gen-xer?utm_source=clipboard&amp;utm_campaign=wtshare&amp;utm_medium=widget&amp;utm_content=https%253A%252F%252Fsoundcloud.com%252Fsooz_j%252Fmusings-of-a-gen-xer" target="_blank" rel="noopener noreferrer">Sooz's Podcast</a></p>
<p>Me... I'm off to list with Spotify!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Musings from a Gen-Xer</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>I came across a post on the socials during a doom scrolling session the other night. Picture below for eventual context. The author had been to a talk by a LinkedIn company exe (I think) and posted their key takeaways from the talk, which really resonated.</p>
<p>One of the benefits of growing older, aside from the extra chin hair and having the telly volume up to 40 (but still needing to ask what are they saying), is that there comes a point when what is behind you is longer than what is ahead of you (deep, frightening).</p>
<p>But that provides a rich history and tapestry of experience to tap into, to put what is happening now into context and perspective.</p>
<p>Back in the day <a href="https://www.linkedin.com/in/yvonne-daymond-0bb8141/" target="_blank" rel="noopener noreferrer">Yvonne Daymond</a> , <a href="https://www.linkedin.com/in/sallyringwood/" target="_blank" rel="noopener noreferrer">Sally Ringwood</a> and I were young earnest junior BAs, fresh from the frontline, on a huge project for “the Ministry&quot;. BAs and the business were on one floor and the developers were on another - never to engage over our badly but enthusiastically written use cases (yes I'm that old) that were tossed over the fence.</p>
<p>This particular day we were working away and we suddenly became aware of a presence, an unusual temperature had fallen over the room, we looked up and there was a developer.</p>
<p>Not close enough to trigger a “hi”, but in that kind of menacing lurking zone.</p>
<p>He was a classic type that was around in those days - glasses, face full of hair, stained trousers, white crumpled shirt, with the plastic pocket liner that hadn't been able to contain the inevitable leak from his pens.</p>
<p>He just stood there staring, no doubt wondering what the hell we were, and who had let us out of the provinces (Taranaki, Hawkes Bay, Wanuiomata) and how we could ask for such ridiculous things in our requirements. (A1 - The system retrieves for the user all the stuff they have to manually retrieve now from 84 different systems)</p>
<p>As he just silently stood there we tried to play it cool and just kept up the pretense of working, with Yvie side mouthing in a semi panicked way &quot;has he gone yet, has he gone yet&quot;.</p>
<p>And just like that, he was gone, never to be seen again. Never asked a thing about our ridiculous requirements, our frontline experience and needless to say the project never got implemented.</p>
<p>Fast forward to 24 hrs ago and it was me approaching a developer who probably doesn't even know what a pen is, and is bamboozled everytime I use the phrase “starter for 10”.</p>
<p>We needed some clarification around some feedback the client had sent us, so asked the dev what he wanted to do, his response was - I will jump on a call with them and have a chat - can show them what we have at the mo and we can see what bits need tweaking.</p>
<p>Do you need me in that chat? I asked, no all good was the reply, happy to take this.</p>
<p>As we have progressed through the years, the pace at which things happen, especially in IT, has sped up beyond belief. What used to take years can now take minutes. The benefit for clients is obvious but the downside is that the wrong things can be built incredibly quickly.</p>
<p>Having a dedicated and competent BA is now a luxury, so a lot of the core BA competency set now has to sit in others.</p>
<p>At Abletech we have a wonderfully engaged, curious and communicative group of developers who are not only able to do the coding, but also the discovery and the critical ongoing analysis tasks. Doing these things well is due to them enacting the fundamental skill of communicating through asking questions and more importantly listening to the answers.</p>
<p>They have a genuine desire to understand the business domain they are working in, as this provides a richness of context for them to deliver the needs of the business, through their beautifully written, AI supported code.</p>
<p>In the early days when we were partnering with Te Awanui Reeder for Koha.Kiwi, all i could hear coming out of the meeting room was Awa repeatedly saying yeah you get it, you get it - he was in the room with the devs, and there wasn't an open laptop in sight. They were talking about tikanga and some of the nuances of Te Ao Māori concepts that would eventually find their way into the code for the Koha solution.</p>
<p>So with all the advancements in IT, I truly believe that it is those fundamental human skills that will keep our clients safe and happy, and that will keep our devs growing into even more amazing human beings.</p>
<p>It is talent that will win the race.</p>
<p>And it needs to be all about Curiosity, Creativity, Communication, Courage and Compassion.</p>
<p>And it's now me with the full face of hair, and a secret desire for a plastic pocket liner.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t8ipu2dabx8v86s40jm4ggxn" alt="" data-big=https://cms-assets.abletech.nz/large_1724973027821_6c0ea0de25.jpeg sizes="(min-width: 1280px) 673px, (min-width: 768px) 336px, (min-width: 1024px) 505px, (min-width: 640px) 105px" src="https://cms-assets.abletech.nz/1724973027821_6c0ea0de25.jpeg" srcset="https://cms-assets.abletech.nz/large_1724973027821_6c0ea0de25.jpeg 673w, https://cms-assets.abletech.nz/small_1724973027821_6c0ea0de25.jpeg 336w, https://cms-assets.abletech.nz/medium_1724973027821_6c0ea0de25.jpeg 505w, https://cms-assets.abletech.nz/thumbnail_1724973027821_6c0ea0de25.jpeg 105w" data-zooming-width="673" data-zooming-height="1000" loading="lazy" width="673" height="1000"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>How can we guard against CrowdStrike</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>As the dust settles on the CrowdStrike event, it highlights the juxtaposition of combating security issues with both third party Agents / automated software update systems and trying to keep our servers patched up and secure.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ujbn53uudsode029cycwsmqj" alt="" data-big=https://cms-assets.abletech.nz/large_falcon_laser_ed38512f6b.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/falcon_laser_ed38512f6b.png" srcset="https://cms-assets.abletech.nz/large_falcon_laser_ed38512f6b.png 1000w, https://cms-assets.abletech.nz/small_falcon_laser_ed38512f6b.png 500w, https://cms-assets.abletech.nz/medium_falcon_laser_ed38512f6b.png 750w, https://cms-assets.abletech.nz/thumbnail_falcon_laser_ed38512f6b.png 245w" data-zooming-width="1000" data-zooming-height="456" loading="lazy" width="1000" height="456"></figure>
</div>
<div style="text-align: right; font-size: 8pt">Image: Abletech 2024(c)</div>
<p>Any automated patch system that is changing the underlying behaviour and has the ability to affect the behaviour of the applications we spend time crafting and that are so critical to the operation of your business.</p>
<p><strong>However</strong>, applying best practices across our software development lifecycle has many safeguards to protect us from incidents like this - we just have to be sure we don't shortcut them as we jump on board with some vendors fancy toolset in the name of &quot;protecting us&quot; - for a service fee.</p>
<ol>
<li>Utilising <strong>infrastructure as code</strong>, which is a key enabler to repeatable infrastructure build/recreation processes.</li>
<li>Ready access to a <strong>high functioning support team</strong> able to investigate infrastructure issues, apply patches and rebuild infrastructure. This is not phone support to a team of ticket takers, but actually software engineers who can diagnose and resolve issues.</li>
<li>Always think about dependencies you are introducing and minimise them.</li>
<li>Use <strong>CI/CD pipelines</strong> to ensure updates/patches can be rolled out systematically across environments.  Your pipelines should be backed by high coverage automated testing processes.</li>
<li>Use of both containerisation and/or virtualisation is key to bundle updates and rolling out change-sets between environments.</li>
<li>Use of <strong>cloud native services</strong> such as DynamoDB, Lambda and other infrastructure light / cloud native services. Removing servers that you manage can reduce your footprint and exposure.</li>
<li>Ensure you have <strong>High observability and monitoring</strong> channels so you can &quot;see&quot; what is happening - critical to get an understanding when things go wrong.</li>
<li>Ensure patches are rolled out to &quot;pre-production&quot; environments first so you can fully test the integration of your server. Zero-day patches need to be rolled out in timely fashion so managing the risk/timeliness here is crucial. Security oriented operating systems such as CentOS allow patches to flow from non-production to production in a managed process so we can be sure our automated testing catches any issues before they hit production.</li>
</ol>
<p>These practices are not new but should reduce your exposure and improve your system recovery should (or when) this plays out again.</p>
<p>Get in touch to discuss your unique requirements and how you can better prepare yourself for resilience in the ever increasing connected world our systems operate in.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Regular Expressions - 2 tips for maintainability</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p><strong>This transcript is from our monthly internal Tech Talks sessions - presented by Nigel Ramsay.</strong></p>
<p>Today I'm talking about regular expressions, and I've got two tips for maintainability. These are two learnings that Santosh and I discovered as part of our work with <a href="https://addressfinder.com" target="_blank" rel="noopener noreferrer">Addressfinder</a>, enhancing the address verification algorithms for Australia.</p>
<p>The two tips are the multi-line option and named captures. I'll give some examples, and I'll talk about how it’s used.</p>
<video controls="">
  <source src="https://cms-assets.abletech.nz/regular_expressions_2_tips_for_maintainability_compressed_b0d0eb4799.mp4" type="video/mp4">
</video>
<p>So first up I've got a regular expression which is going to be our base regular expression. This is a regular expression that did exist, in source code. And we've pulled it out, it’s been tweaked a little bit for the presentation.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="i2tkvb8cjb6148n4opkye3j7" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_3_40638f53d4.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_3_40638f53d4.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_3_40638f53d4.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_3_40638f53d4.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_3_40638f53d4.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>It's trying to identify, a particular type of phrase that's within some text. And in this example, it's trying to identify a &quot;unit type&quot; and then some numbers. So an example that might be <code>Unit 17</code> or <code>Unit 205</code> or <code>Flat 19</code> or <code>Flat 19a</code> .</p>
<p>I show an example here where there's some sample text <code>nigel ramsay    unit     44    hahaha</code>.</p>
<p>If you've ever worked with me, you'll recognise &quot;hahaha&quot;. This regular expression at the moment does extract out those elements, but it's a bit hard to read. I'm not sure about the non-technical people here, you're probably thinking &quot;I wonder what's going on here&quot;. It's more complicated - I think this is actually classic of regular expressions in general, especially when you encounter a regular expression that another person has written, and you're looking and thinking &quot;I wonder what this is about&quot;. And, you know, there's lots of like special control characters and that sort of thing. We do recognise some of them, but it takes quite a bit of brain power to work it out.</p>
<h2>Tip 1: the multi-line option</h2>
<p>Let's have a look at the multi-line option.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="gso0y0vrto84zi4yt8ixtxq0" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_4_cd0d7eded7.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_4_cd0d7eded7.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_4_cd0d7eded7.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_4_cd0d7eded7.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_4_cd0d7eded7.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>To add this, you add a &quot;slash x&quot; on the end of your regular expression. And what that does is it tells the regex engine to ignore all whitespace.</p>
<p>Here's an example, I've taken the previous example and I've appended a &quot;slash x&quot; on it.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="j25hf7gr420orylaxlf6adbg" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_5_1375870930.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_5_1375870930.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_5_1375870930.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_5_1375870930.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_5_1375870930.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>And you can see we can, you can get the regex to flow all across multiple lines. So one thing you have to be aware of is that back in that previous example, we did have some important whitespace in here. So we need to keep an eye on that.</p>
<p>I've just converted those space characters from the previous version into a <code>\s</code>. I know that <code>\s</code> is a bit more generous than just a space character, but that's fine for our purposes. So what that lets us do is have the important elements on separate lines. So it's quite a lot easier to read, which is great.</p>
<h3>Embedding comments into the regular expression</h3>
<p>And then you can also put comments against each line. The comments get ignored by the regex parser.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="aujbn029p396g6yi3uacmg4b" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_6_4b3ccaaa73.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_6_4b3ccaaa73.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_6_4b3ccaaa73.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_6_4b3ccaaa73.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_6_4b3ccaaa73.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>For example, if we talk about that regular expression, there's a leading prefix - which this <code>.*</code> represents. And then we've got a series of unit types, such as &quot;unit&quot;, &quot;apartment&quot; or &quot;shop&quot;. In the real example, there's actually about 20 or 30 of them.</p>
<p>Then, there's a mandatory space. Followed by some digits or letters <code>\w+</code>. And I give an example of &quot;10b&quot;, for example. Then some more optional white space and then anything else.</p>
<p>So you can see you can have multiple line comments as well if you want to. There's plenty of room to make it that much easier for like the next developer who's looking at your code, to have a look at it, and not to spend a lot of time thinking about it.</p>
<h3>Iterative regular expression development with Rubular.com</h3>
<p>There's a website called <a href="https://rubular.com" target="_blank" rel="noopener noreferrer">rubular.com</a> which allows you build up regular expressions, interactively and try them out with real examples.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kq6xmzjb9x1cue1ftk92ti0o" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_7_b537d2b3a5.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_7_b537d2b3a5.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_7_b537d2b3a5.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_7_b537d2b3a5.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_7_b537d2b3a5.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>You can see here that I've taken that code and copied and pasted it into into the box. Now on Rubular, normally this box is a single line, but the text field has an expansion control that you can grab and you drag down, expanding to have multiple lines. You paste it right in there and it works, and it's identifying the matches.</p>
<h2>Tip 2: named captures</h2>
<p>What is a capture?</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q6py1fokqjft398a31a5bfrp" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_8_4d489fda80.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_8_4d489fda80.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_8_4d489fda80.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_8_4d489fda80.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_8_4d489fda80.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>Captures allow you to extract elements from a string, and they're normally numbered. So in the previous example we had brackets around each one of those elements - the brackets indicate it's a capture. So when you pass a string through the regular expression, you pull out a portion of that string, one part at a time.</p>
<p>And normally you have to pull them out with an index. So the first one is &quot;index one&quot;, the second is &quot;index two&quot;, the third is &quot;index three&quot;. But named captures allow you to write labels, rather than extracting by number. You can say, this capture here is going to be called the &quot;prefix&quot;. And, this other capture is going to be called the &quot;suffix&quot;.</p>
<p>It's one step toward making it like a little bit easier.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sa2m8m841pvwp79juxdnjr2t" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_9_9ebee82548.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_9_9ebee82548.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_9_9ebee82548.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_9_9ebee82548.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_9_9ebee82548.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>So I've taken the previous example and I've added some code to show how it's being used. You can see that I'm kind of pulling out the portions. So for example, the &quot;unit type&quot; and &quot;identifier&quot; is match number two and match number three. And that matches the dark pink colour up above.</p>
<h3>How to define a named capture</h3>
<p>So, let's try out a named capture. How do we make a name capture?</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tqijcr855mr6k0704npz8wn5" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_10_5a05658eb6.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_10_5a05658eb6.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_10_5a05658eb6.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_10_5a05658eb6.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_10_5a05658eb6.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>We take the brackets that we previously had, we pull them apart and add question mark, as well as label between greater than, and less than signs.</p>
<p>Let's look at an example - I've taken that previous regex and I've named the &quot;prefix&quot;, &quot;unit type&quot;, &quot;unit identifier&quot; and &quot;suffix&quot;.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pe2bq6ius3xaxd2uoshhah2u" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_11_3792d940e3.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_11_3792d940e3.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_11_3792d940e3.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_11_3792d940e3.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_11_3792d940e3.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>Now the cool thing about the labels is that I've been able to truncate my comments a little, as the label is providing part of the comment about what's going on. I don't have to make mention of the prefix in the comment, as the label is now indicating that it's the prefix.</p>
<p>So here's the code example of this being used:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="gbjninm30ytz6xr870xa6az6" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_12_7a39227ee6.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_12_7a39227ee6.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_12_7a39227ee6.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_12_7a39227ee6.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_12_7a39227ee6.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>We can now kind of pull out the different elements as they are named. It's a little easier to read. Like in the first tip, it just makes it a little easier for the next person who's maintaining your code.</p>
<p>If you're doing some maintenance of the regular expression, and what was &quot;index one&quot; is now &quot;index two&quot; because you added an additional capture in the middle. These labels will stay the same, which is great.</p>
<h3>Named captures are visible with Rubular.com</h3>
<p>Finally, these named captures are also compatible with Rubular.com.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l7cnpaysz09dxxidfpcvf2x5" alt="" data-big=https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_13_416ae6dd00.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Regular_expressions_two_tips_for_maintainability_slide_13_416ae6dd00.png" srcset="https://cms-assets.abletech.nz/small_Regular_expressions_two_tips_for_maintainability_slide_13_416ae6dd00.png 500w, https://cms-assets.abletech.nz/medium_Regular_expressions_two_tips_for_maintainability_slide_13_416ae6dd00.png 750w, https://cms-assets.abletech.nz/thumbnail_Regular_expressions_two_tips_for_maintainability_slide_13_416ae6dd00.png 245w" data-zooming-width="750" data-zooming-height="422" loading="lazy" width="750" height="422"></figure>
</div>
<p>You can see the capture groups are now displayed with labels. You can see the prefix, the unit type, the unit identifier, and the trailing suffix.</p>
<h2>Summary</h2>
<p>In summary, the multi-line option with <code>/x</code> gives you like improved readability. You can define inline comments, on multi lines as well.</p>
<p>The name captions allow you to extract data without needing the offsets. They contribute to making the documentation a little bit better.</p>
<p>And finally, using <a href="https://rubular.com" target="_blank" rel="noopener noreferrer">Rubular.com</a> makes working with regular expressions awesome.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Heart Health Awareness Month and AED Locations</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>February marks Heart Health Awareness Month and a good time to give Abletech’s not-for-profit community project AEDLocations a plug.</p>
<p>AEDLocations empowers its users to be potential life savers. The app works using a location database with over 17,000 defibrillator locations. The nearest AED’s will be displayed on a map with a list ordered by proximity and availability. Once the location is selected, details about the device and also directions can be shown.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="b5inm2e77vfpje0eoxfmedbs" alt="" data-big=https://cms-assets.abletech.nz/small_aed_at_abletech_77e767a530.png sizes="(min-width: 768px) 500px, (min-width: 640px) 198px" src="https://cms-assets.abletech.nz/aed_at_abletech_77e767a530.png" srcset="https://cms-assets.abletech.nz/small_aed_at_abletech_77e767a530.png 500w, https://cms-assets.abletech.nz/thumbnail_aed_at_abletech_77e767a530.png 198w" data-zooming-width="500" data-zooming-height="394" loading="lazy" width="500" height="394"></figure>
</div>
<p>As well as many publicised instances where the AEDLocations app has been used to save lives, our own Business Development Manager, Jaron Marsh, had his own personal experience at a Christmas event when someone collapsed without warning. With knowledge of the app, this led Jaron to quickly and calmly respond and locate the nearest defibrillator just around the corner at the fire station. Knocking on their door triggered a team of first responders to arrive on site with their AED (to the surprise of the people still on the phone to 111) and within minutes they had attended to the person.</p>
<p>Does your business have a defibrillator available? Is it registered in the AEDLocations database? If not you can add it <a href="https://aedlocations.co.nz/form/" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>Download the <a href="https://apps.apple.com/nz/app/aed-locations/id424094430" target="_blank" rel="noopener noreferrer">iOS app</a>, the <a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations" target="_blank" rel="noopener noreferrer">Android app</a> or run <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">directly from the browser</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>A sentiment engine using ChatGPT's completions API</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>OpenAI's completions API opens up endless possibilities to integrate powerful analysis of text based data and convert it to a structured response, suitable for processing by an application.</p>
<p>We have recently had a number of examples where we've been able to leverage this technology. One task was to determine the sentiment of someone's comment before displaying that comment on a public website.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="f86xxh12zal9sphpf7d3caad" alt="AI image" data-big=https://cms-assets.abletech.nz/large_brain_49a4ca36ef.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/brain_49a4ca36ef.jpeg" srcset="https://cms-assets.abletech.nz/large_brain_49a4ca36ef.jpeg 1000w, https://cms-assets.abletech.nz/small_brain_49a4ca36ef.jpeg 500w, https://cms-assets.abletech.nz/medium_brain_49a4ca36ef.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_brain_49a4ca36ef.jpeg 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<p>Traditionally, you may have been inclined to do this with an ever growing stop/allow list. This task has now been trivialised by the use of OpenAI's API.</p>
<p>Consider an <strong>Obituary</strong> where users can add <strong>thoughts and comments</strong> onto a <strong>public website</strong>.  We need to weed out any offensive, derogatory or inappropriate comments before posting the comment on the Obituary page.</p>
<p>We also need to structure the result so that it is easily processable by the consuming application.</p>
<p>A simple script can be written to complete this task using the ChatGPT's Completions API.</p>
<pre><code class="hljs language-python">comment = args[<span class="hljs-number">1</span>] <span class="hljs-comment"># users comment to be published</span>
result = openai.ChatCompletion.create(
    model=model_id,
    messages = [
        {<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: comment},
        {
            <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>,
            <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;The context is an Obituary
              displayed on a web page. Any one can
              add a comment onto the persons obituary.
              You are to determine if the comment is
              safe, appropriate and respectful to
              display on the website.&quot;&quot;&quot;</span>
        },
        {
            <span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;system&quot;</span>,
            <span class="hljs-string">&quot;content&quot;</span>: <span class="hljs-string">&quot;&quot;&quot;Please output a JSON document
              with keys `publish` and `reason`. The
              `publish` field will contain the boolean
              true or false, and the reason will be why
              it qualifies or not for the public website&quot;&quot;&quot;</span>
        },
    ]
 )
<span class="hljs-built_in">print</span>(result.choices[<span class="hljs-number">0</span>].message.content)
</code></pre>
<p>With the user comment</p>
<blockquote>
<p>Rest in peace</p>
</blockquote>
<pre><code class="hljs"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;publish&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;reason&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;The comment is safe, appropriate, and respectful.&quot;</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>ChatGPT has an impressive capability to understand language semantics, evaluate the context and provide a value enhanced system result.</p>
<p>Experimentation is key to engineer the prompt in such a way that produces the best result. The more data you can provide in the context, the better the result.</p>
<p>Consider the following comment:</p>
<blockquote>
<p>I am glad you lived a long and beautiful life</p>
</blockquote>
<p>While this may be appropriate for an aged adult, it would be inappropriate for a child. ChatGPT was able to handle situations like this by adding the persons age into the context provided.</p>
<p>We can solve this by providing the subjects age into the system context as follows:</p>
<pre><code class="hljs"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;role&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;system&quot;</span><span class="hljs-punctuation">,</span> 
  <span class="hljs-attr">&quot;content&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;The person was {age} years old&quot;</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>We were able to engineer ChatGPT to provide the desired response:</p>
<pre><code class="hljs">{
   <span class="hljs-comment">&quot;publish&quot;</span>: <span class="hljs-keyword">false</span>,
   <span class="hljs-comment">&quot;reason&quot;</span>: <span class="hljs-comment">&quot;The comment is inappropriate as it refers to the
     deceased as a young child, implying that their life was cut short.
     Furthermore, it is not respectful to speculate on the length or 
     happiness of someone&#x27;s life based solely on their age.&quot;</span>
}
</code></pre>
<blockquote>
<p>&quot;Whenever you are dealing with unstructured data sources (images, pdfs, sound, text) AI can play a part in simplifying your solution. &quot;
Marcus Baguley</p>
</blockquote>
<p>This is just a taster to help you think about the data you are processing and how that might change using generative AI and emerging API capabilities. The are many use cases you can apply this solution to.</p>
<p>Please <a href="https://abletech.nz/contact/">chat to us</a> if you want to know how you can best leverage AI technologies to solve your business problems.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Getting answers from CloudWatch Logs Insights</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>CloudWatch Logs Insights enables you to interactively search and analyse your log data in Amazon CloudWatch Logs.</p>
<h2>1. Why use CloudWatch Logs Insights?</h2>
<ul>
<li>Easy integration with other AWS services</li>
<li>Automatic field discovery from JSON logs</li>
<li>Purpose-built query language</li>
<li>One less service to integrate</li>
</ul>
<p>AWS services can be configured to ship their logs to CloudWatch Logs. That makes Insights a sensible choice without having to worry about setting up additional services.</p>
<p>Many log services have an overly simplified query language or a painful to use, slow UI. CloudWatch insights's query language is powerful enough to ask complicated questions of it.</p>
<h2>2. Capabilities</h2>
<p>CloudWatch Logs Insights automatically discovers JSON keys in lines logged from Lambda logs. It works by searching for the first JSON fragment in each log event.</p>
<p>If a Lambda log event contains multiple JSON fragments, you can manually parse and extract the log fields by using the <code>parse</code> command, which you can read more about <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html" target="_blank" rel="noopener noreferrer">here</a>. We will assume for the examples below there is a single JSON fragment.</p>
<p>The <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html" target="_blank" rel="noopener noreferrer">query language</a> is lightweight. We will run through a couple of the key capabilities:</p>
<ol>
<li>Filter</li>
<li>Stats</li>
<li>Sort and Limit</li>
<li>Display and Fields</li>
</ol>
<h3>Quick and dirty examples</h3>
<p>I love to give examples on what the tool looks like early. This will give the rest of the information context.</p>
<div class="image-wrapper" style="height: min(Infinity, calc(100vw / 0)">
<figure><img src="https://cms-assets.abletech.nz/insights_screen_c519f33720.png" alt="Insights query page" loading="lazy"></figure>
</div>
<p>The way it works,</p>
<ol>
<li>
<p>You select your Log Groups, as many as you want.</p>
</li>
<li>
<p>Check your date range up the top. Start small, like the last <code>1h</code> of logs, to save on cost while refining your query (more on that below).</p>
</li>
<li>
<p>Start with a simple query to get an idea of what you are looking at, like:</p>
<pre><code class="hljs"><span class="hljs-attribute">limit</span> <span class="hljs-number">20</span>
</code></pre>
</li>
<li>
<p>Refine the query from there, adding commands like <code>filter</code>, <code>stats</code> and <code>sort</code>.</p>
</li>
</ol>
<p>Here are a few finished queries to give you an idea of how they look at the end:</p>
<p><strong>Example 1</strong>: Find all logs that have a 50x error, or a log message that sounds like an error or a timeout. Aggregate the results by the <code>@log</code> group, the <code>message</code>, and <code>statusCode</code>, then count the distinct <code>sessionId</code>'s and the total count of errors in each bucket. Finally, sort by <code>totalCount</code> of errors.</p>
<pre><code class="hljs"><span class="hljs-keyword">filter</span> statusCode <span class="hljs-keyword">like</span> /<span class="hljs-number">50</span>/ <span class="hljs-keyword">or</span> message <span class="hljs-keyword">like</span> /[Ff]ailed/ <span class="hljs-keyword">or</span> message <span class="hljs-keyword">like</span> /timeout/ <span class="hljs-keyword">or</span> message <span class="hljs-keyword">like</span> /[Ee]rror/
| stats count_distinct(sessionId) <span class="hljs-keyword">as</span> sessionCount, count(*) <span class="hljs-keyword">as</span> totalCount <span class="hljs-keyword">by</span> @<span class="hljs-keyword">log</span>, message, statusCode
| sort <span class="hljs-keyword">by</span> totalCount <span class="hljs-keyword">desc</span>
</code></pre>
<p><strong>Example 2</strong>: Display the error and statusCode for log lines that have an <code>error</code> key in the first JSON fragment of the log line.</p>
<pre><code class="hljs">fields <span class="hljs-keyword">error</span>, statusCode
| <span class="hljs-keyword">filter</span> ispresent(<span class="hljs-keyword">error</span>)
</code></pre>
<p><strong>Example 3</strong>: Display the entire log, and timestamp, of the most recent 20 log lines. Remember this is scoped to the time range that you specify in the UI.</p>
<pre><code class="hljs">fields <span class="hljs-meta">@timestamp,</span> <span class="hljs-meta">@message</span>
|<span class="hljs-string"> sort @timestamp desc
</span>|<span class="hljs-string"> limit 20 
</span></code></pre>
<p>See the <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html" target="_blank" rel="noopener noreferrer">official documentation on AWS</a> for more examples.</p>
<h3>Filter</h3>
<p><strong><code>filter</code></strong> is the bread and butter of a query. It uses SQL-esqe terms such as <code>like</code> and <code>not like</code>. You can use regular expressions or text matches.</p>
<p>Examples:</p>
<ol>
<li>
<p>Filter out all terms in the first JSON object, that have the key &quot;<code>message</code>&quot; and whose value matches the regex <code>/Recaptcha response/</code>.</p>
<pre><code class="hljs">filter message not like <span class="hljs-symbol">/Recaptcha</span> response<span class="hljs-symbol">/</span>
</code></pre>
</li>
<li>
<p>You can add multiple queries using <code>and</code></p>
<pre><code class="hljs">filter <span class="hljs-keyword">message</span> <span class="hljs-keyword">like</span> /Recaptcha Failed/  <span class="hljs-keyword">and</span> <span class="hljs-keyword">message</span> <span class="hljs-keyword">like</span> /timeout-<span class="hljs-keyword">or</span>-duplicate/
</code></pre>
</li>
<li>
<p>You can daisy-chain filter usage. The below query is the same as the above one.</p>
<pre><code class="hljs"><span class="hljs-keyword">filter</span> message <span class="hljs-keyword">like</span> /Recaptcha Failed/
| <span class="hljs-keyword">filter</span> message <span class="hljs-keyword">like</span> /timeout-<span class="hljs-keyword">or</span>-duplicate/
</code></pre>
</li>
</ol>
<h3>Stats</h3>
<p><strong><code>stats</code></strong> is how multiple lines are aggregated into a sensible summary. Within <code>stat</code> you can perform SQL-esqe <code>count</code>'s and a grouping using <code>by</code>.</p>
<p>Examples:</p>
<ol>
<li>
<p>Count the number of distinct <code>requestId</code>'s in the logs</p>
<pre><code class="hljs"><span class="hljs-variable">stats</span> <span class="hljs-function"><span class="hljs-title">count_distinct</span>(<span class="hljs-variable">requestId</span>)</span>
</code></pre>
</li>
<li>
<p>Combine the above with a filter</p>
<pre><code class="hljs"><span class="hljs-variable">filter</span> <span class="hljs-variable">statusCode</span> <span class="hljs-variable">like</span> /<span class="hljs-number">50</span>/
| <span class="hljs-variable">stats</span> <span class="hljs-function"><span class="hljs-title">count_distinct</span>(<span class="hljs-variable">requestId</span>)</span>
</code></pre>
</li>
<li>
<p>Group log counts by IP address</p>
<pre><code class="hljs">stats <span class="hljs-keyword">count</span>(*) <span class="hljs-keyword">by</span> requestIP
</code></pre>
</li>
<li>
<p>Filter those counts to show those that made more that 10 log lines</p>
<pre><code class="hljs">stats count(*) <span class="hljs-keyword">as</span> request_count <span class="hljs-keyword">by</span> requestIP
| <span class="hljs-keyword">filter</span> request_count &gt; <span class="hljs-number">10</span>
| sort <span class="hljs-keyword">by</span> request_count <span class="hljs-keyword">desc</span>
</code></pre>
</li>
<li>
<p>Filter all log lines to those that include the key &quot;error&quot; in the first JSON object. Display the full list of error messages with totals next to them.</p>
<pre><code class="hljs">filter ispresent(<span class="hljs-keyword">error</span>)
| stats <span class="hljs-keyword">count</span>(*) <span class="hljs-keyword">as</span> error_count <span class="hljs-keyword">by</span> <span class="hljs-keyword">error</span>
| <span class="hljs-keyword">sort</span> error_count <span class="hljs-keyword">desc</span>
</code></pre>
</li>
</ol>
<div class="image-wrapper" style="height: min(Infinity, calc(100vw / 0)">
<figure><img src="https://cms-assets.abletech.nz/error_count_32b1ad7043.png" alt="Using the stats command to group aggregate results" loading="lazy"></figure>
</div>
<p>When using the <code>stats</code> command, it is possible to group results. To do this we use <code>bin</code>.</p>
<p>Below is an example using <code>bin</code> to group results into a time series visualisation which can be graphed using the &quot;Visualisation&quot; tab.</p>
<pre><code class="hljs"><span class="hljs-variable">filter</span> <span class="hljs-function"><span class="hljs-title">ispresent</span>(<span class="hljs-variable">error</span>)</span>
| <span class="hljs-variable">stats</span> <span class="hljs-function"><span class="hljs-title">count</span>(*) <span class="hljs-variable">by</span> <span class="hljs-title">bin</span>(<span class="hljs-number">30</span><span class="hljs-variable">m</span>)</span>
</code></pre>
<div class="image-wrapper" style="height: min(Infinity, calc(100vw / 0)">
<figure><img src="https://cms-assets.abletech.nz/error_count_graph_18da2c00da.png" alt="Grouping stats results into 30 minute buckets" loading="lazy"></figure>
</div>
<h3>Sort and Limit</h3>
<p><code>sort</code> and <code>limit</code> are what they sound like.</p>
<p>With <code>sort</code> you can add <code>asc</code> or <code>desc</code> to control the order. For example, your log will likely have a timestamp. You could sort like this:</p>
<pre><code class="hljs"><span class="hljs-built_in">sort</span> @<span class="hljs-built_in">timestamp</span> desc
</code></pre>
<p><code>limit</code> is another simple one. Limits the number of log events returned. Useful to keep your bill down when trying to work out the correct query to run.</p>
<pre><code class="hljs"><span class="hljs-attribute">limit</span> <span class="hljs-number">20</span>
</code></pre>
<h3>Display and Fields</h3>
<p><code>display</code> and <code>fields</code> are similar, and the <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html" target="_blank" rel="noopener noreferrer">documentation</a> is vague as to the cost difference between them.</p>
<p>My read is that <code>fields</code> restricts the data to a subset <em>before</em> subsequent operations, which saves you money. <code>display</code> filters the results at the end of the query.</p>
<p>Examples, assuming you have a JSON fragment in your log with two keys: <code>fieldA</code> and <code>fieldB</code>:</p>
<p>Example 1: Return <code>fieldA</code> and <code>fieldB</code> where <code>fieldA</code> matches the string &quot;error&quot;,</p>
<pre><code class="hljs">fields fieldA, fieldB
| <span class="hljs-keyword">filter</span> fieldA like /<span class="hljs-keyword">error</span>/
</code></pre>
<p>Example 2: The same response as Example 1, but using <code>display</code> instead,</p>
<pre><code class="hljs">filter fieldA <span class="hljs-keyword">like</span> /<span class="hljs-keyword">error</span>/
| <span class="hljs-keyword">display</span> fieldA, fieldB
</code></pre>
<p>My interpretation is that Example 1 is more efficient, as it restricts the amount of data being operated on earlier in the query.</p>
<h2>3. Cost</h2>
<p>Pricing is based on usage. Insights' queries incur charges based on the amount of data that is queried. It's free tier is lumped in with CloudWatch logs:</p>
<blockquote>
<p>The free plan gives you 5GB Data (ingestion, archive storage, and data scanned by Logs Insights queries).</p>
</blockquote>
<p>After that you pay per GB of data Collected, Stored and Analysed.</p>
<p>For an application doing 670k API requests and generating 3M log events per day it would cost (in USD):</p>
<ul>
<li>$0.335 to ingest all the logs for a day</li>
<li>$0.0165 per day to store 1x days worth of logs. For a rolling 30 day period, this would cost 30x $0.0165 daily; roughly $0.50 per day.</li>
<li>$0.004 to run a query that scans across all logs for that day.</li>
</ul>
<p>This adds to roughly $25USD per month, with query usage billed on top of that, which might be an additional $15USD depending on your usage and your consideration towards cost when running queries.</p>
<p>Insights is powerful, but you could rack up a considerable bill if you were not mindful of the pricing structure when writing queries. It will happily process gigabytes of logs and then send you the bill after.</p>
<h2>Summary</h2>
<p>Today you have learned the surface of what is available through CloudWatch Logs Insights. It is important to consider what data your application is logging and why, then once you have have that data, make sure you use it!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Sustainability in IT business. What you can do to decarbonise</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>Most of us are onboard with the fact that humans have been overconsuming the earth's resources at an unsustainable rate. Action from a global, government, and local government level is still unsatisfactory. Everyone needs to start doing their bit, and hopefully each little bit adds up and makes a big difference.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="diknlyjrc0vkykmsxvt9j69w" alt="Think sustainability abletech" data-big=https://cms-assets.abletech.nz/large_sustainable_01_2ab8ef6124.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/sustainable_01_2ab8ef6124.png" srcset="https://cms-assets.abletech.nz/large_sustainable_01_2ab8ef6124.png 1000w, https://cms-assets.abletech.nz/small_sustainable_01_2ab8ef6124.png 500w, https://cms-assets.abletech.nz/medium_sustainable_01_2ab8ef6124.png 750w, https://cms-assets.abletech.nz/thumbnail_sustainable_01_2ab8ef6124.png 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<h2>What can you do to be a sustainable business? And what are we doing?</h2>
<p>Below is some of the low hanging fruit and other initiatives which we have been able to bring into practice as a Wellington IT Services and Product Company and play a part in New Zealand’s sustainable business community.</p>
<h3>Working Remotely</h3>
<p>Remote meetings and working remotely cuts down on travel. One positive thing that has come out of the Covid era, is the adoption of remote work. Our clients, from small businesses to large corporates to government, are all now comfortable with online meetings, and 9/10 of these function as productively as in-person meetings.</p>
<h3>Commuting</h3>
<p>Our office includes a bike room, and with showers at work there aren't many excuses not to ride to work. Encourage commuting by bike or ebike.
Encourage your team to use public transportation where possible!</p>
<h3>Server / cloud hosting</h3>
<p>One of the larger controllable carbon contributors is our server hosting. Consider your data centre's region and where the power is coming from. New Zealand for instance uses up to 82% renewable energy sources (in 2021), whereas our friendly neighbours over the ditch use only 24% renewables.
You can also consider the hardware/processors that run your software. The new AMD chips are more efficient than Intel for instance and adopting efficient silicon will be a win for the planet.</p>
<h3>Software Design</h3>
<p>Design your software to limit its footprint. Adopting shared services and on demand features of cloud computing can drastically reduce your carbon footprint. The advent of Function as a Service (FaaS) means you move from having servers sitting idle, consuming resources, to an on demand shared model, reducing your hosting costs and carbon footprint.</p>
<h3>Build efficient algorithms</h3>
<p><a href="https://addressfinder.nz/blog/technology/saving-the-planet-1024-bytes-at-a-time/" target="_blank" rel="noopener noreferrer">Lowering the cost</a> of your compute time adds up when that function is executed millions of times. Reduce internet traffic, think about image size, CSS and JS size and compression, and how those bundles are transferred and consumed across the globe - all resulting in a little bit less carbon. This can add up!</p>
<h3>Waste / Recycle</h3>
<p>Waste is where you will find ways to reduce your carbon footprint. Ensure you have, and encourage the use of, recycling facilities. Use reusable/recyclable bowls when bringing in food. Choose suppliers who also respect the environment, who also use recyclables and don’t over package their supplies.</p>
<h3>Carbon Reporting / Measuring</h3>
<p>We monitor and <a href="https://abletech.nz/article/abletech-and-co2-emissions-growing-our-understanding/">report on our carbon emissions</a> at the board level. Graphs <a href="https://abletech.nz/article/abletech-and-co2-emissions/">visualise the carbon footprint</a> of our activities and anomalies or bad trends can be picked up and discussed. If you are interested in how we are doing this, please do get in touch!</p>
<h3>Cutting costs</h3>
<p>Generally, when you are spending money, you are creating carbon. Review your laptop refresh policies and other discretionary spending budgets to see if there are any opportunities to consume less.</p>
<h3>Offsetting your carbon</h3>
<p>Once you are recording your carbon, it becomes much more real and you can identify ways to reduce your footprint. It is unlikely that you are Carbon Zero. You can look at offsetting the remainder of your footprint using carbon sink projects such as <a href="https://treesthatcount.co.nz" target="_blank" rel="noopener noreferrer">Project Crimson</a>, and <a href="https://toitu.co.nz/" target="_blank" rel="noopener noreferrer">Toitū</a>. <em>Declare a goal to be Carbon Zero by 2025!</em></p>
<h3>Participate in initiatives</h3>
<p>Get the team involved in activities that raise awareness and help the planet. We have been involved in yearly <a href="https://abletech.nz/article/world-environment-day/">tree planting initiatives</a> which help get the team involved in living and breathing our sustainability values (as well as being a great team building event).</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yd6kofahlrew6jj207eel8dr" alt="Tree Planting Image" data-big=https://cms-assets.abletech.nz/large_tree_planting_2022_ea514528b4.jpg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/tree_planting_2022_ea514528b4.jpg" srcset="https://cms-assets.abletech.nz/large_tree_planting_2022_ea514528b4.jpg 750w, https://cms-assets.abletech.nz/small_tree_planting_2022_ea514528b4.jpg 375w, https://cms-assets.abletech.nz/medium_tree_planting_2022_ea514528b4.jpg 563w, https://cms-assets.abletech.nz/thumbnail_tree_planting_2022_ea514528b4.jpg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
</div>
<p>This year we supported the amazing 2022 <a href="https://creativehq.co.nz/climate-response-accelerator/" target="_blank" rel="noopener noreferrer">Climate Response Accelerator</a> held by our partners at <a href="https://creativehq.co.nz/" target="_blank" rel="noopener noreferrer">Creative HQ</a>. The Climate Response Accelerator is a 12-week programme designed to help impact-focused entrepreneurs solve some of our planet’s most urgent challenges! Pretty cool stuff if you ask us!</p>
<h3>Supply Chain</h3>
<p>You use a bunch of services in the day-to-day running of your business. Apply your sustainability check to your clients and suppliers. We switched to<a href="https://abletech.nz/article/carbon-zero-electricity/"> a carbon zero electricity supplier</a> for example, which encourages our network to be doing the right thing. Sustainable business is efficient business. Wherever you apply sustainability thinking, savings can be found.</p>
<h3>What else can we do??</h3>
<p>Please make sure to comment on our <a href="https://www.linkedin.com/company/332451" target="_blank" rel="noopener noreferrer">Linkedin post</a> about ways you are reducing your carbon footprint so we can move forward, learn and improve together.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>2022 UX and UI in Application Development</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mj3pnumjhl0ie0zdrqcxdl3q" alt="" data-big=https://cms-assets.abletech.nz/large_irfan_simsar_wx_Wulfj_N_G0_unsplash_8f296b669f.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/irfan_simsar_wx_Wulfj_N_G0_unsplash_8f296b669f.jpg" srcset="https://cms-assets.abletech.nz/large_irfan_simsar_wx_Wulfj_N_G0_unsplash_8f296b669f.jpg 1000w, https://cms-assets.abletech.nz/small_irfan_simsar_wx_Wulfj_N_G0_unsplash_8f296b669f.jpg 500w, https://cms-assets.abletech.nz/medium_irfan_simsar_wx_Wulfj_N_G0_unsplash_8f296b669f.jpg 750w, https://cms-assets.abletech.nz/thumbnail_irfan_simsar_wx_Wulfj_N_G0_unsplash_8f296b669f.jpg 245w" data-zooming-width="1000" data-zooming-height="573" loading="lazy" width="1000" height="573"></figure>
</div>
<h2>What is UX design?</h2>
<p>User Experience, or UX, is a term that gets used (and a little abused) quite a bit. But what does it actually mean?</p>
<p>At a high level, UX is the experience a person has with a product or service.</p>
<p>Simple right? But as with many simple explanations, it encompases a fair bit. We’ll cover some of what UX design and UX research means in this article, as well as what it means for us here at Abletech and what makes it an important  part of everything we do.  Let’s get into it!</p>
<h2>The UX burrito!</h2>
<p>I think of UX like a burrito. Yes, I know, spot the Texan! But hear me out…</p>
<p>You may not really understand everything in it (like, what goes into guacamole and why is it amazing?), and by themselves, each of those ingredients is good, but put them together, roll them up, and WOW!</p>
<p>Now, while I ignore how much I want a burrito, let’s talk about what those ingredients are in UX UI design and how, when combined with the latest ux design trends,  they can nourish your project through to the end.</p>
<h2>What is the difference between UI and UX design?</h2>
<p>You might be wondering why UX designer skills are something you should care about and if it is the same thing as UI design skills. The two are different, but often-times used together, and sometimes even interchangeably.</p>
<p>In fact, in the past these two job titles were combined to be defined as simply a web designer or sometimes a digital designer. It is only our increased sophistication in modern web/digital design that has given birth to the specific disciplines of UX and UI.</p>
<img src="https://cms-assets.abletech.nz/blog_ux_ui_1_cd59f7ae91.gif" alt="gif showing stages of UX and UI development" width="600">
<p>So, if you’re as confused as a cat tryin’ to bury poop on a marble floor, you have good reason to be!</p>
<p>Don’t worry, though - we’re here to break it down for you.</p>
<h3>UX Design</h3>
<p>UX design is the art of learning about and designing around the user: what they need from the solution, and how they experience that solution.</p>
<img src="https://cms-assets.abletech.nz/user_flow_cf6ef950be.gif" alt="gif of a user flow" width="600">  
<p>Using their UX design skills, a UXer is there at the start of a discovery process, learning about the user and (while often working with technical solution leads) developing user-centric designs.</p>
<p>Throughout the design and development sprints, it is a UXers job to maintain touch points with the team and be an advocate for the user.</p>
<h3>UI Design</h3>
<p>UI design encompasses the visual appearance and functionality design. Some might say that UI design picks up where UX leaves off, taking those experiences and wireframes, then crafting the visuals to make them work for the user.</p>
<p>In order for a UI designer to bridge the gap between UX and development, they create design systems which a developer uses like a blueprint for a house.</p>
<h3>Since we’re doing food analogies</h3>
<p>If we think of UX UI like a pizza.... UX is that you’re having a pizza, the kind of pizza, crust and size! And UI is the toppings and sauce: cheese, pepperoni, anchovies, pineapple (for those who just like to watch the world burn), or a few jalapeños!</p>
<h2>What does a UX Designer actually do?</h2>
<blockquote>
<p>“User experience is like a joke; if you have to explain it, it’s a bad one” - Swiss UX/UI News</p>
</blockquote>
<p>Essentially, UX is a human-centered design approach, which means as a discipline, it’s very reliant on user research.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pzcg5qxvtyfifij9ol5rt4lc" alt="" data-big=https://cms-assets.abletech.nz/large_amelie_mourichon_sv8o_O_Qa_Ub_o_unsplash_1_c7bc538514.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/amelie_mourichon_sv8o_O_Qa_Ub_o_unsplash_1_c7bc538514.jpg" srcset="https://cms-assets.abletech.nz/large_amelie_mourichon_sv8o_O_Qa_Ub_o_unsplash_1_c7bc538514.jpg 1000w, https://cms-assets.abletech.nz/small_amelie_mourichon_sv8o_O_Qa_Ub_o_unsplash_1_c7bc538514.jpg 500w, https://cms-assets.abletech.nz/medium_amelie_mourichon_sv8o_O_Qa_Ub_o_unsplash_1_c7bc538514.jpg 750w, https://cms-assets.abletech.nz/thumbnail_amelie_mourichon_sv8o_O_Qa_Ub_o_unsplash_1_c7bc538514.jpg 245w" data-zooming-width="1000" data-zooming-height="575" loading="lazy" width="1000" height="575"></figure>
</div>
<p>A UX designer (or UX researcher) will use different user research methods to find out things like:</p>
<ul>
<li>Influences (external) to their experience</li>
<li>Goals</li>
<li>Motivations</li>
<li>Pain points</li>
<li>Technical ability</li>
<li>Quotes that summarize their feelings and thoughts</li>
</ul>
<p>Then, they use their UX skills and all of that research to keep the user voice heard throughout the design process.</p>
<p>A UX Designer will focus on aspects like:</p>
<ul>
<li>user flows (through the site, application or product) that are seamless and allow the user to easily get to the information they need</li>
<li>carefully crafted architecture and content which has exactly as much information as the user needs (because we want an uncomplicated, friction-free experience)</li>
<li>a wireframe, which is like a basic skeleton of the interface and has the most basic navigational and page information without the aesthetics like colours or fonts</li>
<li>an overall pleasant experience using the product or service.</li>
</ul>
<h2>Okay, so then what does a UI designer do?</h2>
<blockquote>
<p>“Ease of use may be invisible, but its absence sure isn’t.&quot; - IBM</p>
</blockquote>
<p>A UI designer usually crafts graphical user interfaces (GUIs), but can also create voice-controlled interfaces (VUIs), and gesture-based interfaces (in virtual reality, for example). For the sake of this article, we’ll focus on GUIs.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hea9lp1wcd793vgzunk2pxif" alt="" data-big=https://cms-assets.abletech.nz/large_ux_indonesia_8mik_J83_Lm_SQ_unsplash_1_05bc3175ad.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/ux_indonesia_8mik_J83_Lm_SQ_unsplash_1_05bc3175ad.jpg" srcset="https://cms-assets.abletech.nz/large_ux_indonesia_8mik_J83_Lm_SQ_unsplash_1_05bc3175ad.jpg 1000w, https://cms-assets.abletech.nz/small_ux_indonesia_8mik_J83_Lm_SQ_unsplash_1_05bc3175ad.jpg 500w, https://cms-assets.abletech.nz/medium_ux_indonesia_8mik_J83_Lm_SQ_unsplash_1_05bc3175ad.jpg 750w, https://cms-assets.abletech.nz/thumbnail_ux_indonesia_8mik_J83_Lm_SQ_unsplash_1_05bc3175ad.jpg 245w" data-zooming-width="1000" data-zooming-height="575" loading="lazy" width="1000" height="575"></figure>
</div>
<p>Before a UI designer begins, they have the following in their arsenal:</p>
<ul>
<li>An awareness of modern web app ui design trends and accessibility design implications.</li>
<li>The UX designers research on target user groups and web ui design that they would be most comfortable with.</li>
<li>An understanding of the brand identity of the client for whom they are designing.</li>
</ul>
<p>Then, a UI designer uses those tools to create design systems which will often include:</p>
<ul>
<li>pattern libraries</li>
<li>style guides</li>
<li>components</li>
</ul>
<p>Web application UI design also encompasses the interactivity of a site or application. That is, what happens to elements in the different states of interaction. These states may include:</p>
<ul>
<li>default</li>
<li>hover</li>
<li>focus</li>
<li>active</li>
<li>disabled</li>
<li>selected</li>
<li>expanded/collapsed</li>
<li>and combinations of these.</li>
</ul>
<h2>UX &amp; UI -It’s complicated</h2>
<p>The relationship between UX and UI can certainly have overlaps. But don’t be fooled: if you want to learn these two skill areas, you will need to understand each of them as separate disciplines. And you’ll likely need to revisit that understanding regularly because the definitions are always shifting.</p>
<blockquote>
<p>“User Experience is like relationships. It’s complicated” - Yina Li</p>
</blockquote>
<h2>In a nutshell</h2>
<p>This article merely scratched the surface of UX and UI. Both are umbrella terms that include a wide range of disciplines and skills that are valuable to anybody crafting solutions to problems which involve users.</p>
<p>These skills are hugely important to the <a href="https://abletech.nz/about/">Abletech team</a>; in fact, it is at the heart of everything we do! So please contact us to discuss your needs and how our team of UX and UI designers can help guide and support your project from idea through to fruition with a <a href="https://abletech.nz/services/guidance/#ux">custom-crafted UX strategy and lens</a>.</p>
<h2>Becoming a UX guru or a UI ninja</h2>
<p>Keen to take on one of these job titles? Or just eager to learn more about skills needed to be a UX or UI designer?</p>
<p>There is a huge amount of information available on the roles and responsibilities of UX and UI, both together and independently. However, there are many free resources available as well.</p>
<p>Some of our favourites to learn about the skills ux designers need are:</p>
<p><a href="https://www.interaction-design.org/" target="_blank" rel="noopener noreferrer">https://www.interaction-design.org/</a></p>
<p><a href="https://uxdesign.cc/" target="_blank" rel="noopener noreferrer">https://uxdesign.cc/</a></p>
<p>A couple of essential books to get a good base for your UX understanding:</p>
<p><a href="https://www.amazon.com/dp/0465050654?tag=donnorman" target="_blank" rel="noopener noreferrer">The design of everyday things by Don Norman</a></p>
<p><a href="https://www.amazon.com/Dont-Make-Think-Revisited-Usability/dp/0321965515/ref=sr_1_1?crid=5TYGE7T57CG1&amp;keywords=don%27t+make+me+think&amp;qid=1651549580&amp;s=books&amp;sprefix=don%27t+make+me+think%2Cstripbooks-intl-ship%2C264&amp;sr=1-1" target="_blank" rel="noopener noreferrer">Don't Make Me Think, Revisited: A Common Sense Approach to Web Usability by Steve Krug</a></p>
<p>And if you’re looking for some training or how to improve your ux skills, here are a few of our favourites:</p>
<p><a href="https://grow.google/certificates/ux-design/" target="_blank" rel="noopener noreferrer">https://grow.google/certificates/ux-design/</a> - Google UX Design Certificate is low cost and requires only 10 hours per week.</p>
<p><a href="https://www.nngroup.com/training/" target="_blank" rel="noopener noreferrer">https://www.nngroup.com/training/</a> - Nielsen Norman Group is very well-respected but not cheap. There are some excellent training sources here and you can work toward your UX Certification from them.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Abletech Values - Part 1</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>At Abletech we’re proud of our team’s culture. In July 2020 we ran a <a href="https://abletech.nz/article/what-makes-us-tick/">workshop to identify exactly what our team values are</a>. Everyone had a chance to speak and we collaborated to agree on four team values.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lt7dua1o05us2jsw2c7op9ts" alt="" data-big=https://cms-assets.abletech.nz/medium_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 186px" src="https://cms-assets.abletech.nz/1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png" srcset="https://cms-assets.abletech.nz/small_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png 500w, https://cms-assets.abletech.nz/medium_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png 750w, https://cms-assets.abletech.nz/thumbnail_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png 186w" data-zooming-width="750" data-zooming-height="629" loading="lazy" width="750" height="629"></figure>
</div>
<p>Values are an important part of any team culture, having a defined set of values and knowing the behaviours that align with them make it easier for any new team members to understand what is expected of them.</p>
<p>In April 2022 we checked in with the team to find out how connected to the values they felt NOW and ask if they were empowered to live them each day at Abletech. We will be using this feedback and other observations to inform a company wide refresher workshop which will run in the coming months to both celebrate our values and address any gaps.</p>
<p>This blog is a summary of the results from the April survey.</p>
<h2>Continuous Improvement</h2>
<p>Our team values continuous improvement as individuals, as  teams and in terms of the work we do. We seek out ways to use the latest technology to make our work more efficient, while growing  the skill sets and capabilities of our people and improving the ways we work as a team to deliver great outcomes to our clients. We strive to always approach our work with the aim of doing better than last time.</p>
<blockquote>
<p>&quot;At work we carry out retrospectives both internally and with clients to identify areas which haven't gone as well as they could have, so that we can work out how to improve in the next piece of work&quot;</p>
</blockquote>
<blockquote>
<p>&quot;Striving to learn new technology, ways of doing things and improving one's skill-set. Doesn't have to be tech-related either, anything to improve or strive to constantly be better.&quot;</p>
</blockquote>
<h2>Do the right thing</h2>
<p>At Abletech we are  proud of our values and try to align all the projects and clients we take on with them. We include a range of voices in our decision making processes, and aim to ensure the needs of others and ourselves are respected in the work we do. We stay honest with our clients and our people, keeping them informed of changes, increases in scope etc. We look out for each other and are ethical in how we treat people. On a more personal level, the teams are always on the lookout for opportunities to volunteer time and resources to help others.</p>
<blockquote>
<p>&quot;We don't cut corners - our code follows best practice and likewise when we represent ourselves to our clients - we won't sell them short. We strive to do what's right in terms of development and working with others.&quot;</p>
</blockquote>
<h2>Support &amp; Respect for all</h2>
<p>This means treating everyone with respect and making sure that we are making all people feel welcome at our workplace. We do this by being mindful of language used in the office and on written documents. Being aware that not everyone works or interacts in the same way, so listening to people's requests and making sure our processes allow for differences.
Making an effort to make people feel comfortable, for example, learning how to pronounce a person's name before they come in and using their correct pronouns, simple things to do that can make a world of difference.</p>
<blockquote>
<p>&quot;Basically, the golden rule - treat others as you wish to be treated. Plus, standing up for those who need it and/or can't stand up for themselves. The respect part of that statement extends to being genuine about your intentions and not using people as talking points to make yourself appear as a saviour of some sort. Be respectful by being genuine.&quot;</p>
</blockquote>
<blockquote>
<p>&quot;We're supported to be our best, ensuring our health and wellbeing is looked after and we have the tools required to complete our work. Also we look out for each other and have each other's backs on projects and when required.&quot;</p>
</blockquote>
<h2>Sustainable</h2>
<p>To us sustainability applies to both the work we do and the world around us. We run our office on <a href="https://abletech.nz/article/carbon-zero-electricity/">carbon zero electricity</a>, have a room for charging e-bikes in the office and run other initiatives to help reduce our impact on the planet. We strive to build and maintain code in a way that it's maintainable by all and built with quality at the forefront of our apps and are passionate about ensuring our team is working in a sustainable way, encouraging regular breaks and running team activities as a way of providing a mix of fun and work.</p>
<blockquote>
<p>&quot;Doing what we can to ensure this business is outworking this and actively seeking ways to do better not just for the environment, it's about how we work, longevity of our decisions with a view to the future&quot;</p>
</blockquote>
<blockquote>
<p>&quot;I walk or take public transport when I can, instead of driving. Recycling and using organisations that promote sustainability.&quot;</p>
</blockquote>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Devs for Ukraine Conference</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>On the early mornings of 26th and 27th April (4am NZ time) members of the developer teams from Abletech and Addressfinder attended the <a href="https://www.devsforukraine.io/" target="_blank" rel="noopener noreferrer">Devs For Ukraine</a> Conference, a free, online engineering conference with the goal to raise funds and provide support to Ukraine.</p>
<p>The situation in Ukraine has been ongoing for many weeks now and as news unfolds of how people are impacted by the invasion, this was an opportunity show our support, learn something new, donate, and in some small way make a difference.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="x20h58vqi6div6kja8rqnv31" alt="" data-big=https://cms-assets.abletech.nz/large_Devs_for_Ukraine_1732a39875.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Devs_for_Ukraine_1732a39875.png" srcset="https://cms-assets.abletech.nz/large_Devs_for_Ukraine_1732a39875.png 1000w, https://cms-assets.abletech.nz/small_Devs_for_Ukraine_1732a39875.png 500w, https://cms-assets.abletech.nz/medium_Devs_for_Ukraine_1732a39875.png 750w, https://cms-assets.abletech.nz/thumbnail_Devs_for_Ukraine_1732a39875.png 245w" data-zooming-width="1000" data-zooming-height="525" loading="lazy" width="1000" height="525"></figure>
</div>
<p>Starting out with a goal to raise $15K to be divided between <a href="https://www.devsforukraine.io/#ngos" target="_blank" rel="noopener noreferrer">8 Non-Governmental Organisations and Humanitarian Funds</a>, the total amount raised by the end of Day 2 was $102K.</p>
<p>If you are in a position to help financially, you can still make a <a href="https://www.devsforukraine.io/?modal=donate" target="_blank" rel="noopener noreferrer">donation here</a></p>
<p>This charity event was put together by a team of people at <a href="https://remote.com/" target="_blank" rel="noopener noreferrer">Remote</a>, and featured speakers from across the globe.</p>
<p>If you were unable to attend the conference, we have provided links to the talks below, along with some reflections from the team on each talk.</p>
<h2><a href="https://youtu.be/Sks0vMaj0Uw" target="_blank" rel="noopener noreferrer">Day 1  - Front-End</a></h2>
<p>MC Cassidy Williams <a href="https://twitter.com/cassidoo" target="_blank" rel="noopener noreferrer">@cassidoo</a> (Head of DX and Education, Remote)</p>
<h3><a href="https://youtu.be/KUB3mbA-5Ac" target="_blank" rel="noopener noreferrer">Simple JStures can go a long way</a></h3>
<p><em>Motion detection and hand gestures, using TensorFlow JS and more</em><br>
Charlie Gerard <a href="https://twitter.com/devdevcharlie" target="_blank" rel="noopener noreferrer">@devdevcharlie</a> (Senior Developer Advocate, Stripe)</p>
<ul>
<li>Interesting thoughts about the future of gesture recognition and accessibility.</li>
<li>Led to some thoughts about user gestures and how they could be used in mobile apps. (mobile OS already use them a bit, will they get more common in various apps?)</li>
<li>Great to see how creative tech can be, and not just in the sense that we can use creativity to write good code, but also how we can use tech for creative outputs.</li>
<li>This talk is very approachable and makes the whole process of setting up an <a href="https://www.arduino.cc/" target="_blank" rel="noopener noreferrer">Arduino</a> device seem like a breeze. Go give it a go!</li>
</ul>
<h3><a href="https://www.arduino.cc/" target="_blank" rel="noopener noreferrer">The Future Of Performance Tooling</a></h3>
<p><em>What we can expect when we want to measure performance in Chrome</em><br>
Addy Osmani <a href="https://www.arduino.cc/" target="_blank" rel="noopener noreferrer">@addyosmani</a> (Engineering Manager, Google Chrome)</p>
<p>Some cool tools that we want to try out for our own apps:</p>
<ul>
<li>DevTools Recorder (record, replay, measure user flows) - these can then be exported (?) into Cypress for automated testing.</li>
<li>Perfetto UI - records Chrome browser performance traces</li>
<li>Stack Packs - extension to Lighthouse (under development?) - adds guidance/stack-based recommendations for performance.</li>
<li>Lighthouse TreeMap - visualise all unused/used dependencies.</li>
<li>Cloudflare Early Hints preloads linked pages and allows using Brotli compression</li>
<li>Clouudflare Workers can be used to test changes without making changes to the underlying page source</li>
<li>Chrome Dev Tools Recorder can be used to record a test and export to your test tool or source control</li>
</ul>
<blockquote>
<p>“Frameworks with good defaults make a difference”</p>
</blockquote>
<h3><a href="https://youtu.be/jR_W5hd-o4o" target="_blank" rel="noopener noreferrer">Introduction to reactive programming</a></h3>
<p><em>The basics of Reactive Programming</em><br>
Ben Lesh <a href="https://twitter.com/BenLesh" target="_blank" rel="noopener noreferrer">@BenLesh</a> (Rxjs project lead)</p>
<ul>
<li>This was a great talk and gave me the Aha moment of what reactive programming is all about. Thankyou @BenLesh</li>
</ul>
<h3><a href="https://youtu.be/4SzLisCvb4M" target="_blank" rel="noopener noreferrer">Third Time's the Charm: Taking Another Look at Vue 3</a></h3>
<p><em>Some of the awesome features of Vue 3 that you might have missed before</em><br>
Ben Hong <a href="https://twitter.com/bencodezen" target="_blank" rel="noopener noreferrer">@bencodezen</a> (Senior Staff DX Engineer, Netlify &amp; Vue.js Core Team)</p>
<ul>
<li>The talk was well presented and rolled out Vue’s API in a very clear, concise and understandable way.</li>
</ul>
<h3><a href="https://youtu.be/C2AuGBGLMa8" target="_blank" rel="noopener noreferrer">Local state and server cache: finding a balance</a></h3>
<p><em>The power of vue-query, and the reasonings behind using it</em><br>
Natalia Tepluhina <a href="https://twitter.com/N_Tepluhina" target="_blank" rel="noopener noreferrer">@N_Tephluhina</a> (Staff Frontend Engineer, Gitlab &amp; Vue.js Core Team)</p>
<ul>
<li>I have been using use_query for some time now, and this talk cleared up my understanding of its internals and API. I wish I had watched this earlier!</li>
</ul>
<h3><a href="https://youtu.be/jWafEXS7EE0" target="_blank" rel="noopener noreferrer">Q&amp;A</a></h3>
<p><em>Cassidy Williams and Sara Vieira ask Dan Abramov questions from the developer community about React, the web, the future, and more</em><br>
Dan Abramov <a href="https://twitter.com/dan_abramov" target="_blank" rel="noopener noreferrer">@dan_abramov</a> (React Team, Meta)</p>
<ul>
<li>Debugging is a great skill to have. Learn principles of debugging.</li>
</ul>
<h3><a href="https://youtu.be/lK11UP1qOLw" target="_blank" rel="noopener noreferrer">Solving everyday problems with data visualisation</a></h3>
<p><em>An incredible talk about data visualizations, Observable notebooks, and the very real impact of the invasion in Ukraine</em><br>
Volodymyr Agafonkin  <a href="https://twitter.com/mourner" target="_blank" rel="noopener noreferrer">@mourner</a> (Engineer, Mapbox)</p>
<ul>
<li>This talk was really interesting. Super cool to see all the visualisations Volodymyr had made in Observable. Especially the one where he used mathematics and visualisation to determine the <a href="https://observablehq.com/@mourner/kinematics-of-reverse-angle-parking?collection=@mourner/personal" target="_blank" rel="noopener noreferrer">best way to reverse angle park his car.</a></li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="af4jyuvcxli0wxq6kzhzd8yi" alt="" data-big=https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_31_54_AM_5cb1e6b913.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 222px" src="https://cms-assets.abletech.nz/Screen_Shot_2022_04_29_at_9_31_54_AM_5cb1e6b913.png" srcset="https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_31_54_AM_5cb1e6b913.png 1000w, https://cms-assets.abletech.nz/small_Screen_Shot_2022_04_29_at_9_31_54_AM_5cb1e6b913.png 500w, https://cms-assets.abletech.nz/medium_Screen_Shot_2022_04_29_at_9_31_54_AM_5cb1e6b913.png 750w, https://cms-assets.abletech.nz/thumbnail_Screen_Shot_2022_04_29_at_9_31_54_AM_5cb1e6b913.png 222w" data-zooming-width="1000" data-zooming-height="702" loading="lazy" width="1000" height="702"></figure>
</div>
<ul>
<li>It was especially sobering, and added a human-aspect, to see the visualisations he had made with respect to living in Ukraine with the war. Volodymyr used <a href="https://observablehq.com/@mourner/sirens" target="_blank" rel="noopener noreferrer">visualisations of air raid sirens</a> to help him decide when it was safe to return to Kyiv.</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sqicrfiys82qpa1pks0zeg0k" alt="" data-big=https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_30_40_AM_c55bbb9788.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Screen_Shot_2022_04_29_at_9_30_40_AM_c55bbb9788.png" srcset="https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_30_40_AM_c55bbb9788.png 1000w, https://cms-assets.abletech.nz/small_Screen_Shot_2022_04_29_at_9_30_40_AM_c55bbb9788.png 500w, https://cms-assets.abletech.nz/medium_Screen_Shot_2022_04_29_at_9_30_40_AM_c55bbb9788.png 750w, https://cms-assets.abletech.nz/thumbnail_Screen_Shot_2022_04_29_at_9_30_40_AM_c55bbb9788.png 245w" data-zooming-width="1000" data-zooming-height="634" loading="lazy" width="1000" height="634"></figure>
</div>
<h3><a href="https://youtu.be/wUksfAEZmYE" target="_blank" rel="noopener noreferrer">A RESTful API for Your Wellbeing</a></h3>
<p><em>A punny talk about avoiding burnout, finding a balance at work... and HTTP status codes</em><br>
Jessica Janiuk <a href="https://twitter.com/ThePunderWoman" target="_blank" rel="noopener noreferrer">@ThePunderWoman</a> (Angular Team, Google)</p>
<ul>
<li>Unlimited PTO does not help employee well-being - people take on average 13% less leave.</li>
<li>Make it clear for people to take leave</li>
<li>Separate home and work. If you are working from home, turn the lights off in your home office and return to home</li>
<li>Take healthy long breaks</li>
</ul>
<h2><a href="https://youtu.be/qAotgvBcoLs" target="_blank" rel="noopener noreferrer">Day 2 - Back-End</a></h2>
<p>MC Tobi Pfeiffer <a href="https://twitter.com/PragTob" target="_blank" rel="noopener noreferrer">@PragTob</a> (Staff Engineer, Remote)</p>
<h3><a href="https://youtu.be/OLFgZA0ulrg" target="_blank" rel="noopener noreferrer">Almost on time</a></h3>
<p><em>Some mind-blowing Ruby... to machine code</em><br>
Aaron Patterson <a href="https://twitter.com/tenderlove" target="_blank" rel="noopener noreferrer">@tenderlove</a> (Shopify)</p>
<ul>
<li>Did not know you could disassemble Ruby code</li>
<li>I could not believe how good Aaron is at explaining highly technical concepts in a precise and understandable manner</li>
</ul>
<h3><a href="https://youtu.be/i31HdYOwdmM" target="_blank" rel="noopener noreferrer">If you build it, they will come</a></h3>
<p><em>Learn about factories and powerful elements of object-oriented programming in Ruby</em><br>
Sandi Metz <a href="https://twitter.com/sandimetz" target="_blank" rel="noopener noreferrer">@sandimetz</a> (Rubyist)</p>
<ul>
<li>Factories loosen coupling, lowers cost of change</li>
<li>Consider using a Factory class to hide conditionals around building objects</li>
<li>Understand the difference between open and closed factories, when to use them, and what to watch out for.</li>
</ul>
<blockquote>
<p>“If you don’t like it, don’t look”</p>
</blockquote>
<blockquote>
<p>“Factories are where conditionals go to die”</p>
</blockquote>
<h3><a href="https://youtu.be/qIbd8cl-Hhc" target="_blank" rel="noopener noreferrer">Engineering for Startups</a></h3>
<p><em>Founder and CEO Lilly talks about the highs and the lows of engineering, pivoting, and scaling as a small startup</em><br>
Lilly Chen <a href="https://twitter.com/lillydoingecon" target="_blank" rel="noopener noreferrer">@lillydoingecon</a> (Founder, Contenda)</p>
<ul>
<li>Writing code as fast as possible and equally fast in deleting it is what is required to identify product market fit</li>
<li>Once you have product market fit the backlog for the product will grow quickly - as users will be getting value out of the product</li>
<li>Make technology choices to suit the stage of the product. Eg “Chose DynamoDB as they didn’t know what the Database would look like” and “Didn’t use OpenAPI as they didn’t know what the API would look like.”</li>
</ul>
<blockquote>
<p>“You are probably overthinking architecture”</p>
</blockquote>
<h3><a href="https://youtu.be/eD9j_QsXWyA" target="_blank" rel="noopener noreferrer">Fireside chat</a></h3>
<p><em>Tobi Pfeiffer asks Andreask Klinger and Saša Jurić about Elixir, scaling, systems, and more</em><br>
Saša Jurić <a href="https://twitter.com/sasajuric" target="_blank" rel="noopener noreferrer">@sasajuric</a> (Elixir Mentor) Andreas Klinger <a href="https://twitter.com/andreasklinger" target="_blank" rel="noopener noreferrer">@andreasklinger</a> (CTO, On Deck)</p>
<ul>
<li>Enjoyed the juxtaposition of the discussion on technical debt. “Some prototypes become products!” “Prototypes always become products once you hit product market fit”</li>
</ul>
<blockquote>
<p>“Sometimes the best code to write is no code at all”</p>
</blockquote>
<h3><a href="https://youtu.be/Aw9Xd4XAhdE" target="_blank" rel="noopener noreferrer">Confident Elixir</a></h3>
<p><em>Tetiana teaches us about Elixir, the learning curve, and helping your teams learn it</em><br>
Tetiana Dushenkivska <a href="https://twitter.com/Tetiana12345678" target="_blank" rel="noopener noreferrer">@Tetiana12345678</a> (Programmer &amp; Creator of Elixircards and Gitcards)</p>
<ul>
<li>Useful tips for helping navigate the highs and lows that come with learning new skills</li>
<li>Another sobering example of the realities of being a Ukranian during this invasion, Tetiana shared before and after images of the town square in the Ukrainian city of Kharkiv where she grew up</li>
</ul>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="b1o7g6uc3nzdv916fjntrf4c" alt="Kharkiv Freedom Square" data-big=https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_17_05_AM_b4dfcf7de2.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Screen_Shot_2022_04_29_at_9_17_05_AM_b4dfcf7de2.png" srcset="https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_17_05_AM_b4dfcf7de2.png 1000w, https://cms-assets.abletech.nz/small_Screen_Shot_2022_04_29_at_9_17_05_AM_b4dfcf7de2.png 500w, https://cms-assets.abletech.nz/medium_Screen_Shot_2022_04_29_at_9_17_05_AM_b4dfcf7de2.png 750w, https://cms-assets.abletech.nz/thumbnail_Screen_Shot_2022_04_29_at_9_17_05_AM_b4dfcf7de2.png 245w" data-zooming-width="1000" data-zooming-height="593" loading="lazy" width="1000" height="593"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e46clymkjkzqm0omxh1fnp8z" alt="" data-big=https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_16_37_AM_f4e4c34248.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Screen_Shot_2022_04_29_at_9_16_37_AM_f4e4c34248.png" srcset="https://cms-assets.abletech.nz/large_Screen_Shot_2022_04_29_at_9_16_37_AM_f4e4c34248.png 1000w, https://cms-assets.abletech.nz/small_Screen_Shot_2022_04_29_at_9_16_37_AM_f4e4c34248.png 500w, https://cms-assets.abletech.nz/medium_Screen_Shot_2022_04_29_at_9_16_37_AM_f4e4c34248.png 750w, https://cms-assets.abletech.nz/thumbnail_Screen_Shot_2022_04_29_at_9_16_37_AM_f4e4c34248.png 245w" data-zooming-width="1000" data-zooming-height="594" loading="lazy" width="1000" height="594"></figure>
</div>
<h3><a href="https://youtu.be/nG5-pKNgiUM" target="_blank" rel="noopener noreferrer">Patterns for Sanity</a></h3>
<p><em>Patterns in Elixir and Phoenix to save your brain</em><br>
Vittoria Bitton <a href="https://twitter.com/vittoria_bitton" target="_blank" rel="noopener noreferrer">@vittoria_bitton</a> (Senior Backend Engineer, Remote)</p>
<ul>
<li>Great to see that we use most of the patterns in our own Elixir services</li>
</ul>
<h3><a href="https://youtu.be/2Vhd5fw59UY" target="_blank" rel="noopener noreferrer">Q&amp;A</a></h3>
<p><em>Tobi Pfeiffer asks Elixir creator José Valim some burning questions from the community</em><br>
José Valim <a href="https://twitter.com/josevalim" target="_blank" rel="noopener noreferrer">@josevalim</a> (Chief Adoption Officer, Dashbit &amp; Creator of Elixir)</p>
<ul>
<li>Language decisions always have trade offs, small decisions and changes can have big flow on effects</li>
<li>Elixir expanding feature set for Numerical computing, Data processing and Machine Learning (Nx and Broadway)</li>
<li>Elixir now can be compiled for GPU (see use cases in Nx)</li>
<li>Article incoming for dot notation</li>
<li>Elixir being used all over place, even in embedded systems</li>
</ul>
<h3><a href="https://youtu.be/pI50CzjdOAE" target="_blank" rel="noopener noreferrer">Opening Doors with Open Source</a></h3>
<p><em>The power of inclusion in open source and bringing others into the industry</em><br>
Anjana Vakil <a href="https://twitter.com/AnjanaVakil" target="_blank" rel="noopener noreferrer">@AnjanaVakil</a> (Senior Developer Advocate, Hasura)</p>
<ul>
<li>Hasura is written in Haskell!</li>
<li>Hasura Documentation is open source, with each page having a link to github. This helps the community to help the community, and builds a stronger community.</li>
</ul>
<h3>Conference Organisers:</h3>
<p>A shout out to the amazing job the organisers provided. It was incredible.</p>
<ul>
<li>Sara Viera - Frontend Developer <a href="https://twitter.com/NikkitaFTW" target="_blank" rel="noopener noreferrer">@NikkitaFTW</a></li>
<li>Marcelo Lebre - COO at Remote <a href="https://twitter.com/marcelo_lebre" target="_blank" rel="noopener noreferrer">@marcelo_lebre</a></li>
<li>Cassidy Williams - Head of Developer Experience <a href="https://twitter.com/cassidoo" target="_blank" rel="noopener noreferrer">@cassidoo</a></li>
<li>Adrien Thomas - Staff Product Designer <a href="https://twitter.com/adrienths" target="_blank" rel="noopener noreferrer">@adrienths</a></li>
<li>Tobi Pfeiffer - Staff Engineer &amp; Bunny Lover <a href="https://twitter.com/PragTob" target="_blank" rel="noopener noreferrer">@PragTob</a></li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Supporting diversity in an IT workplace</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>In 2021, our CEO Michelle Harvey was concerned by the general lack of diversity being seen in applicants for software development roles. Sure, there were some great young people coming through the system, but there was an obvious lack of representation from important parts of our society.</p>
<p>We have programmes in place to support the career growth of a diverse team, but attracting a full range of diversity into the sector seemed to be problematic.</p>
<p>We see the benefit to our company culture and love that we have a team from across many different backgrounds. The benefit of having a broad representation of society contributing to the design of technology that all New Zealanders use, is also well understood at Abletech.</p>
<p>One deeply under-represented group, sector wide, is Maori and Pasifika.
So how could we do better and be part of the solution?</p>
<p>Then someone suggested she look into <a href="https://tuputoa.org.nz/" target="_blank" rel="noopener noreferrer">TupuToa</a>, whose mission is to “grow Maori and Pacific leaders for a greater Aotearoa”. After meeting with the TupuToa team and being inspired by their vision, Abletech signed up as a partner. A few months later, Jet Paese came on board as a summer intern on the development team.</p>
<p>Jet joined Abletech at a very exciting time. In fact he started the very same day the company was awarded a hugely important piece of work; the COVID-19 <a href="https://www.health.govt.nz/covid-19-novel-coronavirus/covid-19-resources-and-tools/covid-19-contact-tracing-form" target="_blank" rel="noopener noreferrer">contact tracing form</a> | Te Whatu Ora (Health NZ). Delivering this incredibly important project required a huge effort from the whole Abletech team which meant Jet could be involved in a real project from day one. In fact he was demonstrating his work to Te Whatu Ora (Health NZ) in his first month!</p>
<p>Jet was able to contribute meaningfully while receiving great support from the Abletech team and his navigator, Sofara Aiono, from TupuToa. It was great having Jet’s youthful enthusiasm on the team and he enjoyed his time with Abletech so much that when offered a permanent position, he said yes! We caught up with Jet to learn more about his internship experience.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="riaicdnujsxzxpe0lkag6v63" alt="" data-big=https://cms-assets.abletech.nz/large_jet_5f8370efc8.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 214px" src="https://cms-assets.abletech.nz/jet_5f8370efc8.jpg" srcset="https://cms-assets.abletech.nz/large_jet_5f8370efc8.jpg 1000w, https://cms-assets.abletech.nz/small_jet_5f8370efc8.jpg 500w, https://cms-assets.abletech.nz/medium_jet_5f8370efc8.jpg 750w, https://cms-assets.abletech.nz/thumbnail_jet_5f8370efc8.jpg 214w" data-zooming-width="1000" data-zooming-height="729" loading="lazy" width="1000" height="729"></figure>
</div>
<p>What has Abletech’s support meant to you and your confidence as you embark on your engineering career?</p>
<blockquote>
<p>The support here has made me feel really comfortable from day one. I've never felt like an outsider and that meant I could start working towards my potential from day one</p>
</blockquote>
<p>What advice would you give others pursuing IT internships through Abletech or other companies?</p>
<blockquote>
<p>Just go for it. It's an opportunity you should never pass up. You will feel nervous but whether you chose Abletech or another software development company, there will be lovely people waiting there to welcome you</p>
</blockquote>
<p>What makes you excited to be working at Abletech?</p>
<blockquote>
<p>Being around like minded people with the same passions and the ability to continuously learn and grow during my time here, however long that will be</p>
</blockquote>
<p>What has been a highlight of your time here?</p>
<blockquote>
<p>It was really good to be able to work across both companies,  Abletech and (sister company) <a href="http://addressfinder.com" target="_blank" rel="noopener noreferrer">AddressFinder</a> as it gave me a grasp of how the whole organisation is run</p>
</blockquote>
<p>Abletech was so pleased with the TupuToa partnership that we have committed to providing internship opportunities again this year as part of a wider programme of work supporting diversity and inclusion in our workplace.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tii5lvsvxonyb3nk8aqb8m6d" alt="" data-big=https://cms-assets.abletech.nz/large_jet_and_abletech_37f9502d2c.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 225px" src="https://cms-assets.abletech.nz/jet_and_abletech_37f9502d2c.jpg" srcset="https://cms-assets.abletech.nz/large_jet_and_abletech_37f9502d2c.jpg 1000w, https://cms-assets.abletech.nz/small_jet_and_abletech_37f9502d2c.jpg 500w, https://cms-assets.abletech.nz/medium_jet_and_abletech_37f9502d2c.jpg 750w, https://cms-assets.abletech.nz/thumbnail_jet_and_abletech_37f9502d2c.jpg 225w" data-zooming-width="1000" data-zooming-height="695" loading="lazy" width="1000" height="695"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Building a party and teams service in less than a day using Hasura</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>The following post will explain how you can build a simple party management and team service complete with relationship graph, access control and permissions in less than a day. We’ll be using a tool called Hasura with data stored in a Postgres database.</p>
<p>Scroll down for a TL;DR and for an example code base.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="gscf4nvp6iybwsvager9bz3b" alt="" data-big=https://cms-assets.abletech.nz/large_abletech_hasura_aecb4d8f42.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/abletech_hasura_aecb4d8f42.jpg" srcset="https://cms-assets.abletech.nz/large_abletech_hasura_aecb4d8f42.jpg 1000w, https://cms-assets.abletech.nz/small_abletech_hasura_aecb4d8f42.jpg 500w, https://cms-assets.abletech.nz/medium_abletech_hasura_aecb4d8f42.jpg 750w, https://cms-assets.abletech.nz/thumbnail_abletech_hasura_aecb4d8f42.jpg 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<h2>What is Hasura?</h2>
<p>Hasura acts almost like an ORM but it’s intended to be used by clients too. It sits on top of your data layer and exposes a set of GraphQL queries, mutations and subscriptions for interfacing with your data. It provides flexible authentication and allows you to build relationships between your tables, even allowing you to introduce remote data sources and schemas.</p>
<h2>The task</h2>
<p>As a proof of concept, and with some inspiration from a previous Abletech research project by <a href="https://github.com/aspett" target="_blank" rel="noopener noreferrer">Andrew Pett</a>, Parteams was born. This combines the “Party Role Relationship” pattern with the “Teams” Pattern. We have found both patterns to be extremely useful in a variety of use cases from small systems to enterprise systems.</p>
<p>The “Party Role Relationship” pattern proves really useful in describing Organisation, company structures and the different and complex relationship between Parties.</p>
<p>The “Teams” pattern provides a useful abstraction when controlling access to a particular set of resources by a group of subjects.</p>
<p>“Parteams” provides a hybrid relational graph of data using parties and relationships with some access control permissions built in using the fundamentals of teams and resources.</p>
<p>The entire graph and team structure is scoped by scoping rules at the data access layer, ensuring the authenticated user is within the data structures to obtain access to the underlying data.</p>
<p>The main ideas in “Parteams” are:</p>
<ul>
<li><strong>Parties</strong> - these are nodes in our relational graph - they can be people or organisations.</li>
<li><strong>Relationships</strong> - these are  edges in our relational graph, they can be directed and hold the type of relationship between our parties.</li>
<li><strong>Teams</strong> - these are constructs which group parties and resources together. They contain <strong>subjects</strong> and <strong>resources</strong>. The subjects relate to parties, where the subjects are granted access to resources with an access level of read, write or admin. Clients are able to query teams to see if a given subject has access to a particular resource.</li>
</ul>
<h2>Getting started</h2>
<h3>Setting up Hasura</h3>
<p>First step was getting Hasura and our database set up. You could opt to use Hasura Cloud, which is fully-managed, however we have opted to get our environment running locally first and deploy out to our own cloud infrastructure later. We’ve also opted to use Postgres as our database engine, you could choose many of the other options supported by Hasura.</p>
<p>Start by creating a docker-compose file with two containers, one for your graphql-engine, and another for your Postgres instance.</p>
<pre><code class="hljs language-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">&#x27;3.6&#x27;</span>
<span class="hljs-attr">services:</span>
 <span class="hljs-attr">postgres:</span>
   <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
   <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
   <span class="hljs-attr">volumes:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">db_data:/var/lib/postgresql/data</span>
   <span class="hljs-attr">environment:</span>
     <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">postgrespassword</span>
   <span class="hljs-attr">ports:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;5432:5432&quot;</span>
 <span class="hljs-attr">graphql-engine:</span>
   <span class="hljs-attr">image:</span> <span class="hljs-string">hasura/graphql-engine:v2.3.1</span>
   <span class="hljs-attr">ports:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;8080:8080&quot;</span>
   <span class="hljs-attr">depends_on:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;postgres&quot;</span>
   <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
   <span class="hljs-attr">environment:</span>
     <span class="hljs-comment">## postgres database to store Hasura metadata</span>
     <span class="hljs-attr">HASURA_GRAPHQL_METADATA_DATABASE_URL:</span> <span class="hljs-string">postgres://postgres:postgrespassword@postgres:5432/postgres</span>
     <span class="hljs-comment">## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs</span>
     <span class="hljs-attr">PG_DATABASE_URL:</span> <span class="hljs-string">postgres://postgres:postgrespassword@postgres:5432/postgres</span>
     <span class="hljs-comment">## enable the console served by server</span>
     <span class="hljs-attr">HASURA_GRAPHQL_ENABLE_CONSOLE:</span> <span class="hljs-string">&quot;true&quot;</span> <span class="hljs-comment"># set to &quot;false&quot; to disable console</span>
     <span class="hljs-comment">## enable debugging mode. It is recommended to disable this in production</span>
     <span class="hljs-attr">HASURA_GRAPHQL_DEV_MODE:</span> <span class="hljs-string">&quot;true&quot;</span>
     <span class="hljs-attr">HASURA_GRAPHQL_ENABLED_LOG_TYPES:</span> <span class="hljs-string">startup,</span> <span class="hljs-string">http-log,</span> <span class="hljs-string">webhook-log,</span> <span class="hljs-string">websocket-log,</span> <span class="hljs-string">query-log</span>
     <span class="hljs-comment">## uncomment next line to set an admin secret</span>
     <span class="hljs-attr">HASURA_GRAPHQL_ADMIN_SECRET:</span> <span class="hljs-string">myadminsecretkey</span>
<span class="hljs-attr">volumes:</span>
 <span class="hljs-attr">db_data:</span>
</code></pre>
<p><em>docker-compose.yml</em></p>
<p>For our graphql-engine, we set up the container with some sensible defaults, like enabling the console, some additional nice-to-haves for developing and a super-simple secret.</p>
<h3>Connecting up the database</h3>
<p>First up, we’ll need to connect our database. The easiest way to do this is via the Hasura console. To start the console, you can run the following commands:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">export</span> HASURA_GRAPHQL_ADMIN_SECRET=myadminsecretkey
hasura console
</code></pre>
<p><em>Start the Hasura console</em></p>
<p>This will run the Hasura console at <a href="http://localhost:9695" target="_blank" rel="noopener noreferrer">http://localhost:9695/console</a>. Following this, you’re able to select the “Data” tab, and then “Connect Database”. Give your database a name, select the driver and you can select to connect via environment variable - <code>PG_DATABASE_URL</code>.</p>
<p>Note: this demo assumes you want to start fresh with a PostgreSQL database, you can also connect up another database full of schemas/tables and your own data and see what Hasura has to offer. If you choose to do this, you can leverage as little or as much of what Hasura has to offer, but with your existing schemas, relationships and data.</p>
<h3>Setting up our tables</h3>
<p>Next step is to create our migrations. We want to create tables, with foreign keys, for our <strong>parties</strong>, <strong>party_types</strong>, <strong>relationships</strong>, <strong>relationship_types</strong>, <strong>teams</strong>, <strong>resources</strong> and <strong>subjects</strong>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h5c5887y0u86ftxwtdj896b7" alt="" data-big=https://cms-assets.abletech.nz/large_parties_table_e066e9cfed.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/parties_table_e066e9cfed.png" srcset="https://cms-assets.abletech.nz/large_parties_table_e066e9cfed.png 1000w, https://cms-assets.abletech.nz/small_parties_table_e066e9cfed.png 500w, https://cms-assets.abletech.nz/medium_parties_table_e066e9cfed.png 750w, https://cms-assets.abletech.nz/thumbnail_parties_table_e066e9cfed.png 245w" data-zooming-width="1000" data-zooming-height="318" loading="lazy" width="1000" height="318"></figure>
</div>
<p><em><code>parties</code> table schema</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mmk2dgw0tfc0aj027qeik7wk" alt="" data-big=https://cms-assets.abletech.nz/large_relationship_types_table_a096b6759e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/relationship_types_table_a096b6759e.png" srcset="https://cms-assets.abletech.nz/large_relationship_types_table_a096b6759e.png 1000w, https://cms-assets.abletech.nz/small_relationship_types_table_a096b6759e.png 500w, https://cms-assets.abletech.nz/medium_relationship_types_table_a096b6759e.png 750w, https://cms-assets.abletech.nz/thumbnail_relationship_types_table_a096b6759e.png 245w" data-zooming-width="1000" data-zooming-height="254" loading="lazy" width="1000" height="254"></figure>
</div>
<p><em><code>relationship_types</code> table schema</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wjgpe9919wel2svjhxlejob1" alt="" data-big=https://cms-assets.abletech.nz/large_relationships_table_df32b13568.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/relationships_table_df32b13568.png" srcset="https://cms-assets.abletech.nz/large_relationships_table_df32b13568.png 1000w, https://cms-assets.abletech.nz/small_relationships_table_df32b13568.png 500w, https://cms-assets.abletech.nz/medium_relationships_table_df32b13568.png 750w, https://cms-assets.abletech.nz/thumbnail_relationships_table_df32b13568.png 245w" data-zooming-width="1000" data-zooming-height="429" loading="lazy" width="1000" height="429"></figure>
</div>
<p><em><code>relationships</code> table schema</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="z3geuoea3td427wtp5fyaih1" alt="" data-big=https://cms-assets.abletech.nz/large_teams_table_fe0d30615b.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/teams_table_fe0d30615b.png" srcset="https://cms-assets.abletech.nz/large_teams_table_fe0d30615b.png 1000w, https://cms-assets.abletech.nz/small_teams_table_fe0d30615b.png 500w, https://cms-assets.abletech.nz/medium_teams_table_fe0d30615b.png 750w, https://cms-assets.abletech.nz/thumbnail_teams_table_fe0d30615b.png 245w" data-zooming-width="1000" data-zooming-height="315" loading="lazy" width="1000" height="315"></figure>
</div>
<p><em><code>teams</code> table schema</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="skr777yg5e2m8giuwjs2t0wl" alt="" data-big=https://cms-assets.abletech.nz/large_resources_table_bc1bb306d2.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/resources_table_bc1bb306d2.png" srcset="https://cms-assets.abletech.nz/large_resources_table_bc1bb306d2.png 1000w, https://cms-assets.abletech.nz/small_resources_table_bc1bb306d2.png 500w, https://cms-assets.abletech.nz/medium_resources_table_bc1bb306d2.png 750w, https://cms-assets.abletech.nz/thumbnail_resources_table_bc1bb306d2.png 245w" data-zooming-width="1000" data-zooming-height="371" loading="lazy" width="1000" height="371"></figure>
</div>
<p><em><code>resources</code> table schema</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yj4t4ckjhaj76bozp48deuq7" alt="" data-big=https://cms-assets.abletech.nz/large_subjects_table_58215ae91e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/subjects_table_58215ae91e.png" srcset="https://cms-assets.abletech.nz/large_subjects_table_58215ae91e.png 1000w, https://cms-assets.abletech.nz/small_subjects_table_58215ae91e.png 500w, https://cms-assets.abletech.nz/medium_subjects_table_58215ae91e.png 750w, https://cms-assets.abletech.nz/thumbnail_subjects_table_58215ae91e.png 245w" data-zooming-width="1000" data-zooming-height="430" loading="lazy" width="1000" height="430"></figure>
</div>
<p><em><code>subjects</code> table schema</em></p>
<p>We can do this easily via the Hasura console (alternatively, you can define migrations using pure SQL using the SQL editor, or the CLI). Here we can specify the columns, types, foreign keys and unique indexes as well as any constraints or computed fields the table may have.</p>
<p>If you’ve chosen to create tables via the console, note how migrations get created as you create tables in the UI.</p>
<p>Note: if you want to skip this step, you can clone our demo repo and apply the migrations and metadata from what we’ve previously defined. To do this - clone the repository, setup/run Hasura and the Postgres instance in Docker and run:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">export</span> HASURA_ADMIN_GRAPHQL_SECRET=’mysecretadminkey’
hasura metadata apply <span class="hljs-comment"># apply our metadata (will result in inconsistencies as we haven’t created our tables)</span>
hasura migrate apply <span class="hljs-comment"># apply the migrations (will create our tables, constraints, indices)</span>
hasura metadata reload <span class="hljs-comment"># reload the metadata (hopefully removes any inconsistencies)</span>
</code></pre>
<p><em>Applying the metadata/migrations from configuration</em></p>
<p>Additionally, if you’re using a Postgres database, you’re able to utilise Postgres views. A Postgres view represents a “virtual” table in the database - built from underlying queries. Hasura lets you build select queries on top of this view meaning you can access data across multiple tables without actually storing a representation of this data. However, if you find that your views are becoming increasingly complex, and are performant to execute, you can use Postgres’ materialized views which will store a representation in the database. Note: it’s important to refresh these materialized views periodically or when the underlying data changes. Read <a href="https://hasura.io/docs/latest/graphql/core/guides/postgres/views/" target="_blank" rel="noopener noreferrer">Hasura's documentation</a> for more.</p>
<h3>Seeding our data</h3>
<p>Now that we’ve set up our tables, we want to set up some test data. Again, Hasura Console comes to the rescue. We can populate our database by creating records for each table.</p>
<p>For this demo, we’ll create the following:</p>
<h4>Parties</h4>
<ul>
<li>Abletech (Organisation)</li>
<li>Matt/Sam/Marcus (People)</li>
</ul>
<h4>Relationships</h4>
<ul>
<li>Abletech - Matt/Sam (membership)</li>
<li>Abletech - Marcus (ownership)</li>
</ul>
<h4>Teams</h4>
<ul>
<li>Team Abletech (resources: a Github project URL, subjects: Matt/Sam/Marcus)</li>
</ul>
<p>This data can also be dumped to a seed SQL file by running:</p>
<pre><code class="hljs language-bash">hasura seed create initial_seed_data --from-table parties --from-table party_types --from-table relationship_types --from-table relationships --from-table resources --from-table subjects --from-table teams
</code></pre>
<p>Once dumped to a file, others can simply run <code>hasura seed apply</code> and have the same data on their local Hasura instance. This is especially useful for test data and verifying queries/mutations are working as expected.</p>
<p>Again, if you’re wanting to skip this step - you’re able to use the generated seed file in our demo repository and load this into your locally running Hasura instance (assuming you’ve set up the tables from the previous step).</p>
<pre><code class="hljs language-bash">hasura seed apply
</code></pre>
<p><em>Apply the seed data from configuration</em></p>
<h2>Adding and altering our relationships</h2>
<p>Now that we’ve created our tables, we want to create relationships between them so that we can really take advantage of GraphQL’s association-based structure. Adding relationships will allow us to access associated data structures via a single query. Note there are two types of relationships, <strong>object relationships</strong>  which represent a one-to-one relationship and <strong>array relationships</strong> which represent a one-to-many relationship.</p>
<p>Under each table, we want to “track&quot; each table’s foreign key relationships, as well as alter the relationships names so that they’re more representative of what relationships they represent.</p>
<p>Under the <strong>parties</strong> table we want to create array relationships to the <em>from_party_relationships</em>, <em>to_party_relationships</em> and <em>team_subjects</em>.</p>
<p>Under the <strong>relationships</strong> table we want to create object relationships to the <em>from_party</em> and the <em>to_party</em>.</p>
<p>Under the <strong>resources</strong> and <strong>subjects</strong> tables we want to create object relationships to the <em>team</em>.</p>
<p>Under the <strong>teams</strong> table we want to create array relationships to the <em>resources</em> and <em>subjects</em>.</p>
<p>Again, if you want to skip this, you’re able to apply the metadata using the instructions from above.</p>
<h2>Having a play</h2>
<p>Now that we’ve set up our schemas and inserted some dummy data - we can really see what Hasura has to offer!</p>
<p>Under the “APIs'' tab in the console, we’re given a GraphiQL tool. This shows us all the queries/mutations/subscriptions that Hasura has automatically generated for us. We’re able to query our parties, teams, and relationships as well as call mutations to create us some teams and parties. Note that the GraphiQL tool has an <code>X-Hasura-Admin-Secret</code>  header, note that this value is populated to the environment specified in our docker-compose file. The presence of this header indicates that we’re accessing our schema with the “admin” role - meaning we have over-arching access to the entire data set and schema.</p>
<p>Here are some example queries that may be useful to you:</p>
<h3>Showing a party</h3>
<p>This is the simplest query - we're not querying any associations - just fetching a record from the database using its primary key.</p>
<pre><code class="hljs language-graphql"><span class="hljs-keyword">query</span> ShowParty <span class="hljs-punctuation">{</span>
  parties_by_pk<span class="hljs-punctuation">(</span><span class="hljs-symbol">id</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;xxx&quot;</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">{</span>
    email
    id
    name
    party_type
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p><em>Shows a singular party</em></p>
<h3>Showing a team</h3>
<p>Slightly more complicated - we're querying our &quot;Teams&quot; table and fetching its associated subjects and resources.</p>
<pre><code class="hljs language-graphql"><span class="hljs-keyword">query</span> ShowTeam <span class="hljs-punctuation">{</span>
  teams_by_pk<span class="hljs-punctuation">(</span><span class="hljs-symbol">id</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;xxx&quot;</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">{</span>
    created_at
    id
    name
    resources <span class="hljs-punctuation">{</span>
      id
      identifier
    <span class="hljs-punctuation">}</span>
    subjects <span class="hljs-punctuation">{</span>
      id
      party <span class="hljs-punctuation">{</span>
        id
        created_at
        email
        name
        party_type
        updated_at
      <span class="hljs-punctuation">}</span>
      permission
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p><em>Shows a singular team</em></p>
<h3>Inserting a set of teams/resources/subjects</h3>
<p>A more complicated mutation - we're inserting several records with this one API call - a team, resource, subject and party.</p>
<pre><code class="hljs language-graphql"><span class="hljs-keyword">mutation</span> InsertTeamWithSubjectsAndResource <span class="hljs-punctuation">{</span>
  insert_teams_one<span class="hljs-punctuation">(</span><span class="hljs-symbol">object</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-symbol">name</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Another Abletech Team&quot;</span>, <span class="hljs-symbol">resources</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-symbol">data</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-symbol">identifier</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;some_resource&quot;</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">}</span>, <span class="hljs-symbol">subjects</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-symbol">data</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-symbol">party</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-symbol">data</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-symbol">name</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Jeremy Farnault&quot;</span>, <span class="hljs-symbol">party_type</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;person&quot;</span>, <span class="hljs-symbol">email</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;jeremy@abletech.co.nz&quot;</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">}</span>, <span class="hljs-symbol">permission</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;admin&quot;</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">}</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">{</span>
    id
    name
    subjects <span class="hljs-punctuation">{</span>
      id
      permission
      party <span class="hljs-punctuation">{</span>
        id
        email
        name
      <span class="hljs-punctuation">}</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p><em>Inserts a set of team, resource, subject and party in one query</em></p>
<p>As you can see, we’ve managed to expose our entire database via a single GraphQL schema with little to no effort or code. However, as we’ve discussed, we’re accessing this data with an “admin” role - generally not ideal if you’re allowing a client to directly access your data! As a result, we probably want to secure our queries/mutations and data.</p>
<h2>Restricting access</h2>
<p>With Hasura you can set up authentication using JWT. The JWT must contain <code>x-hasura-default-role</code> and <code>x-hasura-allowed-roles</code> and you can define other optional <code>x-hasura-*</code> claims. We won’t go into authentication in depth in this article, but you can configure your Hasura instance with the same JWT secret as your authentication provider, and set up your authentication provider to create JWTs with the claims defined above. The <code>x-hasura-default-role</code> and <code>x-hasura-*</code> claims will then be available to you when defining permissions. You can read more about JWT authentication in <a href="https://hasura.io/docs/latest/graphql/core/auth/authentication/jwt/" target="_blank" rel="noopener noreferrer">Hasura's documentation</a>.</p>
<p>Permissions are defined on a table level and can be set on a row, on a column or both. They’re granular enough that you can specify if particular roles can perform inserts, updates, selects and deletes. You can also match on values passed through in the JWT claims.</p>
<p>For our Parteams service, let's assume our auth server returns back a JWT containing a <code>x-hasura-default-role</code> of “authenticated” and an <code>x-hasura-user-id</code> which corresponds to a party’s ID in our database.</p>
<p>For our <strong>parties</strong> table, let's assume we only want users with the same party ID as their <code>x-hasura-user-id</code> to be able to select it.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ti0lndxthes03f4yxfvqzctm" alt="" data-big=https://cms-assets.abletech.nz/medium_parties_permissions_159f44a295.png sizes="(min-width: 768px) 475px, (min-width: 1024px) 712px, (min-width: 640px) 148px" src="https://cms-assets.abletech.nz/parties_permissions_159f44a295.png" srcset="https://cms-assets.abletech.nz/small_parties_permissions_159f44a295.png 475w, https://cms-assets.abletech.nz/medium_parties_permissions_159f44a295.png 712w, https://cms-assets.abletech.nz/thumbnail_parties_permissions_159f44a295.png 148w" data-zooming-width="712" data-zooming-height="750" loading="lazy" width="712" height="750"></figure>
</div>
<p><em>Setting select permissions on the <code>parties</code> table</em></p>
<p>Getting a bit more complicated, we can also create permissions based on associations. For example, say for our teams, we only want users who are a subject in a team to be able to query it. We’re able to look at a team’s subjects and only allow selections if a <code>party_id</code> matches.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qf4hl4xaiw5aqqqkudsarkui" alt="" data-big=https://cms-assets.abletech.nz/small_team_permissions_aa1a80196d.png sizes="(min-width: 768px) 500px, (min-width: 640px) 167px" src="https://cms-assets.abletech.nz/team_permissions_aa1a80196d.png" srcset="https://cms-assets.abletech.nz/small_team_permissions_aa1a80196d.png 500w, https://cms-assets.abletech.nz/thumbnail_team_permissions_aa1a80196d.png 167w" data-zooming-width="500" data-zooming-height="468" loading="lazy" width="500" height="468"></figure>
</div>
<p><em>Setting select permissions on the <code>teams</code> table</em></p>
<p>We’re able to also query on updates - say we want to restrict updates to parties so users are only able to their own records we can do this - we can also restrict which columns a user can update. We can also define post-update checks to validate on what users send through.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="htw2xxw4wlsro3454w8ttria" alt="" data-big=https://cms-assets.abletech.nz/large_team_update_permissions_f3750aacbb.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 179px" src="https://cms-assets.abletech.nz/team_update_permissions_f3750aacbb.png" srcset="https://cms-assets.abletech.nz/large_team_update_permissions_f3750aacbb.png 1000w, https://cms-assets.abletech.nz/small_team_update_permissions_f3750aacbb.png 500w, https://cms-assets.abletech.nz/medium_team_update_permissions_f3750aacbb.png 750w, https://cms-assets.abletech.nz/thumbnail_team_update_permissions_f3750aacbb.png 179w" data-zooming-width="1000" data-zooming-height="871" loading="lazy" width="1000" height="871"></figure>
</div>
<p><em>Setting update permissions on the <code>teams</code> table</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="y2uyafpnqq2339q61velcge7" alt="" data-big=https://cms-assets.abletech.nz/large_team_column_permissions_a6cc5c13cb.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/team_column_permissions_a6cc5c13cb.png" srcset="https://cms-assets.abletech.nz/large_team_column_permissions_a6cc5c13cb.png 1000w, https://cms-assets.abletech.nz/small_team_column_permissions_a6cc5c13cb.png 500w, https://cms-assets.abletech.nz/medium_team_column_permissions_a6cc5c13cb.png 750w, https://cms-assets.abletech.nz/thumbnail_team_column_permissions_a6cc5c13cb.png 245w" data-zooming-width="1000" data-zooming-height="304" loading="lazy" width="1000" height="304"></figure>
</div>
<p><em>Setting update column permissions on the <code>teams</code> table</em></p>
<h2>Creating actions/computed fields</h2>
<p>If you need more information than what’s available in the database, you can enrich your schemas with actions and computed fields.</p>
<p><strong>Actions</strong> can be used to add a field onto a schema and instead of resolving from the database you can point the field to an API. Hasura Actions allow you to specify an endpoint, any request headers and transform the payload, request options etc. to match what your API is expecting.</p>
<p>We’ve found backing our actions with a serverless Lambda function has been super useful and will allow you not only to resolve fields, but provide validation and enable business logic to be performed.</p>
<p>It’s important to note that your actions should be restricted so they can only be called by your Hasura instance - one way we’ve adopted is adding a secret to the request header. Alternatively you can lock down your functions and Hasura instance to a secure/restricted VPN.</p>
<p><strong>Computed fields</strong> are a way of resolving a field using a custom SQL function. Hasura supports scalar computed fields as well as table computed fields. Although, I would imagine for the majority of use cases - using a Postgres view (for read operations at least) would be simpler and more efficient.</p>
<h2>What we liked</h2>
<p>Overall, we really liked how fast it was to set up a GraphQL engine and a Hasura instance. Hasura provided a simple interface that let us set up tables, foreign key relationships, permissions and create data - this made it easy to get started and we didn’t even have to write any SQL. However, it was great that if you needed the ability to write more complex SQL queries, you could do so without relying on the interface.</p>
<p>Being able to track/not track various tables and relationships was good too - this meant we could choose what schemas were able to be queried/mutated. Additionally, the flexibility of defining custom relationships (and having the UI provide suggestions for relationships) meant that we could make our schemas as flexible as possible.</p>
<p>The ability to set up permissions on any value and reference relationships (as well as relationships of relationships) was incredibly helpful too. Traditionally, if we wanted to implement permissions - there’d be a lot more boilerplate code that we would need to write. With Hasura, we were able to fetch a value from our JWT’s claims and compare this against values in our database.</p>
<p>Lastly, being able to enrich your GraphQL schemas utilising additional sources provides great flexibility. Using Hasura’s Actions to provide validation, or to resolve other fields as well as computed fields to hook into SQL functions make for an extensible and dynamic schema.</p>
<h2>What we'd like to see in future</h2>
<p>It was great that we could create root queries/mutations to resolve with actions, however it would be awesome to be able to resolve individual fields on existing types with actions. For example, if I had a type “User” that was resolved from a “users” table in our linked database but wanted to resolve a field on the “User” type from an external API. This may be possible with computed fields, or a remote schema but enriching this data from an action would enhance usability.</p>
<p>Tests are something we would like to have documentation on or assistance with in the future. Being a Rails/Elixir/JS shop, Abletech has a strong background in test-driven development. This is a bit trickier to implement with Hasura. Our unguided approach would be to stand up a separate testing application for integration tests against the GraphQL engine and configuration with some seed test data. Some guidelines of testing best practices from Hasura would be an added bonus.</p>
<h2>Next steps</h2>
<p>In terms of next steps for this demo app - we would be keen to implement more remote data sources - either in the form of remote schemas or events. Remote schemas would allow us to “schema stitch” another GraphQL schema to the one exposed by Hasura whereas events would allow us to trigger custom business logic after data is modified.</p>
<p>Additionally, Hasura provides REST endpoints. These are particularly useful if you have clients that aren’t accustomed to (or simply don’t want to) consume the GraphQL endpoints. A next step here, could be to expose a selection of queries/mutations as RESTful endpoints to test functionality.</p>
<h2>TL;DR</h2>
<p>Hasura is a super powerful tool that you’re able to place in front of your database. It exposes a GraphQL schema with a whole heap of useful queries, mutations and subscriptions and reduces your time to market immensely. It has rich permission management and the ability to hook up external APIs using Actions and trigger APIs using Events.</p>
<p>The source code used for this demo can be found on the <a href="https://github.com/abletech/parteam-demo" target="_blank" rel="noopener noreferrer">Abletech Github</a>.</p>
<p>Get in touch today if you want a hosted solution built tailored to your needs that utilises Hasura or GraphQL.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Choosing a Headless CMS</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>Content Management Systems seem to be a hot topic at the moment. We have been building varying solutions for several clients with integrated CMS systems, including our very own websites abletech.nz and addressfinder.nz. Our personal experience sees us on a three to five year lifecycle with CMS’s. Abletech went from WordPress, to BlogSpot, to Tumblr, to Medium, and now to Strapi over the course of 16 years! This article outlines some of our learnings, why we keep changing, and what we are really after from our CMS.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="n2k1k1oeerszmbg54vfhp5b9" alt="" data-big=https://cms-assets.abletech.nz/large_strapi_fca820d82c.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/strapi_fca820d82c.jpg" srcset="https://cms-assets.abletech.nz/large_strapi_fca820d82c.jpg 1000w, https://cms-assets.abletech.nz/small_strapi_fca820d82c.jpg 500w, https://cms-assets.abletech.nz/medium_strapi_fca820d82c.jpg 750w, https://cms-assets.abletech.nz/thumbnail_strapi_fca820d82c.jpg 245w" data-zooming-width="1000" data-zooming-height="422" loading="lazy" width="1000" height="422"></figure>
</div>
<h2>Why use a CMS at all?</h2>
<p>The general driver seems to be providing the business with the ability to quickly publish content, be it a marketing website, web application, or even a native mobile app. Using a CMS allows us to separate the quickly changing content from the slower changing application. The attractiveness of not requiring an expensive developer to be involved with making changes is also a big drawcard.</p>
<h2>Content</h2>
<blockquote>
<p>We seek a fantastic user experience in creating, reviewing, previewing, and publishing our content. We also generally need the ability to define our own content types. Contact cards, blogs posts, opening hours, locations, and any other types that our publishers need access to and inject into our content rich applications.</p>
</blockquote>
<h3>Media</h3>
<blockquote>
<p>A killer feature of the CMS is image and asset management. Traditional mechanisms to handle images within a website by including images in source repositories such as Git is not a great idea (doing so can quickly bloat and slow down your github actions due to increased repository size and bloat.) Cloud storage is a better option (such as AWS S3), but without a management facility wrapped around it you will struggle to find and manage your images in your content.</p>
</blockquote>
<p>A good CMS should allow you to not only effectively manage images (upload, categorise, find etc), but also automagically format your images into various sizes to optimise them for delivery across different devices (desktop, mobile etc) and network speeds (rendering small images ahead of higher density refined images in a consistent systemised manner).</p>
<h2>Headless CMS or CMS</h2>
<p>Headless refers to the CMS just being used to create and manage content. The content is injected into your application by a real time API or via a build process. Full CMS's (sound we call these headfull 😀) which provide the content, and theme builders etc to deliver the content into a rich fully featured site.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bfqxjkrrstmwuz4wmckixadz" alt="" data-big=https://cms-assets.abletech.nz/large_cms_f6e3df0b43.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/cms_f6e3df0b43.jpg" srcset="https://cms-assets.abletech.nz/large_cms_f6e3df0b43.jpg 1000w, https://cms-assets.abletech.nz/small_cms_f6e3df0b43.jpg 500w, https://cms-assets.abletech.nz/medium_cms_f6e3df0b43.jpg 750w, https://cms-assets.abletech.nz/thumbnail_cms_f6e3df0b43.jpg 245w" data-zooming-width="1000" data-zooming-height="181" loading="lazy" width="1000" height="181"></figure>
</div>
<p>If you are creating a brochure website for your business and have little in the way of brand then a fully-featured CMS that can give you a responsive out-of-the-box design is certainly a nice way to go, However, if you want full control in delivering a unique, unconstrained, rich online experience showcasing your brand - with precise control over concerns such as performance, accessibility, availability, and security - then you will likely find that your out-of-the-box CMS will fail. A headless CMS is also required to push/pull information into mobile or web applications (it would be difficult or impossible to do this without an API directly into the content).</p>
<h2>OK, so which Headless CMS then?</h2>
<p>Googling for Headless CMS sets you on a path of choosing and evaluating many! We took the time to make a comparison of the top ones we found and please feel free to review our own comparison and feedback your experiences as well.</p>
<iframe src="https://docs.google.com/spreadsheets/d/e/2PACX-1vSyVAQJZ5l5e_e_bxf8Ahu7c9_RJzDDUMWau4mWSteJ12-loWOt79fqiiw5gLV5jPMkV9DPqM5s9blm/pubhtml?widget=true&amp;headers=false"></iframe>
<p><a href="https://docs.google.com/spreadsheets/d/e/2PACX-1vSyVAQJZ5l5e_e_bxf8Ahu7c9_RJzDDUMWau4mWSteJ12-loWOt79fqiiw5gLV5jPMkV9DPqM5s9blm/pubhtml" target="_blank" rel="noopener noreferrer">Full Page showing Prismic, Contentful, GraphCMS, DatoCMS, Ghost, Strapi comparison</a></p>
<p>We settled on Strapi as it provided us with a future-proofed content system with a friendly UX. We had also been burnt by lock-in from other CMS providers. You can read about our (very historic article) of why we shifted from Tumblr to Medium <a href="https://abletech.nz/resource/migrating-to-medium-from-tumblr/">here</a>; and Medium certainly had a lot of advantages (the editing UX is the best we have found and supports commenting). It is however rather opinionated in what it lets you do. Try putting formatted  code blocks and using it for other application content. The main reason we had to leave, however,  was due to SEO.</p>
<h2>SEO Matters</h2>
<p>Medium changed its business model and policies since we first started using it. They now don’t support separate domain hosting (although we were grandfathered so technically not a problem for us), and Medium has removed SEO advantages of publishing content on Medium to your external services/product websites (they don’t transfer the google juice to you and prefer to keep it to themselves!). The business model they are working to is luring people into their Medium content.</p>
<p>As a technical example of what they are doing, you can take a look at how links that are published in a Medium article (a hyperlink being an important part of how the internet works!) and notice that they have a no-follow attribute. This means that although you can link off to external websites from your stories, Medium has instructed search engines not to go to (follow) that site and ignore it for SEO purposes. So being hosted on Medium did nothing to boost the SEO of abletech.nz and addressfinder.nz sites! So bye bye Medium from us</p>
<pre><code class="hljs">&lt;a <span class="hljs-attribute">href</span>=<span class="hljs-string">&quot;external link&quot;</span> <span class="hljs-attribute">rel</span>=<span class="hljs-string">&quot;.. nofollow&quot;</span> <span class="hljs-attribute">target</span>=<span class="hljs-string">&quot;_blank&quot;</span>&gt;
</code></pre>
<blockquote>
<p>There is still no silver bullet in great rich headless content management</p>
</blockquote>
<p>As I write this content, I am doing it outside of our CMS. Why? The user experience is not up to scratch for the requirements I have over editing content. I need spell correction, the ability to review, suggest changes,  and comment on content, as well as options for simultaneous edits on the same document. We are also tied to MarkDown, this is fine for developers but a hindrance for non-technical authors. We were unable to find a headless CMS that satisfied our requirements around authoring experience (perhaps Abletech should build a new Headless CMS and we certainly have considered this!).</p>
<h3>Other things to look for</h3>
<ul>
<li>Strong UX</li>
<li>Support for your own content types</li>
<li>Support for multiple users / roles / permissions</li>
<li>Support for tables, graphs, rich HTML, code sniplets</li>
<li>Realistic pricing plans for your business</li>
<li>Ability to export your data out should you choose to leave</li>
<li>Rich media support</li>
<li>Commenting on drafts - resolving comments</li>
<li>Image resizing, searching, tagging</li>
</ul>
<h3>Additional CMS requirements</h3>
<h4>Versioned content</h4>
<p>Although our goal is for business people to add business content to our dynamic content, we shouldn’t forget about software best practices either.  Allowing your publishers to publish straight to a content website is one thing, but when inserting content into an application, giving publishers the ability to publish without putting the content through your development pipeline is a risky business. We have seen too often undesirable side effects of moving content out of control of normal build/QA/pipelined processes and into the hands of content publishers only to break UX, or worse, the application. Problems can arise from:
Content that does not fit or changes the layout due to not being tested within the application before publishing to production
Content that is subsequently unpublished, breaking a dependency that is required for the execution of the application.</p>
<p>Ensure you have thought through a robust pipeline of testing changes before allowing those changes to hit production. Your content changes should traverse your normal pipeline including testing phases (integration tests, UI device tests, accessibility tests), QA, and then be rolled out to production. To roll out multiple changes (or a change set) may require your CMS to support versioned content - something that seems to be missing from most CMS’s. In some instances, we have needed to devise our own robust pipelines for dealing with these challenging constraints.</p>
<h4>Open Source</h4>
<p>Choosing a system that is open source provides you with options to extend/enrich any missing features and also give you security over service. Ask yourself if the CMS provider will still be around in five years, will they disappear, merge, discontinue the product? Even with Strapi, however,  there are hooks in the code to allow the organisation to further monetise the offering (even though they say they won’t!)</p>
<h4>Serverless</h4>
<p>If I had my way I would choose a platform that leveraged the FAAS (function as a service such as AWS Lambda). Our CMS’s can be costly to host and run and are often little used except for the publishing and build phases. This would reduce the cost substantially (as well as reduce the environmental impact).</p>
<h2>Wrapping up</h2>
<p>It is also important to realise that you will have your own specific requirements that need to be addressed. Please share any feedback to hello@abletech.nz and also do get in touch if you need help determining the best fit for your content management needs.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>What App Should You Develop?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>Navigating the choices for building front ends can be daunting. Like everything technology based, you are faced with a bunch of technology choices and tradeoffs. This article attempts to distill your <strong>app development</strong> options and outline the various pros and cons.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qwod59syrdeopzdi2oqsz0ho" alt="" data-big=https://cms-assets.abletech.nz/large_design_for_mobile_dca3f37d2e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/design_for_mobile_dca3f37d2e.png" srcset="https://cms-assets.abletech.nz/large_design_for_mobile_dca3f37d2e.png 1000w, https://cms-assets.abletech.nz/small_design_for_mobile_dca3f37d2e.png 500w, https://cms-assets.abletech.nz/medium_design_for_mobile_dca3f37d2e.png 750w, https://cms-assets.abletech.nz/thumbnail_design_for_mobile_dca3f37d2e.png 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<p>Before making any decisions, it is important to understand both what the end-user experience is and your marketing objectives:</p>
<ul>
<li>Do you need an App Store presence?</li>
<li>Will the App be used across desktop and mobile devices?</li>
<li>Do you already have a responsive web application?</li>
<li>What is your budget and time constraints for delivery?</li>
</ul>
<p>As well as user and business concerns, there are other technical questions that need considering:</p>
<ul>
<li>What is the capability of your development team - do they know how to build a web app or native app?</li>
<li>Does your application have an API that is optimized around building a front end?</li>
<li>Are there Native specific features that you require?</li>
<li>What specific libraries do you require, and are these available for your chosen technology?</li>
<li>Do you want offline-only capabilities?</li>
<li>What Native Only features do you need access to?</li>
</ul>
<p>Answering these questions will help guide your decisions and move forward with your app development  strategy.</p>
<h2>Web App Development Process</h2>
<p>What are the different options in building a front end mobile application?
<strong>Below, we discuss the different mobile web app development processes and technology options available:</strong></p>
<h3>Responsive Web application</h3>
<p>A responsive web application is simply a desktop web application that knows how to behave on a mobile device. The codebase is usually (but not necessarily) shared between responsive versions and desktop versions, and the server side code and business logic is reused in both instances. This is normally the fastest and most economic way to deliver mobile experiences. Any web application development project where mobile might be used to navigate the site should support a Mobile First design approach</p>
<h3>Progressive Web App Development</h3>
<p>A Progressive Web application (PWA) is an extension of a responsive web application; with the difference being that the web application can be bundled and launched from the phone App (not via opening the browser). Apple’s App store does not support PWA as a first class App, but with Google’s Play Store you can deploy a progressive web app. The main advantage of a progressive web app is presence in the Play Store. If you have solid channels to reach your customers, then bookmarking your Responsive Web page is a good option and removes the overhead of the Play Store deployment process.</p>
<h3>Native application</h3>
<p>When you compare progressive web apps vs native apps - building native applications implies you are going full-in on the App Store and Play Store deployments. Don’t underestimate the effort required to polish and submit an App through these channels. You can also find yourself up against commercial obligations if your App has any e-commerce characteristics especially with the App Store.</p>
<p>The advantages, though, are many. You get full exposure on the App Store(s), access to all Native features as well as unlocking performance benefits from the native device.</p>
<h4>Use a Native technology</h4>
<p>On iOS you can choose Swift, or Objective C. The support on stack overflow is amazing and most integration with Native technology has been solved many times before with ample examples within the online documentation and support forums.</p>
<p>On Android, Java, or Kotlin is the go-to. As Java has been around for a while, its community support is more widespread, although Kotlin is definitely a more pleasing language to develop in and community support is growing.</p>
<h3>What about Cross Platform technologies?</h3>
<p>There are other options too if you don’t want the overhead of maintaining two separate code bases. Several technologies exist to allow you to write the code once(ish) and deploy to both the App Store and Play Store.</p>
<p>The mainstream choices (as of 2022) are React Native or Dart/Flutter.</p>
<p><strong>React Native</strong>: The React Native technology allows you to leverage React knowledge, and apply that knowledge to building Android and iOS applications using the same/similar code base. Both Android and iOS have a large number of supporting components that abstract Native features so there should be little need to write your own Native adapter.</p>
<p>Building up the UI is surprisingly like creating a standard Web application but renders Native UI components. You can also write business and API integration logic in Javascript/Typescript and share this between applications. Other Javascript advantages then come into play, you can pull in from a vast array of NPM packages; and provided they can run on the Javascript runtimes of Android, and iOS (iOS uses Javascript Core and misses a few features which need to be worked around or Shimmed).</p>
<p><strong>Dart and Flutter</strong>: Google has tried to solve multi platform <strong>App development</strong> as well. Using the Dart programming language, they created the Flutter platform. Experience shows less overhead / DeviceOps of cross platform development, however the lack of support of pulling in supporting libraries from the NPM package base might be a problem for some use cases (You can’t execute javascript code inside your Dart language).</p>
<p>With both React Native and Flutter approaches, the drawbacks are in the packaging, and getting the App to run across your development environment, the iOS device, and the Android device.</p>
<p>Although code can be reused, be prepared that you will no doubt need hooks in your code to deal with differences in underlying features of the device. Even though these platforms try to abstract out the differences in the API’s, not all features/properties and events are available on the different platforms so as a developer you are not completely isolated from the differences.</p>
<p>Deploying to the App/Play stores is a very Native experience and in all technologies there seem to be no shortcuts in the pain to packaging, signing and versioning a well-polished product.</p>
<h3>Shortcut Options - HTML to the rescue</h3>
<blockquote>
<p>A common approach generally used to bridge the gap between building a fully featured mobile application and leveraging a responsive website is to build a hybrid application that uses Native to gain App/Play store presence, and provide access to Native features such as notifications, high performance QR scanning, state persistence, and either embed an authenticated WebView into your Native App, or launch out of your Native App to an authenticated session in the devices browser.</p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="te9vtwfw1sy2wcnvha93ftsu" alt="" data-big=https://cms-assets.abletech.nz/large_app_stores_08977befff.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/app_stores_08977befff.png" srcset="https://cms-assets.abletech.nz/large_app_stores_08977befff.png 1000w, https://cms-assets.abletech.nz/small_app_stores_08977befff.png 500w, https://cms-assets.abletech.nz/medium_app_stores_08977befff.png 750w, https://cms-assets.abletech.nz/thumbnail_app_stores_08977befff.png 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<p>You can iterate on this and move more functionality into your Native App over time. Examples of this include: rendering graphs and reports that you have already built in Web into your Native App and rarely used features, such as profile or settings, that you can bring into your Native application via a web view.</p>
<h3>Talk about Touch?</h3>
<p>When designing for mobile/tablets, your UX must be aware of some web features that are not available on mobile. Some web features such as Hover don’t exist on Native, and Native has a range of new features that don’t exist in web such as swiping, pinching, orientation, device themes, location, and vibration which all can change the UX between your Web world and your Native world.</p>
<h3>And what about Tablets?</h3>
<p>Tablets again can have quite a different user experience than a mobile. Mobile Apps generally don’t look great on the Tablet's larger device screen dimensions, and the use cases on a tablet often differ from mobile (and again for desktop!). Think about what your users are doing in each of the user contexts; are they on the couch, bus, walking, with internet or without?</p>
<h3>How about testing?</h3>
<p>Writing suitable tests around your codebase is much harder on UI components than traditional web applications but is equally important. Look into cloud testing frameworks like BrowserStack which offer multi-device testing for device coverage and executing your test suite. Ensure you have adequate crash reporting enabled within your application, and keep an eye on crash reports as you roll out new versions.</p>
<p>Native applications require an app store approval process for releases, and then require existing users to update to the new version of the app. If you discover bugs in your application, there may be users that continue to run the old version of the app despite having a new version available. Other complications can arise with maintaining version compatible APIs with all the client applications. Building in a forced upgrade feature into your app is a great idea - from the start.</p>
<h2>Summary</h2>
<p>This article has only touched the surface on the concerns and tradeoffs in navigating your options in choosing the appropriate technology for your unique requirements.</p>
<p>It’s important to work with a <a href="https://abletech.nz/services/development/">web app development company</a> that has the right team, support and experience in platform-specific design and development - helping you choose the right web app development process early on.</p>
<p><a href="https://abletech.nz/contact">Please do get in touch</a> to discuss your requirements, and provide assistance in guiding and supporting your business’s mobile strategy.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Lighthouse Funds - Going Retail (Part 3)</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>JAMstack with Next.js, Netlify and Prismic CMS</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s6dfp8zkrymffyg5o5esjatn" alt="" data-big=https://cms-assets.abletech.nz/large_Screen_Shot_2022_03_18_at_10_43_56_AM_c781a349af.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Screen_Shot_2022_03_18_at_10_43_56_AM_c781a349af.png" srcset="https://cms-assets.abletech.nz/large_Screen_Shot_2022_03_18_at_10_43_56_AM_c781a349af.png 1000w, https://cms-assets.abletech.nz/small_Screen_Shot_2022_03_18_at_10_43_56_AM_c781a349af.png 500w, https://cms-assets.abletech.nz/medium_Screen_Shot_2022_03_18_at_10_43_56_AM_c781a349af.png 750w, https://cms-assets.abletech.nz/thumbnail_Screen_Shot_2022_03_18_at_10_43_56_AM_c781a349af.png 245w" data-zooming-width="1000" data-zooming-height="472" loading="lazy" width="1000" height="472"></figure>
</div>
<p><em>The new <a href="https://lighthousefunds.nz/" target="_blank" rel="noopener noreferrer">Lighthouse Funds</a> site</em></p>
<p>This article is the last  of a three-part series. Make sure you’ve read <a href="https://abletech.nz/article/lighthouse-funds-going-retail-part-1/">part one</a> and <a href="https://abletech.nz/article/lighthouse-funds-going-retail-part-2/">part two</a> before reading on!</p>
<h2>The Netlify Platform</h2>
<h3>“Netlify it all”</h3>
<p>Netlify describes itself as “An intuitive Git-based workflow and powerful serverless platform to build, deploy, and collaborate on web apps”</p>
<p>The Lighthouse Funds UI is utilising three of the Netlify products:</p>
<ul>
<li>
<p>Build — Automated builds &amp; deployment</p>
</li>
<li>
<p>Edge — A global network to serve your front-end</p>
</li>
<li>
<p>Functions — Scalable serverless functions</p>
</li>
</ul>
<h3>Netlify Build</h3>
<p>Netlify Build makes use of the following modern web development practices:</p>
<p><strong>Atomic deployment</strong></p>
<ul>
<li>
<p>Make updates available only when they are complete and totally in place</p>
</li>
<li>
<p>The new version of the site only begins receiving requests when all assets and configurations are available</p>
</li>
</ul>
<p><strong>Immutable deployment</strong></p>
<ul>
<li>
<p>Guarantee the integrity of previous deploys by insulating them from future actions</p>
</li>
<li>
<p>Paired with atomic deploys, immutable deploys make it possible for sites to enjoy functionality such as instant rollbacks and versioning</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tg9hjsb3vchi6wdv6j9hntmf" alt="" data-big=https://cms-assets.abletech.nz/large_image5_5491db3f88.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/image5_5491db3f88.png" srcset="https://cms-assets.abletech.nz/large_image5_5491db3f88.png 1000w, https://cms-assets.abletech.nz/small_image5_5491db3f88.png 500w, https://cms-assets.abletech.nz/medium_image5_5491db3f88.png 750w, https://cms-assets.abletech.nz/thumbnail_image5_5491db3f88.png 245w" data-zooming-width="1000" data-zooming-height="487" loading="lazy" width="1000" height="487"></figure>
</div>
<p><em>Image source: <a href="https://www.netlify.com/products/build/" target="_blank" rel="noopener noreferrer">https://www.netlify.com/products/build/</a></em></p>
<p>For the Lighthouse Funds site, Netlify automatically builds and deploys site previews based on Github pull requests. For a successful build of a commit within a PR, a deploy preview will be generated.</p>
<p>Netlify also automatically builds and deploys to the staging (development) environment when a pull request is merged into the staging branch, and in the same fashion, to the production environment when staging is merged into the main branch.</p>
<h3>Netlify Edge</h3>
<p>“A fast, resilient network for web apps. Connected to your development workflow and designed to handle the most complex tasks — or even to run your own custom logic.”</p>
<p>Standard content delivery networks (CDNs) fundamentally function as local asset dumps. Your images, videos, and other downloadable content is stored in numerous CDN nodes around the world, and ensure that when your browser requests assets, that they come from the closest server, not from halfway across the globe. They also provide high uptime — if one server goes down, traffic is rerouted to the next closest server. This helps prevent the severity of events like DDoS attacks.</p>
<p>Netlify Edge takes this concept even further by hosting your entire site on CDN. This means the HTML, JS and CSS etc. are also stored in numerous locations around the world. The Edge CDN utilises logic layers that add extra control — allowing instant cache invalidation and rollbacks, providing consistent site state through atomic deploys, and shaping traffing through custom rewrite, redirect and proxy rules.</p>
<p>In this project, I valued the Git-integrated CI/CD pipeline that Edge provides, and the easy file-based configuration that lives alongside the code in our Git repository. Having almost everything in one place (Git) keeps it simple and clear to anyone picking up the project in the future.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="a016u4z25z6bi3bngsn1okkt" alt="" data-big=https://cms-assets.abletech.nz/large_image17_391582c70d.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/image17_391582c70d.png" srcset="https://cms-assets.abletech.nz/large_image17_391582c70d.png 1000w, https://cms-assets.abletech.nz/small_image17_391582c70d.png 500w, https://cms-assets.abletech.nz/medium_image17_391582c70d.png 750w, https://cms-assets.abletech.nz/thumbnail_image17_391582c70d.png 245w" data-zooming-width="1000" data-zooming-height="424" loading="lazy" width="1000" height="424"></figure>
</div>
<p><em>Image source: <a href="https://www.netlify.com/products/edge/" target="_blank" rel="noopener noreferrer">https://www.netlify.com/products/edge/</a></em></p>
<h3>Netlify Functions</h3>
<p>Netlify Functions are solely server-side code, for things like creating API endpoints, running code automatically in response to certain events, or processing complex jobs in the background.</p>
<p>When used with Next.js, Netlify creates a Function for each Next.js page that requires one.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="i3ymzbuydgg35j7dd83otai8" alt="" data-big=https://cms-assets.abletech.nz/large_image15_446984ac99.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 174px" src="https://cms-assets.abletech.nz/image15_446984ac99.png" srcset="https://cms-assets.abletech.nz/large_image15_446984ac99.png 1000w, https://cms-assets.abletech.nz/small_image15_446984ac99.png 500w, https://cms-assets.abletech.nz/medium_image15_446984ac99.png 750w, https://cms-assets.abletech.nz/thumbnail_image15_446984ac99.png 174w" data-zooming-width="1000" data-zooming-height="894" loading="lazy" width="1000" height="894"></figure>
</div>
<p><em>Netlify Build log</em></p>
<p>You’ll see in this image, that there are icons next to each entry in the log. The lambda icon represents pages or APIs that require a function. The hollow circle represents a Statically Generated page with no props (JSON data), and the solid circle represents a Statically Generated page that does have props (JSON data).</p>
<h2>To sum it all up</h2>
<h3>Operating costs</h3>
<p><strong>Prismic</strong></p>
<p>We are on the Starter Developer plan, which is $84.00 USD ($121.32 NZD*)  annually, or $7.00 USD ($10.11 NZD*) per month. This gives us three users, but is otherwise exactly the same as the free Community plan (one user) and the $180.00 USD Small plan (seven users). These Developer plans include unlimited API calls, documents, custom types, locales, assets, and image optimisations, as well as 100 GB bandwidth per month, served via an AWS Cloudfront Edge CDN. We use on average 12.6 MB of the 100 GB.</p>
<p><strong>AWS S3</strong></p>
<p>S3 Standard Pricing starts at $0.025 USD per GB, and the public bucket storing the documents contains 27.5 MB of data, meaning the monthly cost for storage is $0.0006875 USD ($0.00099 NZD*). This totals $0.01188 NZD annually, so is essentially negligible.</p>
<p><strong>Mailchimp</strong></p>
<p>The Essentials Marketing plan is billed at $19.55 NZD per month ($234.60 NZD annually). The main driver for upgrading from the Free plan was the extra seats — three versus one — and the ability to remove Mailchimp branding from emails sent to audience members.</p>
<p><strong>Netlify</strong></p>
<p>We are on the free Starter plan which gives us one team member, and 100GB of bandwidth and 300 build minutes per month. With the current traffic and size of the site, we don’t even get close to our bandwidth limit. We never exceeded the build minute limit during the development phase, and have used less than 30 minutes per month on Prismic and manually initiated rebuilds since we went live in May. For now, the free plan is more than sufficient, but if we start a new project phase in the future with server-side rendering requirements, we may need to upgrade.</p>
<p><strong>Total</strong></p>
<p>All up, this site costs $29.66 NZD per month, or $355.92 NZD annually, to operate.</p>
<p>Note: as the domains were purchased in 2015 and used ever since, these ongoing costs haven’t been included in the operating costs for the new site build.</p>
<h3>Maintenance</h3>
<p>Currently, the only developer time needed to maintain the site is when monthly factsheets or quarterly reports need to be added to the AWS S3 bucket, which then requires a manual Netlify rebuild. There are plans to streamline this process by adding an upload API to the admin dashboard (built as part of the Rails app), which would call a webhook and trigger a rebuild of the site. This would allow Lighthouse Funds to own this process themselves. The same could be done for adding unit prices from the admin dashboard, which are used for the interactive chart on the home page.</p>
<h3>Looking ahead</h3>
<p>I am confident Next.js and the supporting tools and technologies we have chosen will provide a strong foundation for future features and/or project phases. The documentation and code examples are excellent, there is lots of support and knowledge available in the developer community, and the server-side rendering option gives us the ability to build dynamic and user-specific content.</p>
<h3>A word from Lighthouse Funds</h3>
<blockquote>
<h4>It was a pleasure working with the Abletech team on our new website. We’re especially happy with the ability to easily self-publish blog posts. It’s a big step-up from our old site.</h4>
<h4>— Mark Donnell, Founder &amp; Investment Manager</h4>
</blockquote>
<p>If you’re interested in finding out more about Lighthouse Funds, take a look into the <a href="https://lighthousefunds.nz/our-managed-fund" target="_blank" rel="noopener noreferrer">Fund details</a>, or follow <a href="https://lighthousefunds.nz/get-started" target="_blank" rel="noopener noreferrer">these steps to get started</a>.</p>
<p>*As at 13 October 2021</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Lighthouse Funds - Going Retail (Part 2)</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>JAMstack with Next.js, Netlify and Prismic CMS</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wthmz1yjw0euk6oue0gd32qy" alt="" data-big=https://cms-assets.abletech.nz/large_Screen_Shot_2022_03_18_at_10_44_20_AM_bd867ce6a1.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Screen_Shot_2022_03_18_at_10_44_20_AM_bd867ce6a1.png" srcset="https://cms-assets.abletech.nz/large_Screen_Shot_2022_03_18_at_10_44_20_AM_bd867ce6a1.png 1000w, https://cms-assets.abletech.nz/small_Screen_Shot_2022_03_18_at_10_44_20_AM_bd867ce6a1.png 500w, https://cms-assets.abletech.nz/medium_Screen_Shot_2022_03_18_at_10_44_20_AM_bd867ce6a1.png 750w, https://cms-assets.abletech.nz/thumbnail_Screen_Shot_2022_03_18_at_10_44_20_AM_bd867ce6a1.png 245w" data-zooming-width="1000" data-zooming-height="472" loading="lazy" width="1000" height="472"></figure>
</div>
<p><em>The new <a href="https://lighthousefunds.nz/" target="_blank" rel="noopener noreferrer">Lighthouse Funds</a> site</em></p>
<p>This article is the second of a three-part series. Make sure you’ve read <a href="https://abletech.nz/article/lighthouse-funds-going-retail-part-1/">part one</a> before reading on!</p>
<h2>Prismic CMS: The Headless Website Builder for JAMstack</h2>
<p>Prismic is a Headless CMS, also known as an API CMS. This is a way to author content, but instead of having your content coupled to a particular output, it provides your content as data over an API.</p>
<p>We chose Prismic because it was built with developer productivity in mind. It provides the primary function of a CMS — delivering content — in a straightforward and efficient manner.</p>
<p>The visual builder to model content is simple, but if you work better in JSON, there’s an option for that too. Once these types are defined, it’s easy to handover to content creators, who can use the drag and drop type components and rich text WYSIWYG editor to customise a page or post.</p>
<p>There are 5 steps to setting up your Prismic repository:</p>
<h3>1. Modeling your custom types</h3>
<p>I created a number of single-use and reusable custom types that would be applicable for each blog post. Nothing is required in Prismic, nor do they have validation rules for any of their fields — read more about that <a href="https://prismic.io/blog/required-fields" target="_blank" rel="noopener noreferrer">here</a>.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lu8rs72wywe5wcy5urbz47dw" alt="" data-big=https://cms-assets.abletech.nz/large_1_fy_O_Osil0_Ar6_Mx_N_Skyr_TT_Q_2b3df5b58b.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_fy_O_Osil0_Ar6_Mx_N_Skyr_TT_Q_2b3df5b58b.png" srcset="https://cms-assets.abletech.nz/large_1_fy_O_Osil0_Ar6_Mx_N_Skyr_TT_Q_2b3df5b58b.png 1000w, https://cms-assets.abletech.nz/small_1_fy_O_Osil0_Ar6_Mx_N_Skyr_TT_Q_2b3df5b58b.png 500w, https://cms-assets.abletech.nz/medium_1_fy_O_Osil0_Ar6_Mx_N_Skyr_TT_Q_2b3df5b58b.png 750w, https://cms-assets.abletech.nz/thumbnail_1_fy_O_Osil0_Ar6_Mx_N_Skyr_TT_Q_2b3df5b58b.png 245w" data-zooming-width="1000" data-zooming-height="349" loading="lazy" width="1000" height="349"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nx9u4pih2d6f4z2hjswfomu1" alt="" data-big=https://cms-assets.abletech.nz/large_1_Xx_Ae1yai_FR_9h_UKACSW_Gf_HQ_ea1516609e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Xx_Ae1yai_FR_9h_UKACSW_Gf_HQ_ea1516609e.png" srcset="https://cms-assets.abletech.nz/large_1_Xx_Ae1yai_FR_9h_UKACSW_Gf_HQ_ea1516609e.png 1000w, https://cms-assets.abletech.nz/small_1_Xx_Ae1yai_FR_9h_UKACSW_Gf_HQ_ea1516609e.png 500w, https://cms-assets.abletech.nz/medium_1_Xx_Ae1yai_FR_9h_UKACSW_Gf_HQ_ea1516609e.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Xx_Ae1yai_FR_9h_UKACSW_Gf_HQ_ea1516609e.png 245w" data-zooming-width="1000" data-zooming-height="427" loading="lazy" width="1000" height="427"></figure>
</div>
<p><em>Single-use types</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="x2i1dsa5zcgl8jb2v1yk9y03" alt="" data-big=https://cms-assets.abletech.nz/large_1_Nio_MVL_Mmvx1_Vp_Xgxewq_Fo_Q_699c017af1.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Nio_MVL_Mmvx1_Vp_Xgxewq_Fo_Q_699c017af1.png" srcset="https://cms-assets.abletech.nz/large_1_Nio_MVL_Mmvx1_Vp_Xgxewq_Fo_Q_699c017af1.png 1000w, https://cms-assets.abletech.nz/small_1_Nio_MVL_Mmvx1_Vp_Xgxewq_Fo_Q_699c017af1.png 500w, https://cms-assets.abletech.nz/medium_1_Nio_MVL_Mmvx1_Vp_Xgxewq_Fo_Q_699c017af1.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Nio_MVL_Mmvx1_Vp_Xgxewq_Fo_Q_699c017af1.png 245w" data-zooming-width="1000" data-zooming-height="306" loading="lazy" width="1000" height="306"></figure>
</div>
<p><em>Reusable types</em></p>
<h3>2. Creating content based on those types</h3>
<p>Once the initial setup was complete, it was easy enough for anyone to start populating content. I’ve handed over to the product owner to create the blog posts, meaning there is now zero development time spent on creating and publishing blog posts to the live site.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="oa3ote2xvt4bs5ppzec21kff" alt="" data-big=https://cms-assets.abletech.nz/medium_1_yj0_WB_Yjm_JO_6_F_xj_Qn_J35_MQ_86a7111482.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_yj0_WB_Yjm_JO_6_F_xj_Qn_J35_MQ_86a7111482.png" srcset="https://cms-assets.abletech.nz/small_1_yj0_WB_Yjm_JO_6_F_xj_Qn_J35_MQ_86a7111482.png 500w, https://cms-assets.abletech.nz/medium_1_yj0_WB_Yjm_JO_6_F_xj_Qn_J35_MQ_86a7111482.png 750w, https://cms-assets.abletech.nz/thumbnail_1_yj0_WB_Yjm_JO_6_F_xj_Qn_J35_MQ_86a7111482.png 245w" data-zooming-width="750" data-zooming-height="428" loading="lazy" width="750" height="428"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bi1kw5si3tzafnkx5dwudhpc" alt="" data-big=https://cms-assets.abletech.nz/medium_1_O0dpf1_Wf54d_Er68_Tgvh_Z0w_489f4b2ff8.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_O0dpf1_Wf54d_Er68_Tgvh_Z0w_489f4b2ff8.png" srcset="https://cms-assets.abletech.nz/small_1_O0dpf1_Wf54d_Er68_Tgvh_Z0w_489f4b2ff8.png 500w, https://cms-assets.abletech.nz/medium_1_O0dpf1_Wf54d_Er68_Tgvh_Z0w_489f4b2ff8.png 750w, https://cms-assets.abletech.nz/thumbnail_1_O0dpf1_Wf54d_Er68_Tgvh_Z0w_489f4b2ff8.png 245w" data-zooming-width="750" data-zooming-height="432" loading="lazy" width="750" height="432"></figure>
</div>
<h3>3. Configuring Prismic within your app and build process</h3>
<p>As we’d already chosen Next.js, it was convenient that Prismic had a <a href="https://prismic.io/nextjs" target="_blank" rel="noopener noreferrer">Next-specific guide</a>.</p>
<p>At the time of implementation, Prismic’s Slice Machine functionality wasn’t an offering. All I needed to do was:</p>
<p><code>yarn global add prismic-cli</code></p>
<p>Then I installed the “Sample blog” project, to use as a starting point. This provided me with some basic pages for listing all the posts, and showing a single post. It also included the JSON structure for some custom types, which I ended up refactoring, and then used them to edit my own custom types.</p>
<h3>4. Querying the content</h3>
<p>The sample project included a configuration file, where I could declare the API endpoint for our repository, and the access token.</p>
<pre><code class="hljs language-js"><span class="hljs-comment">// -- Prismic API endpoint</span>
<span class="hljs-comment">// Determines which repository to query and fetch data from</span>
<span class="hljs-comment">// Configure your site&#x27;s access point here</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> apiEndpoint = <span class="hljs-string">`<span class="hljs-subst">${process.env.PRISMIC_API_ENDPOINT}</span>`</span>

<span class="hljs-comment">// -- Access Token if the repository is not public</span>
<span class="hljs-comment">// Generate a token in your dashboard and configure it here if your repository is private</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> accessToken = <span class="hljs-string">`<span class="hljs-subst">${process.env.PRISMIC_ACCESS_TOKEN}</span>`</span>
</code></pre>
<p><em>~/prismic-configuration.js</em></p>
<p>It also included some utilities such as helpers that instantiated a <a href="https://www.npmjs.com/package/@prismicio/client" target="_blank" rel="noopener noreferrer">Prismic client</a>, which is used to fetch documents from the Prismic repository.</p>
<pre><code class="hljs language-js"><span class="hljs-comment">// Client method to query documents from the Prismic repo</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title function_">Client</span> = (<span class="hljs-params">req = <span class="hljs-literal">null</span></span>) =&gt;
  <span class="hljs-title class_">Prismic</span>.<span class="hljs-title function_">client</span>(apiEndpoint, <span class="hljs-title function_">createClientOptions</span>(req, accessToken))

<span class="hljs-keyword">const</span> <span class="hljs-title function_">createClientOptions</span> = (<span class="hljs-params">req = <span class="hljs-literal">null</span>, prismicAccessToken = <span class="hljs-literal">null</span></span>) =&gt; {
  <span class="hljs-keyword">const</span> reqOption = req ? { req } : {}
  <span class="hljs-keyword">const</span> accessTokenOption = prismicAccessToken ? { <span class="hljs-attr">accessToken</span>: prismicAccessToken } : {}
  <span class="hljs-keyword">return</span> {
    ...reqOption,
    ...accessTokenOption,
  }
}
</code></pre>
<p><em>~/utils/prismicHelpers.js</em></p>
<p>The client has a number of <a href="https://prismic.io/docs/technologies/how-to-query-the-api-javascript" target="_blank" rel="noopener noreferrer">helper functions</a> that allow us to query documents by different attributes, such as the UID, type, tag, date, or a specific field defined in the custom types.</p>
<p>As I built our blog post component — and it’s routes — around the UID, I used the <code>getByUID</code> helper function like so:</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getStaticProps</span>(<span class="hljs-params">{ params, preview = <span class="hljs-literal">null</span>, previewData = {} }</span>) {
  <span class="hljs-keyword">const</span> { ref } = previewData

  <span class="hljs-keyword">const</span> post = (<span class="hljs-keyword">await</span> <span class="hljs-title class_">Client</span>().<span class="hljs-title function_">getByUID</span>(<span class="hljs-string">&#x27;post&#x27;</span>, params.<span class="hljs-property">uid</span>, ref ? { ref } : <span class="hljs-literal">null</span>)) || {}

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      preview,
      post,
    },
  }
}
</code></pre>
<p><em>~/pages/blog/[uid].js</em></p>
<h3>5. Deployment</h3>
<p>The project is deployed on Netlify — which I detail further down. I needed to add the two environment variables used in the configuration file mentioned above: <code>PRISMIC_API_ENDPOINT</code> and <code>PRISMIC_ACCESS_TOKEN</code>. These can be created in the Build &amp; Deploy settings.</p>
<p>I then set up two build hooks (also created in the Build &amp; Deploy setting) for triggering a site rebuild when new posts are published to both our staging and production environments.</p>
<p>I copied the URL that the Netlify UI created, and provided it to Prismic under the Webhooks settings:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qshpm4mluofprgjwbgjgghzr" alt="" data-big=https://cms-assets.abletech.nz/large_1_2f_Ph4_C3_S_Mbrc75_Fr_M9_R_Kw_45ed7ccc9a.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_2f_Ph4_C3_S_Mbrc75_Fr_M9_R_Kw_45ed7ccc9a.png" srcset="https://cms-assets.abletech.nz/large_1_2f_Ph4_C3_S_Mbrc75_Fr_M9_R_Kw_45ed7ccc9a.png 1000w, https://cms-assets.abletech.nz/small_1_2f_Ph4_C3_S_Mbrc75_Fr_M9_R_Kw_45ed7ccc9a.png 500w, https://cms-assets.abletech.nz/medium_1_2f_Ph4_C3_S_Mbrc75_Fr_M9_R_Kw_45ed7ccc9a.png 750w, https://cms-assets.abletech.nz/thumbnail_1_2f_Ph4_C3_S_Mbrc75_Fr_M9_R_Kw_45ed7ccc9a.png 245w" data-zooming-width="1000" data-zooming-height="386" loading="lazy" width="1000" height="386"></figure>
</div>
<p><em>Webhooks set up in the Lighthouse Funds Prismic repository — one for each environment.</em></p>
<p><strong>A powerful Prismic feature: Previews</strong></p>
<p><a href="https://prismic.io/docs/technologies/previews-nextjs" target="_blank" rel="noopener noreferrer">Previews</a> give content editors the ability to preview content without publishing it to the live site. The sample Next.js project included the two API files required to configure preview mode: <code>~/pages/api/preview.js</code> and <code>~/pages/api/exit-preview.js</code>.</p>
<p>The <code>/preview</code> API  generates a temporary and secure ref (a token that references a specific content version) for the preview session, and then sets a cookie with this token on the browser, and redirects the user to the preview URL.</p>
<p>The <code>/exit-preview</code> API clears this cookie. To access the functionality, the user needs to manually add <code>/api/exit-preview</code> to the URL in their browser.</p>
<p>I found this functionality particularly useful when testing out custom types in Prismic. I was also able to set up previews in our staging environment, meaning they could be easily shared outside of my local environment.</p>
<h2>The MailChimp Marketing API</h2>
<p>From the Lighthouse website point of view, Mailchimp is being solely used to manage audiences — to subscribe users to one of the audience groups — <a href="https://lighthousefunds.nz/blog" target="_blank" rel="noopener noreferrer">Insights</a> and <a href="https://lighthousefunds.nz/kiwisaver" target="_blank" rel="noopener noreferrer">KiwiSaver</a>.</p>
<p>I used Next.js API routes to create a <code>/subscribe</code> API that works together with a custom React component <code>MailChimpSignupForm.jsx</code> to pass a user’s email address and the Group ID (determined by which form/page the user is signing up from) to the Mailchimp Marketing API client, which is then used to add a member to the relevant Group.</p>
<p>Alongside the Prismic API routes inside the <code>~/pages/api</code> folder, I created a <code>subscribe.js</code> file, which sets some configuration for the <a href="https://www.npmjs.com/package/@mailchimp/mailchimp_marketing" target="_blank" rel="noopener noreferrer">Mailchimp Marketing client</a>, and then when called from the MailChimpSignupForm component, attempts to either add or update a list member in the given audience group:</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">MAILCHIMP_REGION</span> = process.<span class="hljs-property">env</span>.<span class="hljs-property">MAILCHIMP_API_KEY</span>.<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27;-&#x27;</span>)[<span class="hljs-number">1</span>]

mailchimp.<span class="hljs-title function_">setConfig</span>({
  <span class="hljs-attr">apiKey</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">MAILCHIMP_API_KEY</span>,
  <span class="hljs-attr">server</span>: <span class="hljs-variable constant_">MAILCHIMP_REGION</span>,
})

<span class="hljs-keyword">const</span> <span class="hljs-title function_">subscribe</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params">req, res</span>) =&gt; {
  <span class="hljs-keyword">const</span> { email, groupId } = req.<span class="hljs-property">body</span>

  <span class="hljs-keyword">if</span> (!email) {
    <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: <span class="hljs-string">&#x27;Email is required&#x27;</span> })
  }

  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// See: https://mailchimp.com/developer/marketing/api/list-members/add-or-update-list-member/</span>
    <span class="hljs-keyword">await</span> mailchimp.<span class="hljs-property">lists</span>.<span class="hljs-title function_">setListMember</span>(
      process.<span class="hljs-property">env</span>.<span class="hljs-property">MAILCHIMP_AUDIENCE_ID</span>,
      <span class="hljs-title function_">md5</span>(email.<span class="hljs-title function_">toLowerCase</span>()),
      {
        <span class="hljs-attr">email_address</span>: email,
        <span class="hljs-attr">status_if_new</span>: <span class="hljs-string">&#x27;subscribed&#x27;</span>,
        <span class="hljs-attr">status</span>: <span class="hljs-string">&#x27;subscribed&#x27;</span>,
        <span class="hljs-attr">interests</span>: {
          [groupId]: <span class="hljs-literal">true</span>,
        },
      }
    )

    <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">201</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: <span class="hljs-string">&#x27;&#x27;</span> })
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">const</span> { detail, status, title } = error.<span class="hljs-property">response</span>.<span class="hljs-property">body</span>

    <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(status).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: <span class="hljs-string">`<span class="hljs-subst">${title}</span>: <span class="hljs-subst">${detail}</span>`</span> })
  }
}
</code></pre>
<p><em>~/pages/api/suscribe.js</em></p>
<h2>AWS SDK / S3 Client</h2>
<p>Downloadable documents and forms are stored in an AWS S3 bucket. I used the <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/index.html" target="_blank" rel="noopener noreferrer">S3 Client</a> to fetch the monthly factsheets and the quarterly reports that are stored in this bucket.</p>
<p>I used credentials generated from a specific IAM user in AWS to configure the client, which are stored as environment variables in Netlify. There is a policy attached to this user which gives the user List and Read access to the bucket. The <code>ListObjectsV2Command</code> returns all (up to 1000) objects in a specified bucket. I filter the objects returned by the presence of a <code>.pdf</code> file format, and then determine the year and label (month or quarter, depending on the document type) from the object name, which follows a specific naming convention. From there, I build up an object to be injected into the Library page component as <code>props</code>.</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getStaticProps</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> monthlyFactsheetsParams = {
    <span class="hljs-title class_">Bucket</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">LIGHTHOUSE_PUBLIC_BUCKET</span>,
    <span class="hljs-title class_">Prefix</span>: <span class="hljs-string">&#x27;monthly-factsheets/&#x27;</span>,
  }
  <span class="hljs-keyword">const</span> quarterlyReportsParams = {
    <span class="hljs-title class_">Bucket</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">LIGHTHOUSE_PUBLIC_BUCKET</span>,
    <span class="hljs-title class_">Prefix</span>: <span class="hljs-string">&#x27;quarterly-reports/&#x27;</span>,
  }

  <span class="hljs-keyword">const</span> monthlyFactsheetsData = <span class="hljs-keyword">await</span> <span class="hljs-title function_">getFilesFromS3Bucket</span>(monthlyFactsheetsParams)

  <span class="hljs-keyword">const</span> quarterlyReportsData = <span class="hljs-keyword">await</span> <span class="hljs-title function_">getFilesFromS3Bucket</span>(quarterlyReportsParams)

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      monthlyFactsheetsData,
      quarterlyReportsData,
    },
  }
}
</code></pre>
<p><em>Data fetching function in ~/pages/library.js, making use of the getFilesFromS3Bucket function below</em></p>
<pre><code class="hljs language-js"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getFilesFromS3Bucket</span>(<span class="hljs-params">params</span>) {
  <span class="hljs-keyword">const</span> s3Client = <span class="hljs-keyword">new</span> <span class="hljs-title function_">S3Client</span>({
    <span class="hljs-attr">credentials</span>: {
      <span class="hljs-attr">accessKeyId</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">NEXT_PUBLIC_AWS_ACCESS_KEY_ID</span>,
      <span class="hljs-attr">secretAccessKey</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">NEXT_PUBLIC_AWS_SECRET_ACCESS_KEY</span>,
    },
    <span class="hljs-attr">region</span>: <span class="hljs-string">&#x27;ap-southeast-2&#x27;</span>,
  })

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> s3Client
    .<span class="hljs-title function_">send</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ListObjectsV2Command</span>(params))
    .<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> objects = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(<span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(data.<span class="hljs-property">Contents</span>))

      <span class="hljs-keyword">return</span> objects.<span class="hljs-title function_">reduce</span>(<span class="hljs-function">(<span class="hljs-params">acc, val</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (val.<span class="hljs-property">Key</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;.pdf&#x27;</span>)) {
          <span class="hljs-keyword">const</span> fileNameSegments = val.<span class="hljs-property">Key</span>.<span class="hljs-title function_">replace</span>(<span class="hljs-string">&#x27;.pdf&#x27;</span>, <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27;_&#x27;</span>)
          <span class="hljs-keyword">const</span> label = fileNameSegments[fileNameSegments.<span class="hljs-property">length</span> - <span class="hljs-number">2</span>]
          <span class="hljs-keyword">const</span> year = fileNameSegments[fileNameSegments.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>]

          <span class="hljs-keyword">if</span> (acc[year]) {
            acc[year] = { ...acc[year], [label]: val.<span class="hljs-property">Key</span> }
          } <span class="hljs-keyword">else</span> {
            acc[year] = { [label]: val.<span class="hljs-property">Key</span> }
          }
        }
        <span class="hljs-keyword">return</span> acc
      }, {})
    })
}
</code></pre>
<p><em>Reusable function to fetch files stored in the S3 bucket, and then build and return an object keyed by label (month or quarter) and then year</em></p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;2021&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;April&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;monthly-factsheets/2021/Lighthouse_Global_Equity_Fund-Looking_back_on_April_2021.pdf&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;August&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;monthly-factsheets/2021/Lighthouse_Global_Equity_Fund-Looking_back_on_August_2021.pdf&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;July&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;monthly-factsheets/2021/Lighthouse_Global_Equity_Fund-Looking_back_on_July_2021.pdf&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;June&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;monthly-factsheets/2021/Lighthouse_Global_Equity_Fund-Looking_back_on_June_2021.pdf&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;May&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;monthly-factsheets/2021/Lighthouse_Global_Equity_Fund-Looking_back_on_May_2021.pdf&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;September&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;monthly-factsheets/2021/Lighthouse_Global_Equity_Fund-Looking_back_on_September_2021.pdf&quot;</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p><em>Example data passed in as props to ~/pages/library.js</em></p>
<h2>Up next</h2>
<p>In <a href="https://abletech.nz/article/lighthouse-funds-going-retail-part-3/">part three</a> of this three-part series, I explain how the Netlify platform  is used to build and ship  the site with minimal configuration. I also touch on operating costs and ongoing maintenance, and why the tools and technologies we chose have set us up well for the future.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>What is Agile?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Software Engineering</h2>
<p>To understand how agile came to be popular in our industry it helps to understand what came before. When software came out of University Computer Laboratories and people with Computer Science degrees were working in companies using computers to build business applications, the industry looked to existing industries for inspiration of how to structure their work. The industry they chose was engineering. A number of principals were adopted from Engineering and applied to software projects.</p>
<h3>Get it right first time</h3>
<p>It is important to engineer a bridge so that it will not collapse when traffic volumes increase over the next 20 years and a Commercial Aeroplane must not come apart when under stress beyond what you could expect when flying in normal conditions.</p>
<p>Software was also expected to have no defects when used in production. If an error was found this was a major problem because software was delivered on physical media and we did not have the internet to deliver updates or patches. For business software, contracts would include penalty clauses for any down time resulting from software failures.</p>
<h3>Rework is expensive</h3>
<p>Software was also fully specified before any code was written. Business Analysts and System Architects would spend months detailing the requirements and Architecture of the application before any developer is given the details and allowed to build any functionality. Reworking code to accommodate changes in requirements was considered costly and complex and was avoided.</p>
<h3>Rigorous process</h3>
<p>Software development followed a well defined sequential process called the software development lifecycle with specific artifacts produced at the end of each step that fed into the next phase of development.</p>
<h2>Software Development Lifecycle</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mook8p9ubd6xhn55edu5iw09" alt="" data-big=https://cms-assets.abletech.nz/large_Software_Development_Lifecycle_59ee9183c0.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 221px" src="https://cms-assets.abletech.nz/Software_Development_Lifecycle_59ee9183c0.png" srcset="https://cms-assets.abletech.nz/large_Software_Development_Lifecycle_59ee9183c0.png 1000w, https://cms-assets.abletech.nz/small_Software_Development_Lifecycle_59ee9183c0.png 500w, https://cms-assets.abletech.nz/medium_Software_Development_Lifecycle_59ee9183c0.png 750w, https://cms-assets.abletech.nz/thumbnail_Software_Development_Lifecycle_59ee9183c0.png 221w" data-zooming-width="1000" data-zooming-height="707" loading="lazy" width="1000" height="707"></figure>
</div>
<h3>Fixed Budget</h3>
<p>A project budget allocated at the initiation stage was expected to be met regardless of size or complexity. When projects inevitably exceeded the budget due to unforeseen requirements or changes in cost of inputs it was considered a &quot;budget blowout&quot; and a failure.</p>
<h3>Requirements Document Example</h3>
<p>As a developer in a company that follows the software development lifecycle you would be provided with a business requirements document by a Business Analyst for the feature you were expected to implement. The document would follow a template similar to the one below.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lcv3sd8bvoln2gak90audohw" alt="" data-big=https://cms-assets.abletech.nz/medium_usecaseimg_211b7938df.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/usecaseimg_211b7938df.png" srcset="https://cms-assets.abletech.nz/small_usecaseimg_211b7938df.png 500w, https://cms-assets.abletech.nz/medium_usecaseimg_211b7938df.png 750w, https://cms-assets.abletech.nz/thumbnail_usecaseimg_211b7938df.png 245w" data-zooming-width="750" data-zooming-height="428" loading="lazy" width="750" height="428"></figure>
</div>
<p>The document contains all the rules and valid flows including alternative flows and error conditions. You were expected to translate this into the code. As a developer you would not communicate with the users of the software and may not understand the reasoning for the design, alternatives that were considered and discarded or tradeoffs made.</p>
<p>This process resulted in some high profile failures. One high profile failure in Australia was the Queensland Health payroll system.</p>
<h2>The Queensland Health payroll scandal</h2>
<p>The Payroll system was to provide accurate pay and rostering advice to 78,000 Queensland Health staff by July 1, 2008.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="v9so6esell50ewk1sc1pxmw3" alt="" data-big=https://cms-assets.abletech.nz/medium_qld_health_logo_AA_be617bb207.jpg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/qld_health_logo_AA_be617bb207.jpg" srcset="https://cms-assets.abletech.nz/small_qld_health_logo_AA_be617bb207.jpg 500w, https://cms-assets.abletech.nz/medium_qld_health_logo_AA_be617bb207.jpg 750w, https://cms-assets.abletech.nz/thumbnail_qld_health_logo_AA_be617bb207.jpg 234w" data-zooming-width="750" data-zooming-height="500" loading="lazy" width="750" height="500"></figure>
</div>
<p>It was built by the Government owned CorpTech. The contract was to build a &quot;whole-of-government human resources and finance solution&quot; with a budget of $125 million. It went live in March 2010 after 10 &quot;aborted attempts&quot;, despite very real doubts. It was not adopted by other government agencies.</p>
<p>When it went live almost 78,000 Queensland Health staff were underpaid, overpaid or not paid at all. More than five years later the cost to taxpayers was $1.2 billion. It was a compromised choice, and as the Commission of Inquiry said, &quot;It was a catastrophic failure.&quot;</p>
<p>See <a href="https://www.smh.com.au/technology/worst-failure-of-public-administration-in-this-nation-payroll-system-20130807-hv1cw.html" target="_blank" rel="noopener noreferrer">SMH Article</a></p>
<h2>Lean Manufacturing</h2>
<p>People who were disillusioned with software engineering looked for other inspiration for ways to avoid the failures they were experiencing. They settled on a manufacturing technique pioneered by Taiichi Ohno at Toyota called Lean manufacturing. The goal was to reduce waste in a manufacturing system without sacrificing productivity.  In Lean manufacturing the customer defines what is of value in terms of what they would pay for the product or service. If the customer is not interested in paying for a feature then it is waste and can be removed.</p>
<h3>Principles of Lean</h3>
<ol>
<li>Identify Value</li>
</ol>
<p>Value is what the customer is willing to pay for. Design products to meet the needs of customers. Do not try to promote features that customers have not told you they value</p>
<ol start="2">
<li>Map the Value Stream</li>
</ol>
<p>Under all the steps and processes involved in taking a specific product from raw materials to delivering the final product to the customer.</p>
<p>Use customer value as a reference point. Identify all the activities that contribute to these values. Activities that do not add value to the end customer are considered waste.</p>
<p>Consider the complete life-cycle of a product. This includes the product’s design, the customers’ use of the product and the disposal of the product when it has reached the end of it's useful life.</p>
<ol start="3">
<li>Create Flow</li>
</ol>
<p>After the waste has been removed from the value stream the next step is to be sure the remaining steps flow smoothly. There must be no interruptions, delays, or bottlenecks in the process. Having clear visibility of the value stream and measuring how long the product remains at each step in the process is essential.</p>
<ol start="4">
<li>Establish Pull</li>
</ol>
<p>&quot;Pull&quot; is the central concept of “just in time” manufacturing or delivery. It is allowing the demand for a product to determine the rate at which they are provided and when inputs are needed. It means the customer can “pull” the product from you as needed. This can dramatically improve the time to market (or time to customer).</p>
<ol start="5">
<li>Seek Perfection</li>
</ol>
<p>It is important to remember lean is not a static system and requires constant effort and vigilance to perfect. There are always changes and improvements that can be made to any system.</p>
<h2>Agile</h2>
<p>The people who looked to lean manufacturing applied these principles in different ways and had different focuses. Some methodologies that became popular were extreme programming (XP), Scrum, Dynamic System Development Method (DSDM), Rapid Application Development (RAD), Crystal, Adaptive Software Development (ASD). When they came together they agreed on a set of values and principles that the different practices shared.</p>
<h3>The Agile Manifesto</h3>
<p>Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation
Responding to change over following a plan</p>
<h2>12 Agile Manifesto Principles</h2>
<ol>
<li><strong>Customer satisfaction through early and continuous software delivery</strong> – Customers are happier when they receive working software at regular intervals, rather than waiting extended periods of time between releases.</li>
<li><strong>Accommodate changing requirements throughout the development process</strong> – The ability to avoid delays when a requirement or feature request changes.</li>
<li><strong>Frequent delivery of working software</strong> – Scrum accommodates this principle since the team operates in software sprints or iterations that ensure regular delivery of working software.</li>
<li><strong>Collaboration between the business stakeholders and developers throughout the project</strong> – Better decisions are made when the business and technical team are aligned.</li>
<li><strong>Support, trust, and motivate the people involved</strong> – Motivated teams are more likely to deliver their best work than unhappy teams.</li>
<li><strong>Enable face-to-face interactions</strong> – Communication is more successful when development teams are co-located.</li>
<li><strong>Working software is the primary measure of progress</strong> – Delivering functional software to the customer is the ultimate factor that measures progress.</li>
<li><strong>Agile processes to support a consistent development pace</strong> – Teams establish a repeatable and maintainable speed at which they can deliver working software, and they repeat it with each release.</li>
<li><strong>Attention to technical detail and design enhances agility</strong> – The right skills and good design ensures the team can maintain the pace, constantly improve the product, and sustain change.</li>
<li><strong>Simplicity</strong> – Develop just enough to get the job done for right now.</li>
<li><strong>Self-organizing teams encourage great architectures, requirements, and designs</strong> – Skilled and motivated team members who have decision-making power, take ownership, communicate regularly with other team members, and share ideas that deliver quality products.</li>
<li><strong>Regular reflections on how to become more effective</strong> – Self-improvement, process improvement, advancing skills, and techniques help team members work more efficiently.</li>
</ol>
<h2>Scrum</h2>
<p>A key member of the scrum team is the scrum master. They are responsible for organising the various activities and tracking progress. They are a servant/leader in that they can make decisions but are also responsible for helping the team to deal with external factors that may limit the team's ability to deliver.</p>
<p>Scrum teams divide their work into stories. These represent a unit of deliverable functionality to the user of the application. The stories are prioritised from most valuable to least and worked on in that order. The stories are worked on in a series of sprints. These are timeboxed and typically last for 2-4 weeks.</p>
<p>During sprint planning the team will ensure they have the same shared understanding of what is required to complete each story and will estimate how much effort is required to complete the story.</p>
<p>The team will determine how many stories they will complete in the sprint. The team will be as honest as possible in their estimate and are expected to work together to meet that expectation. If the team fails to complete the work in the sprint it is the whole team who are responsible.</p>
<p>The scrum master ensures that progress is visible by tracking the progress of the team via a team board and charts. They will gather metrics over time and deliver their insights during team retrospectives.</p>
<h3>Stories</h3>
<p>A story contains all the information required to complete the functionality. It will contain the desired outcome in the form of:</p>
<p>As a <role>
I want to <action>
So that <outcome></p>
<p>e.g.
As a student
I want to submit my exam answers
So that they can be marked by my teacher</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="j2xyjayv2xnpqtvvnzkp7gka" alt="" data-big=https://cms-assets.abletech.nz/large_story_card_front_and_back_cbe12ef6e6.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/story_card_front_and_back_cbe12ef6e6.png" srcset="https://cms-assets.abletech.nz/large_story_card_front_and_back_cbe12ef6e6.png 1000w, https://cms-assets.abletech.nz/small_story_card_front_and_back_cbe12ef6e6.png 500w, https://cms-assets.abletech.nz/medium_story_card_front_and_back_cbe12ef6e6.png 750w, https://cms-assets.abletech.nz/thumbnail_story_card_front_and_back_cbe12ef6e6.png 245w" data-zooming-width="1000" data-zooming-height="344" loading="lazy" width="1000" height="344"></figure>
</div>
<p>Acceptance criteria is provided to guide testing of the functionality. When the acceptance criteria is satisfied the story is done.</p>
<h3>Estimation</h3>
<p>Once the team has a clear understanding of the requirements for a story it is sized. A story is selected as a baseline. All other stories are sized relative to the baseline story. If the baseline story is estimated as a 5 then a story that is a 2 or 3 is roughly half the size of the baseline story.</p>
<p>The size is determined by the relative complexity, not how long someone may think it will take to complete. This is because it is not yet clear who will be working on the story and so different people with different experience could be working on it.</p>
<p>Each person is given a chance to size the story and only once each person has made their choice are the other members' estimates revealed to avoid influencing the other team members. If the estimates are similar it is recorded and the next story is assessed. If the estimates are varied then people are given a chance to explain their choice. This is valuable as it often surfaces misunderstandings or useful information that was missed when the story was originally discussed.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nhzmowxbw8p68z9r8nzq6r3n" alt="" data-big=https://cms-assets.abletech.nz/large_Planning_Poker_33c267e6da.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 221px" src="https://cms-assets.abletech.nz/Planning_Poker_33c267e6da.png" srcset="https://cms-assets.abletech.nz/large_Planning_Poker_33c267e6da.png 1000w, https://cms-assets.abletech.nz/small_Planning_Poker_33c267e6da.png 500w, https://cms-assets.abletech.nz/medium_Planning_Poker_33c267e6da.png 750w, https://cms-assets.abletech.nz/thumbnail_Planning_Poker_33c267e6da.png 221w" data-zooming-width="1000" data-zooming-height="707" loading="lazy" width="1000" height="707"></figure>
</div>
<h3>Burn Down Chart</h3>
<p>The burn down chart is a useful tool for visualising the progress of the team towards their goal of completing the stories by the end of the sprint. A trend line is included to show the ideal number of story points remaining at the end of each day for the duration of the sprint.</p>
<p>It is the job of the scrum master to track the points left and plot the next day on the chart. This shows how the team is tracking. If they are falling behind it can mean that either the estimates were too optimistic or some other factor has caused the team to slow their velocity. It is up to the team to identify these issues and try to remove or mitigate them to keep their progress on track.</p>
<p>An example burndown chart where progress goes according to plan</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tw2wf1hhbhcef1vjndhg0huq" alt="" data-big=https://cms-assets.abletech.nz/large_Burn_Down_Ideal_96337e5d21.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Burn_Down_Ideal_96337e5d21.png" srcset="https://cms-assets.abletech.nz/large_Burn_Down_Ideal_96337e5d21.png 1000w, https://cms-assets.abletech.nz/small_Burn_Down_Ideal_96337e5d21.png 500w, https://cms-assets.abletech.nz/medium_Burn_Down_Ideal_96337e5d21.png 750w, https://cms-assets.abletech.nz/thumbnail_Burn_Down_Ideal_96337e5d21.png 245w" data-zooming-width="1000" data-zooming-height="618" loading="lazy" width="1000" height="618"></figure>
</div>
<p>A more typical chart where many stories are still in progress due to limited testing resources until the sprint is almost complete and the tester is able to finish testing the final stories</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="svvcth79pcqbct79bzxad1yo" alt="" data-big=https://cms-assets.abletech.nz/large_Burn_Down_Realistic_658453fe78.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Burn_Down_Realistic_658453fe78.png" srcset="https://cms-assets.abletech.nz/large_Burn_Down_Realistic_658453fe78.png 1000w, https://cms-assets.abletech.nz/small_Burn_Down_Realistic_658453fe78.png 500w, https://cms-assets.abletech.nz/medium_Burn_Down_Realistic_658453fe78.png 750w, https://cms-assets.abletech.nz/thumbnail_Burn_Down_Realistic_658453fe78.png 245w" data-zooming-width="1000" data-zooming-height="618" loading="lazy" width="1000" height="618"></figure>
</div>
<h2>Kanban</h2>
<p>The Kanban methodology is focused on flow. As in lean manufacturing the goal is to reduce waste and unnecessary delays. To that end the most important metric for a Kanban project is to have consistency in the process and reduce the time it takes for a story to move from the project backlog to being available for users providing the Just in Time delivery.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pc4y951dnmrqxmqj0mj4n2sk" alt="" data-big=https://cms-assets.abletech.nz/large_Evergiven_66db88e60b.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Evergiven_66db88e60b.jpg" srcset="https://cms-assets.abletech.nz/large_Evergiven_66db88e60b.jpg 1000w, https://cms-assets.abletech.nz/small_Evergiven_66db88e60b.jpg 500w, https://cms-assets.abletech.nz/medium_Evergiven_66db88e60b.jpg 750w, https://cms-assets.abletech.nz/thumbnail_Evergiven_66db88e60b.jpg 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<h3>WIP</h3>
<p>The main tool to achieve this is Work In Progress (WIP) limits. This is limiting the number of stories that can be in any particular part of the development process. By limiting the work in progress it is easier to spot where the bottlenecks are in the process and focus on removing them. If stories are being held up at any point the work ceases until they are moved.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rnzs2avfatyyidmdqe2ochja" alt="" data-big=https://cms-assets.abletech.nz/large_Kanban_Board_Example_8276ceeb55.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 217px" src="https://cms-assets.abletech.nz/Kanban_Board_Example_8276ceeb55.png" srcset="https://cms-assets.abletech.nz/large_Kanban_Board_Example_8276ceeb55.png 1000w, https://cms-assets.abletech.nz/small_Kanban_Board_Example_8276ceeb55.png 500w, https://cms-assets.abletech.nz/medium_Kanban_Board_Example_8276ceeb55.png 750w, https://cms-assets.abletech.nz/thumbnail_Kanban_Board_Example_8276ceeb55.png 217w" data-zooming-width="1000" data-zooming-height="718" loading="lazy" width="1000" height="718"></figure>
</div>
<h3>Measuring Flow</h3>
<p>To measure flow in a Kanban project a Cumulative Flow Diagram (CFD) is used. By measuring the number of stories in each column each day we can get useful metrics about the rate stories are moving through the system and where they are being held up and causing wasted time.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="n9zfzu8ockc3ylhc81e3gtdh" alt="" data-big=https://cms-assets.abletech.nz/large_Cumulative_Flow_Diagram_CFD_b2429cc01a.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Cumulative_Flow_Diagram_CFD_b2429cc01a.png" srcset="https://cms-assets.abletech.nz/large_Cumulative_Flow_Diagram_CFD_b2429cc01a.png 1000w, https://cms-assets.abletech.nz/small_Cumulative_Flow_Diagram_CFD_b2429cc01a.png 500w, https://cms-assets.abletech.nz/medium_Cumulative_Flow_Diagram_CFD_b2429cc01a.png 750w, https://cms-assets.abletech.nz/thumbnail_Cumulative_Flow_Diagram_CFD_b2429cc01a.png 245w" data-zooming-width="1000" data-zooming-height="618" loading="lazy" width="1000" height="618"></figure>
</div>
<h4>Lead Time</h4>
<p>Time for a work item to be completed from when it enters the backlog. This can indicate that there are more items entering the backlog than the team can reasonably complete with the resources they have.</p>
<h4>Cycle Time</h4>
<p>Time for a work item to be completed from when it first moves into WIP. The goal is to reduce this time. Any waste in the system or bottleneck will cause this metric to increase over time. The team should assess this time regularly to identify areas where it can be improved.</p>
<h4>Total WIP</h4>
<p>All items that are being worked on at a point in time. This can show how close the system is to full capacity (all WIP limits are reached) this can indicate that the team is taking on too much work. Again if much of the WIP is work in the input queue then it may mean the team is under-resourced to tackle the work being allocated.</p>
<h2>Conclusion</h2>
<p>With a clear understanding of the objectives and tools of the agile methodology you use in your organisation your teams will be able to work together as a cohesive unit. They will solve the problems they face - both technical and organisational. With the right approach to deal with the challenges your team will have more time and energy to produce high quality work in less time.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Lighthouse Funds - Going Retail (Part 1)</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>JAMstack with Next.js, Netlify and Prismic CMS</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qoms5rsynfqloqieim6oyanf" alt="" data-big=https://cms-assets.abletech.nz/large_Light_House_Funds_3f72891e25.PNG sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Light_House_Funds_3f72891e25.PNG" srcset="https://cms-assets.abletech.nz/large_Light_House_Funds_3f72891e25.PNG 1000w, https://cms-assets.abletech.nz/small_Light_House_Funds_3f72891e25.PNG 500w, https://cms-assets.abletech.nz/medium_Light_House_Funds_3f72891e25.PNG 750w, https://cms-assets.abletech.nz/thumbnail_Light_House_Funds_3f72891e25.PNG 245w" data-zooming-width="1000" data-zooming-height="504" loading="lazy" width="1000" height="504"></figure>
</div>
<p><em>The new <a href="https://lighthousefunds.nz/" target="_blank" rel="noopener noreferrer">Lighthouse Funds</a> site</em></p>
<h2>Who is Lighthouse Funds?</h2>
<p>As per their <a href="https://lighthousefunds.nz/" target="_blank" rel="noopener noreferrer">new website</a>, Lighthouse Funds is “the investment manager of the Lighthouse Global Equity Fund. We’re responsible for identifying and arranging the Fund’s investments”.</p>
<p>They’ve been around as an option for wholesale investors since 2013, but as of April 2021 they have “re-packaged that fund for retail investors”.</p>
<p>Abletech had been supporting the old marketing site for Lighthouse Funds since 2015, but we knew this project would be a total rebuild, with new content and requirements such as:</p>
<ul>
<li>
<p>Interactive charts</p>
</li>
<li>
<p>A blog</p>
</li>
<li>
<p>A newsletter</p>
</li>
<li>
<p>Downloadable documents and forms</p>
</li>
<li>
<p>Directings users to sign up through <a href="https://investnow.co.nz/" target="_blank" rel="noopener noreferrer">InvestNow</a></p>
</li>
</ul>
<p>We settled on the <a href="https://jamstack.org/" target="_blank" rel="noopener noreferrer">JAMstack</a> approach, using <a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a> to build a statically-generated site, <a href="https://prismic.io/" target="_blank" rel="noopener noreferrer">Prismic CMS</a> to serve the blog content, <a href="https://mailchimp.com/" target="_blank" rel="noopener noreferrer">Mailchimp</a> to manage the newsletters, <a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener noreferrer">AWS S3</a> to store the documents and forms, and <a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a> to build and deploy the app. This provided many advantages including rapid delivery of a robust solution with low infrastructure setup and maintenance costs.</p>
<p>We also knew there could be further project phases in the future, that might include features such as authentication and user dashboards. Next.js allows us to provide dynamic content rendering through its hybrid approach to static-site generation, server-side rendering, and client-side rendering.</p>
<h2>What even is JAMstack?</h2>
<p>A term that describes a modern web development architecture based on a set of fundamental web technologies:</p>
<ul>
<li>
<p>JavaScript</p>
</li>
<li>
<p>APIs</p>
</li>
<li>
<p>Markup</p>
</li>
</ul>
<p>JAMstack isn’t a specific technology, it’s a specific approach to building apps and websites:</p>
<ul>
<li>
<p>Code in a Git repository</p>
</li>
<li>
<p>Continuous Delivery system</p>
</li>
<li>
<p>Microservice APIs/serverless functions to deliver data, processing and personalisation (at build <em>or</em> run time)</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="juk4ervpwhevunfi6htkazgu" alt="" data-big=https://cms-assets.abletech.nz/large_jamstack2_fe5fea4507.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/jamstack2_fe5fea4507.jpg" srcset="https://cms-assets.abletech.nz/large_jamstack2_fe5fea4507.jpg 1000w, https://cms-assets.abletech.nz/small_jamstack2_fe5fea4507.jpg 500w, https://cms-assets.abletech.nz/medium_jamstack2_fe5fea4507.jpg 750w, https://cms-assets.abletech.nz/thumbnail_jamstack2_fe5fea4507.jpg 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<p><em>Image source: <a href="https://twenty-tech.com/jamstack-how-it-can-be-your-next-web-development-architecture/" target="_blank" rel="noopener noreferrer">https://twenty-tech.com/jamstack-how-it-can-be-your-next-web-development-architecture/</a></em></p>
<p>In a traditional workflow, when the user requests a certain page, the server will receive that request, build the HTML on demand, and serve it back to the user.</p>
<p>In the JAMstack workflow, the HTML is pre-rendered as part of the build process, and is served from a CDN — a Content Delivery Network.</p>
<h2>How it all fits together</h2>
<p>(Or “How I want it all to fit together”)</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="xea1ewxjsb17p2vt3nf8psj1" alt="" data-big=https://cms-assets.abletech.nz/large_lighthouse_architecture_339cc442dd.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/lighthouse_architecture_339cc442dd.jpg" srcset="https://cms-assets.abletech.nz/large_lighthouse_architecture_339cc442dd.jpg 1000w, https://cms-assets.abletech.nz/small_lighthouse_architecture_339cc442dd.jpg 500w, https://cms-assets.abletech.nz/medium_lighthouse_architecture_339cc442dd.jpg 750w, https://cms-assets.abletech.nz/thumbnail_lighthouse_architecture_339cc442dd.jpg 245w" data-zooming-width="1000" data-zooming-height="571" loading="lazy" width="1000" height="571"></figure>
</div>
<p><em>Lighthouse Funds site architecture</em></p>
<p>I’ll delve more into Netlify later on, but its integration with Git providers is a useful and powerful feature. Netlify lets you link a GitHub, GitLab, or Bitbucket repository to a site for continuous deployment. Each time you push to your Git provider, Netlify runs a build with your tool of choice and deploys the result to their CDN.</p>
<p>In our case, when a developer pushes to the &quot;staging&quot; or &quot;main&quot; branches in our Github repository, a build kicks off in Netlify, which produces a bunch of HTML, CSS, and JS files that are pushed to the Netlify Edge CDN.</p>
<p>The build process also deploys three API endpoints (yes, you can write APIs in Next.js!), and a server-side rendered page as Netlify Functions. These are deployed as serverless Lambda functions without the need to create an AWS account, and with function management handled directly within Netlify.</p>
<p>Publishing a post via Prismic CMS also triggers the build process. When a new post is written and published, a webhook will kick off the Netlify build, which will pick up the new post and create a page based on the dynamic routing setup in the Next.js app.</p>
<p>The end goal is to use webhooks to automatically rebuild the site when new prices are uploaded to the bespoke Rails API, and when new documents are added to the S3 bucket that stores the application forms, monthly factsheets, quarterly reports, etc. These last two processes are currently manual, so it will be great to automate them and save developer time.</p>
<p>As the site is a statically-generated site, when a user accesses it via the browser, the browser will communicate with the CDN and serve back the pre-rendered HTML, and then hydrate it with the JS and execute React, making the page interactive. The CDN will also call off to the AWS Lambda functions when necessary.</p>
<h2>Next.js: The React Framework for Production</h2>
<p>Let’s talk about Next.js!</p>
<p>Some of their key, out-of-the-box features include:</p>
<ul>
<li>
<p>Zero configuration — automatic compilation and bundling</p>
</li>
<li>
<p>Hybrid Static Site Generation and Server Side Rendering, you can pre-render pages at build time (SSG) or build them at request time (SSR)</p>
</li>
<li>
<p>Fast refresh — hot module replacement means there is a fast, reliable live-editing development experience</p>
</li>
<li>
<p>File-system routing — every component under the &quot;pages&quot; directory becomes a route</p>
</li>
<li>
<p>API routes — optionally create API endpoints to provide backend functionality</p>
</li>
<li>
<p>Built-in CSS support — component-level styles with CSS modules and Sass</p>
</li>
<li>
<p>Code-splitting and Bundling — optimised bundle splitting algorithm created by the Google Chrome team</p>
</li>
</ul>
<p>It pays to understand the key differences between Server Side Rendering (SSR) and Client Side Rendering (CSR), in order to understand the benefits of pre-rendering, or Static Site Generation (SSG).</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kemsigdoe9ijlif6tj5mdgd1" alt="" data-big=https://cms-assets.abletech.nz/large_image4_d70258ed02.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 219px" src="https://cms-assets.abletech.nz/image4_d70258ed02.png" srcset="https://cms-assets.abletech.nz/large_image4_d70258ed02.png 1000w, https://cms-assets.abletech.nz/small_image4_d70258ed02.png 500w, https://cms-assets.abletech.nz/medium_image4_d70258ed02.png 750w, https://cms-assets.abletech.nz/thumbnail_image4_d70258ed02.png 219w" data-zooming-width="1000" data-zooming-height="714" loading="lazy" width="1000" height="714"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pss7dzqsijbf1kup9hu2p50b" alt="" data-big=https://cms-assets.abletech.nz/large_image6_0744cc4ad0.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 221px" src="https://cms-assets.abletech.nz/image6_0744cc4ad0.png" srcset="https://cms-assets.abletech.nz/large_image6_0744cc4ad0.png 1000w, https://cms-assets.abletech.nz/small_image6_0744cc4ad0.png 500w, https://cms-assets.abletech.nz/medium_image6_0744cc4ad0.png 750w, https://cms-assets.abletech.nz/thumbnail_image6_0744cc4ad0.png 221w" data-zooming-width="1000" data-zooming-height="706" loading="lazy" width="1000" height="706"></figure>
</div>
<p><em>Image source: <a href="https://www.smashingmagazine.com/2020/07/differences-static-generated-sites-server-side-rendered-apps/" target="_blank" rel="noopener noreferrer">https://www.smashingmagazine.com/2020/07/differences-static-generated-sites-server-side-rendered-apps/</a></em></p>
<p>Client Side Rendering works by providing the browser with an <code>index.html</code> file which contains all the CSS and JS bundles to load. Once these files are loaded, the JS code in them can manipulate and render content on the browser using DOM manipulation. The page then becomes visible and interactive.</p>
<p>In Server Side Rendering, when a certain URL path is loaded, the HTML structure is generated server-side, alongside the minimal JS bundle required for that page, and is then provided to the browser. The page is now visible, but not yet interactive. Behind the scenes, the JS code is working to make the page interactive — this process is called hydration.</p>
<h3>Next.js Pre-Rendering</h3>
<p>Next.js takes an approach called pre-rendering, of which there are two forms:</p>
<ul>
<li>
<p>Static Generation: the HTML is generated at <strong>build time</strong> and will be reused on each request</p>
</li>
<li>
<p>Server-side rendering: the HTML is generated on <strong>each request</strong></p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l4ioc32seakkqwhot4bszohy" alt="" data-big=https://cms-assets.abletech.nz/large_image18_c53297bebf.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 218px" src="https://cms-assets.abletech.nz/image18_c53297bebf.png" srcset="https://cms-assets.abletech.nz/large_image18_c53297bebf.png 1000w, https://cms-assets.abletech.nz/small_image18_c53297bebf.png 500w, https://cms-assets.abletech.nz/medium_image18_c53297bebf.png 750w, https://cms-assets.abletech.nz/thumbnail_image18_c53297bebf.png 218w" data-zooming-width="1000" data-zooming-height="715" loading="lazy" width="1000" height="715"></figure>
</div>
<p><em>Image source: <a href="https://david-neuman.com/nextjs-pre-rendering/" target="_blank" rel="noopener noreferrer">https://david-neuman.com/nextjs-pre-rendering/</a></em></p>
<h3>Let’s look at some code</h3>
<p>When you export the <code>async</code> function <code>getStaticProps</code> from a page, Next.js will pre-render this page at build time using the props returned by <code>getStaticProps</code>.</p>
<p>It should be used if:</p>
<ul>
<li>
<p>The data required to render the page is available at build time ahead of a user’s request</p>
</li>
<li>
<p>The data comes from a headless CMS</p>
</li>
<li>
<p>The data can be publicly cached (not user-specific)</p>
</li>
<li>
<p>The page must be pre-rendered (for SEO) and be very fast — <code>getStaticProps</code> generates HTML and JSON files, both of which can be cached by a CDN to improve performance</p>
</li>
</ul>
<p>If a page has dynamic routes — blog posts, for example — and uses <code>getStaticProps</code>, it needs to define a list of paths that have to be rendered to HTML at build time.</p>
<p>So, when you export the <code>async</code> function <code>getStaticPaths</code> from a page that uses dynamic routes, Next.js will statically pre-render all the paths returned by <code>getStaticPaths</code>.</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getStaticProps</span>(<span class="hljs-params">{ params, preview = <span class="hljs-literal">null</span>, previewData = {} }</span>) {
  <span class="hljs-keyword">const</span> { ref } = previewData

  <span class="hljs-keyword">const</span> post = (<span class="hljs-keyword">await</span> <span class="hljs-title class_">Client</span>().<span class="hljs-title function_">getByUID</span>(<span class="hljs-string">&#x27;post&#x27;</span>, params.<span class="hljs-property">uid</span>, ref ? { ref } : <span class="hljs-literal">null</span>)) || {}

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      preview,
      post,
    },
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getStaticPaths</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> documents = <span class="hljs-keyword">await</span> <span class="hljs-title function_">queryRepeatableDocuments</span>(<span class="hljs-function"><span class="hljs-params">doc</span> =&gt;</span> doc.<span class="hljs-property">type</span> === <span class="hljs-string">&#x27;post&#x27;</span>)
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">paths</span>: documents.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">doc</span> =&gt;</span> <span class="hljs-string">`/blog/<span class="hljs-subst">${doc.uid}</span>`</span>),
    <span class="hljs-attr">fallback</span>: <span class="hljs-string">&#x27;blocking&#x27;</span>,
  }
}
</code></pre>
<p><em>~/pages/blog/[uid].js</em></p>
<p>Next.js will statically generate <code>/blog/post/1</code> and <code>/blog/post/2</code> at build time using the page component in <code>~/pages/blog/[uid].js</code>.</p>
<p><strong>Note re. the <code>fallback: blocking</code> option in <code>getStaticPaths</code>. If a specified path isn’t returned, Next.js will server-side render the page on demand, e.g. a blog post preview.</strong></p>
<h3>Let’s look at some more code</h3>
<p>When you export the <code>async</code> function <code>getServerSideProps</code> from a page, Next.js will pre-render this page on <strong>each request</strong> using the data returned by <code>getServerSideProps</code>.</p>
<ul>
<li>
<p>It should be used only if you need to pre-render a page whose data must be fetched at request time (e.g. user-specific content)</p>
</li>
<li>
<p>Time to first byte will be slower than <code>getStaticProps</code> because the server must compute the result on <strong>every request</strong></p>
</li>
<li>
<p>The result cannot be cached by a CDN (without extra configuration)</p>
</li>
</ul>
<pre><code class="hljs language-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getServerSideProps</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> dataUrl = <span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_RAILS_APP_API_ENDPOINT}</span>/investment_option_returns/investment_option_returns_for_past_five_years`</span>
  <span class="hljs-keyword">const</span> comparisonChartData = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(dataUrl).<span class="hljs-title function_">then</span>(<span class="hljs-keyword">async</span> res =&gt; {
    <span class="hljs-keyword">if</span> (res.<span class="hljs-property">status</span> !== <span class="hljs-number">200</span>) {
      <span class="hljs-keyword">return</span> []
    }

    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.<span class="hljs-title function_">json</span>()
    <span class="hljs-keyword">return</span> data
  })

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      comparisonChartData,
    },
  }
}
</code></pre>
<p><em>~/pages/index.js</em></p>
<p>This is from the home page, and returns monthly unit price data from the Rails API. It’s currently called at request time because there is no webhook set up to rebuild the app when new data is added via the admin UI.</p>
<h2>Up next</h2>
<p>In <a href="https://abletech.nz/article/lighthouse-funds-going-retail-part-2/">part two</a> of this three-part series, I dive into Prismic — our chosen Headless CMS, the Mailchimp Marketing API that we use to maintain multiple audience lists for newsletters, and how we set up AWS S3 to store downloadable documents.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Getting started with Stripe, Rails and React in three easy steps</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>A client recently requested to add the functionality to be able to accept credit card payments within a Rails application that we were building. Specifically, they wanted users to be able to purchase vouchers using credit card payments and pass on any processing fees. Once the payment was complete, the transaction had to be recorded within the app and an email needed to be sent out to both the purchaser and the giftee. Stripe came into the picture then.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hx9awo2k0cvl6i7d6yzkl9q6" alt="" data-big=https://cms-assets.abletech.nz/large_stripe_ed8c9a42e7.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/stripe_ed8c9a42e7.png" srcset="https://cms-assets.abletech.nz/large_stripe_ed8c9a42e7.png 1000w, https://cms-assets.abletech.nz/small_stripe_ed8c9a42e7.png 500w, https://cms-assets.abletech.nz/medium_stripe_ed8c9a42e7.png 750w, https://cms-assets.abletech.nz/thumbnail_stripe_ed8c9a42e7.png 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<p>Stripe offers payment processing software for websites and mobile applications. Their APIs and documentation make it super simple to set up and accept credit card payments whilst complying with regulations. Credit card information never touches our app and is directly sent to Stripe. The steps to setup Stripe in our app was pretty straightforward and I will cover it below.</p>
<h3>Step One</h3>
<p>We had to set up an endpoint within our Rails app - this would allow us to create what Stripe calls a “payment intent”. A payment intent represents a single customer session within Stripe. Stripe recommends you create a payment intent as soon as you know the payment amount.  The API we created required the client to pass through an amount and some metadata used to populate information for our voucher. The Rails app then calculated the fee charged by Stripe, added it onto the total amount, created a payment intent within Stripe and returned back the payment intent’s client_secret. This is what the UI will now use to interface directly with Stripe’s APIs.</p>
<pre><code class="hljs language-ruby"><span class="hljs-keyword">class</span> <span class="hljs-title class_">CreatePaymentIntent</span>
  <span class="hljs-keyword">class</span> <span class="hljs-title class_">InvalidAmountError</span> &lt; <span class="hljs-title class_ inherited__">StandardError</span>; <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">class</span> <span class="hljs-title class_">InvalidEmailError</span> &lt; <span class="hljs-title class_ inherited__">StandardError</span>; <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">include</span> <span class="hljs-title class_">UseCase</span>

  <span class="hljs-built_in">attr_reader</span> <span class="hljs-symbol">:payment_intent</span>, <span class="hljs-symbol">:metadata</span>

  <span class="hljs-keyword">def</span> <span class="hljs-title function_">initialize</span>(<span class="hljs-params"><span class="hljs-symbol">amount:</span>, <span class="hljs-symbol">from_name:</span>, <span class="hljs-symbol">to_name:</span>, <span class="hljs-symbol">from_email:</span>, <span class="hljs-symbol">to_email:</span>, <span class="hljs-symbol">note:</span> <span class="hljs-literal">nil</span></span>)
    <span class="hljs-variable">@amount</span> = amount
    <span class="hljs-variable">@from_name</span> = from_name
    <span class="hljs-variable">@to_name</span> = to_name
    <span class="hljs-variable">@from_email</span> = from_email
    <span class="hljs-variable">@to_email</span> = to_email
    <span class="hljs-variable">@note</span> = note
  <span class="hljs-keyword">end</span>

  <span class="hljs-keyword">def</span> <span class="hljs-title function_">perform</span>
    validate_emails
    validate_payment_amount
    determine_processing_fee_and_payment_amount
    determine_total_payment_amount_in_cents
    create_payment_intent!
  <span class="hljs-keyword">rescue</span> <span class="hljs-title class_">InvalidEmailError</span>
    <span class="hljs-literal">nil</span>
  <span class="hljs-keyword">rescue</span> <span class="hljs-title class_">InvalidAmountError</span> =&gt; e
    errors.add(<span class="hljs-symbol">:amount</span>, e.message)
  <span class="hljs-keyword">rescue</span> <span class="hljs-title class_">StandardError</span> =&gt; e
    errors.add(<span class="hljs-symbol">:base</span>, e.message)
  <span class="hljs-keyword">end</span>

  <span class="hljs-keyword">private</span>

  <span class="hljs-comment"># Check if the from and to email addresses are valid</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">validate_emails</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Check the payment amount is between the minimum voucher amount and maximum voucher amount</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">validate_payment_amount</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Calculate the processing fee and payment amount</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">determine_processing_fee_and_payment_amount</span>
    <span class="hljs-comment"># See: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers</span>
    <span class="hljs-variable">@payment_amount</span> = (<span class="hljs-variable">@amount</span> + <span class="hljs-title class_">ApplicationConfig</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:STRIPE_PROCESSING_FEE_CHARGE</span>) / (<span class="hljs-number">1</span> - (<span class="hljs-title class_">ApplicationConfig</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:STRIPE_PROCESSING_FEE_PERCENTAGE</span> / <span class="hljs-number">100</span>))
    <span class="hljs-variable">@processing_fee</span> = <span class="hljs-variable">@payment_amount</span> - <span class="hljs-variable">@amount</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Calculate the payment amount in cents</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">determine_total_payment_amount_in_cents</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-keyword">def</span> <span class="hljs-title function_">create_payment_intent!</span>
    <span class="hljs-variable">@payment_intent</span> = <span class="hljs-title class_">Stripe</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:PaymentIntent</span>.create(<span class="hljs-symbol">amount:</span> <span class="hljs-variable">@payment_amount_in_cents</span>, <span class="hljs-symbol">currency:</span> <span class="hljs-title class_">ApplicationConfig</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:STRIPE_CURRENCY</span>, <span class="hljs-symbol">metadata:</span> construct_metadata)
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Build a hash representation with the amount, processing fee, from_name, to_name, from_email, to_email and note</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">construct_metadata</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<h3>Step Two</h3>
<p>After creating a payment intent, we set up a simple checkout form. This was done using the React Stripe.js library, the Elements provider and components and two hooks (useElements and useStripe). The form used the client_secret from step one as well as an API key which can be obtained from the Stripe console. You’re also able to style these components how you like.</p>
<h3>Step Three</h3>
<p>After setting up the checkout form, we set up a webhook endpoint within our Rails app. Once a payment has been successfully processed, Stripe sends a <code>payment_intent.succeeded</code> event.  We configured the webhook endpoint within the Stripe dashboard to get Stripe to send events to. This webhook would issue a voucher and then email the purchaser and the recipient. Stripe also provides other events such as when a payment is created and fails.</p>
<pre><code class="hljs language-ruby"><span class="hljs-keyword">class</span> <span class="hljs-title class_">HandleStripeEvent</span>
  <span class="hljs-keyword">class</span> <span class="hljs-title class_">UnsupportedEventError</span> &lt; <span class="hljs-title class_ inherited__">StandardError</span>; <span class="hljs-keyword">end</span>

  <span class="hljs-variable constant_">PAYMENT_INTENT_SUCCEEDED_EVENT_TYPE</span> = <span class="hljs-string">&#x27;payment_intent.succeeded&#x27;</span>

  <span class="hljs-keyword">include</span> <span class="hljs-title class_">UseCase</span>

  <span class="hljs-built_in">attr_reader</span> <span class="hljs-symbol">:event</span>

  <span class="hljs-keyword">def</span> <span class="hljs-title function_">initialize</span>(<span class="hljs-params"><span class="hljs-symbol">payload:</span>, <span class="hljs-symbol">signature:</span></span>)
    <span class="hljs-variable">@payload</span> = payload
    <span class="hljs-variable">@signature</span> = signature
  <span class="hljs-keyword">end</span>

  <span class="hljs-keyword">def</span> <span class="hljs-title function_">perform</span>
    construct_event
    construct_metadata
    construct_payment_provider_metadata
    add_job_to_queue
  <span class="hljs-keyword">rescue</span> <span class="hljs-variable constant_">JSON</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:ParserError</span>
    errors.add(<span class="hljs-symbol">:event</span>, <span class="hljs-title class_">I18n</span>.t(<span class="hljs-string">&#x27;use_cases.handle_stripe_event.json_parse_error&#x27;</span>))
  <span class="hljs-keyword">rescue</span> <span class="hljs-title class_">Stripe</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:SignatureVerificationError</span>
    errors.add(<span class="hljs-symbol">:event</span>, <span class="hljs-title class_">I18n</span>.t(<span class="hljs-string">&#x27;use_cases.handle_stripe_event.invalid_signature_error&#x27;</span>))
  <span class="hljs-keyword">rescue</span> <span class="hljs-title class_">UnsupportedEventError</span> =&gt; e
    errors.add(<span class="hljs-symbol">:event</span>, e.message)
  <span class="hljs-keyword">end</span>

  <span class="hljs-keyword">private</span>

  <span class="hljs-keyword">def</span> <span class="hljs-title function_">construct_event</span>
    <span class="hljs-variable">@event</span> = <span class="hljs-title class_">Stripe</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:Webhook</span>.construct_event(<span class="hljs-variable">@payload</span>, <span class="hljs-variable">@signature</span>, endpoint_secret)
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Build a hash representation of the metadata from the event data</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">construct_metadata</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Build payment provider metadata hash of Stripe event identifier (from the event id) and the Stripe payment intent identifier (from the event data object id)</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">construct_payment_provider_metadata</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Add payment intent succeeded events received to the IssueVoucherJob queue to process the event asynchronously with the basic event metadata and the payment provider metadata</span>
  <span class="hljs-comment"># Raise unsupported event error for any other event types</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_job_to_queue</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Stripe endpoint secret value</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">endpoint_secret</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Voila - that’s the Stripe checkout flow complete. From here, the Stripe dashboard offers excellent visibility over payments that have been processed, or are in progress. It also offers fantastic tools to visualise events that have been sent and the response received from your APIs.</p>
<p>We barely scratched the surface of what Stripe offers - Stripe has the ability to handle invoices, plans, quotes, subscriptions and more. We found that the combination of the payment intents API and their component library meant we could get this feature up and running fast, with minimal configuration.</p>
<p>Get in touch today, if you have an application that may need to accept credit card payments in future.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Supporting young Māori and Pacific peoples in leadership</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>TupuToa</h2>
<p>When presented with some damning statistics on the presence of Māori and Pacific peoples in corporate-sector leadership positions, Abletech was delighted to join a campaign for change.</p>
<h3>Addressing society inequities</h3>
<p>Non-profit organisation TupuToa has a goal of ‘growing Māori and Pacific leaders for a greater Aotearoa’. Its focus is on addressing a shameful situation in which:</p>
<ul>
<li>while Māori and Pacific peoples make up a quarter of our population, they’re largely invisible in corporate roles, especially leadership positions</li>
<li>no NZX listed company has a Māori or Pacific chief executive</li>
<li>only 17% of New Zealand’s top 60 firms have executives who identify as other than European/Pākehā.</li>
</ul>
<p>To achieve its goal, TupuToa has developed a range of initiatives designed to:</p>
<ul>
<li>reduce the entry and advancement barriers that are biased against Māori and Pacific communities</li>
<li>support organisations in successfully recruiting and onboarding Māori and Pacific peoples.</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="b19137j7xp4t991qf307dwuk" alt="" data-big=https://cms-assets.abletech.nz/small_720x480_253488_367593_KWV_2_356e006c13.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/720x480_253488_367593_KWV_2_356e006c13.jpg" srcset="https://cms-assets.abletech.nz/small_720x480_253488_367593_KWV_2_356e006c13.jpg 500w, https://cms-assets.abletech.nz/thumbnail_720x480_253488_367593_KWV_2_356e006c13.jpg 245w" data-zooming-width="500" data-zooming-height="249" loading="lazy" width="500" height="249"></figure>
</div>
<h3>Working together to achieve results</h3>
<p>Abletech has joined the drive for change as part of our commitment to being a good employer that offers equal opportunities to all and is striving to achieve greater diversity in our workforce.</p>
<p>We support TupuToa’s Internship Programme – a comprehensive employment pathway that, through 12-week paid internships, provides professional opportunities and personalised support for Māori and Pacific tertiary students in corporate, government and community organisations.</p>
<p>As the TupuToa website says, “Many of our interns are the first in their families to undertake tertiary study, and with the guidance and pastoral care of TupuToa Navigators [who provide personalised support during the internships], they are better equipped to succeed in a corporate environment.”</p>
<h3>Together is better</h3>
<p>“Our industry offers exciting employment opportunities for all young people,” says Michelle Harvey, Abletech’s CEO.</p>
<p>“We offer graduates who join us a great working environment, stimulating projects to work on, and a sound foundation for future growth and development – whether they stay with us or venture to pastures new.”</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Provider of choice for the New Zealand Government</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Pae Hokohoko | Marketplace</h2>
<p>When agencies in New Zealand’s public sector need the services that Abletech provides, they simply visit Pae Hokohoko | Marketplace to find us.</p>
<p>This online service lists an extensive range of organisations that have been pre-qualified to provide an equally extensive range of products and services to government agencies. Every provider has been assessed according to its track record, personnel, experience, capabilities and security.</p>
<p>The great thing about Marketplace is that buyer agencies have instant access to information on services and products, and it covers the whole procurement process, from search to confirmation. As the Marketplace website states, “It’s radically simplifying the initial primary procurement process, reducing the barriers for suppliers engaging with government – and making it easier for agencies to access innovation.”</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bq5bcg71uxgmt8r0jj5k2y5r" alt="" data-big=https://cms-assets.abletech.nz/small_1_WZC_7_Mv_J_Dn0ie_Kh_GC_Nkx_Lg_2159040f60.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_WZC_7_Mv_J_Dn0ie_Kh_GC_Nkx_Lg_2159040f60.png" srcset="https://cms-assets.abletech.nz/small_1_WZC_7_Mv_J_Dn0ie_Kh_GC_Nkx_Lg_2159040f60.png 500w, https://cms-assets.abletech.nz/thumbnail_1_WZC_7_Mv_J_Dn0ie_Kh_GC_Nkx_Lg_2159040f60.png 245w" data-zooming-width="500" data-zooming-height="204" loading="lazy" width="500" height="204"></figure>
</div>
<h3>Spanning the spectrum</h3>
<p>Abletech offers government agencies a range of approved digital services, spanning back- and front- end development, cloud-transition, business intelligence, information architecture, data mining and analysis, tools and analytics, and native application development.</p>
<p>We also offer tried and tested software-development services that include:</p>
<ul>
<li>infrastructure design, implementation and support</li>
<li>bespoke development</li>
<li>DevOps consulting</li>
<li>database performance consulting</li>
<li>software-lifecycle services</li>
<li>platform migration services</li>
<li>technical strategy development and reviews</li>
<li>global infrastructure services.</li>
</ul>
<p>Keen to know more? <a href="/contact/" target="_blank" rel="noopener noreferrer">We’d love to talk</a>!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Partnering to solve Earth’s urgent challenges</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The Creative HQ Climate Response Accelerator</h2>
<p>We all love the satisfaction that comes with solving a problem – especially when it’s a gnarly one. So when we heard about <a href="https://creativehq.co.nz/climate-response-accelerator/" target="_blank" rel="noopener noreferrer">Creative HQ’s Climate Response Accelerator</a>, we couldn’t resist getting involved. Together, we’re helping some of the smartest people in New Zealand to solve some of the biggest climate-related challenges facing our planet.</p>
<h3>Supporting innovation</h3>
<p>The Climate Response Accelerator is a 12-week programme in which teams of innovators from around Aotearoa are provided with equity-free funding, mentorship and tools and resources to tackle important global issues.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rjyovily0a9eg8moomoagvnn" alt="" data-big=https://cms-assets.abletech.nz/large_Screen_Shot_2021_11_09_at_12_02_59_PM_337dbd0af7.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/Screen_Shot_2021_11_09_at_12_02_59_PM_337dbd0af7.png" srcset="https://cms-assets.abletech.nz/large_Screen_Shot_2021_11_09_at_12_02_59_PM_337dbd0af7.png 1000w, https://cms-assets.abletech.nz/small_Screen_Shot_2021_11_09_at_12_02_59_PM_337dbd0af7.png 500w, https://cms-assets.abletech.nz/medium_Screen_Shot_2021_11_09_at_12_02_59_PM_337dbd0af7.png 750w, https://cms-assets.abletech.nz/thumbnail_Screen_Shot_2021_11_09_at_12_02_59_PM_337dbd0af7.png 245w" data-zooming-width="1000" data-zooming-height="452" loading="lazy" width="1000" height="452"></figure>
</div>
<p>Abletech is right by their side, providing technology-related advice and support as and when the teams need it. We’re joined in our work by other supporters who range from tenacious start-up founders to board-level executives who provide connections, expertise and support during and after the programme.</p>
<h3>Making a contribution</h3>
<p>Opportunities like these are right up our alley.</p>
<ul>
<li>As innovators ourselves, we relish opportunities to help others to innovate.</li>
<li>As a company passionate about protecting our planet, we love working with others doing the same thing.</li>
<li>As New Zealanders, we’re proud to support local people and businesses that are collaborating to deliver change on a global scale.</li>
</ul>
<p>Together, we can do great things!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>RealESmart - Investment property calculator</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3>Resolve</h3>
<p>At Resolve, we work with businesses and organisations to create innovative and practical management solutions to exceptional and high performing teams and streamlined systems. Our business, growth and legal experience will add value, increase effectiveness, improve profit, increase team engagement and customer/client experiences for any organisation. With a sub-specialist interest in property and its management, Resolve assists its clients whether not for profits or private businesses to ensure its property portfolio is managed exceptionally well to deliver its purpose.</p>
<h4>Legal and Dispute Resolution:</h4>
<p>At Resolve, we help turn issues into opportunities. We have legal and mediation expertise to assist our clients to work towards resolution of any disputes.</p>
<p>With a sub-specialist interest in property and having practised business, commercial and property law, we provide current and relevant legal advice on property, general business and commercial matters. With a sub-specialty interest in unit titled, commercial and residential properties, at Resolve we help our clients navigate through their options.</p>
<h4>Property Management:</h4>
<p>At Resolve, we manage a small portfolio of residential properties throughout the Wellington region as a blueprint for best practice to support our investor clients and the property management businesses that we work with throughout New Zealand. Our wealth of experience, industry memberships and best practice methodologies ensures that our business clients benefit from our experience and business processes. We lead the way in ensuring our investors and tenants enjoy a high calibre and professional property management service.</p>
<h4>Inspect Realestate:</h4>
<p>In partnership with realestate.co.nz for New Zealand, Resolve assists Inspect Realestate and realestate.co.nz to deliver a suite of quality property management and business growth tools to the property management industry.</p>
<h3>Property data you can count on</h3>
<p>RealESmart is an online calculator that helps with residential property decision-making. It calculates both investment and yield.</p>
<p>For people buying their first home, considering major renovations or looking at buying a rental property, the RealESmart calculator brings together a range of data from independent and highly reliable sources, enabling people to understand not just the costs of meeting a mortgage, but also other costs like rates and insurance premiums.</p>
<p>It also calculates the returns you might expect – what you could earn if you needed to rent your house out, and what your return on investment would be if you are buying a house for rental purposes.</p>
<p>Our RealESmart property investment calculator has been developed to 'take away the pain and lift the gain' by bringing together independently sourced and highly reliable data sets, building a back-end that can calculate everything from rental appraisals to investment yields, and delivering an interface that is easy to use and fast to deliver.</p>
<p>Now everyone has a tool to help make the best decisions on their property journey.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vsziuap72g5242hxeq5baaiu" alt="" data-big=https://cms-assets.abletech.nz/medium_realesmart1_e665dca0cd.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/realesmart1_e665dca0cd.png" srcset="https://cms-assets.abletech.nz/small_realesmart1_e665dca0cd.png 500w, https://cms-assets.abletech.nz/medium_realesmart1_e665dca0cd.png 750w, https://cms-assets.abletech.nz/thumbnail_realesmart1_e665dca0cd.png 234w" data-zooming-width="750" data-zooming-height="499" loading="lazy" width="750" height="499"></figure>
</div>
<p>The idea came from property consultant Vesna Wells who previously used a spreadsheet to help with property investment decisions. The spreadsheet was good, but her team of property professionals at Resolve knew it could be better.</p>
<blockquote>
<h4>We consulted experts in real estate, property management, investing, first home buyers, renters, property managers, IT and marketing specialists to share their user experience</h4>
</blockquote>
<p><em>— Vesna Wells CEO Resolve, founder RealESmart</em></p>
<p>Turning the spreadsheet into an online calculator made sense. RealESmart is making quite an impact with hundreds of people using it for property income, expense and yield calculations, appraisals, comparisons and more.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lsufnugjk7zxkuks5jb5i77e" alt="" data-big=https://cms-assets.abletech.nz/medium_realesmart3_22ad74890a.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/realesmart3_22ad74890a.png" srcset="https://cms-assets.abletech.nz/small_realesmart3_22ad74890a.png 500w, https://cms-assets.abletech.nz/medium_realesmart3_22ad74890a.png 750w, https://cms-assets.abletech.nz/thumbnail_realesmart3_22ad74890a.png 245w" data-zooming-width="750" data-zooming-height="361" loading="lazy" width="750" height="361"></figure>
</div>
<p><em>Abletech</em></p>
<p>Vesna and Craig from Resolve needed a calculator that could take market data from trusted sources and calculate the return on investment. They engaged Abletech to build RealESmart. At its simplest RealESmart provides free fast rental appraisals, and the premium features give multiple property investors everything they need.</p>
<blockquote>
<h4>We connected with reliable and respected platforms that provide excellent market data, as well as applying our own industry understanding, to enable an intelligent and seamless user experience</h4>
</blockquote>
<p><em>— Vesna Wells CEO Resolve, founder RealESmart</em></p>
<p>RealESmart draws on reliable market data from CoreLogic, MBIE and Resolve’s analyses so that any property investor can make informed decisions about potential investments.</p>
<blockquote>
<h4>The third-party integrations with datasets like CoreLogic have given RealESmart excellent usability</h4>
</blockquote>
<p><em>— Joseph Leniston Abletech Team Lead</em></p>
<h3>The process</h3>
<p>Resolve used a collaborative Agile methodology to build the calculator. Together, Abletech and Resolve did the work in phases. Each phase was separated into sprints with established markers. These gave both Resolve and Abletech shared expectations, and constant visibility, of exactly what was happening. The Agile process combined face-to-face meetings with stand-ups and online workflow collaboration. Everyone was kept in the loop at all times.</p>
<h3>How does Agile work?</h3>
<p>With an Agile process teams work collaboratively. Requirements and solutions are set. Flexible plans are made. Early delivery is shared and continually improved. Changes are made as required.</p>
<p>Stand-ups were scheduled. Stories were set and put into a shared activity interface so everyone could track progress. Acceptance criteria were added to each story. A definition of ‘done’ was agreed.</p>
<blockquote>
<h4>Our Definition of Done was when each sprint was built and deployed into staging for Resolve to assess it and identify any bugs. After we fixed the bugs each sprint was signed off before being deployed into production</h4>
</blockquote>
<p><em>— Joseph Leniston Abletech Team Lead</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s2dcxsscuujbt54xjcq2bcxl" alt="" data-big=https://cms-assets.abletech.nz/medium_realesmart2_9d0ef15b28.jpg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/realesmart2_9d0ef15b28.jpg" srcset="https://cms-assets.abletech.nz/small_realesmart2_9d0ef15b28.jpg 500w, https://cms-assets.abletech.nz/medium_realesmart2_9d0ef15b28.jpg 750w, https://cms-assets.abletech.nz/thumbnail_realesmart2_9d0ef15b28.jpg 234w" data-zooming-width="750" data-zooming-height="500" loading="lazy" width="750" height="500"></figure>
</div>
<p>The team at Resolve built an innovative user-friendly tool for property investors that uses up-to-date property market data. The calculator is popular with property enthusiasts, buyers and investors. Watch this space; the RealESmart team has more features in the pipeline.</p>
<blockquote>
<h4>From mum and dad investors, property managers, real estate professionals and aligned property professionals (business brokers, mortgage brokers, accountants), we have received fantastic feedback that they find RealESmart really easy to use</h4>
</blockquote>
<p><em>— Vesna Wells CEO Resolve, founder RealESmart</em></p>
<h3>What does RealESmart do?</h3>
<p><a href="https://www.realesmart.co.nz/#/home" target="_blank" rel="noopener noreferrer">RealESmart</a> calculates both investment and yield.</p>
<p>✔ Rental appraisals<br>
✔ Mortgage calculations<br>
✔ Property performance yield stats<br>
✔ Investment forecasting<br>
✔ Customisable property settings<br>
✔ Annual expenses<br>
✔ Save unlimited properties<br>
✔ Property ranking and comparison<br>
✔ Printable broker/bank reports<br>
✔ Advanced property details<br>
✔ Property history details<br>
✔ Property photos</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Restart A Heart day</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>It’s international Restart A Heart day ❤️</h2>
<p>You can Restart A Heart anytime anywhere. Find your nearest defibrillator using <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">aedlocations.co.nz or the AED app</a>.</p>
<blockquote>
<p>❤️ Call 111
❤️ Start chest compressions
❤️ Deliver an AED shock</p>
</blockquote>
<p>We send our ❤️ to AED Locations, St John New Zealand, Wellington Free Ambulance, Fire and Emergency NZ, the Health Promotion Agency and Heart Foundation NZ for #RestartAHeart day. Thank you for your work in our communities.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="krr4fsjzj8rcq7hjtlwd0epi" alt="" data-big=https://cms-assets.abletech.nz/large_1_Mexvia_K2ubtc_T4_M_Uj_UDM_3_Q_b9da6fd3ca.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_Mexvia_K2ubtc_T4_M_Uj_UDM_3_Q_b9da6fd3ca.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Mexvia_K2ubtc_T4_M_Uj_UDM_3_Q_b9da6fd3ca.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Mexvia_K2ubtc_T4_M_Uj_UDM_3_Q_b9da6fd3ca.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Mexvia_K2ubtc_T4_M_Uj_UDM_3_Q_b9da6fd3ca.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Mexvia_K2ubtc_T4_M_Uj_UDM_3_Q_b9da6fd3ca.jpeg 156w" data-zooming-width="1000" data-zooming-height="1000" loading="lazy" width="1000" height="1000"></figure>
</div>
<h3>Where will you be when you’re a bystander to a heart attack?</h3>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">AED Locations</a> will guide you to your nearest defibrillators.</p>
<ul>
<li>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">Check out the website</a></p>
</li>
<li>
<p><a href="https://itunes.apple.com/nz/app/aed-locations/id424094430?mt=8" target="_blank" rel="noopener noreferrer">Download the AED Locations app from iTunes</a></p>
</li>
<li>
<p><a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations" target="_blank" rel="noopener noreferrer">Download the AED Locations app from Google Play</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Abletech and CO2 Emissions</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Recently, with the encouragement of Abletech Director Nigel Ramsay, we looked at how our company is contributing to CO2 emissions. Nigel has written about his family’s improvements. Next we looked at Abletech.</h2>
<p>Where do you start? The New Zealand <a href="https://sustainable.org.nz" target="_blank" rel="noopener noreferrer">Sustainable Business Network</a> has lots of great advice. It has published a very good paper on <a href="https://sustainable.org.nz/wp-content/uploads/2014/02/How-to-calculate-your-carbon-emissions.pdf" target="_blank" rel="noopener noreferrer">how to calculate your carbon emissions</a>. Following their advice and using the recommended <a href="http://catalystnz.co.nz/ace-carbon-calculator" target="_blank" rel="noopener noreferrer">ACE Carbon Calculator</a>, which is an independently developed New Zealand specific tool, NZ is now starting to understand our companies’ contributions to CO2 emissions.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="u1ram3oyekwjvak8df0eo86h" alt="" data-big=https://cms-assets.abletech.nz/small_11_L2_HCJ_8_Jlxt9bxgqurks_Xw_0caaeed4ce.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/11_L2_HCJ_8_Jlxt9bxgqurks_Xw_0caaeed4ce.png" srcset="https://cms-assets.abletech.nz/small_11_L2_HCJ_8_Jlxt9bxgqurks_Xw_0caaeed4ce.png 500w, https://cms-assets.abletech.nz/thumbnail_11_L2_HCJ_8_Jlxt9bxgqurks_Xw_0caaeed4ce.png 245w" data-zooming-width="500" data-zooming-height="309" loading="lazy" width="500" height="309"></figure>
</div>
<p>With just two months of tracking under our belts we can see that Transport, specifically flights, is our biggest contributor. The big difference between October and November flights is that we had some international travel. As a rule we seek to minimise travel, and use video conferencing, but sometimes face-to-face meetings have to happen. Now we are understanding our baseline CO2 emissions this will allow us to target reductions.</p>
<p>Electricity is our second highest contributor to our carbon footprint. This is an area where we have already started making changes. Earlier this year we visited the <a href="https://abletech.nz/article/ecocentre">EcoCentre</a> run by the Wellington Sustainability Trust, after that visit we came away with some things we could do at work, and at home, to improve our energy efficiency.</p>
<p>In our office, for point heating solutions, specifically our meeting rooms, we have adopted <a href="https://sustaintrust.org.nz/collections/heating/products/atlantic-tatou-radiant-heater-with-digital-built-in-timer" target="_blank" rel="noopener noreferrer">efficient radiant heaters</a> with motion sensors so they are only in use when required.</p>
<p>We have also installed <a href="https://pricespy.co.nz/product.php?q=HS100&amp;p=4397913" target="_blank" rel="noopener noreferrer">WiFi Smart Switches</a> on high-power-use items such as our coffee machine and fresh air venting system, so these power consumers can be switched off when our office is closed. The smart switch is WiFi controlled and enables scheduled On/Off switching with an easy-to-use app.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="apeshdpy899gt6fmiq38r1ku" alt="" data-big=https://cms-assets.abletech.nz/medium_18h_Nt_Tua_Pw_Z_Ap1_x0o_OVJA_7230e0104a.jpeg sizes="(min-width: 768px) 363px, (min-width: 1024px) 545px, (min-width: 640px) 113px" src="https://cms-assets.abletech.nz/18h_Nt_Tua_Pw_Z_Ap1_x0o_OVJA_7230e0104a.jpeg" srcset="https://cms-assets.abletech.nz/small_18h_Nt_Tua_Pw_Z_Ap1_x0o_OVJA_7230e0104a.jpeg 363w, https://cms-assets.abletech.nz/medium_18h_Nt_Tua_Pw_Z_Ap1_x0o_OVJA_7230e0104a.jpeg 545w, https://cms-assets.abletech.nz/thumbnail_18h_Nt_Tua_Pw_Z_Ap1_x0o_OVJA_7230e0104a.jpeg 113w" data-zooming-width="545" data-zooming-height="750" loading="lazy" width="545" height="750"></figure>
</div>
<p>We are just at the beginning of understanding our CO2 emissions baseline. But as Peter Drucker says:</p>
<blockquote>
<p>If you can’t measure it, you can’t improve it</p>
</blockquote>
<p>We have now started to measure so that we can improve.</p>
<h3>Read more</h3>
<ul>
<li>Part 2 of our <a href="https://abletech.nz/article/abletech-and-co2-emissions-growing-our-understanding">growing understanding of our emissions</a></li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Kia kaha te reo</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Encourage and promote the Māori voice</h2>
<p>Māori Language Week helps us all remember that te reo is a cornerstone of New Zealand culture, and an important part of our country’s heritage. Te reo Māori is an official language of NZ. It’s a great time to be part of the broader language revival and raise public awareness and usage. Our participation links with our #TeamValues.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q24kpmpb59pgmfxxblqbmbn8" alt="" data-big=https://cms-assets.abletech.nz/large_1j_Zrtojj_GD_Bpmklm_Ws_Xy3_Rw_5231366c23.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1j_Zrtojj_GD_Bpmklm_Ws_Xy3_Rw_5231366c23.png" srcset="https://cms-assets.abletech.nz/large_1j_Zrtojj_GD_Bpmklm_Ws_Xy3_Rw_5231366c23.png 1000w, https://cms-assets.abletech.nz/small_1j_Zrtojj_GD_Bpmklm_Ws_Xy3_Rw_5231366c23.png 500w, https://cms-assets.abletech.nz/medium_1j_Zrtojj_GD_Bpmklm_Ws_Xy3_Rw_5231366c23.png 750w, https://cms-assets.abletech.nz/thumbnail_1j_Zrtojj_GD_Bpmklm_Ws_Xy3_Rw_5231366c23.png 245w" data-zooming-width="1000" data-zooming-height="606" loading="lazy" width="1000" height="606"></figure>
</div>
<p>A diverse range of nationalities are represented in our team. We want to encourage everyone to learn, and use, te reo Māori at work and in our communities.</p>
<h3>How will we participate?</h3>
<ul>
<li>
<p>Use Māori greetings in our verbal and non-verbal communication</p>
</li>
<li>
<p>Learn-as-we go with informative visuals around our workplace</p>
</li>
<li>
<p>Offer <a href="http://www.tokureo.maori.nz/" target="_blank" rel="noopener noreferrer">video tutorials</a> at lunchtimes during Māori Language Week</p>
</li>
<li>
<p>Offer cultural competency professional development to go further in our understanding</p>
</li>
<li>
<p>Label particular items so we can ako (learn) names</p>
</li>
<li>
<p>Share te reo agile methodology terms</p>
</li>
<li>
<p>Encourage the <a href="https://kupu.co.nz/" target="_blank" rel="noopener noreferrer">Kupu app</a> for learning new words</p>
</li>
</ul>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="o7tzllkr8bu9u05afvi5fsdp" alt="" data-big=https://cms-assets.abletech.nz/large_1c_Hp9b_Oox_Hchvc_I_Fbd_Kk_Fag_6e495661c6.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1c_Hp9b_Oox_Hchvc_I_Fbd_Kk_Fag_6e495661c6.png" srcset="https://cms-assets.abletech.nz/large_1c_Hp9b_Oox_Hchvc_I_Fbd_Kk_Fag_6e495661c6.png 1000w, https://cms-assets.abletech.nz/small_1c_Hp9b_Oox_Hchvc_I_Fbd_Kk_Fag_6e495661c6.png 500w, https://cms-assets.abletech.nz/medium_1c_Hp9b_Oox_Hchvc_I_Fbd_Kk_Fag_6e495661c6.png 750w, https://cms-assets.abletech.nz/thumbnail_1c_Hp9b_Oox_Hchvc_I_Fbd_Kk_Fag_6e495661c6.png 245w" data-zooming-width="1000" data-zooming-height="566" loading="lazy" width="1000" height="566"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ccy220srvq8uvz1s1jjfhwq5" alt="" data-big=https://cms-assets.abletech.nz/medium_1_RD_29_Nf_SWJJ_2p_UG_1z_Co1_L8_Q_53ba9b70af.png sizes="(min-width: 768px) 368px, (min-width: 1024px) 552px, (min-width: 640px) 115px" src="https://cms-assets.abletech.nz/1_RD_29_Nf_SWJJ_2p_UG_1z_Co1_L8_Q_53ba9b70af.png" srcset="https://cms-assets.abletech.nz/small_1_RD_29_Nf_SWJJ_2p_UG_1z_Co1_L8_Q_53ba9b70af.png 368w, https://cms-assets.abletech.nz/medium_1_RD_29_Nf_SWJJ_2p_UG_1z_Co1_L8_Q_53ba9b70af.png 552w, https://cms-assets.abletech.nz/thumbnail_1_RD_29_Nf_SWJJ_2p_UG_1z_Co1_L8_Q_53ba9b70af.png 115w" data-zooming-width="552" data-zooming-height="750" loading="lazy" width="552" height="750"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wuqlnf71zy727i0ekx0kmlmp" alt="" data-big=https://cms-assets.abletech.nz/large_1ny6_Cm_Pfvks_J8_Iy_Sw_We_T1_Sw_a05797b501.jpeg sizes="(min-width: 1280px) 688px, (min-width: 768px) 344px, (min-width: 1024px) 516px, (min-width: 640px) 107px" src="https://cms-assets.abletech.nz/1ny6_Cm_Pfvks_J8_Iy_Sw_We_T1_Sw_a05797b501.jpeg" srcset="https://cms-assets.abletech.nz/large_1ny6_Cm_Pfvks_J8_Iy_Sw_We_T1_Sw_a05797b501.jpeg 688w, https://cms-assets.abletech.nz/small_1ny6_Cm_Pfvks_J8_Iy_Sw_We_T1_Sw_a05797b501.jpeg 344w, https://cms-assets.abletech.nz/medium_1ny6_Cm_Pfvks_J8_Iy_Sw_We_T1_Sw_a05797b501.jpeg 516w, https://cms-assets.abletech.nz/thumbnail_1ny6_Cm_Pfvks_J8_Iy_Sw_We_T1_Sw_a05797b501.jpeg 107w" data-zooming-width="688" data-zooming-height="1000" loading="lazy" width="688" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="d0jytbwc0ha71kq66l4erhyv" alt="" data-big=https://cms-assets.abletech.nz/medium_1x6_Xkm_Hlz_Nh3_Vu_MT_An3_Mb_A_4362be5951.png sizes="(min-width: 768px) 478px, (min-width: 1024px) 717px, (min-width: 640px) 149px" src="https://cms-assets.abletech.nz/1x6_Xkm_Hlz_Nh3_Vu_MT_An3_Mb_A_4362be5951.png" srcset="https://cms-assets.abletech.nz/small_1x6_Xkm_Hlz_Nh3_Vu_MT_An3_Mb_A_4362be5951.png 478w, https://cms-assets.abletech.nz/medium_1x6_Xkm_Hlz_Nh3_Vu_MT_An3_Mb_A_4362be5951.png 717w, https://cms-assets.abletech.nz/thumbnail_1x6_Xkm_Hlz_Nh3_Vu_MT_An3_Mb_A_4362be5951.png 149w" data-zooming-width="717" data-zooming-height="750" loading="lazy" width="717" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="a6o8seto8vy2w08nomvci6yj" alt="" data-big=https://cms-assets.abletech.nz/large_1w_SEFA_Ns1x_Hynj_W_siwg_Xyw_4b3ff4ad60.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1w_SEFA_Ns1x_Hynj_W_siwg_Xyw_4b3ff4ad60.png" srcset="https://cms-assets.abletech.nz/large_1w_SEFA_Ns1x_Hynj_W_siwg_Xyw_4b3ff4ad60.png 1000w, https://cms-assets.abletech.nz/small_1w_SEFA_Ns1x_Hynj_W_siwg_Xyw_4b3ff4ad60.png 500w, https://cms-assets.abletech.nz/medium_1w_SEFA_Ns1x_Hynj_W_siwg_Xyw_4b3ff4ad60.png 750w, https://cms-assets.abletech.nz/thumbnail_1w_SEFA_Ns1x_Hynj_W_siwg_Xyw_4b3ff4ad60.png 245w" data-zooming-width="1000" data-zooming-height="547" loading="lazy" width="1000" height="547"></figure>
</div>
<p>Māori Language Week is September 14–20 or 14–20 Mahuru.</p>
<p>Encouraging and promoting Māori Language connects with our Abletech Team Values. <a href="https://stories.abletech.nz/what-makes-us-tick-848956cf7700" target="_blank" rel="noopener noreferrer">Read more about that here.</a></p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ww8rxwtdun24dgwh37dycyys" alt="" data-big=https://cms-assets.abletech.nz/medium_1_I6r_KF_9_P1yaoh_Hjsq_DCB_Zg_be48f870cd.png sizes="(min-width: 768px) 354px, (min-width: 1024px) 531px, (min-width: 640px) 111px" src="https://cms-assets.abletech.nz/1_I6r_KF_9_P1yaoh_Hjsq_DCB_Zg_be48f870cd.png" srcset="https://cms-assets.abletech.nz/small_1_I6r_KF_9_P1yaoh_Hjsq_DCB_Zg_be48f870cd.png 354w, https://cms-assets.abletech.nz/medium_1_I6r_KF_9_P1yaoh_Hjsq_DCB_Zg_be48f870cd.png 531w, https://cms-assets.abletech.nz/thumbnail_1_I6r_KF_9_P1yaoh_Hjsq_DCB_Zg_be48f870cd.png 111w" data-zooming-width="531" data-zooming-height="750" loading="lazy" width="531" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vzo4sawrvu17xecmo3q7r26j" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_11nr4_Q8_R_Uj_E0_Tkk_J9m_Dc_RA_3be1d966d3.png sizes="(min-width: 640px) 124px" src="https://cms-assets.abletech.nz/11nr4_Q8_R_Uj_E0_Tkk_J9m_Dc_RA_3be1d966d3.png" srcset="https://cms-assets.abletech.nz/thumbnail_11nr4_Q8_R_Uj_E0_Tkk_J9m_Dc_RA_3be1d966d3.png 124w" data-zooming-width="124" data-zooming-height="156" loading="lazy" width="124" height="156"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qsz9kw5uqzv9g4a2ab3y9z72" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_V_Wa_Jhu_FG_4z_R_JDP_0g_Wtot_A_4db5b81262.png sizes="(min-width: 640px) 174px" src="https://cms-assets.abletech.nz/1_V_Wa_Jhu_FG_4z_R_JDP_0g_Wtot_A_4db5b81262.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_V_Wa_Jhu_FG_4z_R_JDP_0g_Wtot_A_4db5b81262.png 174w" data-zooming-width="174" data-zooming-height="156" loading="lazy" width="174" height="156"></figure>
</div>
<h3>Background</h3>
<p>Every year since 1975 New Zealand has marked Māori Language Week. The campaign to revive the language has been a long one. There was a time when te reo was suppressed in schools and society. In the mid-1980s Sir James Henare recalled being sent into the bush to cut a piece of pirita (supplejack vine) with which he was struck for speaking te reo in the school grounds.</p>
<p>The future of te reo Māori was the subject of a claim before the Waitangi Tribunal in 1985. The tribunal’s recommendations were far-reaching. Māori became an official language of New Zealand in 1987. Te Taura Whiri i te Reo Māori was established in the same year to promote te reo. Along with the Human Rights Commission and Te Puni Kōkiri, it plays a key role in the annual Māori Language Week. <a href="https://nzhistory.govt.nz/culture/maori-language-week" target="_blank" rel="noopener noreferrer">Read more about the history of the language</a> and <a href="https://nzhistory.govt.nz/culture/maori-language-week/100-maori-words" target="_blank" rel="noopener noreferrer">hear 100 words every New Zealander should know</a>.</p>
<p>#ContinuousImprovement #SupportAndRespectForAll #Sustainable</p>
<p>#TeWikioteReoMāori, #MāoriLanguageWeek #TeReo</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Sustainable future</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>We’re interested in working towards a sustainable future for our global environment</h2>
<p>At Abletech we have embarked on the process of seeking advice and following recommendations. Read about some of our efforts below.</p>
<p>The tech sector is working on the climate crisis and finding ways to take action against the negative effects our industry has on the environment. Groups like our local one, Sustainable Tech, are useful for enabling technology professionals to learn more and take action.</p>
<p>Our shared vision is that we can support the broader climate movement through exploring the impact of the tech industry, on our natural environment.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hvhzvso4b4jo21e84la1tee0" alt="" data-big=https://cms-assets.abletech.nz/small_1_L_Dztptqg_KF_8_FW_Sxmb_Ir_BDA_d992dfca02.png sizes="(min-width: 768px) 500px, (min-width: 640px) 237px" src="https://cms-assets.abletech.nz/1_L_Dztptqg_KF_8_FW_Sxmb_Ir_BDA_d992dfca02.png" srcset="https://cms-assets.abletech.nz/small_1_L_Dztptqg_KF_8_FW_Sxmb_Ir_BDA_d992dfca02.png 500w, https://cms-assets.abletech.nz/thumbnail_1_L_Dztptqg_KF_8_FW_Sxmb_Ir_BDA_d992dfca02.png 237w" data-zooming-width="500" data-zooming-height="329" loading="lazy" width="500" height="329"></figure>
</div>
<h3>Dec 2018:</h3>
<p><a href="https://abletech.nz/article/abletech-and-co2-emissions"><strong>Abletech and CO2 Emissions</strong></a></p>
<h3>Feb 2019:</h3>
<ul>
<li><a href="https://abletech.nz/article/abletech-and-co2-emissions-growing-our-understanding"><strong>Abletech and CO2 emissions — growing our understanding</strong></a></li>
</ul>
<h3>April 2019:</h3>
<ul>
<li><a href="https://abletech.nz/article/recording-our-carbon-footprint-at-home"><strong>Recording our carbon footprint at home</strong></a></li>
</ul>
<h3>June 2019:</h3>
<ul>
<li><a href="https://abletech.nz/article/ev-q-a-sounds-eco-friendly-in-that-it-doesnt-use-fossil-fuel-but-what-about-the-batteries"><strong>EV Q&amp;A: Sounds eco-friendly in that it doesn’t use fossil fuel, but what about the batteries?</strong></a></li>
</ul>
<h3>Aug 2019:</h3>
<ul>
<li><a href="https://abletech.nz/article/carbon-zero-electricity"><strong>Carbon Zero electricity</strong></a></li>
<li><a href="https://abletech.nz/article/moo-ving-to-glass"><strong>🥛 Moo-ving to glass 🐮</strong></a></li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Toitū zoo — carbon neutral</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Abletech family do at the zoo</h2>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wylgrju9p9arqcgbl6xvx1x7" alt="" data-big=https://cms-assets.abletech.nz/large_1_F9bp_K0c_Tv_Jgl9_RV_Az_W6br_A_efe5ae1ea0.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_F9bp_K0c_Tv_Jgl9_RV_Az_W6br_A_efe5ae1ea0.jpeg" srcset="https://cms-assets.abletech.nz/large_1_F9bp_K0c_Tv_Jgl9_RV_Az_W6br_A_efe5ae1ea0.jpeg 1000w, https://cms-assets.abletech.nz/small_1_F9bp_K0c_Tv_Jgl9_RV_Az_W6br_A_efe5ae1ea0.jpeg 500w, https://cms-assets.abletech.nz/medium_1_F9bp_K0c_Tv_Jgl9_RV_Az_W6br_A_efe5ae1ea0.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_F9bp_K0c_Tv_Jgl9_RV_Az_W6br_A_efe5ae1ea0.jpeg 245w" data-zooming-width="1000" data-zooming-height="491" loading="lazy" width="1000" height="491"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vfttgknu89qpri4gxl1w46qf" alt="" data-big=https://cms-assets.abletech.nz/large_1mt63_Z0_PE_2rk_Zo_L_Ol7c_V_Iqg_36f6fce321.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 211px" src="https://cms-assets.abletech.nz/1mt63_Z0_PE_2rk_Zo_L_Ol7c_V_Iqg_36f6fce321.jpeg" srcset="https://cms-assets.abletech.nz/large_1mt63_Z0_PE_2rk_Zo_L_Ol7c_V_Iqg_36f6fce321.jpeg 1000w, https://cms-assets.abletech.nz/small_1mt63_Z0_PE_2rk_Zo_L_Ol7c_V_Iqg_36f6fce321.jpeg 500w, https://cms-assets.abletech.nz/medium_1mt63_Z0_PE_2rk_Zo_L_Ol7c_V_Iqg_36f6fce321.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1mt63_Z0_PE_2rk_Zo_L_Ol7c_V_Iqg_36f6fce321.jpeg 211w" data-zooming-width="1000" data-zooming-height="741" loading="lazy" width="1000" height="741"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="c7o3075x61hys94cd9x2xe2y" alt="" data-big=https://cms-assets.abletech.nz/large_1_Le_OQG_9_A_Xg_F1v_Xhh_Jxrw_Oag_46ae766720.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Le_OQG_9_A_Xg_F1v_Xhh_Jxrw_Oag_46ae766720.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Le_OQG_9_A_Xg_F1v_Xhh_Jxrw_Oag_46ae766720.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Le_OQG_9_A_Xg_F1v_Xhh_Jxrw_Oag_46ae766720.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Le_OQG_9_A_Xg_F1v_Xhh_Jxrw_Oag_46ae766720.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Le_OQG_9_A_Xg_F1v_Xhh_Jxrw_Oag_46ae766720.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p>We went wild for our Christmas celebrations and spent a lovely sunny Saturday in Wellington’s green belt at the world’s first Toitū zoo. Wellington’s zoo is officially carbon neutral.</p>
<p>We stayed into the evening for dinner and enjoyed watching the monkeys cavorting around in their trees from our deck at the Archibald Centre. Fantastic to see the entire zoo looking so well cared for.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="o0t3kume7k3i89s740yzw535" alt="" data-big=https://cms-assets.abletech.nz/medium_1_K8_KAA_Cz_Rh2_Hsvu_U5_n4e_Xg_937d251c2e.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 219px" src="https://cms-assets.abletech.nz/1_K8_KAA_Cz_Rh2_Hsvu_U5_n4e_Xg_937d251c2e.jpeg" srcset="https://cms-assets.abletech.nz/small_1_K8_KAA_Cz_Rh2_Hsvu_U5_n4e_Xg_937d251c2e.jpeg 500w, https://cms-assets.abletech.nz/medium_1_K8_KAA_Cz_Rh2_Hsvu_U5_n4e_Xg_937d251c2e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_K8_KAA_Cz_Rh2_Hsvu_U5_n4e_Xg_937d251c2e.jpeg 219w" data-zooming-width="750" data-zooming-height="534" loading="lazy" width="750" height="534"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t96vg02na0yl4v4yeikz72p8" alt="" data-big=https://cms-assets.abletech.nz/large_1_Z_Gp8_CVA_Ks1iwc_R5rbwg_x_A_14962cd3b2.jpeg sizes="(min-width: 1280px) 997px, (min-width: 768px) 499px, (min-width: 1024px) 748px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_Z_Gp8_CVA_Ks1iwc_R5rbwg_x_A_14962cd3b2.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Z_Gp8_CVA_Ks1iwc_R5rbwg_x_A_14962cd3b2.jpeg 997w, https://cms-assets.abletech.nz/small_1_Z_Gp8_CVA_Ks1iwc_R5rbwg_x_A_14962cd3b2.jpeg 499w, https://cms-assets.abletech.nz/medium_1_Z_Gp8_CVA_Ks1iwc_R5rbwg_x_A_14962cd3b2.jpeg 748w, https://cms-assets.abletech.nz/thumbnail_1_Z_Gp8_CVA_Ks1iwc_R5rbwg_x_A_14962cd3b2.jpeg 156w" data-zooming-width="997" data-zooming-height="1000" loading="lazy" width="997" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="c1gdupjihhjmqn1ya41xvc1s" alt="" data-big=https://cms-assets.abletech.nz/large_1dt6rhk7l_Dm_H89_B8_C_Lo_Nlzg_6d8d054007.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1dt6rhk7l_Dm_H89_B8_C_Lo_Nlzg_6d8d054007.jpeg" srcset="https://cms-assets.abletech.nz/large_1dt6rhk7l_Dm_H89_B8_C_Lo_Nlzg_6d8d054007.jpeg 1000w, https://cms-assets.abletech.nz/small_1dt6rhk7l_Dm_H89_B8_C_Lo_Nlzg_6d8d054007.jpeg 500w, https://cms-assets.abletech.nz/medium_1dt6rhk7l_Dm_H89_B8_C_Lo_Nlzg_6d8d054007.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1dt6rhk7l_Dm_H89_B8_C_Lo_Nlzg_6d8d054007.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p>Home to more than 500 native and exotic endangered animals of all shapes and sizes. Wellington Zoo has been caring for animals since 1906. It’s committed to excellence in animal welfare and conservation.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ygp6s69w4kpsdh0i724qip2f" alt="" data-big=https://cms-assets.abletech.nz/large_1_Oq_O_Hn_Jrpnr_Kq_S_Tl_JQ_Kmbr_Q_8dc4103cdd.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 221px" src="https://cms-assets.abletech.nz/1_Oq_O_Hn_Jrpnr_Kq_S_Tl_JQ_Kmbr_Q_8dc4103cdd.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Oq_O_Hn_Jrpnr_Kq_S_Tl_JQ_Kmbr_Q_8dc4103cdd.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Oq_O_Hn_Jrpnr_Kq_S_Tl_JQ_Kmbr_Q_8dc4103cdd.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Oq_O_Hn_Jrpnr_Kq_S_Tl_JQ_Kmbr_Q_8dc4103cdd.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Oq_O_Hn_Jrpnr_Kq_S_Tl_JQ_Kmbr_Q_8dc4103cdd.jpeg 221w" data-zooming-width="1000" data-zooming-height="706" loading="lazy" width="1000" height="706"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tmxvpy35iwm4cdw5lgan5e2b" alt="" data-big=https://cms-assets.abletech.nz/large_1_GR_0_I0_MA_4_Xc_UP_Hj5_G_Yfnj3w_c7bb8fa005.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_GR_0_I0_MA_4_Xc_UP_Hj5_G_Yfnj3w_c7bb8fa005.jpeg" srcset="https://cms-assets.abletech.nz/large_1_GR_0_I0_MA_4_Xc_UP_Hj5_G_Yfnj3w_c7bb8fa005.jpeg 1000w, https://cms-assets.abletech.nz/small_1_GR_0_I0_MA_4_Xc_UP_Hj5_G_Yfnj3w_c7bb8fa005.jpeg 500w, https://cms-assets.abletech.nz/medium_1_GR_0_I0_MA_4_Xc_UP_Hj5_G_Yfnj3w_c7bb8fa005.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_GR_0_I0_MA_4_Xc_UP_Hj5_G_Yfnj3w_c7bb8fa005.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>What makes us tick</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Figuring out our cultural cornerstones</h2>
<p>At Abletech we’re proud of our team’s culture. This week we identified exactly what our team values are. It was great! The workshop was facilitated by our GM Michelle. Everyone had a chance to speak and we collaborated to agree on four team values.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zg4e07wtskk8bti16z919tlt" alt="" data-big=https://cms-assets.abletech.nz/large_17gr_G_Da7sa_Z3_B8_Pr_GR_Aw_F_Aw_d56ccb4c03.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/17gr_G_Da7sa_Z3_B8_Pr_GR_Aw_F_Aw_d56ccb4c03.jpeg" srcset="https://cms-assets.abletech.nz/large_17gr_G_Da7sa_Z3_B8_Pr_GR_Aw_F_Aw_d56ccb4c03.jpeg 1000w, https://cms-assets.abletech.nz/small_17gr_G_Da7sa_Z3_B8_Pr_GR_Aw_F_Aw_d56ccb4c03.jpeg 500w, https://cms-assets.abletech.nz/medium_17gr_G_Da7sa_Z3_B8_Pr_GR_Aw_F_Aw_d56ccb4c03.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_17gr_G_Da7sa_Z3_B8_Pr_GR_Aw_F_Aw_d56ccb4c03.jpeg 245w" data-zooming-width="1000" data-zooming-height="437" loading="lazy" width="1000" height="437"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ublf9mtt5j0fy4aqhv8fm5tz" alt="" data-big=https://cms-assets.abletech.nz/large_1_C_Tt_Hq_Jg8t_Bqb_K_Wj5lxzzw_e426729099.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_C_Tt_Hq_Jg8t_Bqb_K_Wj5lxzzw_e426729099.jpeg" srcset="https://cms-assets.abletech.nz/large_1_C_Tt_Hq_Jg8t_Bqb_K_Wj5lxzzw_e426729099.jpeg 1000w, https://cms-assets.abletech.nz/small_1_C_Tt_Hq_Jg8t_Bqb_K_Wj5lxzzw_e426729099.jpeg 500w, https://cms-assets.abletech.nz/medium_1_C_Tt_Hq_Jg8t_Bqb_K_Wj5lxzzw_e426729099.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_C_Tt_Hq_Jg8t_Bqb_K_Wj5lxzzw_e426729099.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="r0bujwh1ymdkh9ewnosvdfug" alt="" data-big=https://cms-assets.abletech.nz/large_1xj4go_Exm_HT_Mpbuzga1_Rm_Fw_8f82ba152d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1xj4go_Exm_HT_Mpbuzga1_Rm_Fw_8f82ba152d.jpeg" srcset="https://cms-assets.abletech.nz/large_1xj4go_Exm_HT_Mpbuzga1_Rm_Fw_8f82ba152d.jpeg 1000w, https://cms-assets.abletech.nz/small_1xj4go_Exm_HT_Mpbuzga1_Rm_Fw_8f82ba152d.jpeg 500w, https://cms-assets.abletech.nz/medium_1xj4go_Exm_HT_Mpbuzga1_Rm_Fw_8f82ba152d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1xj4go_Exm_HT_Mpbuzga1_Rm_Fw_8f82ba152d.jpeg 245w" data-zooming-width="1000" data-zooming-height="443" loading="lazy" width="1000" height="443"></figure>
</div>
<p>In the days leading up to our collective values workshop each of us thought about our best strengths and what we value most about Abletech.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="icga1161pedlzc9bvr18c9mj" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_Vau_VTL_Si1_LO_Gq_Cf_Ha_A_On_Rw_4e383f5597.jpeg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Vau_VTL_Si1_LO_Gq_Cf_Ha_A_On_Rw_4e383f5597.jpeg" srcset="https://cms-assets.abletech.nz/thumbnail_1_Vau_VTL_Si1_LO_Gq_Cf_Ha_A_On_Rw_4e383f5597.jpeg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e0gcqygav3brzy63nk4z8po7" alt="" data-big=https://cms-assets.abletech.nz/large_1m_Bgn8_Iy_I_n9q_Ixxq_Cpgqqg_6aae8aef87.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1m_Bgn8_Iy_I_n9q_Ixxq_Cpgqqg_6aae8aef87.jpeg" srcset="https://cms-assets.abletech.nz/large_1m_Bgn8_Iy_I_n9q_Ixxq_Cpgqqg_6aae8aef87.jpeg 1000w, https://cms-assets.abletech.nz/small_1m_Bgn8_Iy_I_n9q_Ixxq_Cpgqqg_6aae8aef87.jpeg 500w, https://cms-assets.abletech.nz/medium_1m_Bgn8_Iy_I_n9q_Ixxq_Cpgqqg_6aae8aef87.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1m_Bgn8_Iy_I_n9q_Ixxq_Cpgqqg_6aae8aef87.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="xl0y08djdzxmza9hujwrkg8i" alt="" data-big=https://cms-assets.abletech.nz/small_1jsu_Xkwyofh_OI_Pal_F_Nk_LA_Jg_a0114d383b.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1jsu_Xkwyofh_OI_Pal_F_Nk_LA_Jg_a0114d383b.jpeg" srcset="https://cms-assets.abletech.nz/small_1jsu_Xkwyofh_OI_Pal_F_Nk_LA_Jg_a0114d383b.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1jsu_Xkwyofh_OI_Pal_F_Nk_LA_Jg_a0114d383b.jpeg 245w" data-zooming-width="500" data-zooming-height="228" loading="lazy" width="500" height="228"></figure>
</div>
<p>Surrounded by inspiration, we paired up to share our values and agree on three or four. After a few rounds of revisions and feedback we identified four shared values we have at Abletech.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dtg8w1g3vy80dw6ni9cj5adp" alt="" data-big=https://cms-assets.abletech.nz/small_10_Jkb_KGF_2_Pq_Dpm_C_Hc7mhqjw_a3f3071b69.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/10_Jkb_KGF_2_Pq_Dpm_C_Hc7mhqjw_a3f3071b69.jpeg" srcset="https://cms-assets.abletech.nz/small_10_Jkb_KGF_2_Pq_Dpm_C_Hc7mhqjw_a3f3071b69.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_10_Jkb_KGF_2_Pq_Dpm_C_Hc7mhqjw_a3f3071b69.jpeg 245w" data-zooming-width="500" data-zooming-height="197" loading="lazy" width="500" height="197"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uxsxcxzpwdmk3689uvs4h45u" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1z4i_Rb_E7_K2_pt_Bn_L3_S9_AG_Hw_7f6af29b91.jpeg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1z4i_Rb_E7_K2_pt_Bn_L3_S9_AG_Hw_7f6af29b91.jpeg" srcset="https://cms-assets.abletech.nz/thumbnail_1z4i_Rb_E7_K2_pt_Bn_L3_S9_AG_Hw_7f6af29b91.jpeg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sn99k5r4q9qrl6lxboctp2dy" alt="" data-big=https://cms-assets.abletech.nz/small_1l8_Rb_Q1zfdea_EQ_Dpd_Oz6_V_Nw_3c2c00708e.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1l8_Rb_Q1zfdea_EQ_Dpd_Oz6_V_Nw_3c2c00708e.jpeg" srcset="https://cms-assets.abletech.nz/small_1l8_Rb_Q1zfdea_EQ_Dpd_Oz6_V_Nw_3c2c00708e.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1l8_Rb_Q1zfdea_EQ_Dpd_Oz6_V_Nw_3c2c00708e.jpeg 245w" data-zooming-width="500" data-zooming-height="229" loading="lazy" width="500" height="229"></figure>
</div>
<p>The next stage is working out what our values mean in practice, how they are woven into our daily work, and any areas for improvement. What a great way to reset our focus and remind ourselves what’s most important about how we work. It’s also giving us a useful reference of our team’s cultural cornerstones.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lt7dua1o05us2jsw2c7op9ts" alt="" data-big=https://cms-assets.abletech.nz/medium_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 186px" src="https://cms-assets.abletech.nz/1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png" srcset="https://cms-assets.abletech.nz/small_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png 500w, https://cms-assets.abletech.nz/medium_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png 750w, https://cms-assets.abletech.nz/thumbnail_1_F7_H6_GF_3_RQ_Yy_H_Pc_Tk6p1_Dw_A_f470ecafb1.png 186w" data-zooming-width="750" data-zooming-height="629" loading="lazy" width="750" height="629"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Build your next app on Blockchain inspired technology</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Reviewing: AWS service Amazon Quantum Ledger Database (QLDB)</h2>
<p>How secure are your apps? Spoiler — not very.</p>
<p>Having a security oriented development pipeline will go a long way in securing your online applications. The unfortunate reality is however, that your system probably still has undiscovered security holes. No amount of pen testing or security code reviews will uncover 100% of them and it would be naive to think otherwise.</p>
<p>So given that our systems and processes are fallible, what can we do to protect the integrity of the data in the case of a breach?</p>
<h3>Protect your data integrity</h3>
<p>I suggest building your systems, or parts of your systems, on immutable databases, and outsourcing the management and security model of the databases to trusted cloud services. Erroneous record changes can then be identified, and reversed, and integrity recovered.</p>
<p>Welcome to Amazon Quantum Ledger Database (QLDB)</p>
<blockquote>
<p>QLDB will store a complete record of your history — period</p>
</blockquote>
<p>Amazon offering a service for a journal database is a big deal. Incorporating it into your solutions where data integrity matters reduces your risk and will help you sleep.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vs4jtpdwoviofb0pym1bmqiw" alt="[https://aws.amazon.com/qldb/](https://aws.amazon.com/qldb/)" data-big=https://cms-assets.abletech.nz/large_1p_V_ZU_9_Wsf_Pl_Q2jr0_QWB_Qe_Q_98d1002772.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1p_V_ZU_9_Wsf_Pl_Q2jr0_QWB_Qe_Q_98d1002772.png" srcset="https://cms-assets.abletech.nz/large_1p_V_ZU_9_Wsf_Pl_Q2jr0_QWB_Qe_Q_98d1002772.png 1000w, https://cms-assets.abletech.nz/small_1p_V_ZU_9_Wsf_Pl_Q2jr0_QWB_Qe_Q_98d1002772.png 500w, https://cms-assets.abletech.nz/medium_1p_V_ZU_9_Wsf_Pl_Q2jr0_QWB_Qe_Q_98d1002772.png 750w, https://cms-assets.abletech.nz/thumbnail_1p_V_ZU_9_Wsf_Pl_Q2jr0_QWB_Qe_Q_98d1002772.png 245w" data-zooming-width="1000" data-zooming-height="488" loading="lazy" width="1000" height="488"></figure>
</div>
<p><em><a href="https://aws.amazon.com/qldb/" target="_blank" rel="noopener noreferrer">https://aws.amazon.com/qldb/</a></em></p>
<p>Ask yourself, what would happen if someone changed or deleted some of your data? Can anyone gain or lose something of value if the data integrity is compromised. For example: What is the impact if an intruder wipes out your entire customer email records?</p>
<p>You may be doing backups, but how many important transactions have occurred since the last backup? How long do you keep backups, how realistic is it that you can recover data from backups that may have been caused from a long running security breach/ or software bug?</p>
<p>The journal system ensures nothing is mutated, a full history and audit is available, and this complexity is provided to you by the service — no need to build a sub standard system yourself. This will pay off big time when you need to trace back through time to understand an error in your dataset. The database is audited, and timestamped, and that process is not managed by your application code which is also subject to bugs and attacks.</p>
<blockquote>
<p>This not only protects you against intruders but also software bugs as well.</p>
</blockquote>
<p>Think of a migration that has an unforeseen side effect of deleting some business critical data. The error can be identified, and reversing transactions added to the dataset to restore integrity.</p>
<blockquote>
<p>It’s cryptographically verifiable — so basically blockchain. Want your startup value to go 10x? Put blockchain on the tin.</p>
</blockquote>
<p>The database should also be reasonably familiar to your developers. It is fully ACID, multi AZ (highly available and durable), serverless (scales to your needs and no infrastructure to maintain), joins, and indexes (although not composite indexes yet). It has an SQL ‘like’ API as well as a document oriented ION API.</p>
<p>With QLDB, your ledger is still only as safe as your AWS environment. Ensure you have your policies AWS security policies watertight. If your AWS environment is compromised, your append-only database could potentially be replaced with another version of it (public block chain to the rescue).</p>
<p>As with any cloud service, you need to think about your development and testing pipelines. You want to keep this efficient as possible so you don’t compromise project productivity.</p>
<p>Contact us to see what other cloud services you might be able to leverage in your solutions or how to have performant development processes in a cloud service environment.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Performance comparison for Docker for Mac, VM Linux Docker and Linux Docker Native</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="c128fr9nm7yf2zkmaf95snky" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_15_Hm_Ryrgi5yq_Qxg_TW_4_Moz_Q_28aecc1e5a.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/15_Hm_Ryrgi5yq_Qxg_TW_4_Moz_Q_28aecc1e5a.png" srcset="https://cms-assets.abletech.nz/thumbnail_15_Hm_Ryrgi5yq_Qxg_TW_4_Moz_Q_28aecc1e5a.png 245w" data-zooming-width="245" data-zooming-height="138" loading="lazy" width="245" height="138"></figure>
</div>
<p>Reducing developer cycle time between a code change and running a test case is hugely important for an effective working environment. Developers can maintain focus and not get distracted by pausing and waiting for things to finish.</p>
<p>With Docker becoming a standardised toolset for developing and running services locally, it is important that it does not add additional overhead in cycle time.</p>
<p>Here we compare the performance of various machine setups. We run a single test, and the entire test suite, across an Elixir service. The change induced was a config change which involves a recompile (87 files), and we initiated the test framework. The second test included the same change but running the full test suite.</p>
<pre><code class="hljs language-text">╔══════════════════════════════╦══════════╦════════════╗
║ Machine                      ║ One Test ║ Test Suite ║
╠══════════════════════════════╬══════════╬════════════╣
║ 1. Docker for Mac            ║ 1m41.2s  ║ 2m45       ║
║ 2. Mac - VM - Linux - Docker ║ 0m6.1s   ║ 0m56       ║
║ 3. Linux - Docker            ║ 0m4.2s   ║ 0m23       ║
╚══════════════════════════════╩══════════╩════════════╝
</code></pre>
<h3>Machine Specifics</h3>
<p>Machines 1 and 2 are recent MacBook Pros: 2.6 GHz Intel Core i7 with 16G Ram. Machine 3 is a more powerful Thinkpad X1E I7 8750H 32G Ram.</p>
<h3>Conclusion</h3>
<p>The tests showed that using Docker for Mac for development is a rather poor (read terrible) experience.</p>
<p>Presumably this is due to the file sharing system between the docker container and the host machine — Docker for Mac / Mac OS have still got a long way to go here. We run a shared file system so changed files can be pushed to git using the host credentials. (Please see the following story on how to <a href="https://abletech.nz/resource/optimising-docker-for-mac-and-elixir/">Optimise for Docker for Mac</a>).</p>
<p>If you develop on a Mac, either spend time optimising the file sharing mechanism, or run a Linux VM with Docker for Linux for your development . For top performance, consider ditching the fancy Apple gear in favour of a clunky IBM (oops Lenovo).</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hofuxp2a26svlguixz3r86f5" alt="Clear winner!" data-big=https://cms-assets.abletech.nz/thumbnail_1_Vi7e_L_St_F_Wp_Rlu1_Bl_Vwmf_Mw_26fde89221.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Vi7e_L_St_F_Wp_Rlu1_Bl_Vwmf_Mw_26fde89221.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_Vi7e_L_St_F_Wp_Rlu1_Bl_Vwmf_Mw_26fde89221.png 245w" data-zooming-width="245" data-zooming-height="141" loading="lazy" width="245" height="141"></figure>
</div>
<p><em>Clear winner!</em></p>
<p>Read the follow-up article on <a href="https://abletech.nz/resource/optimising-docker-for-mac-and-elixir/">Optimising Docker for Mac and Elixir</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Rails best practice security exceptions</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Rails InvalidAuthenticityToken — around the edges</h2>
<p>However, these security features can adversely affect real users and you may notice <code>ActionController::InvalidAuthenticityToken</code> exceptions appearing in your production runtime which can be challenging to reproduce.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rw1qxlqiomaskljgm7s4rk6e" alt="" data-big=https://cms-assets.abletech.nz/large_1t_SH_Eh_Im_Xzo4_P87_Zt_Vh_AD_Xw_f03b045f4b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1t_SH_Eh_Im_Xzo4_P87_Zt_Vh_AD_Xw_f03b045f4b.jpeg" srcset="https://cms-assets.abletech.nz/large_1t_SH_Eh_Im_Xzo4_P87_Zt_Vh_AD_Xw_f03b045f4b.jpeg 1000w, https://cms-assets.abletech.nz/small_1t_SH_Eh_Im_Xzo4_P87_Zt_Vh_AD_Xw_f03b045f4b.jpeg 500w, https://cms-assets.abletech.nz/medium_1t_SH_Eh_Im_Xzo4_P87_Zt_Vh_AD_Xw_f03b045f4b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1t_SH_Eh_Im_Xzo4_P87_Zt_Vh_AD_Xw_f03b045f4b.jpeg 208w" data-zooming-width="1000" data-zooming-height="751" loading="lazy" width="1000" height="751"></figure>
</div>
<p>The following scenarios have been identified that can create these exceptions in your production environment for ‘real’ users.</p>
<h2>Stale authenticity token on a form</h2>
<p>The User session has timed out. The user then logs back in and back pages to a form which contains an authenticity token for the old session. The browser will present the new session cookie when the form is submitted. Boom.</p>
<p>To resolve this the page form page should be reloaded from the server with the new session. Setting the following response headers will cause the browser to reload the form, and not use a cached version on disk.</p>
<pre><code class="hljs"><span class="hljs-comment"># nginx config</span>
<span class="hljs-keyword">add_header </span><span class="hljs-string">&quot;Cache-Control&quot;</span> <span class="hljs-string">&quot;max-age=0, no-cache, no-store, must-revalidate&quot;</span>;
<span class="hljs-keyword">add_header </span><span class="hljs-string">&quot;Pragma&quot;</span> <span class="hljs-string">&quot;no-cache&quot;</span>;
<span class="hljs-keyword">add_header </span><span class="hljs-string">&quot;Expires&quot;</span> <span class="hljs-string">&quot;-1&quot;</span>;
</code></pre>
<h3>User leaves a stale form open AND in a new tab logs out and logs in</h3>
<p>In this case a new session is established, but the old page still exists and hence contains the old authenticity token (it doesn’t get a chance to reload using cache headers detailed above). You really don’t want someone submitting an old form on a new session so the authenticity token is kind of giving you a good side effect in this situation.</p>
<p>This is a UX question as well as a technical one and there are several options to prevent the user from getting an error.</p>
<p>Option 1 rescues the exception and does a GET request to refresh the page. The page will then load with a valid authenticity_token.</p>
<p>Option 2 uses the <code>reset_session</code> option to always brutally force them off the session. No logging occurs here which is unfortunate if you want monitor for real CSRF events. The page flow will then be caught by an <code>authorize</code> action in the request chain.</p>
<pre><code class="hljs">`
<span class="hljs-comment"># option 1`</span>

rescue_from <span class="hljs-title class_">ActionController</span>::<span class="hljs-title class_">InvalidAuthenticityToken</span> <span class="hljs-keyword">do</span> |_exception|
  flash[<span class="hljs-symbol">:alert</span>] = <span class="hljs-string">&#x27;The session has been reset. Please try again.&#x27;</span>
  redirect_back <span class="hljs-symbol">fallback_location:</span> root_path
<span class="hljs-keyword">end</span>

<span class="hljs-comment"># option 2</span>

protect_from_forgery <span class="hljs-symbol">with:</span> <span class="hljs-symbol">:reset_session</span>
</code></pre>
<p>There are other options that <code>protect_from_forgery</code> provides as well and you should read the full description of the <a href="https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html" target="_blank" rel="noopener noreferrer">API</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>What does it mean to go serverless?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Serverless application development with Typescript — what are my options?</h2>
<p>It’s time to look closely at serverless development. Come on a journey to investigate the serverless development model with Typescript. This article will take you through a potential framework, ORM, relational database provider, how to test, and our conclusions.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nf4ob3l2bx3j40sxxo36a1vq" alt="" data-big=https://cms-assets.abletech.nz/medium_1r_Tzvkh2_Judnb_Uf0_JG_3n_RGQ_e11c8abb13.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 186px" src="https://cms-assets.abletech.nz/1r_Tzvkh2_Judnb_Uf0_JG_3n_RGQ_e11c8abb13.png" srcset="https://cms-assets.abletech.nz/small_1r_Tzvkh2_Judnb_Uf0_JG_3n_RGQ_e11c8abb13.png 500w, https://cms-assets.abletech.nz/medium_1r_Tzvkh2_Judnb_Uf0_JG_3n_RGQ_e11c8abb13.png 750w, https://cms-assets.abletech.nz/thumbnail_1r_Tzvkh2_Judnb_Uf0_JG_3n_RGQ_e11c8abb13.png 186w" data-zooming-width="750" data-zooming-height="629" loading="lazy" width="750" height="629"></figure>
</div>
<h3>What is serverless?</h3>
<p>Serverless computing is a shift away from the traditional set up where a developer looks after the server keeping it secure, available and up and running at all times. Serverless removes the management of the server infrastructure and physical hardware away from the developer.</p>
<h3>Why do I like serverless?</h3>
<p>It’s simpler. Serverless architecture makes sense. It’s a lower cost, more environmentally sustainable option where apps aren’t sitting idle on servers. As a developer I just want to focus on the code: building and maintaining applications. With serverless I don’t need to juggle with the infrastructure and hardware.</p>
<p><strong>Cheaper, better, faster</strong></p>
<p>Going serverless saves money. You only pay for what you use. It also saves energy. Our carbon footprint from having servers waiting for requests, burning CPU cycles, is reduced. Serverless also saves time. It reduces the need to spend time provisioning resources. In theory, developing using a serverless architecture (and an associated framework) should be simple and be able to be performed by anyone, not just someone with an infrastructure background.</p>
<p>In a serverless environment, servers don’t sit idle waiting to respond to requests. Instead, functions are spun up as required. Developers no longer need to worry about the hardware applications run on. This can also assist with the scalability of a service.</p>
<blockquote>
<h4>With client demand for serverless application development rising we experimented with some options</h4>
</blockquote>
<h2><strong>Serverless development with Typescript</strong></h2>
<p>Here at Abletech, we’re always on the lookout for new frameworks and methodologies. Andrew and I investigated Typescript, along with developing using a serverless development model, for future projects.</p>
<p>We needed to start with research about Typescript on serverless architecture, so we looked into various technologies and design patterns. We have lots of experience building APIs in Ruby on Rails and Elixir with the Phoenix framework so we tried to utilise our experience and similar patterns with these frameworks.</p>
<p>This blog article outlines our findings and what we liked/didn’t like about what we tried — note this isn’t your typical ‘getting started’ tutorial but more of a summary of our learnings.</p>
<h3><strong>Why Serverless and Typescript you ask?</strong></h3>
<p>Why Typescript? Well, coming from Ruby and Elixir — both dynamically typed — we wanted to trial working with a typed language and see if this helped with our efficiency. With a lot of our developers using VS Code, we’d heard how well Typescript integrated and wanted to give it a try. Additionally, many of our developers are already competent with Javascript making the transition super easy.</p>
<h3><strong>Time to pick a framework — Next.js</strong></h3>
<p>After looking into several frameworks, our first step was to investigate Next.js. <a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a> is an open-source React web framework primarily used for generating static websites with server-side rendering, it is highly influenced by the Jamstack architecture. <a href="https://abletech.nz/resource/how-to-build-and-deploy-your-personal-blog-with-next-js">Abletech has used Next.js previously for several projects</a>, and <a href="https://www.kalopilato.com/blog/how-to-build-and-deploy-your-personal-blog-with-next-js" target="_blank" rel="noopener noreferrer">Kalo has used it for his own personal blog</a>, so we had some experienced team members that we could lean on for inspiration.</p>
<p>We liked that Next.js came with built in support for API routes, this allowed for fast and easy development using a folder-based structure. Next.js being an opinionated framework meant we could focus on writing our application code rather than setting up the application itself. Out of the box, you could export a handler with a request and response object as arguments. Next.js provided helpers to return JSON, redirecting and setting the HTTP status as well as parsing route and query parameters.</p>
<p>Relying on the folder based structure meant we could setup a simple User CRUD API using two files and in a matter of minutes.</p>
<p>For example, within our <code>pages/api/users</code> directory, we can add the following which will handle listing all users:</p>
<pre><code class="hljs language-typescript"><span class="hljs-keyword">const</span> handler = <span class="hljs-title function_">async</span> (<span class="hljs-attr">_req</span>: <span class="hljs-title class_">NextApiRequest</span>, <span class="hljs-attr">res</span>: <span class="hljs-title class_">NextApiResponse</span>): <span class="hljs-title class_">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
  <span class="hljs-keyword">const</span> users = [{<span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;123&#x27;</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;Bob&#x27;</span>, <span class="hljs-attr">email</span>: <span class="hljs-string">&#x27;bob@bobmail.com&#x27;</span>}]
  res.<span class="hljs-title function_">status</span>(<span class="hljs-number">200</span>).<span class="hljs-title function_">json</span>(users)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler

</code></pre>
<h3><strong>What about an ORM?</strong></h3>
<p>Having set up our routes, the next step was to integrate with a database. We wanted to maintain some familiarity with Rails and Phoenix so decided to stick with a standard relational database and PostgreSQL. There were many ORMs that supported PostgreSQL however we settled on <a href="https://www.prisma.io/" target="_blank" rel="noopener noreferrer">Prisma</a> due to its ease of integration with Next.js.</p>
<p>Prisma is a relatively new ORM that focuses on developer productivity. The strength here is that there’s a single source of truth between the database schema and the ‘models’ defined in the application’s schema. This took a little bit of getting used to as with Rails and Phoenix, we were used to defining models separately to the schema. Prisma’s client component also provides type-safe database queries making it easy to spot and debug code at compile time.</p>
<p>We liked how Prisma integrated with Next.js seamlessly and Prisma Studio which allowed us to create data easily using their browser based tool. Prisma’s <code>prisma db push</code>command was a new concept to us but allowed for rapid prototyping. Essentially, this command pushed the schema we’d defined locally in the Prisma schema file to the database allowing setup without the need for migrations.</p>
<p>Whilst having local schemas created by introspecting the database was great for developer productivity, we found we were lacking some functionality that we were used to with <a href="https://guides.rubyonrails.org/active_record_basics.html" target="_blank" rel="noopener noreferrer">ActiveRecord</a> (Rails’ ORM) and <a href="https://hexdocs.pm/ecto/Ecto.html" target="_blank" rel="noopener noreferrer">Ecto</a> (Elixir’s query/database wrapper). Validation seemed to be left to the database level meaning we couldn’t cleanse or verify our data before inserting it. Arguably, this was by design however it’s nice having that functionality out of the box with Active Record and Ecto. Whilst, Prisma provides a validator API which takes the generated model’s type and ensures that input being passed in is type safe, we found this difficult to use and it didn’t actually strip out invalid keys if we passed in the entire request params body.</p>
<p>Additionally, Prisma seemed to be lacking support for transactions. Prisma Client provided functionality to insert records and associations in a transaction as well as for batch updating/creating/deleting records however if you wanted to perform some complex business logic wrapped in a transaction — you’d run into trouble. Prisma does provide a transaction function where you can pass through raw queries, however this somewhat defeats the purpose of using an ORM if you’re writing all your complex queries in SQL. If you wanted to read more, we found <a href="https://www.reddit.com/r/programming/comments/ncldcp/prisma_orm_how_to_use_the_great_database_mapping/" target="_blank" rel="noopener noreferrer">this Reddit thread</a> pretty interesting.</p>
<p>As we were only using Prisma for simple CRUD operations, it sufficed for our requirements and hence we decided to push on — in future, we would most likely investigate other ORMs for our use cases.</p>
<h3><strong>How will a relational database work with serverless applications?</strong></h3>
<p>Getting our database setup locally with Prisma was simple enough — creating a docker-compose file, adding a database service, exposing the port, constructing a connection string and exporting it as an environment variable to our project. However, we needed a solution that would work when we deployed our Lambda functions to AWS. Traditionally, we’d setup an RDS instance which our service would communicate with directly.</p>
<p>With the use of Lambda we needed to be able support the potential for cold-starts and a high number of connections continuously connecting and disconnecting to the database. Without appropriate management, or throttling, we could easily overwhelm our RDS instance. This is where <a href="https://aws.amazon.com/rds/proxy/" target="_blank" rel="noopener noreferrer">AWS’s RDS Proxy</a> comes in.</p>
<p>RDS Proxy will pool and share connections for us allowing for scalability and efficiently managed connections. Rather than creating a new connection for every Lambda invocation, RDS Proxy will attempt to reuse existing connections from warm connection pools. RDS Proxy sits between our Lambda functions and the RDS instance abstracting all the connection management away from the developer. In essence, we connect to the RDS proxy and it handles everything else for us.</p>
<p>We liked how easy it was to setup an RDS proxy, it was simple enough to follow the instructions in the AWS console and connect one to our existing RDS instance. RDS Proxy also meant we could interact with the database how we would normally, no different to if we were connecting to a traditional RDS instance. The only downside we noticed was the need to have the RDS instance and the proxy within a security group. This meant the database was locked to one region meaning availability could be somewhat reduced if we wanted to create a service that was to be used globally.</p>
<p>Potentially we could’ve investigated the use of a NoSQL option such as <a href="https://aws.amazon.com/dynamodb/global-tables/" target="_blank" rel="noopener noreferrer">DynamoDB global tables</a>, or gone down the route of a non-AWS provider such as <a href="https://fauna.com/" target="_blank" rel="noopener noreferrer">Fauna</a>— which was built for serverless applications.</p>
<h3><strong>How do I deploy my Next.js application?</strong></h3>
<p>We wanted to stick with AWS as we use their services across a lot of our projects and we’re already familiar with their products and how they operate. We explored using the <a href="https://www.serverless.com/open-source/" target="_blank" rel="noopener noreferrer">Serverless framework</a> to see if we could abstract away some of the resource provisioning for us. So, what is the Serverless framework? It’s a free open-source framework using Node allowing for the provisioning of event-driven resources — it’s provider agnostic meaning it’ll work across AWS, Google Cloud Platform, Azure etc. All definitions and requirements are defined using a <code>serverless.yml</code>file in the root directory of the project.</p>
<p>Having settled on AWS for the provider, we needed to provision Lambdas and API Gateway endpoints. For this, we used the <a href="https://github.com/serverless-nextjs/serverless-next.js" target="_blank" rel="noopener noreferrer">Serverless Next.js component</a>. This component deploys out our API routes to Lambda@Edge functions allowing them to be run and served from CloudFront edge locations. The idea behind using this component meant that by default, there is zero configuration and instead defaults can be extended. Sounds good, but note this caused some problems for us… more to come on this later.</p>
<p>Deploying out our API routes was incredibly easy, we added a serverless.yml file, specified that we wanted to use the Next.js Serverless component and away we went. Our functions were deployed and APIs were created with a single <code>serverless</code> command. However, in order to allow our Lambdas access to our RDS proxy, we needed to add the Lambdas into the same VPC as our proxy and RDS instance…. Unfortunately, this isn’t possible when your Lambda functions are deployed to edge locations.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">typescriptArchitecture:</span>
  <span class="hljs-attr">component:</span> <span class="hljs-string">&quot;./node_modules/@sls-next/serverless-component&quot;</span>
</code></pre>
<p>In order to solve this, we could’ve exposed our proxy to the world (bad) or allowed various IPs (slightly less bad), however this would’ve defeated the purpose of deploying our functions to edge locations. We also investigated whether we could use regular Lambda functions, deployed to our local ap-southeast-2 region. However, this was not possible using the Serverless Next.js component, although the developers are working towards genericizing the component and adding other provider support.</p>
<p>In the end, we determined that whilst Next.js is great for when we have lots of static content it may not be the best solution for server side development. Also using the Next.js Serverless component with an RDS proxy isn’t possible unless you roll your own stack and construct your own Serverless templates — defeating the purpose of the ease of use of Serverless.</p>
<h3><strong>Can we use Serverless without a framework?</strong></h3>
<p>Having decided not to go down the route of Next.js, we looked at alternatives. We enjoyed using the Serverless framework so decided to investigate rolling our own stack and moving away from a framework whilst still using Prisma as our ORM and RDS and RDS Proxy for our database.</p>
<p>Our first step was to setup a serverless.yml file, with various APIs all pointing to functions — again, we wanted to create simple CRUD APIs for a User model. This is what we came up with:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">service:</span> <span class="hljs-string">typescript-serverless-boilerplate</span>

<span class="hljs-attr">provider:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">aws</span>
  <span class="hljs-attr">runtime:</span> <span class="hljs-string">nodejs12.x</span>
  <span class="hljs-attr">region:</span> <span class="hljs-string">ap-southeast-2</span>
  <span class="hljs-attr">environment:</span>
    <span class="hljs-attr">NODE_ENV:</span> <span class="hljs-string">dev</span>

<span class="hljs-attr">plugins:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">serverless-plugin-typescript</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">serverless-offline</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">serverless-dotenv-plugin</span>

<span class="hljs-attr">functions:</span>
  <span class="hljs-attr">findUsers:</span>
    <span class="hljs-attr">handler:</span> <span class="hljs-string">app/handler.findUsers</span>
    <span class="hljs-attr">events:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">http:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">users</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">get</span>
  <span class="hljs-attr">findUser:</span>
    <span class="hljs-attr">handler:</span> <span class="hljs-string">app/handler.findUser</span>
    <span class="hljs-attr">events:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">http:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">users/{id}</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">get</span>
  <span class="hljs-attr">createUser:</span>
    <span class="hljs-attr">handler:</span> <span class="hljs-string">app/handler.createUser</span>
    <span class="hljs-attr">events:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">http:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">users</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">post</span>
  <span class="hljs-attr">updateUser:</span>
    <span class="hljs-attr">handler:</span> <span class="hljs-string">app/handler.updateUser</span>
    <span class="hljs-attr">events:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">http:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">users/{id}</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">put</span>
  <span class="hljs-attr">deleteUser:</span>
    <span class="hljs-attr">handler:</span> <span class="hljs-string">app/handler.deleteUser</span>
    <span class="hljs-attr">events:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">http:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">users/{id}</span>
          <span class="hljs-attr">method:</span> <span class="hljs-string">delete</span>
</code></pre>
<p>After defining the functions, we created a handler, mapping the definitions to functions that will execute our requests:</p>
<pre><code class="hljs language-typescript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">APIGatewayProxyHandler</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;aws-lambda&#x27;</span>

<span class="hljs-keyword">import</span> { createOne, deleteOne, find, findOne, updateOne } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./controller/users&#x27;</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">findUsers</span>: <span class="hljs-title class_">APIGatewayProxyHandler</span> = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-title function_">find</span>()
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">findUser</span>: <span class="hljs-title class_">APIGatewayProxyHandler</span> = <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-title function_">findOne</span>(event)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">createUser</span>: <span class="hljs-title class_">APIGatewayProxyHandler</span> = <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-title function_">createOne</span>(event)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">updateUser</span>: <span class="hljs-title class_">APIGatewayProxyHandler</span> = <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-title function_">updateOne</span>(event)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-attr">deleteUser</span>: <span class="hljs-title class_">APIGatewayProxyHandler</span> = <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-title function_">deleteOne</span>(event)
}

</code></pre>
<p>We can also specify VPC configuration and security group IDs so that Lambdas are provisioned within our existing security group and can talk to other resources that may already exist or were created earlier:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">provider:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">aws</span>
  <span class="hljs-attr">runtime:</span> <span class="hljs-string">nodejs12.x</span>
  <span class="hljs-attr">region:</span> <span class="hljs-string">ap-southeast-2</span>
  <span class="hljs-attr">vpc:</span>
    <span class="hljs-attr">securityGroupIds:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">sg-xxxx</span>
    <span class="hljs-attr">subnetIds:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">subnet-xxx</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">subnet-xxx</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">subnet-xxx</span>
</code></pre>
<p>With our Lambdas being provisioned in our existing security group we’re able to communicate with our RDS proxy. This was done by specifying the database URL as an environment variable. We could have also provisioned our RDS proxy and RDS instance using a CloudFormation template which Serverless supports out of the box. This would have been added using the “resources” section of the yml file.</p>
<p>Being able to develop locally was also a major requirement. Utilising the <a href="https://github.com/dherault/serverless-offline" target="_blank" rel="noopener noreferrer">serverless-offline</a> component, we were able to run up our service on our own machines, with Lambda functions and APIs replicated as if we were calling them directly on AWS. Again, this made development super easy and we could test that what we were deploying, would work locally first.</p>
<p>Whilst we lost some of the sensible defaults and structure by rolling our own stack, we were impressed by the flexibility of Serverless components and how easy it was to setup our Lambdas and API Gateway. By defining all our required functions in the yml file, we were able to structure our code using a standard MVC pattern and develop no differently to if we were deploying to a more traditional platform. Serverless deploys were often quick as well making for a quick feedback loop. Monitoring provided by AWS Lambda as well as allowing to test offline using the serverless-offline plugin, meant debugging was simple and allowed us to pinpoint errors in our code.</p>
<p>Whilst this was super cool and fun to play around with, being new to Typescript and creating an Express-backed API meant that in future, I’d probably feel more comfortable following a framework, or doing some reading into some established patterns for Typescript API apps. However, for a simple CRUD API, I enjoyed the ease at which you could define a handler and write to the database without much overhead. Additionally, we managed to provision Lambdas and API gateway endpoints without even going near the AWS console.</p>
<h3><strong>But what about Typescript?</strong></h3>
<p>Not being too experienced in the world of Typescript, and not following any particular framework, we wanted to setup our boilerplate app with some sensible defaults for formatting and linting. For starters, we followed <a href="https://www.metachris.com/2021/04/starting-a-typescript-project-in-2021/" target="_blank" rel="noopener noreferrer">Chris Hager’s blog article</a> setting up our tsconfig using his instructions and linting using typescript-eslint. This boilerplate set us up with a modern tool-set for Typescript development in 2021. With tools constantly changing and becoming deprecated, we gound Chris’s blog article helpful in setting up a boilerplate Typescript project.</p>
<p>The last step was to setup our app with a prettier config and linting/formatting on commit and push using Husky.</p>
<h3><strong>A note on testing</strong></h3>
<p>Another one of our requirements when doing this analysis was a decent testing framework. We settled on using <a href="https://jestjs.io/" target="_blank" rel="noopener noreferrer">Jest</a> as our framework as we’d had experience using this with our React-based UIs and because it has the ability to run tests in parallel.</p>
<p>We started off by unit testing our user’s controller by following<a href="https://www.prisma.io/docs/guides/testing/unit-testing" target="_blank" rel="noopener noreferrer"> the examples in the Prisma documentation</a>. Whilst this was great asserting that our requests/responses returned expected results, we were mocking the database provider meaning we couldn’t fully test a real-world scenario. Now, if you Google the debates on whether or not to test database integration in your unit tests, or if you should leave these to your integration tests, you’ll come up with a myriad of differing opinions. Side-stepping this argument, having come from Rails and Phoenix land, we felt more comfortable testing this integration in our unit tests.</p>
<p>Usually in Rails or Phoenix, we would wrap our tests in transactions, execute the function being tested, assert what’s expected and rollback the database so it’s in a clean state ready for the next test. This is generally handled for us if we’re using a testing library such as <a href="https://rspec.info/" target="_blank" rel="noopener noreferrer">RSpec</a> or <a href="https://hexdocs.pm/ex_unit/ExUnit.html" target="_blank" rel="noopener noreferrer">ExUnit</a>. Unfortunately, finding a similar library and integrating it with our project was quite difficult. Additionally, if we were to follow this process, we wouldn’t be able to take advantage of Jest’s parallel test execution.</p>
<p>Enter, <a href="https://github.com/allaboutapps/integresql" target="_blank" rel="noopener noreferrer">Integresql</a>. Created by allaboutapps, Integresql uses PostgreSQL templates to create a database per test allowing for fast, parallel tests utilizing a real database. When our test runner starts, we’re able to initialise a template, execute our migrations and finalise it. Before each test, we’re able to initialize a test database, which creates a database from our template, point Prisma at the newly created database, run our test and assert what’s expected. IntgereSQL’s interface is a RESTful JSON API allowing us to simply make HTTP calls in beforeAll and beforeEach setup functions. Additionally, as IntegreSQL can be run using Docker, we can add it into our Docker network and set it up with all our containers talking to each other fairly easily.</p>
<pre><code class="hljs language-typescript"><span class="hljs-keyword">import</span> { exec <span class="hljs-keyword">as</span> childProcessExec } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;child_process&#x27;</span>
<span class="hljs-keyword">import</span> util <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;util&#x27;</span>
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;axios&#x27;</span>
<span class="hljs-keyword">import</span> prisma <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;../../lib/prisma&#x27;</span>

<span class="hljs-keyword">import</span> { v4 <span class="hljs-keyword">as</span> uuidv4 } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;uuid&#x27;</span>

<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">PRISMA_BINARY</span> = <span class="hljs-string">&#x27;./node_modules/.bin/prisma2&#x27;</span>

<span class="hljs-keyword">const</span> hash = <span class="hljs-title function_">uuidv4</span>()
<span class="hljs-keyword">let</span> <span class="hljs-attr">databaseId</span>: <span class="hljs-built_in">string</span>

<span class="hljs-title function_">beforeAll</span>(<span class="hljs-keyword">async</span> done =&gt; {
  <span class="hljs-keyword">const</span> exec = util.<span class="hljs-title function_">promisify</span>(childProcessExec)

  <span class="hljs-comment">// Create a template database</span>
  <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: createData } = <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">post</span>(<span class="hljs-string">`<span class="hljs-subst">${process.env.INTEGRESQL_URL}</span>/templates`</span>, { hash })

  <span class="hljs-comment">// Create the tables</span>
  <span class="hljs-keyword">const</span> databaseUrl = <span class="hljs-string">`<span class="hljs-subst">${process.env.DATABASE_HOST}</span>/<span class="hljs-subst">${createData.database.config.database}</span>`</span>
  <span class="hljs-keyword">await</span> <span class="hljs-title function_">exec</span>(<span class="hljs-string">`DATABASE_URL=<span class="hljs-subst">${databaseUrl}</span> <span class="hljs-subst">${PRISMA_BINARY}</span> db push --preview-feature --force-reset `</span>)

  <span class="hljs-comment">// Finalise the template</span>
  <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">put</span>(<span class="hljs-string">`<span class="hljs-subst">${process.env.INTEGRESQL_URL}</span>/templates/<span class="hljs-subst">${hash}</span>`</span>)
  <span class="hljs-title function_">done</span>()
}, <span class="hljs-number">40_000</span>)

<span class="hljs-title function_">beforeEach</span>(<span class="hljs-keyword">async</span> done =&gt; {
  <span class="hljs-comment">// Setup a new test database per-test</span>
  <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: getData } = <span class="hljs-keyword">await</span> axios.<span class="hljs-title function_">get</span>(<span class="hljs-string">`<span class="hljs-subst">${process.env.INTEGRESQL_URL}</span>/templates/<span class="hljs-subst">${hash}</span>/tests`</span>)

  <span class="hljs-comment">// Export it as the DATABASE_URL so Prisma knows</span>
  databaseId = getData.<span class="hljs-property">database</span>.<span class="hljs-property">config</span>.<span class="hljs-property">database</span>
  process.<span class="hljs-property">env</span>.<span class="hljs-property">DATABASE_URL</span> = <span class="hljs-string">`<span class="hljs-subst">${process.env.DATABASE_HOST}</span>/<span class="hljs-subst">${databaseId}</span>`</span>

  <span class="hljs-title function_">done</span>()
})

<span class="hljs-title function_">afterEach</span>(<span class="hljs-function">() =&gt;</span> {
  prisma.$disconnect()
})

</code></pre>
<h3><strong>Conclusion</strong></h3>
<p>To summarise, we investigated several different options for potential Serverless Typescript API-only services. Whilst we enjoyed using Next.js’s API routes, this didn’t seem like a particularly extensible solution for a logic-heavy API-only backend app and it seemed more particularly suited for static-content sites. On top of that, without rolling our own Serverless configuration for Next.js, we couldn’t deploy to a regular Lambda function meaning we weren’t able to provision our Lambdas in a VPC allowing access to our RDS Proxy.</p>
<p>Prisma as an ORM was good for a simple CRUD API app, and would probably suit applications that didn’t have complex business logic. We enjoyed the developer experience and how it was relatively easy to setup although having the singular schema did take a bit of getting used to. Prisma does however lack some of the functionality such as validation, transaction management and some connection configuration that we are used to with other ORMs so in future we’d probably look at the viability of using another ORM.</p>
<p>RDS Proxy was great — we liked how we could interface with it exactly how we would with a regular RDS instance. Setting up was a breeze and we could add it into our VPC and security group in no time at all.</p>
<p>Whilst we couldn’t use Next.js, The next best option was to roll our own Serverless stack. This proved slightly more beneficial and allowed for more flexibility with defining and provisioning our own resources. Again, we liked how easy it was to setup Lambdas and an API Gateway endpoint with everything being defined in the yml file. Configuring VPCs, and the fact we could define CloudFormation templates within the yml file, was a nice developer experience too. Being new to Typescript, we were a little worried about going it alone without a framework, so in future, we’d probably either investigate other frameworks or do some more investigation into common design patterns in Typescript.</p>
<p>Lastly, IntegreSQL was a great tool allowing for fast, parallel execution of tests with each test receiving its own database. It fulfilled our need for unit testing our app with proper database testing without the need to mock our database provider. We’re looking at rolling this tool out across some of our other backend services.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Working on Pull Requests</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The Abletech way</h2>
<p>Similar to how we write sentences, there are a lot of ways to write code and produce the same outcome. Because each developer has their own coding style, ensuring the code quality is necessary before integrating it into the code repository. Code reviews provide a means to ensure that the quality of the code meets the standard of the team and identify any potential defects or improvements. Developers integrate this into a process called pull request (PR).</p>
<p>There are so many definitions one can find on the internet about what a pull request is, but essentially, a pull request is a review request. It is created for other contributors to examine the piece of code and add review comments. Often there are code updates, and once they are happy with the proposed changes, the code gets merged into the base branch.</p>
<p>Over several years of experience as a software developer who has worked with numerous teams, I’ve realised that each has its way of doing PR. While there are plenty of published resources about code styles and coding best practices, only very few talk about PRs. So in an attempt to standardise or establish consistency and completeness in creating and reviewing PRs, we, at Abletech, had a workshop about pull requests and how we do (or don’t do) it.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qz5dzywwssdsg6jeyse4d5dh" alt="" data-big=https://cms-assets.abletech.nz/small_0_Yndf9i_RM_1u_Ot_FD_Xm_027eaf0558.png sizes="(min-width: 768px) 453px, (min-width: 640px) 141px" src="https://cms-assets.abletech.nz/0_Yndf9i_RM_1u_Ot_FD_Xm_027eaf0558.png" srcset="https://cms-assets.abletech.nz/small_0_Yndf9i_RM_1u_Ot_FD_Xm_027eaf0558.png 453w, https://cms-assets.abletech.nz/thumbnail_0_Yndf9i_RM_1u_Ot_FD_Xm_027eaf0558.png 141w" data-zooming-width="453" data-zooming-height="500" loading="lazy" width="453" height="500"></figure>
</div>
<h3><strong>Creating a pull request</strong></h3>
<ul>
<li>
<p>Use a helpful title and description — add the story ticket, give an overview, and add screenshots or videos for front-end changes</p>
</li>
<li>
<p>Link pull request to dependencies</p>
</li>
<li>
<p>Conduct a review of your own PR, similar to how you review other people’s work</p>
</li>
<li>
<p>Add a comment on architectural changes and new packages</p>
</li>
<li>
<p>Use clear and concise commits e.g. avoid using ambiguous commits like “Fixed based on PR feedback”</p>
</li>
<li>
<p>Ensure the request passed the CI pipeline</p>
</li>
<li>
<p>Give the reviewer a walk-through for difficult or long PRs</p>
</li>
<li>
<p>Well-indented code</p>
</li>
</ul>
<h3><strong>Conducting a code review</strong></h3>
<ul>
<li>
<p>Check the title and description, screenshots or videos</p>
</li>
<li>
<p>Check the number of files changed</p>
</li>
<li>
<p>Check the build status to see if it passed</p>
</li>
<li>
<p>Read existing comments</p>
</li>
<li>
<p>Check commit history</p>
</li>
<li>
<p>Check test coverage — ensure success and failure scenarios are covered</p>
</li>
<li>
<p>Leverage existing library code</p>
</li>
<li>
<p>Check if there are architectural changes not required for the PR</p>
</li>
<li>
<p>Consider security and performance</p>
</li>
<li>
<p>Check readability and coding styles</p>
</li>
<li>
<p>Mark files as viewed after review</p>
</li>
<li>
<p>Update the associated ticket</p>
</li>
<li>
<p>Pull branch and view locally when needed</p>
</li>
<li>
<p>Prioritize urgent PRs</p>
</li>
</ul>
<h3>Offering feedback</h3>
<ul>
<li>
<p>Suggest an alternative approach to solving the problem. If available, provide links to documentation and give examples</p>
</li>
<li>
<p>Acknowledge good code. Add a thumbs up or positive feedback</p>
</li>
<li>
<p>Use constructive feedback</p>
</li>
<li>
<p>Use it as a learning tool. Be suggestive rather than directive</p>
</li>
<li>
<p>Give hints or discuss the suggested solution instead of giving the solution straightaway</p>
</li>
<li>
<p>Give questions that provoke thought. Open a discussion</p>
</li>
<li>
<p>Submit pull review then comment.</p>
</li>
<li>
<p>Observe the language used</p>
</li>
<li>
<p>Pair on complicated PR</p>
</li>
<li>
<p>Add a comment that says more than just approved when done</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="p3n8v8sks1brbitt3a1y6vfx" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_owymfk_Dh_Bu_HZAQKM_beb682d325.jpeg sizes="(min-width: 640px) 125px" src="https://cms-assets.abletech.nz/0_owymfk_Dh_Bu_HZAQKM_beb682d325.jpeg" srcset="https://cms-assets.abletech.nz/thumbnail_0_owymfk_Dh_Bu_HZAQKM_beb682d325.jpeg 125w" data-zooming-width="125" data-zooming-height="156" loading="lazy" width="125" height="156"></figure>
</div>
<h3>Responding to a feedback</h3>
<ul>
<li>
<p>Don’t take it personally. Step back and don’t react right away</p>
</li>
<li>
<p>Treat it as a learning opportunity</p>
</li>
<li>
<p>Acknowledge feedback. Respond to comments</p>
</li>
<li>
<p>Mark as resolved/completed or thumbs up a review when done</p>
</li>
<li>
<p>Talk about the feedback you don’t agree with</p>
</li>
<li>
<p>Re-requesting review when feedback has been accommodated</p>
</li>
</ul>
<h3>Do’s and don’ts when working on a pull request</h3>
<ul>
<li>
<p>Don’t use insult or inflammatory language</p>
</li>
<li>
<p>Don’t be offensive</p>
</li>
<li>
<p>Don’t hold off the PR</p>
</li>
<li>
<p>Avoid personal opinions</p>
</li>
<li>
<p>Avoid LGTM</p>
</li>
<li>
<p>Approve minor issue</p>
</li>
<li>
<p>Use static code analyser as part of CI</p>
</li>
<li>
<p>Approve PR you don’t agree with but create a ticket for technical debt</p>
</li>
<li>
<p>Use <em>— fixup</em> in commits that are related to an existing commit</p>
</li>
</ul>
<p>Working with pull requests is not easy. Ensuring code quality is hard. There’s no written standard but this works for us. While all of these points may not necessarily apply to everyone, surely some of these are advantageous and suitable, if not practised yet.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Business transformation</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Strategic software partners</h2>
<p>Loyalty NZ chose Abletech as a key partner to transform their business and build their software capability.</p>
<p>This month we spotlight Abletech’s work with Loyalty NZ by featuring a testimonial from Chief Technology and Data Officer Brian Ferris:</p>
<blockquote>
<h4>At Loyalty NZ we combine leading technology, data and analytics solutions with an agile approach to deliver successful outcomes for our partners and our members. A key challenge in our business is to be one step ahead with software development and ready to scale up while remaining flexible. Back in 2012, we first partnered with Abletech who helped us to transform our business and build our software capability</h4>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tyi29ucmak1zl56zfxabpihc" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1djib_Whdk0_C_Uu_LAQKD_3k_E_Bg_eadfa33ba7.png sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1djib_Whdk0_C_Uu_LAQKD_3k_E_Bg_eadfa33ba7.png" srcset="https://cms-assets.abletech.nz/thumbnail_1djib_Whdk0_C_Uu_LAQKD_3k_E_Bg_eadfa33ba7.png 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<blockquote>
<h4>Working with Abletech has meant we have been able to progressively bring our software development capability in-house over the past nine years. We now have high performing in-house capability with leading cloud technologies, multiple delivery squads and agile delivery.</h4>
<h4>Over more recent times, we have called on Abletech to scale up our existing team for project delivery so we can move more quickly when we need to. This works well as Abletech has built up an in-depth knowledge of our business and has a smart and responsive team.</h4>
<h4>Abletech has worked alongside us as our strategic software development partner for almost a decade now and their team has been instrumental in helping our business evolve.</h4>
</blockquote>
<h3><strong>Brian Ferris, Chief Technology and Data Officer, Loyalty NZ</strong></h3>
<p>June 2021</p>
<h3>Read more about Abletech</h3>
<ul>
<li>
<p><a href="https://abletech.nz/portfolio/custom-software-development-services">Custom high performing software development</a></p>
</li>
<li>
<p><a href="https://abletech.nz/portfolio/preferred-software-development-supplier-for-nz-government">Preferred software development supplier for NZ government</a></p>
</li>
<li>
<p><a href="https://abletech.nz/portfolio/crs-farm-management-software">Farm management software</a></p>
</li>
<li>
<p><a href="https://abletech.nz/portfolio/bdo-gisborne-accounting-and-financial-reporting">Accounting and financial reporting</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>EV Q&A —Charging: do you need a special plug at home?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>No. You can charge overnight with a normal power socket</h2>
<p>Most people with electric cars (EVs) normally need to charge up the battery <strong>every 2 or 3 days</strong>. If you have a newer car with a larger battery, you might go a week without charging. If you drive longer distances in an older EV, you might need to charge daily.</p>
<h2>Regular household socket</h2>
<p>You can plug your car into a regular power socket. Maybe you have one in your garage, or perhaps you have an outside charging socket. Either of these will charge up the car battery, but at a <strong>slower rate</strong>. Most (maybe all) EVs come with a cable that connects the car socket to a household socket.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lo4an2m09v1cw4y4kml51wlh" alt="Standard household socket used for EV charging" data-big=https://cms-assets.abletech.nz/small_1s_HK_1s3_At_XE_Ptg_XSB_4m_Ezxg_52da2d2dd4.png sizes="(min-width: 768px) 500px, (min-width: 640px) 192px" src="https://cms-assets.abletech.nz/1s_HK_1s3_At_XE_Ptg_XSB_4m_Ezxg_52da2d2dd4.png" srcset="https://cms-assets.abletech.nz/small_1s_HK_1s3_At_XE_Ptg_XSB_4m_Ezxg_52da2d2dd4.png 500w, https://cms-assets.abletech.nz/thumbnail_1s_HK_1s3_At_XE_Ptg_XSB_4m_Ezxg_52da2d2dd4.png 192w" data-zooming-width="500" data-zooming-height="406" loading="lazy" width="500" height="406"></figure>
</div>
<p><em>Standard household socket used for EV charging</em></p>
<p>Most of the Nissan Leaf style cars will take all night to recharge an empty battery. Newer EVs such as the Kia Niro EV or the Hyandai Kona have much larger batteries, and a refill from empty would take all day and night on a standard household socket.</p>
<p>In our case, we use this option when we <strong>travel away from home</strong>. We plug our Nissan Leaf into a household socket, and it charges up slowly overnight. It’s generally at, or near, 100% by the morning.</p>
<h2>Caravan style socket</h2>
<p>You normally only see these at camp grounds, but they can be a good and <strong>fairly cheap</strong> option for charging an electric car. These sockets have thicker wiring and allow a <strong>faster charge</strong>. They are often weatherproof, so are a good option for outside charging.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="p64eo3zrc8nagki3msfpl1zj" alt="Caravan style plugs" data-big=https://cms-assets.abletech.nz/medium_13_Oaqut_XEK_9y_Caciqn_Co9_Q_7e09302e3c.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 222px" src="https://cms-assets.abletech.nz/13_Oaqut_XEK_9y_Caciqn_Co9_Q_7e09302e3c.png" srcset="https://cms-assets.abletech.nz/small_13_Oaqut_XEK_9y_Caciqn_Co9_Q_7e09302e3c.png 500w, https://cms-assets.abletech.nz/medium_13_Oaqut_XEK_9y_Caciqn_Co9_Q_7e09302e3c.png 750w, https://cms-assets.abletech.nz/thumbnail_13_Oaqut_XEK_9y_Caciqn_Co9_Q_7e09302e3c.png 222w" data-zooming-width="750" data-zooming-height="526" loading="lazy" width="750" height="526"></figure>
</div>
<p><em>Caravan style plugs</em></p>
<p>These plugs (and cabling) are usually rated for 16 or 32 amps, which is about 2–3 times quicker than your regular household power socket.</p>
<p>In our case, we had one of these sockets (the 16 amp version) professionally installed by an electrician. We get about 15% of charge every hour, so a mostly empty car can be up to 100% in an afternoon.</p>
<p>We had to buy a <strong>special charging cable</strong> with the caravan style plug on one end. This cost around $600 from Australia.</p>
<h2>Wall chargers</h2>
<p>If you have room in your garage, then a <a href="https://evolutionaustralia.com.au/product-category/ev-wall-chargers/" target="_blank" rel="noopener noreferrer">wall charger</a> is another option. They cost between $1000 and $2000 and charge at rates of 30 amps and above. You’d need a newer electric car to take advantage of the <strong>faster charging rates</strong>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jwgfwadmukqkgjzrfcy1wb92" alt="" data-big=https://cms-assets.abletech.nz/medium_1_Il_Wemc_G_Gc_HB_6_TJM_42_Bf_EDQ_9a39947b03.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 200px" src="https://cms-assets.abletech.nz/1_Il_Wemc_G_Gc_HB_6_TJM_42_Bf_EDQ_9a39947b03.png" srcset="https://cms-assets.abletech.nz/small_1_Il_Wemc_G_Gc_HB_6_TJM_42_Bf_EDQ_9a39947b03.png 500w, https://cms-assets.abletech.nz/medium_1_Il_Wemc_G_Gc_HB_6_TJM_42_Bf_EDQ_9a39947b03.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Il_Wemc_G_Gc_HB_6_TJM_42_Bf_EDQ_9a39947b03.png 200w" data-zooming-width="750" data-zooming-height="584" loading="lazy" width="750" height="584"></figure>
</div>
<h2>Very expensive fast chargers</h2>
<p>If money is not a factor, you can have a DC fast charger installed. These will typically charge at 50 amps, which means your car will fully charge in around about an hour. I believe they are around about $20,000.</p>
<h2>If I park on the road do I need some sort of extension cord from my house?</h2>
<p>If you have no garage or off-street parking, your <strong>charging options are limited</strong>. You could install a charge socket at the edge of your boundary, but you are not allowed to drape an extension cord over the footpath.</p>
<h3>Your options would include:</h3>
<ol>
<li>
<p>Charge away from home at a fast charger (see section below)</p>
</li>
<li>
<p>Charge at work</p>
</li>
<li>
<p>Talk to your local council</p>
</li>
</ol>
<p>There are not a lot of options really. The Wellington City Council is starting an <a href="https://wellington.govt.nz/services/parking-and-roads/smart-transport/charging-electric-cars" target="_blank" rel="noopener noreferrer">on-street charging trial</a>, but there are very few of the charge points installed as yet.</p>
<h2>Public fast chargers</h2>
<p>Alternatively, you could use one of the many public fast chargers that are available in all cities (and many small towns), and on most highways in New Zealand (see <a href="https://charge.net.nz/map/" target="_blank" rel="noopener noreferrer">map</a>).</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="i70l6464xr9mm9vv0x243ecr" alt="Fast charger in downtown Wellington" data-big=https://cms-assets.abletech.nz/large_1v_He_GY_Kv_BM_Ga0z8_Fqw_Jc9_XQ_b3de170b28.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 223px" src="https://cms-assets.abletech.nz/1v_He_GY_Kv_BM_Ga0z8_Fqw_Jc9_XQ_b3de170b28.jpeg" srcset="https://cms-assets.abletech.nz/large_1v_He_GY_Kv_BM_Ga0z8_Fqw_Jc9_XQ_b3de170b28.jpeg 1000w, https://cms-assets.abletech.nz/small_1v_He_GY_Kv_BM_Ga0z8_Fqw_Jc9_XQ_b3de170b28.jpeg 500w, https://cms-assets.abletech.nz/medium_1v_He_GY_Kv_BM_Ga0z8_Fqw_Jc9_XQ_b3de170b28.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1v_He_GY_Kv_BM_Ga0z8_Fqw_Jc9_XQ_b3de170b28.jpeg 223w" data-zooming-width="1000" data-zooming-height="699" loading="lazy" width="1000" height="699"></figure>
</div>
<p><em>Fast charger in downtown Wellington</em></p>
<p>The costs of using the <a href="https://charge.net.nz" target="_blank" rel="noopener noreferrer">ChargeNet</a> fast chargers are 25c per minute plus 25c per kilowatt hour. For a Nissan Leaf needing a typical top-up of 20 kWh, you should expect a bill of around $15.</p>
<h2>My advice</h2>
<p>If you can <strong>charge in your garage</strong> from a regular power socket, do this for <strong>zero setup cost</strong>. See if the charge times are satisfactory for your circumstances. If not, then look at either a caravan charger or a dedicated wall charger.</p>
<h2>Learn More</h2>
<p>Read some of my previous posts in the EV Q&amp;A series:</p>
<ol>
<li>
<p><a href="https://abletech.nz/article/traveling-to-palmy-in-an-electric-vehicle/">Travelling to Palmy in an Electric Vehicle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-do-the-cars-cost-more-than-petrol-cars">Do EV cars cost more than petrol cars?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-if-i-go-up-a-hill-do-i-gain-power-when-i-go-down">If I go up a hill, do I gain power when I go down?</a></p>
</li>
</ol>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>EV Q&A: Is it gutless on the hills?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>When the going gets tough… electric cars are quite resilient</h2>
<p>We were asked: are electric cars able to travel up hills at a reasonable pace, or are they really slow when hard work is required?</p>
<p>Our car is a Nissan Leaf. We’ve had it for about a year now, and we’ve driven up and down a few hills. In fact, we live on top of hill.</p>
<p>Now, the Nissan Leaf is not some sort of a sports car — it’s the opposite. It’s a rather plain, medium sized, city car. It does not have large wheels. It does not boast a large, powerful motor.</p>
<p>But, our car goes up hills quite well.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="y8o5q4bnocf8mo6337m9582y" alt="Ohiro Road" data-big=https://cms-assets.abletech.nz/large_1subzy_EG_Mt0_Dbb_Kt_Om_Sh1jw_0f51d135a2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1subzy_EG_Mt0_Dbb_Kt_Om_Sh1jw_0f51d135a2.jpeg" srcset="https://cms-assets.abletech.nz/large_1subzy_EG_Mt0_Dbb_Kt_Om_Sh1jw_0f51d135a2.jpeg 1000w, https://cms-assets.abletech.nz/small_1subzy_EG_Mt0_Dbb_Kt_Om_Sh1jw_0f51d135a2.jpeg 500w, https://cms-assets.abletech.nz/medium_1subzy_EG_Mt0_Dbb_Kt_Om_Sh1jw_0f51d135a2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1subzy_EG_Mt0_Dbb_Kt_Om_Sh1jw_0f51d135a2.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Ohiro Road</em></p>
<h2>City hills</h2>
<p>The first hill that springs to mind is Ohiro Road. This is a fairly steep stretch of road that connects Aro Valley to Brooklyn. The road has a usual town speed limit of 50 kmph.</p>
<p>Our car handles itself well on this road, with a few sweeps left and right as the road ascends up the hill. We’re driving around 45–50 kmph much of the way.</p>
<p>Being an electric car, the motor remains quiet. You see that the car is working harder, on the dashboard’s power-usage gauge (it shows a large number of dots).</p>
<h2>Similar to diesel vehicles on hills</h2>
<p>Our previous car was a Ford Territory — a turbo diesel SUV. Diesel cars (like EVs) generally have more torque than their petrol equivalents. This means the motors/engines don’t have to work so hard to achieve the same pulling power.</p>
<p>Electric cars are similar. The main difference is that the torque is available from 0 kmph. There is no need for revving or gear engagement. This makes hill driving (especially from stopped/slow speeds) particularly ‘peppy’.</p>
<h2>Motorway Hills</h2>
<p>Our car is not a sports car. It has a very modest motor - just 80 kW of power. As a result, it feels less powerful at open road speeds.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e9ys2vvedvsje91wkfwxl0ze" alt="Ngauranga Gorge on a classic Wellington day" data-big=https://cms-assets.abletech.nz/large_12_US_5r5_Ti_X_Kz_DVXCQT_0_rgw_38630ae027.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/12_US_5r5_Ti_X_Kz_DVXCQT_0_rgw_38630ae027.jpeg" srcset="https://cms-assets.abletech.nz/large_12_US_5r5_Ti_X_Kz_DVXCQT_0_rgw_38630ae027.jpeg 1000w, https://cms-assets.abletech.nz/small_12_US_5r5_Ti_X_Kz_DVXCQT_0_rgw_38630ae027.jpeg 500w, https://cms-assets.abletech.nz/medium_12_US_5r5_Ti_X_Kz_DVXCQT_0_rgw_38630ae027.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_12_US_5r5_Ti_X_Kz_DVXCQT_0_rgw_38630ae027.jpeg 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<p><em>Ngauranga Gorge on a classic Wellington day</em></p>
<p>It’s perfectly fine driving up State Highway 1 at Ngauranga Gorge. The speed limit there is 80 kmph, and the Leaf just purrs up that road.</p>
<p>The main difference between the Leaf and the Territory, is that you can put your foot down in the Territory and you’ll accelerate rapidly.</p>
<p>If you need similar performance at high speeds from an electric car, you’d best avoid the older Nissan Leaf. You’d be better off with a Kia Niro EV or a Hyandai Kona — they have more powerful motors that would have no trouble with Ngauranga Gorge. Obviously, any of the Tesla range would give you excellent performance too.</p>
<h2>Is it gutless on the hills?</h2>
<p>The driving performance on hills in our second hand Nissan Leaf is fine. If you want high performance at motorway speeds, instead look at nearly all of the <a href="https://driveelectric.org.nz/individuals/ev-models-and-where-to-buy/" target="_blank" rel="noopener noreferrer">new EVs on the market</a> today.</p>
<h2>More articles from the EV Q&amp;A series</h2>
<ul>
<li>
<p><a href="https://abletech.nz/article/traveling-to-palmy-in-an-electric-vehicle">Travelling to Palmy in an Electric Vehicle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-do-the-cars-cost-more-than-petrol-cars">EV Q&amp;A — Do the cars cost more than petrol cars?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-charging-do-you-need-a-special-plug-at-home">EV Q&amp;A — Charging: do you need a special plug at home?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-if-i-go-up-a-hill-do-i-gain-power-when-i-go-down">EV Q&amp;A — If I go up a hill, do I gain power when I go down?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-is-this-ev-thing-just-a-fad-or-is-it-here-to-stay-got-evidence">EV Q&amp;A — Is this EV thing just a fad?</a></p>
</li>
<li>
<p><a href="mailto:nigel@ramsay.org.nz" target="_blank" rel="noopener noreferrer">Submit a question</a> for my EV Q&amp;A series</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>How to configure VS Code to format Elixir code</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Code auto-formatted with Elixir 1.6</h2>
<p>Here is a step by step guide on how to configure <em>Visual Studio Code</em> to automatically format your Elixir code using the new built-in formatter included in version <code>1.6</code>.</p>
<h2>Step 1: upgrade your version of Elixir</h2>
<p>Obviously, you will need to have the <a href="https://elixir-lang.org/blog/2018/01/17/elixir-v1-6-0-released/" target="_blank" rel="noopener noreferrer">most recent version of Elixir</a> installed locally. If you are using OSX, then you have likely installed Elixir using Homebrew. If this is the case, the the following is all you need to do:</p>
<pre><code class="hljs"><span class="hljs-attribute">brew upgrade elixir</span>
</code></pre>
<p>You can then confirm you have the upgrade by typing:</p>
<pre><code class="hljs">elixir <span class="hljs-comment">--version</span>
</code></pre>
<p>You should see a message such as:</p>
<pre><code class="hljs">Erlang/OTP <span class="hljs-number">20</span> <span class="hljs-selector-attr">[erts-9.2]</span> <span class="hljs-selector-attr">[source]</span> <span class="hljs-selector-attr">[64-bit]</span> <span class="hljs-selector-attr">[smp:8:8]</span> <span class="hljs-selector-attr">[ds:8:8:10]</span> <span class="hljs-selector-attr">[async-threads:10]</span> <span class="hljs-selector-attr">[hipe]</span> <span class="hljs-selector-attr">[kernel-poll:false]</span> <span class="hljs-selector-attr">[dtrace]</span>

Elixir <span class="hljs-number">1.6</span>.<span class="hljs-number">0</span> (compiled with OTP <span class="hljs-number">20</span>)
</code></pre>
<h2>Step 2: confirm the formatter works</h2>
<p>The formatter is delivered as a <em>mix task</em> and can be easily tested by running something like:</p>
<pre><code class="hljs">mix <span class="hljs-keyword">format</span> &lt;<span class="hljs-keyword">filename</span>&gt;
</code></pre>
<p>Give it a go on a file in your project and see the changes it makes.</p>
<h2>Step 3: configure default formatting rules</h2>
<p>The format task looks for a file in your project directory named <code>.formatter.exs</code> with rules for which file types and locations should be formatted. Our Elixir project at <a href="https://addressfinder.com.au/" target="_blank" rel="noopener noreferrer">AddressFinder</a> is pretty standard, so you can probably start with ours:</p>
<pre><code class="hljs">[
  inputs: [<span class="hljs-string">&quot;mix.exs&quot;</span>, <span class="hljs-string">&quot;{config,lib,test}/**/*.{ex,exs}&quot;</span>]
]
</code></pre>
<p>With this file present, <code>mix format</code> will format all the above files.</p>
<p>Note: it’s very fast!</p>
<h2>Step 4: install the VS Code extension</h2>
<p>Open up VS Code, and open the extensions pane from the sidebar. You need to search for and install an <a href="https://marketplace.visualstudio.com/items?itemName=sammkj.vscode-elixir-formatter" target="_blank" rel="noopener noreferrer">extension</a> called <code>vscode-elixir-formatter</code>.</p>
<p>This extension requires you to reload VS Code, so make sure you do this.</p>
<p>You can manually activate the formatter by bringing up the <em>command palette</em> (⌘+⇧+P) and searching for “Format Document”.</p>
<p>This will format the current file using Elixir’s format task.</p>
<h2>Step 5: automatically format on save</h2>
<p>Open the User Settings (⌘+,) and set the following config:</p>
<pre><code class="hljs"><span class="hljs-attr">&quot;editor.formatOnSave&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
</code></pre>
<p>You can optionally add the following setting to format your code as you go:</p>
<pre><code class="hljs"><span class="hljs-attr">&quot;editor.formatOnType&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
</code></pre>
<h2>Optional Extra: enforcing formatted code within your CI</h2>
<p>The format task has an optional parameter <code>--check-formatted</code>. This can be added to your <code>.travis.yml</code> file, and be used to require that all source code is auto-formatted correctly.</p>
<p>You should try this out locally first, before adding it to your project:</p>
<pre><code class="hljs">mix <span class="hljs-keyword">format</span> --check-<span class="hljs-keyword">formatted</span>
</code></pre>
<p>If the check fails, you will receive an error message such as:</p>
<pre><code class="hljs"><span class="hljs-comment">** (Mix) mix format failed due to --check-formatted.</span>
The following <span class="hljs-keyword">files</span> were <span class="hljs-keyword">not</span> formatted:

<span class="hljs-comment">* lib/address_builder/au/build/paf_address_util.ex</span>
</code></pre>
<p>Travis will detect this error and mark the build as a fail.</p>
<h2>Summary</h2>
<p><a href="https://abletech.nz/">We’ve</a> been loving Elixir’s new code formatter, and we encourage people to upgrade Elixir 1.6 soon and give it a go.</p>
<p>Useful resources:</p>
<ul>
<li>
<p>Plugins for other editors: <a href="https://elixirforum.com/t/elixir-code-formatter-plugins-for-editors-ides-wiki/9851" target="_blank" rel="noopener noreferrer">https://elixirforum.com/t/elixir-code-formatter-plugins-for-editors-ides-wiki/9851</a></p>
</li>
<li>
<p>The hexdocs on <code>Code.format_string/2</code> which performs the formatting <a href="https://hexdocs.pm/elixir/Code.html#format_string!/2" target="_blank" rel="noopener noreferrer">https://hexdocs.pm/elixir/Code.html#format_string!/2</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Elixir for Addressfinder</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>We’ve been trying out Elixir for the first time. Sit in on this Abletech Tech Talk to find out what it was like to learn Elixir.</h2>
<p><a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a> and Tracey Norrish have been using Elixir, instead of Ruby, for an <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">Addressfinder</a> project. Watch a video or read the summary:</p>
<h2>Using Elixir instead of Ruby</h2>
<p>Nigel and Tracey have been looking at using Elixir as an alternative to Ruby for some of the Addressfinder address processing.</p>
<h3>Problem</h3>
<p>Address building currently takes too long.</p>
<p>It typically takes us a minimum of two to three days. We start from an empty system, to building all the data, and getting it shipped into production. It’s a massive job. It does a lot — we are working with a large data set of millions of addresses. We have around seven data sources that we merge and export to the Addressfinder API project.</p>
<p>We’ve tuned the process but any mistakes made aren’t obvious until the end causing time-wasting repetition.</p>
<h2>Solution</h2>
<p>Elixir is able to process the data more efficiently.</p>
<blockquote>
<h4>A process that previously took 90 minutes takes only 35 seconds using Elixir</h4>
</blockquote>
<p>Elixir is very quick and we’ve also fine-tuned the process of how we take our data across to the database.</p>
<p>Watch a video of Tech Talk on Elixir:</p>
<p><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item vimeo-player" type="text/html" width="640" height="390" src="https://player.vimeo.com/video/216066735" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div></p>
<h2>Ruby vs Elixir</h2>
<h3>Ruby</h3>
<p>Using Ruby, every record was loaded and then an actual insert would be executed to put the record into the database. Doing that 2 million times takes a while.</p>
<h3>Elixir</h3>
<p>With Elixir we can use a Postgres COPY to stream the data into the database, which is more efficient. The overhead of getting 2 million records into, then out of, Elixir, is quite low. Elixir can do it faster and Postgres can write the data into the database.</p>
<h2>Learning Elixir</h2>
<p>We had a job to do, so we used it as an opportunity to learn Elixir.</p>
<p>We’ve been taking one data set (which is being deprecated) and replacing it with another data set. Rather than rewriting using Ruby, we’re using Elixir. This way we learn Elixir and replace the data set at the same time.</p>
<p>In the same way that Ruby is a language, so is Elixir.</p>
<p>As Ruby on Rails runs on an application framework, Elixir does have an equivalent called Phoenix. We’re not using Phoenix because we’re not building a web app.</p>
<p>Elixir comes with a project structure, even if you’re not using Phoenix, which is nice. It’s got test expectations, directories where you put files, fixtures, configurations.</p>
<p>In addition to Elixir, we’re also using a database integration tool called Ecto, and a CSV Parser called NimbleCSV.</p>
<h3>How did it go?</h3>
<p>There are similarities between the two languages. For example, in Ruby we normally run a rake task operation. Elixir has the equivalent — tasks that you can write called mix files. Elixir also has modules that group together functions and built-in documentation where you can define what a command is going to do.</p>
<p>Elixir does pattern matching which is an interesting and useful feature.</p>
<p>We used ExGuard for testing and it ran quickly.</p>
<h2>Recap</h2>
<p>Elixir works well and is useful</p>
<p>You group things together by module, you put functions in them</p>
<p>There’s a pipe operator which is pretty easy to get your head around</p>
<p>There’s tests, which are familiar</p>
<p>Elixir runs like a rocket — it’s really quick</p>
<p>The learning curve isn’t too bad — you’re writing similar things to with Ruby — just using a pipe operator.</p>
<p>Instead of using an object, you have a module with bunch of methods on it. You pass the state around between each function call, but the pipe operator helps with this.</p>
<p>Elixir’s finely tuned for efficiency with the pipe operator</p>
<p>Now we’re getting used to Elixir we’re able to get things out quickly</p>
<p>The code looks fairly familiar (with funny operators)</p>
<p>With our particular problem of processing data. A lot of data has to pass through the system and be manipulated. If a language like Elixir can help us do that in a much more efficient manner that’s brilliant.</p>
<h3>Next steps for us</h3>
<p>We’ve got further plans for using Elixir with additions we’ll need to make to our data.</p>
<p>We’re set up with good infrastructure now, for doing what we need. We have a docker compose file, we’ve got docker containers: database, Elixir, Rails, Elastic Search.</p>
<p>Once we’ve built it, we throw it away and turn off the VM. We’ve got a Travis file, as Travis support Elixir “out of the box”.</p>
<h2>Questions</h2>
<h3>Do you have an Erlang VM process that sits there sleeping?</h3>
<p>It does it on demand.</p>
<h3>Where did you get online support?</h3>
<p>There are two Elixir Slack groups — a <a href="https://elixir-slackin.herokuapp.com" target="_blank" rel="noopener noreferrer">global slack channel</a>, and an Australia/NZ combined community too. The Elixir online documentation is very good too. There’s <a href="https://stackoverflow.com/questions/tagged/elixir" target="_blank" rel="noopener noreferrer">Stack Overflow</a> and an <a href="https://elixirforum.com" target="_blank" rel="noopener noreferrer">Elixir forum</a>.</p>
<h2>Outcome</h2>
<p>Time intensive tasks can definitely be sped up by using Elixir. The technology works efficiently.</p>
<h3>We’re excited about Elixir:</h3>
<ul>
<li>
<p>It’s allowing us to write faster code which is important for us</p>
</li>
<li>
<p>It’s not complicated — it’s familiar and accessible</p>
</li>
<li>
<p>Once you’ve got some good patterns to follow it’s quick to get going</p>
</li>
<li>
<p>In the future it could move towards being a general purpose solution for some problems we need to solve</p>
</li>
<li>
<p>Much like Rails, the learning phase will take time. Elixir, Erlang and Phoenix are quite big and you can’t learn it all in one go.</p>
</li>
<li>
<p>We’ve learnt little bits like data processing, talking to the database. Down the track we’ll learn further steps.</p>
</li>
<li>
<p>Elixir has exciting potential. It’s quick, familiar, aesthetic and reliable.</p>
</li>
</ul>
<p>Read more about <a href="https://abletech.nz/article/elixir-keeps-running">Elixir in Marcus Baguley’s</a> blog post.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>World Summit Awards Nomination</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Best and Most Innovative Digital Solution</h2>
<p>Wellington/Salzburg 17 October, 2017</p>
<p>AED Locations has been selected as Best and Most Innovative National Digital Solution in Health &amp; Well-Being in New Zealand. This means AED Locations qualifies for evaluation by the WSA Jury along with 391 international nominations.</p>
<p>The International World Summit Awards showcase to the world, digital innovation from Mexico to New Zealand, from Qatar to Germany.</p>
<blockquote>
<p>The 2017 nominees show the richness, diversity, future and innovation of digital solutions on a global scale and prove how digital technology can improve society in each corner of the world.</p>
</blockquote>
<p>AED Locations was nominated for the World Summit Award 2017 as the best and most innovative digital solution in Health &amp; Well-Being from New Zealand. The nominees will be evaluated based on seven fundamental criteria:</p>
<p>Content, Functionality, Design, Technology, Innovation, Impact and Globa/UN value.</p>
<p>AED Locations’ nomination to the WSA is already an award in itself — the qualification to compete and compare on an international level and being the best in Health and Well-Being nationally.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nijl1afddc8yttn4mjjtmdg3" alt="" data-big=https://cms-assets.abletech.nz/large_1i_PFPA_Eh_Rgig7v8pe_At_Gp_ZQ_35e0bc95fb.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1i_PFPA_Eh_Rgig7v8pe_At_Gp_ZQ_35e0bc95fb.jpeg" srcset="https://cms-assets.abletech.nz/large_1i_PFPA_Eh_Rgig7v8pe_At_Gp_ZQ_35e0bc95fb.jpeg 1000w, https://cms-assets.abletech.nz/small_1i_PFPA_Eh_Rgig7v8pe_At_Gp_ZQ_35e0bc95fb.jpeg 500w, https://cms-assets.abletech.nz/medium_1i_PFPA_Eh_Rgig7v8pe_At_Gp_ZQ_35e0bc95fb.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1i_PFPA_Eh_Rgig7v8pe_At_Gp_ZQ_35e0bc95fb.jpeg 245w" data-zooming-width="1000" data-zooming-height="383" loading="lazy" width="1000" height="383"></figure>
</div>
<p>The <a href="https://www.worldsummitawards.org/" target="_blank" rel="noopener noreferrer">World Summit Awards</a> is a global initiative within the framework of the United Nations World Summit on the Information Society. WSA judges and celebrates innovative solutions from all over the world. In 2017, digital innovations have been nominated from over 90 countries.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ghiyn08r1bwvi91rxkeus0oo" alt="" data-big=https://cms-assets.abletech.nz/large_1_Jb7ux_Bmj_Fq_Kl1od_Wa_Swcyg_711753739d.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Jb7ux_Bmj_Fq_Kl1od_Wa_Swcyg_711753739d.png" srcset="https://cms-assets.abletech.nz/large_1_Jb7ux_Bmj_Fq_Kl1od_Wa_Swcyg_711753739d.png 1000w, https://cms-assets.abletech.nz/small_1_Jb7ux_Bmj_Fq_Kl1od_Wa_Swcyg_711753739d.png 500w, https://cms-assets.abletech.nz/medium_1_Jb7ux_Bmj_Fq_Kl1od_Wa_Swcyg_711753739d.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Jb7ux_Bmj_Fq_Kl1od_Wa_Swcyg_711753739d.png 245w" data-zooming-width="1000" data-zooming-height="525" loading="lazy" width="1000" height="525"></figure>
</div>
<p>Abletech’s AED Locations is one of seven solutions nominated from the Australia/New Zealand region.</p>
<p>The World Summit Awards focus on how innovative people are using digital solutions to take action on local issues. AED Locations is a great example of how entrepreneurs have used technology to impact our community and solve local issues.</p>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">Check out AED Locations now!</a></p>
<p>The World Summit Award judges pay close attention to the United Nations Sustainable Development Goals (UN SDGs) and how AED Locations uses technology to strengthen the chain of survival, ensure healthy lives, build resilient infrastructure, promote sustainable industrialisation and foster innovation. AED Locations is highly innovative. It provides quick, efficient access to information.</p>
<p>AED Locations is a great example of quality information technology being used to access vital information during an emergency. The app takes the potential of technology and turns that potential into a life-saving reality.</p>
<p>Here at Abletech we’re delighted to be nominated for a World Summit Award as the best and most innovative digital solution in Health &amp; Well-Being from New Zealand.</p>
<h3>AED Locations</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/aed-locations-video/">Read more about the app and watch the video</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/sam-spends-summer-streamlining-%ef%b8%8f%ef%b8%8fsite/">Read about recent improvements made by Sam Gard</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Kiwi Ruby</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Developers from AddressFinder and Abletech went to Kiwi Ruby, a conference for Rubyists and Ruby-curious. The first day offered talks by Ruby enthusiasts and the second a full day of ‘deep dive workshops’ on a huge variety of topics like dry-rb . Participants could go to either, or both, days.</h2>
<p>Our developers have been to Ruby conferences overseas for years. Having this on home turf made it a good chance to catch up with fellow New Zealand Ruby developers and support the local Ruby community.</p>
<p>By all reports the conference team did an excellent job of organising their first Kiwi Ruby conference at Te Papa, Wellington. Logistics ran smoothly and the conference appeared professional. There was a good range of speakers on a diverse range of topics to interest beginners right through to people who’ve been using Ruby for a long time.</p>
<ul>
<li>
<p>Some topics discussed at Kiwi Ruby have been doing the rounds for a long time but it was good to hear fresh perspectives on issues like how to manage time and how to mentor people new to the community.</p>
</li>
<li>
<p>A highlight for one of our devs was <a href="https://en.wikipedia.org/wiki/UTF-8" target="_blank" rel="noopener noreferrer">UTF-8 encoding</a>. “It’s super smart in that it is backwards compatible with ASCII, and you can decode characters inside a stream.”</p>
</li>
<li>
<p>How to deal with getting a very bad code review. This topic encouraged devs to have some empathy and make an effort not to shame the receiver who can be left feeling embarrassed, stupid or angry with themselves after receiving negative feedback. Here’s a <a href="https://www.ted.com/talks/brene_brown_listening_to_shame" target="_blank" rel="noopener noreferrer">TED talk about delving into shame and confronting it</a>.</p>
</li>
<li>
<p>Our devs also enjoyed the deep dive into Hello, World! They found it interesting to see how Ruby code is interpreted by MRI into assembly code.</p>
</li>
<li>
<p>There were helpful skills-based talks on topics like accelerating learning, reaching your potential, mentoring, parenting and how you can use your skills to help people without profit motives.</p>
</li>
<li>
<p>Goodie bags — these were popular, especially the <a href="https://www.facebook.com/AddressFinder/posts/176522186243224" target="_blank" rel="noopener noreferrer">AddressFinder swag</a> 😉</p>
</li>
</ul>
<p>Find out more about why <a href="https://blog.addressfinder.io/kiwi-ruby-2901b4d3d45b" target="_blank" rel="noopener noreferrer">AddressFinder sponsored Kiwi Ruby</a>.</p>
<p>Kiwi Ruby is a community event run by volunteers, overseen by <a href="http://www.ruby.nz/" target="_blank" rel="noopener noreferrer">Ruby New Zealand</a>.</p>
<p>The organisers are <a href="https://www.twitter.com/merxplat" target="_blank" rel="noopener noreferrer">Merrin Macleod</a>, <a href="https://www.twitter.com/raquelxmoss" target="_blank" rel="noopener noreferrer">Raquel Moss</a>, <a href="https://www.twitter.com/plainricedinner" target="_blank" rel="noopener noreferrer">Rose Lu</a>, <a href="https://www.twitter.com/terrcin" target="_blank" rel="noopener noreferrer">Nahum Wild</a>, and <a href="https://github.com/parndt" target="_blank" rel="noopener noreferrer">Philip Arndt</a>. Between them they’ve run Rails Camps, Rails Girls events, and local meetups for many years.</p>
<p>They thought it was time for New Zealand to have a Ruby conference so they mustered the time, energy and support and mixed it with initiative, to make Kiwi Ruby happen. Thanks for bringing it home 💖</p>
<p><em>Originally published at <a href="https://blog.addressfinder.io/kiwi-ruby-d29b474ff33d" target="_blank" rel="noopener noreferrer">blog.addressfinder.io</a> on November 8, 2017.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part two</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The conversion of this VW Beetle, to electric power, is underway. In part two of the process there’s talk of torque, power and brakes.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="y5mkquczf7cxwe4lkg8nlhjf" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0ruiu_U45_Jq_C8j_Y_Vf_Z_9067ffa812.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0ruiu_U45_Jq_C8j_Y_Vf_Z_9067ffa812.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0ruiu_U45_Jq_C8j_Y_Vf_Z_9067ffa812.jpg 245w" data-zooming-width="245" data-zooming-height="61" loading="lazy" width="245" height="61"></figure>
</div>
<p><strong>Going and Stopping</strong></p>
<p>Having decided to convert our VW Beetle (Trixie) to 100% electric, there are a number of changes underway.</p>
<p>Trixie currently has a 1600cc (roughly 40 horsepower) engine. The new electric motor is an HPEVS AC-50 which produces slightly more power than the petrol engine, but with instant torque.</p>
<p>The motor is paired with a Curtis AC Motor Controller which, among other things, supports regenerative braking. This will be great for Wellington’s hills.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wcb46xkb6mo9e8l94jmtegpf" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_03t7qwl_KG_8_H4_Pcx_Q_7b0cdb3752.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/03t7qwl_KG_8_H4_Pcx_Q_7b0cdb3752.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_03t7qwl_KG_8_H4_Pcx_Q_7b0cdb3752.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
</div>
<p>The fuel tank will be replaced with 37 lithium iron phosphate cells, making a 122V battery and delivering about 22 KWh of power. This will give us about a 150km driving range per charge.</p>
<p>Before starting the conversion I decided to update the front brakes from drum to discs. While I still had the car running with the petrol engine it was much easier to do the brake change and then fully test the outcome. EMPI Disc kits are awesome; they include a new master brake cylinder to match the new disc brakes — here is a picture of the outcome:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nhcoqb8zav7bga77vyeufw9d" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_BH_9lbt_Cjixw_G_Oc_P_a37566ed7b.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_BH_9lbt_Cjixw_G_Oc_P_a37566ed7b.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_BH_9lbt_Cjixw_G_Oc_P_a37566ed7b.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
</div>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part nine</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Keeping it cool</h2>
<p>An irony of the eTrixie project is that VW Beetles are air cooled, but eTrixie’s new motor controller needs water cooling! In part nine we look at ancillary systems such as water cooling the motor controller and the 12v system.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="r752x35ydsny8dwy4vak90h5" alt="12v battery" data-big=https://cms-assets.abletech.nz/large_1_P_Rt2_VC_3rbl_X_Xs_Hw_CF_Nn_Pg_ed2d6363f3.jpeg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/1_P_Rt2_VC_3rbl_X_Xs_Hw_CF_Nn_Pg_ed2d6363f3.jpeg" srcset="https://cms-assets.abletech.nz/large_1_P_Rt2_VC_3rbl_X_Xs_Hw_CF_Nn_Pg_ed2d6363f3.jpeg 750w, https://cms-assets.abletech.nz/small_1_P_Rt2_VC_3rbl_X_Xs_Hw_CF_Nn_Pg_ed2d6363f3.jpeg 375w, https://cms-assets.abletech.nz/medium_1_P_Rt2_VC_3rbl_X_Xs_Hw_CF_Nn_Pg_ed2d6363f3.jpeg 563w, https://cms-assets.abletech.nz/thumbnail_1_P_Rt2_VC_3rbl_X_Xs_Hw_CF_Nn_Pg_ed2d6363f3.jpeg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
</div>
<p><em>12v battery</em></p>
<p>The 120v system powering eTrixie is electrically isolated from the car. eTrixie’s old 12v system still exists. Because 12v starting current is no longer needed, I’ve replaced the old lead acid battery with a smaller LiFePo4 motorbike battery. This is connected to a 400W DC/DC converter which takes the main battery voltage and converts it to 12v. The main job of the 12v system is running the ignition relays and powering the lights (brake, tail indicator and headlights).</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bp87mlav8af0rpxq4g2qcczg" alt="DC/DC converter (left — above charge port), water cooler (right)" data-big=https://cms-assets.abletech.nz/medium_1h_O77m_JY_5_L_Wd_YD_Mul_Mkxw_Eg_4df9a3b05a.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1h_O77m_JY_5_L_Wd_YD_Mul_Mkxw_Eg_4df9a3b05a.jpeg" srcset="https://cms-assets.abletech.nz/small_1h_O77m_JY_5_L_Wd_YD_Mul_Mkxw_Eg_4df9a3b05a.jpeg 500w, https://cms-assets.abletech.nz/medium_1h_O77m_JY_5_L_Wd_YD_Mul_Mkxw_Eg_4df9a3b05a.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1h_O77m_JY_5_L_Wd_YD_Mul_Mkxw_Eg_4df9a3b05a.jpeg 156w" data-zooming-width="750" data-zooming-height="750" loading="lazy" width="750" height="750"></figure>
</div>
<p><em>DC/DC converter (left — above charge port), water cooler (right)</em></p>
<p>A new job for the 12v system is the water cooler — the motor controller is mounted on a water fed chiller plate which is connected to a high-end PC water cooler.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ilzbtt39lu8fxra8xx8ciled" alt="Water cooler" data-big=https://cms-assets.abletech.nz/medium_1_Wd_Rm_PUG_Naz_N58_RKV_2u_W_Wo_A_59b12b8ea3.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_Wd_Rm_PUG_Naz_N58_RKV_2u_W_Wo_A_59b12b8ea3.jpeg" srcset="https://cms-assets.abletech.nz/small_1_Wd_Rm_PUG_Naz_N58_RKV_2u_W_Wo_A_59b12b8ea3.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Wd_Rm_PUG_Naz_N58_RKV_2u_W_Wo_A_59b12b8ea3.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Wd_Rm_PUG_Naz_N58_RKV_2u_W_Wo_A_59b12b8ea3.jpeg 156w" data-zooming-width="750" data-zooming-height="750" loading="lazy" width="750" height="750"></figure>
</div>
<p><em>Water cooler</em></p>
<p>There is also a need for a couple of new dash board indicator lights — one to show when the ignition is turned on and another to show the handbrake is engaged. The solution for this is to repurpose the green oil pressure light to be the ignition ‘on’ indicator and the red generator light to be the handbrake indicator. This is a great approach as it keeps the dashboard unchanged.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="eh9obq3ulhlexuxty5ums39t" alt="Repurposed dash lights" data-big=https://cms-assets.abletech.nz/large_1lmyl3wloo7_Z_Inequ03_Caw_A_8c76eabdcc.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1lmyl3wloo7_Z_Inequ03_Caw_A_8c76eabdcc.jpeg" srcset="https://cms-assets.abletech.nz/large_1lmyl3wloo7_Z_Inequ03_Caw_A_8c76eabdcc.jpeg 1000w, https://cms-assets.abletech.nz/small_1lmyl3wloo7_Z_Inequ03_Caw_A_8c76eabdcc.jpeg 500w, https://cms-assets.abletech.nz/medium_1lmyl3wloo7_Z_Inequ03_Caw_A_8c76eabdcc.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1lmyl3wloo7_Z_Inequ03_Caw_A_8c76eabdcc.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Repurposed dash lights</em></p>
<p>That’s about it for ancillary systems — because ’65 Beetles don’t have power brakes, power steering or air-conditioning there is nothing required here. I’m still thinking about heating — I’m researching options for this — still a few months to go until winter!</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Elixir keeps running</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3>Eighteen months ago I deployed my first Elixir application. It sits in-front of a badly written PHP app (not ours — we don’t php or write bad apps😂) and checks security tokens, and permission when accessing server resources. It was my first production deployment of an Elixir application. I chose Elixir for its performance as a proxy service along with the custom database integration and security logic that would be demanded for the project in hand.</h3>
<p>What I have been really impressed by, is that the same app has been running for 18 months and has not been recycled, redeloyed, restarted or anything. Check out the memory footprint 1 year on…</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sqs02xdo8bhmt3vydifs5kwi" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_Hm_Rr2_Dd8_T5r_ME_9_O2omhp_Zw_16fd0efb67.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Hm_Rr2_Dd8_T5r_ME_9_O2omhp_Zw_16fd0efb67.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_Hm_Rr2_Dd8_T5r_ME_9_O2omhp_Zw_16fd0efb67.png 245w" data-zooming-width="245" data-zooming-height="75" loading="lazy" width="245" height="75"></figure>
</div>
<p>At Abletech we have Elixir firmly on our radar and keep this tool on the ready for any other projects that will suit its sweetspot of high performance, highly reliant beautiful code.</p>
<p>Several of us are bidding to attend <a href="https://elixirconf.com/" target="_blank" rel="noopener noreferrer">Elixir Conf 2017</a>. Come talk to us if you have any projects that could do with a bit of Elixir magic — <a href="https://abletech.nz/">Abletech</a> will be very keen to help.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Wellrailed</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Check out the speakers for this week’s Wellrailed meetup:</h2>
<h3>Regan Ryan</h3>
<p>Regan Ryan will give an introduction to Ruby Meta Programming — learn the nuts and bolts of how Ruby works, and begin the meta-programming journey</p>
<h3>Mathew Hartley</h3>
<p>Mathew Hartley will introduce and demonstrate Amazon’s IoT, as well as some of the big data tools like Athena and Quicksight. The goal is to show a few of the (quite interesting) tools you wouldn’t be using in production, in the context of recording house temperatures.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="maazjaihs9v9490wi1y24j7h" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_Duj_Tmz_OZ_o_QQ_Gb_Hg_Ae_If_BA_727569e3df.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Duj_Tmz_OZ_o_QQ_Gb_Hg_Ae_If_BA_727569e3df.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_Duj_Tmz_OZ_o_QQ_Gb_Hg_Ae_If_BA_727569e3df.png 245w" data-zooming-width="245" data-zooming-height="132" loading="lazy" width="245" height="132"></figure>
</div>
<h3>Come along</h3>
<p>Thursday 27th July. Held at Optimal Workshop: Level 2, 2–12 Allen Street, Wellington. Doors open at 5:30pm with talks starting at 6:00pm. Pizza and chatting/networking after the first talk.</p>
<h3>Join the conversation</h3>
<p>Meetups are on the 4th Thursday of each month. Further details are on the <a href="https://www.meetup.com/wellrailed/events/240630447/" target="_blank" rel="noopener noreferrer">Wellrailed</a> site or in the Ruby NZ Slack channel. Get an invite from the official <a href="http://slack.ruby.nz/" target="_blank" rel="noopener noreferrer">Ruby NZ site</a>. Don’t forgot to join the #wellington channel as that’s where discussion and announcements about these meetups happen.</p>
<p><em>Originally published at <a href="https://www.meetup.com/wellrailed/events/240630447/" target="_blank" rel="noopener noreferrer">www.meetup.com</a>.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>The future of tech</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Last night we met the next generation of tech-experts</h2>
<p>Crazy fun and good conversations at the Summer of Tech session last night. A big thank you to all the students who came and chatted with us. You all have exciting careers ahead of you!</p>
<p>There was a huge crowd of employers and students. We met some awesome potential interns who’d prepared well for chatting with us. We’re looking forward to offering one of you some paid work experience soon 🤝</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mcgfj5w51hh9m32h0auccqu7" alt="" data-big=https://cms-assets.abletech.nz/large_1u30n0_ISTV_Or4_M_Sx3_Xv_ipw_f2f2cb854d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 229px" src="https://cms-assets.abletech.nz/1u30n0_ISTV_Or4_M_Sx3_Xv_ipw_f2f2cb854d.jpeg" srcset="https://cms-assets.abletech.nz/large_1u30n0_ISTV_Or4_M_Sx3_Xv_ipw_f2f2cb854d.jpeg 1000w, https://cms-assets.abletech.nz/small_1u30n0_ISTV_Or4_M_Sx3_Xv_ipw_f2f2cb854d.jpeg 500w, https://cms-assets.abletech.nz/medium_1u30n0_ISTV_Or4_M_Sx3_Xv_ipw_f2f2cb854d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1u30n0_ISTV_Or4_M_Sx3_Xv_ipw_f2f2cb854d.jpeg 229w" data-zooming-width="1000" data-zooming-height="682" loading="lazy" width="1000" height="682"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ct6vkqouta5y1e65c0v8zvhp" alt="" data-big=https://cms-assets.abletech.nz/large_1vz_Qhhjjets_uu0_Kv_Akx_Kx_Q_e3faa9195f.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1vz_Qhhjjets_uu0_Kv_Akx_Kx_Q_e3faa9195f.jpeg" srcset="https://cms-assets.abletech.nz/large_1vz_Qhhjjets_uu0_Kv_Akx_Kx_Q_e3faa9195f.jpeg 1000w, https://cms-assets.abletech.nz/small_1vz_Qhhjjets_uu0_Kv_Akx_Kx_Q_e3faa9195f.jpeg 500w, https://cms-assets.abletech.nz/medium_1vz_Qhhjjets_uu0_Kv_Akx_Kx_Q_e3faa9195f.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1vz_Qhhjjets_uu0_Kv_Akx_Kx_Q_e3faa9195f.jpeg 245w" data-zooming-width="1000" data-zooming-height="625" loading="lazy" width="1000" height="625"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bz23seylukwzolmsdcq16xrc" alt="" data-big=https://cms-assets.abletech.nz/large_1z_Iov_g_L3e_Xki_U_Sc_1d_Lpaw_5467e5cb65.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 238px" src="https://cms-assets.abletech.nz/1z_Iov_g_L3e_Xki_U_Sc_1d_Lpaw_5467e5cb65.jpeg" srcset="https://cms-assets.abletech.nz/large_1z_Iov_g_L3e_Xki_U_Sc_1d_Lpaw_5467e5cb65.jpeg 1000w, https://cms-assets.abletech.nz/small_1z_Iov_g_L3e_Xki_U_Sc_1d_Lpaw_5467e5cb65.jpeg 500w, https://cms-assets.abletech.nz/medium_1z_Iov_g_L3e_Xki_U_Sc_1d_Lpaw_5467e5cb65.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1z_Iov_g_L3e_Xki_U_Sc_1d_Lpaw_5467e5cb65.jpeg 238w" data-zooming-width="1000" data-zooming-height="655" loading="lazy" width="1000" height="655"></figure>
</div>
<p>Summer of Tech do a fantastic job. The event was well run — pairing students and employers together giving both ample opportunities to talk to one another.</p>
<p>There was a wide variety of skills and interests from design students wanting to get into front-end, software engineering students wanting to extend their language knowledge into rails and students wanting to try the full stack.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ytna2udvlhuboy8xwezdgzx8" alt="Summer of Tech runs an excellent programme" data-big=https://cms-assets.abletech.nz/large_1j_L_Xb_Gjivw_Gskfx9_Bet_R_Ndw_ecccb63a75.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1j_L_Xb_Gjivw_Gskfx9_Bet_R_Ndw_ecccb63a75.png" srcset="https://cms-assets.abletech.nz/large_1j_L_Xb_Gjivw_Gskfx9_Bet_R_Ndw_ecccb63a75.png 1000w, https://cms-assets.abletech.nz/small_1j_L_Xb_Gjivw_Gskfx9_Bet_R_Ndw_ecccb63a75.png 500w, https://cms-assets.abletech.nz/medium_1j_L_Xb_Gjivw_Gskfx9_Bet_R_Ndw_ecccb63a75.png 750w, https://cms-assets.abletech.nz/thumbnail_1j_L_Xb_Gjivw_Gskfx9_Bet_R_Ndw_ecccb63a75.png 245w" data-zooming-width="1000" data-zooming-height="358" loading="lazy" width="1000" height="358"></figure>
</div>
<p><em>Summer of Tech runs an excellent programme</em></p>
<p>We love these <a href="https://abletech.nz/article/kick-start-your-tech-career">Meet &amp; Greets</a>. Read more from Matt Lee — our latest <a href="https://abletech.nz/article/interning-at-abletech">intern who became an employee</a>. He started as an <a href="https://abletech.nz/article/my-first-month-at-abletech">intern</a> and has now settled in. And read about another employee, and previous intern, Sam Gard’s experience at a <a href="https://abletech.nz/article/cssconf-jsconf-australia-2018/">conference in Melbourne</a> this year.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>The Abletech story — part two</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>In the second part of Abletech’s story, the company has grown. It has invested in a diverse team with a broad knowledge base. The result? A multitude of skills and experiences to approach each project with cutting edge innovation and tailored solutions.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zjfdl7eroxozivv069kl0o5u" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0d_NE_Hi_MG_Cb3_Wk_Hfgs_a81c100b81.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0d_NE_Hi_MG_Cb3_Wk_Hfgs_a81c100b81.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0d_NE_Hi_MG_Cb3_Wk_Hfgs_a81c100b81.jpg 245w" data-zooming-width="245" data-zooming-height="117" loading="lazy" width="245" height="117"></figure>
</div>
<h2>People matter</h2>
<p>Following on from <a href="https://abletech.nz/article/the-abletech-story-part-one">The Abletech story — part 1</a> we pick up the narrative at the point where Marcus and Nigel had begun hand-picking developers.</p>
<p>Marcus and Nigel have always been discerning. They’re selective about their technology choices. They’re able to select the clients they work with. They also pride themselves on their choice of staff.</p>
<p>“The people we have on the team are so important,” explains Nigel. “We are careful to have a good balance of skills, seniority, experience, diversity and people who share our values.”</p>
<p>“It’s crucial,” Marcus agrees. “We are intentional about how we grow and what sorts of hires we take on.”</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="edjaqatnrqsrirg0rgyh35kp" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_C_Jz_Bhqqf_WFIFPZ_4cc2a92981.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_C_Jz_Bhqqf_WFIFPZ_4cc2a92981.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_C_Jz_Bhqqf_WFIFPZ_4cc2a92981.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
</div>
<p>Nigel explains that they are always on the look-out for people. “We prefer to be in ‘constant hire mode’ so we have the right mix available when we need them.” Abletech doesn’t wait to find the right people at the right time. “The chances are slim that the best person will be out there looking for a job at exactly the same time as we’re needing them,” says Nigel.</p>
<p>Abletech offers company shares to its staff and involves everyone in the company’s strategic moves. The team enjoys a relaxed, positive atmosphere and have regular team events such as <a href="https://stories.abletech.nz/elixir-for-addressfinder-7f14228745a2" target="_blank" rel="noopener noreferrer">knowledge and skill sharing tech-sessions</a>.</p>
<p>“We don’t want to grow too quickly,” Nigel explains. “It’s more important to us that we have the right people on the team and that they can genuinely take part in the business.”</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wlil8eog4bkwkzjmu19xws4w" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_07cvcf_E0f3_Yz_P_Dp_Gd_631d9e5978.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/07cvcf_E0f3_Yz_P_Dp_Gd_631d9e5978.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_07cvcf_E0f3_Yz_P_Dp_Gd_631d9e5978.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<h2>Able Technology Ltd became Abletech</h2>
<p>In 2010 Carl Penwarden joined the directorship. With a background in engineering and telecommunications management, he foresaw an increasing demand for marketplace choice. Carl had worked on national broadband mapping for the government, and was managing fibre optic cabling at CityLink.</p>
<p>**Cloud computing
**“I loved it but I could see there needed to be a disaggregation of the networks and services,” explains Carl. “There was going to be more and more demand for cloud technology done well.”</p>
<p>Carl had known Nigel for ten years. They got chatting with John Clegg, who owned an online mapping and addressing business. “John was wanting to go overseas,” explains Carl. “But he had new customers and he wanted to leave them in a safe pair of hands.” Carl joined as Managing Director, they became Abletech, and acquired ProjectX Technology.</p>
<p>This acquisition included the staff, products and systems of ProjectX’s core business, minus the name. At the time, Marcus explained that the move was, “a natural progression for Abletech as an influential member of Wellington’s growing Ruby on Rails software development community”. He was right; it was another good choice.</p>
<p>The service, AddressFinder, is now enjoying growing success in both NZ and Australia and Abletech are key players in the Ruby on Rails community.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uoaosibmpysrtxg4caq2v8b4" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_03_Ubxb40_Us_r_N_o_R_39ad6e45d9.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/03_Ubxb40_Us_r_N_o_R_39ad6e45d9.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_03_Ubxb40_Us_r_N_o_R_39ad6e45d9.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<p>**Developing in an agile environment
**Abletech was able to transition AddressFinder customers like NZ Post and GWRC into clients of further software services. This side of Abletech has also grown in popularity. Carl says the ethos of Abletech has always been quite simple. “We deliver awesome solutions.” He says it doesn’t get more complicated than that.</p>
<p>“We’re big on delivery, people decide what they want and how much they’ll pay for it. That’s what they get: exactly what they want.”</p>
<p>Abletech are also experienced at getting a minimum viable product out into the marketplace, fast, in front of customers. “The key thing is getting feedback and using that to inform further iterations,” Carl explains. “Ruby on Rails is the perfect tool for testing and analysing a new business idea.”</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qyfuf1i3r3n7eforbf77klfe" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Nqb_O5_We_Mh_Mhy_NBF_5_88b1abe73d.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Nqb_O5_We_Mh_Mhy_NBF_5_88b1abe73d.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_Nqb_O5_We_Mh_Mhy_NBF_5_88b1abe73d.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<h2>Today</h2>
<p>These days Abletech builds and grows its own SaaS businesses and the team identifies new opportunities together. As organisations move from larger centralised technology stacks, they approach Abletech to integrate modern micro-technologies that scale to their needs. Abletech are known for having a solid development process and technical goodness.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="u7kvrfxx2xtx3gtz3hkykhf6" alt="" data-big=https://cms-assets.abletech.nz/large_1d1_Y_Hx8_Nt_Tk_Lt_H12_Y6ar_L_Xg_469b29cb11.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1d1_Y_Hx8_Nt_Tk_Lt_H12_Y6ar_L_Xg_469b29cb11.jpeg" srcset="https://cms-assets.abletech.nz/large_1d1_Y_Hx8_Nt_Tk_Lt_H12_Y6ar_L_Xg_469b29cb11.jpeg 1000w, https://cms-assets.abletech.nz/small_1d1_Y_Hx8_Nt_Tk_Lt_H12_Y6ar_L_Xg_469b29cb11.jpeg 500w, https://cms-assets.abletech.nz/medium_1d1_Y_Hx8_Nt_Tk_Lt_H12_Y6ar_L_Xg_469b29cb11.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1d1_Y_Hx8_Nt_Tk_Lt_H12_Y6ar_L_Xg_469b29cb11.jpeg 245w" data-zooming-width="1000" data-zooming-height="412" loading="lazy" width="1000" height="412"></figure>
</div>
<p>Marcus and Nigel are still fired-up about what they do. “I love the atmosphere,” says Marcus. “Every day we’re getting stuff done, pleasing clients and controlling our destiny.” Nigel agrees, “people say they’ve seen what we’ve done for others and want us to do it for them”.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="krt2x0041ko46won3991x23t" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_MQ_5_P_Oyw2zyvy_Wu_VJ_f2baaacc9e.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_MQ_5_P_Oyw2zyvy_Wu_VJ_f2baaacc9e.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_MQ_5_P_Oyw2zyvy_Wu_VJ_f2baaacc9e.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<p>Although they’re also directors, Marcus and Nigel love nothing better than to roll up their sleeves and get stuck into code, while Carl enjoys promoting and maintaining the growing business and team. Enthusiasm for cloud technologies is alive and well. Abletech’s culture thrives as the team advances the mission to deliver innovative solutions together.</p>
<p>There’s no shortage of work for the talented developers at Abletech but they take it all in their stride. They pace themselves with a healthy balance of activities including <a href="https://stories.abletech.nz/how-to-old-ghost-road-c40e63e7c1cd" target="_blank" rel="noopener noreferrer">cycling</a>, <a href="https://stories.abletech.nz/abletech-developers-spent-a-few-days-in-melbourne-for-the-annual-ruby-conference-9657d0a1ade2" target="_blank" rel="noopener noreferrer">conferences</a>, <a href="https://stories.abletech.nz/national-volunteer-week-270df8b62e29" target="_blank" rel="noopener noreferrer">community good</a>, <a href="https://stories.abletech.nz/healthy-developers-53e51a3d857b" target="_blank" rel="noopener noreferrer">food</a> and <a href="https://stories.abletech.nz/parachute-downsizing-and-lessons-learnt-6fb228aed9e9" target="_blank" rel="noopener noreferrer">crazy hobbies</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bdzu85jzcdm0o20l3ex0ly11" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0h_C4qc_AET_Wf7r_Pp_Jt_648f810005.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0h_C4qc_AET_Wf7r_Pp_Jt_648f810005.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0h_C4qc_AET_Wf7r_Pp_Jt_648f810005.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
</div>
<p><strong>Everyone’s happy that everyone’s happy</strong>
Marcus and Nigel have found great team-mates who also love cloud tech. Clients are equally excited about Abletech developing ideas into reality using the latest web and mobile technologies.</p>
<p><a href="https://abletech.nz/article/the-abletech-story-part-one">The Abletech Story — Part One</a> explains the initial ideas and people who started the company.</p>
<p>Watch the <a href="https://abletech.nz/article/the-story-of-abletech">Abletech story in a minute</a> in this animation.</p>
<p><a href="https://abletech.nz/contact/">Get in touch</a> to chat!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Team meeting</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>At Abletech we all work on a variety of projects and tasks, for clients and in-house products.</p>
<p>Abletechers are spread across many locations; today we even have a team in the mighty Waikato.</p>
<p>We meet every month to chat and eat together. We catch up on what each other’s been doing and what’s in the Abletech pipeline.</p>
<p>Our regular team meetings are always enhanced with a delicious lunch.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="x49zf9lfim1r6a1jy6em1pms" alt="" data-big=https://cms-assets.abletech.nz/large_1czi_Kom_Lka_Qp_Z1_H6h1s_Vz_Q_d53489bd4d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1czi_Kom_Lka_Qp_Z1_H6h1s_Vz_Q_d53489bd4d.jpeg" srcset="https://cms-assets.abletech.nz/large_1czi_Kom_Lka_Qp_Z1_H6h1s_Vz_Q_d53489bd4d.jpeg 1000w, https://cms-assets.abletech.nz/small_1czi_Kom_Lka_Qp_Z1_H6h1s_Vz_Q_d53489bd4d.jpeg 500w, https://cms-assets.abletech.nz/medium_1czi_Kom_Lka_Qp_Z1_H6h1s_Vz_Q_d53489bd4d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1czi_Kom_Lka_Qp_Z1_H6h1s_Vz_Q_d53489bd4d.jpeg 156w" data-zooming-width="1000" data-zooming-height="1000" loading="lazy" width="1000" height="1000"></figure>
</div>
<p>This was popular — chocolate mousse. It had a berry compote and it tasted divine.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Replace Postman with humble Bash</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Cameron Fowler recommends you simplify your toolset by replacing Postman with Bash</h2>
<p><a href="https://www.getpostman.com/" target="_blank" rel="noopener noreferrer">Postman</a> is a great API testing tool. It has a boat-load of functionality that I will never use, but at the heart of it, it does these things:</p>
<ol>
<li>
<p>Calls an API in a reproducible way</p>
</li>
<li>
<p>Formats the response nicely</p>
</li>
<li>
<p>Handles passing variables into the API request and parsing variables out of the API response</p>
</li>
</ol>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="csms85og3e0dfl96nhhanzza" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1l4bs_J_Nbct_KP_0030_Cxz_Sglw_590aa51ada.png sizes="(min-width: 640px) 174px" src="https://cms-assets.abletech.nz/1l4bs_J_Nbct_KP_0030_Cxz_Sglw_590aa51ada.png" srcset="https://cms-assets.abletech.nz/thumbnail_1l4bs_J_Nbct_KP_0030_Cxz_Sglw_590aa51ada.png 174w" data-zooming-width="174" data-zooming-height="156" loading="lazy" width="174" height="156"></figure>
</div>
<p>I’m all in favour for up-skilling in tools I already have available on my machine, and simplifying my toolset. Often I find that some product I’ve been using for years is actually solving a problem that has already been solved by a developer in the form of a command line tool.</p>
<p>I figured that this is a fairly simple problem to solve using <code>bash</code> and <code>curl</code> and some JSON parsing (in my case, I used <code>jq</code>). It turns out it is simple and very powerful.</p>
<h3>Simply set some environment variables in a file:</h3>
<pre><code class="hljs"><span class="hljs-comment"># file: ./caller/cameron</span>
<span class="hljs-built_in">export</span> <span class="hljs-attribute">HOST</span>=<span class="hljs-string">&quot;my.test.example.com&quot;</span>
<span class="hljs-built_in">export</span> <span class="hljs-attribute">USERNAME</span>=<span class="hljs-string">&quot;cameron&quot;</span>
<span class="hljs-built_in">export</span> <span class="hljs-attribute">password</span>=<span class="hljs-string">&quot;...&quot;</span>
</code></pre>
<h3>Then put other HTTP calls in other files, and source them from the command line. I would expect to call our ‘system’ like this:</h3>
<pre><code class="hljs">. caller<span class="hljs-symbol">/cameron</span> <span class="hljs-comment"># which container the above environment variables</span>
. get_bearer_token <span class="hljs-comment"># which calls an API and gets a token, setting it as another environment variable</span>
<span class="hljs-symbol">./get_test_endpoint</span> <span class="hljs-comment"># a simple authorised API call</span>
<span class="hljs-symbol">./post_test_endpoint</span> <span class="hljs-comment"># an example of a post.</span>
</code></pre>
<h3>An example of how you might use the response to get out a BEARER token:</h3>
<pre><code class="hljs"><span class="hljs-comment"># file: ./get_bearer_token</span>
curl <span class="hljs-params">--trace-ascii</span> last_trace.curl \
  <span class="hljs-params">--include</span> <span class="hljs-params">--silent</span> <span class="hljs-params">--show-error</span> \
  <span class="hljs-params">--header</span> [@headers]<span class="hljs-params">(http://twitter.com/headers)</span> \
  <span class="hljs-params">--request</span> POST \
  <span class="hljs-params">--url</span> <span class="hljs-string">&quot;$HOST/auth/token&quot;</span> \
  <span class="hljs-params">--data</span> @- &lt;&lt;EOF &gt; last_response.http
{
  <span class="hljs-string">&quot;username&quot;</span>: <span class="hljs-string">&quot;$USERNAME&quot;</span>,
  <span class="hljs-string">&quot;password&quot;</span>: <span class="hljs-string">&quot;$PASSWORD&quot;</span>,
  <span class="hljs-string">&quot;clientId&quot;</span>: <span class="hljs-string">&quot;test-client&quot;</span>,
  <span class="hljs-string">&quot;grantType&quot;</span>: <span class="hljs-string">&quot;password&quot;</span>,
  <span class="hljs-string">&quot;remember&quot;</span>: <span class="hljs-literal">false</span>
}
EOF

cat last_response.http
<span class="hljs-keyword">echo</span>

BEARER_TOKEN=<span class="hljs-string">&quot;$(tail -n 1 last_response.http | jq -r &#x27;.accessToken&#x27;)&quot;</span>
export BEARER_TOKEN
<span class="hljs-keyword">echo</span> <span class="hljs-string">&quot;$BEARER_TOKEN&quot;</span>
</code></pre>
<h3>A GET:</h3>
<pre><code class="hljs">file: ./get_test_endpoint (dont forget chmod +<span class="hljs-attribute">x</span>)
curl <span class="hljs-attr">--trace-ascii</span> last_trace<span class="hljs-selector-class">.curl</span> \
  <span class="hljs-attr">--include</span> <span class="hljs-attr">--silent</span> <span class="hljs-attr">--show-error</span> \
  <span class="hljs-attr">--header</span> <span class="hljs-string">&quot;Authorization: Bearer $BEARER_TOKEN&quot;</span> \
  <span class="hljs-attr">--request</span> GET \
  <span class="hljs-attr">--url</span> <span class="hljs-string">&quot;$HOST/test-endpoint&quot;</span> &gt; last_response<span class="hljs-selector-class">.http</span>
</code></pre>
<h3>A cut down POST example:</h3>
<pre><code class="hljs">file: ./post_test_endpoint (dont forget chmod +<span class="hljs-attribute">x</span>)

curl <span class="hljs-attr">--trace-ascii</span> last_trace<span class="hljs-selector-class">.curl</span> \
  <span class="hljs-attr">--include</span> <span class="hljs-attr">--silent</span> <span class="hljs-attr">--show-error</span> \
  <span class="hljs-attr">--header</span> <span class="hljs-string">&quot;Authorization: Bearer $BEARER_TOKEN&quot;</span> \
  <span class="hljs-attr">--request</span> POST \
  <span class="hljs-attr">--url</span> <span class="hljs-string">&quot;$HOST/test-create-endpoint&quot;</span> \
  <span class="hljs-attr">--data</span> @- &lt;&lt;EOF &gt; last_response<span class="hljs-selector-class">.http</span>
{
  <span class="hljs-string">&quot;mode&quot;</span>: <span class="hljs-string">&quot;test&quot;</span>,
  <span class="hljs-string">&quot;payload&quot;</span>: null,
  <span class="hljs-string">&quot;testdata&quot;</span>: <span class="hljs-number">0</span>,
  <span class="hljs-string">&quot;easyJSON&quot;</span>: true
}
EOF
</code></pre>
<ul>
<li>
<p><a href="https://abletech.nz/resource/vims-most-magical-autocomplete">Vim’s most magical autocomplete</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/a-quick-replace-in-vim">A quick replace in Vim</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/til-you-can-use-function-keys-as-bash-shortcuts">Using function keys as Bash shortcuts</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>ReactJS basics</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>React is used by big names like Netflix, Yahoo and Facebook. Michael Kuhinica, from Sharesight, took us behind the scenes of this popular user interface library to look at the benefits of using React.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k4sjz9jtht1taa74bk5xa0de" alt="ReactJS Basics with Michael Kuhinica from Sharesight" data-big=https://cms-assets.abletech.nz/large_1_M_Ciw_E_Lq_YY_3rg_Gkkuyeyf_KA_41d3d6811e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_M_Ciw_E_Lq_YY_3rg_Gkkuyeyf_KA_41d3d6811e.png" srcset="https://cms-assets.abletech.nz/large_1_M_Ciw_E_Lq_YY_3rg_Gkkuyeyf_KA_41d3d6811e.png 1000w, https://cms-assets.abletech.nz/small_1_M_Ciw_E_Lq_YY_3rg_Gkkuyeyf_KA_41d3d6811e.png 500w, https://cms-assets.abletech.nz/medium_1_M_Ciw_E_Lq_YY_3rg_Gkkuyeyf_KA_41d3d6811e.png 750w, https://cms-assets.abletech.nz/thumbnail_1_M_Ciw_E_Lq_YY_3rg_Gkkuyeyf_KA_41d3d6811e.png 245w" data-zooming-width="1000" data-zooming-height="562" loading="lazy" width="1000" height="562"></figure>
</div>
<p><em>ReactJS Basics with Michael Kuhinica from Sharesight</em></p>
<p>Online investment portfolio tracker, Sharesight, are making the most of React technology.</p>
<p>Take seventeen minutes to watch this Abletech Tech Talk and find out why React is getting so much love.</p>
<p>Catch another Abletech Tech Talk:</p>
<ul>
<li>
<p><a href="https://abletech.nz/article/elixir-for-addressfinder">Elixir</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/enspiral-dev-academy-meetup">Intro to TypeScript</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/rails-girls-workshops">Running a Rails Girls weekend</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/check-out-turbolinks">Turbolinks</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/controlling-midi-hardware-from-the-browser">Running MIDI hardware from your browser</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/terraform">Terraform</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/health">Health</a> — physical, mental and nutritional well being</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Prepared for an emergency</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Some of us will have a long way to walk if there’s an emergency. We took a look through the Abletech Go Bags during the team lunch today. Here’s what we found:</h2>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hmmf7od4daewiz3inzv4vjn2" alt="" data-big=https://cms-assets.abletech.nz/large_1u5_Yc_U_Itnfh_Cd_XRBWY_FV_Og_2d3dbb303d.jpeg sizes="(min-width: 1280px) 656px, (min-width: 768px) 328px, (min-width: 1024px) 492px, (min-width: 640px) 102px" src="https://cms-assets.abletech.nz/1u5_Yc_U_Itnfh_Cd_XRBWY_FV_Og_2d3dbb303d.jpeg" srcset="https://cms-assets.abletech.nz/large_1u5_Yc_U_Itnfh_Cd_XRBWY_FV_Og_2d3dbb303d.jpeg 656w, https://cms-assets.abletech.nz/small_1u5_Yc_U_Itnfh_Cd_XRBWY_FV_Og_2d3dbb303d.jpeg 328w, https://cms-assets.abletech.nz/medium_1u5_Yc_U_Itnfh_Cd_XRBWY_FV_Og_2d3dbb303d.jpeg 492w, https://cms-assets.abletech.nz/thumbnail_1u5_Yc_U_Itnfh_Cd_XRBWY_FV_Og_2d3dbb303d.jpeg 102w" data-zooming-width="656" data-zooming-height="1000" loading="lazy" width="656" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="c2okwhxqcqubss7myt3kbbdi" alt="" data-big=https://cms-assets.abletech.nz/large_1pj_Im_Eo4m2_S_Sk4g_Cz_45_P_Zg_9ccca5a79d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1pj_Im_Eo4m2_S_Sk4g_Cz_45_P_Zg_9ccca5a79d.jpeg" srcset="https://cms-assets.abletech.nz/large_1pj_Im_Eo4m2_S_Sk4g_Cz_45_P_Zg_9ccca5a79d.jpeg 1000w, https://cms-assets.abletech.nz/small_1pj_Im_Eo4m2_S_Sk4g_Cz_45_P_Zg_9ccca5a79d.jpeg 500w, https://cms-assets.abletech.nz/medium_1pj_Im_Eo4m2_S_Sk4g_Cz_45_P_Zg_9ccca5a79d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1pj_Im_Eo4m2_S_Sk4g_Cz_45_P_Zg_9ccca5a79d.jpeg 245w" data-zooming-width="1000" data-zooming-height="582" loading="lazy" width="1000" height="582"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e7d4rh4pdc5b86ej3o502okm" alt="Radios, torches, batteries, food, medicines, first aid, warm gear, water" data-big=https://cms-assets.abletech.nz/large_1jv_Mq9_Uzmh_Gfi_Zwmxi2_O_Lrg_3775bb4a05.jpeg sizes="(min-width: 1280px) 999px, (min-width: 768px) 500px, (min-width: 1024px) 749px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1jv_Mq9_Uzmh_Gfi_Zwmxi2_O_Lrg_3775bb4a05.jpeg" srcset="https://cms-assets.abletech.nz/large_1jv_Mq9_Uzmh_Gfi_Zwmxi2_O_Lrg_3775bb4a05.jpeg 999w, https://cms-assets.abletech.nz/small_1jv_Mq9_Uzmh_Gfi_Zwmxi2_O_Lrg_3775bb4a05.jpeg 500w, https://cms-assets.abletech.nz/medium_1jv_Mq9_Uzmh_Gfi_Zwmxi2_O_Lrg_3775bb4a05.jpeg 749w, https://cms-assets.abletech.nz/thumbnail_1jv_Mq9_Uzmh_Gfi_Zwmxi2_O_Lrg_3775bb4a05.jpeg 156w" data-zooming-width="999" data-zooming-height="1000" loading="lazy" width="999" height="1000"></figure>
</div>
<p><em>Radios, torches, batteries, food, medicines, first aid, warm gear, water</em></p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="arcij4m6k5mi7p3dpylt8u9d" alt="" data-big=https://cms-assets.abletech.nz/small_1j_Wx_J_Xccw5n47x120h_Eh_LNQ_c20c88bef5.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1j_Wx_J_Xccw5n47x120h_Eh_LNQ_c20c88bef5.png" srcset="https://cms-assets.abletech.nz/small_1j_Wx_J_Xccw5n47x120h_Eh_LNQ_c20c88bef5.png 500w, https://cms-assets.abletech.nz/thumbnail_1j_Wx_J_Xccw5n47x120h_Eh_LNQ_c20c88bef5.png 245w" data-zooming-width="500" data-zooming-height="230" loading="lazy" width="500" height="230"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ygyn0e9ft3vxedbey73c6g5w" alt=""  sizes="" src="https://cms-assets.abletech.nz/1_OR_Hc_MW_2na_I1_B89_Yq_Kj_Pn_Og_3b2ec75b02.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="xc19o1sd53cavzia3nx5lvyv" alt="" data-big=https://cms-assets.abletech.nz/large_1_L_Jvf_Ri_Js_CV_1_Q6c_Hn_T7g6_Og_d12f14ee9c.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 215px" src="https://cms-assets.abletech.nz/1_L_Jvf_Ri_Js_CV_1_Q6c_Hn_T7g6_Og_d12f14ee9c.jpeg" srcset="https://cms-assets.abletech.nz/large_1_L_Jvf_Ri_Js_CV_1_Q6c_Hn_T7g6_Og_d12f14ee9c.jpeg 1000w, https://cms-assets.abletech.nz/small_1_L_Jvf_Ri_Js_CV_1_Q6c_Hn_T7g6_Og_d12f14ee9c.jpeg 500w, https://cms-assets.abletech.nz/medium_1_L_Jvf_Ri_Js_CV_1_Q6c_Hn_T7g6_Og_d12f14ee9c.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_L_Jvf_Ri_Js_CV_1_Q6c_Hn_T7g6_Og_d12f14ee9c.jpeg 215w" data-zooming-width="1000" data-zooming-height="726" loading="lazy" width="1000" height="726"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="xt3r9z38b4dkaaf6ub6f21iq" alt="" data-big=https://cms-assets.abletech.nz/large_13_Upp_BUL_5_Zj69l_X_Iz_xgj_g_c95fc5c3fc.jpeg sizes="(min-width: 1280px) 748px, (min-width: 768px) 374px, (min-width: 1024px) 561px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/13_Upp_BUL_5_Zj69l_X_Iz_xgj_g_c95fc5c3fc.jpeg" srcset="https://cms-assets.abletech.nz/large_13_Upp_BUL_5_Zj69l_X_Iz_xgj_g_c95fc5c3fc.jpeg 748w, https://cms-assets.abletech.nz/small_13_Upp_BUL_5_Zj69l_X_Iz_xgj_g_c95fc5c3fc.jpeg 374w, https://cms-assets.abletech.nz/medium_13_Upp_BUL_5_Zj69l_X_Iz_xgj_g_c95fc5c3fc.jpeg 561w, https://cms-assets.abletech.nz/thumbnail_13_Upp_BUL_5_Zj69l_X_Iz_xgj_g_c95fc5c3fc.jpeg 117w" data-zooming-width="748" data-zooming-height="1000" loading="lazy" width="748" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="o54o9otyqeyu7klepxeqz88q" alt="The best emergency kit was Nigel’s solar powered phone charger" data-big=https://cms-assets.abletech.nz/large_1_Rold_VA_5_Bji1c_Yd8532s_Gg_dfc38b289f.jpeg sizes="(min-width: 1280px) 966px, (min-width: 768px) 483px, (min-width: 1024px) 725px, (min-width: 640px) 151px" src="https://cms-assets.abletech.nz/1_Rold_VA_5_Bji1c_Yd8532s_Gg_dfc38b289f.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Rold_VA_5_Bji1c_Yd8532s_Gg_dfc38b289f.jpeg 966w, https://cms-assets.abletech.nz/small_1_Rold_VA_5_Bji1c_Yd8532s_Gg_dfc38b289f.jpeg 483w, https://cms-assets.abletech.nz/medium_1_Rold_VA_5_Bji1c_Yd8532s_Gg_dfc38b289f.jpeg 725w, https://cms-assets.abletech.nz/thumbnail_1_Rold_VA_5_Bji1c_Yd8532s_Gg_dfc38b289f.jpeg 151w" data-zooming-width="966" data-zooming-height="1000" loading="lazy" width="966" height="1000"></figure>
</div>
<p><em>The best emergency kit was Nigel’s solar powered phone charger</em></p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="iij4xscf95v8myd34j5f4d6e" alt="" data-big=https://cms-assets.abletech.nz/large_1h_Dnwz_J2xwhhh_HVYH_Dvl_Bo_Q_56a3dbbc59.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1h_Dnwz_J2xwhhh_HVYH_Dvl_Bo_Q_56a3dbbc59.jpeg" srcset="https://cms-assets.abletech.nz/large_1h_Dnwz_J2xwhhh_HVYH_Dvl_Bo_Q_56a3dbbc59.jpeg 1000w, https://cms-assets.abletech.nz/small_1h_Dnwz_J2xwhhh_HVYH_Dvl_Bo_Q_56a3dbbc59.jpeg 500w, https://cms-assets.abletech.nz/medium_1h_Dnwz_J2xwhhh_HVYH_Dvl_Bo_Q_56a3dbbc59.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1h_Dnwz_J2xwhhh_HVYH_Dvl_Bo_Q_56a3dbbc59.jpeg 245w" data-zooming-width="1000" data-zooming-height="609" loading="lazy" width="1000" height="609"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hexx5n63prxz1jvyafwk0r6z" alt="Team lunch was delicious thanks to Food Envy" data-big=https://cms-assets.abletech.nz/large_1wsi2qw_VH_Is9_N549_Jf_F_Zim_Q_f94485f5f2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/1wsi2qw_VH_Is9_N549_Jf_F_Zim_Q_f94485f5f2.jpeg" srcset="https://cms-assets.abletech.nz/large_1wsi2qw_VH_Is9_N549_Jf_F_Zim_Q_f94485f5f2.jpeg 1000w, https://cms-assets.abletech.nz/small_1wsi2qw_VH_Is9_N549_Jf_F_Zim_Q_f94485f5f2.jpeg 500w, https://cms-assets.abletech.nz/medium_1wsi2qw_VH_Is9_N549_Jf_F_Zim_Q_f94485f5f2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1wsi2qw_VH_Is9_N549_Jf_F_Zim_Q_f94485f5f2.jpeg 234w" data-zooming-width="1000" data-zooming-height="667" loading="lazy" width="1000" height="667"></figure>
</div>
<p><em>Team lunch was delicious thanks to Food Envy</em></p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zijr5hix7c7xv0ulftlklztc" alt="" data-big=https://cms-assets.abletech.nz/large_1u_Va_m_Ik_Nn_X_Hi_G_Nmsgkm4j_A_e9d3ab3184.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1u_Va_m_Ik_Nn_X_Hi_G_Nmsgkm4j_A_e9d3ab3184.jpeg" srcset="https://cms-assets.abletech.nz/large_1u_Va_m_Ik_Nn_X_Hi_G_Nmsgkm4j_A_e9d3ab3184.jpeg 1000w, https://cms-assets.abletech.nz/small_1u_Va_m_Ik_Nn_X_Hi_G_Nmsgkm4j_A_e9d3ab3184.jpeg 500w, https://cms-assets.abletech.nz/medium_1u_Va_m_Ik_Nn_X_Hi_G_Nmsgkm4j_A_e9d3ab3184.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1u_Va_m_Ik_Nn_X_Hi_G_Nmsgkm4j_A_e9d3ab3184.jpeg 245w" data-zooming-width="1000" data-zooming-height="605" loading="lazy" width="1000" height="605"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ww0iumty2joba7qqdlcynchh" alt="" data-big=https://cms-assets.abletech.nz/large_1_R_Yh5_Dc_QLIE_Tq_B0_G0v_Teoj_Q_e31142bbae.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/1_R_Yh5_Dc_QLIE_Tq_B0_G0v_Teoj_Q_e31142bbae.jpeg" srcset="https://cms-assets.abletech.nz/large_1_R_Yh5_Dc_QLIE_Tq_B0_G0v_Teoj_Q_e31142bbae.jpeg 1000w, https://cms-assets.abletech.nz/small_1_R_Yh5_Dc_QLIE_Tq_B0_G0v_Teoj_Q_e31142bbae.jpeg 500w, https://cms-assets.abletech.nz/medium_1_R_Yh5_Dc_QLIE_Tq_B0_G0v_Teoj_Q_e31142bbae.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_R_Yh5_Dc_QLIE_Tq_B0_G0v_Teoj_Q_e31142bbae.jpeg 234w" data-zooming-width="1000" data-zooming-height="667" loading="lazy" width="1000" height="667"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="o06wv32ixly7sm8nzmkjx6dd" alt="" data-big=https://cms-assets.abletech.nz/large_1qs_Nt_CF_Ltu0_T2_Zp_Us0_Uc_Jh_Q_9f366bad58.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 236px" src="https://cms-assets.abletech.nz/1qs_Nt_CF_Ltu0_T2_Zp_Us0_Uc_Jh_Q_9f366bad58.jpeg" srcset="https://cms-assets.abletech.nz/large_1qs_Nt_CF_Ltu0_T2_Zp_Us0_Uc_Jh_Q_9f366bad58.jpeg 1000w, https://cms-assets.abletech.nz/small_1qs_Nt_CF_Ltu0_T2_Zp_Us0_Uc_Jh_Q_9f366bad58.jpeg 500w, https://cms-assets.abletech.nz/medium_1qs_Nt_CF_Ltu0_T2_Zp_Us0_Uc_Jh_Q_9f366bad58.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1qs_Nt_CF_Ltu0_T2_Zp_Us0_Uc_Jh_Q_9f366bad58.jpeg 236w" data-zooming-width="1000" data-zooming-height="661" loading="lazy" width="1000" height="661"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Happiness — a fundamental human goal</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>In 2011 the UN General Assembly adopted a resolution which recognised happiness as a ‘fundamental human goal’.</h2>
<p>The UN proclaimed March 20th** International Day of Happiness** to emphasise our need for the ‘happiness and well-being of all peoples’.</p>
<p>How happy are you? How happy are other people when they’re around you? Here’s some food for thought:</p>
<h3>The Happiness of Software Developers</h3>
<p>In a recent RubyConf AU talk developer Sandi Metz spoke about how complicated happiness, and unhappiness, is. She’s written a book called *99 Bottles of OOP *about making your code more satisfying to contemplate, but she’s gone further and thought about why some business teams are more happy than others.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rkh2wsqtnkjqwzbaby6vkupt" alt="Sandi Metz" data-big=https://cms-assets.abletech.nz/large_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 182px" src="https://cms-assets.abletech.nz/1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg" srcset="https://cms-assets.abletech.nz/large_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 1000w, https://cms-assets.abletech.nz/small_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 500w, https://cms-assets.abletech.nz/medium_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 182w" data-zooming-width="1000" data-zooming-height="856" loading="lazy" width="1000" height="856"></figure>
</div>
<p><em>Slide from Sandi Metz’s keynote talk</em></p>
<p>Sandi’s noticed that, in workplaces, there seem to be common criteria for happiness. In her conference talk she referred to how people relate with each other, persuade each other and contribute to the happiness of each other. She talked about social proof, authority and the influence of others’ judgement. She reckons that liking someone is linked to trust and reciprocity. She referenced books like Dale Carnegie’s famous <em>How to Win Friends and Influence People</em> and recent research that looks at the psychology of persuasion.</p>
<p>In the area of team-work Sandi suggested we contemplate these areas when we’re thinking about our own happiness:</p>
<ol>
<li>
<p><strong>Being approachable</strong> — try to be a good listener, try to remember people’s names, be sincere, let other people know they’re important to you</p>
</li>
<li>
<p><strong>Winning people to your way of thinking</strong> — show respect for others’ opinions, if you’re wrong admit it, let other people talk, agree that their point-of-view is correct, assume they have good motives</p>
</li>
<li>
<p>**Being a leader **— begin with praise and appreciation, discuss common mistakes, praise even the slightest improvement, use encouragement, see if you can make other team-mates happy</p>
</li>
<li>
<p><strong>If you want to change other people start by changing yourself</strong> — it’s generally agreed that you can’t change other people but Sandi reckons that if you alter your own actions, your thoughts and your words it just might change others</p>
</li>
</ol>
<p>What do you think?</p>
<p><a href="https://www.sandimetz.com/blog/" target="_blank" rel="noopener noreferrer">Read Sandi’s blog.</a></p>
<h3>Read more</h3>
<p><a href="https://medium.com/@tania.walker" target="_blank" rel="noopener noreferrer">Tania Walker</a>’s <a href="https://abletech.nz/article/rubyconf-2018">highlights from RubyConf AU 2018</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Elixir meetup at Abletech</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Abletech hosted the latest Elixir meetup in Wellington</h2>
<p>Elixir is rapidly gaining interest and popularity. Kiwi developers connect online to learn and share tips together via Twitter and the AU/NZ Slack group. Wellington Elixir User Group members get together for Hack Nights.</p>
<p>The latest meetup for Wellington Elixir enthusiasts was a great presentation hosted at the new Abletech offices. The group heard from Nahum Wild who demonstrated how to deploy to production.</p>
<p>The event was live streamed to people in Auckland, Hamilton and Canada.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nhmp3llpnnkrcsaogisj6jnq" alt="" data-big=https://cms-assets.abletech.nz/large_1_E_Ihfc_Fj_N7gyh_Ylc1_U_Rty_Fg_a2e1c777c9.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_E_Ihfc_Fj_N7gyh_Ylc1_U_Rty_Fg_a2e1c777c9.jpeg" srcset="https://cms-assets.abletech.nz/large_1_E_Ihfc_Fj_N7gyh_Ylc1_U_Rty_Fg_a2e1c777c9.jpeg 1000w, https://cms-assets.abletech.nz/small_1_E_Ihfc_Fj_N7gyh_Ylc1_U_Rty_Fg_a2e1c777c9.jpeg 500w, https://cms-assets.abletech.nz/medium_1_E_Ihfc_Fj_N7gyh_Ylc1_U_Rty_Fg_a2e1c777c9.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_E_Ihfc_Fj_N7gyh_Ylc1_U_Rty_Fg_a2e1c777c9.jpeg 245w" data-zooming-width="1000" data-zooming-height="603" loading="lazy" width="1000" height="603"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vx3a8sflkf7rubcmuucrj44v" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_Lr4_TR_Qg_Klz_SR_Wgl_S_Ah_Ba_Gg_a08a9944fd.jpeg sizes="(min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_Lr4_TR_Qg_Klz_SR_Wgl_S_Ah_Ba_Gg_a08a9944fd.jpeg" srcset="https://cms-assets.abletech.nz/thumbnail_1_Lr4_TR_Qg_Klz_SR_Wgl_S_Ah_Ba_Gg_a08a9944fd.jpeg 156w" data-zooming-width="156" data-zooming-height="156" loading="lazy" width="156" height="156"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="a82zyeus3kkp127pfg68nkp8" alt="Elixir presentation from Nahum with a break for Sal’s pizza sponsored by [Sailthru](http://carnival.io/)" data-big=https://cms-assets.abletech.nz/large_1d4_OPP_75_BC_Ok_F_Tn_Lw_Ga_Hx_Fw_4065c93c1d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 235px" src="https://cms-assets.abletech.nz/1d4_OPP_75_BC_Ok_F_Tn_Lw_Ga_Hx_Fw_4065c93c1d.jpeg" srcset="https://cms-assets.abletech.nz/large_1d4_OPP_75_BC_Ok_F_Tn_Lw_Ga_Hx_Fw_4065c93c1d.jpeg 1000w, https://cms-assets.abletech.nz/small_1d4_OPP_75_BC_Ok_F_Tn_Lw_Ga_Hx_Fw_4065c93c1d.jpeg 500w, https://cms-assets.abletech.nz/medium_1d4_OPP_75_BC_Ok_F_Tn_Lw_Ga_Hx_Fw_4065c93c1d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1d4_OPP_75_BC_Ok_F_Tn_Lw_Ga_Hx_Fw_4065c93c1d.jpeg 235w" data-zooming-width="1000" data-zooming-height="664" loading="lazy" width="1000" height="664"></figure>
</div>
<p><em>Elixir presentation from Nahum with a break for Sal’s pizza sponsored by <a href="http://carnival.io/" target="_blank" rel="noopener noreferrer">Sailthru</a></em></p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dkq5eamsfa2fl2h45xqjpfg9" alt="" data-big=https://cms-assets.abletech.nz/large_1_J_Vw_XV_So_Aza_Dztv_Jw1_Luw0_Q_1ebb784975.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_J_Vw_XV_So_Aza_Dztv_Jw1_Luw0_Q_1ebb784975.jpeg" srcset="https://cms-assets.abletech.nz/large_1_J_Vw_XV_So_Aza_Dztv_Jw1_Luw0_Q_1ebb784975.jpeg 1000w, https://cms-assets.abletech.nz/small_1_J_Vw_XV_So_Aza_Dztv_Jw1_Luw0_Q_1ebb784975.jpeg 500w, https://cms-assets.abletech.nz/medium_1_J_Vw_XV_So_Aza_Dztv_Jw1_Luw0_Q_1ebb784975.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_J_Vw_XV_So_Aza_Dztv_Jw1_Luw0_Q_1ebb784975.jpeg 245w" data-zooming-width="1000" data-zooming-height="521" loading="lazy" width="1000" height="521"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="eqno6sixmq4994iifnvefnce" alt="" data-big=https://cms-assets.abletech.nz/large_1u_GE_d8y_H_Bixl_ZW_5u_AV_0_K_Hw_6ff5b14321.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 236px" src="https://cms-assets.abletech.nz/1u_GE_d8y_H_Bixl_ZW_5u_AV_0_K_Hw_6ff5b14321.jpeg" srcset="https://cms-assets.abletech.nz/large_1u_GE_d8y_H_Bixl_ZW_5u_AV_0_K_Hw_6ff5b14321.jpeg 1000w, https://cms-assets.abletech.nz/small_1u_GE_d8y_H_Bixl_ZW_5u_AV_0_K_Hw_6ff5b14321.jpeg 500w, https://cms-assets.abletech.nz/medium_1u_GE_d8y_H_Bixl_ZW_5u_AV_0_K_Hw_6ff5b14321.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1u_GE_d8y_H_Bixl_ZW_5u_AV_0_K_Hw_6ff5b14321.jpeg 236w" data-zooming-width="1000" data-zooming-height="660" loading="lazy" width="1000" height="660"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lptb7am0cqfs9mnvlndwt0ls" alt="Nahum organised video conf gear and the juice. [Abletech](https://abletech.nz/) supplied fresh scones with jam and cream 😋" data-big=https://cms-assets.abletech.nz/large_1c4ss_G_Wren1pj7b_G_Qmk_Uev_A_1738a6daf2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1c4ss_G_Wren1pj7b_G_Qmk_Uev_A_1738a6daf2.jpeg" srcset="https://cms-assets.abletech.nz/large_1c4ss_G_Wren1pj7b_G_Qmk_Uev_A_1738a6daf2.jpeg 1000w, https://cms-assets.abletech.nz/small_1c4ss_G_Wren1pj7b_G_Qmk_Uev_A_1738a6daf2.jpeg 500w, https://cms-assets.abletech.nz/medium_1c4ss_G_Wren1pj7b_G_Qmk_Uev_A_1738a6daf2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1c4ss_G_Wren1pj7b_G_Qmk_Uev_A_1738a6daf2.jpeg 245w" data-zooming-width="1000" data-zooming-height="622" loading="lazy" width="1000" height="622"></figure>
</div>
<p><em>Nahum organised video conf gear and the juice. <a href="https://abletech.nz/">Abletech</a> supplied fresh scones with jam and cream 😋</em></p>
<p>Nahum demonstrated using Kubernetes to deploy his application to production. The app consisted of:</p>
<ul>
<li>
<p>React frontend</p>
</li>
<li>
<p>Phoenix API backend</p>
</li>
<li>
<p>Distillery for building Elixir binaries</p>
</li>
<li>
<p>GitLab CI/CD for making Docker Images which are deployed to a Kubernetes cluster</p>
</li>
</ul>
<h3>Read more:</h3>
<p>The <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">AddressFinder</a> and Rapid Projects teams at <a href="https://abletech.nz/">Abletech</a> have been using Elixir. Find out more from them here:</p>
<ul>
<li>
<p>Watch a video to <a href="https://abletech.nz/resource/understanding-genservers">understand GenServers</a></p>
</li>
<li>
<p>See how <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a> <a href="https://abletech.nz/resource/how-to-configure-vs-code-to-format-elixir-code">configures VS Code to format Elixir code</a></p>
</li>
<li>
<p>Read about the <a href="https://abletech.nz/article/the-elixir-equivalent-of-rspecs-focus-true">Elixir equivalent of Rspec’s focus: true</a></p>
</li>
<li>
<p>Find out more about the <a href="https://www.meetup.com/Wellington-Elixir-User-Group/" target="_blank" rel="noopener noreferrer">Wellington Elixir Group</a></p>
</li>
<li>
<p>Find out about <a href="https://abletech.nz/resource/elixir-for-javascript-and-ruby-developers">converting an existing Ruby application to Elixir</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Accessibility, error handling and service workers</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Highlights from recent CSS and JS conferences. Kate Norquay summarises talks she enjoyed from Julie Grundy, Brittany Storoz, Alexander Pope and more…</h2>
<p>Kate’s favourite talks include Julie Grundy on accessibility, Theresa Ma on improving the way designers and engineers work together, and Diana Mounter from GitHub on the interaction of colour systems.</p>
<p>Also from the JS conference held in Melbourne: Brittany Storoz on error handling, Alexander Pope about service workers and Tim Holman’s talk about art made with JavaScript.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="egcq93kggcmw2o8z2t8rfy30" alt="" data-big=https://cms-assets.abletech.nz/large_1nmi_Xh9_Dtp2_WYG_Vrw_SBF_8_NQ_6fe4390a42.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1nmi_Xh9_Dtp2_WYG_Vrw_SBF_8_NQ_6fe4390a42.png" srcset="https://cms-assets.abletech.nz/large_1nmi_Xh9_Dtp2_WYG_Vrw_SBF_8_NQ_6fe4390a42.png 1000w, https://cms-assets.abletech.nz/small_1nmi_Xh9_Dtp2_WYG_Vrw_SBF_8_NQ_6fe4390a42.png 500w, https://cms-assets.abletech.nz/medium_1nmi_Xh9_Dtp2_WYG_Vrw_SBF_8_NQ_6fe4390a42.png 750w, https://cms-assets.abletech.nz/thumbnail_1nmi_Xh9_Dtp2_WYG_Vrw_SBF_8_NQ_6fe4390a42.png 245w" data-zooming-width="1000" data-zooming-height="540" loading="lazy" width="1000" height="540"></figure>
</div>
<h3>Read more</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/cssconf-jsconf-australia-2018">More highlights from the CSS &amp; JS conf</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/rubyconf-2018">Tania reviews the 2018 Ruby conference in Sydney</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/kiwi-ruby">Kiwi Ruby</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/national-javascript-conference">NZ Javascript Conf</a></p>
</li>
<li>
<p>Graham, infrastructure lead at Coinbase gives an <a href="https://abletech.nz/article/coinbase">insight behind-the-scenes</a></p>
</li>
<li>
<p>Intern reflects on <a href="https://stories.abletech.nz/interning-at-abletech-a41037060b11" target="_blank" rel="noopener noreferrer">three months at Abletech</a></p>
</li>
<li>
<p>How to <a href="https://stories.abletech.nz/how-to-configure-vs-code-to-format-elixir-code-a39e89cdbc59" target="_blank" rel="noopener noreferrer">configure VS code to format Elixir code</a></p>
</li>
<li>
<p>Follow Abletech on <a href="https://twitter.com/Abletech" target="_blank" rel="noopener noreferrer">Twitter</a>, <a href="https://www.facebook.com/abletech" target="_blank" rel="noopener noreferrer">Facebook</a> or <a href="https://nz.linkedin.com/company/able-technology" target="_blank" rel="noopener noreferrer">LinkedIn</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Young New Zealander of the Year</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Congratulations to Kendall Flutey: co-founder of EdTech start-up Banqer</h2>
<p>We’re excited to see Kendall’s been recognised for growing her idea into Banqer: a successful revenue generating financial education app.</p>
<p>We have a special interest in Kendall. She joined our <a href="https://abletech.nz/about/">Abletech team</a> in 2014 and quickly developed her Ruby on Rails skills. Amongst other projects Kendall worked on our in-house service AddressFinder. She combined her programming skills with her business background and was such an asset to our team. <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">AddressFinder</a> is now a high performing profitable service used by over 1000 websites in <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">New Zealand</a> and <a href="https://addressfinder.com.au/" target="_blank" rel="noopener noreferrer">Australia</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="gida56dr1nxmkb2to06lbxad" alt="" data-big=https://cms-assets.abletech.nz/medium_13_UH_gk_Tf_FTQ_c7_J_Gcb7o7_A_3f89390e9d.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 202px" src="https://cms-assets.abletech.nz/13_UH_gk_Tf_FTQ_c7_J_Gcb7o7_A_3f89390e9d.png" srcset="https://cms-assets.abletech.nz/small_13_UH_gk_Tf_FTQ_c7_J_Gcb7o7_A_3f89390e9d.png 500w, https://cms-assets.abletech.nz/medium_13_UH_gk_Tf_FTQ_c7_J_Gcb7o7_A_3f89390e9d.png 750w, https://cms-assets.abletech.nz/thumbnail_13_UH_gk_Tf_FTQ_c7_J_Gcb7o7_A_3f89390e9d.png 202w" data-zooming-width="750" data-zooming-height="578" loading="lazy" width="750" height="578"></figure>
</div>
<p>In April 2015 Kendall joined a team of Abletech women who visited Wellington Girls’ College to talk about what it’s like to work in the Tech industry. In 2015 Kendall won the BNZ Start Up Alley competition. And she officially became an inspiring woman. Then Kendall left Abletech to teach kids about money!</p>
<p>Because Kendall’s skills are so wide-ranging she was able to guide Banqer from conception, through technical development, to become a popular and successful life-tool. Kendall’s equally happy in the classroom or the boardroom, and fosters corporate and social partners with ease. We’re big fans!</p>
<p>Over 70,000 Australasian kids are now becoming curious about money and confident in finance, for free, thanks to Banqer. How cool is that?</p>
<p>Kendall’s fully deserving of the title: Young New Zealander of the Year. She’s using her myriad of skills to better the financial education of students. It’s paying off. Kids are talking with their parents, teachers, and fellow students about financial decisions, tax, investments, insurance and savings. Kids are interested in the economy and how money shapes our world.</p>
<p>Congratulations Kendall 🥂 you deserve cake.</p>
<h3>Read more:</h3>
<ul>
<li>
<p><a href="https://www.banqer.co/" target="_blank" rel="noopener noreferrer">Check out Banqer</a></p>
</li>
<li>
<p><a href="https://abletech.nz/articles">What’s going on at Abletech</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Sharesight wins 2017 Finnies award for Excellence in Industry Collaboration & Partnerships</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Congratulations to our friends at Sharesight! FinTech Australia has named Sharesight the 2017 “Finnies” award winner for Excellence in Industry Collaboration and Partnerships (Australia)!</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q1qfur1qkrs00gbzxjgnpxve" alt="" data-big=https://cms-assets.abletech.nz/medium_featured_2017_finnie_awards_winners_5c4809bb42.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/featured_2017_finnie_awards_winners_5c4809bb42.jpeg" srcset="https://cms-assets.abletech.nz/small_featured_2017_finnie_awards_winners_5c4809bb42.jpeg 500w, https://cms-assets.abletech.nz/medium_featured_2017_finnie_awards_winners_5c4809bb42.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_featured_2017_finnie_awards_winners_5c4809bb42.jpeg 245w" data-zooming-width="750" data-zooming-height="394" loading="lazy" width="750" height="394"></figure>
</div>
<p>Sharesight are 100% committed to the “Rebel Alliance” — that is an ecosystem of connected fintechs. This award is really a story about APIs and how open data architecture provides the best outcome for consumers.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="d659pjo56kz9q7ymq27mooel" alt="" data-big=https://cms-assets.abletech.nz/medium_2017_finnie_awards_josh_5e5391088e.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/2017_finnie_awards_josh_5e5391088e.jpeg" srcset="https://cms-assets.abletech.nz/small_2017_finnie_awards_josh_5e5391088e.jpeg 500w, https://cms-assets.abletech.nz/medium_2017_finnie_awards_josh_5e5391088e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_2017_finnie_awards_josh_5e5391088e.jpeg 234w" data-zooming-width="750" data-zooming-height="500" loading="lazy" width="750" height="500"></figure>
</div>
<p>Sharesight Account Manager Josh Rodrigues accepting the award</p>
<p>Financial services used to be an impenetrable, vertically integrated, bank-dominated industry. This is changing. If you think of this like a column of children’s’ blocks, it’s been toppled over and consumers now have the ability to rebuild it to their needs. We have work to do, but it is happening.</p>
<blockquote>
<p><em>So honoured to have won “Excellence in Industry Collaboration &amp; Partnerships (Australia)” at <a href="https://twitter.com/ausfintech" target="_blank" rel="noopener noreferrer">@ausfintech</a>’s <a href="https://twitter.com/hashtag/finnieawards?src=hash" target="_blank" rel="noopener noreferrer">#finnieawards</a>! <a href="https://twitter.com/hashtag/fintech?src=hash" target="_blank" rel="noopener noreferrer">#fintech</a><a href="https://t.co/A15HA6YASG" target="_blank" rel="noopener noreferrer">pic.twitter.com/A15HA6YASG</a></em>
<em>— Sharesight (@sharesight) <a href="https://twitter.com/sharesight/status/867333157678272513" target="_blank" rel="noopener noreferrer">May 24, 2017</a></em></p>
</blockquote>
<p>Sharesight’s commitment to building an open set of APIs has enabled them to partner with fintechs and industry leaders. Macrovue, Six Park, <a href="https://www.sharesight.com/blog/visualise-your-portfolio-with-sharesight-and-simply-wall-st/" target="_blank" rel="noopener noreferrer">Simply Wall Street</a>, <a href="https://www.sharesight.com/blog/sharesight-teams-up-with-cmc-markets-stockbroking/" target="_blank" rel="noopener noreferrer">CMC Stockbroking</a>, <a href="https://www.sharesight.com/blog/pwc-partners-with-sharesight/" target="_blank" rel="noopener noreferrer">PwC</a>, <a href="https://www.sharesight.com/blog/livewire-market-insights-within-your-portfolio/" target="_blank" rel="noopener noreferrer">Livewire</a>, <a href="https://www.sharesight.com/xero/" target="_blank" rel="noopener noreferrer">Xero</a>, and others are all members of the Sharesight ecosystem.</p>
<blockquote>
<p><em>Excellence in Industry Collaboration &amp; Partnerships (<a href="https://twitter.com/hashtag/Australia?src=hash" target="_blank" rel="noopener noreferrer">#Australia</a>) winner is <a href="https://twitter.com/sharesight" target="_blank" rel="noopener noreferrer">@sharesight</a> <a href="https://twitter.com/hashtag/finnieawards?src=hash" target="_blank" rel="noopener noreferrer">#finnieawards</a><a href="https://t.co/JgMRTqIBIf" target="_blank" rel="noopener noreferrer">https://t.co/JgMRTqIBIf</a><a href="https://twitter.com/JobsforNSW" target="_blank" rel="noopener noreferrer">@JobsforNSW</a></em>
<em>— FinTech Australia (@ausfintech) <a href="https://twitter.com/ausfintech/status/867331487535738880" target="_blank" rel="noopener noreferrer">May 24, 2017</a></em></p>
</blockquote>
<p>This award would not have been possible without their willingness to join the Rebel Alliance, so the beers are on us next time. Let’s keep up it, we’ve got work to do!</p>
<blockquote>
<p><a href="https://twitter.com/hashtag/fintech?src=hash" target="_blank" rel="noopener noreferrer">#fintech</a> family photo. Congrats all <a href="https://twitter.com/hashtag/winners?src=hash" target="_blank" rel="noopener noreferrer">#winners</a><a href="https://twitter.com/hashtag/finnieawards?src=hash" target="_blank" rel="noopener noreferrer">#finnieawards</a><a href="https://twitter.com/JobsforNSW" target="_blank" rel="noopener noreferrer">@JobsforNSW</a><a href="https://t.co/TpIJRj1qyV" target="_blank" rel="noopener noreferrer">pic.twitter.com/TpIJRj1qyV</a>*
<em>— FinTech Australia (@ausfintech) <a href="https://twitter.com/ausfintech/status/867338379725230080" target="_blank" rel="noopener noreferrer">May 24, 2017</a></em></p>
</blockquote>
<p>For you developers out there, check out the <a href="https://portfolio.sharesight.com/api" target="_blank" rel="noopener noreferrer">Sharesight API</a>.</p>
<p>Sharesight thanksFinTech Australia for organising these awards, and for providing a community for Australian fintechs to learn and collaborate.</p>
<p><em>Originally published at <a href="https://www.sharesight.com/blog/sharesight-wins-2017-finnies-award-for-excellence-in-industry-collaboration-partnerships/" target="_blank" rel="noopener noreferrer">www.sharesight.com</a>.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>AED — Find it, use it⚡💓</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3>Using a defibrillator is super simple! Harry from USL Medical gave us an introduction to our AED, and we’ve added our location so the public can find it using the AED Locations app.</h3>
<ol>
<li>
<p>Turn it on</p>
</li>
<li>
<p>Follow prompts</p>
</li>
<li>
<p>Deliver shock if prompted</p>
</li>
</ol>
<p>Go to AED Locations NZ to see your nearest defibrillators.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kpmqu1tq2mcoa6euu6aschq1" alt="" data-big=https://cms-assets.abletech.nz/large_1_A21_H2_Gf_Hcp_Yfytj_Bz_6cv_Q_7025fec33f.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_A21_H2_Gf_Hcp_Yfytj_Bz_6cv_Q_7025fec33f.png" srcset="https://cms-assets.abletech.nz/large_1_A21_H2_Gf_Hcp_Yfytj_Bz_6cv_Q_7025fec33f.png 1000w, https://cms-assets.abletech.nz/small_1_A21_H2_Gf_Hcp_Yfytj_Bz_6cv_Q_7025fec33f.png 500w, https://cms-assets.abletech.nz/medium_1_A21_H2_Gf_Hcp_Yfytj_Bz_6cv_Q_7025fec33f.png 750w, https://cms-assets.abletech.nz/thumbnail_1_A21_H2_Gf_Hcp_Yfytj_Bz_6cv_Q_7025fec33f.png 245w" data-zooming-width="1000" data-zooming-height="502" loading="lazy" width="1000" height="502"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wa4j9w07pjr6y5t2a2z3vei9" alt="" data-big=https://cms-assets.abletech.nz/large_1_Be_Ji97_Ay_Dk_B0_BJ_258_Gm0j_Q_27e907ba30.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 237px" src="https://cms-assets.abletech.nz/1_Be_Ji97_Ay_Dk_B0_BJ_258_Gm0j_Q_27e907ba30.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Be_Ji97_Ay_Dk_B0_BJ_258_Gm0j_Q_27e907ba30.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Be_Ji97_Ay_Dk_B0_BJ_258_Gm0j_Q_27e907ba30.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Be_Ji97_Ay_Dk_B0_BJ_258_Gm0j_Q_27e907ba30.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Be_Ji97_Ay_Dk_B0_BJ_258_Gm0j_Q_27e907ba30.jpeg 237w" data-zooming-width="1000" data-zooming-height="658" loading="lazy" width="1000" height="658"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qm0i2jj62r6gkx39375rsndb" alt="" data-big=https://cms-assets.abletech.nz/large_17v_UD_1_D3zo8l_Uk8_Ct_JC_Zty_A_159139dd50.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/17v_UD_1_D3zo8l_Uk8_Ct_JC_Zty_A_159139dd50.jpeg" srcset="https://cms-assets.abletech.nz/large_17v_UD_1_D3zo8l_Uk8_Ct_JC_Zty_A_159139dd50.jpeg 1000w, https://cms-assets.abletech.nz/small_17v_UD_1_D3zo8l_Uk8_Ct_JC_Zty_A_159139dd50.jpeg 500w, https://cms-assets.abletech.nz/medium_17v_UD_1_D3zo8l_Uk8_Ct_JC_Zty_A_159139dd50.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_17v_UD_1_D3zo8l_Uk8_Ct_JC_Zty_A_159139dd50.jpeg 245w" data-zooming-width="1000" data-zooming-height="532" loading="lazy" width="1000" height="532"></figure>
</div>
<h3>Where will you be when you’re a bystander to a heart attack?</h3>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">AED Locations</a> will guide you to your nearest defibrillators.</p>
<ul>
<li>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">Check out the website</a></p>
</li>
<li>
<p><a href="https://itunes.apple.com/nz/app/aed-locations/id424094430?mt=8" target="_blank" rel="noopener noreferrer">Download the AED Locations app from iTunes</a></p>
</li>
<li>
<p><a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations" target="_blank" rel="noopener noreferrer">Download the AED Locations app from Google Play</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Learning and supporting new technology</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Keeping up with new technology is important. From time to time we see technology emerge that really excites us. Ruby on Rails was once a brand new web application framework. We reckon Elixir could track along a similar path to success.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bfatb7g6hhm2nd8kcfnj5xwc" alt="" data-big=https://cms-assets.abletech.nz/large_1_Lvg_K_Vq_Ioxs_OH_13_N1_Olt0w_Q_ac49c430a5.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 239px" src="https://cms-assets.abletech.nz/1_Lvg_K_Vq_Ioxs_OH_13_N1_Olt0w_Q_ac49c430a5.png" srcset="https://cms-assets.abletech.nz/large_1_Lvg_K_Vq_Ioxs_OH_13_N1_Olt0w_Q_ac49c430a5.png 1000w, https://cms-assets.abletech.nz/small_1_Lvg_K_Vq_Ioxs_OH_13_N1_Olt0w_Q_ac49c430a5.png 500w, https://cms-assets.abletech.nz/medium_1_Lvg_K_Vq_Ioxs_OH_13_N1_Olt0w_Q_ac49c430a5.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Lvg_K_Vq_Ioxs_OH_13_N1_Olt0w_Q_ac49c430a5.png 239w" data-zooming-width="1000" data-zooming-height="651" loading="lazy" width="1000" height="651"></figure>
</div>
<p>In 2006 Abletecher Michael Koziarski gave a popular overview of Ruby on Rails at a Wellington Webstock conference. That same year he also spoke at RailsConf Europe in London. Ruby on Rails has continued to grow and Abletechers, keen on the efficient technology, have grown in their knowledge and practical implementation of it.</p>
<p>Over the years, Abletechers have met with other Rails enthusiasts on a regular basis. A local Wellington Rails user group formed, called Well Railed. There have been regular camps, meetups, community events, conferences, workshops and hackdays.</p>
<p>In 2010 Rails Girls was founded and it’s become an excellent entry-point for women interested in Ruby on Rails. It’s exciting to see <a href="http://railsgirls.com/wellington.html" target="_blank" rel="noopener noreferrer">Rails Girls</a> offering training in a casual, supportive and friendly atmosphere.</p>
<p>In October 2010 Abletech ran an intense three day training course: Speed Rails. By July 2012 Abletech was running a Ruby on Rails bootcamp for Summer of Tech.</p>
<p>At Abletech we still use Ruby on Rails; it continues to work well with agile development techniques.</p>
<p>Just as we were interested in Ruby on Rails right from the beginning, we still keep a keen eye on new technologies that have the potential to make development more efficient. To this end we’ve been looking at a more recent technology, Elixir, that looks like it also has the potential to make ground-breaking advancements in software development. It’s an exciting time to be a developer!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Sharesight in Trade Delegation</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3>Sharesight in London</h3>
<p>Sharesight have joined a group of ambitious fintech startups chosen to make a trip to London. It’s great news for the successful share portfolio tracker company.</p>
<p>At Abletech we’re proud to have been associated with Sharesight from the very beginning.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vqnrasnqoux3vjnpyyb7zjn1" alt="" data-big=https://cms-assets.abletech.nz/medium_1_li_ATV_9j_Tm_Fq_C_Oba6_P_Mf_QA_e5f9435fad.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 239px" src="https://cms-assets.abletech.nz/1_li_ATV_9j_Tm_Fq_C_Oba6_P_Mf_QA_e5f9435fad.png" srcset="https://cms-assets.abletech.nz/small_1_li_ATV_9j_Tm_Fq_C_Oba6_P_Mf_QA_e5f9435fad.png 500w, https://cms-assets.abletech.nz/medium_1_li_ATV_9j_Tm_Fq_C_Oba6_P_Mf_QA_e5f9435fad.png 750w, https://cms-assets.abletech.nz/thumbnail_1_li_ATV_9j_Tm_Fq_C_Oba6_P_Mf_QA_e5f9435fad.png 239w" data-zooming-width="750" data-zooming-height="491" loading="lazy" width="750" height="491"></figure>
</div>
<p>Abletech directors, Marcus and Nigel, built Sharesight in 2007. They partnered with father-son team, Tony and Scott Ryburn, to bootstrap and develop the popular software. Sharesight is now enjoying success as an independent investment utility.</p>
<blockquote>
<h4>Congratulations Sharesight team — leading the way with a vision to scale globally.</h4>
</blockquote>
<p>This week the Australian Financial Review featured an article detailing the trip, who’s going and what’s involved. It’s a reciprocal visit; UK fintech startups came down-under in March.</p>
<ul>
<li>
<p>Read <a href="https://www.sharesight.com/blog/sharesight-wins-spot-on-british-australian-fintech-forum/" target="_blank" rel="noopener noreferrer">Sharesight’s blog</a> about being awarded the spot on the <a href="https://www.britishchamber.com/event/british-australian-fintech-forum?_ga=1.193078191.30278057.1492491847" target="_blank" rel="noopener noreferrer">British Australian Fintech Forum</a>.</p>
</li>
<li>
<p>Read the <a href="http://www.afr.com/technology/aussie-fintechs-forge-closer-ties-with-london-20170411-gvibb2#ixzz4eYJNhFYN" target="_blank" rel="noopener noreferrer">AFR article</a> and check out what all the fuss is about at <a href="https://www.sharesight.com" target="_blank" rel="noopener noreferrer">Sharesight</a>’s website.</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>From Idea to App</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Follow the life-cycle of a good idea as it develops into seriously good technology. What does it take? Here’s the inside scoop on a life-changer.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nno64t20p95z79ukqqbcqbub" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_08_W8_C_Ckxxq_H9_Dgr57_c11a754ca2.png sizes="(min-width: 640px) 239px" src="https://cms-assets.abletech.nz/08_W8_C_Ckxxq_H9_Dgr57_c11a754ca2.png" srcset="https://cms-assets.abletech.nz/thumbnail_08_W8_C_Ckxxq_H9_Dgr57_c11a754ca2.png 239w" data-zooming-width="239" data-zooming-height="156" loading="lazy" width="239" height="156"></figure>
</div>
<p>Recently a woman in Stoke went into cardiac arrest. A police officer used his AED Locator app to find the closest defibrillator. Nelson Bays Area Commander Inspector Mat Arnold-Kelly said the <a href="http://www.stuff.co.nz/national/74271633/phone-app-defibrillator-quickthinking-saves-woman-in-cardiac-arrest" target="_blank" rel="noopener noreferrer">situation</a> was urgent.</p>
<p><em>“The officers used an app on their smartphones to locate the nearest defibrillator, which was at a local supermarket. Thankfully they were able to get the defibrillator and successfully re-started the woman’s heart.”</em></p>
<p>At last count there were over 5000 AED locations in the New Zealand database. The free iPhone and Android apps will quickly tell you the closest defibrillators.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wt6v4wvsiafdu2vlnf3qf4gy" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0n_HN_7t_M_Xhkoiy22t_8d19d352b1.jpg sizes="(min-width: 640px) 239px" src="https://cms-assets.abletech.nz/0n_HN_7t_M_Xhkoiy22t_8d19d352b1.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0n_HN_7t_M_Xhkoiy22t_8d19d352b1.jpg 239w" data-zooming-width="239" data-zooming-height="156" loading="lazy" width="239" height="156"></figure>
</div>
<p><strong>First the good idea</strong></p>
<p>Gareth Jenkin, a Paramedic and Resuscitation Trainer, had a good idea. He was training thousands of people how to save lives using an AED (Automatic External Defibrillator) but he could not tell his students where to find one.</p>
<p><em>Gareth’s good idea: “put defibrillator locations online”.</em></p>
<p><strong>Let’s recap</strong></p>
<p>Gareth had identified the problem: AEDs sat idle as people died nearby. He was motivated not by profit but by the impact a national database could have on the problem. Gareth had identified the need but he had no money or knowledge to build the solution.</p>
<p><em>“I didn’t have the money or the technical know-how,” Gareth Jenkin.</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dfguv6lol6aiyz7yigl1vzd6" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_V1rwb_Xou_ZAC_67_Lj_Q_62c6a535e4.jpg sizes="(min-width: 640px) 239px" src="https://cms-assets.abletech.nz/0_V1rwb_Xou_ZAC_67_Lj_Q_62c6a535e4.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_V1rwb_Xou_ZAC_67_Lj_Q_62c6a535e4.jpg 239w" data-zooming-width="239" data-zooming-height="156" loading="lazy" width="239" height="156"></figure>
</div>
<p><strong>Defining the possibilities</strong></p>
<p>In November 2010, Abletech heard about Gareth’s vision via a New Zealand Listener magazine article. Abletech approached Gareth and made an offer of web and mobile services at no charge.</p>
<p>Gareth and Abletech worked together to define what was possible. They talked about the idea and the problem. They analysed what was required and how the solution would work. Next they designed and built the web and mobile apps, keeping each other in the loop as the solution developed.</p>
<p><strong>The solution to the problem</strong></p>
<p>The website built by <a href="https://abletech.nz/">Abletech</a>, is the hub of AED Locations. New AED locations are mapped online every day. Gareth uses <a href="http://www.mapmysites.com/" target="_blank" rel="noopener noreferrer">Mapmysites</a> to update the data for the AED Locations <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">website</a>. This automatically updates the <a href="https://itunes.apple.com/nz/app/aed-locations/id424094430?mt=8&amp;ign-mpt=uo%3D4" target="_blank" rel="noopener noreferrer">iPhone</a> app and the <a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations" target="_blank" rel="noopener noreferrer">Android app</a>. All the technology, including the hosting, has been provided by Abletech.</p>
<p>*It was a good idea and we could help, plain and simple
*Carl Penwarden, Abletech.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k021119c93dwydu4qoyqo2id" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Q_Rg8_H3_F6fp5mrsdw_a375cfc081.png sizes="(min-width: 640px) 220px" src="https://cms-assets.abletech.nz/0_Q_Rg8_H3_F6fp5mrsdw_a375cfc081.png" srcset="https://cms-assets.abletech.nz/thumbnail_0_Q_Rg8_H3_F6fp5mrsdw_a375cfc081.png 220w" data-zooming-width="220" data-zooming-height="156" loading="lazy" width="220" height="156"></figure>
</div>
<p>Gareth likens AED Locations to a lighthouse, “nobody pays for it, but it keeps people safer”. He says that Abletech has been the back-bone of the project. Abletech say the success of AED Locations is in Gareth’s determination and maintenance of the data. “He does the hard work. He tirelessly promotes AED Locations and encourages owners to list their AED locations,” says Carl Penwarden, Managing Director of Abletech.</p>
<p><strong>Ongoing maintenance</strong></p>
<p>Gareth explains that all police have Apple devices, iPads and iPhones, and these all have the AED Locations app on them. In addition to adding new AED locations daily, Gareth promotes the <a href="https://wellington.govt.nz/about-wellington/mobile-apps" target="_blank" rel="noopener noreferrer">use</a> of the app. <a href="http://www.stuff.co.nz/marlborough-express/news/10708744/Phone-app-finds-defibrillators" target="_blank" rel="noopener noreferrer">Trainers</a> promote the app. Ambulance services use the service to direct bystanders to AEDs. AED suppliers can invite new owners to add their AED. Gareth sends out stickers to people who register their unit to <a href="https://www.odt.co.nz/news/dunedin/defibrillators-easy-find-phone" target="_blank" rel="noopener noreferrer">increase the visibility</a> of both the AED and the website.</p>
<p><strong>Seriously good</strong></p>
<p>Gareth says he’s stubborn. He doesn’t sell AEDs or work for a national organisation. Money is not his motivation; he says he does all this for love. Gareth uses <a href="https://www.facebook.com/AED-Locations-NZ-208562969165864/" target="_blank" rel="noopener noreferrer">Facebook</a> for media feeds and research and if people want to advertise through posts they can. This keeps AED discourse live and current but allows <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">AED Locations</a> to be clear and uncluttered.</p>
<p>Since 2010 Gareth’s nurtured his good idea into a quick, searchable national database and it’s making a real difference. A good idea and good technology, combining for the public good.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q4kx4swfq9rjvyvzk2h4ojmj" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0cv5_Jzb_R2_Vs3aa_ON_1ad7e2febb.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0cv5_Jzb_R2_Vs3aa_ON_1ad7e2febb.png" srcset="https://cms-assets.abletech.nz/thumbnail_0cv5_Jzb_R2_Vs3aa_ON_1ad7e2febb.png 245w" data-zooming-width="245" data-zooming-height="124" loading="lazy" width="245" height="124"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Cardiac arrest</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>NZ’s biggest single killer is heart disease claiming 6300 lives a year</h2>
<p>The latest New Zealand Listener highlights recent research into heart disease.</p>
<p>It was a Listener article, back in 2010, that prompted Abletech to get involved with Gareth Jenkins in building the <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">AED Locations</a> website and app that now maps over 11,000 defibrillators.</p>
<p>With around five people a day suffering cardiac arrests in the community, we’re glad we did.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e20fe2iz3d1ljg6hle4nzv91" alt="" data-big=https://cms-assets.abletech.nz/medium_1_Npzc_J36_HDL_7q015uz_LVX_8w_1a1a168f89.png sizes="(min-width: 768px) 373px, (min-width: 1024px) 560px, (min-width: 640px) 116px" src="https://cms-assets.abletech.nz/1_Npzc_J36_HDL_7q015uz_LVX_8w_1a1a168f89.png" srcset="https://cms-assets.abletech.nz/small_1_Npzc_J36_HDL_7q015uz_LVX_8w_1a1a168f89.png 373w, https://cms-assets.abletech.nz/medium_1_Npzc_J36_HDL_7q015uz_LVX_8w_1a1a168f89.png 560w, https://cms-assets.abletech.nz/thumbnail_1_Npzc_J36_HDL_7q015uz_LVX_8w_1a1a168f89.png 116w" data-zooming-width="560" data-zooming-height="750" loading="lazy" width="560" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="af43rrorqb1r3f9a2oe06v7l" alt="" data-big=https://cms-assets.abletech.nz/large_1l_D2kc_TXTD_Bhk_Tk_Xg_H_Ly3aw_95a95a1455.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 226px" src="https://cms-assets.abletech.nz/1l_D2kc_TXTD_Bhk_Tk_Xg_H_Ly3aw_95a95a1455.jpeg" srcset="https://cms-assets.abletech.nz/large_1l_D2kc_TXTD_Bhk_Tk_Xg_H_Ly3aw_95a95a1455.jpeg 1000w, https://cms-assets.abletech.nz/small_1l_D2kc_TXTD_Bhk_Tk_Xg_H_Ly3aw_95a95a1455.jpeg 500w, https://cms-assets.abletech.nz/medium_1l_D2kc_TXTD_Bhk_Tk_Xg_H_Ly3aw_95a95a1455.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1l_D2kc_TXTD_Bhk_Tk_Xg_H_Ly3aw_95a95a1455.jpeg 226w" data-zooming-width="1000" data-zooming-height="692" loading="lazy" width="1000" height="692"></figure>
</div>
<h3>Read more:</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/%EF%B8%8Fcould-you-restart-a-heart-right-now"><strong>❤️ Could you restart a heart right now?</strong> <em>Where is your nearest AED? There’s an app for that ⚡</em></a></p>
</li>
<li>
<p><a href="https://abletech.nz/portfolio/built-in-wellington-used-in-wellington"><strong>Built in Wellington, used in Wellington</strong> <em>Man seen stumbling in CBD — defibrillator finding app crucial in saving him</em></a></p>
</li>
<li>
<p><a href="https://abletech.nz/portfolio/aed-find-it-use-it"><strong>AED — Find it, use it ⚡ 💓</strong> <em>Using a defibrillator is super simple! Harry from USL Medical gave us an introduction to our AED, and we’ve added our…</em></a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/aed-locations-video/"><strong>AED Locations video</strong> <em>There’s one at Lotto NZ, the Auckland Zoo, Fonterra in Longburn, the Beehive, Kaikoura High School, Balclutha Bowling…</em></a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Custom software development services</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>AddressFinder: location data intelligence and geocoded SaaS</h2>
<p>Custom software is designed for a specific purpose, user or organisation. Read about our Abletech SaaS (Software as a Service) product AddressFinder.</p>
<p><a href="https://abletech.nz/">Abletech</a> built <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">AddressFinder</a> to make online shopping smoother, easier and faster. It simplifies the process of collecting and verifying addresses for everyone from florists with deliveries, to insurance companies with large databases.</p>
<p>The popular cloud-based address verification product has a growing customer base in New Zealand and Australia. Developers use the smart software for auto-completes in online check-outs, spreadsheets and APIs. AddressFinder receives five-star reviews in developer circles and is highly regarded for data quality and reliability.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="aeadelqcatkyc6234pqy4x1m" alt="" data-big=https://cms-assets.abletech.nz/small_0_RYVK_Jw_As_NZ_32xfrh_b2be5b58ae.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_RYVK_Jw_As_NZ_32xfrh_b2be5b58ae.png" srcset="https://cms-assets.abletech.nz/small_0_RYVK_Jw_As_NZ_32xfrh_b2be5b58ae.png 500w, https://cms-assets.abletech.nz/thumbnail_0_RYVK_Jw_As_NZ_32xfrh_b2be5b58ae.png 245w" data-zooming-width="500" data-zooming-height="289" loading="lazy" width="500" height="289"></figure>
</div>
<p><em>Software as a Service — AddressFinder</em></p>
<p>Over the years, online shopping has seen exponential growth. As e-commerce has boomed, businesses have needed a quality predictive address autocomplete service, and organisations have needed trustworthy address validation for identity checks and address list verification.</p>
<h3><strong>The challenge</strong></h3>
<p>Online checkouts were slow and frustrating. Typing mistakes were common. Misdeliveries were frequent and expensive. Address errors caused problems for users, suppliers, couriers, returners, senders. This had a significant impact on budgets, brand reputation and revenue. Due diligence checks were required for customers. The software solution would need to be reliable, accurate and fast. Abletech knew how to help.</p>
<h3><strong>The Abletech difference</strong></h3>
<p>In addition to building <a href="https://abletech.nz/services/development/">custom software solutions</a> for clients, Abletech also develops its own Software as a Service (SaaS) products. AddressFinder is one of these.</p>
<p>Abletech acquired, developed, nurtured and scaled AddressFinder into a service trusted by companies throughout NZ and Australia. If you’ve shopped online, you’ve probably used AddressFinder. It’s a secure and reliable address verification solution.</p>
<p>Nigel Ramsay, Technical Director at Abletech, chose Ruby on Rails for AddressFinder’s development.</p>
<blockquote>
<h4>The beauty of building or rebuilding software is that you can choose the best available tools. We knew what would be best and we built AddressFinder to be here for the long haul</h4>
</blockquote>
<p>AddressFinder combines two of Abletech’s strengths: business and geospatial tech.</p>
<p>It’s user friendly. Developers enjoy the experience of integrating AddressFinder with popular <a href="https://addressfinder.nz/features/#integrations" target="_blank" rel="noopener noreferrer">e-commerce platforms</a> like WooCommerce, Shopify and Magento. They appreciate the easy API and widget documentation. In addition the friendly AddressFinder support team is on the end of the phone if ever needed.</p>
<blockquote>
<h4>Part of what made the implementation stress-free was the great developer documentation</h4>
</blockquote>
<p><em>— Jamie Toy, Chief Information Officer at Cove Insurance</em></p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cpqq7xu18cwoemvcs8nd13zz" alt="" data-big=https://cms-assets.abletech.nz/large_1x4eth_K8_Kroq4_S_Dp_CG_Qg_Hg_A_c465fcbba8.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/1x4eth_K8_Kroq4_S_Dp_CG_Qg_Hg_A_c465fcbba8.jpeg" srcset="https://cms-assets.abletech.nz/large_1x4eth_K8_Kroq4_S_Dp_CG_Qg_Hg_A_c465fcbba8.jpeg 1000w, https://cms-assets.abletech.nz/small_1x4eth_K8_Kroq4_S_Dp_CG_Qg_Hg_A_c465fcbba8.jpeg 500w, https://cms-assets.abletech.nz/medium_1x4eth_K8_Kroq4_S_Dp_CG_Qg_Hg_A_c465fcbba8.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1x4eth_K8_Kroq4_S_Dp_CG_Qg_Hg_A_c465fcbba8.jpeg 234w" data-zooming-width="1000" data-zooming-height="667" loading="lazy" width="1000" height="667"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ob8q2tao6v22nutwhvewi5hu" alt="" data-big=https://cms-assets.abletech.nz/medium_1sq_KF_Mxd35_Ldo5s7_SR_Bge5g_cff2a23f6c.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 229px" src="https://cms-assets.abletech.nz/1sq_KF_Mxd35_Ldo5s7_SR_Bge5g_cff2a23f6c.png" srcset="https://cms-assets.abletech.nz/small_1sq_KF_Mxd35_Ldo5s7_SR_Bge5g_cff2a23f6c.png 500w, https://cms-assets.abletech.nz/medium_1sq_KF_Mxd35_Ldo5s7_SR_Bge5g_cff2a23f6c.png 750w, https://cms-assets.abletech.nz/thumbnail_1sq_KF_Mxd35_Ldo5s7_SR_Bge5g_cff2a23f6c.png 229w" data-zooming-width="750" data-zooming-height="511" loading="lazy" width="750" height="511"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ec7bab6ktp7ik0adkguo1xru" alt="" data-big=https://cms-assets.abletech.nz/large_11_G5_J_UEDC_0i_C_to_Ln_G_Ag_8cfa432916.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/11_G5_J_UEDC_0i_C_to_Ln_G_Ag_8cfa432916.jpeg" srcset="https://cms-assets.abletech.nz/large_11_G5_J_UEDC_0i_C_to_Ln_G_Ag_8cfa432916.jpeg 1000w, https://cms-assets.abletech.nz/small_11_G5_J_UEDC_0i_C_to_Ln_G_Ag_8cfa432916.jpeg 500w, https://cms-assets.abletech.nz/medium_11_G5_J_UEDC_0i_C_to_Ln_G_Ag_8cfa432916.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_11_G5_J_UEDC_0i_C_to_Ln_G_Ag_8cfa432916.jpeg 234w" data-zooming-width="1000" data-zooming-height="667" loading="lazy" width="1000" height="667"></figure>
</div>
<h3><strong>The result</strong></h3>
<p>The carefully chosen technology behind AddressFinder makes it robust for businesses and sustainable for developers. AddressFinder is known for high quality data, reliable service and excellent system security. Financial services needing address verification for customer due diligence use it to streamline their legal requirements.</p>
<blockquote>
<h4>It’s really important for us to have a valid address for each customer. As a financial service we need to ensure our customers are actually who they claim to be</h4>
</blockquote>
<p><em>— Dan Silver, Chief Operating Officer at digital brokerage service Stake</em></p>
<p>AddressFinder customers trust it again and again. It’s mobile-friendly, makes checkouts fast, gives online stores valid deliverable addresses, and gives their users a great experience.</p>
<blockquote>
<h4>AddressFinder helps make our website intuitive and easy to use</h4>
</blockquote>
<p><em>— Liam McCarroll, Technical Lead at BetterBatt</em></p>
<p>Abletech’s development of AddressFinder was born from a desire to offer a quality address solution. It continues to offer quality data, a secure system and great service. Since 2010 AddressFinder has enjoyed impressive growth.</p>
<p>Watch this space for further international expansion.</p>
<h3>Custom software development services</h3>
<p>Say hello to Abletech! We’d love to chat with you about bespoke software designed for a specific purpose, user or organisation: hello@abletech.nz</p>
<h3>Read more</h3>
<ul>
<li>
<p><a href="https://abletech.nz/portfolio/crs-farm-management-software">Farm management software</a></p>
</li>
<li>
<p><a href="https://abletech.nz/portfolio/bdo-gisborne-accounting-and-financial-reporting">Accounting and financial reporting</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Built in Wellington, used in Wellington</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Man seen stumbling in CBD — defibrillator finding app crucial in saving him</h2>
<p>Our app showing AED locations nationwide saved a local life this week. Well done to Alea for using the app to find her nearest defibrillator. Great to see so many AEDs available in the Cuba Mall area:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="spyhisbcdx83hc7ho85bhsxs" alt="Click this image to find your nearest AEDs" data-big=https://cms-assets.abletech.nz/large_1_Neqwl_Otoye_KK_7_L_Cr_Zm_F29_Q_b7bd58bd1c.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Neqwl_Otoye_KK_7_L_Cr_Zm_F29_Q_b7bd58bd1c.png" srcset="https://cms-assets.abletech.nz/large_1_Neqwl_Otoye_KK_7_L_Cr_Zm_F29_Q_b7bd58bd1c.png 1000w, https://cms-assets.abletech.nz/small_1_Neqwl_Otoye_KK_7_L_Cr_Zm_F29_Q_b7bd58bd1c.png 500w, https://cms-assets.abletech.nz/medium_1_Neqwl_Otoye_KK_7_L_Cr_Zm_F29_Q_b7bd58bd1c.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Neqwl_Otoye_KK_7_L_Cr_Zm_F29_Q_b7bd58bd1c.png 245w" data-zooming-width="1000" data-zooming-height="627" loading="lazy" width="1000" height="627"></figure>
</div>
<p><em>Click this image to find your nearest AEDs</em></p>
<p>Quick thinking Body Shop worker Alea Wharekura used the AED finding app on her phone to locate a portable defibrillator, which was crucial in saving the man’s life.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dk9nonjy6a67zfcvefb83m90" alt="Alea Wharekura has the AED Locations app on her phone" data-big=https://cms-assets.abletech.nz/small_02_Hra_Pm_Qi9l6p8_Y_Om_97ebb5221e.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/02_Hra_Pm_Qi9l6p8_Y_Om_97ebb5221e.jpg" srcset="https://cms-assets.abletech.nz/small_02_Hra_Pm_Qi9l6p8_Y_Om_97ebb5221e.jpg 500w, https://cms-assets.abletech.nz/thumbnail_02_Hra_Pm_Qi9l6p8_Y_Om_97ebb5221e.jpg 245w" data-zooming-width="500" data-zooming-height="282" loading="lazy" width="500" height="282"></figure>
</div>
<p><em>Alea Wharekura has the AED Locations app on her phone</em></p>
<p>Technology was crucial in saving a man’s life after he was seen stumbling and sickly down a busy Wellington street.</p>
<p>By using a defibrillator locating app developed in New Zealand — and CPR — a group of medical students, shopkeepers, and emergency services worked together and helped save the 67-year-old’s life after he was spotted by Platypus Shoes staff members weaving past the Manners St shop in the CBD on Thursday afternoon.</p>
<p>Quick thinking Body Shop worker Alea Wharekura used the AED finding app on her phone to locate a portable defibrillator upstairs from Platypus Shoes in the old James Smiths building. The machine and CPR were used to save the man — thought to have had a cardiac arrest — who had become unresponsive near the Manners St shops</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uib0ceb5o7nbpagl4kah0f6a" alt="" data-big=https://cms-assets.abletech.nz/small_0_Q3_HWLG_Lyfc_B_Gbj_QR_2342be0f03.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Q3_HWLG_Lyfc_B_Gbj_QR_2342be0f03.jpg" srcset="https://cms-assets.abletech.nz/small_0_Q3_HWLG_Lyfc_B_Gbj_QR_2342be0f03.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0_Q3_HWLG_Lyfc_B_Gbj_QR_2342be0f03.jpg 245w" data-zooming-width="500" data-zooming-height="282" loading="lazy" width="500" height="282"></figure>
</div>
<p>This group of medical students — pictured with Constable Sandy Cumpstone — leapt into action and performed CPR on the man.</p>
<p>Wharekura had the app because she had just done a first aid course and said the technology was invaluable as many people didn’t know how many of the life-saving devices there were around.</p>
<p><strong>READ MORE</strong>:</p>
<ul>
<li><a href="https://www.stuff.co.nz/national/health/82920198/saving-lives-with-kiwi-defibrillator-locater" target="_blank" rel="noopener noreferrer">Saving lives with Kiwi defibrillator locater</a></li>
<li><a href="https://www.stuff.co.nz/national/health/82920198/saving-lives-with-kiwi-defibrillator-locater" target="_blank" rel="noopener noreferrer">Bar owner wants defibrillators brought out of hiding after man dies</a></li>
<li><a href="https://www.stuff.co.nz/national/health/93751455/aeds-should-be-more-accessible-in-nelson-cardiologist-says?rm=m" target="_blank" rel="noopener noreferrer">Lifesaving AEDs: Know your nearest</a></li>
<li><a href="https://www.stuff.co.nz/dominion-post/news/local-papers/the-wellingtonian/83031132/stop-hiding-defibrillators-founder-of-national-aed-database-says?rm=m" target="_blank" rel="noopener noreferrer">Stop hiding defibrillators founder of national AED database says</a></li>
</ul>
<p>The AED Locations site and app link to Google Maps so users can search for the closest defibrillator.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="z8ogaqkugr130rly5xcfzrpn" alt="" data-big=https://cms-assets.abletech.nz/small_0y9_Nl_COOVQ_3w_Jmned_a661f220cd.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0y9_Nl_COOVQ_3w_Jmned_a661f220cd.jpg" srcset="https://cms-assets.abletech.nz/small_0y9_Nl_COOVQ_3w_Jmned_a661f220cd.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0y9_Nl_COOVQ_3w_Jmned_a661f220cd.jpg 245w" data-zooming-width="500" data-zooming-height="282" loading="lazy" width="500" height="282"></figure>
</div>
<p>Platypus Shoes workers, from left, Sam Barnes, Laura Sutherland-Hardy, Paris Evans and Jacob Williams were part of an impromptu first response unit who helped save a man.</p>
<p>Platypus worker Paris Evans said he and other staff members had noticed the man leaning slumped in the doorway of the shop gasping for air before collapsing, turning purple and becoming unresponsive.</p>
<p>He was laid on the ground as one of a group of medical students began doing CPR — the defibrillator arrived and Evans let the device instruct him as CPR continued.</p>
<p>“We would have been struggling waiting for an ambulance so I highly recommend [defibrillators] for every building,” Evans said.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ym3urd76q8g1e0whq7dyb4ei" alt="" data-big=https://cms-assets.abletech.nz/small_0_Aofsp0_Rjp_Rp_Ht_N_Xh_ec66f847fb.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Aofsp0_Rjp_Rp_Ht_N_Xh_ec66f847fb.jpg" srcset="https://cms-assets.abletech.nz/small_0_Aofsp0_Rjp_Rp_Ht_N_Xh_ec66f847fb.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0_Aofsp0_Rjp_Rp_Ht_N_Xh_ec66f847fb.jpg 245w" data-zooming-width="500" data-zooming-height="282" loading="lazy" width="500" height="282"></figure>
</div>
<p>Kerry Simeon is disappointed nobody reached out to borrow his bar’s defibrillator when a person suffered a cardiac arrest across the road from his New Plymouth pub.</p>
<p>Meanwhile emergency services soon arrived and took over CPR.</p>
<p>Before being taken to hospital the man regained consciousness and was breathing again.</p>
<p>“Although not out of the woods at that point, it was a great sight to see for all involved.”</p>
<p>Police said the man was now doing well and was on the road to recovery.</p>
<p>Late last month New Plymouth bar owner Kerry Simeon said he was frustrated nobody came and got a defibrillator from his business after an 82-year-old died after going into cardiac arrest near the pub.</p>
<p>Simeon is now calling for more businesses to invest in the medical device and for better promotion of where they are available.</p>
<p>According to a website which allows people to register the devices, there are 8971 portable registered defibrillators across the country, including 442 in Wellington city and suburbs.</p>
<p>MATT STEWART</p>
<p>Last updated 13:48, May 31 2018</p>
<p><strong>- Stuff</strong></p>
<p><em>Originally published at <a href="https://www.stuff.co.nz/technology/apps/104355274/defibrillator-locating-app-crucial-in-saving-man-seen-stumbling-down-wellington-street" target="_blank" rel="noopener noreferrer">www.stuff.co.nz</a>.</em></p>
<h3>Where will you be when you’re a bystander to a heart attack?</h3>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">AED Locations</a> will guide you to your nearest defibrillators.</p>
<ul>
<li>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">Check out the website</a></p>
</li>
<li>
<p><a href="https://itunes.apple.com/nz/app/aed-locations/id424094430?mt=8" target="_blank" rel="noopener noreferrer">Download the AED Locations app from iTunes</a></p>
</li>
<li>
<p><a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations" target="_blank" rel="noopener noreferrer">Download the AED Locations app from Google Play</a></p>
</li>
<li>
<p>Read about the <a href="https://abletech.nz/article/sam-spends-summer-streamlining-%ef%b8%8f%ef%b8%8fsite/">development of AED Locations</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Bar owner wants defibrillators brought out of hiding after man dies</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>A person who died after going into cardiac arrest may have had a better chance of survival had someone run across the road to the pub.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zor1m6791hou1n9795754l9u" alt="Kerry Simeon" data-big=https://cms-assets.abletech.nz/small_0f_SJ_7_Zd_XJT_763b_Zu8_a719fe8ff4.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0f_SJ_7_Zd_XJT_763b_Zu8_a719fe8ff4.jpg" srcset="https://cms-assets.abletech.nz/small_0f_SJ_7_Zd_XJT_763b_Zu8_a719fe8ff4.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0f_SJ_7_Zd_XJT_763b_Zu8_a719fe8ff4.jpg 245w" data-zooming-width="500" data-zooming-height="282" loading="lazy" width="500" height="282"></figure>
</div>
<p><em>Kerry Simeon</em></p>
<p>Crowded House Bar and Eatery co-owner Kerry Simeon wants people to know there is a defibrillator available at the bar and believes more businesses should also have them.</p>
<p>New Plymouth Crowded House Bar and Eatery co-owner Kerry Simeon has a defibrillator on his Devon St East premises and is speaking out after an elderly man collapsed on the street outside Aromas Coffee Lounge on Monday.</p>
<p>“I was a little frustrated why nobody came to us when this person collapsed,” said the publican, who had previously let neighbouring businesses know it was available in an emergency.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tam3c52pggmrv07o06vxcs2z" alt="" data-big=https://cms-assets.abletech.nz/small_0t_Ry9_Nt_Iy_QZL_Lg_Xcp_e572447c6d.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0t_Ry9_Nt_Iy_QZL_Lg_Xcp_e572447c6d.jpg" srcset="https://cms-assets.abletech.nz/small_0t_Ry9_Nt_Iy_QZL_Lg_Xcp_e572447c6d.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0t_Ry9_Nt_Iy_QZL_Lg_Xcp_e572447c6d.jpg 245w" data-zooming-width="500" data-zooming-height="282" loading="lazy" width="500" height="282"></figure>
</div>
<p>Kerry Simeon is disappointed nobody reached out to borrow his bar’s defibrillator when a person suffered a cardiac arrest across the road last week.</p>
<p>“I could have gone over there and helped before the ambulance arrived.”</p>
<p><strong>READ MORE</strong>:</p>
<ul>
<li><a href="https://www.stuff.co.nz/national/health/93751455/aeds-should-be-more-accessible-in-nelson-cardiologist-says" target="_blank" rel="noopener noreferrer">Lifesaving AEDs: Know your nearest</a></li>
<li><a href="http://www.stuff.co.nz/timaru-herald/opinion/73884587/editorial-aed-campaign-pays-off" target="_blank" rel="noopener noreferrer">AED campaign pays off</a></li>
<li><a href="http://www.stuff.co.nz/national/health/82920198/saving-lives-with-kiwi-defibrillator-locater" target="_blank" rel="noopener noreferrer">Kiwis saving lives with defibrillators </a></li>
<li><a href="http://www.stuff.co.nz/dominion-post/news/local-papers/the-wellingtonian/83031132/stop-hiding-defibrillators-founder-of-national-aed-database-says" target="_blank" rel="noopener noreferrer">Stop hiding defibrillators</a></li>
</ul>
<p>Without knowing the exact circumstances of the medical incident, Simeon believed being initially treated with a defibrillator could have made all the difference between life and death.</p>
<p>The 82-year-old was transported to Taranaki Base Hospital in a critical condition and later died.</p>
<p>“It’s left me feeling a bit down,” Simeon said.</p>
<p>An automated external defibrillator, or AED machine, is used in cases of life-threatening cardiac arrhythmias which lead to sudden cardiac arrest.</p>
<p>The portable machine, which has a step-by-step voice prompt, retails for around $3000 but can also be rented through various suppliers, including New Zealand Red Cross.</p>
<p>Aromas Coffee Lounge manager Mandy Berry was not aware the man had collapsed until an ambulance arrived at the scene.</p>
<p>She later learned people had been scrambling around looking for a defibrillator.</p>
<p>“I knew Kerry had one and I wish I had known they were looking for one because I would have gone straight to Crowded House.”</p>
<p>Simeon is now calling for more businesses to invest in the medical device and for better promotion of where they are available.</p>
<p>According to a <a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">website</a> which allows people to register a location of the machine, there are 8800 registered across the country, and New Plymouth has 202 of Taranaki’s 294.</p>
<p>Crowded House’s defibrillator was one of the few registered on the city’s main street.</p>
<p>Simeon said it was purchased about one year ago after three occasions where patrons had suffered a cardiac arrest on the premises — one of which was fatal.</p>
<p>It was yet to be used but all of the bar staff had been trained to operate it.</p>
<p>Simeon said there needed to be more knowledge within the community of where to access the machines and he would be calling into neighbouring stores to remind them of his.</p>
<p>“We got this for Crowded but we’re a worldwide village these days.”</p>
<p>A guide to find AEDs in New Zealand is available at <a href="http://www.aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">aedlocations.co.nz</a> or by downloading the app “AED Locations” on your smartphone from the <a href="https://itunes.apple.com/nz/app/aed-locations/id424094430" target="_blank" rel="noopener noreferrer">App Store</a> or <a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations" target="_blank" rel="noopener noreferrer">Google Play</a>.​</p>
<p><em>-Tara Shaskey, April 30 2018</em></p>
<p><em>Originally published at <a href="https://www.stuff.co.nz/taranaki-daily-news/news/103347423/bar-owner-wants-defibrillators-brought-out-of-hiding-after-man-dies" target="_blank" rel="noopener noreferrer">www.stuff.co.nz</a>.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Elixir for JavaScript and Ruby developers</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Come and hear Abletecher Nigel Ramsay talk about his experiences converting an existing Ruby application to Elixir.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t2vruc08tgh2bllka4g61m7z" alt="" data-big=https://cms-assets.abletech.nz/large_1j_Ahgy_Z9z_Rl8wngf4_J15i_Fg_40df5d4e45.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1j_Ahgy_Z9z_Rl8wngf4_J15i_Fg_40df5d4e45.png" srcset="https://cms-assets.abletech.nz/large_1j_Ahgy_Z9z_Rl8wngf4_J15i_Fg_40df5d4e45.png 1000w, https://cms-assets.abletech.nz/small_1j_Ahgy_Z9z_Rl8wngf4_J15i_Fg_40df5d4e45.png 500w, https://cms-assets.abletech.nz/medium_1j_Ahgy_Z9z_Rl8wngf4_J15i_Fg_40df5d4e45.png 750w, https://cms-assets.abletech.nz/thumbnail_1j_Ahgy_Z9z_Rl8wngf4_J15i_Fg_40df5d4e45.png 245w" data-zooming-width="1000" data-zooming-height="528" loading="lazy" width="1000" height="528"></figure>
</div>
<p>This talk will be an introduction to Elixir in the form of a walk-through of a real-world application (Addressfinder).</p>
<p>Nigel will discuss the differences between a language such as Elixir, when compared with Ruby, Javascript, C#, etc.</p>
<p>This will be an interactive presentation with the opportunity for questions along the way.</p>
<p>Find out when and where on the <a href="https://www.meetup.com/Wellington-Elixir-User-Group/events/240281374/" target="_blank" rel="noopener noreferrer">Wellington Elixir meetup</a> page.</p>
<p>We hope to see you there!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>HTTP/2 accelerates Addressfinder</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>HTTP/2 delivers a significant improvement for Addressfinder customers.</h2>
<p>Last week we upgraded the software that powers Addressfinder. We turned on HTTP/2. It’s made the autocomplete even faster.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l9wdtfvxwd47zsnm04zylm9x" alt="" data-big=https://cms-assets.abletech.nz/large_1_D_We_Zn1ry_T_Ko_h_IP_0370m_ZA_5395c1056d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_D_We_Zn1ry_T_Ko_h_IP_0370m_ZA_5395c1056d.jpeg" srcset="https://cms-assets.abletech.nz/large_1_D_We_Zn1ry_T_Ko_h_IP_0370m_ZA_5395c1056d.jpeg 1000w, https://cms-assets.abletech.nz/small_1_D_We_Zn1ry_T_Ko_h_IP_0370m_ZA_5395c1056d.jpeg 500w, https://cms-assets.abletech.nz/medium_1_D_We_Zn1ry_T_Ko_h_IP_0370m_ZA_5395c1056d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_D_We_Zn1ry_T_Ko_h_IP_0370m_ZA_5395c1056d.jpeg 245w" data-zooming-width="1000" data-zooming-height="474" loading="lazy" width="1000" height="474"></figure>
</div>
<p>HTTP/2 is a major change in the way web browsers talk to servers. This is the most significant change in a decade. In the case of Addressfinder, HTTP/2 changes the way a browser talks to the API.</p>
<h3><strong>Faster performance</strong></h3>
<p>The time it takes, for a browser to communicate with the server, and the time it takes for the response, has been significantly reduced.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="di9iknn03uqdlw7nbw01juf8" alt="" data-big=https://cms-assets.abletech.nz/large_1i_I_Be_Jrdzht6_Zue_BY_Ztidug_fa41c1087f.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 239px" src="https://cms-assets.abletech.nz/1i_I_Be_Jrdzht6_Zue_BY_Ztidug_fa41c1087f.jpeg" srcset="https://cms-assets.abletech.nz/large_1i_I_Be_Jrdzht6_Zue_BY_Ztidug_fa41c1087f.jpeg 1000w, https://cms-assets.abletech.nz/small_1i_I_Be_Jrdzht6_Zue_BY_Ztidug_fa41c1087f.jpeg 500w, https://cms-assets.abletech.nz/medium_1i_I_Be_Jrdzht6_Zue_BY_Ztidug_fa41c1087f.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1i_I_Be_Jrdzht6_Zue_BY_Ztidug_fa41c1087f.jpeg 239w" data-zooming-width="1000" data-zooming-height="652" loading="lazy" width="1000" height="652"></figure>
</div>
<p>For a business’s end user, this equates to speed. Addressfinder will be even faster than usual for them, especially if they’re using a mobile device. It will also be noticeably faster for an end user on a computer that has a slower internet connection.</p>
<p>HTTP/2 enables a short-cut when setting up the encrypted link. In essence this removes a transaction which naturally speeds it up.</p>
<h3><strong>Single connection</strong></h3>
<p>HTTP/2 allows multiple requests to connect. Traditionally multiple connections were used for multiple requests. Only one request could be outstanding on a connection at a time.</p>
<p>HTTP/2 is ‘fully multiplexed’ so it can use one connection for multiple requests. It can also intermingle parts of one request with another.</p>
<p>Here at <a href="http://t.umblr.com/redirect?z=https%3A%2F%2Faddressfinder.nz%2F&amp;t=YjEyNDQ1ZjI1YzVmODc0MzJkNjk5ZmE2NmIyNTU2NjNmNjI0MTk1MSx2N0NpZmg0YQ%3D%3D&amp;b=t%3AYY8T6z00kP5bqqCmfoZaWA&amp;p=http%3A%2F%2Fblog.addressfinder.nz%2Fpost%2F158981832123%2Fhttp2-accelerates-addressfinder&amp;m=1" target="_blank" rel="noopener noreferrer">Addressfinder</a> we’ve been waiting for this infrastructure upgrade. This is a performance gain for our customers, and for their customers. The best possible user experience comes from the fastest possible process, so trimming time off requests simply makes life better.</p>
<h3><strong>Do I need to do anything?</strong></h3>
<p>No. There are no changes required at your end. The Addressfinder system will continue to support HTTP/1. If a user’s browser supports HTTP/2 then they will benefit from the improved functionality.</p>
<h3><strong>My site is not encrypted</strong></h3>
<p>HTTP/2 is only for encrypted website connections.</p>
<p>If you’re not using the HTTPS version of our JavaScript widget then this doesn’t apply. You should switch to <a href="https://api.addressfinder.io/assets/v3/widget.js" target="_blank" rel="noopener noreferrer">loading the AF widget with HTTPS</a> and you’ll see an immediate improvement.</p>
<p><em>Originally published at <a href="http://blog.addressfinder.nz/post/158981832123/http2-accelerates-addressfinder" target="_blank" rel="noopener noreferrer">blog.addressfinder.nz</a>.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Addressfinder Dashboard</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>We asked Chris Connolly to improve our Addressfinder Dashboard</h2>
<p>Find out what Chris did, how he did it, and what he learnt along the way.</p>
<h3>What is Addressfinder?</h3>
<p><a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">Addressfinder</a> is a popular service, built by Abletech, used by developers throughout New Zealand and Australia. Thousands of websites use Addressfinder every day. Verifying street addresses for deliveries. Autocompleting street addresses for online checkouts. Verifying spreadsheets of street address data. Using geolocations, or metadata, for mapping and routing.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="o4xyp9i1b05fra43p9owjneo" alt="" data-big=https://cms-assets.abletech.nz/large_1_KK_Ogq_UB_9_B_havwmp_Ltqjw_f17c981c86.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_KK_Ogq_UB_9_B_havwmp_Ltqjw_f17c981c86.png" srcset="https://cms-assets.abletech.nz/large_1_KK_Ogq_UB_9_B_havwmp_Ltqjw_f17c981c86.png 1000w, https://cms-assets.abletech.nz/small_1_KK_Ogq_UB_9_B_havwmp_Ltqjw_f17c981c86.png 500w, https://cms-assets.abletech.nz/medium_1_KK_Ogq_UB_9_B_havwmp_Ltqjw_f17c981c86.png 750w, https://cms-assets.abletech.nz/thumbnail_1_KK_Ogq_UB_9_B_havwmp_Ltqjw_f17c981c86.png 245w" data-zooming-width="1000" data-zooming-height="578" loading="lazy" width="1000" height="578"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vkeoje95qvty9ucknm73xppi" alt="" data-big=https://cms-assets.abletech.nz/large_1_F_e_AV_Ch_Bm_FMT_5_T2ao_VJ_Jx_Q_3945d01ad8.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 229px" src="https://cms-assets.abletech.nz/1_F_e_AV_Ch_Bm_FMT_5_T2ao_VJ_Jx_Q_3945d01ad8.png" srcset="https://cms-assets.abletech.nz/large_1_F_e_AV_Ch_Bm_FMT_5_T2ao_VJ_Jx_Q_3945d01ad8.png 1000w, https://cms-assets.abletech.nz/small_1_F_e_AV_Ch_Bm_FMT_5_T2ao_VJ_Jx_Q_3945d01ad8.png 500w, https://cms-assets.abletech.nz/medium_1_F_e_AV_Ch_Bm_FMT_5_T2ao_VJ_Jx_Q_3945d01ad8.png 750w, https://cms-assets.abletech.nz/thumbnail_1_F_e_AV_Ch_Bm_FMT_5_T2ao_VJ_Jx_Q_3945d01ad8.png 229w" data-zooming-width="1000" data-zooming-height="680" loading="lazy" width="1000" height="680"></figure>
</div>
<h3>Addressfinder Dashboard</h3>
<p>The Addressfinder team has a Dashboard on the office wall. It’s a screen that displays real-time data about current Addressfinder usage. Lots of customers. Lots of address searches.</p>
<p>We wanted intern Chris to improve the immediacy and accuracy of the data. We had been using Google Data Studio and over time we found that we were unable to trust its data. The values weren’t updated well. We needed to improve the reliability of the numbers.</p>
<p>We also wanted to improve the ‘glanceability’. The Dashboard cycled through four auto-tabs and the screen wasn’t always displaying useful information when we glanced at it.</p>
<blockquote>
<p>We needed simplification and consolidation</p>
</blockquote>
<h3>The Architecture</h3>
<p>The new Addressfinder Dashboard Frontend queries a Dashboard API that Chris created in React (frontend) and Rails (backend).</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="a40cojq88x89vs8pawz83g10" alt="" data-big=https://cms-assets.abletech.nz/large_1b_Rg_IT_Xa_De21y_Ials_p_Ml_Q_b6ad36ce22.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1b_Rg_IT_Xa_De21y_Ials_p_Ml_Q_b6ad36ce22.png" srcset="https://cms-assets.abletech.nz/large_1b_Rg_IT_Xa_De21y_Ials_p_Ml_Q_b6ad36ce22.png 1000w, https://cms-assets.abletech.nz/small_1b_Rg_IT_Xa_De21y_Ials_p_Ml_Q_b6ad36ce22.png 500w, https://cms-assets.abletech.nz/medium_1b_Rg_IT_Xa_De21y_Ials_p_Ml_Q_b6ad36ce22.png 750w, https://cms-assets.abletech.nz/thumbnail_1b_Rg_IT_Xa_De21y_Ials_p_Ml_Q_b6ad36ce22.png 245w" data-zooming-width="1000" data-zooming-height="583" loading="lazy" width="1000" height="583"></figure>
</div>
<p>The new system either proxies some requests to the Portal API then transforms the data and sends it back to the front end. Or, in the background, it runs rake tasks to collate some data to store in the database, then when the front end queries the API it just queries that database, and returns it back via the API.</p>
<h3>The Dashboard</h3>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ljkpyfsjki3f2oqk502z7jji" alt="" data-big=https://cms-assets.abletech.nz/large_1_LGPII_Uo_Ow_I_Wvmw4_MI_35x9_A_192c49ada7.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_LGPII_Uo_Ow_I_Wvmw4_MI_35x9_A_192c49ada7.png" srcset="https://cms-assets.abletech.nz/large_1_LGPII_Uo_Ow_I_Wvmw4_MI_35x9_A_192c49ada7.png 1000w, https://cms-assets.abletech.nz/small_1_LGPII_Uo_Ow_I_Wvmw4_MI_35x9_A_192c49ada7.png 500w, https://cms-assets.abletech.nz/medium_1_LGPII_Uo_Ow_I_Wvmw4_MI_35x9_A_192c49ada7.png 750w, https://cms-assets.abletech.nz/thumbnail_1_LGPII_Uo_Ow_I_Wvmw4_MI_35x9_A_192c49ada7.png 245w" data-zooming-width="1000" data-zooming-height="521" loading="lazy" width="1000" height="521"></figure>
</div>
<p>There are two widgets that show 60 Minutes and 30 Days. These show how many hosts (servers and websites) have used Addressfinder. We also get a metric of the highest number we’ve had using Addressfinder.</p>
<p>The new at-a-glance dashboard also displays the number of new signups Addressfinder has had. It’s sectioned into New Zealand and Australia.</p>
<p>We also get Hits By Service and Hits By Account data. Hits By Service shows the top five Addressfinder APIs currently being used. Hits By Account allows us to see, for example, if a host is doing list verification because they pop into the top eight listed. We’ll then see a spike in service use.</p>
<h3>What did Chris learn?</h3>
<p>Chris says he learnt heaps from doing this project; both from working alongside experienced developers (like Jordan and <a href="https://abletech.nz/about/#kate-norquay">Kate</a>) and from carrying out the work. He also learnt how to understand what the clients, <a href="https://abletech.nz/about/#nigel-ramsay">Nigel</a> and <a href="https://abletech.nz/about/#matt-ramsay">Matt</a>, wanted and how to collaborate with them as the project progressed.</p>
<blockquote>
<p>I enjoyed using Ruby on Rails, and learning how MVC frameworks work, learning how React works, Rake tasks, Background tasks, proper deployment and continuous delivery pipelines, setting up integrations between Git and Netlify and Heroku etc. I learnt about Docker from <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel</a> which was good too.</p>
</blockquote>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cf8ee5qc2u3zas8dp63fxi1g" alt="" data-big=https://cms-assets.abletech.nz/large_1w5kbk_V_Zj_G7j_Dckgtrq_KA_57f189aebf.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1w5kbk_V_Zj_G7j_Dckgtrq_KA_57f189aebf.png" srcset="https://cms-assets.abletech.nz/large_1w5kbk_V_Zj_G7j_Dckgtrq_KA_57f189aebf.png 1000w, https://cms-assets.abletech.nz/small_1w5kbk_V_Zj_G7j_Dckgtrq_KA_57f189aebf.png 500w, https://cms-assets.abletech.nz/medium_1w5kbk_V_Zj_G7j_Dckgtrq_KA_57f189aebf.png 750w, https://cms-assets.abletech.nz/thumbnail_1w5kbk_V_Zj_G7j_Dckgtrq_KA_57f189aebf.png 245w" data-zooming-width="1000" data-zooming-height="558" loading="lazy" width="1000" height="558"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vh2usx7dbtcofqvhhony77bt" alt="" data-big=https://cms-assets.abletech.nz/medium_1_T_Jhp3_K8y_R_Sfa_Ez_VI_Fo_Sg_129f47893e.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 206px" src="https://cms-assets.abletech.nz/1_T_Jhp3_K8y_R_Sfa_Ez_VI_Fo_Sg_129f47893e.png" srcset="https://cms-assets.abletech.nz/small_1_T_Jhp3_K8y_R_Sfa_Ez_VI_Fo_Sg_129f47893e.png 500w, https://cms-assets.abletech.nz/medium_1_T_Jhp3_K8y_R_Sfa_Ez_VI_Fo_Sg_129f47893e.png 750w, https://cms-assets.abletech.nz/thumbnail_1_T_Jhp3_K8y_R_Sfa_Ez_VI_Fo_Sg_129f47893e.png 206w" data-zooming-width="750" data-zooming-height="567" loading="lazy" width="750" height="567"></figure>
</div>
<blockquote>
<p>It was great to have hands-on experience of GitFlow and see how it all works in the real world. It was also good to use a proper GitFlow and have the systems in place where collaboration was enabled (with branches) for code reviews and pull requests.
I learnt how to use Heroku and Netlify. VCR. Recording HTTP interactions into cassettes instead of stubbing out data responses. Caching. Also I found the Highcharts library API good to work with.</p>
</blockquote>
<p>The Addressfinder team are finding the new dashboard excellent. Its glanceability has improved, and the screen always provides a useful snapshot of reliable current service activity. Thanks Chris!</p>
<p>Read more from Chris — <a href="https://abletech.nz/resource/the-guide-to-integrating-google-mymaps-onto-your-website-with-react/">here’s his guide to integrating Google MyMaps with a website, using React</a></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Addressfinder</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Two opportunities to get involved with the AddressFinder team</h2>
<p>Our friends over at Addressfinder are looking for a Customer Success Specialist and a Digital Marketer. Feel free to share the <a href="https://blog.addressfinder.io/" target="_blank" rel="noopener noreferrer">opportunities</a> with people who may be interested. Applications close 30 November.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wc44vfaoe7nzl9ysaf2t8b1l" alt="" data-big=https://cms-assets.abletech.nz/large_1bs2gb_Op_Tr_W9_KR_Gi_Dziy99w_5e7b5084b6.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1bs2gb_Op_Tr_W9_KR_Gi_Dziy99w_5e7b5084b6.png" srcset="https://cms-assets.abletech.nz/large_1bs2gb_Op_Tr_W9_KR_Gi_Dziy99w_5e7b5084b6.png 1000w, https://cms-assets.abletech.nz/small_1bs2gb_Op_Tr_W9_KR_Gi_Dziy99w_5e7b5084b6.png 500w, https://cms-assets.abletech.nz/medium_1bs2gb_Op_Tr_W9_KR_Gi_Dziy99w_5e7b5084b6.png 750w, https://cms-assets.abletech.nz/thumbnail_1bs2gb_Op_Tr_W9_KR_Gi_Dziy99w_5e7b5084b6.png 156w" data-zooming-width="1000" data-zooming-height="1000" loading="lazy" width="1000" height="1000"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Chatbot & Natural Language Processing</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Alexandre Barret’s been using a Chatbot through Facebook Messenger. Sit in on this Abletech Tech Talk. He’ll walk you through how it all works and share an example of it being put to very good use for finding the perfect burger 🍔</h2>
<h3>Natural Language Processing and Facebook Messenger 101</h3>
<p>The talk covers the basics of implementing a chatbot through Facebook Messenger using a Natural Language Processing (NLP) API called RecastAI.</p>
<h3>Includes</h3>
<ul>
<li>
<p>NLP — how to to identify user intents</p>
</li>
<li>
<p>NLP — how to train and improve your bot</p>
</li>
<li>
<p>Messenger — how to create a hello world chatbot using the Facebook-Messenger gem</p>
</li>
<li>
<p>Example — overview of the development of 🍔 <a href="https://www.facebook.com/bravowellybot" target="_blank" rel="noopener noreferrer">BravoWellyBot</a> in Ruby</p>
</li>
</ul>
<p>Alex also gave an overview of the chatbot at a <a href="https://www.meetup.com/wellrailed/" target="_blank" rel="noopener noreferrer">Wellrailed</a> Ruby on Rails meetup in Wellington. Thanks for sharing your learnings and insights Alex.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h252ssy4nm5hx9g9pofeeu6z" alt="" data-big=https://cms-assets.abletech.nz/large_1x_Gp_Kw0sb_H_YD_Xb_TSU_7_Vj_WA_4d3134ae9c.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1x_Gp_Kw0sb_H_YD_Xb_TSU_7_Vj_WA_4d3134ae9c.png" srcset="https://cms-assets.abletech.nz/large_1x_Gp_Kw0sb_H_YD_Xb_TSU_7_Vj_WA_4d3134ae9c.png 1000w, https://cms-assets.abletech.nz/small_1x_Gp_Kw0sb_H_YD_Xb_TSU_7_Vj_WA_4d3134ae9c.png 500w, https://cms-assets.abletech.nz/medium_1x_Gp_Kw0sb_H_YD_Xb_TSU_7_Vj_WA_4d3134ae9c.png 750w, https://cms-assets.abletech.nz/thumbnail_1x_Gp_Kw0sb_H_YD_Xb_TSU_7_Vj_WA_4d3134ae9c.png 245w" data-zooming-width="1000" data-zooming-height="361" loading="lazy" width="1000" height="361"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Vim’s most magical autocomplete</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>One of Vim’s most incredible, built in, features is autocomplete. It’s also got some neat tricks that you can use to make your coding very efficient. This is all native Vim. No plugins here.</h2>
<p>Let’s jump straight to the point: Vim can autocomplete a word, but it also knows the context of where it found the keyword, and can complete the next word after it.</p>
<p>A place I often find this used is refactoring specs. Say I have a <code>user = ...</code> assignment in a spec that I want to extract out into a <code>let</code>. I can’t autocomplete the whole line, because they start differently. I <em>could</em> copy the line and modify it. <strong>OR</strong>, I could do this…</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="usq5tqernzygdc4ohyhv9ww4" alt=""  sizes="" src="https://cms-assets.abletech.nz/1ig_O_Ql_Ge_Mea9zq_E_Mte_Fe_Th_Q_6f208bc688.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p>Notice how the context after the <code>create :user</code> is all autocompleted. So fast.</p>
<h3>The relevant Vim help</h3>
<p>The help text for <code>:h i_CTRL-P</code> states,</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wcc8vm3mwzu5kcbbe1c8ucli" alt="" data-big=https://cms-assets.abletech.nz/small_1b_JMYTW_Oh_L3_Az_M_Ang4_Xp7_YA_c85e439086.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1b_JMYTW_Oh_L3_Az_M_Ang4_Xp7_YA_c85e439086.png" srcset="https://cms-assets.abletech.nz/small_1b_JMYTW_Oh_L3_Az_M_Ang4_Xp7_YA_c85e439086.png 500w, https://cms-assets.abletech.nz/thumbnail_1b_JMYTW_Oh_L3_Az_M_Ang4_Xp7_YA_c85e439086.png 245w" data-zooming-width="500" data-zooming-height="233" loading="lazy" width="500" height="233"></figure>
</div>
<blockquote>
<p>Further use of CTRL-X CTRL-N or CTRL-X CTRL-P will copy the words following the previous expansion in other contexts unless a double CTRL-X is used.</p>
</blockquote>
<p>So, the pattern is</p>
<ol>
<li>
<p>Autocomplete a word with <code>CTRL-P</code> or <code>CTRL-N</code></p>
</li>
<li>
<p>Complete context by mashing <code>CTRL-X CTRL-P</code> or <code>CTRL-X CTRL-N</code></p>
</li>
</ol>
<p>Give it a go, it wont take long to become intuitive and incredibly useful.</p>
<p>P.S. for the Emacs users who have yet to convert to Vim, you can check out <a href="https://www.emacswiki.org/emacs/HippieExpand" target="_blank" rel="noopener noreferrer">HippieExpand</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Using any method with Ruby iterators</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Occasionally I need to iterate over the results of a query, and perform some operation on each item. Until recently, I was only aware of option 1 and option 2 below.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dnh1mxrzgamk5x4krneg3zzu" alt="" data-big=https://cms-assets.abletech.nz/large_1_Alr_Y_Fol_W_Cx_Tt_d_XL_5_JN_Jw_6b4b7870f0.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Alr_Y_Fol_W_Cx_Tt_d_XL_5_JN_Jw_6b4b7870f0.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Alr_Y_Fol_W_Cx_Tt_d_XL_5_JN_Jw_6b4b7870f0.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Alr_Y_Fol_W_Cx_Tt_d_XL_5_JN_Jw_6b4b7870f0.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Alr_Y_Fol_W_Cx_Tt_d_XL_5_JN_Jw_6b4b7870f0.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Alr_Y_Fol_W_Cx_Tt_d_XL_5_JN_Jw_6b4b7870f0.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<h3>Option 1: Define a block for the iterator</h3>
<pre><code class="hljs"><span class="hljs-keyword">class</span> <span class="hljs-title class_">User</span>
  <span class="hljs-keyword">def</span> <span class="hljs-title function_">anonymise</span>
    <span class="hljs-title class_">Users</span>.find_each <span class="hljs-keyword">do</span> |<span class="hljs-params">user</span>|
      user.update_columns <span class="hljs-symbol">email:</span> <span class="hljs-string">&quot;<span class="hljs-subst">#{user.id}</span>@devnullmail.com&quot;</span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<h3>Option 2: Define a method on the User object, and then pass it to the find_each method</h3>
<pre><code class="hljs"><span class="hljs-keyword">class</span> AnonymiseUsers
  <span class="hljs-keyword">include</span> UseCasePattern
  
  def <span class="hljs-keyword">perform</span>
    <span class="hljs-keyword">User</span>.find_each &amp;:anonymise
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">class</span> <span class="hljs-keyword">User</span>
  def anonymise
    update_columns email: &quot;#{user.id}@devnullmail.com&quot;
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<h3>Option 3: Define a method, and then pass it to the find_each method</h3>
<pre><code class="hljs"><span class="hljs-keyword">class</span> AnonymiseUsers
  <span class="hljs-keyword">include</span> UseCasePattern
  
  def <span class="hljs-keyword">perform</span>
    <span class="hljs-keyword">User</span>.find_each &amp;<span class="hljs-keyword">method</span>(:anonymise)
  <span class="hljs-keyword">end</span>
  
  private
  
  def anonymise(<span class="hljs-keyword">user</span>)
    <span class="hljs-keyword">user</span>.update_columns email: &quot;#{user.id}@devnullmail.com&quot;
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>This third option is useful. It allows me to keep the anonymise code out of the User class. Usually it’s best to keep related code together, and the code within AnonymiseUsers is not expected to be required elsewhere in the code base.</p>
<p>It also has the advantage of keeping the User class, which can often become a “God Class”, as slim as possible.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Ubuntu — Applying Security Updates</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>It is all too easy to deploy your app, and keep doing your day job, only to log onto your server and notice that 124 packages are out of date. As Trump would say: Bad. Make sure part of your ongoing server maintenance procedures includes keeping an active eye on <a href="https://www.ubuntu.com/usn/" target="_blank" rel="noopener noreferrer">updates</a> and applying these ASAP.</p>
<h3>Automatic Updates</h3>
<p>If you can talk your client into automatic updates, you can configure the <a href="https://wiki.debian.org/UnattendedUpgrades" target="_blank" rel="noopener noreferrer">unattended-updates</a> to be run by cron and have all security patches automatically applied.</p>
<h3>Manual Updates</h3>
<p>To see the count of security packages that can should applied:</p>
<pre><code class="hljs"><span class="hljs-built_in">sudo</span> /usr/lib/update-notifier/apt-check 2&gt;&amp;1 | <span class="hljs-built_in">cut</span> -d <span class="hljs-string">&#x27;;&#x27;</span> -f 2
</code></pre>
<p>To see the list of security packages that can be applied:</p>
<pre><code class="hljs">sudo unattended-upgrade --dry-run -d | <span class="hljs-keyword">grep</span> <span class="hljs-string">&#x27;Checking&#x27;</span> | <span class="hljs-keyword">grep</span> security | awk <span class="hljs-string">&#x27;{ print $2 }&#x27;</span>
</code></pre>
<p>To apply updates</p>
<pre><code class="hljs">sudo apt-<span class="hljs-built_in">get</span> update
sudo apt-<span class="hljs-built_in">get</span><span class="hljs-built_in"> upgrade
</span></code></pre>
<p>If a reboot is required or desired for testing</p>
<pre><code class="hljs">sudo shutdown -r <span class="hljs-built_in">now</span>
</code></pre>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Travelling Light On Heavy Ground</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>If you wish to travel far and fast, travel light - Cesar Pavese</h2>
<p>At Abletech, we love tests. After all, an application is only as good as its tests. And for the most part tests love us, but sometimes they love us a little too much.</p>
<p>No matter how much you optimise your tests, when a project or application gets to a certain size or scope, a comprehensive test suite can get a little unwieldy to run as a whole. For example, the last three applications I’ve worked on have had multiple thousands of unit and integration tests each, which if run locally end-to-end for one of these apps could last up to 50 minutes!</p>
<p>Most of the time though, it’s easy enough to limit the tests you’re running to a specific area that you’re working on e.g. with <em>Rspec</em>, limiting the test run to either a specific file <code>rspec ./spec/controllers/users_controller_spec.rb</code> or directory <code>rspec ./spec/models</code>.</p>
<p>However, when the full test suite is too slow to run in its entirety, but your code is scattered over the standard <a href="http://rubyonrails.org/doctrine/#convention-over-configuration" target="_blank" rel="noopener noreferrer">convention-over-configuration</a> codebase, a developer is often faced with having to run each test file individually — which can be painful, tedious and leave a developer open to accidentally omitting a test from any given test run. Not good for productivity, frustration levels or accuracy.</p>
<p>In this most recent case, we were working on a new API for a client to service their upcoming mobile apps where we had dozens of files spread over each of the <em>controllers</em>, <em>models</em>, <a href="https://github.com/rails-api/active_model_serializers" target="_blank" rel="noopener noreferrer"><em>serializers</em></a>, <a href="https://github.com/AbleTech/use_case_pattern/" target="_blank" rel="noopener noreferrer"><em>use_cases</em></a> and <em>jobs</em> directories.</p>
<h3>What can a developer do to fix this? If in doubt, script it!</h3>
<p>While convention-over-configuration undoubtably made it difficult to test due to the spread of code, it also can come to your aid when applied well.
In this case, as our intent was keep the API code and <a href="https://en.wikipedia.org/wiki/Separation_of_concerns" target="_blank" rel="noopener noreferrer">logic separate from the existing logic</a> in the web application, we had namespaced the code under the existing directories (<em>controllers/</em> etc) with <em>/api/mobile/[version]</em>.</p>
<p>We had a discernible pattern we could recognise - <code>/[directory]/api/mobile</code> - just not one that <em>Rspec</em> could automatically work with.</p>
<p>So, with that namespace pattern, we were able to use the *Rspec <code>*RakeTask </code>to whip up a test suite specifically for this API project that could not only comprehensively test all the code we were working on in mere seconds but also provide us with piece-of-mind that nothing could get left out.</p>
<p>Here’s the code (<em>lib/tasks/spec_suites.rake</em>):</p>
<pre><code class="hljs"><span class="hljs-keyword">require</span> <span class="hljs-string">&#x27;rspec/core/rake_task&#x27;</span>

namespace <span class="hljs-symbol">:spec</span> <span class="hljs-keyword">do</span>
  namespace <span class="hljs-symbol">:suite</span> <span class="hljs-keyword">do</span>
      desc <span class="hljs-string">&quot;Run all specs in Mobile APIs spec suite&quot;</span>
      <span class="hljs-title class_">RSpec</span>::<span class="hljs-title class_">Core</span>::<span class="hljs-title class_">RakeTask</span>.new(<span class="hljs-symbol">:mobile</span>) <span class="hljs-keyword">do</span> |t|
        t.rspec_opts = [<span class="hljs-string">&#x27;--options&#x27;</span>, <span class="hljs-string">&quot;<span class="hljs-subst">#{<span class="hljs-title class_">Rails</span>.root}</span>/spec/spec.opts&quot;</span>]
        t.pattern = <span class="hljs-string">&#x27;spec/*/api/mobile{,/*/**}/*_spec.rb&#x27;</span>
      <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Note the <code>rspec_opts</code> picking up the existing <em>Rspec</em> options, and the non-recursive/recursive directory reference in the <code>pattern</code>.</p>
<p>And to run this is as simple as running<code>rake spec:suite:mobile</code> (<code>spec:suites</code>being the namespaces that conform to the file name and <code>mobile</code>being the actual task name.)
Bear in mind that you’ll want to exclude this from being loaded into any environment that <em>Rspec</em> isn’t loaded in (e.g. everything except test and development normally) by either excluding it in what ever task loading process you have or surrounding the task in an environment check.</p>
<p>So, in summary — tests are good; convention is good; simplicity is good; it’s all good.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>TIL, you can use function keys as bash shortcuts</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>You can assign a function key in bash to type text into your terminal. I’m not talking about snippet expansion that will work everywhere, this is just a bash feature. I really like it, as it works over SSH, and can be setup when I provision my development machine with Ansible (a future blog post perhaps!).</p>
<p>Here is the magic:</p>
<pre><code class="hljs"><span class="hljs-comment"># f10 does git add</span>
<span class="hljs-built_in">bind</span> <span class="hljs-string">&#x27;&quot;\e[21~&quot;:&quot;git add -A; git status\n&#x27;</span>

<span class="hljs-comment"># f11 does git commit</span>
<span class="hljs-built_in">bind</span> <span class="hljs-string">&#x27;&quot;\e[23~&quot;:&quot;git commit --verbose\n&quot;&#x27;</span>

<span class="hljs-comment"># f12 does git push</span>
<span class="hljs-built_in">bind</span> <span class="hljs-string">&#x27;&quot;\e[24~&quot;:&quot;git push; git status\n&#x27;</span>
</code></pre>
<p>A breakdown of the magic:</p>
<ul>
<li>
<p><code>bind</code> bind a key sequence to a macro.</p>
</li>
<li>
<p><code>\e</code> The escape key.</p>
</li>
<li>
<p><code>[21~</code> A sequence following an <code>esc</code> represents a key on the keyboard. In this case, <code>f10</code>.</p>
</li>
<li>
<p><code>\n</code> A newline character. Think of it as hitting <code>return</code> at the end of the line.</p>
</li>
</ul>
<p>Very useful. In fact, I use <code>f9</code> to start a simple HTTP server so I can view code coverage over an SSH connection. Check out <a href="https://abletech.nz/resource/a-simple-web-server/">how to start an HTTP server in one line here</a>.</p>
<p>To find which character sequence you need, in your terminal hit <code>ctrl-v</code> then the key. It will print out something that looks like this: <code>^[[21~</code>. The <code>^[</code> stands for the escape key, but it can also be represented as <code>\e</code>, which I find more readable when deciphering scripts I wrote months ago. Different terminal emulators can output different sequences for same key, so if you have unexpected behaviour on a new terminal, double check the sequence.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Testing ActiveRecord Concerns</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Isolate Rails concerns with temporary databases</h2>
<p>ActiveRecord classes manage persistence and have a tight relationship with their database tables. This relationship, sometimes, makes testing tricky and even trickier when testing Rails concerns. This article describes how to test a concern in isolation from its ActiveRecord class and its associated database table.</p>
<p><em>The code examples are written using RSpec and switching to Minitest is possible but requires a fair bit of work.</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sm100ubhpbj6n6zeqvr2nsib" alt="Photo by [Elif Dilara Bora](https://unsplash.com/@elifborae?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/venice-carnival?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)" data-big=https://cms-assets.abletech.nz/large_0_Rn_ZA_Yt3_bkr4aa_VJ_bf220a8996.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Rn_ZA_Yt3_bkr4aa_VJ_bf220a8996.jpg" srcset="https://cms-assets.abletech.nz/large_0_Rn_ZA_Yt3_bkr4aa_VJ_bf220a8996.jpg 1000w, https://cms-assets.abletech.nz/small_0_Rn_ZA_Yt3_bkr4aa_VJ_bf220a8996.jpg 500w, https://cms-assets.abletech.nz/medium_0_Rn_ZA_Yt3_bkr4aa_VJ_bf220a8996.jpg 750w, https://cms-assets.abletech.nz/thumbnail_0_Rn_ZA_Yt3_bkr4aa_VJ_bf220a8996.jpg 245w" data-zooming-width="1000" data-zooming-height="548" loading="lazy" width="1000" height="548"></figure>
</div>
<p><em>Photo by <a href="https://unsplash.com/@elifborae?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Elif Dilara Bora</a> on <a href="https://unsplash.com/s/photos/venice-carnival?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash</a></em></p>
<h2>What are concerns?</h2>
<p>Concerns are the Rails way to grant a role or an interface to a Ruby class. They provide a nicer syntax than Ruby and aim to clarify confusion around dependencies when used with nested modules. Here is the <a href="https://api.rubyonrails.org/v6.1.0/classes/ActiveSupport/Concern.html" target="_blank" rel="noopener noreferrer">documentation</a>.</p>
<h2>Example: The Reviewable concern</h2>
<p>In this example, we’ll look at an ActiveRecord class <code>Post</code> which includes a <code>Reviewable</code> concern. To work properly the concern needs to be included in an ActiveRecord class hooked to a table with a <code>reviewed_at:datetime</code> column.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="n9vvj7b7t13zi21q2yysp2cs" alt="" data-big=https://cms-assets.abletech.nz/small_1_Bc_AZU_31_Ng_Nqd6q_Uk_F2_HAJQ_93eb41ed99.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 161px" src="https://cms-assets.abletech.nz/1_Bc_AZU_31_Ng_Nqd6q_Uk_F2_HAJQ_93eb41ed99.jpeg" srcset="https://cms-assets.abletech.nz/small_1_Bc_AZU_31_Ng_Nqd6q_Uk_F2_HAJQ_93eb41ed99.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1_Bc_AZU_31_Ng_Nqd6q_Uk_F2_HAJQ_93eb41ed99.jpeg 161w" data-zooming-width="500" data-zooming-height="485" loading="lazy" width="500" height="485"></figure>
</div>
<h2>TL;DR solution</h2>
<p>Below is the gist for people looking to see how it’s done. The main idea is to test every concern with a vanilla ApplicationRecord class connected to a temporary database table.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mk38tn1q732yyr6xyi35tzlq" alt="" data-big=https://cms-assets.abletech.nz/small_1_Rz4c_C_Zrqm_q_V_Ekkoxis_Cw_ded72720d5.png sizes="(min-width: 768px) 500px, (min-width: 640px) 220px" src="https://cms-assets.abletech.nz/1_Rz4c_C_Zrqm_q_V_Ekkoxis_Cw_ded72720d5.png" srcset="https://cms-assets.abletech.nz/small_1_Rz4c_C_Zrqm_q_V_Ekkoxis_Cw_ded72720d5.png 500w, https://cms-assets.abletech.nz/thumbnail_1_Rz4c_C_Zrqm_q_V_Ekkoxis_Cw_ded72720d5.png 220w" data-zooming-width="500" data-zooming-height="355" loading="lazy" width="500" height="355"></figure>
</div>
<p>Let’s take a moment to appreciate how explicit this is. The test displays all the information to teach future devs how the <code>Reviewable</code> concern is setup: how to grant the role and the minimal schema required for an ActiveRecord to acquire the role. To understand what <code>Reviewable</code> does, someone can open <code>'path/to/reviewable/shared/examples'</code> and eliminate all the noise from huge test files by only seeing the tests related to <code>Reviewable</code> behaviour.</p>
<p>Here is a full <a href="https://gist.github.com/AlexB52/0e186b6bd5220d42351f5cffe47439e7" target="_blank" rel="noopener noreferrer">working example</a>.</p>
<h2>Why test concerns in isolation?</h2>
<p>Switching to an isolated table to test concerns ensures that concerns are decoupled from the first ActiveRecord class they’ve been introduced into, <code>Post</code> in this example.</p>
<p>Failing to extract and test your concern in another class than the original ActiveRecord class is not reusable. It is also a smell that the role is not fully understood or is the wrong abstraction.</p>
<p>Having the concern tested this way gives you more confidence in reusing <code>Reviewable</code> with any ActiveRecord class that has a <code>reviewed_at:datetime</code> column in its table.</p>
<h2>Testing</h2>
<h3>Concerns and interfaces</h3>
<p>In OOP, to successfully test a role, you need to define and test its public interface and Rails concerns are no exception. Because <code>Reviewable</code> is included in the <code>Post</code>, we start by writing the interface tests in the <code>post_spec.rb</code> file.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yyk6ufd12rsjd9zrm1wpnbwc" alt="" data-big=https://cms-assets.abletech.nz/small_1avrf2o2t_EIFX_Ud_VEH_58_P_Cg_23e446a8dd.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1avrf2o2t_EIFX_Ud_VEH_58_P_Cg_23e446a8dd.png" srcset="https://cms-assets.abletech.nz/small_1avrf2o2t_EIFX_Ud_VEH_58_P_Cg_23e446a8dd.png 500w, https://cms-assets.abletech.nz/thumbnail_1avrf2o2t_EIFX_Ud_VEH_58_P_Cg_23e446a8dd.png 245w" data-zooming-width="500" data-zooming-height="212" loading="lazy" width="500" height="212"></figure>
</div>
<h3>Concerns and fakes</h3>
<p>A role/concern is meant to be shared with other Ruby classes. Currently, <code>Reviewable</code> is only included in the <code>Post</code> model, however, nothing stops us from including it in other classes, especially testing classes. To do so we extract the role tests into shared tests and include those in the <code>post_spec.rb</code> and <code>reviewable_spec.rb</code> files:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="r7mssy8a4o6kpibyfa5hcwj2" alt="" data-big=https://cms-assets.abletech.nz/medium_16zm_K6_Mo_PFM_6_Pti_Ed_X8_5_Q_509e0e5e92.png sizes="(min-width: 768px) 361px, (min-width: 1024px) 542px, (min-width: 640px) 113px" src="https://cms-assets.abletech.nz/16zm_K6_Mo_PFM_6_Pti_Ed_X8_5_Q_509e0e5e92.png" srcset="https://cms-assets.abletech.nz/small_16zm_K6_Mo_PFM_6_Pti_Ed_X8_5_Q_509e0e5e92.png 361w, https://cms-assets.abletech.nz/medium_16zm_K6_Mo_PFM_6_Pti_Ed_X8_5_Q_509e0e5e92.png 542w, https://cms-assets.abletech.nz/thumbnail_16zm_K6_Mo_PFM_6_Pti_Ed_X8_5_Q_509e0e5e92.png 113w" data-zooming-width="542" data-zooming-height="750" loading="lazy" width="542" height="750"></figure>
</div>
<h3>Concerns and ActiveRecord</h3>
<p>One problem with this test is that while <code>Post</code> and <code>FakeReviewable</code> share the same interface, they do not share the same behaviour. More importantly, this behaviour is tied to the existence of a table column <code>reviewed_at:datetime</code> hooked to the model class. Let's start by adding more tests.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="aqio83yrrytjnxhpv723y8i5" alt="" data-big=https://cms-assets.abletech.nz/medium_1qo_Eg_Iz_Rn_H_Owic_Q_Jl5t_Q2og_9bd2f74428.png sizes="(min-width: 768px) 347px, (min-width: 1024px) 521px, (min-width: 640px) 108px" src="https://cms-assets.abletech.nz/1qo_Eg_Iz_Rn_H_Owic_Q_Jl5t_Q2og_9bd2f74428.png" srcset="https://cms-assets.abletech.nz/small_1qo_Eg_Iz_Rn_H_Owic_Q_Jl5t_Q2og_9bd2f74428.png 347w, https://cms-assets.abletech.nz/medium_1qo_Eg_Iz_Rn_H_Owic_Q_Jl5t_Q2og_9bd2f74428.png 521w, https://cms-assets.abletech.nz/thumbnail_1qo_Eg_Iz_Rn_H_Owic_Q_Jl5t_Q2og_9bd2f74428.png 108w" data-zooming-width="521" data-zooming-height="750" loading="lazy" width="521" height="750"></figure>
</div>
<p>While this causes no problem for <code>Post</code>, our <code>FakeReviewable</code> class is now in trouble. Few methods are now using ActiveRecord methods like <code>#reload</code> or <code>#assign_attributes</code>. Even the <code>Reviewable</code> module is using the <code>#update</code> method. This concern is only to be used with ActiveRecord classes. We could fight against ActiveRecord but a nice workaround is to embrace it and define <code>FakeReviewable</code> as one ActiveRecord class:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kbb8yov3er7wkqbqzxfvwio2" alt="" data-big=https://cms-assets.abletech.nz/small_1a2vz_Wt_LZ_7709_Tmu_O_Mb_Pv_OQ_b56261a5b3.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1a2vz_Wt_LZ_7709_Tmu_O_Mb_Pv_OQ_b56261a5b3.png" srcset="https://cms-assets.abletech.nz/small_1a2vz_Wt_LZ_7709_Tmu_O_Mb_Pv_OQ_b56261a5b3.png 500w, https://cms-assets.abletech.nz/thumbnail_1a2vz_Wt_LZ_7709_Tmu_O_Mb_Pv_OQ_b56261a5b3.png 245w" data-zooming-width="500" data-zooming-height="220" loading="lazy" width="500" height="220"></figure>
</div>
<h3>Concerns and database integrity</h3>
<p>We could stop here and move on to write the scope tests but there is one big problem with this. More often than not, models like <code>Post</code> have further validation rules even in their database table. Let's imagine a scenario like this one:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="u1hupek80c98q6rbe7wjbi2c" alt="" data-big=https://cms-assets.abletech.nz/small_1_V_24w9_TNM_Ava_A3u9r_Xy1_Q_e8a420ed67.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_V_24w9_TNM_Ava_A3u9r_Xy1_Q_e8a420ed67.png" srcset="https://cms-assets.abletech.nz/small_1_V_24w9_TNM_Ava_A3u9r_Xy1_Q_e8a420ed67.png 500w, https://cms-assets.abletech.nz/thumbnail_1_V_24w9_TNM_Ava_A3u9r_Xy1_Q_e8a420ed67.png 245w" data-zooming-width="500" data-zooming-height="254" loading="lazy" width="500" height="254"></figure>
</div>
<p>We now need to give our shared examples a valid <code>reviewable</code> record or the tests won't pass anymore. We update our code like so:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qle73cpa8ikadv27xwm5u1pl" alt="" data-big=https://cms-assets.abletech.nz/small_108cywh0_Ie_P7_Hl_Ncdnifi_Aw_c3e40a21f5.png sizes="(min-width: 768px) 500px, (min-width: 640px) 218px" src="https://cms-assets.abletech.nz/108cywh0_Ie_P7_Hl_Ncdnifi_Aw_c3e40a21f5.png" srcset="https://cms-assets.abletech.nz/small_108cywh0_Ie_P7_Hl_Ncdnifi_Aw_c3e40a21f5.png 500w, https://cms-assets.abletech.nz/thumbnail_108cywh0_Ie_P7_Hl_Ncdnifi_Aw_c3e40a21f5.png 218w" data-zooming-width="500" data-zooming-height="359" loading="lazy" width="500" height="359"></figure>
</div>
<p>But this will still not work, as <code>FakeReviewable</code> class is attached to the <code>posts</code> database table and it still requires <code>:title</code>, and <code>:author</code> to be populated. It almost feels like we need a dedicated table for <code>FakeReviewable</code> class...</p>
<h3>Switching to temporary database tables</h3>
<p>In an ideal world, we would need a <code>fake_reviewables</code> table with a single <code>reviewed_at</code> column so that we remove the need for <code>title</code> and <code>author_id</code> to be populated. One way to do this is to create a dedicated <code>fake_reviewables</code> testing table in your <code>schema.rb</code> but that table will also end up in your production database.</p>
<p>While we could argue that this is no big deal and there is nothing wrong with having testing tables in production, I’ll end this article with some code on how to switch to an in-memory SQLite <code>fake_reviewables</code> table.</p>
<p>One way to do this is to include helpers to switch to an in-memory database. Here is the <code>InMemoryDatabaseHelpers</code> module and its usage with <code>FakeReviewable</code>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="n9t30jr3jf9cdc66l9umrjg8" alt="" data-big=https://cms-assets.abletech.nz/medium_1_PFKN_Fc_Epkgx_MV_5_HGJ_Nh_Zfw_c19e356a02.png sizes="(min-width: 768px) 442px, (min-width: 1024px) 663px, (min-width: 640px) 138px" src="https://cms-assets.abletech.nz/1_PFKN_Fc_Epkgx_MV_5_HGJ_Nh_Zfw_c19e356a02.png" srcset="https://cms-assets.abletech.nz/small_1_PFKN_Fc_Epkgx_MV_5_HGJ_Nh_Zfw_c19e356a02.png 442w, https://cms-assets.abletech.nz/medium_1_PFKN_Fc_Epkgx_MV_5_HGJ_Nh_Zfw_c19e356a02.png 663w, https://cms-assets.abletech.nz/thumbnail_1_PFKN_Fc_Epkgx_MV_5_HGJ_Nh_Zfw_c19e356a02.png 138w" data-zooming-width="663" data-zooming-height="750" loading="lazy" width="663" height="750"></figure>
</div>
<p>And finally the solution described in the TL;DR</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="z6lla78kr32z838jm178a6ch" alt="" data-big=https://cms-assets.abletech.nz/small_1_Hm_kn_Mpx_I_Kkj_U8s_O_Zyl_Pm_A_cc60bba552.png sizes="(min-width: 768px) 500px, (min-width: 640px) 220px" src="https://cms-assets.abletech.nz/1_Hm_kn_Mpx_I_Kkj_U8s_O_Zyl_Pm_A_cc60bba552.png" srcset="https://cms-assets.abletech.nz/small_1_Hm_kn_Mpx_I_Kkj_U8s_O_Zyl_Pm_A_cc60bba552.png 500w, https://cms-assets.abletech.nz/thumbnail_1_Hm_kn_Mpx_I_Kkj_U8s_O_Zyl_Pm_A_cc60bba552.png 220w" data-zooming-width="500" data-zooming-height="355" loading="lazy" width="500" height="355"></figure>
</div>
<h2>Food for thought</h2>
<h3>What about testing scopes?</h3>
<p>This article is quite long already. The same principles would apply to test scopes. If you’re interested in a fully working spec suite, here is the <a href="https://gist.github.com/AlexB52/0e186b6bd5220d42351f5cffe47439e7" target="_blank" rel="noopener noreferrer">Gist: Testing ActiveRecord Concerns</a>.</p>
<h3>Raw SQL queries</h3>
<p>Most of SQL syntax is shared across the mainstream databases and thanks to Rails the SQL is also abstracted in a DSL.</p>
<p>This method of testing concerns will work for most of the use cases, however, concerns introducing raw SQL queries can be a problem. Raw SQL queries can use different syntax between MySQL, SQLite or PostgreSQL. For example, PostgreSQL has a specific syntax for window functions like <code>OVER (PARTITION BY x)</code> which I think doesn't exist in SQLite.</p>
<p>In this case, another testing approach would be required for that specific concern. Hopefully, raw SQLs are the exception and not the standard in your Rails codebase.</p>
<h3>Tests are fast</h3>
<p>Tests run on a <code>SQLite memory</code> database are fast, faster than using MySQL or PostgreSQL to test your application. Here is a quick benchmark to show the differences between PostgreSQL, SQLite file and in-memory databases. The result shows the creation of a thousand posts on a rails console with each adapter.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="j14sds7qfv8m614om62sr2cy" alt="" data-big=https://cms-assets.abletech.nz/small_1j_kx_Pg_Ww_Aggwwjc_KR_0g8_YA_74e5a459fc.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1j_kx_Pg_Ww_Aggwwjc_KR_0g8_YA_74e5a459fc.png" srcset="https://cms-assets.abletech.nz/small_1j_kx_Pg_Ww_Aggwwjc_KR_0g8_YA_74e5a459fc.png 500w, https://cms-assets.abletech.nz/thumbnail_1j_kx_Pg_Ww_Aggwwjc_KR_0g8_YA_74e5a459fc.png 245w" data-zooming-width="500" data-zooming-height="130" loading="lazy" width="500" height="130"></figure>
</div>
<h3>Cost of switching</h3>
<p>We haven’t properly profiled our test suite but our current CI time doesn’t seem to have been impacted. Here is a quick benchmark showing the cost of instantiating an in-memory SQLite database and switching back to PostgreSQL.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sbinhtlimvp2uuxzspuwo256" alt="" data-big=https://cms-assets.abletech.nz/small_1p_Tw_Al_L_Mhtsk_U_Gmj4_P_On_Uw_b431408848.png sizes="(min-width: 768px) 500px, (min-width: 640px) 217px" src="https://cms-assets.abletech.nz/1p_Tw_Al_L_Mhtsk_U_Gmj4_P_On_Uw_b431408848.png" srcset="https://cms-assets.abletech.nz/small_1p_Tw_Al_L_Mhtsk_U_Gmj4_P_On_Uw_b431408848.png 500w, https://cms-assets.abletech.nz/thumbnail_1p_Tw_Al_L_Mhtsk_U_Gmj4_P_On_Uw_b431408848.png 217w" data-zooming-width="500" data-zooming-height="360" loading="lazy" width="500" height="360"></figure>
</div>
<p>Switching locally to an in-memory SQLite database for some tests is not taking too long to instantiate. With those results, we could even consider switching before every test that requires a temporary database without being too significant.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ohsybvu0y0w1od6vqleeicdu" alt="" data-big=https://cms-assets.abletech.nz/small_1sdg_U8_BS_Tt12czu8_D_Sgb_Ttg_fedf058a8f.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1sdg_U8_BS_Tt12czu8_D_Sgb_Ttg_fedf058a8f.png" srcset="https://cms-assets.abletech.nz/small_1sdg_U8_BS_Tt12czu8_D_Sgb_Ttg_fedf058a8f.png 500w, https://cms-assets.abletech.nz/thumbnail_1sdg_U8_BS_Tt12czu8_D_Sgb_Ttg_fedf058a8f.png 245w" data-zooming-width="500" data-zooming-height="113" loading="lazy" width="500" height="113"></figure>
</div>
<h3>Minitest</h3>
<p>I love Minitest but I am not aware of a standard method to run expensive tasks before a group of tests like RSpec does with <code>before(:all)</code>. One way would be to use <a href="https://github.com/jeremyevans/minitest-hooks" target="_blank" rel="noopener noreferrer">minitest-hooks gem</a> which helps you wrap expensive tasks in a similar fashion to RSpec.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Setting Kafka’s pace with Broadway</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Using Broadway to parallelise and stay in control</h2>
<p>Looking to turn your push dataflow into a pull Broadway pipeline? Here’s an introduction.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="xh928fh41e5yqj6971sqb0sy" alt="" data-big=https://cms-assets.abletech.nz/medium_1_Xnr_K7f_Iu_Cqn17q_OQ_Tp_GGIQ_a0b714fca7.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 186px" src="https://cms-assets.abletech.nz/1_Xnr_K7f_Iu_Cqn17q_OQ_Tp_GGIQ_a0b714fca7.png" srcset="https://cms-assets.abletech.nz/small_1_Xnr_K7f_Iu_Cqn17q_OQ_Tp_GGIQ_a0b714fca7.png 500w, https://cms-assets.abletech.nz/medium_1_Xnr_K7f_Iu_Cqn17q_OQ_Tp_GGIQ_a0b714fca7.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Xnr_K7f_Iu_Cqn17q_OQ_Tp_GGIQ_a0b714fca7.png 186w" data-zooming-width="750" data-zooming-height="629" loading="lazy" width="750" height="629"></figure>
</div>
<p>Recently my colleague, Matt, and I were faced with a new Kafka experience: roughly a million messages placed on a highly partitioned Kafka topic, and a consumer which does a simple query to the database for each message — what followed was some impressively fast processing, a thrashed database, and some unhappy users.</p>
<blockquote>
<h4>This is the story of how we wrangled Kafka using Broadway in an environment where we are unable to change Kafka configuration</h4>
</blockquote>
<p><a href="https://sendy.elixir-radar.com/w/EvyA9KEYIVzXIB3LqQyX0g/J8921cxQ4jeEDmbVKDWh02aw/VgvDi33qhTbZhhVmplhybg" target="_blank" rel="noopener noreferrer">This article was also picked up by Elixir Radar in their newsletter</a>.</p>
<h2>KafkaEx background</h2>
<p>We use KafkaEx for consuming the Kafka service in our microservice applications. It’s generally easy to set up, and get going with, and we have few complaints about it.</p>
<p>In order to fully consume messages off a Kafka topic, KafkaEx will create at least one connection per partition. Each connection is in fact a GenConsumer, which is responsible for consuming messages. The implication of this is that if you have an application consuming a topic with 50 partitions, you will automatically end up with a minimum of 50 concurrent consumers. If you naively scale your application horizontally, especially without clustering, you can multiply your instance count by 50 in this case.</p>
<p>Having *instances * partitions *consumers per topic isn’t necessarily an issue in and of itself, however the kicker with KafkaEx is that unless you want to manually set up each of your consumers, you’re going to use a ConsumerGroup which doesn’t provide any mechanism for throttling other than by the mechanism of back-pressure.</p>
<blockquote>
<h4>The flow of data becomes ‘push’ rather than ‘pull’ from the perspective of our application</h4>
</blockquote>
<p>Recall back to the experience we faced where a million messages were coming through, doing a simple database query each. The result was that *instances * consumers *all competing for a database connection over and over caused a huge load on a small database, and prevented APIs from getting access to a connection to the database in a timely fashion.</p>
<h2>Cue the next feature</h2>
<p>While a one-off (ish) of database starvation isn’t the end of the world for this particular application, we had a feature coming up that needed to increase the complexity of this message processor. Instead of a simple database call, it would have to double the database calls and make an outbound API call to another service.</p>
<blockquote>
<h4>Having the processing rate unlimited would now be unacceptable as it may interfere with the performance of other systems</h4>
</blockquote>
<p>At this point I knew we needed to introduce some artificial back-pressure in order to make sure neither our application, nor the external services, were being overwhelmed. KafkaEx will only grab more messages when it thinks it’s finished processing the messages it has in hand already. If we can convince it that it’s not done for a bit longer, we can slow down the process to be <em>controlled</em>.</p>
<p><em>Another option could be to use <a href="https://github.com/dashbitco/broadway_kafka" target="_blank" rel="noopener noreferrer">broadway_kafka </a>at this point, but we have built some systems around the use of KafkaEx which would make this a non-trivial transition</em></p>
<h2>Enter Broadway</h2>
<p>I am a big fan of <a href="https://hexdocs.pm/flow" target="_blank" rel="noopener noreferrer">flow</a>, which builds on top of <a href="https://hexdocs.pm/gen_stage" target="_blank" rel="noopener noreferrer">gen_stage</a>, so I had at least a little awareness of <a href="https://hexdocs.pm/broadway" target="_blank" rel="noopener noreferrer">Broadway</a>. Broadway is a library used to “Build concurrent and multi-stage data ingestion and data processing pipelines with Elixir.”</p>
<p>Broadway offers some key functionalities to us that make it perfect for solving our performance problem:</p>
<ul>
<li><strong>Back-pressure</strong></li>
<li>Automatic acknowledgements at the end of the pipeline</li>
<li>Batching</li>
<li>Fault tolerance with minimal data loss</li>
<li>Graceful shutdown</li>
<li>Built-in testing</li>
<li>Custom failure handling</li>
<li>Ordering and partitioning</li>
<li><strong>Rate-limiting</strong></li>
<li>Metrics</li>
</ul>
<h2>The components of a broadway pipeline</h2>
<p>A Broadway pipeline needs a producer and a consumer. For us, we already produce data through KafkaEx, and we can easily consume that data. The problem with hooking up KafkaEx to Broadway, is that KafkaEx in a <em>ConsumerGroup</em> configuration moves data through a <strong>push</strong> mechanism, whereas Broadway works on the principle of downstream stages *pulling *data.</p>
<p>We can start a simple Broadway pipeline with just the below code, defining the consumer (processor) inline. This is well explained in the Broadway documentation:</p>
<pre><code class="hljs language-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">BroadwayPipeline</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> <span class="hljs-title class_">Broadway</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(_opts) <span class="hljs-keyword">do</span>
    <span class="hljs-title class_">Broadway</span>.start_link(__MODULE__,
      <span class="hljs-symbol">name:</span> <span class="hljs-title class_">BroadwayPipeline</span>,
      <span class="hljs-symbol">producer:</span> [
        <span class="hljs-symbol">module:</span> {<span class="hljs-title class_">Producer</span>, []},
        <span class="hljs-symbol">concurrency:</span> <span class="hljs-number">1</span>,
      ],
      <span class="hljs-symbol">processors:</span> [
        <span class="hljs-symbol">default:</span> [<span class="hljs-symbol">concurrency:</span> <span class="hljs-number">1</span>]
      ]
    )
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@impl</span> <span class="hljs-literal">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_message</span></span>(processor, message, context) <span class="hljs-keyword">do</span>
    <span class="hljs-title class_">IO</span>.inspect(<span class="hljs-string">&quot;Processing <span class="hljs-subst">#{inspect(message)}</span>&quot;</span>)
    
    <span class="hljs-comment"># Call the database, call some APIs, etc</span>
    
    message
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>The part that’s missing is <em>Producer</em>; the glue between KafkaEx and Broadway.</p>
<h2>The glue</h2>
<p>As I mentioned before, we need to turn the data flow from <em>push</em> to <em>pull</em>, and we need to force back-pressure on the Kafka consumer. One way to achieve this is a simple GenServer which queues messages from Kafka, and forces Kafka to wait before putting more messages into the queue.</p>
<pre><code class="hljs language-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">BlockingBuffer</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@moduledoc</span> <span class="hljs-string">&quot;&quot;&quot;
  A process which holds a queue and blocks when the length of the queue
  exceeds a predefined number (100)
  &quot;&quot;&quot;</span>

  <span class="hljs-keyword">use</span> <span class="hljs-title class_">GenServer</span>
  
  <span class="hljs-variable">@max_buffer</span> <span class="hljs-number">100</span> <span class="hljs-comment"># Tweak as required, or move into opts</span>

  <span class="hljs-comment"># GenServer startup</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(opts) <span class="hljs-keyword">do</span>
    <span class="hljs-title class_">GenServer</span>.start_link(__MODULE__, opts, <span class="hljs-symbol">name:</span> __MODULE__)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(__opts) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:ok</span>, {<span class="hljs-symbol">:queue</span>.new(), <span class="hljs-number">0</span>}}
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Public API</span>
  <span class="hljs-variable">@spec</span> push(any) :: <span class="hljs-symbol">:ok</span> | no_return
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">push</span></span>(message) <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">case</span> <span class="hljs-title class_">GenServer</span>.call(__MODULE__, {<span class="hljs-symbol">:push</span>, message}) <span class="hljs-keyword">do</span>
      <span class="hljs-symbol">:wait</span> -&gt;
        wait_time = <span class="hljs-number">100</span> <span class="hljs-comment"># ms - consider tweaking and adding jitter</span>
        <span class="hljs-symbol">:timer</span>.sleep(wait_time) <span class="hljs-comment"># Calling process (KafkaEx consumer) sleeps</span>
        throttled_push(message) <span class="hljs-comment"># Try again!</span>

      <span class="hljs-symbol">:pushed</span> -&gt;
        <span class="hljs-symbol">:ok</span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@spec</span> drain(pos_integer) :: list(any) 
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">drain</span></span>(demand) <span class="hljs-keyword">do</span>
    <span class="hljs-title class_">GenServer</span>.call(__MODULE__, {<span class="hljs-symbol">:drain</span>, demand})
  <span class="hljs-keyword">end</span>

  <span class="hljs-comment"># Message callbacks</span>
  <span class="hljs-variable">@impl</span> <span class="hljs-literal">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_call</span></span>({<span class="hljs-symbol">:push</span>, _message}, _from, {_queue, count} = state) <span class="hljs-keyword">when</span> count &gt; <span class="hljs-variable">@max_buffer</span> <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:reply</span>, <span class="hljs-symbol">:wait</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_call</span></span>({<span class="hljs-symbol">:push</span>, message}, _from, {queue, count}) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:reply</span>, <span class="hljs-symbol">:pushed</span>, {<span class="hljs-symbol">:queue</span>.<span class="hljs-keyword">in</span>(message, queue), count + <span class="hljs-number">1</span>}}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_call</span></span>({<span class="hljs-symbol">:drain</span>, demand}, _from, {queue, count}) <span class="hljs-keyword">when</span> demand &gt; <span class="hljs-number">0</span> <span class="hljs-keyword">do</span>
    {items, queue} =
      <span class="hljs-title class_">Enum</span>.reduce((<span class="hljs-number">0</span>..demand), {[], queue}, <span class="hljs-keyword">fn</span> _i, {items, queue} -&gt;
        <span class="hljs-keyword">case</span> <span class="hljs-symbol">:queue</span>.out(queue) <span class="hljs-keyword">do</span>
          {{<span class="hljs-symbol">:value</span>, value}, queue} -&gt;
            {[value | items], queue}

          {<span class="hljs-symbol">:empty</span>, queue} -&gt;
            {items, queue}
        <span class="hljs-keyword">end</span>
      <span class="hljs-keyword">end</span>)

    {<span class="hljs-symbol">:reply</span>, <span class="hljs-title class_">Enum</span>.reverse(items), {queue, count - length(items)}}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Now we’ve got a process that converts <em>push</em> to <em>pull</em>, we can write a producer. This can simply pull from this queue when downstream stages ask it for demand.</p>
<p>We also need to store how much demand the producer has been asked for, so that if there are no Kafka messages for a time, then some start being received again, we can kickstart the pipeline once more.</p>
<pre><code class="hljs language-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">Producer</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> <span class="hljs-title class_">GenStage</span>

  <span class="hljs-variable">@behaviour</span> <span class="hljs-title class_">Broadway</span>.<span class="hljs-title class_">Producer</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_opts) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:producer</span>, <span class="hljs-number">0</span>}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_demand</span></span>(demand, stored_demand) <span class="hljs-keyword">do</span>
    total_demand = demand + stored_demand

    messages =
      total_demand
      |&gt; <span class="hljs-title class_">BlockingBuffer</span>.drain()
      |&gt; <span class="hljs-title class_">Enum</span>.map(&amp;%<span class="hljs-title class_">Broadway</span>.<span class="hljs-title class_">Message</span>{<span class="hljs-symbol">data:</span> &amp;<span class="hljs-number">1</span>, <span class="hljs-symbol">acknowledger:</span> {<span class="hljs-title class_">Broadway</span>.<span class="hljs-title class_">NoopAcknowledger</span>, <span class="hljs-literal">nil</span>, <span class="hljs-literal">nil</span>}}) <span class="hljs-comment"># Create a broadway message from the kafka message</span>
      
    message_count = length(messages)

    <span class="hljs-comment"># If we&#x27;ve run out of messages, we need to kickstart the pipeline again</span>
    <span class="hljs-comment"># so, try drain again soon</span>
    <span class="hljs-keyword">if</span> message_count &lt; total_demand <span class="hljs-keyword">do</span>
      <span class="hljs-title class_">Process</span>.send_after(self(), <span class="hljs-symbol">:kick_start</span>, <span class="hljs-number">1_000</span>)
    <span class="hljs-keyword">end</span>

    {<span class="hljs-symbol">:noreply</span>, messages, total_demand - message_count}
  <span class="hljs-keyword">end</span>
  
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>(<span class="hljs-symbol">:kick_start</span>, stored_demand) <span class="hljs-keyword">do</span>
    handle_demand(<span class="hljs-number">0</span>, stored_demand)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>This is great — now we can change our Kafka consumer to use <em>BlockingBuffer.push(message)</em> and update the <em>BroadwayPipeline.handle_message/3</em> to do the processing of the Kafka message.</p>
<p>This will move all the concurrent processing into the Broadway pipeline. As our configuration stands, we’re going to be processing in a single process, at an unlimited rate… but Broadway lets us change this easily in configuration. Let’s alter the way we start the Broadway pipeline now. Let’s imagine we want to process a million messages a day, and we want to restrict the open database connections per instance to five — we can easily configure that like below:</p>
<pre><code class="hljs language-elixir">  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(_opts) <span class="hljs-keyword">do</span>
    <span class="hljs-title class_">Broadway</span>.start_link(__MODULE__,
      <span class="hljs-symbol">name:</span> <span class="hljs-title class_">BroadwayPipeline</span>,
      <span class="hljs-symbol">producer:</span> [
        <span class="hljs-symbol">module:</span> {<span class="hljs-title class_">Producer</span>, []},
        <span class="hljs-symbol">concurrency:</span> <span class="hljs-number">1</span>,
        <span class="hljs-symbol">rate_limiting:</span> [<span class="hljs-symbol">allowed_messages:</span> <span class="hljs-number">11</span>, <span class="hljs-symbol">interval:</span> <span class="hljs-number">1000</span>] <span class="hljs-comment"># 11/s is just over 950k / day</span>
      ],
      <span class="hljs-symbol">processors:</span> [
        <span class="hljs-symbol">default:</span> [<span class="hljs-symbol">concurrency:</span> <span class="hljs-number">5</span>]
      ]
    )
  <span class="hljs-keyword">end</span>
</code></pre>
<h2>Summary</h2>
<p>Usually people look to Elixir to parallelise their workload, but sometimes it’s easy to over-parallelise and shoot yourself in the foot. By making use of Broadway, we can easily parallelise while staying in control. This is just a taste of what Broadway offers — I’d encourage you to <a href="https://hexdocs.pm/broadway" target="_blank" rel="noopener noreferrer">check out their documentation</a> to learn more about its functionality and use cases.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Seamless API mocking for front-end development</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Common API mocking scenarios and solutions using Mock Service Worker</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l2k3lqm3f4bo6itrgp89us9s" alt="Image by [Ralf Kunze](https://pixabay.com/users/realworkhard-23566/) from [Pixabay](https://pixabay.com/photos/balance-stones-stack-110850/)" data-big=https://cms-assets.abletech.nz/large_16_Az4p9_Vifd_BVMEFH_Mru_P_Yg_cd18bbbe85.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/16_Az4p9_Vifd_BVMEFH_Mru_P_Yg_cd18bbbe85.jpeg" srcset="https://cms-assets.abletech.nz/large_16_Az4p9_Vifd_BVMEFH_Mru_P_Yg_cd18bbbe85.jpeg 1000w, https://cms-assets.abletech.nz/small_16_Az4p9_Vifd_BVMEFH_Mru_P_Yg_cd18bbbe85.jpeg 500w, https://cms-assets.abletech.nz/medium_16_Az4p9_Vifd_BVMEFH_Mru_P_Yg_cd18bbbe85.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_16_Az4p9_Vifd_BVMEFH_Mru_P_Yg_cd18bbbe85.jpeg 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<p><em>Image by <a href="https://pixabay.com/users/realworkhard-23566/" target="_blank" rel="noopener noreferrer">Ralf Kunze</a> from <a href="https://pixabay.com/photos/balance-stones-stack-110850/" target="_blank" rel="noopener noreferrer">Pixabay</a></em></p>
<p>One of the more common recurring pain points I’ve seen in front-end development workflows is API integration. APIs are, more often than not, a cornerstone of our web applications, a hard dependency, so it’s not too surprising that they can also be a source of issues for front-end developers. Here are a few scenarios that I’ve regularly encountered on various projects:</p>
<ul>
<li>
<p>Testing: how do we test our API integrations with confidence?</p>
</li>
<li>
<p>Incomplete APIs: we start building a feature, we’re two days deep and we discover that the API isn’t returning what we need, we’re blocked! Can we continue before the API discrepancy is resolved?</p>
</li>
<li>
<p>Non-existent APIs: our team is swarming, we’re in a time crunch, we need to ship this feature fast and API development hasn’t started yet. Can we deliver a turnkey UI without the API?</p>
</li>
</ul>
<p>I’ve tried a variety of solutions to these problems in the past but I was never entirely happy with the results. That is, until I discovered <a href="https://mswjs.io/" target="_blank" rel="noopener noreferrer">Mock Service Worker</a> (MSW): a service worker based library designed to intercept and respond to, forward or modify API interactions at the network level (I know we’re talking about service workers here but don’t worry, MSW provides support for Node environments too). In this article we’ll take a look at each of the above scenarios and how we can address them using MSW.</p>
<h3>First things first</h3>
<p>I’ve created a small application with <a href="https://create-react-app.dev/docs/getting-started/" target="_blank" rel="noopener noreferrer">Create React App</a> to demonstrate working solutions as we go. The app integrates with the public <a href="https://openlibrary.org/developers/api" target="_blank" rel="noopener noreferrer">Open Library API</a>, starting with a simple document search. The <a href="https://github.com/kalopilato/api-request-mocking-demo/tree/main" target="_blank" rel="noopener noreferrer">main branch</a> has the complete, finished source code for this article, and there is a <a href="https://github.com/kalopilato/api-request-mocking-demo/pull/1" target="_blank" rel="noopener noreferrer">pull request</a> containing commits for each scenario as we go. If you’d like to try these solutions out for yourself feel free to check out the <a href="https://github.com/kalopilato/api-request-mocking-demo/tree/f3dbd7283bcb2f3818f423452aeb5ce3c188fef2" target="_blank" rel="noopener noreferrer">base commit for the PR</a> and code along!</p>
<p><em><strong>NOTE:</strong> while the demo application is built with React, MSW itself is library/framework agnostic so don’t let that hold you back if you’re coming from other frameworks!</em></p>
<h3>Testing</h3>
<p>API integration testing could easily be a lengthy article in itself but let’s just say that I’ve tried a range of techniques in the past and usually end up settling on dependency injection or HTTP client mocking. These approaches have their merits in certain use cases, for example dependency injection can be particularly useful when unit testing shared components, but in the majority of cases what I’ve really wanted is to integration test critical application flows <em>as close to the application’s boundary as possible</em>. In the case of our demo application, I want to know that when a user performs a document search the API is called and the response is handled correctly.</p>
<p>MSW gives us a mechanism to mock APIs at the network layer of our runtime environment, it’s about as close as we can get to calling a real API while remaining within the bounds of our local test framework. It’s even simple to set up. Start by installing <strong>msw</strong>:</p>
<pre><code class="hljs">yarn <span class="hljs-built_in">add</span> -D msw
</code></pre>
<p>Since our tests aren’t running in a browser we can’t technically use service workers in our test suite. MSW has this covered by providing us with server-like functionality for use in Node environments. We’ll use this to manage our API request mocking for testing:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./apiMocks/server.js</span>
<span class="hljs-keyword">import</span> { rest } from <span class="hljs-string">&quot;msw&quot;</span>;
<span class="hljs-keyword">import</span> { setupServer } from <span class="hljs-string">&quot;msw/node&quot;</span>;

<span class="hljs-keyword">import</span> searchResults from <span class="hljs-string">&quot;./fixtures/documentSearch&quot;</span>;

<span class="hljs-keyword">const</span> handlers = [
  rest.<span class="hljs-keyword">get</span>(<span class="hljs-string">&quot;https://openlibrary.org/search.json&quot;</span>, <span class="hljs-keyword">async</span> (_req, res, ctx) =&gt;
    res(ctx.json(searchResults))
  ),
];

<span class="hljs-keyword">const</span> server = setupServer(...handlers);

<span class="hljs-keyword">export</span> { server };
</code></pre>
<p>This small code snippet packs a punch. We’ve set up a server, and defined a <strong>handler</strong> for the search API’s URL, returning a JSON fixture when it is executed. A few notes:</p>
<ul>
<li>
<p>Directory structure: as you can see I’ve added an <strong>apiMocks</strong> directory to the project to house the <strong>server.js</strong> file. I’ve also added a <strong>fixtures</strong> directory to contain all of the JSON examples for our API responses — this can make it easier to ensure consistency between our mocked responses and API contracts</p>
</li>
<li>
<p>Handlers: if you have experience with setting up server side routes in Express or similar then the signature of these handlers will look familiar. We define a handler to match on a given path with a given HTTP verb and pass it a <strong>resolver</strong> to respond to, forward or modify any intercepted requests matching on that path and verb. In this case we’re just returning our JSON fixture, but you’ll see in the following scenarios that we can do much more with these resolvers</p>
</li>
<li>
<p>Example code: if you’re referring to the <a href="https://github.com/kalopilato/api-request-mocking-demo" target="_blank" rel="noopener noreferrer">demo app source</a> you’ll notice that the code is structured a little differently and a helper function is used to create the handlers, you can ignore that for now but a pattern like this is handy as you scale up your API integrations and need to override handlers for testing</p>
</li>
</ul>
<p>Now we need to start up our server when we run our test suite. We also reset our server’s handlers in between tests to clear out any overrides from the previous test:</p>
<pre><code class="hljs"><span class="hljs-regexp">//</span> ./setupTests
...
beforeAll(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> server.listen({ onUnhandledRequest: <span class="hljs-string">&#x27;warn&#x27;</span> }));
afterEach(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> server.resetHandlers());
afterAll(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> server.close());
...
</code></pre>
<p><em><strong>Note:</strong> when I first started using MSW for testing I added wildcard handlers to highlight any unmocked API requests in my test runs. Since then MSW have added a configuration option to take care of this for us: <strong>onUnhandledRequest</strong></em></p>
<p>And now for our tests (using <a href="https://testing-library.com/docs/react-testing-library/intro" target="_blank" rel="noopener noreferrer">React Testing Library</a>’s test utilities):</p>
<pre><code class="hljs"><span class="hljs-keyword">import</span> { rest } <span class="hljs-keyword">from</span> &quot;msw&quot;;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">server</span> } <span class="hljs-keyword">from</span> &quot;./apiMocks/server&quot;;
<span class="hljs-keyword">import</span> { errorResponse } <span class="hljs-keyword">from</span> &quot;./apiMocks/fixtures/documentSearch&quot;;

// MSW responds <span class="hljs-keyword">to</span> our <span class="hljs-keyword">search</span> request <span class="hljs-keyword">with</span> the <span class="hljs-keyword">handler</span> defined <span class="hljs-keyword">in</span> `<span class="hljs-keyword">server</span>.js` <span class="hljs-keyword">by</span> <span class="hljs-keyword">default</span>
it(&quot;searches for a document and displays the results&quot;, async () =&gt; {
  render(&lt;App /&gt;);

  let searchField = screen.getByLabelText(/^<span class="hljs-keyword">search</span>$/i);
  userEvent.<span class="hljs-keyword">type</span>(searchField, &quot;the secret life of cats&quot;);
  let searchButton = screen.getByRole(&quot;button&quot;, { <span class="hljs-type">name</span>: /^<span class="hljs-keyword">search</span>$/i });
  userEvent.click(searchButton);

  await screen.findByText(/^the secret life <span class="hljs-keyword">of</span> cats <span class="hljs-keyword">by</span> claire bessant$/i);
  screen.getByText(/^the secret life <span class="hljs-keyword">of</span> cats <span class="hljs-keyword">by</span> ralph reese$/i);
});

// <span class="hljs-keyword">And</span> we can override the <span class="hljs-keyword">default</span> <span class="hljs-keyword">handler</span> <span class="hljs-keyword">in</span> `<span class="hljs-keyword">server</span>.js` <span class="hljs-keyword">to</span> define responses <span class="hljs-keyword">case</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">case</span>
it(&quot;searches for a document and displays an error message when the request fails&quot;, async () =&gt; {
  render(&lt;App /&gt;);

  <span class="hljs-keyword">server</span>.use(
    rest.<span class="hljs-keyword">get</span>(&quot;https://openlibrary.org/search.json&quot;, async (_req, res, ctx) =&gt; {
      <span class="hljs-keyword">return</span> res(ctx.status(<span class="hljs-number">500</span>), ctx.json(errorResponse));
    })
  );

  let searchField = screen.getByLabelText(/^<span class="hljs-keyword">search</span>$/i);
  userEvent.<span class="hljs-keyword">type</span>(searchField, &quot;the secret life of cats&quot;);
  let searchButton = screen.getByRole(&quot;button&quot;, { <span class="hljs-type">name</span>: /^<span class="hljs-keyword">search</span>$/i });
  userEvent.click(searchButton);

  await screen.findByText(/^something went wrong: <span class="hljs-type">internal</span> <span class="hljs-keyword">server</span> error$/i);
});
</code></pre>
<p>As you can see it didn’t take much effort to set this up, and we now have a low-friction pattern for integration testing components with API dependencies, and a higher degree of confidence that our critical flow works than we would get from dependency injection or HTTP client mocking. Taking this a step further, you could even use the request handlers to validate outbound requests against the API contracts e.g. <a href="https://mswjs.io/docs/recipes/request-assertions#include-failure-scenarios" target="_blank" rel="noopener noreferrer">handling failure scenarios</a>.</p>
<h3>Incomplete APIs</h3>
<p>So we have an app that can search The Open Library for documents and display matches. Now imagine that we have a requirement to display a link to purchase the document, and the URL should be provided by the search API, but it currently isn’t. At this point I would advocate for resolving the API discrepancy before proceeding, but let’s just say that we’ve talked it through with our team and agreed to continue with an updated API spec.</p>
<p>So what are our options? We could insert the missing field into the API response (e.g. in our promise’s <strong>then</strong> block), we could hardcode the link into our component, or anything in between, but these approaches all involve polluting our application code with temporary patches, and that’s without even considering our tests. So what if we could just modify the response <em>before</em> it hits our application code? Then we could build our feature as if we have the required API support, music to my ears. We’ll set up MSW to do just that.</p>
<p>First we use MSW’s CLI to generate a service worker in our public directory:</p>
<pre><code class="hljs">npx msw <span class="hljs-keyword">init</span> ./<span class="hljs-keyword">public</span>
</code></pre>
<p>Next we configure the worker — this is essentially creating a browser equivalent of the <strong>server.js</strong> file we use in our test setup:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./apiMocks/browser.js</span>
<span class="hljs-keyword">import</span> { rest, setupWorker } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;msw&quot;</span>;

<span class="hljs-keyword">const</span> handlers = [
  rest.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;https://openlibrary.org/search.json&quot;</span>, <span class="hljs-title function_">async</span> (req, res, ctx) =&gt; {
    <span class="hljs-comment">// Forward the intercepted request on to the real API and capture the response</span>
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> ctx.<span class="hljs-title function_">fetch</span>(req);
    <span class="hljs-keyword">const</span> responseData = <span class="hljs-keyword">await</span> response.<span class="hljs-title function_">json</span>();

    <span class="hljs-comment">// Patch the response before returning it to the caller</span>
    responseData.<span class="hljs-property">docs</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-function">(<span class="hljs-params">doc</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (doc.<span class="hljs-property">store_link</span> === <span class="hljs-literal">undefined</span>)
        doc.<span class="hljs-property">store_link</span> = <span class="hljs-string">`https://fake.store.com/<span class="hljs-subst">${
          doc.isbn?.length ? doc.isbn[<span class="hljs-number">0</span>] : <span class="hljs-string">&quot;&quot;</span>
        }</span>`</span>;
    });
    <span class="hljs-keyword">return</span> <span class="hljs-title function_">res</span>(ctx.<span class="hljs-title function_">json</span>(responseData));
  }),
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> worker = <span class="hljs-title function_">setupWorker</span>(...handlers);
</code></pre>
<p>And finally we start up our service worker on app load (we generally don’t want to run up this service worker in deployed environments so we’ll load it conditionally):</p>
<pre><code class="hljs">// ./src/index.js
...

if (process.env.NODE_ENV <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">&quot;development&quot;</span>) {
  const { worker } <span class="hljs-operator">=</span> require(<span class="hljs-string">&quot;./apiMocks/browser&quot;</span>)<span class="hljs-comment">;</span>
  worker.start()<span class="hljs-comment">;</span>
}

...
</code></pre>
<p>If you reload your app, run a document search and inspect the response you should see that a <strong>store_link</strong> field appears on every document. You can now update your fixture and write your feature and tests as if the field was there all along. The best parts? Because we’ve only patched the response if the field is missing, once the API is updated the correct values will flow through the handler, and our response patch is <em>outside</em> of our application code; the only cleanup remaining is to delete the patched handler from our worker.</p>
<h3>Non-existent APIs</h3>
<p>So what if you need to implement a UI for an API that hasn’t been built yet? As long as you have a stable API schema (again, I’d advocate for having the API built first if possible) you can combine the above approaches to completely mock the API for both development and testing, without hacks or patches in your application code, and have your code practically ready to ship when the API is complete. All it takes is:</p>
<ol>
<li>
<p>Add a handler to <strong>server.js</strong> (for testing) and <strong>browser.js</strong> (for development), returning the desired data/fixture</p>
</li>
<li>
<p>Write your tests and code to interact with the API as normal, following the API schema</p>
</li>
<li>
<p>Once the API is available, simply delete the handler from <strong>browser.js</strong>!</p>
</li>
</ol>
<p>Let’s do a quick example. We’ll add some basic “reading list” functionality to our document listing:</p>
<ol>
<li>
<p>Mark any documents that have been added to the reading list (<strong>GET</strong> the <strong>/reading_list</strong> and match it against the document list)</p>
</li>
<li>
<p>Support adding a document to the reading list if it has not already been added (<strong>POST</strong> an id to <strong>/reading_list/add</strong> to add a document)</p>
</li>
</ol>
<p>Yep, you guessed it, these APIs don’t exist. We’ll mock them out, and this time we’ll go a step further and use the intercepted request’s body to maintain a reading list in our mocks:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./apiMocks/browser.js</span>
<span class="hljs-keyword">const</span> readingList = { works: [] };

...

<span class="hljs-keyword">const</span> handlers = [
  ...,
  <span class="hljs-comment">// Mock API to fetch the Reading List</span>
  rest.<span class="hljs-keyword">get</span>(
    <span class="hljs-string">&quot;https://openlibrary.org/reading_list&quot;</span>,
    <span class="hljs-keyword">async</span> (_req, res, ctx) =&gt; {
      <span class="hljs-keyword">return</span> res(ctx.json(readingList));
    }
  ),
  <span class="hljs-comment">// Mock API to add a Document to the Reading List</span>
  rest.post(
    <span class="hljs-string">&quot;https://openlibrary.org/reading_list/add&quot;</span>,
    <span class="hljs-keyword">async</span> (req, res, ctx) =&gt; {
      <span class="hljs-comment">// We take the workId from the request body and add it to our stubbed reading list</span>
      let { workId } = req.body;
      readingList.works.push(workId);
      <span class="hljs-keyword">return</span> res(ctx.status(<span class="hljs-number">200</span>), ctx.delay(<span class="hljs-number">500</span>));
    }
  ),
];
</code></pre>
<p>That’s all there is to it. Now we can call those APIs in our application code, write tests, and even observe documents being added to the reading list. As with the previous scenario, once the APIs are ready, we just delete these handlers from the worker and we’re good to go!</p>
<h3>Final thoughts</h3>
<p>Now that we’ve taken a look at a few common API mocking problem scenarios I hope that you can see how much value we can get out of the Mock Service Worker library for front-end development. The highlights for me are:</p>
<ul>
<li>
<p>Non-invasive mocking: mock APIs <em>outside</em> of our application code without relying on patterns like dependency injection</p>
</li>
<li>
<p>Flexible and familiar request handlers: write server-like mocks without managing an actual HTTP server</p>
</li>
<li>
<p>Ease of use: very good developer ergonomics for setting up, defining and overriding API mocks</p>
</li>
<li>
<p>Great documentation: <a href="https://mswjs.io/docs/" target="_blank" rel="noopener noreferrer">take a look</a>, I don’t think I’ve come across any undocumented features/configurations in the time I’ve been using MSW</p>
</li>
<li>
<p>Support for both REST <em>and</em> GraphQL APIs</p>
</li>
<li>
<p>Support for both Browser <em>and</em> Node environments</p>
</li>
<li>
<p>Direct control of mocked responses: we choose how to manage our response data, we could even use an in-memory database if we wanted</p>
</li>
</ul>
<p>Next time you’re considering how to mock API interactions give MSW a go!</p>
<p><em>If you’d like you can view the <a href="https://www.kalopilato.com/blog/seamless-api-mocking-for-front-end-development" target="_blank" rel="noopener noreferrer">original article on my blog</a>.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Retest — Docker & Feature Specs</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Last year, I read the amazing 99 Bottles of OOP by Sandi Metz, Katrina Owen &amp; TJ Stankus and decided to create a tool to help me refactor code based on the method described in the book. I work in a consultancy and get to touch multiple codebases regularly. I wanted a tool that would allow me to refactor code on any ruby projects with no setup. Retest was born.</h2>
<p><strong>Retest promise</strong></p>
<blockquote>
<p><em>A simple CLI to watch file changes and run their matching ruby specs. Works on any ruby projects with no setup.</em></p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l8lzga2nhf3176n8r8cegfwu" alt="CI of retest v1.0.0" data-big=https://cms-assets.abletech.nz/large_0p_V_Oh_C1z_U_Dx_Decj6_C_3f5190f9bc.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 204px" src="https://cms-assets.abletech.nz/0p_V_Oh_C1z_U_Dx_Decj6_C_3f5190f9bc.png" srcset="https://cms-assets.abletech.nz/large_0p_V_Oh_C1z_U_Dx_Decj6_C_3f5190f9bc.png 1000w, https://cms-assets.abletech.nz/small_0p_V_Oh_C1z_U_Dx_Decj6_C_3f5190f9bc.png 500w, https://cms-assets.abletech.nz/medium_0p_V_Oh_C1z_U_Dx_Decj6_C_3f5190f9bc.png 750w, https://cms-assets.abletech.nz/thumbnail_0p_V_Oh_C1z_U_Dx_Decj6_C_3f5190f9bc.png 204w" data-zooming-width="1000" data-zooming-height="763" loading="lazy" width="1000" height="763"></figure>
</div>
<p><em>CI of retest v1.0.0</em></p>
<h2>Testing the gem</h2>
<p>For some time I relied only on unit tests and manual testing of different ruby setups like Rails, Ruby ad-hoc, Hanami. This was becoming difficult as each setup can be paired with Minitest or RSpec.</p>
<p>End-to-end (E2E) Testing retest is an interesting challenge. I need to run tests locally and on GitHub actions for a specific git branch. The latest state of the gem must be built and tested on multiple ruby setups. For each ruby setup, I need to test whether the gem:</p>
<ul>
<li>
<p>finds the appropriate test file</p>
</li>
<li>
<p>uses the correct test command</p>
</li>
<li>
<p>displays the correct output after making file changes to the repository</p>
</li>
</ul>
<p><strong>Solution: GitHub strategies paired with minimal Docker repositories.</strong></p>
<h2>Using Docker</h2>
<p>I have a love/hate relationship with Docker. We use it extensively at work. I understand its benefits and why people use it but more often than not Docker is a slow and frustrating experience. Unless you have an image lying around, you know you’re up for a treat when a Docker app that hasn’t been touched for a year needs an issue fixed. Fixing Docker often takes longer than fixing the issue itself…</p>
<p>However, I recently used Docker to test <a href="https://github.com/AlexB52/retest" target="_blank" rel="noopener noreferrer">retest</a> on different Ruby environments. Docker allows me to spin up different ruby apps in a container with retest installed.</p>
<p>Currently, Retest is being tested on:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pyuoaypwl5kqpevkd9ub0y3g" alt="test matrix" data-big=https://cms-assets.abletech.nz/small_1zt_A_Iul_UFH_3_Tp2ro7w73r_Q_ad056dd278.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1zt_A_Iul_UFH_3_Tp2ro7w73r_Q_ad056dd278.png" srcset="https://cms-assets.abletech.nz/small_1zt_A_Iul_UFH_3_Tp2ro7w73r_Q_ad056dd278.png 500w, https://cms-assets.abletech.nz/thumbnail_1zt_A_Iul_UFH_3_Tp2ro7w73r_Q_ad056dd278.png 245w" data-zooming-width="500" data-zooming-height="250" loading="lazy" width="500" height="250"></figure>
</div>
<p><em>test matrix</em></p>
<p><em><strong>Bonus: Git commands are tested on a dedicated git-ruby docker container for the -diff feature.</strong></em></p>
<p>Check out the <a href="https://github.com/AlexB52/retest" target="_blank" rel="noopener noreferrer">gem</a>, those setups live in the <code>features</code> folder. All feature specs follow the same structure.</p>
<h3>GitHub actions</h3>
<p>I use a strategy to dynamically spin up 6 jobs (one per ruby app) and call its corresponding test command.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jnrc15pe9l6sp2uhdxk3l5t8" alt="feature specs configuration for GitHub actions" data-big=https://cms-assets.abletech.nz/large_1_Bpz5_Q_l_F299y47_Irv_Wvj4w_3b7422cdfd.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Bpz5_Q_l_F299y47_Irv_Wvj4w_3b7422cdfd.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Bpz5_Q_l_F299y47_Irv_Wvj4w_3b7422cdfd.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Bpz5_Q_l_F299y47_Irv_Wvj4w_3b7422cdfd.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Bpz5_Q_l_F299y47_Irv_Wvj4w_3b7422cdfd.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Bpz5_Q_l_F299y47_Irv_Wvj4w_3b7422cdfd.jpeg 245w" data-zooming-width="1000" data-zooming-height="518" loading="lazy" width="1000" height="518"></figure>
</div>
<p><em>feature specs configuration for GitHub actions</em></p>
<h3>Test commands</h3>
<p>A setup can be tested on GitHub actions and locally via a dedicated <code>bin/test</code> command. In this example, we run the <code>bin/test/rails-app</code> for the rails app using minitest.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jxyotq5xqh989vp4gsitomyh" alt="test command to run on GitHub action or locally" data-big=https://cms-assets.abletech.nz/large_1azs_MR_Z_Dm_Y3_CI_Agv_I6_Odd_Q_e3e701b41f.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1azs_MR_Z_Dm_Y3_CI_Agv_I6_Odd_Q_e3e701b41f.jpeg" srcset="https://cms-assets.abletech.nz/large_1azs_MR_Z_Dm_Y3_CI_Agv_I6_Odd_Q_e3e701b41f.jpeg 1000w, https://cms-assets.abletech.nz/small_1azs_MR_Z_Dm_Y3_CI_Agv_I6_Odd_Q_e3e701b41f.jpeg 500w, https://cms-assets.abletech.nz/medium_1azs_MR_Z_Dm_Y3_CI_Agv_I6_Odd_Q_e3e701b41f.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1azs_MR_Z_Dm_Y3_CI_Agv_I6_Odd_Q_e3e701b41f.jpeg 245w" data-zooming-width="1000" data-zooming-height="305" loading="lazy" width="1000" height="305"></figure>
</div>
<p><em>test command to run on GitHub action or locally</em></p>
<h3>Dockerfile configuration</h3>
<p>The dockerfile fits the setup tested, in this case, a rails app without webpack :) One thing to note is that retest is also installed with <code>RUN gem install retest.gem</code></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kcamala9fsklljpnb6oyvq5z" alt="Docker configuration for the rails app using minitest" data-big=https://cms-assets.abletech.nz/large_1_Km_Xhy7_VOX_Zgv7r_SRE_Mblf_A_852b76cb66.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 180px" src="https://cms-assets.abletech.nz/1_Km_Xhy7_VOX_Zgv7r_SRE_Mblf_A_852b76cb66.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Km_Xhy7_VOX_Zgv7r_SRE_Mblf_A_852b76cb66.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Km_Xhy7_VOX_Zgv7r_SRE_Mblf_A_852b76cb66.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Km_Xhy7_VOX_Zgv7r_SRE_Mblf_A_852b76cb66.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Km_Xhy7_VOX_Zgv7r_SRE_Mblf_A_852b76cb66.jpeg 180w" data-zooming-width="1000" data-zooming-height="867" loading="lazy" width="1000" height="867"></figure>
</div>
<p><em>Docker configuration for the rails app using minitest</em></p>
<h3>The E2E test file</h3>
<p>Each app has a <code>retest/retest_test.rb</code> file which is a test suite tailored for the setup under test. Here are some examples of tests used.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="oysqhkvnqec2wlu29vsmr3zb" alt="Integration test examples run for a rails setup" data-big=https://cms-assets.abletech.nz/large_1w_N_EF_1z_Tr_DT_Ba_S9ds_JQ_4p_A_aa7d47c54a.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 207px" src="https://cms-assets.abletech.nz/1w_N_EF_1z_Tr_DT_Ba_S9ds_JQ_4p_A_aa7d47c54a.jpeg" srcset="https://cms-assets.abletech.nz/large_1w_N_EF_1z_Tr_DT_Ba_S9ds_JQ_4p_A_aa7d47c54a.jpeg 1000w, https://cms-assets.abletech.nz/small_1w_N_EF_1z_Tr_DT_Ba_S9ds_JQ_4p_A_aa7d47c54a.jpeg 500w, https://cms-assets.abletech.nz/medium_1w_N_EF_1z_Tr_DT_Ba_S9ds_JQ_4p_A_aa7d47c54a.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1w_N_EF_1z_Tr_DT_Ba_S9ds_JQ_4p_A_aa7d47c54a.jpeg 207w" data-zooming-width="1000" data-zooming-height="753" loading="lazy" width="1000" height="753"></figure>
</div>
<p><em>Integration test examples run for a rails setup</em></p>
<h2>Interesting notes</h2>
<h3>Launching retest on a separate process</h3>
<p>Because retest needs a separate window to display test results as people change files, I spawn a process in the container that runs retest and I write into a log file. I spawn a retest process per test.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="oxq5ggtx5e10wfq62kimnnqg" alt="Helpers to spawn and kill a ruby process in test suite" data-big=https://cms-assets.abletech.nz/large_15zumx_MU_8iwk_HL_Xaw_O7gnkg_50cb9c62b7.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/15zumx_MU_8iwk_HL_Xaw_O7gnkg_50cb9c62b7.jpeg" srcset="https://cms-assets.abletech.nz/large_15zumx_MU_8iwk_HL_Xaw_O7gnkg_50cb9c62b7.jpeg 1000w, https://cms-assets.abletech.nz/small_15zumx_MU_8iwk_HL_Xaw_O7gnkg_50cb9c62b7.jpeg 500w, https://cms-assets.abletech.nz/medium_15zumx_MU_8iwk_HL_Xaw_O7gnkg_50cb9c62b7.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_15zumx_MU_8iwk_HL_Xaw_O7gnkg_50cb9c62b7.jpeg 245w" data-zooming-width="1000" data-zooming-height="377" loading="lazy" width="1000" height="377"></figure>
</div>
<p><em>Helpers to spawn and kill a ruby process in test suite</em></p>
<h3>Helper methods</h3>
<p>Each repository has a group of helper methods to imitate the creation, update and deletion of a file in the repository under test (and trigger retest).</p>
<p>Each of those helper methods is implementing a different sleeping time based on the repository type. A rails app will take longer to run a test than a ruby program that is why the sleeping time is 10 seconds for a rails app but 1 second on a ruby program.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pj0hu7nkurf9khjhjyt80pfu" alt="Helper methods to imitate a developer working on the repository" data-big=https://cms-assets.abletech.nz/large_1c0_MB_4u_Ltv_P7_Ce_YKPJ_4_FDOA_1203d0b38c.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1c0_MB_4u_Ltv_P7_Ce_YKPJ_4_FDOA_1203d0b38c.jpeg" srcset="https://cms-assets.abletech.nz/large_1c0_MB_4u_Ltv_P7_Ce_YKPJ_4_FDOA_1203d0b38c.jpeg 1000w, https://cms-assets.abletech.nz/small_1c0_MB_4u_Ltv_P7_Ce_YKPJ_4_FDOA_1203d0b38c.jpeg 500w, https://cms-assets.abletech.nz/medium_1c0_MB_4u_Ltv_P7_Ce_YKPJ_4_FDOA_1203d0b38c.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1c0_MB_4u_Ltv_P7_Ce_YKPJ_4_FDOA_1203d0b38c.jpeg 245w" data-zooming-width="1000" data-zooming-height="568" loading="lazy" width="1000" height="568"></figure>
</div>
<p><em>Helper methods to imitate a developer working on the repository</em></p>
<h3>CI Time</h3>
<p>Overall CI runs in less than three minutes as each docker job is run in parallel and unit tests are run in less than 30 seconds.</p>
<p><em>Originally published at <a href="https://alexbarret.com" target="_blank" rel="noopener noreferrer">https://alexbarret.com</a></em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Reflect on past commits</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Two minute tip</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s35cr893d5rt5b1uldo4whgl" alt="Photo by [Yancy Min](https://unsplash.com/@yancymin?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/git?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)" data-big=https://cms-assets.abletech.nz/large_1_Rpd_CB_8ss_H_Vk6dp_HL_31r_Kb_A_07765f1f78.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 235px" src="https://cms-assets.abletech.nz/1_Rpd_CB_8ss_H_Vk6dp_HL_31r_Kb_A_07765f1f78.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Rpd_CB_8ss_H_Vk6dp_HL_31r_Kb_A_07765f1f78.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Rpd_CB_8ss_H_Vk6dp_HL_31r_Kb_A_07765f1f78.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Rpd_CB_8ss_H_Vk6dp_HL_31r_Kb_A_07765f1f78.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Rpd_CB_8ss_H_Vk6dp_HL_31r_Kb_A_07765f1f78.jpeg 235w" data-zooming-width="1000" data-zooming-height="663" loading="lazy" width="1000" height="663"></figure>
</div>
<p><em>Photo by <a href="https://unsplash.com/@yancymin?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Yancy Min</a> on <a href="https://unsplash.com/s/photos/git?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash</a></em></p>
<p>One of Abletech’s Team Values is Continuous Improvement. We believe in getting better at what we do, a little bit more, every day. We are committed to ‘do the right thing’ and always leave a codebase in better shape than we found it.</p>
<p>Programming is all about writing code that is easy to understand. Why? Developers write code but, most importantly, developers read code. A lot of it. Reading code that is hard to understand is frustrating, scary and slows down productivity.</p>
<h3>Can you understand your old code?</h3>
<p>Let’s face it, any new commit is likely to add a bit of technical debt. You probably already get code reviews but they can be rushed by a deadline, or overlooked because it was too long. You could even argue that your pull request was merely a draft that is waiting to be improved. So, why not be proactive and reflect on your past commits?</p>
<p>I invite you to put your version of the <strong>#iwrote</strong> function in your terminal startup file. When you need a small break, take 2 minutes to reflect on what you wrote “1 month ago” or “6 months ago” or “1 year ago”.</p>
<pre><code class="hljs"><span class="hljs-comment"># ~/.zshrc</span>

<span class="hljs-function"><span class="hljs-title">iwrote</span></span>() { git <span class="hljs-built_in">log</span> --<span class="hljs-keyword">until</span>=<span class="hljs-variable">$1</span> -n <span class="hljs-variable">${2:-3}</span> --author=<span class="hljs-string">&quot;<span class="hljs-subst">$(git config user.email)</span>&quot;</span> --pretty=<span class="hljs-string">&quot;%H&quot;</span> | xargs git show }

<span class="hljs-comment"># usage:</span>
<span class="hljs-comment"># iwrote &quot;1 year ago&quot;</span>
<span class="hljs-comment"># iwrote &quot;6 months ago&quot; 4</span>
</code></pre>
<p><em>The function above takes two parameters, both from the git log command. The first parameter is a period that can be used in the until option of git log. The second is an optional number of commits that defaults to 3 when skipped.</em></p>
<p>Just like listening to your voice, or watching a recorded video of yourself, reading your past commits can be cringy. However, it is also a powerful way to improve your craft.</p>
<p>Things you can ask yourself while reading past commits:</p>
<blockquote>
<p>Is the change easy to understand? What could make that change better? Is this commit message helping understand the change? Is this commit intent clear enough? Is the change worth having a dedicated commit or could it have been squashed with another one? Has my coding style evolved? In what way and why?</p>
</blockquote>
<p>Why not try it? Make it fun. It will only take two minutes.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Recording our carbon footprint at home</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Measuring our carbon footprint is the first step in the pathway to reducing and eventually eliminating carbon emissions. Being able to visualise where the majority of our emissions come from allows us to identify the places where making a change will have the most impact.</h2>
<p>Last year I started recording our emissions, and we now have 6 months of data that is worth sharing.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tnd6eh7cg4u6ng0d1v2v4ins" alt="" data-big=https://cms-assets.abletech.nz/small_1k_Oxfa9_Ko_Icj_R_Rv_AC_0t_N2lw_a1442d2772.png sizes="(min-width: 768px) 500px, (min-width: 640px) 193px" src="https://cms-assets.abletech.nz/1k_Oxfa9_Ko_Icj_R_Rv_AC_0t_N2lw_a1442d2772.png" srcset="https://cms-assets.abletech.nz/small_1k_Oxfa9_Ko_Icj_R_Rv_AC_0t_N2lw_a1442d2772.png 500w, https://cms-assets.abletech.nz/thumbnail_1k_Oxfa9_Ko_Icj_R_Rv_AC_0t_N2lw_a1442d2772.png 193w" data-zooming-width="500" data-zooming-height="403" loading="lazy" width="500" height="403"></figure>
</div>
<p>If you sum up all the emission sources, you can see the proportions.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="v73lzrb1rxuqqmwxpsu489es" alt="" data-big=https://cms-assets.abletech.nz/small_1_Ll_Qad4_Qb6sysi_Sbr_Al_FQ_356f663ce9.png sizes="(min-width: 768px) 500px, (min-width: 640px) 190px" src="https://cms-assets.abletech.nz/1_Ll_Qad4_Qb6sysi_Sbr_Al_FQ_356f663ce9.png" srcset="https://cms-assets.abletech.nz/small_1_Ll_Qad4_Qb6sysi_Sbr_Al_FQ_356f663ce9.png 500w, https://cms-assets.abletech.nz/thumbnail_1_Ll_Qad4_Qb6sysi_Sbr_Al_FQ_356f663ce9.png 190w" data-zooming-width="500" data-zooming-height="412" loading="lazy" width="500" height="412"></figure>
</div>
<p>As you can see, flights and gas (heating, hot water) are our largest emitters. Focusing on reducing these two sources of carbon will have a much larger impact, as together they equal 87% of our emissions.</p>
<h2>Data sources</h2>
<p>The raw data all comes from our energy suppliers. I then enter this into the <a href="https://www.enviro-mark.com" target="_blank" rel="noopener noreferrer">Enviro-Mark</a> <a href="http://calculators.enviro-mark.com/public?calculator=household" target="_blank" rel="noopener noreferrer">Household Emissions Calculator</a>, and extract the kg CO2e (kilograms of CO2 or equivalent) figure. I then drop this into a <a href="https://docs.google.com/spreadsheets/d/1U26Nq0jsxn5FNhag-QWkvctaMm0Je9hkryWlwpgk2Vs/edit#gid=0" target="_blank" rel="noopener noreferrer">basic spreadsheet</a> to create the charts.</p>
<h2>Reducing emissions</h2>
<p>What can our family do to reduce and eliminate our CO2 emissions?</p>
<p>When we flew up to Auckland recently, we ticked the <a href="https://www.airnewzealand.co.nz/sustainability-customer-carbon-offset" target="_blank" rel="noopener noreferrer">Air New Zealand carbon offset</a> box. When you tick this box, Air New Zealand purchase carbon credits on your behalf. The cost of the carbon credits for our return flight for 5 people was $15.70, which does not seem like a large amount of money. According to the Air New Zealand website, these credits are purchased from a variety of sources including some tree planting activities in New Zealand.</p>
<p>Planting a tree only temporarily removes carbon from the atmosphere. Carbon is released again when the tree dies. I see this as better than nothing, but only just. The problem is that the jet fuel that was burnt will never be returned to the earth again.</p>
<p>The Enviro-Mark website also offers a way to <a href="https://calculators.enviro-mark.com/public/Forms/PaymentPage.aspx?calculator=household" target="_blank" rel="noopener noreferrer">purchase carbon offsets</a>. I like the idea of supporting a project with an ongoing benefit, such as energy generation or the removal of energy consumption (low emission cook stoves). Next time we fly, I think we’ll not tick the Air New Zealand box, but instead visit the <a href="https://calculators.enviro-mark.com/public/Forms/PaymentPage.aspx?calculator=household" target="_blank" rel="noopener noreferrer">Enviro-Mark</a> website.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="iqss6w6rqvw3gvrs4832iinb" alt="" data-big=https://cms-assets.abletech.nz/medium_1_Wcrka3fm77e_St_Bub_WY_9_L_Lg_61e7800627.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 172px" src="https://cms-assets.abletech.nz/1_Wcrka3fm77e_St_Bub_WY_9_L_Lg_61e7800627.png" srcset="https://cms-assets.abletech.nz/small_1_Wcrka3fm77e_St_Bub_WY_9_L_Lg_61e7800627.png 500w, https://cms-assets.abletech.nz/medium_1_Wcrka3fm77e_St_Bub_WY_9_L_Lg_61e7800627.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Wcrka3fm77e_St_Bub_WY_9_L_Lg_61e7800627.png 172w" data-zooming-width="750" data-zooming-height="679" loading="lazy" width="750" height="679"></figure>
</div>
<h2>‘Natural’ Gas for heating and hot water</h2>
<p>Our main heat source comes from the burning of Natural Gas. <a href="https://en.wikipedia.org/wiki/Natural_gas" target="_blank" rel="noopener noreferrer">Wikipedia</a> explains that this is also known as ‘Fossil Gas’ and comprises mostly of methane. The chart from Wikipedia shows that it is somewhat cleaner that burning oil and coal. I had thought the difference was more.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q14q013mloyhsmu4llvwepk4" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_10t_P6t_ISG_Xn_24z_Bumkm9_Yw_8c098435aa.png sizes="(min-width: 640px) 234px" src="https://cms-assets.abletech.nz/10t_P6t_ISG_Xn_24z_Bumkm9_Yw_8c098435aa.png" srcset="https://cms-assets.abletech.nz/thumbnail_10t_P6t_ISG_Xn_24z_Bumkm9_Yw_8c098435aa.png 234w" data-zooming-width="234" data-zooming-height="156" loading="lazy" width="234" height="156"></figure>
</div>
<p>We need to stop burning this stuff soon.</p>
<h2>New heat source</h2>
<p>We need a new heat source that doesn’t require burning fossil fuels. In Wellington, I believe the options available include:</p>
<ol>
<li>
<p>Wood fires</p>
</li>
<li>
<p>Electricity</p>
</li>
</ol>
<p>We used to have a wood fire at our old place. I love a good fire, but they are a lot of work and are generally either on full or off. It’s difficult to maintain a steady temperature.</p>
<p>Electricity is the natural choice. Especially in New Zealand where are electricity generation is very clean (and <a href="https://www.nzherald.co.nz/business/news/article.cfm?c_id=3&amp;objectid=12216660" target="_blank" rel="noopener noreferrer">getting cleaner</a>). With the coming age of the electric car, I can see demand for electricity growing. This will result in even more generation coming online as the market responds to this growth. And that new generation will all be ‘green’ as <a href="https://youtu.be/2b3ttqYDwF0" target="_blank" rel="noopener noreferrer">coal cannot compete</a> with renewable generation these days.</p>
<p>With our recent switch to a <a href="https://ecotricity.co.nz/" target="_blank" rel="noopener noreferrer">zero-carbon electricity retailer</a>, any electricity we do use will be even cleaner.</p>
<h2>Heat pumps</h2>
<p>A heat pump seems to be the right choice for our family. You can get ‘<strong>air to water’ heat pumps</strong> (also known as ‘hydronic’ heat pumps) that generate hot water for piping around your house with radiators. We would need to have one retrofitted and replace our existing gas boiler.</p>
<p>Another option would be a whole house, <strong>ducted heat pump system</strong>.</p>
<p>We would also need a <strong>hot water heat pump</strong> for showers, etc.</p>
<p>The advantage that modern heat pumps have is the high efficiency. We had a heat pump at our old place, and it had a COP (coefficient of performance) of 3.5. I thought this was amazing, with 1 kilowatt of electrical energy being converted into 3.5 kilowatts of heat energy.</p>
<p>The technology has continued to improve, with many heat pumps having a COP of over 4, and <a href="https://www.stiebel.co.nz/wpl-25-ac-wpl-25-acs" target="_blank" rel="noopener noreferrer">some even exceeding 5</a>. Incredible!</p>
<h2>Next steps</h2>
<p>We will:</p>
<ol>
<li>
<p>Explore options around replacing our gas with a heat pump solution</p>
</li>
<li>
<p>Keep measuring our emissions</p>
</li>
<li>
<p>Try buying carbon offsets from Enviro-Mark</p>
</li>
</ol>
<p>And report back <a href="https://nigel.ramsay.org.nz/" target="_blank" rel="noopener noreferrer">here</a> with progress on how we’re getting on.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Please don’t wait for the Politicians</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>We have to give up on hoping that our government might do something to address climate change. At the moment, there are too many vested interests, lobby groups and others that block moves to start doing anything.</h2>
<p>Instead, each person, each family, each company needs to take the initiative instead. We need to reduce, and then finally stop consuming carbon altogether.</p>
<p>To us in NZ, this means petrol, diesel and so called “natural” gas. Because of the high “greenness” of our electricity grid, we can switch these hydrocarbon sources to electricity and pretty much achieve that goal.</p>
<p>This year, we stopped using our Ford Territory SUV every day. We have been using a second hand Nissan Leaf from Japan. This car runs on electricity (and with recent fuel price increases) has been a larger saving than we thought.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tv5ln3zv4qyqpleace7dhekq" alt="photo cred: Elizabeth Lies" data-big=https://cms-assets.abletech.nz/large_1_Nyolrfar_Go_UYR_Hf_Rt_B_Vq_A_157957dd12.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Nyolrfar_Go_UYR_Hf_Rt_B_Vq_A_157957dd12.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Nyolrfar_Go_UYR_Hf_Rt_B_Vq_A_157957dd12.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Nyolrfar_Go_UYR_Hf_Rt_B_Vq_A_157957dd12.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Nyolrfar_Go_UYR_Hf_Rt_B_Vq_A_157957dd12.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Nyolrfar_Go_UYR_Hf_Rt_B_Vq_A_157957dd12.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>photo cred: Elizabeth Lies</em></p>
<p>The next change we are likely to make will be the removal of our “natural” gas consumption. Our gas heating and hot water is very nice and all, but it’s terrible on the environment. We will research options to convert this to a air-to-water heat pump system. This will cost us some money, but with a 450% efficiency rate — should end up being cheaper than gas in the long term.</p>
<p>I would encourage other people, families and companies to think about what they can do themselves to reduce the use of carbon (petrol, gas, diesel).</p>
<p>Please don’t wait for the politicians.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Optimising Docker for Mac and Elixir</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>In response to highlight how slow Docker for Mac can be in syncing files, between the host and docker container, we can try and optimise the docker-compose override file used in development, to ensure we sync as little as possible to the host file system.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="b6ww4lyuq1ig3fz18gbsdnn1" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_F_Sl_XJ_Ep5u5xrl_Uu7tm_Ih_Fw_0a4c142655.png sizes="(min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_F_Sl_XJ_Ep5u5xrl_Uu7tm_Ih_Fw_0a4c142655.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_F_Sl_XJ_Ep5u5xrl_Uu7tm_Ih_Fw_0a4c142655.png 156w" data-zooming-width="156" data-zooming-height="156" loading="lazy" width="156" height="156"></figure>
</div>
<p>The minimum set of files we want to sync is the actual source code that is committed into git. Look at your <code>.gitignore</code> for hints on what to remove in your project. For our Elixir service we have dependencies, and build artifacts, that are written to disk, so moving <code>deps</code> and <code>_build</code> folders external to the shared project folder should speed things up.</p>
<p>Let’s set up a <code>docker-compose-override.yml</code>file. This file is loaded by default unless you explicitly don’t load it by using the <code>-f</code> flag (eg <code>docker-compose -f docker-compose.yml build</code>) will not use the override file, whereas (eg <code>docker-compose build</code>) will use the developer overlay file (as well as the original).</p>
<ol>
<li>Update your source code so you can explicitly set the<code>deps_path</code> and <code>build_path</code> attributes:</li>
</ol>
<p><code>mix.exs</code></p>
<pre><code class="hljs"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">project</span></span> <span class="hljs-keyword">do</span>
    [
      <span class="hljs-symbol">app:</span> <span class="hljs-symbol">:lab_fulfillment_service</span>,
      <span class="hljs-symbol">aliases:</span> aliases(),
      <span class="hljs-symbol">build_path:</span> <span class="hljs-title class_">System</span>.get_env(<span class="hljs-string">&quot;ELIXIR_BUILD_PATH&quot;</span>) || <span class="hljs-string">&#x27;./_build&#x27;</span>,
      <span class="hljs-symbol">deps_path:</span> <span class="hljs-title class_">System</span>.get_env(<span class="hljs-string">&quot;ELIXIR_DEPS_PATH&quot;</span>) || <span class="hljs-string">&#x27;./deps&#x27;</span>,
      <span class="hljs-symbol">deps:</span> deps()
    ]
  <span class="hljs-keyword">end</span>
</code></pre>
<ol start="2">
<li>Update the <code>docker-compose-override.yml</code>file to pass through the location of where to put the <code>build</code> and <code>deps</code> files:</li>
</ol>
<pre><code class="hljs"><span class="hljs-params">app:</span>
    <span class="hljs-params">volumes:</span>
      <span class="hljs-operator">-</span> .:<span class="hljs-symbol">/opt/app</span>
      <span class="hljs-operator">-</span> elixir-artifacts:<span class="hljs-symbol">/opt/elixir-artifacts</span>
   <span class="hljs-params">environment:</span>
      <span class="hljs-params">ELIXIR_BUILD_PATH:</span> <span class="hljs-symbol">/opt/elixir-artifacts/_build</span>
      <span class="hljs-params">ELIXIR_DEPS_PATH:</span> <span class="hljs-symbol">/opt/elixir-artifacts/deps</span>

<span class="hljs-params">volumes:</span>
  <span class="hljs-params">elixir-artifacts:</span> {}
</code></pre>
<h3>What are the performance results?</h3>
<p>The results show a speed improvement of 2.6 to 8 times. However, this is still slow compared to linux. If you must run Docker for Mac I would still recommend further optimisation investigations. Also note that the optimisation actually made the linux version slower. This suggests that using an explicit volume is slower than the default volume. The full results are here:</p>
<pre><code class="hljs language-text">╔══════════════════════════════════════╦══════════╦════════════╗
║ Machine                              ║ One Test ║ Test Suite ║
╠══════════════════════════════════════╬══════════╬════════════╣
║ 1. Docker for Mac                    ║ 1m41.02s ║ 2m45       ║
║ 1. Docker for Mac (optimised)        ║ 0m12.980 ║ 1m02.9s    ║
║ 2. VM Linux Docker on Mac            ║ 0m6.1s   ║ 0m56       ║
║ 2. VM Linux Docker on Mac (optimised)║ 0m12.980 ║ 1m02.9s    ║
╚══════════════════════════════════════╩══════════╩════════════╝

</code></pre>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>.NET adventures</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Step-by-step blogging app using Blazor</h2>
<p>TL;DR: here’s the project on <a href="https://github.com/fsanggang/golb" target="_blank" rel="noopener noreferrer">Github</a>.</p>
<p>I’ve never had anything to do with the .NET platform in all my time at Abletech. So, why now?</p>
<p>Simply put: it’s an untapped potential revenue stream, and likely a large one at that. The demand for ‘c#’ greatly outstrips that for ‘elixir’ — at the time of writing, there was a mere handful of jobs returned by ‘elixir’, versus several hundred for ‘c#’ on <a href="https://www.seek.co.nz/" target="_blank" rel="noopener noreferrer">SEEK</a>. Happily, this also lines up with one of Abletech’s <a href="https://stories.abletech.nz/what-makes-us-tick-848956cf7700" target="_blank" rel="noopener noreferrer">team values</a>: to be a profitable, sustainable business.</p>
<p>I also happened to have a couple of days in between billable client work, as well as a long weekend made even longer thanks to some leave, at my disposal. This meant I could put in some investment time towards learning something new. Once again, this happily lines up with another of Abletech’s <a href="https://stories.abletech.nz/what-makes-us-tick-848956cf7700" target="_blank" rel="noopener noreferrer">team values</a>: to strive for continuous improvement.</p>
<h2>The project — a Blazor blogging app</h2>
<p><a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor" target="_blank" rel="noopener noreferrer">Blazor</a> is a fairly new framework for building web UIs using C# — that’s right, no more JavaScript. This should remind the Elixir-inclined of Phoenix LiveView. Don’t confuse Blazor components (with .razor extension) with the earlier Razor pages (with .cshtml extension).</p>
<p>For my investment time project, I chose to explore how I could use Blazor to create a blogging app. Although admittedly a boring choice, it does cover a lot of the basics: database access, basic CRUD, routing and so on.</p>
<p>The key word here is explore: before I started, I knew nothing about C# or EntityFramework or any of that. There was a lot of flailing around at the beginning, and I hope that what follows might be of help to someone else who also decides to dive into .NET.</p>
<h2><strong>Initial setup</strong></h2>
<p>There is a Visual Studio for macOs, but pay attention to the <a href="https://visualstudio.microsoft.com/vs/mac/#vs_mac_table" target="_blank" rel="noopener noreferrer">supported features </a>(or lack thereof) compared to the Windows version. You will need to install the <code>dotnet</code> CLI as well — I got it off the ‘official’ <a href="http://ASP.NET" target="_blank" rel="noopener noreferrer">ASP.NET</a> <a href="https://dotnet.microsoft.com/learn/aspnet/hello-world-tutorial/install" target="_blank" rel="noopener noreferrer">Hello World tutorial</a>.</p>
<p>At this point you could fire up Visual Studio and create a new project like so… <em>but don’t</em>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ea48f9zpil22f1cnqzd6ak05" alt="" data-big=https://cms-assets.abletech.nz/large_1_Wp0s_Ooy_Q_Tzb_I_Xkc_Hi_U0_FA_4cfbaf8187.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 215px" src="https://cms-assets.abletech.nz/1_Wp0s_Ooy_Q_Tzb_I_Xkc_Hi_U0_FA_4cfbaf8187.png" srcset="https://cms-assets.abletech.nz/large_1_Wp0s_Ooy_Q_Tzb_I_Xkc_Hi_U0_FA_4cfbaf8187.png 1000w, https://cms-assets.abletech.nz/small_1_Wp0s_Ooy_Q_Tzb_I_Xkc_Hi_U0_FA_4cfbaf8187.png 500w, https://cms-assets.abletech.nz/medium_1_Wp0s_Ooy_Q_Tzb_I_Xkc_Hi_U0_FA_4cfbaf8187.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Wp0s_Ooy_Q_Tzb_I_Xkc_Hi_U0_FA_4cfbaf8187.png 215w" data-zooming-width="1000" data-zooming-height="727" loading="lazy" width="1000" height="727"></figure>
</div>
<p>By default, Visual Studio for Windows will create a project designed to use SQL Server, but Visual Studio for macOS will create a project designed to use SQLite instead. Unfortunately SQLite has some limitations around schema changes, eg. it’s possible to add, but not remove a column from a table, although to be fair there are <a href="https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/migrations?view=aspnetcore-3.1&amp;tabs=visual-studio" target="_blank" rel="noopener noreferrer">workarounds</a> for this.</p>
<p><strong>Create project</strong></p>
<p>I believe it is better practice, and more production-ready to create an app that uses SQL Server instead. To do this, you will need to use the <code>dotnet</code> CLI:</p>
<pre><code class="hljs language-shell">dotnet new blazorserver --use-local-db --auth Individual --name Golb
</code></pre>
<p>A starter project should be created, and you should be able to see that it is configured to use SQL Server:</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Startup.cs</span>

services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;
    options.UseSqlServer(
        Configuration.GetConnectionString(<span class="hljs-string">&quot;DefaultConnection&quot;</span>)));
</code></pre>
<p>Note that creating a project using the <code>dotnet</code> CLI will not create a <code>.gitignore</code> file for you. You may want to create a throwaway project using the IDE and copy over the generated <code>.gitignore</code> file to your real project. You will also be missing a <code>Golb.sln</code> file — simply select the <code>Golb.csproj</code> file in Visual Studio for macOS, and the solution will be created automagically.</p>
<p><strong>Run SQL Server</strong></p>
<p>For macOS, the only option is to <a href="https://docs.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-ver15&amp;pivots=cs1-bash" target="_blank" rel="noopener noreferrer">run SQL Server</a> with Docker:</p>
<pre><code class="hljs language-shell">docker pull mcr.microsoft.com/mssql/server:2019-latest
docker run -d - name sql_server -e ACCEPT_EULA=Y -e SA_PASSWORD=&lt;yourPasswordHere&gt; -p 1433:1433 mcr.microsoft.com/mssql/server:2019-latest
</code></pre>
<p>You should be able to see it running:</p>
<pre><code class="hljs language-shell">docker ps
CONTAINER ID        IMAGE                                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
831295b80b53        mcr.microsoft.com/mssql/server:2019-latest   &quot;/opt/mssql/bin/perm…&quot;   3 minutes ago       Up 2 seconds        0.0.0.0:1433-&gt;1433/tcp   sql_server
</code></pre>
<p>To stop or restart:</p>
<pre><code class="hljs language-shell">docker stop sql_server
docker start sql_server
</code></pre>
<p><strong>Link app and database</strong></p>
<p>Remember the <code>DefaultConnection</code> from earlier? Update it to point to our Docker-ised SQL Server. Note that <code>Password</code> must match the <code>SA_PASSWORD</code> used when the SQL Server container was run up:</p>
<pre><code class="hljs language-json"><span class="hljs-comment">// appsettings.json</span>
<span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;ConnectionStrings&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;DefaultConnection&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Data Source=localhost,1433;User ID=sa;Password=superSecurePwd123;Connect Timeout=30;Database=GolbDatabase&quot;</span>
  <span class="hljs-punctuation">}</span>
  ... etc
<span class="hljs-punctuation">}</span>
</code></pre>
<p><strong>Run migrations (?)</strong></p>
<p>In our local development environment, it is convenient to ensure all migrations are run on app startup. Add the following:</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Startup.cs</span>

 <span class="hljs-comment">// Should not user Database.Migrate() in production, instead migrations should be done as part of deployment</span>
<span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> srvc = app.ApplicationServices.GetRequiredService&lt;IServiceScopeFactory&gt;().CreateScope())
    {
        <span class="hljs-keyword">var</span> context = srvc.ServiceProvider.GetService&lt;ApplicationDbContext&gt;();
        context.Database.Migrate();
    }
</code></pre>
<p>However, this is a poor approach in production eg. if there are migration errors, your app won’t be available. Unfortunately I did not have time to investigate this further.</p>
<p><strong>Sanity check</strong></p>
<p>Visual Studio for Windows has a built-in Server Explorer that can be used to check database connectivity and to run queries. Unfortunately this does not appear to be available in Visual Studio for macOs.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="b6tl4jzuh4m7so288jowbua4" alt="" data-big=https://cms-assets.abletech.nz/large_1_UK_Yl_DBMN_0_JWX_Nt_Bwm_Hp8kw_ac178541f2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_UK_Yl_DBMN_0_JWX_Nt_Bwm_Hp8kw_ac178541f2.jpeg" srcset="https://cms-assets.abletech.nz/large_1_UK_Yl_DBMN_0_JWX_Nt_Bwm_Hp8kw_ac178541f2.jpeg 1000w, https://cms-assets.abletech.nz/small_1_UK_Yl_DBMN_0_JWX_Nt_Bwm_Hp8kw_ac178541f2.jpeg 500w, https://cms-assets.abletech.nz/medium_1_UK_Yl_DBMN_0_JWX_Nt_Bwm_Hp8kw_ac178541f2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_UK_Yl_DBMN_0_JWX_Nt_Bwm_Hp8kw_ac178541f2.jpeg 245w" data-zooming-width="1000" data-zooming-height="493" loading="lazy" width="1000" height="493"></figure>
</div>
<p>However, if you’ve been following along and supplied the <code>—- auth Individual</code>option when creating the app, the starter project should have been created with Register and Login pages. This means we can check for database connectivity by registering a new user.</p>
<p>Fire up your app either by clicking ▶ in the IDE, or with <code>dotnet run</code>, navigate to the Register page and enter the usual user details. Don’t forget to click ‘Click here to confirm your account’:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e4u14wq43ncdczrzpenh1s2o" alt="" data-big=https://cms-assets.abletech.nz/large_1_Fp_Bt_L_Lr3ubja_Wy_Pnw0_BTOA_cd1cb50aa7.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Fp_Bt_L_Lr3ubja_Wy_Pnw0_BTOA_cd1cb50aa7.png" srcset="https://cms-assets.abletech.nz/large_1_Fp_Bt_L_Lr3ubja_Wy_Pnw0_BTOA_cd1cb50aa7.png 1000w, https://cms-assets.abletech.nz/small_1_Fp_Bt_L_Lr3ubja_Wy_Pnw0_BTOA_cd1cb50aa7.png 500w, https://cms-assets.abletech.nz/medium_1_Fp_Bt_L_Lr3ubja_Wy_Pnw0_BTOA_cd1cb50aa7.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Fp_Bt_L_Lr3ubja_Wy_Pnw0_BTOA_cd1cb50aa7.png 245w" data-zooming-width="1000" data-zooming-height="582" loading="lazy" width="1000" height="582"></figure>
</div>
<p>If that all worked, you should now be able to login to the app.</p>
<h2><strong>Database migrations and mapping</strong></h2>
<p>Entity Framework is the object-database mapper for the .NET platform, and it doesn’t come pre-installed. For the Elixir people, this would be similar to Ecto, and for the Ruby/Rails people, think ActiveRecord.</p>
<p><strong>Install dotnet-ef</strong></p>
<p>You can install this through Visual Studio for macOs via Project &gt; Manage NuGet packages… or through the <code>dotnet</code> CLI:</p>
<pre><code class="hljs language-shell">dotnet tool install --global dotnet-ef
</code></pre>
<p>You may need to add this to your path, eg. for <code>zsh</code> users, add something like this to your <code>~/.zshrc</code>:</p>
<pre><code class="hljs language-shell">export PATH=&quot;$PATH:/Users/fionasanggang/.dotnet/tools&quot;
</code></pre>
<p><strong>Add a Post model</strong></p>
<p>Create a new model that will represent the table that will be created in the database:</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Data/Post.cs</span>

<span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Golb.Data</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Post</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-built_in">string</span> Body { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> DateTime PostedAt { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    }
}
</code></pre>
<p>And then hook it up to the database context:</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Data/ApplicationDbContext.cs</span>

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationDbContext</span> : <span class="hljs-title">IdentityDbContext</span>
{
    ...<span class="hljs-function">etc

    <span class="hljs-keyword">public</span> <span class="hljs-title">DbSet</span>&lt;<span class="hljs-title">Post</span>&gt; Posts</span> { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnModelCreating</span>(<span class="hljs-params">ModelBuilder builder</span>)</span>
    {
        builder.Entity&lt;Post&gt;();
        <span class="hljs-keyword">base</span>.OnModelCreating(builder);
    }
}
</code></pre>
<p><strong>Create the Posts database table</strong></p>
<p>Now run the following to generate the migration file, and then run the migration:</p>
<pre><code class="hljs language-shell">dotnet ef migrations add Posts
dotnet ef database update
</code></pre>
<p><strong>Sanity check</strong></p>
<p>As previously mentioned, there doesn’t appear to be a built-in Server Explorer in Visual Studio for macOs, so to check that we now indeed have a Posts table, connect to the <code>sql_server</code> container:</p>
<pre><code class="hljs language-shell">docker exec -it sql_server bash
</code></pre>
<p>Users familiar with <code>psql</code> will be happy to know that <code>sqlcmd</code> is reasonably similar. The command <code>/opt/mssql-tools/bin/sqlcmd -?</code> may come in handy, but for our immediate purposes, just run the following:</p>
<pre><code class="hljs language-shell">/opt/mssql-tools/bin/sqlcmd -S localhost -U &quot;sa&quot; -P &quot;yourPasswordHere&quot; -I

</code></pre>
<p>Now let’s run some queries. Note that unlike Postgres queries which are terminated with <code>;</code> , each <code>sqlcmd</code> command must be followed by <code>GO</code>(omitted in the examples below for brevity):</p>
<pre><code class="hljs language-sql"><span class="hljs-keyword">select</span> Name <span class="hljs-keyword">from</span> sys.Databases
use GolbDatabase
<span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> information_schema.tables
<span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> information_schema.columns <span class="hljs-keyword">where</span> table_name <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;Posts&#x27;</span>
</code></pre>
<p>You should be able to see a Posts table, with the the columns Id, Title, Body and PostedAt… sort of. In SQL Server, there is no simple equivalent like Postgres’s <code>\x</code> to view records vertically.</p>
<h2><strong>Basic CRUD</strong></h2>
<p>The starter project has a FetchData component with a useful pattern to follow for fetching data, so let’s do something similar to list Posts.</p>
<p><strong>List posts</strong></p>
<p>First, the access layer: create a service that will carry out database-related actions. This is similar to a context in Elixir apps.</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Services/PostService.cs</span>

<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> System.Collections.Generic;
<span class="hljs-keyword">using</span> System.Threading.Tasks;
<span class="hljs-keyword">using</span> Golb.Data;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Golb.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IPostService</span>
    {
        Task&lt;Post[]&gt; Get();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PostService</span> : <span class="hljs-title">IPostService</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ApplicationDbContext _context;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PostService</span>(<span class="hljs-params">ApplicationDbContext context</span>)</span>
        {
            _context = context;
        }

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Post[]&gt; Get()
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> _context.Posts.ToArrayAsync();
        }
    }
}
</code></pre>
<p>Register this service on startup. I am unsure why I had to use <code>AddTransient</code> as opposed to <code>AddSingleton</code> or <code>AddScoped</code>, but was unable to investigate this further.</p>
<pre><code class="hljs language-c#">... etc
<span class="hljs-keyword">using</span> Golb.Services;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ConfigureServices</span>(<span class="hljs-params">IServiceCollection services</span>)</span>
{
    ...etc

    services.AddTransient&lt;PostService&gt;();
}
</code></pre>
<p>Then call the service to list all posts. Notice how routing, markup and code are all in the same Blazor component. This shouldn’t look too unfamiliar if you’ve dabbled in Vue.js.</p>
<pre><code class="hljs language-html">// Pages/Post/Index.razor

@page &quot;/posts&quot;

@using Golb.Data
@using Golb.Services
@inject PostService service

@if (posts == null)
{
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">em</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
}
else if (posts.Length == 0)
{
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>No posts<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
}
else
{
    @foreach (Post post in posts)
    {
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/posts/@post.Id&quot;</span>&gt;</span>@post.Title<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>@post.PostedAt<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>@post.Body<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
     }
}

@code {
    private Post[] posts;

    protected override async Task OnInitializedAsync()
    {
        posts = await service.Get();
        posts = posts.OrderByDescending(post =&gt; post.PostedAt).ToArray<span class="hljs-tag">&lt;<span class="hljs-name">Post</span>&gt;</span>();
    }
}

</code></pre>
<p>And add a new link to the navigation bar:</p>
<pre><code class="hljs language-html">... etc

<span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;nav-item px-3&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">NavLink</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;nav-link&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;posts&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;oi oi-list-rich&quot;</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">&quot;true&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span> Posts
    <span class="hljs-tag">&lt;/<span class="hljs-name">NavLink</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

... etc
</code></pre>
<p>Now you should be able to navigate to <code>localhost:5001/posts</code>… except there are no posts yet to display.</p>
<p><strong>The rest</strong></p>
<p>The remaining CRUD actions follow essentially the same pattern: add the functionality to PostService and use it in the component. So I won’t go over them in detail here, but leave it to the reader to check out the completed project on <a href="https://gist.github.com/fsanggang" target="_blank" rel="noopener noreferrer">Github</a>.</p>
<h2>Role-based authorisation</h2>
<p>The .NET platform supports <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-3.1#role-based-and-policy-based-authorization" target="_blank" rel="noopener noreferrer">role-based or policy-based authorisation</a>, and I chose the former for this project. I want only users with an <code>Admin</code> role to be able to create, update, and delete posts. All users, whether authenticated or not, should be able to view the list of posts, and to view each individual post.</p>
<p><strong>The Admin role</strong></p>
<p>I am unsure as to the best way to achieve this. Eventually I opted to use the existing registration and authentication functionality provided by the starter project to create an <code>Admin</code> user, and then assign an <code>Admin</code> role to that user on application startup.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sg972cpm1ub5xoyyk1wj7rzp" alt="" data-big=https://cms-assets.abletech.nz/large_1_RP_7qht_G1hw_Vtb_ZD_2_COZ_0cw_f5f1a1c429.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_RP_7qht_G1hw_Vtb_ZD_2_COZ_0cw_f5f1a1c429.png" srcset="https://cms-assets.abletech.nz/large_1_RP_7qht_G1hw_Vtb_ZD_2_COZ_0cw_f5f1a1c429.png 1000w, https://cms-assets.abletech.nz/small_1_RP_7qht_G1hw_Vtb_ZD_2_COZ_0cw_f5f1a1c429.png 500w, https://cms-assets.abletech.nz/medium_1_RP_7qht_G1hw_Vtb_ZD_2_COZ_0cw_f5f1a1c429.png 750w, https://cms-assets.abletech.nz/thumbnail_1_RP_7qht_G1hw_Vtb_ZD_2_COZ_0cw_f5f1a1c429.png 245w" data-zooming-width="1000" data-zooming-height="580" loading="lazy" width="1000" height="580"></figure>
</div>
<p>First the initialiser. This creates an <code>Admin</code> role if one does not already exist and links it with our user with the email <a href="mailto:admin@test.com" target="_blank" rel="noopener noreferrer">admin@test.com</a> — if you were to look in the database, you should see a new AspNetRoles record with the name <code>Admin</code>, and a new join record in the AspNetUserRoles table linking this <code>Admin</code> role to our <a href="mailto:admin@test.com" target="_blank" rel="noopener noreferrer">admin@test.com</a> user.</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Data/ApplicationInitialiser.cs</span>

<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Identity;
<span class="hljs-keyword">using</span> System.Threading.Tasks;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationInitialiser</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-built_in">string</span> ADMIN_ROLE = <span class="hljs-string">&quot;Admin&quot;</span>;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Initialise</span>(<span class="hljs-params">UserManager&lt;IdentityUser&gt; userManager, RoleManager&lt;IdentityRole&gt; roleManager</span>)</span>
    {
        <span class="hljs-comment">// ensure there is a ADMIN_ROLE if not create one</span>
        IdentityRole adminRole = <span class="hljs-keyword">await</span> roleManager.FindByNameAsync(ADMIN_ROLE);

        <span class="hljs-keyword">if</span> (adminRole == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">await</span> roleManager.CreateAsync(<span class="hljs-keyword">new</span> IdentityRole(ADMIN_ROLE));
        }

        <span class="hljs-comment">// Ensure a user named admin@test.com is an Admin</span>
        <span class="hljs-keyword">var</span> user = <span class="hljs-keyword">await</span> userManager.FindByEmailAsync(<span class="hljs-string">&quot;admin@test.com&quot;</span>);

        <span class="hljs-keyword">if</span> (user != <span class="hljs-literal">null</span>)
        {
            <span class="hljs-built_in">bool</span> userIsAdmin = <span class="hljs-keyword">await</span> userManager.IsInRoleAsync(user, ADMIN_ROLE);

            <span class="hljs-keyword">if</span> (!userIsAdmin)
            {
                <span class="hljs-keyword">await</span> userManager.AddToRoleAsync(user, ADMIN_ROLE);
            }
        }
    }
}
</code></pre>
<p>Then make sure our initialiser is run on application startup. Don’t forget to rebuild and restart the application.</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Startup.cs</span>

...etc

<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Authorization;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ConfigureServices</span>(<span class="hljs-params">IServiceCollection services</span>)</span>
{
    ... etc
    services.AddDefaultIdentity&lt;IdentityUser&gt;(options =&gt; options.SignIn.RequireConfirmedAccount = <span class="hljs-literal">true</span>)
        .AddRoles&lt;IdentityRole&gt;()
        .AddEntityFrameworkStores&lt;ApplicationDbContext&gt;();
}


<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Configure</span>(<span class="hljs-params">IApplicationBuilder app, IWebHostEnvironment env, UserManager&lt;IdentityUser&gt; userManager, RoleManager&lt;IdentityRole&gt; roleManager</span>)</span>
{
    ... etc
    ApplicationInitialiser.Initialise(userManager, roleManager).Wait();
}
</code></pre>
<p><strong>Authorising access</strong></p>
<p>With Blazor, we can selectively apply role-based authorisation to UI elements in Razor components, or to the entire Razor component altogether.</p>
<p>For example, wrap the <code>+ New Post</code> button in an <code>AuthorizeView</code> component:</p>
<pre><code class="hljs language-html">// Pages/Post/Index.razor

<span class="hljs-tag">&lt;<span class="hljs-name">AuthorizeView</span> <span class="hljs-attr">Roles</span>=<span class="hljs-string">&quot;Admin&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;form-group float-right&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;btn btn-success&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;posts/new&quot;</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;oi oi-plus&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span>
            <span class="hljs-symbol">&amp;nbsp;</span> New Post
        <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">AuthorizeView</span>&gt;</span>
</code></pre>
<p>Or use the <code>[Authorize]</code> attribute to prevent non-<code>Admin</code> users from accessing <code>localhost:5001/posts/new</code> altogether:</p>
<pre><code class="hljs language-html">// Pages/Post/New.razor


@page &quot;/posts/new&quot;
@attribute [Authorize(Roles = &quot;Admin&quot;)]


... etc
</code></pre>
<h2>Markdown</h2>
<p>So far, posts are displayed without any formatting, not very much like a blogging app at all. I want to enable users to create or edit a post with Markdown, and display a preview of their changes alongside as they type.</p>
<p><strong>Adding the Markdig package</strong></p>
<p>Luckily there is already a <a href="https://www.nuget.org/packages/Markdig/" target="_blank" rel="noopener noreferrer">Nuget package</a> for a .NET Markdown processor. You can install this through Visual Studio for macOs via Project &gt; Manage NuGet packages… or through the<code>dotnet</code> CLI:</p>
<pre><code class="hljs language-shell">dotnet add package Markdig --version 0.22.0
</code></pre>
<p><strong>Markdown preview</strong></p>
<p>Markdig makes it easy to convert Markdown to to HTML, so we can use that for a post's body:</p>
<pre><code class="hljs language-c#"><span class="hljs-comment">// Post.cs</span>
<span class="hljs-keyword">using</span> Markdig;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Golb.Data</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Post</span>
    {
        ... etc

        <span class="hljs-keyword">public</span> <span class="hljs-built_in">string</span> MarkupString {
            <span class="hljs-keyword">get</span> 
            {
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.Body == <span class="hljs-literal">null</span> ? <span class="hljs-string">&quot;&quot;</span> : Markdown.ToHtml(<span class="hljs-keyword">this</span>.Body);
            }
        }
    }
}
</code></pre>
<p>And we can add two-way binding between user input and the rendered, HTML preview:</p>
<pre><code class="hljs language-html">// Pages/Post/Edit.razor

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;row&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;col-6&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;hidden&quot;</span> @<span class="hljs-attr">bind-value</span>=<span class="hljs-string">&quot;@post.Body&quot;</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">&quot;20&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;form-control&quot;</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;body&quot;</span> @<span class="hljs-attr">bind</span>=<span class="hljs-string">&quot;@post.Body&quot;</span> @<span class="hljs-attr">bind:event</span>=<span class="hljs-string">&quot;oninput&quot;</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;col-6&quot;</span>&gt;</span>
        @((MarkupString) post.MarkupString)
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>If everything worked as expected, you should now be able to do something like this:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zd0k18gawcls2gxzfcalpl29" alt=""  sizes="" src="https://cms-assets.abletech.nz/1e_WY_0_GQ_Tf_GP_1_LK_5_ESBXYAXA_2d90568a2b.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p>I also used a post’s <code>MarkupString</code> instead of its body when creating a new post, as well as when displaying a single post. Once again I’ll leave it to the reader to check out the project on <a href="https://github.com/fsanggang/golb" target="_blank" rel="noopener noreferrer">Github</a>.</p>
<h2><strong>Final notes</strong></h2>
<p>And there you have it: some semblance of a blogging app. If you missed it, here is the project on <a href="https://github.com/fsanggang/golb" target="_blank" rel="noopener noreferrer">Github</a>.</p>
<p>Blazor itself looks promising, but I did run into some trouble while working on this project. I think my main bugbears were:</p>
<ul>
<li>
<p>Visual Studio for macOs is not mature, which means that existing tutorials, answers on Stack Overflow etc can be confusing.</p>
</li>
<li>
<p>The official documentation is very good, but seemed fragmented. I could not find a single official tutorial for what I thought should be a fairly basic, example app.</p>
</li>
<li>
<p>Having to rebuild and rerun every time I make a change. Ugh!</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Insomnia — API testing application</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>I’d like to recommend the app Insomnia, a desktop application for testing HTTP and API endpoints.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="en118oy3stu8ggd3z2waq3xa" alt="" data-big=https://cms-assets.abletech.nz/large_1_O_Grm_RA_Ce_9_Jg_Wuk4_Os_Z9mg_6d076bb58b.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_O_Grm_RA_Ce_9_Jg_Wuk4_Os_Z9mg_6d076bb58b.png" srcset="https://cms-assets.abletech.nz/large_1_O_Grm_RA_Ce_9_Jg_Wuk4_Os_Z9mg_6d076bb58b.png 1000w, https://cms-assets.abletech.nz/small_1_O_Grm_RA_Ce_9_Jg_Wuk4_Os_Z9mg_6d076bb58b.png 500w, https://cms-assets.abletech.nz/medium_1_O_Grm_RA_Ce_9_Jg_Wuk4_Os_Z9mg_6d076bb58b.png 750w, https://cms-assets.abletech.nz/thumbnail_1_O_Grm_RA_Ce_9_Jg_Wuk4_Os_Z9mg_6d076bb58b.png 245w" data-zooming-width="1000" data-zooming-height="463" loading="lazy" width="1000" height="463"></figure>
</div>
<p>Previously, I have used the Chrome app “Postman” but I needed to test non-standard HTTP methods (not supported in Postman).</p>
<h2>Support for non-standard HTTP methods</h2>
<p>I am using Insomnia to test communication with a Belkin Wemo Switch, which makes use of the SUBSCRIBE method for registering for change notifications. The switch then sends an EVENT method when the state is changed.</p>
<p>Developing my <a href="http://github.com/nigelramsay/wemo/" target="_blank" rel="noopener noreferrer">Elixir library for the Wemo Switch</a> has been possible with the help of Insomnia. Postman only supports a fixed list of HTTP methods.</p>
<h2>Stand-alone application</h2>
<p>It looks like Insomnia is an Electron based application, and it is available for all 3 major desktop platforms — Windows, Linux and OS X. It is free, but you can choose to pay if you want team synchronisation capabilities.</p>
<p>Being a stand-alone application is great, as I no longer need to start up Chrome to then start up Postman. As I am using Chrome less frequently these days, this means one less step.</p>
<p>Finally, Insomnia is <a href="https://github.com/getinsomnia/insomnia" target="_blank" rel="noopener noreferrer">open source</a>.</p>
<p><a href="https://insomnia.rest" target="_blank" rel="noopener noreferrer">Learn more and download Insomnia</a> at <a href="https://insomnia.rest" target="_blank" rel="noopener noreferrer">https://insomnia.rest</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Implementing Facebook SDK with Turbolinks 5</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>I am using the Turbolinks library on a static website to get a snappy feeling when navigating, similar to a single-page application.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hley84hrwfnmgtsbmd4qk304" alt="Bridging Turbolinks and Facebook SDK" data-big=https://cms-assets.abletech.nz/large_1_Sal_Nfoehqo_Ox_W2_j_E_Hq0ug_8973d01915.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Sal_Nfoehqo_Ox_W2_j_E_Hq0ug_8973d01915.png" srcset="https://cms-assets.abletech.nz/large_1_Sal_Nfoehqo_Ox_W2_j_E_Hq0ug_8973d01915.png 1000w, https://cms-assets.abletech.nz/small_1_Sal_Nfoehqo_Ox_W2_j_E_Hq0ug_8973d01915.png 500w, https://cms-assets.abletech.nz/medium_1_Sal_Nfoehqo_Ox_W2_j_E_Hq0ug_8973d01915.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Sal_Nfoehqo_Ox_W2_j_E_Hq0ug_8973d01915.png 245w" data-zooming-width="1000" data-zooming-height="578" loading="lazy" width="1000" height="578"></figure>
</div>
<p><em>Bridging Turbolinks and Facebook SDK</em></p>
<p>I came across some difficulties when using the Javascript SDK in a Turbolinks context to render multiple Facebook plugins on several pages. The elements would render on first hit and not render on any other page. When finally rendered across all pages, the elements would blink between the cached preview and the final rendering.</p>
<p>TL;DR - Here is the final implementation using the <a href="https://github.com/turbolinks/turbolinks#persisting-elements-across-page-loads" target="_blank" rel="noopener noreferrer">data-turbolinks-permanent</a> feature. Scroll down for the explanation.</p>
<pre><code class="hljs language-ruby"><span class="hljs-comment"># /source/layouts/layout.erb</span>
&lt;body <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;&lt;%= page_classes %&gt;&quot;</span>&gt;
  &lt;%= <span class="hljs-keyword">yield</span> %&gt;
  &lt;div id=<span class="hljs-string">&#x27;permanent&#x27;</span> data-turbolinks-permanent&gt;&lt;<span class="hljs-regexp">/div&gt;
&lt;/body</span>&gt;
</code></pre>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">FBInit</span>(<span class="hljs-params"></span>) {
  <span class="hljs-variable constant_">FB</span>.<span class="hljs-title function_">init</span>({
    appId      : <span class="hljs-string">&#x27;YOUR_KEY&#x27;</span>,
    xfbml      : <span class="hljs-literal">true</span>,
    version    : <span class="hljs-string">&#x27;v2.8&#x27;</span>
  });
  $(<span class="hljs-string">&#x27;#permanent&#x27;</span>).<span class="hljs-title function_">append</span>( $(<span class="hljs-string">&#x27;#fb-root&#x27;</span>).<span class="hljs-title function_">detach</span>() );
};

$(<span class="hljs-variable language_">document</span>).<span class="hljs-title function_">ready</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params"></span>){
  $.<span class="hljs-title function_">getScript</span>( <span class="hljs-string">&quot;//connect.facebook.net/en_US/sdk.js#xfbml=1&amp;version=v2.8&quot;</span>, <span class="hljs-title class_">FBInit</span>);
});

$(<span class="hljs-variable language_">document</span>).<span class="hljs-title function_">on</span>(<span class="hljs-string">&#x27;turbolinks:load&#x27;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>){
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-variable constant_">FB</span> !== <span class="hljs-string">&quot;undefined&quot;</span> &amp;&amp; <span class="hljs-variable constant_">FB</span> !== <span class="hljs-literal">null</span>) {
    <span class="hljs-variable constant_">FB</span>.<span class="hljs-property">XFBML</span>.<span class="hljs-title function_">parse</span>();
  }
});

$(<span class="hljs-variable language_">document</span>).<span class="hljs-title function_">on</span>(<span class="hljs-string">&quot;turbolinks:before-cache&quot;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
    $(<span class="hljs-string">&#x27;[data-turbolinks-no-cache]&#x27;</span>).<span class="hljs-title function_">remove</span>();
});
</code></pre>
<p>Then use any Facebook plugins using the <code>data-turbolinks-no-cache</code> attribute like this:</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">data-turbolinks-no-cache</span> 
  <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;fb-like&quot;</span> 
  <span class="hljs-attr">data-href</span>=<span class="hljs-string">&quot;#&quot;</span> 
  <span class="hljs-attr">data-layout</span>=<span class="hljs-string">&quot;standard&quot;</span> 
  <span class="hljs-attr">data-action</span>=<span class="hljs-string">&quot;like&quot;</span> 
  <span class="hljs-attr">data-size</span>=<span class="hljs-string">&quot;small&quot;</span> 
  <span class="hljs-attr">data-show-faces</span>=<span class="hljs-string">&quot;true&quot;</span> 
  <span class="hljs-attr">data-share</span>=<span class="hljs-string">&quot;true&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h2>Explanation</h2>
<h3>Rendering all Facebook plugins on pages</h3>
<p>I am using Middleman to generate all the assets and, like most frameworks, there is a layout template where the body content goes. A &lt;div&gt; tag has been added right before the &lt;/body&gt; tag.</p>
<pre><code class="hljs"><span class="language-xml"># /source/layouts/layout.erb

<span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;&lt;%=</span></span></span><span class="language-ruby"> page_classes </span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">%&gt;&quot;</span>&gt;</span>
  &lt;%=</span><span class="language-ruby"> <span class="hljs-keyword">yield</span> </span><span class="language-xml">%&gt;
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&#x27;permanent&#x27;</span> <span class="hljs-attr">data-turbolinks-permanent</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</span></code></pre>
<p><em>Notice the <code>data-turbolinks-permanent</code> attribute on the <code>div#permanent</code></em></p>
<p><strong>This allows any content on that div to persist and be passed on to all the other pages you will navigate to. See the <a href="https://github.com/turbolinks/turbolinks#persisting-elements-across-page-loads" target="_blank" rel="noopener noreferrer">feature on documentation</a>.</strong></p>
<p>Here is the javascript using jQuery for convenience.</p>
<pre><code class="hljs"><span class="hljs-comment"># /source/javascripts/_facebook.js</span>

function <span class="hljs-title class_">FBInit</span>() {
  <span class="hljs-title class_">FB</span>.init({
    appId      : <span class="hljs-string">&#x27;YOUR_KEY&#x27;</span>,
    xfbml      : <span class="hljs-literal">true</span>,
    version    : <span class="hljs-string">&#x27;v2.8&#x27;</span>
  });

  <span class="hljs-variable">$(</span><span class="hljs-string">&#x27;#permanent&#x27;</span>).append( <span class="hljs-variable">$(</span><span class="hljs-string">&#x27;#fb-root&#x27;</span>).detach() );
};

<span class="hljs-variable">$(</span>document).ready(function(){
  <span class="hljs-variable">$.</span>getScript( <span class="hljs-string">&quot;//connect.facebook.net/en_US/sdk.js#xfbml=1&amp;version=v2.8&quot;</span>, <span class="hljs-title class_">FBInit</span>);
});
</code></pre>
<p><em>Calling <code>FB.init()</code> after the library is downloaded will append the<code>#fb-root</code> to the bottom of the page. The responsibility of that element is to render Facebook plugins and therefore needs to appear on any page with a Facebook plugin.</em></p>
<p><em>After <code>FB.init()</code> the <code>#fb-root</code> element is moved to the <code>div#permanent</code> from the layout.</em></p>
<p><strong>As a result the <code>#fb-root</code> is persisted across all pages.</strong></p>
<p>Then we render the plugins on each page.</p>
<pre><code class="hljs">$(<span class="hljs-variable language_">document</span>).<span class="hljs-title function_">on</span>(<span class="hljs-string">&#x27;turbolinks:load&#x27;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>){
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-variable constant_">FB</span> !== <span class="hljs-string">&quot;undefined&quot;</span> &amp;&amp; <span class="hljs-variable constant_">FB</span> !== <span class="hljs-literal">null</span>) {
    <span class="hljs-variable constant_">FB</span>.<span class="hljs-property">XFBML</span>.<span class="hljs-title function_">parse</span>();
  }
});
</code></pre>
<p><em>This adds an event listener to trigger (on each visit) the parsing function that renders all Facebook plugins in the current page. <a href="https://github.com/turbolinks/turbolinks#full-list-of-events" target="_blank" rel="noopener noreferrer">See the list of available events for turbolinks</a>.</em></p>
<p><strong>Now all Facebook plugins will be rendered.</strong></p>
<h3>Solve the flickering effect</h3>
<p>Another problem appears when coming back to a previous page that holds a Facebook plugin. The page blinks as the preview shows the rendered plugin, and then <code>turbolinks:load</code> event re-renders it, causing the plugin to disappear and reappear in a fraction of a second.</p>
<p>To avoid this you can add an attribute <code>data-turbolinks-no-cache</code> to all the Facebook plugins like this:</p>
<pre><code class="hljs">&lt;div <span class="hljs-keyword">data</span>-turbolinks-no-cache 
  <span class="hljs-keyword">class</span>=<span class="hljs-string">&quot;fb-like&quot;</span> 
  <span class="hljs-keyword">data</span>-href=<span class="hljs-string">&quot;[#](http://localhost:4567/)&quot;</span> 
  <span class="hljs-keyword">data</span>-layout=<span class="hljs-string">&quot;standard&quot;</span> 
  <span class="hljs-keyword">data</span>-action=<span class="hljs-string">&quot;like&quot;</span> 
  <span class="hljs-keyword">data</span>-size=<span class="hljs-string">&quot;small&quot;</span> 
  <span class="hljs-keyword">data</span>-show-faces=<span class="hljs-string">&quot;true&quot;</span> 
  <span class="hljs-keyword">data</span>-share=<span class="hljs-string">&quot;true&quot;</span>&gt;&lt;/div&gt;
</code></pre>
<p>And in your javascript:</p>
<pre><code class="hljs">$(<span class="hljs-variable language_">document</span>).<span class="hljs-title function_">on</span>(<span class="hljs-string">&quot;turbolinks:before-cache&quot;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
    $(<span class="hljs-string">&#x27;[data-turbolinks-no-cache]&#x27;</span>).<span class="hljs-title function_">remove</span>();
});
</code></pre>
<p>This allows any elements with the <code>data-turbolinks-no-cache</code> attribute to be removed from the turbolinks cached preview of the page.</p>
<p><em>The <code>data-turbolinks-no-cache</code> attribute is just a placeholder and any name can be used as it is not part of the turbolinks core library.</em></p>
<p><em>Also, the attribute can be added to any DOM element that needs to be removed from a cached preview.</em></p>
<p><strong>Done !</strong></p>
<p>Now it is possible for you to use the FB object and render Facebook plugins on all your pages.</p>
<p>I decided on this implementation, based on this <a href="https://github.com/turbolinks/turbolinks/issues/221" target="_blank" rel="noopener noreferrer">conversation</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>How To Build and Deploy Your Personal Blog With Next.js</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Image by Free-Photos from Pixabay</h2>
<p><em>The source code for this guide is <a href="https://github.com/kalopilato/blog-app-starter" target="_blank" rel="noopener noreferrer">available on Github</a> and you can view the <a href="https://www.kalopilato.com/blog/how-to-build-and-deploy-your-personal-blog-with-next-js" target="_blank" rel="noopener noreferrer">original article on my blog</a> (built with this starter).</em></p>
<p>If you’ve been thinking about building your own blog platform and have done a little research you’ve probably noticed that there are <em>plenty</em> of guides to choose from. So why write another one? I have a couple of reasons:</p>
<ol>
<li>
<p>I read through a few of these guides and they always seemed to be missing something: they might be functional but lacking basic styling, styled but not production ready, using technologies that weren’t quite what I was looking for… they can leave you with technical issues to work through before you can ship your app and start publishing blog posts. That’s not to say that these guides aren’t useful. In fact I took a lot of inspiration from reading a few such guides (special shout out to <a href="https://www.netlify.com/blog/2020/05/04/building-a-markdown-blog-with-next-9.4-and-netlify/" target="_blank" rel="noopener noreferrer">Cassidy Williams’ excellent post on Netlify</a>!) but I wanted to collect together all of the other bits and pieces that I’ve added while building my blog starter.</p>
</li>
<li>
<p>This project will likely serve as a base for additional features in future posts so, if nothing else, it provides a common starting point.</p>
</li>
</ol>
<p>If you’ve come here from my previous post <a href="https://www.kalopilato.com/blog/how-to-stop-stalling-and-ship-your-personal-blog" target="_blank" rel="noopener noreferrer">How to Stop Stalling and Ship Your Personal Blog</a> you’ll know that I started my blog site with a short list of requirements; just enough to ship a not horrible looking, functional blog app and start publishing posts. So that’s what we’re doing here. I’ll show you how I built this blog and deployed it to the web, ready to start publishing articles.</p>
<p><em><strong>Note:</strong> The goal of this guide is to build and deploy a blogging app in short order. There won’t be any deep dives, but you should be able to follow along just fine if you have some familiarity with JavaScript, React, HTML, CSS and Markdown.</em></p>
<h3>Requirements and Tech</h3>
<p>In my previous post I came up with this set of requirements:</p>
<blockquote>
<p>Static site. Our blog should be snappy, lightweight and easily indexed.
Basic styling. The bar is intentionally low here. Unless you’re a CSS whizz (I am not) styling can be a time vortex so nothing fancy, just make it easily legible and not a responsive train wreck.
Markdown blog posts. I like markdown. It’s familiar, portable, and has a wide range of tooling available.
Dynamic routing. We don’t want to make any functional code changes to publish a post, just write and publish.
Code syntax highlighting. This is a developer blog after all, code needs to be easy on the eyes.
Ship it! Deployed to the web.</p>
</blockquote>
<p>Seems simple enough right? Here’s what we’ll use to make it happen:</p>
<ul>
<li>
<p><a href="https://reactjs.org/" target="_blank" rel="noopener noreferrer">React</a>. This is the front end javascript library that I’m currently most productive in, it has a very rich ecosystem of supporting libraries and is performant (and fun! Don’t underestimate the importance of fun.) It also has some great framework options. Speaking of which…</p>
</li>
<li>
<p><a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a> will take care of the static site generation and dynamic routing. It also gives us the option to use SSR in future if we want. It’s a great framework and a big part of how we can ship this project so quickly.</p>
</li>
<li>
<p><a href="https://github.com/remarkjs/react-markdown" target="_blank" rel="noopener noreferrer">React Markdown</a> to convert our markdown posts into “ready to render” React components.</p>
</li>
<li>
<p><a href="https://highlightjs.org/" target="_blank" rel="noopener noreferrer">Highlight.js</a> for code syntax highlighting.</p>
</li>
<li>
<p><a href="https://tailwindcss.com/" target="_blank" rel="noopener noreferrer">TailwindCSS</a> for styling. Next.js comes with built in CSS-in-JS support but I’m fast becoming a fan of the utility first nature of Tailwind. It also has a pluggable API that will give us a good head start on styling our content.</p>
</li>
<li>
<p><a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a> for hosting and continuous deployment. Netlify is an amazing platform for hosting Jamstack applications. It is super simple to use, has great developer ergonomics and documentation and a steadily growing range of supporting features and services (e.g. redirects, form handling, functions, etc).</p>
</li>
</ul>
<p>Let’s dive in!</p>
<h3>App Setup</h3>
<p>First we’ll initialise our Next.js app. Run this in your terminal (we’ll be using <strong>npm</strong> for this tutorial, if you’d prefer to use <strong>yarn</strong> for this project then omit the <strong>— use-npm</strong> option):</p>
<pre><code class="hljs"><span class="hljs-string">npx</span> <span class="hljs-built_in">create-next-app</span> <span class="hljs-string">personal-blog</span> <span class="hljs-built_in">--use-npm</span>
</code></pre>
<p>You should now have a directory named <strong>personal-blog</strong> containing your new app. We’ll add a config file for Next.js in the root of the project to specify serverless operation for generating our static site:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./next.config.js</span>
<span class="hljs-type">const</span> <span class="hljs-variable">nextConfig</span> <span class="hljs-operator">=</span> {
  target: <span class="hljs-string">&quot;serverless&quot;</span>,
};

<span class="hljs-keyword">module</span>.<span class="hljs-keyword">exports</span> = nextConfig;
</code></pre>
<p>We’ll circle back to extend Next’s config later, but for now let’s start up our app. From the root of the project run:</p>
<pre><code class="hljs">npm <span class="hljs-built_in">run</span> dev
</code></pre>
<p>Open your browser of choice and visit <strong><a href="http://localhost:3000" target="_blank" rel="noopener noreferrer">http://localhost:3000</a></strong>, you should be greeted with Next’s default home page. Nice! Next we’ll clear out the unnecessary boilerplate code so that we have a clean slate for our app. Delete the <strong>pages/api</strong> directory (we won’t be making use of any back end services for now), the contents of the <strong>styles</strong> directory (also remove the <strong>globals.css</strong> import from <strong>pages/_app.js</strong>), the contents of <strong>pages/index.js</strong>, and create a new directory at the root of the project: <strong>components</strong>. Our directory structure should now look like this:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="amvfrjjledymrno9qs51uo4u" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1hzn_Qwzf_A_Ln_U_Vr4ou_Cz9_M_Pg_d577c78822.png sizes="(min-width: 640px) 159px" src="https://cms-assets.abletech.nz/1hzn_Qwzf_A_Ln_U_Vr4ou_Cz9_M_Pg_d577c78822.png" srcset="https://cms-assets.abletech.nz/thumbnail_1hzn_Qwzf_A_Ln_U_Vr4ou_Cz9_M_Pg_d577c78822.png 159w" data-zooming-width="159" data-zooming-height="156" loading="lazy" width="159" height="156"></figure>
</div>
<p>We’ve also just broken our app, so let’s get it working again. Add this to your <strong>pages/index.js</strong> file:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./pages/index.js</span>
<span class="hljs-keyword">const</span> <span class="hljs-title function_">Index</span> = (<span class="hljs-params"></span>) =&gt; {
  <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Blog Posts<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Index</span>;
</code></pre>
<h3>Basic Layout and Styling</h3>
<p>We want our app to have a consistent layout so we’ll create a simple layout component to wrap all of our pages. Create a new component <strong>components/Layout.js</strong> and fill it with this:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/Layout.js</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Head</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;next/head&quot;</span>;

<span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children, pageTitle }</span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;viewport&quot;</span> <span class="hljs-attr">content</span>=<span class="hljs-string">&quot;width=device-width, initial-scale=1&quot;</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{pageTitle}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>My Blog<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>Blog<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>All content © Me<span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Layout</span>;
</code></pre>
<p>The <strong>Head</strong> component from Next.js gives us a mechanism to insert our own header elements (meta tags, etc) into our pages. Now we can use this layout in our <strong>pages/index.js</strong> page:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./pages/index.js</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Layout</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;../components/Layout&quot;</span>;

<span class="hljs-keyword">const</span> <span class="hljs-title function_">Index</span> = (<span class="hljs-params"></span>) =&gt; {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">pageTitle</span>=<span class="hljs-string">&quot;My Blog&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Blog Posts<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Index</span>;
</code></pre>
<p>Refresh your browser and you should see something like this:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qbp1cw3ue5o3egv7amdosfn0" alt="" data-big=https://cms-assets.abletech.nz/large_1_Hrcb_RG_8zl_F_Et_M2v0_Ed_D_Dzg_29963240ac.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Hrcb_RG_8zl_F_Et_M2v0_Ed_D_Dzg_29963240ac.png" srcset="https://cms-assets.abletech.nz/large_1_Hrcb_RG_8zl_F_Et_M2v0_Ed_D_Dzg_29963240ac.png 1000w, https://cms-assets.abletech.nz/small_1_Hrcb_RG_8zl_F_Et_M2v0_Ed_D_Dzg_29963240ac.png 500w, https://cms-assets.abletech.nz/medium_1_Hrcb_RG_8zl_F_Et_M2v0_Ed_D_Dzg_29963240ac.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Hrcb_RG_8zl_F_Et_M2v0_Ed_D_Dzg_29963240ac.png 245w" data-zooming-width="1000" data-zooming-height="316" loading="lazy" width="1000" height="316"></figure>
</div>
<p>Now for some styling. We’ll set up Tailwind and start applying it to our layout. Install Tailwind:</p>
<pre><code class="hljs">npm <span class="hljs-keyword">install</span> tailwindcss
</code></pre>
<p>Create a PostCSS config file (<strong>postcss.config.js</strong>) at the root of the project to load Tailwind as a plugin:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./postcss.config.js</span>
<span class="hljs-keyword">module</span>.<span class="hljs-keyword">exports</span> = {
  plugins: [<span class="hljs-string">&quot;tailwindcss&quot;</span>],
};
</code></pre>
<p>Create a tailwind stylesheet (<strong>styles/tailwind.css</strong>) to load up our tailwind classes:</p>
<pre><code class="hljs"><span class="hljs-comment">/* ./styles/tailwind.css */</span>
<span class="hljs-selector-attr">[@tailwind]</span>(<span class="hljs-attribute">http</span>:<span class="hljs-comment">//twitter.com/tailwind) base;</span>

<span class="hljs-variable">@tailwind</span> components;

<span class="hljs-variable">@tailwind</span> utilities;
</code></pre>
<p>Then import this stylesheet into our <strong>pages/_app.js</strong> file to load it into the application:</p>
<pre><code class="hljs"><span class="hljs-string">//</span> <span class="hljs-string">./pages/_app.js</span>
import <span class="hljs-string">&quot;../styles/tailwind.css&quot;</span>;
<span class="hljs-string">...</span>
</code></pre>
<p>Now restart your app and we should be ready to start applying Tailwind classes to style up our layout. We’ll give our layout a header, footer and content area. Update <strong>components/Layout.js</strong> like so (feel free to choose different colours, spacings, etc):</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/Layout.js</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Head</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;next/head&quot;</span>;

<span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children, pageTitle }</span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;viewport&quot;</span> <span class="hljs-attr">content</span>=<span class="hljs-string">&quot;width=device-width, initial-scale=1&quot;</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{pageTitle}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;flex flex-col min-h-screen&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-full h-16 border-b border-purple-500 flex items-center justify-center&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-11/12 md:w-full max-w-3xl flex flex-row justify-between&quot;</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-2xl text-purple-500&quot;</span>&gt;</span>My Blog<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-2xl text-gray-600&quot;</span>&gt;</span>Blog<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-11/12 md:w-full max-w-2xl mx-auto my-8 flex-grow&quot;</span>&gt;</span>
          {children}
        <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">footer</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;flex flex-col items-center w-full h-24 border-t border-purple-500&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-11/12 md:w-full max-w-3xl m-auto flex flex-row items-center justify-center&quot;</span>&gt;</span>
            All content © Me
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Layout</span>;
</code></pre>
<p>Alright, we’ve got a basic responsive layout with positions for a logo, navigation, content and a footer.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wrhzmy3k1h6ya6ly95l5ix5c" alt="" data-big=https://cms-assets.abletech.nz/large_174u_Owg67n6h_AZ_6_R_Oy_Zx8_Q_d0615cf63d.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/174u_Owg67n6h_AZ_6_R_Oy_Zx8_Q_d0615cf63d.png" srcset="https://cms-assets.abletech.nz/large_174u_Owg67n6h_AZ_6_R_Oy_Zx8_Q_d0615cf63d.png 1000w, https://cms-assets.abletech.nz/small_174u_Owg67n6h_AZ_6_R_Oy_Zx8_Q_d0615cf63d.png 500w, https://cms-assets.abletech.nz/medium_174u_Owg67n6h_AZ_6_R_Oy_Zx8_Q_d0615cf63d.png 750w, https://cms-assets.abletech.nz/thumbnail_174u_Owg67n6h_AZ_6_R_Oy_Zx8_Q_d0615cf63d.png 245w" data-zooming-width="1000" data-zooming-height="602" loading="lazy" width="1000" height="602"></figure>
</div>
<h3>Markdown Part 1: Blog Post List and Dynamic Routes</h3>
<p>We need a way to list our blog posts and link to pages to read them. We <em>could</em> manually create these links/pages every time we write a post but who wants to do that?! We know we’ll be writing our posts in Markdown so let’s create a couple of posts, load them up at build time, and generate the list and blog post pages for our static site.</p>
<p>Create a <strong>posts</strong> directory in the root of the project, and inside that directory create a couple of markdown files e.g.</p>
<pre><code class="hljs"><span class="hljs-comment">## &lt;!-- ./posts/first_post.md --&gt;</span>

<span class="hljs-meta">---
</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">&quot;First Post!&quot;</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">&quot;New Year&#x27;s resolution achieved, I started my very own blog!&quot;</span>
<span class="hljs-attr">date:</span> <span class="hljs-number">1</span> <span class="hljs-string">January</span> <span class="hljs-number">2020</span>

<span class="hljs-meta">---
</span>
<span class="hljs-string">I</span> <span class="hljs-string">wish</span> <span class="hljs-string">I&#x27;d</span> <span class="hljs-string">done</span> <span class="hljs-string">this</span> <span class="hljs-string">ages</span> <span class="hljs-string">ago.</span>

<span class="hljs-comment">## &lt;!-- ./posts/second_post.md --&gt;</span>

<span class="hljs-meta">---
</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">&quot;Second Post&quot;</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">&quot;And I even followed through with a second post!&quot;</span>
<span class="hljs-attr">date:</span> <span class="hljs-number">8</span> <span class="hljs-string">January</span> <span class="hljs-number">2020</span>

<span class="hljs-meta">---
</span>
<span class="hljs-string">Occaecat</span> <span class="hljs-string">Lorem</span> <span class="hljs-string">mollit</span> <span class="hljs-string">cupidatat</span> <span class="hljs-string">elit</span> <span class="hljs-string">incididunt</span> <span class="hljs-string">non</span> <span class="hljs-string">consectetur</span> <span class="hljs-string">eiusmod</span> <span class="hljs-string">qui</span> <span class="hljs-string">adipisicing</span> <span class="hljs-string">duis</span> <span class="hljs-string">sunt</span> <span class="hljs-string">irure</span> <span class="hljs-string">minim.</span>

<span class="hljs-comment">## Lorem Ipsum</span>

<span class="hljs-string">Tempor</span> <span class="hljs-string">sunt</span> <span class="hljs-string">deserunt</span> <span class="hljs-string">qui</span> <span class="hljs-string">quis</span> <span class="hljs-string">commodo</span> <span class="hljs-string">voluptate</span> <span class="hljs-string">laboris</span> <span class="hljs-string">est</span> <span class="hljs-string">ut</span> <span class="hljs-string">qui</span> <span class="hljs-string">in.</span> <span class="hljs-string">Nostrud</span> <span class="hljs-string">ut</span> <span class="hljs-string">laborum</span> <span class="hljs-string">ea</span> <span class="hljs-string">mollit</span> <span class="hljs-string">incididunt</span> <span class="hljs-string">ea</span> <span class="hljs-string">culpa</span> <span class="hljs-string">nisi</span> <span class="hljs-string">sint</span> <span class="hljs-string">excepteur</span> <span class="hljs-string">do.</span> <span class="hljs-string">Eiusmod</span> <span class="hljs-string">aliqua</span> <span class="hljs-string">in</span> <span class="hljs-string">adipisicing</span> <span class="hljs-string">cupidatat</span> <span class="hljs-string">excepteur.</span>

<span class="hljs-comment">## Hipster Ipsum</span>

<span class="hljs-string">I&#x27;m</span> <span class="hljs-string">baby</span> <span class="hljs-string">enamel</span> <span class="hljs-string">pin</span> <span class="hljs-string">swag</span> <span class="hljs-string">gastropub</span> <span class="hljs-string">bitters</span> <span class="hljs-string">migas</span> <span class="hljs-string">lomo,</span> <span class="hljs-string">dreamcatcher</span> <span class="hljs-string">chartreuse</span> <span class="hljs-string">vegan</span> <span class="hljs-string">normcore.</span> <span class="hljs-string">Trust</span> <span class="hljs-string">fund</span> <span class="hljs-string">chicharrones</span> <span class="hljs-string">artisan</span> <span class="hljs-string">live-edge</span> <span class="hljs-string">portland</span> <span class="hljs-string">swag</span> <span class="hljs-string">jianbing</span> <span class="hljs-string">knausgaard</span> <span class="hljs-string">put</span> <span class="hljs-string">a</span> <span class="hljs-string">bird</span> <span class="hljs-string">on</span> <span class="hljs-string">it</span> <span class="hljs-string">brunch</span> <span class="hljs-string">pitchfork</span> <span class="hljs-string">bushwick</span> <span class="hljs-string">kinfolk.</span> <span class="hljs-string">Unicorn</span> <span class="hljs-string">bicycle</span> <span class="hljs-string">rights</span> <span class="hljs-string">waistcoat</span> <span class="hljs-string">messenger</span> <span class="hljs-string">bag</span> <span class="hljs-string">hexagon</span> <span class="hljs-string">glossier</span> <span class="hljs-string">farm-to-table</span> <span class="hljs-string">kinfolk</span> <span class="hljs-string">poutine</span> <span class="hljs-string">occupy</span> <span class="hljs-string">vexillologist</span> <span class="hljs-string">gochujang</span> <span class="hljs-string">skateboard</span> <span class="hljs-string">activated</span> <span class="hljs-string">charcoal.</span> <span class="hljs-string">Street</span> <span class="hljs-string">art</span> <span class="hljs-string">air</span> <span class="hljs-string">plant</span> <span class="hljs-string">tbh</span> <span class="hljs-string">chicharrones,</span> <span class="hljs-string">try-hard</span> <span class="hljs-string">listicle</span> <span class="hljs-string">bushwick</span> <span class="hljs-string">chia</span> <span class="hljs-string">glossier.</span>

<span class="hljs-string">&gt;</span> <span class="hljs-string">Street</span> <span class="hljs-string">art</span> <span class="hljs-string">air</span> <span class="hljs-string">plant</span> <span class="hljs-string">tbh</span>

</code></pre>
<pre><code class="hljs">javascript
const message <span class="hljs-operator">=</span> <span class="hljs-string">&quot;Hello&quot;</span><span class="hljs-comment">;</span>
console.log(message)<span class="hljs-comment">;</span>
</code></pre>
<p>We’ll need a webpack loader to handle these markdown files so install <strong>raw-loader</strong>:</p>
<p><code>npm install raw-loader</code></p>
<p>and update our <strong>next.config.js</strong> to add it to Next’s webpack config:</p>
<pre><code class="hljs">// ./<span class="hljs-built_in">next</span>.<span class="hljs-built_in">config</span>.js
const nextConfig = {
  target: <span class="hljs-string">&quot;serverless&quot;</span>,
  webpack: <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(config)</span></span> {
    <span class="hljs-built_in">config</span>.<span class="hljs-built_in">module</span>.rules.push({
      test: /\.md$/,
      use: <span class="hljs-string">&quot;raw-loader&quot;</span>,
    });
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">config</span>;
  },
};

<span class="hljs-built_in">module</span>.exports = nextConfig;
</code></pre>
<p>You will have noticed that I’ve added a few key:value pairs at the start of the markdown files — this is <strong>frontmatter</strong>, a block of YAML content that we’ll use as meta-data for our file. We’re not using a backend service for our posts (e.g. CMS, database) so we’re essentially using our markdown files as our data layer. Frontmatter makes this possible and we’ll use it for listing our blog posts. To read the frontmatter we’ll use the <strong>gray-matter</strong> package:</p>
<p><code>npm install gray-matter</code></p>
<p>Now we need a way to load these files when we’re building the app. Let’s create a JS file in the <strong>posts</strong> directory to house utility functions for working with our blog post files and add a function to gather up the data for our post list:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./posts/index.js</span>
<span class="hljs-keyword">import</span> matter <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;gray-matter&quot;</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title function_">posts</span> = (<span class="hljs-params"></span>) =&gt;
  (<span class="hljs-function">(<span class="hljs-params">context</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> keys = context.<span class="hljs-title function_">keys</span>();
    <span class="hljs-keyword">const</span> documents = keys.<span class="hljs-title function_">map</span>(context);

<span class="hljs-keyword">return</span> keys
      .<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">key, index</span>) =&gt;</span> {
        <span class="hljs-comment">// We&#x27;ll use the filename as a &#x27;slug&#x27; for the post - this will be used for the post&#x27;s route</span>
        <span class="hljs-keyword">const</span> slug = key.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/^.*[\\\/]/</span>, <span class="hljs-string">&quot;&quot;</span>).<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, -<span class="hljs-number">3</span>);
        <span class="hljs-keyword">const</span> <span class="hljs-variable language_">document</span> = documents[index];
        <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: frontmatter, <span class="hljs-attr">content</span>: body } = <span class="hljs-title function_">matter</span>(<span class="hljs-variable language_">document</span>.<span class="hljs-property">default</span>);

        <span class="hljs-keyword">return</span> { frontmatter, body, slug };
      })
      .<span class="hljs-title function_">sort</span>(
        <span class="hljs-function">(<span class="hljs-params">post1, post2</span>) =&gt;</span>
          <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(post2.<span class="hljs-property">frontmatter</span>.<span class="hljs-property">date</span>) - <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(post1.<span class="hljs-property">frontmatter</span>.<span class="hljs-property">date</span>)
      );
  })(
    <span class="hljs-comment">// Since Next.js uses webpack we can take advantage of webpack&#x27;s `require.context` to load our markdown files</span>
    <span class="hljs-built_in">require</span>.<span class="hljs-title function_">context</span>(<span class="hljs-string">&quot;./&quot;</span>, <span class="hljs-literal">true</span>, <span class="hljs-regexp">/\.md$/</span>)
  );
</code></pre>
<p>And finally we can make use of this function to list the blog posts on our home page. If we export a function named <strong>getStaticProps</strong> from a page file, Next will run this function at build time and pass the result into our component as props. All we need it to do is return the result of our <strong>posts</strong> function and our component will receive the posts data! Update the home page as follows (adding the <strong>getStaticProps</strong> function and <strong>PostsList</strong> component):</p>
<pre><code class="hljs"><span class="hljs-comment">// ./pages/index.js</span>
<span class="hljs-keyword">import</span> { posts } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;../posts&quot;</span>;

<span class="hljs-keyword">import</span> <span class="hljs-title class_">Layout</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;../components/Layout&quot;</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;next/link&quot;</span>;

<span class="hljs-keyword">const</span> <span class="hljs-title function_">Index</span> = (<span class="hljs-params">{ posts }</span>) =&gt; {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">pageTitle</span>=<span class="hljs-string">&quot;My Blog&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-2xl font-semibold&quot;</span>&gt;</span>Blog Posts<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PostsList</span> <span class="hljs-attr">posts</span>=<span class="hljs-string">{posts}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">const</span> <span class="hljs-title function_">PostsList</span> = (<span class="hljs-params">{ posts }</span>) =&gt; {
  <span class="hljs-keyword">if</span> (!posts || !posts.<span class="hljs-property">length</span>) <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>No posts found<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;

  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-full&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;mt-4&quot;</span>&gt;</span>
        {posts.map((post) =&gt; {
          const { frontmatter, slug } = post;
          const { description, date, title } = frontmatter;

          return (
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>
              <span class="hljs-attr">key</span>=<span class="hljs-string">{slug}</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;px-8 py-2 m-0 mt-4 border-b border-card-border hover:bg-gray-100&quot;</span>
            &gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">blog</span>/${<span class="hljs-attr">slug</span>}`}&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">a</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-xl font-medium&quot;</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;mt-2 mb-4 font-light&quot;</span>&gt;</span>{description}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-sm font-hairline&quot;</span>&gt;</span>{date}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          );
        })}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getStaticProps</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> postsData = <span class="hljs-title function_">posts</span>();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      <span class="hljs-attr">posts</span>: postsData,
    },
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Index</span>;
</code></pre>
<p>and you should now see your list of blog posts:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sxt0ojne4wnpent9s20k4sh3" alt="" data-big=https://cms-assets.abletech.nz/large_13l_Nhm_z_XR_7_Kc4d_L_Ta5n4_g_6d0f399d3c.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/13l_Nhm_z_XR_7_Kc4d_L_Ta5n4_g_6d0f399d3c.png" srcset="https://cms-assets.abletech.nz/large_13l_Nhm_z_XR_7_Kc4d_L_Ta5n4_g_6d0f399d3c.png 1000w, https://cms-assets.abletech.nz/small_13l_Nhm_z_XR_7_Kc4d_L_Ta5n4_g_6d0f399d3c.png 500w, https://cms-assets.abletech.nz/medium_13l_Nhm_z_XR_7_Kc4d_L_Ta5n4_g_6d0f399d3c.png 750w, https://cms-assets.abletech.nz/thumbnail_13l_Nhm_z_XR_7_Kc4d_L_Ta5n4_g_6d0f399d3c.png 245w" data-zooming-width="1000" data-zooming-height="570" loading="lazy" width="1000" height="570"></figure>
</div>
<p>You’ve probably noticed that if you click on one of these posts you’re presented with a <strong>404</strong> page. We haven’t created our blog post page yet… onwards!</p>
<h3>Markdown Part 2: Render Blog Post Content</h3>
<p>Now we need to set up a page to render individual blog posts. Next.js uses the directory/file structure to define the available routes, and will dynamically generate routes when the filename is wrapped in brackets. Create a <strong>blog</strong> directory containing a <strong>[post].js</strong> file. This file will need to export three things:</p>
<ol>
<li>
<p>a <strong>getStaticPaths</strong> function to define the routes that we want generated</p>
</li>
<li>
<p>a <strong>getStaticProps</strong> function to load the blog post for the page (much the same as we loaded all posts for the index page)</p>
</li>
<li>
<p>a React component for rendering the post</p>
</li>
</ol>
<p>Let’s start with the routes. We’ll use the post slugs to define the routes so we’ll add a function to the <strong>posts/index.js</strong> utility file to generate the slugs from the file names (this is just a stripped down version of the existing utility function — feel free to use that function instead if you prefer):</p>
<pre><code class="hljs"><span class="hljs-regexp">//</span> ./posts/index.js
<span class="hljs-keyword">export</span> const postSlugs = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span>
  (<span class="hljs-function"><span class="hljs-params">(context)</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> context
      .keys()
      .map(<span class="hljs-function"><span class="hljs-params">(key)</span> =&gt;</span> key.replace(<span class="hljs-regexp">/^.*[\\\/]/</span>, <span class="hljs-string">&quot;&quot;</span>).slice(<span class="hljs-number">0</span>, <span class="hljs-number">-3</span>));
  })(<span class="hljs-built_in">require</span>.context(<span class="hljs-string">&quot;./&quot;</span>, <span class="hljs-literal">true</span>, <span class="hljs-regexp">/\.md$/</span>));
</code></pre>
<p>Now we’ll need a function to get a post using its slug. Add this to the post utilities:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./posts/index.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title function_">postForSlug</span> = <span class="hljs-keyword">async</span> (<span class="hljs-params">slug</span>) =&gt; {
  <span class="hljs-keyword">const</span> <span class="hljs-variable language_">document</span> = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">`./<span class="hljs-subst">${slug}</span>.md`</span>);
  <span class="hljs-keyword">const</span> { <span class="hljs-attr">data</span>: frontmatter, <span class="hljs-attr">content</span>: body } = <span class="hljs-title function_">matter</span>(<span class="hljs-variable language_">document</span>.<span class="hljs-property">default</span>);

  <span class="hljs-keyword">return</span> { frontmatter, body, slug };
};
</code></pre>
<p>Finally we can render the page using our new data wrangling functions. Populate the <strong>[post].js</strong> file like so:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./pages/blog/[post].js</span>
<span class="hljs-keyword">import</span> { postSlugs, postForSlug } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;../../posts&quot;</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Layout</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;../../components/Layout&quot;</span>;

<span class="hljs-keyword">function</span> <span class="hljs-title function_">Post</span>(<span class="hljs-params">{ frontmatter, body }</span>) {
  <span class="hljs-keyword">if</span> (!frontmatter) <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;&gt;</span><span class="hljs-tag">&lt;/&gt;</span></span>;

  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">pageTitle</span>=<span class="hljs-string">{frontmatter.title}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-full&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;max-w-none&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{frontmatter.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;italic&quot;</span>&gt;</span>{frontmatter.date}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          {body}
        <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getStaticProps</span>(<span class="hljs-params">{ params }</span>) {
  <span class="hljs-keyword">const</span> { frontmatter, body } = <span class="hljs-keyword">await</span> <span class="hljs-title function_">postForSlug</span>(params.<span class="hljs-property">post</span>);

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: {
      frontmatter,
      body,
    },
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">getStaticPaths</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">const</span> paths = <span class="hljs-title function_">postSlugs</span>().<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">slug</span>) =&gt;</span> <span class="hljs-string">`/blog/<span class="hljs-subst">${slug}</span>`</span>);

  <span class="hljs-keyword">return</span> {
    paths,
    <span class="hljs-attr">fallback</span>: <span class="hljs-literal">false</span>,
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Post</span>;
</code></pre>
<p>Now if you refresh the home page of your app and click on the most recent post you should be directed to your newly created page that looks something like this:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h3kqoclrewa5fjnzjtxz5non" alt="" data-big=https://cms-assets.abletech.nz/large_1_r_Pg09w2_Re_Wu_V_Xiukuc_Jog_5d17345528.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_r_Pg09w2_Re_Wu_V_Xiukuc_Jog_5d17345528.png" srcset="https://cms-assets.abletech.nz/large_1_r_Pg09w2_Re_Wu_V_Xiukuc_Jog_5d17345528.png 1000w, https://cms-assets.abletech.nz/small_1_r_Pg09w2_Re_Wu_V_Xiukuc_Jog_5d17345528.png 500w, https://cms-assets.abletech.nz/medium_1_r_Pg09w2_Re_Wu_V_Xiukuc_Jog_5d17345528.png 750w, https://cms-assets.abletech.nz/thumbnail_1_r_Pg09w2_Re_Wu_V_Xiukuc_Jog_5d17345528.png 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<p>This is great but we still have some work to do. We obviously need some styling, but more importantly our markdown content is rendering as an unformatted blob of text — we want to get some semantic HTML onto the page. We’ll use <strong>react-markdown</strong> to sort that out:</p>
<p><code>npm install react-markdown</code></p>
<p>and update our Post component like so:</p>
<pre><code class="hljs"><span class="hljs-string">`// ./pages/blog/[post].js
import ReactMarkdown from &quot;react-markdown&quot;;

`</span><span class="hljs-comment">// ./pages/blog/[post].js</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">ReactMarkdown</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;react-markdown&quot;</span>;

...

<span class="hljs-keyword">function</span> <span class="hljs-title function_">Post</span>(<span class="hljs-params">{ frontmatter, body }</span>) {
  <span class="hljs-keyword">if</span> (!frontmatter) <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;&gt;</span><span class="hljs-tag">&lt;/&gt;</span></span>;

  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">pageTitle</span>=<span class="hljs-string">{frontmatter.title}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-full&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;max-w-none&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{frontmatter.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;italic&quot;</span>&gt;</span>{frontmatter.date}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">ReactMarkdown</span> <span class="hljs-attr">source</span>=<span class="hljs-string">{body}</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
  );
}

...
</code></pre>
<p>Ok, so now our markdown content is translated to markup on the page, but it’s not looking very good.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e7y4qqz2kyj5zlmt9hf5wkgh" alt="" data-big=https://cms-assets.abletech.nz/large_1h_PT_Hoa_h_Yg_Zr1_H1_R_Wn_K50w_7292f5859f.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1h_PT_Hoa_h_Yg_Zr1_H1_R_Wn_K50w_7292f5859f.png" srcset="https://cms-assets.abletech.nz/large_1h_PT_Hoa_h_Yg_Zr1_H1_R_Wn_K50w_7292f5859f.png 1000w, https://cms-assets.abletech.nz/small_1h_PT_Hoa_h_Yg_Zr1_H1_R_Wn_K50w_7292f5859f.png 500w, https://cms-assets.abletech.nz/medium_1h_PT_Hoa_h_Yg_Zr1_H1_R_Wn_K50w_7292f5859f.png 750w, https://cms-assets.abletech.nz/thumbnail_1h_PT_Hoa_h_Yg_Zr1_H1_R_Wn_K50w_7292f5859f.png 245w" data-zooming-width="1000" data-zooming-height="621" loading="lazy" width="1000" height="621"></figure>
</div>
<p>Tailwind (by default) has a <a href="https://tailwindcss.com/docs/preflight" target="_blank" rel="noopener noreferrer">very stripped back set of base styles</a>, leaving us with a very plain looking page. This is done with good reason but, fortunately for us, a plugin has been created to add some sensible default styles back to the minimal base styles: <a href="https://github.com/tailwindlabs/tailwindcss-typography" target="_blank" rel="noopener noreferrer">Tailwind’s Typography plugin</a>. Let’s add that in to give our styling a low-cost boost:</p>
<p><code>npm install [@tailwindcss/typography](http://twitter.com/tailwindcss/typography)</code></p>
<p>and now we’ll need to configure Tailwind to use this plugin. Create a <strong>tailwind.config.js</strong> file in the root of the project like so:</p>
<pre><code class="hljs"><span class="hljs-operator">//</span> <span class="hljs-symbol">./tailwind.config.js</span>
m<span class="hljs-attr">odule.exports</span> <span class="hljs-operator">=</span> {
  <span class="hljs-params">future:</span> {
    <span class="hljs-params">removeDeprecatedGapUtilities:</span> <span class="hljs-literal">true</span>,
    <span class="hljs-params">purgeLayersByDefault:</span> <span class="hljs-literal">true</span>,
  },
  <span class="hljs-params">purge:</span> [<span class="hljs-string">&quot;./components/**/*.+(js|jsx)&quot;</span>, <span class="hljs-string">&quot;./pages/**/*.+(js|jsx)&quot;</span>],
  <span class="hljs-params">theme:</span> {},
  <span class="hljs-params">plugins:</span> [require(<span class="hljs-string">&quot;[@tailwindcss/typography](http://twitter.com/tailwindcss/typography)&quot;</span>)],
};
</code></pre>
<p>This adds the Typography plugin and, while we’re at it, we turn on a couple of flags for future compatibility and specify sources to use when determining which tailwind styles are in use and which can be purged.</p>
<p>To use the Typography styles, all we need to do is add the <strong>prose</strong> class to our generated HTML:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./pages/blog/[post].js</span>
...

<span class="hljs-keyword">function</span> <span class="hljs-title function_">Post</span>(<span class="hljs-params">{ frontmatter, body }</span>) {
  <span class="hljs-keyword">if</span> (!frontmatter) <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;&gt;</span><span class="hljs-tag">&lt;/&gt;</span></span>;

  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">pageTitle</span>=<span class="hljs-string">{frontmatter.title}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-full&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;prose max-w-none&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{frontmatter.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;italic&quot;</span>&gt;</span>{frontmatter.date}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">ReactMarkdown</span> <span class="hljs-attr">source</span>=<span class="hljs-string">{body}</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
  );
}

...
</code></pre>
<p>Restart the app and voilà, we have styled markup!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qd3ooo78wsrjb9wcydpe6i2x" alt="" data-big=https://cms-assets.abletech.nz/large_1g_B16u_Vq03f_SFW_Wg_Gczw_IZA_02f2649ac5.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 157px" src="https://cms-assets.abletech.nz/1g_B16u_Vq03f_SFW_Wg_Gczw_IZA_02f2649ac5.png" srcset="https://cms-assets.abletech.nz/large_1g_B16u_Vq03f_SFW_Wg_Gczw_IZA_02f2649ac5.png 1000w, https://cms-assets.abletech.nz/small_1g_B16u_Vq03f_SFW_Wg_Gczw_IZA_02f2649ac5.png 500w, https://cms-assets.abletech.nz/medium_1g_B16u_Vq03f_SFW_Wg_Gczw_IZA_02f2649ac5.png 750w, https://cms-assets.abletech.nz/thumbnail_1g_B16u_Vq03f_SFW_Wg_Gczw_IZA_02f2649ac5.png 157w" data-zooming-width="1000" data-zooming-height="997" loading="lazy" width="1000" height="997"></figure>
</div>
<p>Sooo good, thanks Tailwind! But there’s still one more thing:</p>
<blockquote>
<p>Code syntax highlighting.</p>
</blockquote>
<p>Fortunately ReactMarkdown lets us define our own <strong>renderers</strong> for specific element types. We’re going to use <a href="https://highlightjs.org/" target="_blank" rel="noopener noreferrer">highlight.js</a> to create a custom code block renderer with built in syntax highlighting. Install the package:</p>
<p><code>npm install highlight.js</code></p>
<p>and create a <strong>CodeBlock</strong> component in the <strong>components</strong> directory:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/CodeBlock.js</span>
<span class="hljs-keyword">import</span> highlight <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;highlight.js&quot;</span>;

<span class="hljs-keyword">function</span> <span class="hljs-title function_">CodeBlock</span>(<span class="hljs-params">{ value }</span>) {
  <span class="hljs-keyword">const</span> highlighted = highlight.<span class="hljs-title function_">highlightAuto</span>(value).<span class="hljs-property">value</span>;

  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">pre</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;hljs&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">code</span> <span class="hljs-attr">dangerouslySetInnerHTML</span>=<span class="hljs-string">{{__html:</span> <span class="hljs-attr">highlighted</span>}} /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">pre</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">CodeBlock</span>;
</code></pre>
<p>This will take our <strong>value</strong> (in this case the code block contents) and apply various <strong>hljs</strong> classes to the elements within. Don’t be concerned about the <strong>dangerouslySetInnerHTML</strong> prop — this code is only run at build time and allows the highlighting to be generated by the build system rather than the browser. Now we can use this component as our code block renderer — pass it in to the <strong>ReactMarkdown</strong> component like so:</p>
<pre><code class="hljs"><span class="language-xml">// ./pages/blog/[post].js
import CodeBlock from &quot;../../components/CodeBlock&quot;;

...

<span class="hljs-tag">&lt;<span class="hljs-name">ReactMarkdown</span>
  <span class="hljs-attr">source</span>=<span class="hljs-string">{markdownBody}</span>
  <span class="hljs-attr">renderers</span>=</span></span><span class="hljs-template-variable">{{<span class="hljs-name">code:</span> CodeBlock}}</span><span class="language-xml"><span class="hljs-tag">
/&gt;</span>

...
</span></code></pre>
<p>If you reload the app you won’t see any changes yet, but if you inspect the code block you’ll see <strong>hljs-xxx</strong> classes have been added to the HTML elements within the tags. So that’s all we’re missing, we need to load those classes. <strong>highlight.js</strong> ships with a generous collection of themes to choose from, you can preview them here. Once you’ve found a theme you like we can simply import it as global styles from our <strong>_app.js</strong> file. For example, add this line to use the Night Owl theme:</p>
<pre><code class="hljs"><span class="hljs-string">//</span> <span class="hljs-string">./pages/_app.js</span>
import <span class="hljs-string">&quot;../node_modules/highlight.js/styles/night-owl.css&quot;</span>;

<span class="hljs-string">...</span>
</code></pre>
<p>and we now have syntax highlighting in our code block!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="belaubkfxbuw4uz0wne3ub30" alt="" data-big=https://cms-assets.abletech.nz/large_1j_X_v4_Gk_ULLN_5r_JY_1lss_4_A_e3764cb942.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 179px" src="https://cms-assets.abletech.nz/1j_X_v4_Gk_ULLN_5r_JY_1lss_4_A_e3764cb942.png" srcset="https://cms-assets.abletech.nz/large_1j_X_v4_Gk_ULLN_5r_JY_1lss_4_A_e3764cb942.png 1000w, https://cms-assets.abletech.nz/small_1j_X_v4_Gk_ULLN_5r_JY_1lss_4_A_e3764cb942.png 500w, https://cms-assets.abletech.nz/medium_1j_X_v4_Gk_ULLN_5r_JY_1lss_4_A_e3764cb942.png 750w, https://cms-assets.abletech.nz/thumbnail_1j_X_v4_Gk_ULLN_5r_JY_1lss_4_A_e3764cb942.png 179w" data-zooming-width="1000" data-zooming-height="869" loading="lazy" width="1000" height="869"></figure>
</div>
<p>You can use ReactMarkdown’s <strong>renderers</strong> prop to further customise your markdown rendering as you like — for example, I preferred less whitespace around the headings so I created a custom <strong>heading</strong> renderer.</p>
<h3>Finishing Touches</h3>
<p>We’re in the home stretch! Now that we’ve got the basic blogging engine setup let’s add a few finishing touches before we deploy.</p>
<h3>Favicon</h3>
<p>As you can see our app is still using the default Vercel logo, we should replace it with something of our own. If you already have a logo then that might be an obvious choice but if you don’t, like me, you can generate a simple one from text for now using <a href="https://favicon.io/favicon-generator/" target="_blank" rel="noopener noreferrer">favicon.io</a>’s generator. Just enter some text, choose your colours, download the result and unzip it into your <strong>./public</strong> directory (overwriting the existing <strong>favicon.ico</strong> in the process).</p>
<h3>Navigation</h3>
<p>You’ll recall that our <strong>Layout</strong> component has a <strong>nav</strong> element but it doesn’t contain any links. It’d also be nice if our logo was a link to the root of the app. Next.js provides a <strong>Link</strong> component that we’ll use to wire this up. Wrap our logo with a link:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/Layout.js</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;next/Link&quot;</span>

...

&lt;div className=<span class="hljs-string">&quot;text-2xl text-purple-500&quot;</span>&gt;
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span>&gt;</span>My Blog<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span></span>
&lt;/div&gt;

...
</code></pre>
<p>and you should now be able to click the logo to return to the home page. Create an <strong>AppNav</strong> component to replace the <strong>nav</strong> element:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/Layout.js</span>
...

<span class="hljs-keyword">function</span> <span class="hljs-title function_">AppNav</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-2xl text-gray-600&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;hover:text-purple-500&quot;</span>&gt;</span>Blog<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  );
}
</code></pre>
<p>and you should now be able to click the <strong>Blog</strong> nav link to return to the home page. But wouldn’t it be nice if the current nav link was highlighted? That’s easily fixed too, we just need to create our own wrapper for the <strong>Link</strong> component to update its styling when it’s active. Create an <strong>ActiveLink</strong> component:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/ActiveLink.js</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">React</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;react&quot;</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;next/link&quot;</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;next/router&quot;</span>;

<span class="hljs-keyword">function</span> <span class="hljs-title function_">ActiveLink</span>(<span class="hljs-params">{ href, activeClassName, children }</span>) {
  <span class="hljs-keyword">const</span> router = <span class="hljs-title function_">useRouter</span>();

  <span class="hljs-keyword">const</span> child = <span class="hljs-title class_">React</span>.<span class="hljs-property">Children</span>.<span class="hljs-title function_">only</span>(children);

  <span class="hljs-keyword">let</span> className = child.<span class="hljs-property">props</span>.<span class="hljs-property">className</span> || <span class="hljs-string">&quot;&quot;</span>;
  <span class="hljs-keyword">if</span> (router.<span class="hljs-property">pathname</span> === href &amp;&amp; activeClassName) {
    className = <span class="hljs-string">`<span class="hljs-subst">${className}</span> <span class="hljs-subst">${activeClassName}</span>`</span>.<span class="hljs-title function_">trim</span>();
  }

  <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{href}</span>&gt;</span>{React.cloneElement(child, { className })}<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span></span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">ActiveLink</span>;
</code></pre>
<p>This component renders a <strong>Link</strong>, and watches the current route to see if it matches the link’s <strong>href</strong> prop — if so it applies the specified <strong>activeClassName</strong> to the Link’s child component. Now swap out this component for the <strong>Link</strong> component in our <strong>AppNav</strong> component, passing in an <strong>activeClassName</strong> to highlight the active link:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/Layout.js</span>
...

<span class="hljs-keyword">function</span> <span class="hljs-title function_">AppNav</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-2xl text-gray-600&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ActiveLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/&quot;</span> <span class="hljs-attr">activeClassName</span>=<span class="hljs-string">&quot;text-purple-500&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;hover:text-purple-500&quot;</span>&gt;</span>Blog<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ActiveLink</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  );
}

...
</code></pre>
<p>That’s it, we now have app navigation!</p>
<h3>About Page</h3>
<p>Your readers might want to know a little about the person behind the blog posts so let’s make a space for that. Thanks to Next.js and our new <strong>AppNav</strong> component, adding an About page is trivial. Create a new page:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./pages/about.js</span>
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Layout</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;../components/Layout&quot;</span>;

<span class="hljs-keyword">function</span> <span class="hljs-title function_">About</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">pageTitle</span>=<span class="hljs-string">&quot;About | My Blog&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;prose max-w-none&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>About<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello, I&#x27;m a blogger and I blog about interesting things.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">About</span>;
</code></pre>
<p>and add an entry to our <strong>AppNav</strong> component:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/Layout.js</span>
...

<span class="hljs-keyword">function</span> <span class="hljs-title function_">AppNav</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-2xl text-gray-600&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ActiveLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/&quot;</span> <span class="hljs-attr">activeClassName</span>=<span class="hljs-string">&quot;text-purple-500&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;hover:text-purple-500&quot;</span>&gt;</span>Blog<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ActiveLink</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ActiveLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/about&quot;</span> <span class="hljs-attr">activeClassName</span>=<span class="hljs-string">&quot;text-purple-500&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;hover:text-purple-500&quot;</span>&gt;</span>About<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ActiveLink</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  );
}

...
</code></pre>
<p>And there we have it, an <strong>About</strong> page!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ati9wicy5vt9aa6quzl2jymt" alt="" data-big=https://cms-assets.abletech.nz/large_1u2_UD_70_Fd_JFD_Tc_IL_Spwh_STQ_d589782586.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1u2_UD_70_Fd_JFD_Tc_IL_Spwh_STQ_d589782586.png" srcset="https://cms-assets.abletech.nz/large_1u2_UD_70_Fd_JFD_Tc_IL_Spwh_STQ_d589782586.png 1000w, https://cms-assets.abletech.nz/small_1u2_UD_70_Fd_JFD_Tc_IL_Spwh_STQ_d589782586.png 500w, https://cms-assets.abletech.nz/medium_1u2_UD_70_Fd_JFD_Tc_IL_Spwh_STQ_d589782586.png 750w, https://cms-assets.abletech.nz/thumbnail_1u2_UD_70_Fd_JFD_Tc_IL_Spwh_STQ_d589782586.png 245w" data-zooming-width="1000" data-zooming-height="494" loading="lazy" width="1000" height="494"></figure>
</div>
<p>You might’ve noticed that we used Typography’s <strong>prose</strong> class for the <strong>About</strong> page, just like we did for our <strong>post</strong> page. While we’re at it we might as well use it for our home page to keep our styling consistent app-wide:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./pages/index.js</span>
...

<span class="hljs-keyword">const</span> <span class="hljs-title function_">Index</span> = (<span class="hljs-params">{ posts }</span>) =&gt; {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">pageTitle</span>=<span class="hljs-string">&quot;My Blog&quot;</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;prose max-w-none&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Blog Posts<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PostsList</span> <span class="hljs-attr">posts</span>=<span class="hljs-string">{posts}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
  );
};

...
</code></pre>
<h3>Footer</h3>
<p>Our app’s footer is looking a little lacklustre, let’s use it to link out to our social media channels. We’ll use icons from <a href="https://iconify.design/" target="_blank" rel="noopener noreferrer">Iconify</a> for our links so we’ll need to install a couple of packages. The first is Iconify’s React library:</p>
<p><code>npm install [@iconify/react](http://twitter.com/iconify/react)</code></p>
<p>and the second is the package for your icon set of choice. I chose <a href="https://iconify.design/icon-sets/simple-icons/" target="_blank" rel="noopener noreferrer">Simple Icons</a>:</p>
<p><code>npm install [@iconify/icons-simple-icons](http://twitter.com/iconify/icons-simple-icons)</code></p>
<p>Now you can import the <strong>Icon</strong> component and all relevant icons and add them into our layout’s footer:</p>
<pre><code class="hljs"><span class="hljs-comment">// ./components/Layout.js</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Icon</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;[@iconify/react](http://twitter.com/iconify/react)&quot;</span>;
<span class="hljs-keyword">import</span> githubIcon <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;[@iconify/icons-simple-icons](http://twitter.com/iconify/icons-simple-icons)/github&quot;</span>;
<span class="hljs-keyword">import</span> linkedinIcon <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;[@iconify/icons-simple-icons](http://twitter.com/iconify/icons-simple-icons)/linkedin&quot;</span>;
<span class="hljs-keyword">import</span> twitterIcon <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;[@iconify/icons-simple-icons](http://twitter.com/iconify/icons-simple-icons)/twitter&quot;</span>;

...

<span class="hljs-keyword">function</span> <span class="hljs-title function_">Layout</span>(<span class="hljs-params">{ children, pageTitle }</span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;viewport&quot;</span> <span class="hljs-attr">content</span>=<span class="hljs-string">&quot;width=device-width, initial-scale=1&quot;</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{pageTitle}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;flex flex-col min-h-screen&quot;</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-full h-16 border-b border-purple-500 flex items-center justify-center&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-11/12 md:w-full max-w-3xl flex flex-row justify-between&quot;</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;text-2xl text-purple-500&quot;</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">ActiveLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;/&quot;</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">a</span>&gt;</span>My Blog<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">ActiveLink</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">AppNav</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-11/12 md:w-full max-w-2xl mx-auto my-8 flex-grow&quot;</span>&gt;</span>
          {children}
        <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">footer</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;flex flex-col items-center justify-center w-full h-24 text-gray-600 border-t border-purple-500&quot;</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;flex flex-row&quot;</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;mr-6&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;https://github.com/&quot;</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-6 h-6&quot;</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{githubIcon}</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;mr-6&quot;</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;https://www.linkedin.com/&quot;</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-6 h-6&quot;</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{linkedinIcon}</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;https://www.twitter.com/&quot;</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;w-6 h-6&quot;</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{twitterIcon}</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">&quot;mt-2&quot;</span>&gt;</span>
            All content © Me
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}

...
</code></pre>
<p>Huzzah! Your blog starter is complete!! 🎉 🎉 🎉</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="fx04zjord9v7w414p2vzhjdx" alt="" data-big=https://cms-assets.abletech.nz/large_1weta9_P_Oe_Vt_Ev_Wj66_Ue5r0_Q_0ab9e5ce44.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 235px" src="https://cms-assets.abletech.nz/1weta9_P_Oe_Vt_Ev_Wj66_Ue5r0_Q_0ab9e5ce44.png" srcset="https://cms-assets.abletech.nz/large_1weta9_P_Oe_Vt_Ev_Wj66_Ue5r0_Q_0ab9e5ce44.png 1000w, https://cms-assets.abletech.nz/small_1weta9_P_Oe_Vt_Ev_Wj66_Ue5r0_Q_0ab9e5ce44.png 500w, https://cms-assets.abletech.nz/medium_1weta9_P_Oe_Vt_Ev_Wj66_Ue5r0_Q_0ab9e5ce44.png 750w, https://cms-assets.abletech.nz/thumbnail_1weta9_P_Oe_Vt_Ev_Wj66_Ue5r0_Q_0ab9e5ce44.png 235w" data-zooming-width="1000" data-zooming-height="664" loading="lazy" width="1000" height="664"></figure>
</div>
<h3>Ship It</h3>
<p>As I mentioned earlier, deployment with <a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a> is really simple. You can just push your code to a git hosting provider (e.g. <a href="https://github.com/" target="_blank" rel="noopener noreferrer">GitHub</a>), point Netlify at your repo to create a new site, and tell Netlify how to build your application. Eli Williamson and Jason Lengstorf have written a <a href="https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/" target="_blank" rel="noopener noreferrer">great guide on Netlify</a> that will step you through the process in more detail but here are the basic steps:</p>
<ul>
<li>
<p>If you haven’t already, push your code to GitHub, GitLab or Bitbucket</p>
</li>
<li>
<p>Create a Netlify configuration file in your project to tell Netlify how to <strong>build</strong> your app:</p>
</li>
</ul>
<pre><code class="hljs"><span class="hljs-comment"># ./netlify.toml</span>
<span class="hljs-section">[build]</span>
  <span class="hljs-attr">command</span> = <span class="hljs-string">&quot;yarn build &amp;&amp; yarn export&quot;</span>
  <span class="hljs-attr">publish</span> = <span class="hljs-string">&quot;out&quot;</span>
</code></pre>
<ul>
<li>Create the <strong>export</strong> script in your <strong>package.json</strong> for Netlify to run</li>
</ul>
<pre><code class="hljs"><span class="hljs-string">//</span> <span class="hljs-string">./package.json</span>
<span class="hljs-string">...</span>
<span class="hljs-string">&quot;scripts&quot;</span>: {
  <span class="hljs-string">&quot;export&quot;</span>: <span class="hljs-string">&quot;next export&quot;</span>
}
<span class="hljs-string">...</span>
</code></pre>
<ul>
<li>
<p><a href="https://app.netlify.com/signup" target="_blank" rel="noopener noreferrer">Sign up for a (free) Netlify Account</a></p>
</li>
<li>
<p>On Netlify, create a <strong>New site from Git</strong> (you’ll need to link to your git hosting service and authorise Netlify to access it, then select your <strong>personal-blog</strong> repo)</p>
</li>
<li>
<p>Netlify should see your <strong>netlify.toml</strong> file and prefill the build settings</p>
</li>
<li>
<p>Click <strong>Deploy site</strong></p>
</li>
<li>
<p>Celebrate 🎊</p>
</li>
</ul>
<p>Netlify has now generated a new subdomain on their <strong>netlify.app</strong> domain to host your app! You can simply rename this subdomain if you’d like. I won’t cover it here but I would recommend purchasing your own domain name and <a href="https://docs.netlify.com/domains-https/custom-domains/#assign-a-domain-to-a-site" target="_blank" rel="noopener noreferrer">configuring it for Netlify to use with your new site</a>, even easier: you can <a href="https://docs.netlify.com/domains-https/netlify-dns/domain-registration/#register-a-new-domain" target="_blank" rel="noopener noreferrer">register your domain name with Netlify directly</a>.</p>
<h3>Next Steps</h3>
<p>Now that we’ve shipped our blog platform the most important thing to do is start writing. Establish a habit of writing and publishing regularly. You don’t need to write novellas, your posts don’t need to be perfect, just write about what interests you and form the habit. I’d encourage you to take a similar approach to writing as we did with shipping this blog app: start simple and iterate. Aim for incremental improvements rather than waiting to be great at something before you do it.</p>
<p>This would also be a good time to start thinking about what other features you might want in your platform and make a plan to add them (you could write about these too if you’re stuck for ideas). For example, here’re a few tasks/features I’ve lined up for mine:</p>
<ul>
<li>
<p>Optimise for web search (SEO)</p>
</li>
<li>
<p>Custom error pages</p>
</li>
<li>
<p>Enhanced navigation (e.g. next/prev post)</p>
</li>
<li>
<p>Optimised image handling</p>
</li>
<li>
<p>Tagging and filtering</p>
</li>
<li>
<p>Search</p>
</li>
<li>
<p>Cross posting to other platforms</p>
</li>
<li>
<p>Styling refinements</p>
</li>
<li>
<p>Reader interaction (probably not comments, possibly social media… twitter?)</p>
</li>
</ul>
<h3>Conclusion</h3>
<p>If you’ve been following along I hope that you’ve found this guide useful and that it’s helped you set up your own blog. If following guides isn’t your thing and you just want a head start on creating a blog like this one feel free to fork the <a href="https://github.com/kalopilato/blog-app-starter" target="_blank" rel="noopener noreferrer">repo on Github</a> and make it your own.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>EV Q&A — If I go up a hill, do I gain power when I go down?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Yes. It’s called Regenerative Braking</h2>
<p>The car’s motor will slow down the car when you take you foot off the accelerator, and the byproduct is electricity which flows back into the battery. Technically termed Regenerative Braking, or Regen, this happens automatically (and silently) while you drive. It’s not something you think about.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kzozq6qd48b8hymg5x5djcas" alt="The ‘dots’ indicate the level of energy consumption or generation" data-big=https://cms-assets.abletech.nz/large_1aw_Lt23b_KTP_272_Ec_Iwp_V1_KQ_a380a698cb.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1aw_Lt23b_KTP_272_Ec_Iwp_V1_KQ_a380a698cb.jpeg" srcset="https://cms-assets.abletech.nz/large_1aw_Lt23b_KTP_272_Ec_Iwp_V1_KQ_a380a698cb.jpeg 1000w, https://cms-assets.abletech.nz/small_1aw_Lt23b_KTP_272_Ec_Iwp_V1_KQ_a380a698cb.jpeg 500w, https://cms-assets.abletech.nz/medium_1aw_Lt23b_KTP_272_Ec_Iwp_V1_KQ_a380a698cb.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1aw_Lt23b_KTP_272_Ec_Iwp_V1_KQ_a380a698cb.jpeg 245w" data-zooming-width="1000" data-zooming-height="472" loading="lazy" width="1000" height="472"></figure>
</div>
<p><em>The ‘dots’ indicate the level of energy consumption or generation</em></p>
<p>When you drive down a large hill like Ngauranga Gorge (in Wellington) you certainly notice that your <strong>battery percentage rises</strong> by a few percent. It’s not a massive gain, but every little bit counts.</p>
<p>You will also benefit from regen in stop/start traffic. You’ll recover some energy, but it’s also <strong>easier on the driver</strong>. Less need for alternating your foot between accelerator and brake.</p>
<h3>Increased Regen</h3>
<p>On most electric cars, the level of ‘regen’ is configurable. On our Nissan Leaf, we can choose either D (drive) mode which has a smaller level of regen, and B (braking) mode which has a higher level of regen. As we live on a hill, we normally drive with the higher B mode.</p>
<p>A side effect of the higher regen is that you <strong>very rarely need to use the brakes</strong>. When you take you foot off the accelerator, the car slows down enough. It’s only if you need to come to a complete halt, that you might press the brake.</p>
<p>The new ‘version 2’ Nissan Leaf has a feature called ‘one pedal driving’ which takes this even further. When enabled, as you take your foot totally off the accelerator, it will fully engage the regenerative braking and also apply the ‘actual brakes’ as well.</p>
<h3>Reduced wear and tear</h3>
<p>With the vastly <strong>reduced need for braking</strong>, the brake pads/discs on electric cars <strong>very rarely need replacing</strong>. Depending on driving conditions, an EV may never have them replaced.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ojjtb0l7lo3obe1aermx9vit" alt="" data-big=https://cms-assets.abletech.nz/medium_1f_Mc1r3_Jyso9f_Bc_B7akr_Ee_Q_5503074412.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 241px" src="https://cms-assets.abletech.nz/1f_Mc1r3_Jyso9f_Bc_B7akr_Ee_Q_5503074412.jpeg" srcset="https://cms-assets.abletech.nz/small_1f_Mc1r3_Jyso9f_Bc_B7akr_Ee_Q_5503074412.jpeg 500w, https://cms-assets.abletech.nz/medium_1f_Mc1r3_Jyso9f_Bc_B7akr_Ee_Q_5503074412.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1f_Mc1r3_Jyso9f_Bc_B7akr_Ee_Q_5503074412.jpeg 241w" data-zooming-width="750" data-zooming-height="486" loading="lazy" width="750" height="486"></figure>
</div>
<p>The cost to replace a set for a standard car (and EV) is around $250 every 50,000 kms. That could add up to $1,000 over the life of the vehicle.</p>
<h2>Summary</h2>
<p>Regenerative braking <strong>recovers energy</strong> when driving down a hill, decelerating and in stop/start traffic. The <strong>extended life</strong> of the brake pads will save you money over the life of the vehicle.</p>
<h3>Learn more</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/traveling-to-palmy-in-an-electric-vehicle/">Traveling to Palmy in an Electric Vehicle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-do-the-cars-cost-more-than-petrol-cars">EV Q&amp;A — Do the cars cost more than petrol cars?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-charging-do-you-need-a-special-plug-at-home">EV Q&amp;A — Charging: do you need a special plug at home?</a></p>
</li>
<li>
<p><a href="mailto:nigel@ramsay.org.nz" target="_blank" rel="noopener noreferrer">Submit a question</a> to answer in my EV Q&amp;A series.</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>EV Q&A — Do the cars cost more than petrol cars?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>In this series, I will answer some of the common questions about Electric Vehicles. Feel free to send me some of your own questions to answer.</h2>
<p>There are 3 costs to consider here:</p>
<ol>
<li>
<p>Initial purchase price</p>
</li>
<li>
<p>Fuel prices</p>
</li>
<li>
<p>Maintenance costs</p>
</li>
</ol>
<h2>Initial Purchase Price</h2>
<p>Fully <strong>electric cars are more expensive to buy</strong> than petrol/diesel cars. Price comparisons can be difficult, as the electric versions of cars tend to come with all the ‘bells and whistles’ and have more powerful motors.</p>
<p>New example:</p>
<ul>
<li>
<p>Hyundai Ionic 1.6 Hybrid DCT6 — $46,990</p>
</li>
<li>
<p>Hyundai Ionic 1.6 Hybrid Elite DCT6 — $52,990</p>
</li>
<li>
<p>Hyundai Ionic EV — $59,990</p>
</li>
<li>
<p>Hyundai Ionic EV Elite — $65,990</p>
</li>
</ul>
<p>Second hand example:</p>
<ul>
<li>
<p>TradeMe — Nissan Leaf EV 2015 — approx $18–21k</p>
</li>
<li>
<p>TradeMe — Nissan Qashqai 2015 — approx $16–24k</p>
</li>
</ul>
<p>I chose the Nissan Qashqai as it is a similar hatchback from Nissan, and there are many of them available to compare prices on TradeMe.</p>
<h2>Fuel prices</h2>
<p>When you drive an electric car, you consume electricity from the battery. Charging the battery is typically done at home using the same electricity that powers your house.</p>
<p>The price you pay per unit does differ depending on where you live. There are ways to get your electricity for less than 15c per unit, but in this example I will assume that you pay around 20c per unit (kilowatt hour).</p>
<p><strong>Cost of driving 100 km on the highway</strong></p>
<p>The range of our Nissan Leaf is about 150 km when driving on the open road. We have a battery capacity of 30 kilowatt-hours of which about 26 kWh is usable. This equates to (100 / 150 * 26) = 17 kWh needed to drive 100km. At 20c per unit, that’s <strong>$3.40 in the Leaf</strong>.</p>
<p>Our previous car was a Ford Territory 2012 diesel and on the open road, it had an economy of about 9 litres / 100 km. At current diesel prices of $1.66 per litre, that would total <strong>$14.94 to drive 100 km</strong>.</p>
<p><strong>Cost of driving 100 km in the city</strong></p>
<p>Like all cars, you get better fuel economy when you drive slower (less wind resistance). Our range extends to around 200 km, even when driving on Wellington’s hills. 100 km of city driving would equal <strong>$2.60 in the EV</strong>.</p>
<p>Our Ford Territory performed quite poorly in the city. The fuel economy drops to around 11 litres / 100 km. This equates to <strong>$18.26 for 100 km</strong> of city driving. There would also be <em>road user charges</em> to add on top of this. Electric cars are currently exempt from these, but they will eventually be levied. I’ll leave these out of the equation (for fairness).</p>
<p>Being a turbo-diesel, the Ford Territory has relatively powerful torque when compared with petrol equivalents. I believe that the average economy of a petrol SUV such as the Toyota Highlander is poor when driving on Wellington’s hills. A friend with an older Highlander mentioned city economy of 15 litres / 100 kms. That’s <strong>$33.90</strong> at $2.26 per litre. Ouch!</p>
<h2>Maintenance costs</h2>
<p>We have had our EV now for about a year. Current maintenance costs are zero, although I do have an unfortunate dent that I am yet to have repaired.</p>
<p>We spent about <strong>$3000</strong> on our Ford Territory in the year before we switched to electric. This reflects the costs of owning an aging diesel vehicle. Older petrol vehicles may be less costly.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nlsp436pj6ojao8qsq9zoks7" alt="" data-big=https://cms-assets.abletech.nz/large_1r8w_jr_L3_Rk_F8rc5_Lo_CT_Phw_d17f801de7.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1r8w_jr_L3_Rk_F8rc5_Lo_CT_Phw_d17f801de7.jpeg" srcset="https://cms-assets.abletech.nz/large_1r8w_jr_L3_Rk_F8rc5_Lo_CT_Phw_d17f801de7.jpeg 1000w, https://cms-assets.abletech.nz/small_1r8w_jr_L3_Rk_F8rc5_Lo_CT_Phw_d17f801de7.jpeg 500w, https://cms-assets.abletech.nz/medium_1r8w_jr_L3_Rk_F8rc5_Lo_CT_Phw_d17f801de7.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1r8w_jr_L3_Rk_F8rc5_Lo_CT_Phw_d17f801de7.jpeg 245w" data-zooming-width="1000" data-zooming-height="604" loading="lazy" width="1000" height="604"></figure>
</div>
<h2>Summary</h2>
<p>You should expect to pay a little more when purchasing a new or second hand EV. But from then on, you will be saving money. The rate of savings will depend upon how much driving you do, and what the maintenance costs are of your current vehicle.</p>
<p>In our case, the price difference between the sale of our Ford Territory and the second hand Nissan Leaf was $7,000. We’ve saved about $1,800 in fuel prices and I suspect a similar $3,000 in maintenance costs. At this rate, we should <strong>break even sometime in the next 12 months</strong>.</p>
<p>But, the financial cost/savings is an insignificant reason to dispose of a petrol or diesel vehicle. The main reason is to reduce carbon emissions which have caused the ongoing <a href="https://www.un.org/en/global-issues/climate-change" target="_blank" rel="noopener noreferrer">climate crisis as outlined by the United Nations report</a>.</p>
<h3>Read more</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/traveling-to-palmy-in-an-electric-vehicle">Travelling to Palmy in an Electric Vehicle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-charging-do-you-need-a-special-plug-at-home">EV Q&amp;A – Do you need a special plug at home?</a></p>
</li>
<li>
<p>We switched to <a href="https://abletech.nz/article/carbon-zero-electricity">100% renewable electricity</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — Vlog</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The story of converting Trixie, our ’65 Beetle, into eTrixie is done and dusted!</h2>
<p>We’ve made a vlog for this final instalment so you can see eTrixie in action. It is a little sad that the work is over, but that said it is also great to be finished because every time I get behind the wheel it brings a huge smile to my face!</p>
<p>Now I’m having a wee rest before I start planning a spruce-up for the interior and exterior! Hope you enjoy the video:</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tg1z9dmiv3tk904p0geqnsn7" alt="" data-big=https://cms-assets.abletech.nz/large_1gc_Lr_Ed_Nv3g_K3_RX_2_Cw_Zo_Fog_e11a83ca7e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1gc_Lr_Ed_Nv3g_K3_RX_2_Cw_Zo_Fog_e11a83ca7e.png" srcset="https://cms-assets.abletech.nz/large_1gc_Lr_Ed_Nv3g_K3_RX_2_Cw_Zo_Fog_e11a83ca7e.png 1000w, https://cms-assets.abletech.nz/small_1gc_Lr_Ed_Nv3g_K3_RX_2_Cw_Zo_Fog_e11a83ca7e.png 500w, https://cms-assets.abletech.nz/medium_1gc_Lr_Ed_Nv3g_K3_RX_2_Cw_Zo_Fog_e11a83ca7e.png 750w, https://cms-assets.abletech.nz/thumbnail_1gc_Lr_Ed_Nv3g_K3_RX_2_Cw_Zo_Fog_e11a83ca7e.png 245w" data-zooming-width="1000" data-zooming-height="561" loading="lazy" width="1000" height="561"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part three</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>It’s out with the old and in with the new. Having completed the front disc brake upgrade, the electric conversion of the VW Beetle can now begin. In part three we see removals — of the petrol engine and the petrol tank.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dul9bexdmtqx1mlf05ar8oho" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_S_Sgj_T1_Prns5_L8gx_Y_5be83dec73.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_S_Sgj_T1_Prns5_L8gx_Y_5be83dec73.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_S_Sgj_T1_Prns5_L8gx_Y_5be83dec73.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
</div>
<p>My first step of the electric conversion is to take out the petrol engine and the fuel tank. This is a very sad day as we will lose the distinctive VW engine sound, but on the plus side we will be able to hold a conversation while driving!</p>
<p>Taking the engine and fuel tank out is very easy in a VW.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q9q4427bqj0xcj4w6g4ef5ls" alt="Rear of the VW with petrol engine" data-big=https://cms-assets.abletech.nz/small_0g_Uz_Jv4auy7uz_TG_b_86143873ba.jpg sizes="(min-width: 768px) 410px, (min-width: 640px) 128px" src="https://cms-assets.abletech.nz/0g_Uz_Jv4auy7uz_TG_b_86143873ba.jpg" srcset="https://cms-assets.abletech.nz/small_0g_Uz_Jv4auy7uz_TG_b_86143873ba.jpg 410w, https://cms-assets.abletech.nz/thumbnail_0g_Uz_Jv4auy7uz_TG_b_86143873ba.jpg 128w" data-zooming-width="410" data-zooming-height="500" loading="lazy" width="410" height="500"></figure>
</div>
<p><em>Rear of the VW with petrol engine</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="j6ukx2ro0gigt4vcapa29zzs" alt="VW engine in box. Electric motor on right" data-big=https://cms-assets.abletech.nz/thumbnail_0_Va_N_PZ_Aa_Xe_B4c_Yxh_dd030da203.jpg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_Va_N_PZ_Aa_Xe_B4c_Yxh_dd030da203.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_Va_N_PZ_Aa_Xe_B4c_Yxh_dd030da203.jpg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<p><em>VW engine in box. Electric motor on right</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dm7b72a59pbfwo03ksyal4gk" alt="Rear of the VW with engine missing" data-big=https://cms-assets.abletech.nz/thumbnail_0dm2d_UJM_Up3_Wko_P_062730ae6e.jpg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0dm2d_UJM_Up3_Wko_P_062730ae6e.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0dm2d_UJM_Up3_Wko_P_062730ae6e.jpg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<p><em>Rear of the VW with engine missing</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wfe3saz6g7x7cehglhiyykyo" alt="Front of the VW with petrol tank removed" data-big=https://cms-assets.abletech.nz/thumbnail_0_RXOXH_Z4qv_Bo_Lxqs_79b74afd17.jpg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_RXOXH_Z4qv_Bo_Lxqs_79b74afd17.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_RXOXH_Z4qv_Bo_Lxqs_79b74afd17.jpg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<p><em>Front of the VW with petrol tank removed</em></p>
<p>The 37 new LiFePO4 cells will be split into two banks — 12 cells in the front where the fuel tank used to live and 25 cells on the luggage tray (behind the rear passenger seat).</p>
<p>In order to make a full battery, the two banks of cells need to be cabled together. To make this happen conduits are run inside the chassis tunnel to carry the 70mm2 battery cables from the front to the back of the car.</p>
<p>The battery cable terminates in a contactor box which will be built up and located under the rear passenger seat.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yhlrceev1735dlsei3eypp3j" alt="Front of the VW with orange conduit" data-big=https://cms-assets.abletech.nz/thumbnail_0_r_GTT_Yj6_P8_GJ_54_C9_2b3b36d787.jpg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_r_GTT_Yj6_P8_GJ_54_C9_2b3b36d787.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_r_GTT_Yj6_P8_GJ_54_C9_2b3b36d787.jpg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<p><em>Front of the VW with orange conduit</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cz7zocw1z39sixtcegdiybfh" alt="Inside the VW with battery cables popping out of the conduit" data-big=https://cms-assets.abletech.nz/thumbnail_0h7_Brfv_S_Yi_xk_Mk_Ta_d23a101a45.jpg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0h7_Brfv_S_Yi_xk_Mk_Ta_d23a101a45.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0h7_Brfv_S_Yi_xk_Mk_Ta_d23a101a45.jpg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<p><em>Inside the VW with battery cables popping out of the conduit</em></p>
<p>We are keeping the existing VW transaxle/gearbox. The new electric motor will just bolt on to it.</p>
<p>When the car is 100% electric we won’t really need to change gears as the electric motor will have enough torque to start and stay in 3rd for around-town running. However, it’s much easier to keep the existing gear box and we’ll have gear options!</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part seven</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>High Currents</h2>
<p>In this part we’ll talk about high voltage and current cabling, switches and fuses.</p>
<p>So far I’ve got conduits running down the tunnel from under the back seat to the front of the car. These will carry cables from the battery in the front to the back. I also have the front and rear battery boxes in place. So the next challenge is to hook the two batteries up and connect them to the motor.</p>
<p>The copper cables to do this job are double insulated with a bright orange outer layer. The wire is stranded copper so it remains fairly flexible and has a cross-sectional area of 70mm². Often called welding cable, this cable is designed to carry high voltages (120v in this case) and high currents (peaks of 100s of amps).</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="p0mfz5dm1llwuipp4z9xusk7" alt="70mm² (AWG 2/0) Cable for eTrixie" data-big=https://cms-assets.abletech.nz/large_18hpin_ESY_6_U9_NLDY_Sf_Vm_XNQ_30b2ec37d1.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/18hpin_ESY_6_U9_NLDY_Sf_Vm_XNQ_30b2ec37d1.jpeg" srcset="https://cms-assets.abletech.nz/large_18hpin_ESY_6_U9_NLDY_Sf_Vm_XNQ_30b2ec37d1.jpeg 1000w, https://cms-assets.abletech.nz/small_18hpin_ESY_6_U9_NLDY_Sf_Vm_XNQ_30b2ec37d1.jpeg 500w, https://cms-assets.abletech.nz/medium_18hpin_ESY_6_U9_NLDY_Sf_Vm_XNQ_30b2ec37d1.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_18hpin_ESY_6_U9_NLDY_Sf_Vm_XNQ_30b2ec37d1.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>70mm² (AWG 2/0) Cable for eTrixie</em></p>
<p>The cables need to be protected from fault currents; in this case with 600 amp fuses. Also I need a “master switch” — a manual switch I can throw to disconnect the battery. To meet the standards of the <a href="http://lvvta.org.nz" target="_blank" rel="noopener noreferrer">LVVTA</a> both poles (positive and negative) of both battery banks need to be fused and switched as close as practical to the battery. So eTrixie needs to have two big dual pole switches and four 600 amp fuses. This is a pretty sensible precaution!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="z2t91i7comdoa7y2mkth7jlu" alt="Fuses and Dual Pole Switches" data-big=https://cms-assets.abletech.nz/large_1_N_Kac_Q9d_Df_R_Ttg_n_Uh_Gux_CA_d56734e82a.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_N_Kac_Q9d_Df_R_Ttg_n_Uh_Gux_CA_d56734e82a.jpeg" srcset="https://cms-assets.abletech.nz/large_1_N_Kac_Q9d_Df_R_Ttg_n_Uh_Gux_CA_d56734e82a.jpeg 1000w, https://cms-assets.abletech.nz/small_1_N_Kac_Q9d_Df_R_Ttg_n_Uh_Gux_CA_d56734e82a.jpeg 500w, https://cms-assets.abletech.nz/medium_1_N_Kac_Q9d_Df_R_Ttg_n_Uh_Gux_CA_d56734e82a.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_N_Kac_Q9d_Df_R_Ttg_n_Uh_Gux_CA_d56734e82a.jpeg 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<p><em>Fuses and Dual Pole Switches</em></p>
<p>The hook-up of the batteries, fuses, switches, contactors, shunt and motor required 24 terminations. I was pretty nervous each time I had to cut the cable (mistakes would be expensive!) but I grew in confidence — a clean cut is essential so the stranded cable does not fray, but instead slides freely into the copper lug. Also a hydraulic crimper ensures a great termination. And then finished off with a heavy adhesive lined heatshrink!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="u822mcuvgqanjwb5b7uwgggk" alt="Crimpers and cutter" data-big=https://cms-assets.abletech.nz/large_14_V1q2r_HT_Jy_Cy_GHTI_If_DXJQ_e21840f218.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/14_V1q2r_HT_Jy_Cy_GHTI_If_DXJQ_e21840f218.jpeg" srcset="https://cms-assets.abletech.nz/large_14_V1q2r_HT_Jy_Cy_GHTI_If_DXJQ_e21840f218.jpeg 1000w, https://cms-assets.abletech.nz/small_14_V1q2r_HT_Jy_Cy_GHTI_If_DXJQ_e21840f218.jpeg 500w, https://cms-assets.abletech.nz/medium_14_V1q2r_HT_Jy_Cy_GHTI_If_DXJQ_e21840f218.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_14_V1q2r_HT_Jy_Cy_GHTI_If_DXJQ_e21840f218.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Crimpers and cutter</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s24iax61jj6r4j1iy2tor01u" alt="Terminated cable (sorry the heatshrink is a bit rough on this one)" data-big=https://cms-assets.abletech.nz/large_1qck_Lj_l_Te_HO_Xhu_ELY_Ge_Zrg_86308e914e.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 231px" src="https://cms-assets.abletech.nz/1qck_Lj_l_Te_HO_Xhu_ELY_Ge_Zrg_86308e914e.jpeg" srcset="https://cms-assets.abletech.nz/large_1qck_Lj_l_Te_HO_Xhu_ELY_Ge_Zrg_86308e914e.jpeg 1000w, https://cms-assets.abletech.nz/small_1qck_Lj_l_Te_HO_Xhu_ELY_Ge_Zrg_86308e914e.jpeg 500w, https://cms-assets.abletech.nz/medium_1qck_Lj_l_Te_HO_Xhu_ELY_Ge_Zrg_86308e914e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1qck_Lj_l_Te_HO_Xhu_ELY_Ge_Zrg_86308e914e.jpeg 231w" data-zooming-width="1000" data-zooming-height="676" loading="lazy" width="1000" height="676"></figure>
</div>
<p><em>Terminated cable (sorry the heatshrink is a bit rough on this one)</em></p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part one</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Carl Penwarden reckons he’s inherited a VW Beetle gene. He’s on a mission to convert his car into an electric Beetle. In part one there’s some research to do and certification to prepare for.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cyfk84vvmg6ptvb8fkvid9fy" alt="" data-big=https://cms-assets.abletech.nz/small_0ex6_Sx_Xj_Ej_OMZI_7_Y2_f1c8dcf573.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 230px" src="https://cms-assets.abletech.nz/0ex6_Sx_Xj_Ej_OMZI_7_Y2_f1c8dcf573.jpg" srcset="https://cms-assets.abletech.nz/small_0ex6_Sx_Xj_Ej_OMZI_7_Y2_f1c8dcf573.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0ex6_Sx_Xj_Ej_OMZI_7_Y2_f1c8dcf573.jpg 230w" data-zooming-width="500" data-zooming-height="339" loading="lazy" width="500" height="339"></figure>
</div>
<p>My Dad started young with Volkswagens and his father had Beetles too. I sat my drivers licence in a 1963 Beetle, the first car I owned was a ’59 Beetle, and today I have a ’65 Beetle which my eight-year-old daughter loves riding in.</p>
<p>I got to thinking, how can I make this vintage car relevant to my daughter when she is ready to start driving? One answer is to make it electric! <a href="https://abletech.nz/about/">Andy at Abletech</a> pointed me at the <a href="http://www.zelectricmotors.com/" target="_blank" rel="noopener noreferrer">Zelectric</a> website. They custom build classic electric beetles.</p>
<p>After doing a bit of research I ended up talking to <a href="http://evwest.com/" target="_blank" rel="noopener noreferrer">EV West</a> who designed the electrics and build the custom components for Zelectric. That discussion sealed the deal and I ordered a DIY electric conversion kit for Trixie, our ’65 Beetle.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yoymri2kxm8uk8rr2ny3alz5" alt="" data-big=https://cms-assets.abletech.nz/small_0_Li3_Lf_MSD_3_X_Ing1rn_32f7543578.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 216px" src="https://cms-assets.abletech.nz/0_Li3_Lf_MSD_3_X_Ing1rn_32f7543578.jpg" srcset="https://cms-assets.abletech.nz/small_0_Li3_Lf_MSD_3_X_Ing1rn_32f7543578.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0_Li3_Lf_MSD_3_X_Ing1rn_32f7543578.jpg 216w" data-zooming-width="500" data-zooming-height="361" loading="lazy" width="500" height="361"></figure>
</div>
<p>Before ordering the kit, I investigated what is involved with making sure I end up with a road legal car. I’ve learnt about the Low Volume Vehicle Technical Association (LVVTA) who oversee the certification of these types of modifications.</p>
<p>I have become familiar with the LVVTA’s Low Volume Vehicle Standard 75–00 (Electric and Hybrid Vehicles) and the certifier has been very helpful as we’ve discussed making Trixie electric.</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
<p>Stay tuned!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part 12</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>On The Road</h2>
<p>Now that eTrixie is ready to drive, the final step is Certification.</p>
<p>Obviously, certification was a goal at the outset of this project. Before I started I pored over the LVVTA Standard for Electric and Hybrid Vehicles (<a href="https://stories.abletech.nz/etrixie-part-one-5546284cfd21" target="_blank" rel="noopener noreferrer">see Part 1</a>) and now it is time to see if eTrixie passes the standard. I have enjoyed this aspect of the process because the LVVTA are enthusiasts who have worked with the New Zealand Transport Agency to create standards so the vehicles can be modified or even scratch built.</p>
<p>The standards I have worked with have been clear, practical and the intent behind the requirements is easy to see.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sfc3xfyikckxyxvrvubl3hyy" alt="eTrixie under inspection at the LVVTA" data-big=https://cms-assets.abletech.nz/large_1_Am_Yz_Z_Sv_P8_W_Df_YWL_5_Y_Fr_A2_Q_3c35fa8ff4.jpeg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/1_Am_Yz_Z_Sv_P8_W_Df_YWL_5_Y_Fr_A2_Q_3c35fa8ff4.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Am_Yz_Z_Sv_P8_W_Df_YWL_5_Y_Fr_A2_Q_3c35fa8ff4.jpeg 750w, https://cms-assets.abletech.nz/small_1_Am_Yz_Z_Sv_P8_W_Df_YWL_5_Y_Fr_A2_Q_3c35fa8ff4.jpeg 375w, https://cms-assets.abletech.nz/medium_1_Am_Yz_Z_Sv_P8_W_Df_YWL_5_Y_Fr_A2_Q_3c35fa8ff4.jpeg 563w, https://cms-assets.abletech.nz/thumbnail_1_Am_Yz_Z_Sv_P8_W_Df_YWL_5_Y_Fr_A2_Q_3c35fa8ff4.jpeg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
</div>
<p><em>eTrixie under inspection at the LVVTA</em></p>
<p>I am very pleased to say that eTrixie passed and is now fully certified as an electric vehicle.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="fyfzw27nyc7zb73xwqhkauzi" alt="Certification plate in place!" data-big=https://cms-assets.abletech.nz/large_12_YNQRKO_HQ_Uscc_Sy_J7_Fww_96363d345e.jpeg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/12_YNQRKO_HQ_Uscc_Sy_J7_Fww_96363d345e.jpeg" srcset="https://cms-assets.abletech.nz/large_12_YNQRKO_HQ_Uscc_Sy_J7_Fww_96363d345e.jpeg 750w, https://cms-assets.abletech.nz/small_12_YNQRKO_HQ_Uscc_Sy_J7_Fww_96363d345e.jpeg 375w, https://cms-assets.abletech.nz/medium_12_YNQRKO_HQ_Uscc_Sy_J7_Fww_96363d345e.jpeg 563w, https://cms-assets.abletech.nz/thumbnail_12_YNQRKO_HQ_Uscc_Sy_J7_Fww_96363d345e.jpeg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
</div>
<p><em>Certification plate in place!</em></p>
<p>This project has been so much fun! But I have to say that driving eTrixie is even more fun than I imagined.</p>
<p>Most of the time, for ‘round town running’ 3rd gear is fine. But if I want to take off quickly at the traffic lights then 2nd gear is great! On the motorway I do change into 4th gear. Coming off the motorway I often don’t think about which gear I’m in and stay in 4th.</p>
<p>Surprisingly you can actually start off in 4th gear and not really notice. Because you don’t need to change gear very often when you are moving you seldom need to use the clutch. The electric motor gives the driver a lot of control, especially reversing which is an absolute pleasure. I pop eTrixie into reverse without needing the clutch. Reversing is effortless, even up steep grades!
<strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part 11</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Bottom Balancing</h2>
<p>eTrixie is now on the road — but the conversion job is not over yet. Early in the eTrixie project I investigated Battery Management Systems (BMS).</p>
<p>A BMS requires an electronic circuit to be connected across the terminals of each cell in the battery — these are, in turn, connected to a system manager. The BMS monitors each cell to ensure each is not over-charged or over-discharged. To avoid this happening, the BMS circuit can shunt current to an individual cell.</p>
<p>For eTrixie I’m not going down the BMS track, instead there is only a monitor overseeing the full battery. Does that sound a bit risky?!</p>
<p>A legitimate concern is imbalance as the battery discharges, as you can see from the battery discharge curve below, with LiFePo4 cells the voltage drops off steeply when the cell approaches empty. It is particularly bad when ‘weaker’ cells arrive at this drop-off point ahead of ‘stronger’ cells. The ‘stronger’ cells will continue to push current through a discharged cell which will irreversibly damage it.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="f8kq6vp9cizfi33v658rxg0t" alt="" data-big=https://cms-assets.abletech.nz/small_1_E_Sy5_Y7_NQYQT_Svv_Svib_O_Ld_Q_abf083b452.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_E_Sy5_Y7_NQYQT_Svv_Svib_O_Ld_Q_abf083b452.jpeg" srcset="https://cms-assets.abletech.nz/small_1_E_Sy5_Y7_NQYQT_Svv_Svib_O_Ld_Q_abf083b452.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1_E_Sy5_Y7_NQYQT_Svv_Svib_O_Ld_Q_abf083b452.jpeg 245w" data-zooming-width="500" data-zooming-height="296" loading="lazy" width="500" height="296"></figure>
</div>
<h3>Bottom balance</h3>
<p>A way to solve this problem is to bottom balance. This involves equally discharging all the cells to a known point over the ‘knee’. I chose to parallel my cells and discharge them to 2.7 volts.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t70vjt1a69kgneqio13y3pho" alt="Bottom balancing" data-big=https://cms-assets.abletech.nz/medium_1r_Rmq_M_Zr5_O8_W_V_Ljw_G26_tw_bb823b4e90.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1r_Rmq_M_Zr5_O8_W_V_Ljw_G26_tw_bb823b4e90.jpeg" srcset="https://cms-assets.abletech.nz/small_1r_Rmq_M_Zr5_O8_W_V_Ljw_G26_tw_bb823b4e90.jpeg 500w, https://cms-assets.abletech.nz/medium_1r_Rmq_M_Zr5_O8_W_V_Ljw_G26_tw_bb823b4e90.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1r_Rmq_M_Zr5_O8_W_V_Ljw_G26_tw_bb823b4e90.jpeg 156w" data-zooming-width="750" data-zooming-height="750" loading="lazy" width="750" height="750"></figure>
</div>
<p><em>Bottom balancing</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nv9stet68wuy24k2sx46i8zx" alt="Cells back in the pack!" data-big=https://cms-assets.abletech.nz/large_1trxld9ltjjd_Xb72_EJ_0_Rd0w_de60403cea.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1trxld9ltjjd_Xb72_EJ_0_Rd0w_de60403cea.jpeg" srcset="https://cms-assets.abletech.nz/large_1trxld9ltjjd_Xb72_EJ_0_Rd0w_de60403cea.jpeg 1000w, https://cms-assets.abletech.nz/small_1trxld9ltjjd_Xb72_EJ_0_Rd0w_de60403cea.jpeg 500w, https://cms-assets.abletech.nz/medium_1trxld9ltjjd_Xb72_EJ_0_Rd0w_de60403cea.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1trxld9ltjjd_Xb72_EJ_0_Rd0w_de60403cea.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Cells back in the pack!</em></p>
<p>Once all cells were at 2.7v I put the battery pack back together again and charged the whole pack up again. By starting to charge the cells, from a known bottom point, the next time they discharge to 2.7v they should all arrive at that voltage at the same time or in-balance. The situation of ‘stronger’ vs ‘weaker’ cells will not occur as the cells are in-balance.</p>
<p>So the balancing has been done, and the proof of the pudding will be in the eating!</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part 10</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Putting it all together</h2>
<p>In part ten I’m ready to flick the switches!</p>
<p>The main advice I received before flicking the switch is to make sure I have taken safety precautions. This involves making sure the car is off the ground! It’s necessary because the throttle has not been calibrated and the motor is very likely to start turning.</p>
<p>The first step is to check, and check again, that I have wired-up the batteries correctly. When I was totally satisfied I turned the two dual pole isolation switches — even though I was sure there would be no problem it was still a nervous moment!</p>
<p>Nothing went bang!</p>
<p>The next step was to turn on the ignition switch (after checking the contactor box wiring again). I turned the ignition switch. The contactors clicked and the engine started rotating. Wow! It was actually working.</p>
<p>The next step was to calibrate the throttle and brakes. When done, the motor stopped turning until I actually pressed the throttle pedal.</p>
<p>The other important piece of advice I received was to test and prove the battery charger system was working, and then charge the battery, before driving the car. It would be a bad look to flatten (and potentially damage) the battery during the first drive of the car.</p>
<p>With the help of John at <a href="http://evwest.com/" target="_blank" rel="noopener noreferrer">EV West</a> I set up the battery monitor system and then plugged eTrixie into the mains 240v for the first time — the battery charger whirred into life and the batteries started charging.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pmyq2ae71t1cs5l1pff5fb5a" alt="Juicecord and accompanying smartphone app" data-big=https://cms-assets.abletech.nz/medium_1sqthi8w_I5_WDHT_Lr_QK_C1rg_d72e4f47ca.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1sqthi8w_I5_WDHT_Lr_QK_C1rg_d72e4f47ca.jpeg" srcset="https://cms-assets.abletech.nz/small_1sqthi8w_I5_WDHT_Lr_QK_C1rg_d72e4f47ca.jpeg 500w, https://cms-assets.abletech.nz/medium_1sqthi8w_I5_WDHT_Lr_QK_C1rg_d72e4f47ca.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1sqthi8w_I5_WDHT_Lr_QK_C1rg_d72e4f47ca.jpeg 156w" data-zooming-width="750" data-zooming-height="750" loading="lazy" width="750" height="750"></figure>
</div>
<p><em>Juicecord and accompanying smartphone app</em></p>
<p>I’m currently using a Juicecord 8 amp from <a href="http://www.juicepoint.co.nz/" target="_blank" rel="noopener noreferrer">juicepoint.co.nz</a> which operates with any New Zealand standard 3 pin 10 amp domestic wall socket. This awesome cord is WiFi enabled and I can monitor the mains AC charging parameters with my smartphone.</p>
<blockquote>
<h4>The cool thing is that I can take this cord with me and sip some power from any power point when I go visiting!</h4>
</blockquote>
<p>As eTrixie is equipped with a 2.5kW charger the Juicecord is almost a perfect match. I have also recently changed my electricity provider to <a href="https://ecotricity.co.nz/" target="_blank" rel="noopener noreferrer">Ecotricity</a>, they are passionate about EVs and sustainability — a no brainer for EV owners.</p>
<p>After all that eTrixie is ready to drive!!!</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://stories.abletech.nz/etrixie-part-12-767223ba81d4" target="_blank" rel="noopener noreferrer">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://stories.abletech.nz/etrixie-vlog-617e9fd86a6b" target="_blank" rel="noopener noreferrer">Video walk through</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — A DIY EV in the Real World</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Trixie has now been on the road for a few months. In this time we have used the car for short trips around town, and a couple of longer trips which have used more than 50% of charge in one trip.</h2>
<h3><strong>Real World Economy</strong></h3>
<p>On average with real world driving I’m getting about 120kms range on a charge.</p>
<p>One trip from Wellington to Featherston, was 40 miles (64km) and used 9.8 kWh. This translates to 4.08 miles per kWh, which appears to be similar to a Nissan Leaf, and a little better than a Tesla (which is a much bigger and heavier car).</p>
<p>It is tricky to compare the electric vs petrol cost performance, but if I compare on the basis of energy cost, based on 1 litre of 91 octane fuel in Wellington, I am effectively getting 83km per litre or 233 miles to the gallon. This figures will drop a bit in the future as currently EVs are not subject to ‘road user charges’ — yay!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ymg5f3isu1f7oxsynvndetjw" alt="Charging Trixie “Free of Charge” — Thanks Hutt City!" data-big=https://cms-assets.abletech.nz/large_1_Ip_LO_7_N_Cd5m_Ry6_Qhe_N_Sizqw_2999356bfc.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 184px" src="https://cms-assets.abletech.nz/1_Ip_LO_7_N_Cd5m_Ry6_Qhe_N_Sizqw_2999356bfc.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Ip_LO_7_N_Cd5m_Ry6_Qhe_N_Sizqw_2999356bfc.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Ip_LO_7_N_Cd5m_Ry6_Qhe_N_Sizqw_2999356bfc.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Ip_LO_7_N_Cd5m_Ry6_Qhe_N_Sizqw_2999356bfc.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Ip_LO_7_N_Cd5m_Ry6_Qhe_N_Sizqw_2999356bfc.jpeg 184w" data-zooming-width="1000" data-zooming-height="848" loading="lazy" width="1000" height="848"></figure>
</div>
<p><em>Charging Trixie “Free of Charge” — Thanks Hutt City!</em></p>
<h3><strong>Battery Capacity and Balance</strong></h3>
<p>I have set my battery capacity on the battery monitor to 155Ah, which provides 25Ah buffer on stated capacity of 180Ah. When my battery monitor reaches 0% capacity when driving around after a full charge I should have a 25Ah buffer and at 0% the overall battery voltage is comfortably above 100V, or 2.70V per cell.</p>
<p>In <a href="https://stories.abletech.nz/etrixie-part-11-1f9ae14c4394" target="_blank" rel="noopener noreferrer">part 11 of the eTrixie story</a> I discussed bottom balancing. As well as balancing the battery voltage at the bottom, it is good to keep an eye on voltage at the top, when the battery is fully charged. Poor balance at the top can lead to overcharging a cell. I did my first top balance check last weekend, about 4 months after my initial bottom balance and I have found that at the top all of the 37 cells are measuring an identical 3.34V. I am very happy with this consistency, and pleased that the decision not to use a per cell BMS is an ok decision for the moment.</p>
<h3><strong>Wellington Constructors Car Club</strong></h3>
<p>I was given the opportunity to take Trixie along to the <a href="http://www.constructorscarclub.org.nz/" target="_blank" rel="noopener noreferrer">Constructors Car Club</a> as the ‘guest car’ for their recent club night. It was great fun to talk with so many car enthusiasts. Trixie was well received by the group and I was very thankful for the opportunity to show her off.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dqi3z5eg9m6tp72cevwrfr1y" alt="Trixie meeting with the Constructors!" data-big=https://cms-assets.abletech.nz/large_1_OTS_38_E8ndt_B_Mj_Rf_Sh_Jel_Yg_afb3ab0299.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_OTS_38_E8ndt_B_Mj_Rf_Sh_Jel_Yg_afb3ab0299.jpeg" srcset="https://cms-assets.abletech.nz/large_1_OTS_38_E8ndt_B_Mj_Rf_Sh_Jel_Yg_afb3ab0299.jpeg 1000w, https://cms-assets.abletech.nz/small_1_OTS_38_E8ndt_B_Mj_Rf_Sh_Jel_Yg_afb3ab0299.jpeg 500w, https://cms-assets.abletech.nz/medium_1_OTS_38_E8ndt_B_Mj_Rf_Sh_Jel_Yg_afb3ab0299.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_OTS_38_E8ndt_B_Mj_Rf_Sh_Jel_Yg_afb3ab0299.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Trixie meeting with the Constructors!</em></p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://stories.abletech.nz/etrixie-part-12-767223ba81d4" target="_blank" rel="noopener noreferrer">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://stories.abletech.nz/etrixie-vlog-617e9fd86a6b" target="_blank" rel="noopener noreferrer">Video walk through</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Driving from Taupo to Wellington in an EV</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The goal for this trip was fewer charging stops</h2>
<p>Today, we have returned from a lovely summer holiday near Taupō. The driving distance was 342 km, with an expected driving time of 4:40.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rmsc6b5mtu78zbou4egdsczc" alt="" data-big=https://cms-assets.abletech.nz/large_1qs_EB_Djq_Glrnv_Ay_K_QH_Tx_Dw_319f1b7e7e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 188px" src="https://cms-assets.abletech.nz/1qs_EB_Djq_Glrnv_Ay_K_QH_Tx_Dw_319f1b7e7e.png" srcset="https://cms-assets.abletech.nz/large_1qs_EB_Djq_Glrnv_Ay_K_QH_Tx_Dw_319f1b7e7e.png 1000w, https://cms-assets.abletech.nz/small_1qs_EB_Djq_Glrnv_Ay_K_QH_Tx_Dw_319f1b7e7e.png 500w, https://cms-assets.abletech.nz/medium_1qs_EB_Djq_Glrnv_Ay_K_QH_Tx_Dw_319f1b7e7e.png 750w, https://cms-assets.abletech.nz/thumbnail_1qs_EB_Djq_Glrnv_Ay_K_QH_Tx_Dw_319f1b7e7e.png 188w" data-zooming-width="1000" data-zooming-height="831" loading="lazy" width="1000" height="831"></figure>
</div>
<p>Unlike our <a href="https://abletech.nz/article/driving-to-whakapapa-in-a-nissan-leaf/">previous journey</a>, this time we set out to charge the fewest number of times.</p>
<h2>Stop 1: Mangaweka (7 mins)</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bqmt59fr94yzeznp1b0yp18f" alt="Abandoned DC3 at *Mangaweka International Airport*" data-big=https://cms-assets.abletech.nz/large_1_TS_0n_Tg8_NMDN_Xj_fqr_K_Fv3w_53cc09000a.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_TS_0n_Tg8_NMDN_Xj_fqr_K_Fv3w_53cc09000a.jpeg" srcset="https://cms-assets.abletech.nz/large_1_TS_0n_Tg8_NMDN_Xj_fqr_K_Fv3w_53cc09000a.jpeg 1000w, https://cms-assets.abletech.nz/small_1_TS_0n_Tg8_NMDN_Xj_fqr_K_Fv3w_53cc09000a.jpeg 500w, https://cms-assets.abletech.nz/medium_1_TS_0n_Tg8_NMDN_Xj_fqr_K_Fv3w_53cc09000a.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_TS_0n_Tg8_NMDN_Xj_fqr_K_Fv3w_53cc09000a.jpeg 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<p><em>Abandoned DC3 at <em>Mangaweka International Airport</em></em></p>
<p>We really wanted to stop in Bulls (191 km) — this was well within driving range, but (like last time) we wanted to avoid the possibility of being stuck awaiting a busy charger.</p>
<p>So instead, we stopped at the town beforehand (Mangaweka) for 7 mins and added 5.4 kWh of energy (cost $3.31). This would allow us to stop at our desired charge location of Bulls, but if the charger was in use, we could drive onto the next town (Foxton).</p>
<h2>Stop 2: Bulls (44 mins)</h2>
<p>The charger was not in use. We stopped here for morning tea. The aim was to get to 80% charge, so that we could drive all the way back to Wellington without stopping.</p>
<p>Bulls is a great place to stop. Unlike many of the central North Island towns, there’s plenty of life, and new developments happening. We went to <a href="https://www.tripadvisor.co.nz/Restaurant_Review-g1632649-d7175239-Reviews-Mint_cafe-Bulls_Rangitikei_District_Manawatu_Wanganui_Region_North_Island.html" target="_blank" rel="noopener noreferrer">Mint Cafe</a> at a little strip mall.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t0jvv9k8cu6gu6hwqco4t277" alt="[Bulls, New Zealand](https://www.mapillary.com/app/?lat=-40.174316000000005&lng=175.38488400000006&z=17&focus=photo&pKey=-4i3lLJJmf67b5FQia5rwA&x=0.497277091570022&y=0.6253723530361202&zoom=0)" data-big=https://cms-assets.abletech.nz/large_15_UWZFD_1_Zd_D2x1u_O_Gp_TS_0_Ag_2c3e90c6f1.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/15_UWZFD_1_Zd_D2x1u_O_Gp_TS_0_Ag_2c3e90c6f1.png" srcset="https://cms-assets.abletech.nz/large_15_UWZFD_1_Zd_D2x1u_O_Gp_TS_0_Ag_2c3e90c6f1.png 1000w, https://cms-assets.abletech.nz/small_15_UWZFD_1_Zd_D2x1u_O_Gp_TS_0_Ag_2c3e90c6f1.png 500w, https://cms-assets.abletech.nz/medium_15_UWZFD_1_Zd_D2x1u_O_Gp_TS_0_Ag_2c3e90c6f1.png 750w, https://cms-assets.abletech.nz/thumbnail_15_UWZFD_1_Zd_D2x1u_O_Gp_TS_0_Ag_2c3e90c6f1.png 245w" data-zooming-width="1000" data-zooming-height="607" loading="lazy" width="1000" height="607"></figure>
</div>
<p><em><a href="https://www.mapillary.com/app/?lat=-40.174316000000005&amp;lng=175.38488400000006&amp;z=17&amp;focus=photo&amp;pKey=-4i3lLJJmf67b5FQia5rwA&amp;x=0.497277091570022&amp;y=0.6253723530361202&amp;zoom=0" target="_blank" rel="noopener noreferrer">Bulls, New Zealand</a></em></p>
<p>We ended up spending longer at the cafe than expected, and the car charged up to 89% adding 21.2 kWh for $16.56.</p>
<h2>Summary: Easy drive</h2>
<p>The drive home was easier, with fewer stops.</p>
<p>Total cost was $19.87 for a four and a half hour drive.</p>
<h2>Further reading</h2>
<ul>
<li>
<p><a href="https://abletech.nz/article/driving-to-whakapapa-in-a-nissan-leaf">Driving to Whakapapa in a Nissan Leaf</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/traveling-to-palmy-in-an-electric-vehicle">Travelling to Palmy in an Electric Vehicle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-do-the-cars-cost-more-than-petrol-cars">EV Q&amp;A — Do the cars cost more than petrol cars?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-charging-do-you-need-a-special-plug-at-home">EV Q&amp;A — Charging: do you need a special plug at home?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-if-i-go-up-a-hill-do-i-gain-power-when-i-go-down">EV Q&amp;A — If I go up a hill, do I gain power when I go down?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-is-this-ev-thing-just-a-fad-or-is-it-here-to-stay-got-evidence">EV Q&amp;A — Is this EV thing just a fad?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-is-it-gutless-on-the-hills">EV Q&amp;A — Is it gutless on the hills?</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>CSSConf & JSConf Australia 2018</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Read highlights from Melbourne. Including grid-based layout, web accessibility, faster fonts, WebUSB APIs, abstract syntax trees, error handling and much more.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="xyf111ygd8dkwigybpytl8hc" alt="JSConf-goers enjoying the complimentary coffee on the first day of the event (and some cheery Abletechers on the right!)" data-big=https://cms-assets.abletech.nz/large_1k_Z_u_Hz_D_As2l_By_Ne0w_N_Xu_EA_617e0392c5.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1k_Z_u_Hz_D_As2l_By_Ne0w_N_Xu_EA_617e0392c5.jpeg" srcset="https://cms-assets.abletech.nz/large_1k_Z_u_Hz_D_As2l_By_Ne0w_N_Xu_EA_617e0392c5.jpeg 1000w, https://cms-assets.abletech.nz/small_1k_Z_u_Hz_D_As2l_By_Ne0w_N_Xu_EA_617e0392c5.jpeg 500w, https://cms-assets.abletech.nz/medium_1k_Z_u_Hz_D_As2l_By_Ne0w_N_Xu_EA_617e0392c5.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1k_Z_u_Hz_D_As2l_By_Ne0w_N_Xu_EA_617e0392c5.jpeg 245w" data-zooming-width="1000" data-zooming-height="429" loading="lazy" width="1000" height="429"></figure>
</div>
<p><em>JSConf-goers enjoying the complimentary coffee on the first day of the event (and some cheery Abletechers on the right!)</em></p>
<p>Last month <a href="https://abletech.nz/about/#kate-norquay">Kate Norquay</a> and <a href="https://abletech.nz/about/#sam-gard">Sam Gard</a> attended CSSConf and JSConf, held at Melbourne’s Meat Market — an iconic heritage building reimagined as a venue hire space.</p>
<p>The three days of talks were augmented with delicious catering (breakfast, lunch and dinner); all-you-can-drink coffee, juice, and soda; event t-shirts; and several fantastic social events.</p>
<p>Kate and Sam’s review of some of their favourite speakers:</p>
<h3>CSSConf</h3>
<p><strong>Brenda Storer on CSS Grid</strong></p>
<p>Brenda gave us a thorough overview of the new CSS Grid specification, with a step-by-step guide to implementing Grid to enhance a site, as well as how to provide graceful fallbacks for unsupported browsers (looking at you IE) using Flexbox.</p>
<p><strong>Julie Grundy on A11y</strong></p>
<p>Julie gave us a bunch of useful tips for improving our sites for users with low-vision (which, it turns out make up 9% of Australian web users!). She talked us through a couple of common scenarios these users run into, and gave us a few easy fixes.</p>
<p>Some users struggle to read small text and zoom in on web pages. Julie suggested flipping to the mobile design as the user zooms in.</p>
<p>For colour blind users, it’s important to make sure your page has sufficient contrast. We can also check what our pages look like in greyscale to make sure we don’t have any ‘colour-only indicators’, like links or menu items.</p>
<p>For users viewing the page in high contrast mode, borders around input fields are an easy way to make sure our fields don’t disappear into the background.</p>
<p><strong>Jeremy Wagner on Faster fonts</strong></p>
<p>Jeremy provided some useful tips and tricks on font performance such as:</p>
<ul>
<li>
<p>self-hosting to reduce latency</p>
</li>
<li>
<p>using preload, prefetch and preconnect to open up early connections and DNS look-ups</p>
</li>
<li>
<p>the font-display CSS declaration, which allows control over font swapping (first displaying a locally-installed font then replacing it with the web-font once it has loaded)</p>
</li>
</ul>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sb85exyzmcch1e08tpy7szbt" alt="" data-big=https://cms-assets.abletech.nz/large_1_Na_U_O_Uj_N_Oap_T_C4_O7nc_UAA_ba9fd2119d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Na_U_O_Uj_N_Oap_T_C4_O7nc_UAA_ba9fd2119d.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Na_U_O_Uj_N_Oap_T_C4_O7nc_UAA_ba9fd2119d.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Na_U_O_Uj_N_Oap_T_C4_O7nc_UAA_ba9fd2119d.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Na_U_O_Uj_N_Oap_T_C4_O7nc_UAA_ba9fd2119d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Na_U_O_Uj_N_Oap_T_C4_O7nc_UAA_ba9fd2119d.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="prvfq1j6ff67a7whyle47mvv" alt="Photo credit: Ed Moore & Jeremy Wagner" data-big=https://cms-assets.abletech.nz/large_1z_D_Ziw_Iw47_dk_KUCOEFQZFA_d3072b64a6.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1z_D_Ziw_Iw47_dk_KUCOEFQZFA_d3072b64a6.jpeg" srcset="https://cms-assets.abletech.nz/large_1z_D_Ziw_Iw47_dk_KUCOEFQZFA_d3072b64a6.jpeg 1000w, https://cms-assets.abletech.nz/small_1z_D_Ziw_Iw47_dk_KUCOEFQZFA_d3072b64a6.jpeg 500w, https://cms-assets.abletech.nz/medium_1z_D_Ziw_Iw47_dk_KUCOEFQZFA_d3072b64a6.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1z_D_Ziw_Iw47_dk_KUCOEFQZFA_d3072b64a6.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Photo credit: Ed Moore &amp; Jeremy Wagner</em></p>
<p><strong>Theresa Ma on Effective design &amp; engineering collaboration</strong></p>
<p>Theresa Ma spoke to us about the best ways to get designers and engineers to work effectively together, and a few ways to improve our design systems.</p>
<p>Some of her suggestions were to:</p>
<ul>
<li>
<p>Involve engineers earlier in the design process</p>
</li>
<li>
<p>Have designers and engineers sit together so they can talk to each other casually, without the pressure of being in a meeting.</p>
</li>
<li>
<p>Have designers and engineers go on offsites together</p>
</li>
<li>
<p>Consider which design and engineering decisions need to be made earlier on</p>
</li>
<li>
<p>Use checklists when giving feedback. (Different screen sizes? Error states? Final copy?)</p>
</li>
</ul>
<p><strong>Ivana McConnell on What makes a developer, really?</strong></p>
<p>Ivana spoke on the development identity crisis, and how the weight and gravity that comes with the term developer doesn’t always come with CSS development. She emphasised that devaluing and ignoring CSS as a development skill compromises the language, the people who use it, and the industry itself.</p>
<blockquote>
<p>“CSS isn’t just about presentation. We’ve come a long way and the lines between design and development are now blurred. Fluidity in roles matters and titles matter. Computer science is great, but it’s not the only way to be a developer.”</p>
</blockquote>
<p>Other talks:</p>
<ul>
<li>
<p>Layout animations</p>
</li>
<li>
<p>Colour systems</p>
</li>
<li>
<p>Houdini</p>
</li>
</ul>
<h3>JSConf</h3>
<p><strong>Amy Nguyen on the power of Chrome Developer Tools</strong></p>
<p>Amy gave a talk on how she used Chrome Developer Tools to get tickets to Taylor Swift’s concert. Essentially, Amy had to trick TicketMasters system into thinking she’s a Taylor Swift super fan by watching her music videos over and over again. She took us on a journey, demonstrating a bunch of useful techniques, like setting XHR Breakpoints, and copying network requests into cURL commands.</p>
<p>If you want to know more, check out her <a href="https://medium.com/@amyngyn/look-what-you-made-me-do-chrome-b85eb2a90540" target="_blank" rel="noopener noreferrer">blog post</a> which features a lot of Taylor Swift lyrics combined with coding jokes (it’s really excellent).</p>
<p><strong>Suz Hinton on WebUSB</strong></p>
<p>Suz’s talk on WebUSB API compared the accessible, cross-platform, and safe spec to old ‘hacky’ methods of connecting browsers to hardware, such as Flash and Chrome Serial.</p>
<p>While maintaining that it is still fragile (currently only supported in Chrome), Suz impressed us with her <a href="https://github.com/noopkat/oled-js" target="_blank" rel="noopener noreferrer">oled-js</a> library by uploading an image in the browser and sending it a to SSD1306 OLED screen in her live demo!</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="px1ez9agwd4u8x3ljjaz5jm0" alt="" data-big=https://cms-assets.abletech.nz/large_1rwjk_Xrod_A_Iqs_Ac4n_EF_4_Az_A_59bec9cdff.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1rwjk_Xrod_A_Iqs_Ac4n_EF_4_Az_A_59bec9cdff.jpeg" srcset="https://cms-assets.abletech.nz/large_1rwjk_Xrod_A_Iqs_Ac4n_EF_4_Az_A_59bec9cdff.jpeg 1000w, https://cms-assets.abletech.nz/small_1rwjk_Xrod_A_Iqs_Ac4n_EF_4_Az_A_59bec9cdff.jpeg 500w, https://cms-assets.abletech.nz/medium_1rwjk_Xrod_A_Iqs_Ac4n_EF_4_Az_A_59bec9cdff.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1rwjk_Xrod_A_Iqs_Ac4n_EF_4_Az_A_59bec9cdff.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rmt0ed1wzofn0o5ve9uguge0" alt="Photo credit: Theresa Ma & Duyen Ho" data-big=https://cms-assets.abletech.nz/large_1_D_Jg_JJNHQ_Gz_H1_MNY_5_6b_R_Mw_72a1398f78.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_D_Jg_JJNHQ_Gz_H1_MNY_5_6b_R_Mw_72a1398f78.jpeg" srcset="https://cms-assets.abletech.nz/large_1_D_Jg_JJNHQ_Gz_H1_MNY_5_6b_R_Mw_72a1398f78.jpeg 1000w, https://cms-assets.abletech.nz/small_1_D_Jg_JJNHQ_Gz_H1_MNY_5_6b_R_Mw_72a1398f78.jpeg 500w, https://cms-assets.abletech.nz/medium_1_D_Jg_JJNHQ_Gz_H1_MNY_5_6b_R_Mw_72a1398f78.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_D_Jg_JJNHQ_Gz_H1_MNY_5_6b_R_Mw_72a1398f78.jpeg 208w" data-zooming-width="1000" data-zooming-height="749" loading="lazy" width="1000" height="749"></figure>
</div>
<p><em>Photo credit: Theresa Ma &amp; Duyen Ho</em></p>
<p><strong>Craig Spence on Abstract Syntax Trees</strong></p>
<p>Craig taught us about Abstract Syntax Trees. They are a useful way to represent the structure of source code, and can be used to compile one language into another.</p>
<p>Craig’s talk was Harry Potter themed, and ended with debugging a new programming language — ‘Parseltongue’ in the browser, which I think pretty much blew everyone’s mind.</p>
<p><strong>Tim Holman on Generative Art</strong></p>
<p>Tim gave a speedrun talk on his experience with generative art. He began by detailing the generative art toolbelt — the building blocks of design tools used to generate art (lines, curves, displacement, repetition, shapes, rotation, tiling, and colour). From there, he introduced the ideas of recursion and algorithms to introduce complexity, randomisation, evolution and chaos. This subject was really refreshing, and Tim delivered it all with a fun and charismatic nature.</p>
<blockquote>
<p>It’s something new every time you press refresh. Like art that kind of makes itself?</p>
</blockquote>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="o12y39r6yx6v68r2x4jrhcua" alt="" data-big=https://cms-assets.abletech.nz/large_1_Nc92_G5d_W_Iq_OIA_Bth_Kle1_Q_5dc24b2a4e.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Nc92_G5d_W_Iq_OIA_Bth_Kle1_Q_5dc24b2a4e.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Nc92_G5d_W_Iq_OIA_Bth_Kle1_Q_5dc24b2a4e.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Nc92_G5d_W_Iq_OIA_Bth_Kle1_Q_5dc24b2a4e.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Nc92_G5d_W_Iq_OIA_Bth_Kle1_Q_5dc24b2a4e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Nc92_G5d_W_Iq_OIA_Bth_Kle1_Q_5dc24b2a4e.jpeg 208w" data-zooming-width="1000" data-zooming-height="751" loading="lazy" width="1000" height="751"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="toqzibi1n7iktemsza9qnjh8" alt="Photo credit: Kevin Yank & Craig Ambrose" data-big=https://cms-assets.abletech.nz/large_1_N_Me0_AI_Tx89_Bz6_Ygjq_EJ_Phg_5198a0c31b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_N_Me0_AI_Tx89_Bz6_Ygjq_EJ_Phg_5198a0c31b.jpeg" srcset="https://cms-assets.abletech.nz/large_1_N_Me0_AI_Tx89_Bz6_Ygjq_EJ_Phg_5198a0c31b.jpeg 1000w, https://cms-assets.abletech.nz/small_1_N_Me0_AI_Tx89_Bz6_Ygjq_EJ_Phg_5198a0c31b.jpeg 500w, https://cms-assets.abletech.nz/medium_1_N_Me0_AI_Tx89_Bz6_Ygjq_EJ_Phg_5198a0c31b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_N_Me0_AI_Tx89_Bz6_Ygjq_EJ_Phg_5198a0c31b.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Photo credit: Kevin Yank &amp; Craig Ambrose</em></p>
<p><strong>Brittany Storoz on the sad state of error handling</strong></p>
<p>Brittany gave an eye opening talk about the ways poor error handling is impacting new developers.</p>
<p>Brittany teaches front-end development and has seen newcomers get stuck on errors that more experienced devs would immediately recognise as simple errors in spelling, syntax or mixing up data types. When they are met with unhelpful error messages (Unexpected token &lt; in JSON at position 0, anyone?), they are left spinning their wheels and feeling frustrated, when in reality their code is very close to correct.</p>
<p>Brittany drew attention to improvements we need to make in our error handling, and the empathy and thought that needs to go in to making our errors more understandable to new developers.</p>
<p>Other talks:</p>
<ul>
<li>
<p>Javascript engine internals</p>
</li>
<li>
<p>Node.js and Rust</p>
</li>
<li>
<p>HTTP2</p>
</li>
<li>
<p>Adtech</p>
</li>
<li>
<p>ServiceWorkers</p>
</li>
<li>
<p>Javascript Raytracing Engine</p>
</li>
<li>
<p>Node.js application diagnostics</p>
</li>
<li>
<p>Progressively rolling out accessibility changes</p>
</li>
<li>
<p>Bias, stereotypes and accountability in tech</p>
</li>
</ul>
<p>Notably, CSSConf and JSConf also focused on making events inclusive, safe spaces for everyone. Both conferences had:</p>
<ul>
<li>
<p>Diverse speakers</p>
</li>
<li>
<p>A clearly communicated and enforceable <a href="https://2018.cssconf.com.au/code-of-conduct" target="_blank" rel="noopener noreferrer">Code of Conduct</a></p>
</li>
<li>
<p>Live captioning by <a href="https://twitter.com/stoker_lindsay" target="_blank" rel="noopener noreferrer">Lindsay Stoker</a> from <a href="http://www.whitecoatcaptioning.com/" target="_blank" rel="noopener noreferrer">White Coat Captioning</a></p>
</li>
<li>
<p>A media policy (colour-coded lanyards to indicate your comfort level for being photographed)</p>
</li>
<li>
<p>Quiet spaces/live-streamed talks</p>
</li>
</ul>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dz8y6klc1cerbu70nnc4a557" alt="" data-big=https://cms-assets.abletech.nz/large_14g48_Eej4o_Joz9mc7u_H_Av_A_b7b43051e3.jpeg sizes="(min-width: 1280px) 562px, (min-width: 768px) 281px, (min-width: 1024px) 422px, (min-width: 640px) 88px" src="https://cms-assets.abletech.nz/14g48_Eej4o_Joz9mc7u_H_Av_A_b7b43051e3.jpeg" srcset="https://cms-assets.abletech.nz/large_14g48_Eej4o_Joz9mc7u_H_Av_A_b7b43051e3.jpeg 562w, https://cms-assets.abletech.nz/small_14g48_Eej4o_Joz9mc7u_H_Av_A_b7b43051e3.jpeg 281w, https://cms-assets.abletech.nz/medium_14g48_Eej4o_Joz9mc7u_H_Av_A_b7b43051e3.jpeg 422w, https://cms-assets.abletech.nz/thumbnail_14g48_Eej4o_Joz9mc7u_H_Av_A_b7b43051e3.jpeg 88w" data-zooming-width="562" data-zooming-height="1000" loading="lazy" width="562" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hqaw3lforv2k0a75vamfrn68" alt="" data-big=https://cms-assets.abletech.nz/large_1_WFJV_Ceo_U_Mct_Xc32y_Ti5w9_A_62f9e6d9a6.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_WFJV_Ceo_U_Mct_Xc32y_Ti5w9_A_62f9e6d9a6.jpeg" srcset="https://cms-assets.abletech.nz/large_1_WFJV_Ceo_U_Mct_Xc32y_Ti5w9_A_62f9e6d9a6.jpeg 1000w, https://cms-assets.abletech.nz/small_1_WFJV_Ceo_U_Mct_Xc32y_Ti5w9_A_62f9e6d9a6.jpeg 500w, https://cms-assets.abletech.nz/medium_1_WFJV_Ceo_U_Mct_Xc32y_Ti5w9_A_62f9e6d9a6.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_WFJV_Ceo_U_Mct_Xc32y_Ti5w9_A_62f9e6d9a6.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kihocuhbkdybeg4nic3owp99" alt="" data-big=https://cms-assets.abletech.nz/large_12_BVR_jbw_G8_JL_Sap_M_Jh_J_Cz_Q_c7ab438d9e.jpeg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/12_BVR_jbw_G8_JL_Sap_M_Jh_J_Cz_Q_c7ab438d9e.jpeg" srcset="https://cms-assets.abletech.nz/large_12_BVR_jbw_G8_JL_Sap_M_Jh_J_Cz_Q_c7ab438d9e.jpeg 750w, https://cms-assets.abletech.nz/small_12_BVR_jbw_G8_JL_Sap_M_Jh_J_Cz_Q_c7ab438d9e.jpeg 375w, https://cms-assets.abletech.nz/medium_12_BVR_jbw_G8_JL_Sap_M_Jh_J_Cz_Q_c7ab438d9e.jpeg 563w, https://cms-assets.abletech.nz/thumbnail_12_BVR_jbw_G8_JL_Sap_M_Jh_J_Cz_Q_c7ab438d9e.jpeg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vp89oiq35edjrp7qt5zvsc2g" alt="" data-big=https://cms-assets.abletech.nz/large_1rkz_Qtt_LZVP_4qv_Ko7q_Jsuiw_2ee811ac90.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1rkz_Qtt_LZVP_4qv_Ko7q_Jsuiw_2ee811ac90.jpeg" srcset="https://cms-assets.abletech.nz/large_1rkz_Qtt_LZVP_4qv_Ko7q_Jsuiw_2ee811ac90.jpeg 1000w, https://cms-assets.abletech.nz/small_1rkz_Qtt_LZVP_4qv_Ko7q_Jsuiw_2ee811ac90.jpeg 500w, https://cms-assets.abletech.nz/medium_1rkz_Qtt_LZVP_4qv_Ko7q_Jsuiw_2ee811ac90.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1rkz_Qtt_LZVP_4qv_Ko7q_Jsuiw_2ee811ac90.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zxrgps3i7csj2y8rnic654ug" alt="" data-big=https://cms-assets.abletech.nz/large_1_Ohk1_Eo_Tg4p_EON_Krl3j1zbg_82dc6fbe33.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Ohk1_Eo_Tg4p_EON_Krl3j1zbg_82dc6fbe33.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Ohk1_Eo_Tg4p_EON_Krl3j1zbg_82dc6fbe33.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Ohk1_Eo_Tg4p_EON_Krl3j1zbg_82dc6fbe33.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Ohk1_Eo_Tg4p_EON_Krl3j1zbg_82dc6fbe33.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Ohk1_Eo_Tg4p_EON_Krl3j1zbg_82dc6fbe33.jpeg 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bqzmbykoc2bpmdaacuh7rt1e" alt="Second breakfasts, exploring Melbourne, and enjoying the spectacular JSConf after-party" data-big=https://cms-assets.abletech.nz/large_1x_W6_MD_Oe8v_IJ_Mr_Xr0zj_CAA_6a900fd00e.jpeg sizes="(min-width: 1280px) 562px, (min-width: 768px) 281px, (min-width: 1024px) 422px, (min-width: 640px) 88px" src="https://cms-assets.abletech.nz/1x_W6_MD_Oe8v_IJ_Mr_Xr0zj_CAA_6a900fd00e.jpeg" srcset="https://cms-assets.abletech.nz/large_1x_W6_MD_Oe8v_IJ_Mr_Xr0zj_CAA_6a900fd00e.jpeg 562w, https://cms-assets.abletech.nz/small_1x_W6_MD_Oe8v_IJ_Mr_Xr0zj_CAA_6a900fd00e.jpeg 281w, https://cms-assets.abletech.nz/medium_1x_W6_MD_Oe8v_IJ_Mr_Xr0zj_CAA_6a900fd00e.jpeg 422w, https://cms-assets.abletech.nz/thumbnail_1x_W6_MD_Oe8v_IJ_Mr_Xr0zj_CAA_6a900fd00e.jpeg 88w" data-zooming-width="562" data-zooming-height="1000" loading="lazy" width="562" height="1000"></figure>
</div>
<p><em>Second breakfasts, exploring Melbourne, and enjoying the spectacular JSConf after-party</em></p>
<h3>More from Abletech</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/rubyconf-2018">Tania reviews the 2018 Ruby conference in Sydney</a></p>
</li>
<li>
<p><a href="https://stories.abletech.nz/kiwi-ruby-633e2ae14607" target="_blank" rel="noopener noreferrer">Kiwi Ruby</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/national-javascript-conference">NZ Javascript Conf</a></p>
</li>
<li>
<p>Graham, infrastructure lead at Coinbase gives an <a href="https://abletech.nz/article/coinbase">insight behind-the-scenes</a></p>
</li>
<li>
<p>Intern reflects on <a href="https://abletech.nz/article/interning-at-abletech">three months at Abletech</a></p>
</li>
<li>
<p>How to <a href="https://abletech.nz/resource/how-to-configure-vs-code-to-format-elixir-code/">configure VS code to format Elixir code</a></p>
</li>
<li>
<p>Follow Abletech on <a href="https://twitter.com/Abletech" target="_blank" rel="noopener noreferrer">Twitter</a>, <a href="https://www.facebook.com/abletech" target="_blank" rel="noopener noreferrer">Facebook</a> or <a href="https://nz.linkedin.com/company/able-technology" target="_blank" rel="noopener noreferrer">LinkedIn</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Volunteers Rock</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3>These fantastic people just spent their weekend helping Ruby enthusiasts deepen their knowledge. How cool is that? Their approach made the two day workshop social, supportive and informative. Sponsors donated everything they needed including goods, services, office space and food.</h3>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uhm91f6jrxtne9nxqzvrfvgd" alt="Team Rails Girls Supercharged 2017" data-big=https://cms-assets.abletech.nz/medium_15f_R_Fpje_Mu4h06se_S9i_0og_be200b245c.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/15f_R_Fpje_Mu4h06se_S9i_0og_be200b245c.jpeg" srcset="https://cms-assets.abletech.nz/small_15f_R_Fpje_Mu4h06se_S9i_0og_be200b245c.jpeg 500w, https://cms-assets.abletech.nz/medium_15f_R_Fpje_Mu4h06se_S9i_0og_be200b245c.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_15f_R_Fpje_Mu4h06se_S9i_0og_be200b245c.jpeg 245w" data-zooming-width="750" data-zooming-height="402" loading="lazy" width="750" height="402"></figure>
</div>
<p><em>Team Rails Girls Supercharged 2017</em></p>
<p>Awesome work volunteers! Thanks for your dedication, hard work, time and energy. You’re fantastic 🌟 and we’re impressed 😊</p>
<p>Check out the <a href="https://www.facebook.com/RailsGirlsWellington/" target="_blank" rel="noopener noreferrer">Rails Girls Facebook page</a>.</p>
<p>Read more about the <a href="https://abletech.nz/article/rails-girls-supercharged">Rails Girls Supercharged</a> workshop which leads into the <a href="https://blog.addressfinder.io/kiwi-ruby-2901b4d3d45b" target="_blank" rel="noopener noreferrer">Kiwi Ruby conference</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Volunteer-led coding weekend — RailsBridge</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Opening up the world of coding to people willing to learn.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="fhyd59h3x85gked3pn6fzw5h" alt="Volunteers and participants at the 2018 workshop. Photo cred: RailsBridge" data-big=https://cms-assets.abletech.nz/large_1i_Ek_N_Uybfr_EV_0_HJ_61touz_NA_683b70b3cf.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 221px" src="https://cms-assets.abletech.nz/1i_Ek_N_Uybfr_EV_0_HJ_61touz_NA_683b70b3cf.png" srcset="https://cms-assets.abletech.nz/large_1i_Ek_N_Uybfr_EV_0_HJ_61touz_NA_683b70b3cf.png 1000w, https://cms-assets.abletech.nz/small_1i_Ek_N_Uybfr_EV_0_HJ_61touz_NA_683b70b3cf.png 500w, https://cms-assets.abletech.nz/medium_1i_Ek_N_Uybfr_EV_0_HJ_61touz_NA_683b70b3cf.png 750w, https://cms-assets.abletech.nz/thumbnail_1i_Ek_N_Uybfr_EV_0_HJ_61touz_NA_683b70b3cf.png 221w" data-zooming-width="1000" data-zooming-height="707" loading="lazy" width="1000" height="707"></figure>
</div>
<p><em>Volunteers and participants at the 2018 workshop. Photo cred: RailsBridge</em></p>
<p>Here at Abletech we’re proud sponsors of <a href="http://www.railsbridge.org/" target="_blank" rel="noopener noreferrer">RailsBridge</a>. We’re impressed with the dedication of the volunteers. We’re keen to support people who are genuinely interested in learning to code. And we know that team-work is key in web development, as is collaboration, patience and perseverance.</p>
<p>Over the weekend sponsors, volunteers and tutors joined forces to learn and teach. These weekends rely on sponsoring of everything from venue to food. Wellington’s Flux Federation HQ provided this weekend’s venue. Coaches prepared. People signed up. Amazing food was donated.</p>
<p>The idea of sponsored coding weekends started in San Francisco. People who are new to programming show up with an open mind, willing to learn. People with some coding experience teach all weekend and share their knowledge.</p>
<p>The atmosphere is inclusive and non-hierarchical. The formal goal is to transform tech culture by generating leaders, teachers and mentors who empower the underserved.</p>
<h3>RailsBridge — Rails Girls</h3>
<p>Previously Rails Girls, the RailsBridge organisers now push for diversity of all kinds in tech. These weekends gained popularity teaching Ruby on Rails to women but now they’ve broadened it out to cover gender, race, sexual orientation, ability and class.</p>
<h3>Read more related articles:</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/rails-girls-supercharged">Rails Girls Supercharged and Kiwi Ruby</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/rails-girls-workshops">Rails Girls workshops</a> — organiser’s perspective</p>
</li>
<li>
<p><a href="https://abletech.nz/article/rails-girls-wellington">Rails Girls Wellington</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Understanding GenServers</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Learn the GenServer process with Nigel Ramsay who approaches Elixir from the perspective of a Ruby developer.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h6y6sao2vptqp1rqftm5a0uj" alt="" data-big=https://cms-assets.abletech.nz/small_1p_PM_Wyv71e4_P5_P8_VD_Cw_Oi_Uw_2ba5de1512.png sizes="(min-width: 768px) 500px, (min-width: 640px) 225px" src="https://cms-assets.abletech.nz/1p_PM_Wyv71e4_P5_P8_VD_Cw_Oi_Uw_2ba5de1512.png" srcset="https://cms-assets.abletech.nz/small_1p_PM_Wyv71e4_P5_P8_VD_Cw_Oi_Uw_2ba5de1512.png 500w, https://cms-assets.abletech.nz/thumbnail_1p_PM_Wyv71e4_P5_P8_VD_Cw_Oi_Uw_2ba5de1512.png 225w" data-zooming-width="500" data-zooming-height="347" loading="lazy" width="500" height="347"></figure>
</div>
<p>Nigel recently attended the Elixir conference in the USA.</p>
<p>In this Abletech Tech Talk you’ll see a demo showing a Slack mixed task to see the GenServer in action.</p>
<h3>Want to know more about Elixir?</h3>
<ul>
<li>
<p>Find out about trying <a href="https://abletech.nz/article/elixir-for-addressfinder">Elixir for the first time in this blog post</a> and video about using Elixir in place of Ruby for <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">AddressFinder</a>.</p>
</li>
<li>
<p>Check out <a href="https://twitter.com/AndrewPett1" target="_blank" rel="noopener noreferrer">Andrew Pett</a>’s <a href="https://abletech.nz/resource/elixir-learnings-1/">Elixir Learnings</a>.</p>
</li>
<li>
<p>Read more about <a href="https://abletech.nz/article/elixir-keeps-running">Elixir in Marcus Baguley’s</a> blog post.</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>The Abletech story — part one</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Abletech builds web and mobile software. Our company's clients include sole traders, charities, commercial businesses and large government organisations. In the beginning it was all about some guys who simply loved the internet.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h6v318yzn2vb9z13wtaxioju" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Xm_Jq9_E_Zy9l_O7t5_Kg_005df2de5d.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Xm_Jq9_E_Zy9l_O7t5_Kg_005df2de5d.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_Xm_Jq9_E_Zy9l_O7t5_Kg_005df2de5d.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
</div>
<p>Launched in 2006, Abletech was borne out of a keen desire to boost business systems using cloud technology. Its founders worked, played and studied computing. They understood enterprise strategy and best practice. They became discerning. Discernment is a quality that continues to serve Abletech well.</p>
<h2>Background</h2>
<p>Abletech founders, Marcus Baguley and Nigel Ramsay, smile a lot when they hark back to the circumstances surrounding the beginnings of Abletech. They are reinvigorated as they remember the sense of freedom and independence that came with indulging their enthusiasm for cloud technologies. These days they’re still fired-up about striving for efficiency in the build and the end-use. Both Marcus and Nigel have minds that prefer the fast lane. Both are still hands-on directors of Abletech.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zqjp7gx656xmq1h8v2t7ohrs" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_U81_VW_3y8m2x_Gwzy_c8e90de890.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_U81_VW_3y8m2x_Gwzy_c8e90de890.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_U81_VW_3y8m2x_Gwzy_c8e90de890.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<p>Marcus and Nigel originally worked for large heavily structured organisations. Banking. Government. They were building software with little say over technology choices, plans or implementations. Nigel reflects, “the enterprise choices that the management team were making were impeding the rate at which we could deliver solutions”. These years sound a bit like teen years. Restrictions. Annoyances. Unnecessary complications. But out of the discontent grew a way of reaching organisational objectives using modern approaches.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l0kbbq518xutvc0y224xjpir" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0gc_R42_L2f_Z_Rl_Ol_Zn2_538ec67e09.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0gc_R42_L2f_Z_Rl_Ol_Zn2_538ec67e09.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0gc_R42_L2f_Z_Rl_Ol_Zn2_538ec67e09.jpg 245w" data-zooming-width="245" data-zooming-height="61" loading="lazy" width="245" height="61"></figure>
</div>
<h2>Early days</h2>
<p>The initial trio of founders was Nigel, Koz and Marcus. While they enjoyed the scale and professionalism of big business, all three shared the feeling that technology could serve organisations better. They called their new company Able Technology Ltd. They loved the internet and scheduled a day each week to collaborate at each other’s houses.</p>
<p><strong>Ruby on Rails</strong>
They had a growing interest in an emerging system of coding that was altogether faster and more logical. They had the foresight to believe it was going to change the face of coding. The new system, Ruby on Rails, went on to do just that. It enables much faster builds and delivery than previously existed.</p>
<p>They remember the first ever Rails meet-up with six people in attendance. Marcus explains, “it was more of a hobby, there was no prospect of any commercial work in Rails”, but they all saw its potential. Koz, full name Michael Koziarski, was a contributor to early Rails development from 2004. He left Able Technology Ltd. to focus on his work as a Rails core team member, and other projects.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="c1e5hroro8ee9gxwghrpnqr4" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0az6tuyk_C93t_X0_Xq_V_cab27d6136.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0az6tuyk_C93t_X0_Xq_V_cab27d6136.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0az6tuyk_C93t_X0_Xq_V_cab27d6136.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<h2>Foresight</h2>
<p>These guys could see the dawn of a new era. They predicted that Software as a Service (SaaS) would become increasingly prominent and that Ruby on Rails would invigorate previously arduous design and development. “SaaS was not well understood at that point,” explains Marcus. “But there was a growing need for inexpensive platforms, easy deployments, straightforward upgrades and online applications.”</p>
<p><strong>Efficiency</strong>
Nigel remembers the old days. “Previously we’d do it all manually, coding with Java modules that we packaged and uploaded, we’d code an XML file, tell it where it should go and how it should run. It was slow. It took so long to deploy.” He remembers one single screen with a handful of fields that took a month to make. Ruby on Rails offered continuous integration and continuous deployment. “We write the code, then the merging, compiling, packaging is all automatic.”</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mh5lewnbftvzabbwr1bcef76" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Exhu9px_S_66_Bh_T3r_faedd77e11.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Exhu9px_S_66_Bh_T3r_faedd77e11.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_Exhu9px_S_66_Bh_T3r_faedd77e11.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<h2>Growth</h2>
<p>By 2007 Marcus and Nigel had built Sharesight.com, an online share portfolio manager. They partnered with father-son team, Tony and Scott Ryburn, to bootstrap and develop the popular software.</p>
<p>Scott says that Marcus and Nigel understood the SaaS model and provided expert knowledge, insight and advice. “They delivered an elegant, highly sophisticated product rapidly and cost-effectively. It was above and beyond our expectations.” Sharesight is now enjoying success as an independent investment utility. Xero Australia MD Chris Ridd says, “Sharesight is a key add-on partner for accountants or financial advisers who help clients with their investments. They’re an important player in our strategy to build Xero horizontally and vertically.”</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cx358zvhur2novy1urj2wves" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Rx_G_Zhmnv_Dp2_D_Ylp_V_f2068faf79.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Rx_G_Zhmnv_Dp2_D_Ylp_V_f2068faf79.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_Rx_G_Zhmnv_Dp2_D_Ylp_V_f2068faf79.jpg 245w" data-zooming-width="245" data-zooming-height="91" loading="lazy" width="245" height="91"></figure>
</div>
<p><strong>Word got around</strong>
Marcus and Nigel were becoming sought-after for their organisational nouse. They developed modern solutions for Loadstorm, Terralink, Ministry of Social Development and Radio NZ. As the work picked up, Marcus and Nigel began hand-picking developers to work with them. “Now it was more than just us,” says Nigel. “Our first hire was Shevaun.”</p>
<p>For the next installment read <a href="https://abletech.nz/article/the-abletech-story-part-one">The Abletech Story — Part Two</a> which explains the second phase of the company’s growth.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Te Wiki o Te Reo Māori 2017</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>It’s Māori Language Week.</h2>
<p>How about ordering your coffee in Te Reo today?</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h0nc0ccy3y194wawwtbr5rf7" alt="Image cred: Māori Language Commission" data-big=https://cms-assets.abletech.nz/large_1_UE_Br_Yv1_W2_KM_0q_Gi_Rbvd_A_2f2ea12d5e.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 182px" src="https://cms-assets.abletech.nz/1_UE_Br_Yv1_W2_KM_0q_Gi_Rbvd_A_2f2ea12d5e.jpeg" srcset="https://cms-assets.abletech.nz/large_1_UE_Br_Yv1_W2_KM_0q_Gi_Rbvd_A_2f2ea12d5e.jpeg 1000w, https://cms-assets.abletech.nz/small_1_UE_Br_Yv1_W2_KM_0q_Gi_Rbvd_A_2f2ea12d5e.jpeg 500w, https://cms-assets.abletech.nz/medium_1_UE_Br_Yv1_W2_KM_0q_Gi_Rbvd_A_2f2ea12d5e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_UE_Br_Yv1_W2_KM_0q_Gi_Rbvd_A_2f2ea12d5e.jpeg 182w" data-zooming-width="1000" data-zooming-height="857" loading="lazy" width="1000" height="857"></figure>
</div>
<p><em>Image cred: Māori Language Commission</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Terraform</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Cameron Fowler’s been using Terraform lately. Terraform’s an open-source tool for building, changing and versioning infrastructure. Cam explains it’s like a provisioning piece of software but it runs on cloud infrastructure.</h2>
<p>He shared his Terraform experience with Abletechers at a recent Tech Talk. <a href="https://vimeo.com/224095368" target="_blank" rel="noopener noreferrer">Catch it in this video</a>:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sbhfano42g4fwl9rimuesq7m" alt="" data-big=https://cms-assets.abletech.nz/large_1_A0o4_Fc_Pxw6_ZG_6_f_T_Px_Rvu_A_16035cf5ad.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 244px" src="https://cms-assets.abletech.nz/1_A0o4_Fc_Pxw6_ZG_6_f_T_Px_Rvu_A_16035cf5ad.png" srcset="https://cms-assets.abletech.nz/large_1_A0o4_Fc_Pxw6_ZG_6_f_T_Px_Rvu_A_16035cf5ad.png 1000w, https://cms-assets.abletech.nz/small_1_A0o4_Fc_Pxw6_ZG_6_f_T_Px_Rvu_A_16035cf5ad.png 500w, https://cms-assets.abletech.nz/medium_1_A0o4_Fc_Pxw6_ZG_6_f_T_Px_Rvu_A_16035cf5ad.png 750w, https://cms-assets.abletech.nz/thumbnail_1_A0o4_Fc_Pxw6_ZG_6_f_T_Px_Rvu_A_16035cf5ad.png 244w" data-zooming-width="1000" data-zooming-height="639" loading="lazy" width="1000" height="639"></figure>
</div>
<blockquote>
<p><a href="https://www.terraform.io/intro/index.html" target="_blank" rel="noopener noreferrer">Terraform can manage existing and popular service providers as well as custom in-house solutions. Configuration files describe to Terraform the components needed to run a single application or your entire datacenter.</a></p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rlfguq269bgnzd5errrwjlay" alt="" data-big=https://cms-assets.abletech.nz/medium_1_S7wxm_U_Wvbg_E3_N_Ag_EM_7_Tr_Nw_0f9b899015.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 239px" src="https://cms-assets.abletech.nz/1_S7wxm_U_Wvbg_E3_N_Ag_EM_7_Tr_Nw_0f9b899015.png" srcset="https://cms-assets.abletech.nz/small_1_S7wxm_U_Wvbg_E3_N_Ag_EM_7_Tr_Nw_0f9b899015.png 500w, https://cms-assets.abletech.nz/medium_1_S7wxm_U_Wvbg_E3_N_Ag_EM_7_Tr_Nw_0f9b899015.png 750w, https://cms-assets.abletech.nz/thumbnail_1_S7wxm_U_Wvbg_E3_N_Ag_EM_7_Tr_Nw_0f9b899015.png 239w" data-zooming-width="750" data-zooming-height="490" loading="lazy" width="750" height="490"></figure>
</div>
<p>Catch another Abletech Tech Talk:</p>
<ul>
<li>
<p><a href="https://abletech.nz/resource/elixir-for-javascript-and-ruby-developers">Elixir</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/enspiral-dev-academy-meetup">Intro to TypeScript</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/rails-girls-workshops">Running a Rails Girls weekend</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/rails-girls-workshops">Turbolinks</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/rails-girls-workshops">Running MIDI hardware from your browser</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>🚘 SmartInspect with React Native 📱</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>New inspection app speeds up inspections. Take photos. Record damage. SmartInspect improves the process of making inspections.</h2>
<p>Our Rapid Projects team developed this mobile app in three months. We used React Native and found it fast and efficient. Watch a demo of SmartInspect. Hear what Naiki Pearl and <a href="https://twitter.com/NorquayKate" target="_blank" rel="noopener noreferrer">Kate Norquay</a> found as they developed using React Native.</p>
<p>The Abletech team got a sneak preview of the SmartInspect app last week.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="p98l0xkw3q0nafbttl6lq3vg" alt="" data-big=https://cms-assets.abletech.nz/large_1lx77_Cl_Gw_C6_J_V_Ha2_B_Im_I4_Q_8c5b00d9c0.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 242px" src="https://cms-assets.abletech.nz/1lx77_Cl_Gw_C6_J_V_Ha2_B_Im_I4_Q_8c5b00d9c0.jpeg" srcset="https://cms-assets.abletech.nz/large_1lx77_Cl_Gw_C6_J_V_Ha2_B_Im_I4_Q_8c5b00d9c0.jpeg 1000w, https://cms-assets.abletech.nz/small_1lx77_Cl_Gw_C6_J_V_Ha2_B_Im_I4_Q_8c5b00d9c0.jpeg 500w, https://cms-assets.abletech.nz/medium_1lx77_Cl_Gw_C6_J_V_Ha2_B_Im_I4_Q_8c5b00d9c0.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1lx77_Cl_Gw_C6_J_V_Ha2_B_Im_I4_Q_8c5b00d9c0.jpeg 242w" data-zooming-width="1000" data-zooming-height="645" loading="lazy" width="1000" height="645"></figure>
</div>
<h3>More from Abletech</h3>
<ul>
<li>
<p>Review of the recent <a href="https://abletech.nz/article/cssconf-jsconf-australia-2018">CSS and JS conferences</a> in Australia</p>
</li>
<li>
<p><a href="https://abletech.nz/article/ruby-conf-in-wellington">Kiwi Ruby</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/national-javascript-conference">NZ Javascript Conf</a></p>
</li>
<li>
<p>Graham, infrastructure lead at Coinbase gives an <a href="https://abletech.nz/article/coinbase">insight behind-the-scenes</a></p>
</li>
<li>
<p>Intern reflects on <a href="https://abletech.nz/article/interning-at-abletech">three months at Abletech</a></p>
</li>
<li>
<p>Follow Abletech on <a href="https://twitter.com/Abletech" target="_blank" rel="noopener noreferrer">Twitter</a>, <a href="https://www.facebook.com/abletech" target="_blank" rel="noopener noreferrer">Facebook</a> or <a href="https://nz.linkedin.com/company/able-technology" target="_blank" rel="noopener noreferrer">LinkedIn</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Runscope for API integration tests</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Find out how we use API integration test service: Runscope</h2>
<p>Runscope provides API performance testing. Runscope’s official blurb is this:</p>
<blockquote>
<p>Trust Your APIs
Less downtime. Fewer support tickets. Faster time to resolution. We’ll tell you when your APIs break before your customers do.</p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="co6imhuid4d73kifx0butf46" alt="" data-big=https://cms-assets.abletech.nz/large_1m_QGN_4e_Fl_Yf_U_Tec2_GX_Ufc_EQ_f2acbbecdb.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1m_QGN_4e_Fl_Yf_U_Tec2_GX_Ufc_EQ_f2acbbecdb.png" srcset="https://cms-assets.abletech.nz/large_1m_QGN_4e_Fl_Yf_U_Tec2_GX_Ufc_EQ_f2acbbecdb.png 1000w, https://cms-assets.abletech.nz/small_1m_QGN_4e_Fl_Yf_U_Tec2_GX_Ufc_EQ_f2acbbecdb.png 500w, https://cms-assets.abletech.nz/medium_1m_QGN_4e_Fl_Yf_U_Tec2_GX_Ufc_EQ_f2acbbecdb.png 750w, https://cms-assets.abletech.nz/thumbnail_1m_QGN_4e_Fl_Yf_U_Tec2_GX_Ufc_EQ_f2acbbecdb.png 245w" data-zooming-width="1000" data-zooming-height="578" loading="lazy" width="1000" height="578"></figure>
</div>
<p>In this demonstration, <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a> shows how he configures Runscope to perform integration tests on a series of <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">AddressFinder</a> and <a href="https://abletech.nz/resource/smartinspect-with-react-native">SmartInspect</a> APIs.</p>
<p>The examples show how the output of one API can be used as the input of a second API. Assertions are defined to confirm that status codes and JSON responses are as expected.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t7ic94ekoemgc4y00fmdy6fm" alt="" data-big=https://cms-assets.abletech.nz/large_1q_Zr7t_WB_22x_KT_614_SZ_Tc56w_ac14c70ec8.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1q_Zr7t_WB_22x_KT_614_SZ_Tc56w_ac14c70ec8.png" srcset="https://cms-assets.abletech.nz/large_1q_Zr7t_WB_22x_KT_614_SZ_Tc56w_ac14c70ec8.png 1000w, https://cms-assets.abletech.nz/small_1q_Zr7t_WB_22x_KT_614_SZ_Tc56w_ac14c70ec8.png 500w, https://cms-assets.abletech.nz/medium_1q_Zr7t_WB_22x_KT_614_SZ_Tc56w_ac14c70ec8.png 750w, https://cms-assets.abletech.nz/thumbnail_1q_Zr7t_WB_22x_KT_614_SZ_Tc56w_ac14c70ec8.png 245w" data-zooming-width="1000" data-zooming-height="563" loading="lazy" width="1000" height="563"></figure>
</div>
<p>Finally, Nigel shows how a set of API tests can be pointed at different environments (such as staging, or production) with little extra effort.</p>
<p>Read more from <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a>:</p>
<ul>
<li>
<p><a href="https://abletech.nz/article/travelling-with-a-sim-card-sticker">SIM card stickers</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/adding-docker-to-a-rails-application">Adding Docker to a Rails app</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Ruby Conf in Wellington</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Kiwi Ruby 2019 at Te Papa</h2>
<p>The Ruby community holds conferences all over the globe, using local and international speakers, for Rubyists of all levels. The latest conference was a single day of talks held at Te Papa in Wellington which is two blocks from Abletech’s headquarters.</p>
<p>Kiwi Ruby 2019 was well organised and enjoyable. There were generous sponsors, and lots of extra activities for visitors to the region. The conference team did a fantastic job. The vast majority of speakers were really interesting and although we enjoyed all the speakers, we also had a few favourites…</p>
<h2>Our highlights:</h2>
<h3>Jack Purvis</h3>
<p>Jack’s creative visual presentation was a highlight of the day for Abletechers. Jack’s a visual artist and his talk included live coding and VJing. He demonstrated his app <a href="https://www.visor.live/" target="_blank" rel="noopener noreferrer">Visor</a> which is an IDE for programming music visualisations in Ruby. His visuals wowed the crowd as did his live code written in Ruby.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yyo6bgwv403c90pzcg9s9qc1" alt="" data-big=https://cms-assets.abletech.nz/large_1d_N2k_Pjod9_x_J_Vr7_IW_Qs_A9_Q_d0ee3483e9.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1d_N2k_Pjod9_x_J_Vr7_IW_Qs_A9_Q_d0ee3483e9.jpeg" srcset="https://cms-assets.abletech.nz/large_1d_N2k_Pjod9_x_J_Vr7_IW_Qs_A9_Q_d0ee3483e9.jpeg 1000w, https://cms-assets.abletech.nz/small_1d_N2k_Pjod9_x_J_Vr7_IW_Qs_A9_Q_d0ee3483e9.jpeg 500w, https://cms-assets.abletech.nz/medium_1d_N2k_Pjod9_x_J_Vr7_IW_Qs_A9_Q_d0ee3483e9.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1d_N2k_Pjod9_x_J_Vr7_IW_Qs_A9_Q_d0ee3483e9.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jyagf6jmdn4bfrtfmct6f45u" alt="" data-big=https://cms-assets.abletech.nz/large_1_K_Mbiqun_Nzw_N_Idn9_5g_MP_5w_57be1fd772.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 230px" src="https://cms-assets.abletech.nz/1_K_Mbiqun_Nzw_N_Idn9_5g_MP_5w_57be1fd772.png" srcset="https://cms-assets.abletech.nz/large_1_K_Mbiqun_Nzw_N_Idn9_5g_MP_5w_57be1fd772.png 1000w, https://cms-assets.abletech.nz/small_1_K_Mbiqun_Nzw_N_Idn9_5g_MP_5w_57be1fd772.png 500w, https://cms-assets.abletech.nz/medium_1_K_Mbiqun_Nzw_N_Idn9_5g_MP_5w_57be1fd772.png 750w, https://cms-assets.abletech.nz/thumbnail_1_K_Mbiqun_Nzw_N_Idn9_5g_MP_5w_57be1fd772.png 230w" data-zooming-width="1000" data-zooming-height="680" loading="lazy" width="1000" height="680"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jvfdgxzeax4321fxbh3xch2e" alt="" data-big=https://cms-assets.abletech.nz/large_1_Nzlvd_Uk9_Qgwxr_Ct_Y5_A9d4_A_f3368818d6.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 209px" src="https://cms-assets.abletech.nz/1_Nzlvd_Uk9_Qgwxr_Ct_Y5_A9d4_A_f3368818d6.png" srcset="https://cms-assets.abletech.nz/large_1_Nzlvd_Uk9_Qgwxr_Ct_Y5_A9d4_A_f3368818d6.png 1000w, https://cms-assets.abletech.nz/small_1_Nzlvd_Uk9_Qgwxr_Ct_Y5_A9d4_A_f3368818d6.png 500w, https://cms-assets.abletech.nz/medium_1_Nzlvd_Uk9_Qgwxr_Ct_Y5_A9d4_A_f3368818d6.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Nzlvd_Uk9_Qgwxr_Ct_Y5_A9d4_A_f3368818d6.png 209w" data-zooming-width="1000" data-zooming-height="748" loading="lazy" width="1000" height="748"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="iwqtdarrxaoac5vp7d1jzfgd" alt="" data-big=https://cms-assets.abletech.nz/large_14_Rfl_Lvk8jv_M_Px_We_zxf_S_Sg_cc5a3c860b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/14_Rfl_Lvk8jv_M_Px_We_zxf_S_Sg_cc5a3c860b.jpeg" srcset="https://cms-assets.abletech.nz/large_14_Rfl_Lvk8jv_M_Px_We_zxf_S_Sg_cc5a3c860b.jpeg 1000w, https://cms-assets.abletech.nz/small_14_Rfl_Lvk8jv_M_Px_We_zxf_S_Sg_cc5a3c860b.jpeg 500w, https://cms-assets.abletech.nz/medium_14_Rfl_Lvk8jv_M_Px_We_zxf_S_Sg_cc5a3c860b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_14_Rfl_Lvk8jv_M_Px_We_zxf_S_Sg_cc5a3c860b.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<h3>Nick Johnstone</h3>
<p>Another popular speaker was Nick Johnstone who explored the idea of alternative front-end languages and ‘Ruby in the browser’. Nick gave a live demo of using Ruby to write code for the browser by converting it to JS via WebAssembly. His demo of his <a href="https://github.com/prism-rb/prism" target="_blank" rel="noopener noreferrer">Prism</a> project went without a hitch — he wrote code in Ruby and ran it in his web browser. His talk ended with a twist - his entire presentation had been running using Prism in his web browser!</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tr5rjm4ox12h7ym6jdghav1r" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1um_Kfg_V6ly_Zy_NVX_7_O_Nsn_Zk_A_619491ed8b.png sizes="(min-width: 640px) 174px" src="https://cms-assets.abletech.nz/1um_Kfg_V6ly_Zy_NVX_7_O_Nsn_Zk_A_619491ed8b.png" srcset="https://cms-assets.abletech.nz/thumbnail_1um_Kfg_V6ly_Zy_NVX_7_O_Nsn_Zk_A_619491ed8b.png 174w" data-zooming-width="174" data-zooming-height="156" loading="lazy" width="174" height="156"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dhoxk7p1wriioqcy5k15odin" alt="" data-big=https://cms-assets.abletech.nz/large_12_G_Qx_W0wf_Rn_Fxs1sbld1l_Bg_6eb1638204.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/12_G_Qx_W0wf_Rn_Fxs1sbld1l_Bg_6eb1638204.jpeg" srcset="https://cms-assets.abletech.nz/large_12_G_Qx_W0wf_Rn_Fxs1sbld1l_Bg_6eb1638204.jpeg 1000w, https://cms-assets.abletech.nz/small_12_G_Qx_W0wf_Rn_Fxs1sbld1l_Bg_6eb1638204.jpeg 500w, https://cms-assets.abletech.nz/medium_12_G_Qx_W0wf_Rn_Fxs1sbld1l_Bg_6eb1638204.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_12_G_Qx_W0wf_Rn_Fxs1sbld1l_Bg_6eb1638204.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<h3>Michael Dowse</h3>
<p>Michael opened the conference with his talk about abstraction in the context of his four-year-old daughter Charlie as she learns how the world works. He’s an entertaining speaker and was enjoyable to hear from. He suggested that asking ‘why’ is a recursive function.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="g23m8komk6tkamrn3y7dayaj" alt="" data-big=https://cms-assets.abletech.nz/large_133k_T8c_J6qq_Mg_CV_1oa_Fu_Zw_f31a294d57.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/133k_T8c_J6qq_Mg_CV_1oa_Fu_Zw_f31a294d57.jpeg" srcset="https://cms-assets.abletech.nz/large_133k_T8c_J6qq_Mg_CV_1oa_Fu_Zw_f31a294d57.jpeg 1000w, https://cms-assets.abletech.nz/small_133k_T8c_J6qq_Mg_CV_1oa_Fu_Zw_f31a294d57.jpeg 500w, https://cms-assets.abletech.nz/medium_133k_T8c_J6qq_Mg_CV_1oa_Fu_Zw_f31a294d57.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_133k_T8c_J6qq_Mg_CV_1oa_Fu_Zw_f31a294d57.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wvx1xd1dqifdm4l7snprob26" alt="" data-big=https://cms-assets.abletech.nz/large_1_V_Oy_Oet8t6_w_E2_P_Jtys99_IQ_79bbf5f10b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_V_Oy_Oet8t6_w_E2_P_Jtys99_IQ_79bbf5f10b.jpeg" srcset="https://cms-assets.abletech.nz/large_1_V_Oy_Oet8t6_w_E2_P_Jtys99_IQ_79bbf5f10b.jpeg 1000w, https://cms-assets.abletech.nz/small_1_V_Oy_Oet8t6_w_E2_P_Jtys99_IQ_79bbf5f10b.jpeg 500w, https://cms-assets.abletech.nz/medium_1_V_Oy_Oet8t6_w_E2_P_Jtys99_IQ_79bbf5f10b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_V_Oy_Oet8t6_w_E2_P_Jtys99_IQ_79bbf5f10b.jpeg 156w" data-zooming-width="1000" data-zooming-height="1000" loading="lazy" width="1000" height="1000"></figure>
</div>
<h3>Shaun O’Connell &amp; Sophie Price</h3>
<p>Shaun and Sophie’s design consistency talk was really insightful. They spoke about how Flick is rolling out pattern libraries, high-quality code and a new passion for design systems. They gave an insight into YAML, how developers &amp; designers interact and what abstractions are most useful.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rhsodhclpgbp9u0et7de96of" alt="" data-big=https://cms-assets.abletech.nz/large_1g_Fan_Nkkqf_J1m_Gw_OOHL_Jffw_88485bc301.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1g_Fan_Nkkqf_J1m_Gw_OOHL_Jffw_88485bc301.jpeg" srcset="https://cms-assets.abletech.nz/large_1g_Fan_Nkkqf_J1m_Gw_OOHL_Jffw_88485bc301.jpeg 1000w, https://cms-assets.abletech.nz/small_1g_Fan_Nkkqf_J1m_Gw_OOHL_Jffw_88485bc301.jpeg 500w, https://cms-assets.abletech.nz/medium_1g_Fan_Nkkqf_J1m_Gw_OOHL_Jffw_88485bc301.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1g_Fan_Nkkqf_J1m_Gw_OOHL_Jffw_88485bc301.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vyksu7yusrfx3j0s89s90xzm" alt="" data-big=https://cms-assets.abletech.nz/large_1_GU_6_Ww7x_Yagg_E4_X_Pl_RE_Vu_A_ddc8623fe3.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_GU_6_Ww7x_Yagg_E4_X_Pl_RE_Vu_A_ddc8623fe3.jpeg" srcset="https://cms-assets.abletech.nz/large_1_GU_6_Ww7x_Yagg_E4_X_Pl_RE_Vu_A_ddc8623fe3.jpeg 1000w, https://cms-assets.abletech.nz/small_1_GU_6_Ww7x_Yagg_E4_X_Pl_RE_Vu_A_ddc8623fe3.jpeg 500w, https://cms-assets.abletech.nz/medium_1_GU_6_Ww7x_Yagg_E4_X_Pl_RE_Vu_A_ddc8623fe3.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_GU_6_Ww7x_Yagg_E4_X_Pl_RE_Vu_A_ddc8623fe3.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<h3>Pete Nicholls</h3>
<p>The closing talk was also a goodie. Pete Nicholls spoke about ‘‘Ethics-driven development’. He touched on a variety of challenges in the tech industry which present ethical dilemmas that aren’t being managed, and our responsibility, as technologists, to do better.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kf9exwhsy7ubyupnfevvrj0h" alt="" data-big=https://cms-assets.abletech.nz/large_1bodjao82_Kyq0_Gl14m7_Lffg_8b08736464.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1bodjao82_Kyq0_Gl14m7_Lffg_8b08736464.jpeg" srcset="https://cms-assets.abletech.nz/large_1bodjao82_Kyq0_Gl14m7_Lffg_8b08736464.jpeg 1000w, https://cms-assets.abletech.nz/small_1bodjao82_Kyq0_Gl14m7_Lffg_8b08736464.jpeg 500w, https://cms-assets.abletech.nz/medium_1bodjao82_Kyq0_Gl14m7_Lffg_8b08736464.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1bodjao82_Kyq0_Gl14m7_Lffg_8b08736464.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wa01dglf4kv7gqqqqlr61z9e" alt="" data-big=https://cms-assets.abletech.nz/large_13_RV_3b8s_H1s_M8s_Vtf1_P6ma_A_9278ce2a0b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/13_RV_3b8s_H1s_M8s_Vtf1_P6ma_A_9278ce2a0b.jpeg" srcset="https://cms-assets.abletech.nz/large_13_RV_3b8s_H1s_M8s_Vtf1_P6ma_A_9278ce2a0b.jpeg 1000w, https://cms-assets.abletech.nz/small_13_RV_3b8s_H1s_M8s_Vtf1_P6ma_A_9278ce2a0b.jpeg 500w, https://cms-assets.abletech.nz/medium_13_RV_3b8s_H1s_M8s_Vtf1_P6ma_A_9278ce2a0b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_13_RV_3b8s_H1s_M8s_Vtf1_P6ma_A_9278ce2a0b.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<h2>We’ve been big fans of Ruby for ever</h2>
<ul>
<li>
<p><a href="https://abletech.nz/article/the-abletech-story-part-one">The Abletech story began in 2006 when Ruby was a newborn</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/brighton-ruby-2019">Read our thoughts on this year’s UK Ruby conference in Brighton</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ruby">Last year’s RubyConf in Sydney</a></p>
</li>
</ul>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zygi2oqi1rlnpso0wj1yzikm" alt="" data-big=https://cms-assets.abletech.nz/large_1di_Ep1_a_M_V04_Bu_B2h2ri_AQ_22094dc1c4.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1di_Ep1_a_M_V04_Bu_B2h2ri_AQ_22094dc1c4.jpeg" srcset="https://cms-assets.abletech.nz/large_1di_Ep1_a_M_V04_Bu_B2h2ri_AQ_22094dc1c4.jpeg 1000w, https://cms-assets.abletech.nz/small_1di_Ep1_a_M_V04_Bu_B2h2ri_AQ_22094dc1c4.jpeg 500w, https://cms-assets.abletech.nz/medium_1di_Ep1_a_M_V04_Bu_B2h2ri_AQ_22094dc1c4.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1di_Ep1_a_M_V04_Bu_B2h2ri_AQ_22094dc1c4.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nu2q9fh805uizv8avmvbvp3h" alt="" data-big=https://cms-assets.abletech.nz/large_1u_E4_P_Jm_Himqiidn9l_My0_Ya_Q_1351888ce3.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1u_E4_P_Jm_Himqiidn9l_My0_Ya_Q_1351888ce3.png" srcset="https://cms-assets.abletech.nz/large_1u_E4_P_Jm_Himqiidn9l_My0_Ya_Q_1351888ce3.png 1000w, https://cms-assets.abletech.nz/small_1u_E4_P_Jm_Himqiidn9l_My0_Ya_Q_1351888ce3.png 500w, https://cms-assets.abletech.nz/medium_1u_E4_P_Jm_Himqiidn9l_My0_Ya_Q_1351888ce3.png 750w, https://cms-assets.abletech.nz/thumbnail_1u_E4_P_Jm_Himqiidn9l_My0_Ya_Q_1351888ce3.png 245w" data-zooming-width="1000" data-zooming-height="493" loading="lazy" width="1000" height="493"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Reaching your potential</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>What’s the best environment for you to reach your potential? What are some of the pathways and barriers that exist for you? How does your identity play a role in your opportunities and successes?</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="g7s7de8lj0pci7jhxk6ve302" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Si_Ai_Tr4m_B5vq5d_Dt_f25564468e.png sizes="(min-width: 640px) 220px" src="https://cms-assets.abletech.nz/0_Si_Ai_Tr4m_B5vq5d_Dt_f25564468e.png" srcset="https://cms-assets.abletech.nz/thumbnail_0_Si_Ai_Tr4m_B5vq5d_Dt_f25564468e.png 220w" data-zooming-width="220" data-zooming-height="156" loading="lazy" width="220" height="156"></figure>
</div>
<p><em>As ChenPalmer were scanning the country for awesome examples of superdiverse real women, they came across Dana, of course.</em></p>
<p>Lawyer Mai Chen is interested in diversity. Actually, ‘superdiversity’. She asked Abletecher Dana Iti if they could have a chat about what it’s like to be a woman, and Māori, and work in the tech industry.</p>
<p>Mai Chen is a Managing Partner of ChenPalmer, New Zealand’s first public law firm. As well as working in public and employment law, ChenPalmer, is passionate about superdiversity.</p>
<p><strong>Super Diverse Women Foundation</strong></p>
<p>Superdiversity involves understanding and redefining the traditional factors that make up our identities. Instead of limiting diversity to single axis lines like women or Māori, superdiversity seeks to reflect the authentic experience of real people. In fact, ChenPalmer have just launched a foundation of superdiverse women to recognise, empower and inspire diverse women leaders throughout Australasia. Dana’s a founding member of the foundation.</p>
<p>Dana says there needs to be more support and mentoring to help women achieve their goals.</p>
<p><em>“I wish this sort of support was available years ago.”</em></p>
<p>She says it’s rare to find other women in the tech industry who look like her.</p>
<p><em>“It’s important to shine a light on diverse women who are successful and might otherwise never be heard of.”</em></p>
<p>Dana has spent time thinking about the opportunities and strategies available to her. She’s had experiences in environments that were not conducive to growing.</p>
<p><em>“Adversities do make us stronger.”</em></p>
<p>Thankfully, she has also come across people and organisations that are open and supportive. Dana also says it’s important to feel that you can make mistakes as they teach you important lessons.</p>
<p><em>“I’ve made countless mistakes but I’ve learned from each and every one. I want to keep striving, and serving, because that’s what success is — growing, helping, encouraging.”</em></p>
<p><strong>How did Mai Chen come across Dana Iti?</strong></p>
<p>More and more people are learning about Dana. She’s been through a few ups and downs and she’s developed the courage to tell her story, especially if it might help others learn and understand how they might reach their own potential.</p>
<p>Dana facilitated at a WWGSD conference and some of her story is outlined in her Abletech Mihimihi.</p>
<p>As a founding member, Dana attended the launch of ChenPalmer’s Super Diverse Women and found the celebration of diversity refreshing and inspiring.</p>
<p><em>“I met many successful and influential women who were keen to share their experiences and encourage other women to achieve.”</em></p>
<p>Dana particularly enjoyed getting to know singer-songwriter Bic Runga and linking up with a public-speaking mentor.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bboq6ela4z16siqdxwk0hvpv" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_00_TY_Kvzqg_NX_Ik_Hskp_5e2ce1d3c6.jpg sizes="(min-width: 640px) 156px" src="https://cms-assets.abletech.nz/00_TY_Kvzqg_NX_Ik_Hskp_5e2ce1d3c6.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_00_TY_Kvzqg_NX_Ik_Hskp_5e2ce1d3c6.jpg 156w" data-zooming-width="156" data-zooming-height="156" loading="lazy" width="156" height="156"></figure>
</div>
<p><strong>What’s next for Dana?</strong></p>
<p>ChenPalmer have Dana tagged as an Emerging Leader. As such she’ll be part of the revolution! They’re looking to support women by boosting the next generation, helping women to understand their opportunities and by providing a network of superdiverse women to connect with each other.</p>
<p>Dana’s also been invited to speak on a panel at the upcoming JavaScript conference. She’ll be discussing mental fatigue and burnout in the IT industry under the topic Open Sourcing Mental Health.</p>
<p>Awesome work Dana — our very own Abletech Super Woman. We love having you on the Abletech Team of Super Developers.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uc8cqhbk9udgxl74tcv2kemb" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_02_Y7j6b_HE_Ab_S5_O_Jz_Z_cdf9f76617.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/02_Y7j6b_HE_Ab_S5_O_Jz_Z_cdf9f76617.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_02_Y7j6b_HE_Ab_S5_O_Jz_Z_cdf9f76617.jpg 245w" data-zooming-width="245" data-zooming-height="107" loading="lazy" width="245" height="107"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Rails Girls Workshops</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3>Free workshops are run all over the world by Rails Girls to encourage diversity in tech. These weekend-long workshops are organised by local volunteers. They’re friendly interactive workshops designed to teach women how web applications work. Abletech have been proud sponsors of Wellington Rails Girls for a number of years and Abletechers are regulars at the workshops.</h3>
<blockquote>
<p>To me Rails Girls is a really good way to help people find programming and gain a support network. Dawn Richardson.</p>
</blockquote>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="b77vfvm9x8udm772ldz2k7fd" alt="" data-big=https://cms-assets.abletech.nz/small_1ru0_Um_F_Mgcxp_Sglzp_Rf_Ti_A_4a3c6e0182.png sizes="(min-width: 768px) 492px, (min-width: 640px) 153px" src="https://cms-assets.abletech.nz/1ru0_Um_F_Mgcxp_Sglzp_Rf_Ti_A_4a3c6e0182.png" srcset="https://cms-assets.abletech.nz/small_1ru0_Um_F_Mgcxp_Sglzp_Rf_Ti_A_4a3c6e0182.png 492w, https://cms-assets.abletech.nz/thumbnail_1ru0_Um_F_Mgcxp_Sglzp_Rf_Ti_A_4a3c6e0182.png 153w" data-zooming-width="492" data-zooming-height="500" loading="lazy" width="492" height="500"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rdtx7wqo18x8vshtp385sbdk" alt="" data-big=https://cms-assets.abletech.nz/small_1_Ywyz1_XNQQ_Eh_G0_Ya9wg_w0g_7b35af984e.png sizes="(min-width: 768px) 500px, (min-width: 640px) 206px" src="https://cms-assets.abletech.nz/1_Ywyz1_XNQQ_Eh_G0_Ya9wg_w0g_7b35af984e.png" srcset="https://cms-assets.abletech.nz/small_1_Ywyz1_XNQQ_Eh_G0_Ya9wg_w0g_7b35af984e.png 500w, https://cms-assets.abletech.nz/thumbnail_1_Ywyz1_XNQQ_Eh_G0_Ya9wg_w0g_7b35af984e.png 206w" data-zooming-width="500" data-zooming-height="379" loading="lazy" width="500" height="379"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ctnijghbz0zjuzrhou1hwcou" alt="" data-big=https://cms-assets.abletech.nz/small_1e_Zr_F_Wn_11x_EL_4_N4j_ITW_Hew_fecf4e254c.png sizes="(min-width: 768px) 500px, (min-width: 640px) 190px" src="https://cms-assets.abletech.nz/1e_Zr_F_Wn_11x_EL_4_N4j_ITW_Hew_fecf4e254c.png" srcset="https://cms-assets.abletech.nz/small_1e_Zr_F_Wn_11x_EL_4_N4j_ITW_Hew_fecf4e254c.png 500w, https://cms-assets.abletech.nz/thumbnail_1e_Zr_F_Wn_11x_EL_4_N4j_ITW_Hew_fecf4e254c.png 190w" data-zooming-width="500" data-zooming-height="411" loading="lazy" width="500" height="411"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hmm3wo3bp7fv8w2yhxub8997" alt="" data-big=https://cms-assets.abletech.nz/small_1_X_c7wn_Cj_ZY_On_H_Ua6si_S_Epg_1751260c02.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_X_c7wn_Cj_ZY_On_H_Ua6si_S_Epg_1751260c02.png" srcset="https://cms-assets.abletech.nz/small_1_X_c7wn_Cj_ZY_On_H_Ua6si_S_Epg_1751260c02.png 500w, https://cms-assets.abletech.nz/thumbnail_1_X_c7wn_Cj_ZY_On_H_Ua6si_S_Epg_1751260c02.png 245w" data-zooming-width="500" data-zooming-height="283" loading="lazy" width="500" height="283"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="eotabwf8kdorix5ur86patsw" alt="" data-big=https://cms-assets.abletech.nz/small_1bu_Wmf3o3ou4u6_OH_Sp_T1_K_Dw_1d1be3b263.png sizes="(min-width: 768px) 500px, (min-width: 640px) 205px" src="https://cms-assets.abletech.nz/1bu_Wmf3o3ou4u6_OH_Sp_T1_K_Dw_1d1be3b263.png" srcset="https://cms-assets.abletech.nz/small_1bu_Wmf3o3ou4u6_OH_Sp_T1_K_Dw_1d1be3b263.png 500w, https://cms-assets.abletech.nz/thumbnail_1bu_Wmf3o3ou4u6_OH_Sp_T1_K_Dw_1d1be3b263.png 205w" data-zooming-width="500" data-zooming-height="380" loading="lazy" width="500" height="380"></figure>
</div>
<p>At a recent Abletech Tech Talk, Dawn Richardson explained what was involved organising the April Rails Girls workshop including:</p>
<ul>
<li>
<p>What they did at the workshop</p>
</li>
<li>
<p>How they funded the weekend</p>
</li>
<li>
<p>Food and catering</p>
</li>
<li>
<p>How they made the material relevant and impactful</p>
</li>
</ul>
<p>Abletecher Tania Walker coached last year and volunteered as an organiser this time. Abletech intern Kate Norquay attended as a participant this year and shared her experience at the Tech Talk.</p>
<p>Read on for a summary of the Abletech Tech Talk.</p>
<h2>An organiser’s review, ramblings and lessons learnt</h2>
<p><em>by Dawn Richardson</em></p>
<h3>The Workshop</h3>
<p>The Rails Girls workshops generally start with an ‘installation party’ for helping with any software installation issues. Then there are welcomes and introductions, followed by an inspirational talk with someone approachable and engaging. We introduce participants to a bit of Ruby with TryRuby.org which is an interactive way to start playing around. Then we start learning some Rails using a Rails Girls guide that teaches participants to build a To Do list. We break frequently for food and discussions. On the last day we have short talks from sponsors and a party so everyone can socialise and network together.</p>
<h3>The Funding</h3>
<p>We created a document that outlined how companies could sponsor us. It outlined our cause and offered four sponsorship levels: bronze, silver, gold and Ruby. Each level had perks attached like social media, logo placements, speaking at the after-party etc. We targeted sponsors who were likely to be sympathetic to our cause. Tech companies are keen to support diversity; it’s a hot topic at the moment. Getting sponsors for coffee, beer and food was more of a challenge as they’re not as directly impacted by the issues of diversity in tech.</p>
<p>This year we reached out for sponsorship and we were overwhelmed with the number of organisations who wanted to support us.</p>
<h3>The Food</h3>
<p>With catering comes dietary requirements so it was quite a job to provide appropriate food for those with certain dietary needs and allergies. We learned that some dairy free snacks have honey in them which isn’t suitable for vegan diets.</p>
<h3>Improvements from the last workshop</h3>
<p>For Rails Girls to survive long-term it needs to improve every time. We’ve asked for feedback, taken it on board and kept improvements coming. Here are some of the changes we’ve made.</p>
<p>Because Rails Girls targets those who are underrepresented in tech we made efforts to be inclusive. We tried to make our event open for transgender and non-binary gender people. All our toilets were made unisex and our language was updated appropriately to suit.</p>
<p>We are also trying to be more inclusive of supporting women who are in tech, to learn more, so a variety of skill levels were considered. We made efforts to encourage people, who were able, to work on their personal projects. Some guide modification was required for this.</p>
<p>We also included a <strong>Demo</strong> session which was great. People could demonstrate what they’d built. One project that stood out was designed to teach Te Reo, with sound.</p>
<p>This year we repeated a <strong>Pathways Into Tech</strong> session that worked well last year and was one of the most popular sections of the weekend. There are many different ways to journey in to the world of technology and this session was a great way to support and encourage.</p>
<h3>Participant’s Perspective</h3>
<p>Abletech’s intern, Kate, took part in the workshop this year. She explained that Rails Girls was one of the best tech events she’s ever been to. She sat near those working on personal projects. Kate said that at times she finds coding a bit isolated. She enjoyed the collaboration of the weekend. Kate found that sharing ideas with each other was really cool. She said there was creativity as they worked together. Kate enjoyed the feeling of solidarity at the workshop and wished she had gone to a Rails Girls session when she was learning to code, to gain confidence and to benefit from the support network.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kxl5g8a2mqbjk93vh66iambm" alt="Rails Girls Wellington volunteers" data-big=https://cms-assets.abletech.nz/medium_13d_Lcf3_Dbe7_K0_Jka6_JNT_Qhg_37dacabcbc.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 231px" src="https://cms-assets.abletech.nz/13d_Lcf3_Dbe7_K0_Jka6_JNT_Qhg_37dacabcbc.png" srcset="https://cms-assets.abletech.nz/small_13d_Lcf3_Dbe7_K0_Jka6_JNT_Qhg_37dacabcbc.png 500w, https://cms-assets.abletech.nz/medium_13d_Lcf3_Dbe7_K0_Jka6_JNT_Qhg_37dacabcbc.png 750w, https://cms-assets.abletech.nz/thumbnail_13d_Lcf3_Dbe7_K0_Jka6_JNT_Qhg_37dacabcbc.png 231w" data-zooming-width="750" data-zooming-height="507" loading="lazy" width="750" height="507"></figure>
</div>
<p><em>Rails Girls Wellington volunteers</em></p>
<p>Give Ruby a go <a href="http://tryruby.org/levels/1/challenges/0" target="_blank" rel="noopener noreferrer">right now</a>!</p>
<p>Find out more about <a href="http://railsgirls.com/" target="_blank" rel="noopener noreferrer">Rails Girls</a> or <a href="http://railsgirls.com/wellington.html" target="_blank" rel="noopener noreferrer">Wellington Rails Girls</a>.</p>
<p>Catch another <a href="https://abletech.nz/resource/elixir-for-javascript-and-ruby-developers">Abletech Tech Talk</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Rails Girls Wellington</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Sat 22nd &amp; Sun 23rd April, 2017</h2>
<p>Do you know someone who would be excited about learning Ruby on Rails?</p>
<p>Learn Ruby on Rails at this free two-day workshop. Get in quick! Applications are open and spaces are limited.</p>
<p>Rails Girls offers a casual and friendly environment.</p>
<h3><strong>You learn</strong></h3>
<p>Designing, prototyping and coding with the help from our coaches.</p>
<h3><strong>You need</strong></h3>
<p>Your own laptop, curiosity and a sprinkle of imagination!</p>
<h3><strong>How much does the workshop cost?</strong></h3>
<p>Nothing, it’s free! You just need to be excited! And we can help out with public transport costs if necessary.</p>
<h3><strong>Who is this aimed at?</strong></h3>
<p>People with non-binary gender identities or who identify as a girl or women, with basic knowledge of working with a computer. All ages may attend, but we would prefer very young people to come with a parent or guardian. Please bring your laptop.</p>
<h3><strong>Can men attend?</strong></h3>
<p>Yes, but you need to be accompanied by an interested woman. Also, girls are given a priority.</p>
<h3><strong>Do you have a code of conduct?</strong></h3>
<p>Yes — it is <a href="https://docs.google.com/document/d/1EiTZ-__VDuXpN1OqgVKnYr2Km_N3fD9WWN3g5cv5hAA/edit?usp=sharing" target="_blank" rel="noopener noreferrer">here</a>. We expect all attendees, coaches, and sponsor representatives to behave respectfully to make sure everyone has a great time.</p>
<h3><strong>I know how to program — How can I help?</strong></h3>
<p>We’re also looking for people to be coaches and help out with teaching and sharing the passion for Ruby on Rails. <a href="https://goo.gl/forms/bYilGKtzlw6FVq9o1" target="_blank" rel="noopener noreferrer">Apply here</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yu9i3wm66xb6d98dhfunpt82" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_X2_A_Srv_AU_9_aufzm_Yefb3h_Q_a0af599d48.png sizes="(min-width: 640px) 158px" src="https://cms-assets.abletech.nz/1_X2_A_Srv_AU_9_aufzm_Yefb3h_Q_a0af599d48.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_X2_A_Srv_AU_9_aufzm_Yefb3h_Q_a0af599d48.png 158w" data-zooming-width="158" data-zooming-height="156" loading="lazy" width="158" height="156"></figure>
</div>
<h3>Ready to sign up?</h3>
<p><a href="https://docs.google.com/forms/d/e/1FAIpQLSfIsGms7Gfq_MGT_bI6dcxXnbJgiAGtlIX-5oiczWoHZLAn7g/viewform?c=0&amp;w=1" target="_blank" rel="noopener noreferrer">Complete this application form now!</a></p>
<h2>Schedule:</h2>
<p><em>Note: This schedule is subject to change.</em></p>
<h2>Saturday 22nd April</h2>
<h3>8:30–9:30 Installation party</h3>
<p>For those who have had issues with installation — bring your laptop, so we can help you install Ruby on Rails.</p>
<h3>9:30–9:45 Registration</h3>
<p>Get in early for coffee!</p>
<h3>9:45–10:00 Introduction</h3>
<p>Your organisers will take you through housekeeping and a rough plan for the weekend.</p>
<h3>10:00–10:30 Talk</h3>
<h3>10:30–12:00 Ruby</h3>
<p>With the help of your coaches, and your new friends, start learning the Ruby programming language with <a href="http://tryruby.org/" target="_blank" rel="noopener noreferrer">tryruby.org</a>.</p>
<h3>12:00–12:30 Talk</h3>
<h3>12:30–13:00 Lunch</h3>
<p>Catered by <a href="http://www.foodenvy.co.nz/" target="_blank" rel="noopener noreferrer">Food Envy</a>!</p>
<h3>13:00–15:00 Project Time</h3>
<p>With the help of your coaches, we will help you to create your own web app.</p>
<h3>15:00–15:15 Break</h3>
<p>We need cupcakes, stat!</p>
<h3>15:15–15:30 Talk</h3>
<h3>15:30–16:30 Project Work</h3>
<p>Continue working on web apps and projects.</p>
<h3>16:30–17:00 Wrap up</h3>
<p>Recap what you’ve learned today, and start making a plan for tomorrow.</p>
<h2>Sunday 23rd April</h2>
<h3>9:30–9:45 Introduction</h3>
<p>Morning welcome and introduction.</p>
<h3>9:45–10:15 Panel</h3>
<p>“Pathways into Tech”</p>
<h3>10:15–12:00 Project Work</h3>
<p>Continue working on web apps and projects.</p>
<h3>12:00–13:00 Social lunch with sponsors and tech community</h3>
<h3>13:30–14:30 Wrap up</h3>
<p>Sponsor speeches and wrap up.
Phew, what a day. You did good.</p>
<h3>14:30 Party</h3>
<p>Meet awesome people in tech and hang out with your new friends.</p>
<p><em>Originally published at <a href="http://railsgirls.com/wellington" target="_blank" rel="noopener noreferrer">railsgirls.com</a>.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>National JavaScript Conference</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>New Zealand’s very first dedicated national JavaScript conference happened last week. It had a diverse range of content that educated, challenged and inspired the tech community.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sn29yejfuxpb3buz0dr58kkm" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_04_S8_Cf_Oi_MFWPUNI_2_X_6339968bac.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/04_S8_Cf_Oi_MFWPUNI_2_X_6339968bac.png" srcset="https://cms-assets.abletech.nz/thumbnail_04_S8_Cf_Oi_MFWPUNI_2_X_6339968bac.png 245w" data-zooming-width="245" data-zooming-height="84" loading="lazy" width="245" height="84"></figure>
</div>
<p>Highlights for the <a href="https://abletech.nz/about/">Abletech Team</a> were:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cree7un9u2pe4835pehgwu4z" alt="Igor Cost at [**nz.js(con);](http://conference.javascript.org.nz/)**" data-big=https://cms-assets.abletech.nz/large_1lxp_FI_Dr6q_H0_Mr_XG_Xo_GR_Kww_5319405d04.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1lxp_FI_Dr6q_H0_Mr_XG_Xo_GR_Kww_5319405d04.jpeg" srcset="https://cms-assets.abletech.nz/large_1lxp_FI_Dr6q_H0_Mr_XG_Xo_GR_Kww_5319405d04.jpeg 1000w, https://cms-assets.abletech.nz/small_1lxp_FI_Dr6q_H0_Mr_XG_Xo_GR_Kww_5319405d04.jpeg 500w, https://cms-assets.abletech.nz/medium_1lxp_FI_Dr6q_H0_Mr_XG_Xo_GR_Kww_5319405d04.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1lxp_FI_Dr6q_H0_Mr_XG_Xo_GR_Kww_5319405d04.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Igor Cost at <a href="http://conference.javascript.org.nz/" target="_blank" rel="noopener noreferrer"><strong>nz.js(con);</strong></a></em></p>
<p>**Deep Learning in your Browser by Igor Costa: **
How JavaScript could be used to harness the power of deep learning algorithms to understand the world around us. This presentation explained deep learning in a simple way using existing knowledge in JavaScript, implementing and solving funny and real examples.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ya5q1uxow21c2oc7rh9doh7x" alt="Stefan Judis at [**nz.js(con);](http://conference.javascript.org.nz/)**" data-big=https://cms-assets.abletech.nz/large_1_V_Ku_EGK_Gl_QIYI_4tef_Et_Gi_GQ_da0499cbf9.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_V_Ku_EGK_Gl_QIYI_4tef_Et_Gi_GQ_da0499cbf9.jpeg" srcset="https://cms-assets.abletech.nz/large_1_V_Ku_EGK_Gl_QIYI_4tef_Et_Gi_GQ_da0499cbf9.jpeg 1000w, https://cms-assets.abletech.nz/small_1_V_Ku_EGK_Gl_QIYI_4tef_Et_Gi_GQ_da0499cbf9.jpeg 500w, https://cms-assets.abletech.nz/medium_1_V_Ku_EGK_Gl_QIYI_4tef_Et_Gi_GQ_da0499cbf9.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_V_Ku_EGK_Gl_QIYI_4tef_Et_Gi_GQ_da0499cbf9.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Stefan Judis at <a href="http://conference.javascript.org.nz/" target="_blank" rel="noopener noreferrer"><strong>nz.js(con);</strong></a></em></p>
<p><strong>“I can’t work on my phone” — desktop all the things by Stefan Judis</strong>
The web platform gets stronger and new technologies are coming into our browsers every day. Does this mean that we don’t need desktop apps anymore? No, a big trend is happening in parallel. A new platform to build desktop applications entered the stage–Electron.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="fk76e8gfjmaq0s9ery4tprgy" alt="Julien Simon at [**nz.js(con);](http://conference.javascript.org.nz/)**" data-big=https://cms-assets.abletech.nz/large_1m1oz_M_Gn_Vqo_C53_X_VB_Km4_Q_3f3ca89a42.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1m1oz_M_Gn_Vqo_C53_X_VB_Km4_Q_3f3ca89a42.jpeg" srcset="https://cms-assets.abletech.nz/large_1m1oz_M_Gn_Vqo_C53_X_VB_Km4_Q_3f3ca89a42.jpeg 1000w, https://cms-assets.abletech.nz/small_1m1oz_M_Gn_Vqo_C53_X_VB_Km4_Q_3f3ca89a42.jpeg 500w, https://cms-assets.abletech.nz/medium_1m1oz_M_Gn_Vqo_C53_X_VB_Km4_Q_3f3ca89a42.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1m1oz_M_Gn_Vqo_C53_X_VB_Km4_Q_3f3ca89a42.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Julien Simon at <a href="http://conference.javascript.org.nz/" target="_blank" rel="noopener noreferrer"><strong>nz.js(con);</strong></a></em></p>
<p><strong>Building serverless apps with Node.js by Julien Simon</strong></p>
<p>After a quick refresh on what serverless architectures are and why they matter, this code-level talk demoed how you can easily build and deploy Node.js serverless applications on AWS, using AWS services such as Lambda and API Gateway, as well as several Open Source frameworks.</p>
<p><a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a> found Julien Simon’s talk interesting . Nigel says it is “good to know there are now deployment frameworks that facilitate the automation of collating and shipping groups Lambda functions together into ‘apps’”.</p>
<p>Awesome to see our very own Abletecher Dana Iti on the stage. Dana was invited to be on the panel discussion about mental fatigue in the IT industry. The panel talked about addressing exhaustion, and what can be done to prevent, spot or overcome excessive mental and emotional demands.</p>
<p><strong>Open Sourcing Mental Health</strong>
Mental fatigue and burnout are almost unavoidable in the IT industry, but it’s not something we have to go through alone: there is help out there. This panel talked openly about personal challenges with mental health and how, as an industry, we can deal with it.</p>
<p>Dana says that not everyone feels comfortable talking about mental health, but it seems to help if someone starts the conversation.
“I wanted to help open the door,” Dana says. “It’s something that we all need to be thinking about, even if we’re not ready to talk about it.”
Despite feeling out of her comfort zone, Dana got up on stage to speak, “for those who feel they can’t speak about it in front of others yet,” and she’s glad she did. Feedback was very positive and many wished the session was longer.
“People said they were sitting there in silence listening and taking in every word.”</p>
<p><strong>Technology for Everyone by <a href="https://twitter.com/PrototypeAlex" target="_blank" rel="noopener noreferrer">Alex Gibson</a></strong>
Alex gave an important talk about how the tech community needs to spread past its industry borders to make New Zealand a place where talent not only wants to live, but where talent can grow.</p>
<p>Alex’s inspiring talk covered the crusade to build an all-inclusive, diverse culture of technology for everyone, and we learnt how, as a tech company, we at <a href="https://abletech.nz/">Abletech</a> can help too.</p>
<p>See <a href="https://twitter.com/javascript_nz" target="_blank" rel="noopener noreferrer">conference tweets</a>.</p>
<p>Read more about the <a href="http://conference.javascript.org.nz/" target="_blank" rel="noopener noreferrer">conference topics and speakers</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Google Search Console and canonical pages</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Is Google dropping your pages, thinking they’re duplicates?</h2>
<p><a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a> shows how to see if Google are indexing your pages, or dropping them from the Google Search index. And what to do about it.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="j59q2c0oataujxr592fgjlv2" alt="" data-big=https://cms-assets.abletech.nz/large_1_A_Puiwioc2g_Z_Kqk4_Bp_GMR_2w_9de71c608c.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_A_Puiwioc2g_Z_Kqk4_Bp_GMR_2w_9de71c608c.png" srcset="https://cms-assets.abletech.nz/large_1_A_Puiwioc2g_Z_Kqk4_Bp_GMR_2w_9de71c608c.png 1000w, https://cms-assets.abletech.nz/small_1_A_Puiwioc2g_Z_Kqk4_Bp_GMR_2w_9de71c608c.png 500w, https://cms-assets.abletech.nz/medium_1_A_Puiwioc2g_Z_Kqk4_Bp_GMR_2w_9de71c608c.png 750w, https://cms-assets.abletech.nz/thumbnail_1_A_Puiwioc2g_Z_Kqk4_Bp_GMR_2w_9de71c608c.png 245w" data-zooming-width="1000" data-zooming-height="560" loading="lazy" width="1000" height="560"></figure>
</div>
<p>Google Search Console is a free service that helps you monitor and maintain your site’s presence in Google Search results. It helps you understand how Google views your site and helps you optimise its performance in search results.</p>
<p>Recently we noticed that the Google Search Console was identifying many of our pages as duplicates, and consequently dropping them from the Google Search index.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="atxemj1v8bp1e4fiwczo4fra" alt="" data-big=https://cms-assets.abletech.nz/large_1b_Xnb_GNTW_4_UV_Hc2c_XE_Qeqr_A_a770dc4c02.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1b_Xnb_GNTW_4_UV_Hc2c_XE_Qeqr_A_a770dc4c02.png" srcset="https://cms-assets.abletech.nz/large_1b_Xnb_GNTW_4_UV_Hc2c_XE_Qeqr_A_a770dc4c02.png 1000w, https://cms-assets.abletech.nz/small_1b_Xnb_GNTW_4_UV_Hc2c_XE_Qeqr_A_a770dc4c02.png 500w, https://cms-assets.abletech.nz/medium_1b_Xnb_GNTW_4_UV_Hc2c_XE_Qeqr_A_a770dc4c02.png 750w, https://cms-assets.abletech.nz/thumbnail_1b_Xnb_GNTW_4_UV_Hc2c_XE_Qeqr_A_a770dc4c02.png 245w" data-zooming-width="1000" data-zooming-height="562" loading="lazy" width="1000" height="562"></figure>
</div>
<p>In this video, <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a> shows how we identified the problem and the steps we took to resolve it.</p>
<p>Read more from <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a>:</p>
<ul>
<li>
<p><a href="https://abletech.nz/article/runscope-for-api-integration-tests">API integration testing using Runscope</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/how-to-configure-vs-code-to-format-elixir-code">Configure VS code to format Elixir code</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/travelling-with-a-sim-card-sticker">SIM card stickers</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/adding-docker-to-a-rails-application">Adding Docker to a Rails app</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Electric Bike Conversion</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3>Adding electric power to my bike has eliminated the barrier of steep hills. My travel range has extended and ebike is now my preferred method of transport. I enjoy quietly cruising in the open air without arriving sweaty. My ebike’s economical, friendly on the environment, and I can park right outside my destination.</h3>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="e31jn7biz2ce271romfgp6f2" alt="DIY electric bike conversion" data-big=https://cms-assets.abletech.nz/large_1a_V4_T5_Ube_Ndutl5_D_Ots_Qn_A_e44dbea455.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 190px" src="https://cms-assets.abletech.nz/1a_V4_T5_Ube_Ndutl5_D_Ots_Qn_A_e44dbea455.png" srcset="https://cms-assets.abletech.nz/large_1a_V4_T5_Ube_Ndutl5_D_Ots_Qn_A_e44dbea455.png 1000w, https://cms-assets.abletech.nz/small_1a_V4_T5_Ube_Ndutl5_D_Ots_Qn_A_e44dbea455.png 500w, https://cms-assets.abletech.nz/medium_1a_V4_T5_Ube_Ndutl5_D_Ots_Qn_A_e44dbea455.png 750w, https://cms-assets.abletech.nz/thumbnail_1a_V4_T5_Ube_Ndutl5_D_Ots_Qn_A_e44dbea455.png 190w" data-zooming-width="1000" data-zooming-height="821" loading="lazy" width="1000" height="821"></figure>
</div>
<p><em>DIY electric bike conversion</em></p>
<p>This conversion was a very welcome Christmas present from my husband Mike, and our children, who worked on the project together. Most of the fiddly work was done by Mike and his process is explained below. The kids understand how the new technology works and they’ve enjoyed being part of building a special one-off gift.</p>
<p><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item vimeo-player" type="text/html" width="640" height="390" src="https://player.vimeo.com/video/253342627" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div></p>
<h3>Turning a bike into an ebike</h3>
<p>Below are Mike’s details of the kit and the conversion process.</p>
<h3>Research</h3>
<p>We looked around at the options. We read a lot of forums about electric bike conversions and chatted with people who’d recently converted bikes. We decided that, since we live at the top of a Wellington hill, the best choice was a mid-mounted motor that powers the front crank. Other options included a hub motor that replaces one of the wheels.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zk8xwhmrguhxe4xd3z6pksws" alt="The mid-mounted motor keeps the weight in the centre of the bike" data-big=https://cms-assets.abletech.nz/large_16ue_K3_V8_TE_Nf_F_Nd_ITI_Mi6mg_0397855053.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/16ue_K3_V8_TE_Nf_F_Nd_ITI_Mi6mg_0397855053.jpeg" srcset="https://cms-assets.abletech.nz/large_16ue_K3_V8_TE_Nf_F_Nd_ITI_Mi6mg_0397855053.jpeg 1000w, https://cms-assets.abletech.nz/small_16ue_K3_V8_TE_Nf_F_Nd_ITI_Mi6mg_0397855053.jpeg 500w, https://cms-assets.abletech.nz/medium_16ue_K3_V8_TE_Nf_F_Nd_ITI_Mi6mg_0397855053.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_16ue_K3_V8_TE_Nf_F_Nd_ITI_Mi6mg_0397855053.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>The mid-mounted motor keeps the weight in the centre of the bike</em></p>
<h3>Motor placement</h3>
<p>A mid-mounted motor gives the advantage of the motor being able to leverage the gears. When you choose a gear, the motor assists you in that gear. With a hub motor it’s linked directly to the speed of the wheel. The mid-mounted motor also keeps the weight of the motor in the middle of the bike, so it’s better balanced, which is especially good off-road.</p>
<h3>Conversion kit</h3>
<p>There are a number of kit options available. It came down to buying from Ali Express, ie. directly from China, or we could buy a more locally supplied conversion kit. It can be hard to tell the quality of the kit or the battery pack when purchased from an unknown Chinese supplier. The availability of the battery pack was important too, because you can’t air freight lithium batteries.</p>
<p>We bought the conversion kit, battery and charger from <a href="http://luna-mate.com/" target="_blank" rel="noopener noreferrer">Luna Mate</a>. The motor and controller was shipped from Australia and the battery pack from within New Zealand. The 52V 13.5AH Shark battery pack can be charged, either on or off the bike, using the corresponding 52V charger.</p>
<p>It was just as cost-effective to buy from Luna Mate as it was to buy from Ali Express. The benefit was the peace-of-mind of dealing with an Australasian supplier. If we have any technical, warranty or return issues, they have a local support team.</p>
<p>The kit we used is <a href="http://luna-mate.com/parts/bbs02/" target="_blank" rel="noopener noreferrer">Bafang BBS02</a> and it comes complete with a motor and front crank, front chain ring, crank arms, brake handles, controller and a gear change sensor. We chose the biggest available battery pack that would fit within the frame.</p>
<p>A key decision was what size gear chain to get. With three options we went for the default 46-tooth chain ring. In hindsight this was probably a bit big for the gear set. To fix this we’ll get a replacement chain ring manufactured by <a href="https://lekkie.bike/" target="_blank" rel="noopener noreferrer">Lekkie</a>.</p>
<h3>Fitting the kit</h3>
<p>This was reasonably straight forward but it did need the right tools. We were fortunate to borrow a complete tool set from avid cyclist <a href="https://abletech.nz/about/#carl-penwarden">Carl Penwarden</a> who has <a href="https://stories.abletech.nz/etrixie-part-one-5546284cfd21" target="_blank" rel="noopener noreferrer">converted his VW Beetle car</a> to electric, but none of his bikes (yet!). Specifically, we needed a tool to remove the pedal crank arms and another one to undo the bottom bracket. Once that was done we took out the bottom bracket, and gear set, and bolted-in the motor and front gear set.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ytzufq7e47p9londpmqzm9ve" alt="The battery holder mount didn’t match the water-bottle mount" data-big=https://cms-assets.abletech.nz/large_1_Z_Jg3_Ge7u_KCO_7v_L_Ff_Vcsg2_Q_824b83b674.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Z_Jg3_Ge7u_KCO_7v_L_Ff_Vcsg2_Q_824b83b674.png" srcset="https://cms-assets.abletech.nz/large_1_Z_Jg3_Ge7u_KCO_7v_L_Ff_Vcsg2_Q_824b83b674.png 1000w, https://cms-assets.abletech.nz/small_1_Z_Jg3_Ge7u_KCO_7v_L_Ff_Vcsg2_Q_824b83b674.png 500w, https://cms-assets.abletech.nz/medium_1_Z_Jg3_Ge7u_KCO_7v_L_Ff_Vcsg2_Q_824b83b674.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Z_Jg3_Ge7u_KCO_7v_L_Ff_Vcsg2_Q_824b83b674.png 245w" data-zooming-width="1000" data-zooming-height="594" loading="lazy" width="1000" height="594"></figure>
</div>
<p><em>The battery holder mount didn’t match the water-bottle mount</em></p>
<p>One of the challenges was fitting the battery. The battery came with a well designed holder, but it assumed that the battery was mounted on the frame where the water-bottle holder would normally go. It also assumed where the holes for the holder were. This didn’t match our bike and you can’t buy an adapter.</p>
<p>We made an adapter bracket to mount the battery at the right place on the frame. We created it from a 25x3mm steel strip. We bolted that to the bike first, then to the battery holder. We primed the adapter in a coat of a rust preventer. We also covered it in heat shrink to protect it from scratches and bumps. This step took the most time of the whole conversion.</p>
<p>With the parts all sorted we mounted a new brake lever. Only one lever could be mounted because the Shimano gears had the gear change built into the brake lever so we couldn’t remove the lever that controlled the back gears. If we did that there would be no way to control the gears.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="scnrerephwi0lkuobky1ez4g" alt="New lever on the front brakes shuts off the motor" data-big=https://cms-assets.abletech.nz/large_1c_Gg_C_Gf_Y71k_ZC_2d_VC_Kwoh8_A_4a856ce235.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1c_Gg_C_Gf_Y71k_ZC_2d_VC_Kwoh8_A_4a856ce235.png" srcset="https://cms-assets.abletech.nz/large_1c_Gg_C_Gf_Y71k_ZC_2d_VC_Kwoh8_A_4a856ce235.png 1000w, https://cms-assets.abletech.nz/small_1c_Gg_C_Gf_Y71k_ZC_2d_VC_Kwoh8_A_4a856ce235.png 500w, https://cms-assets.abletech.nz/medium_1c_Gg_C_Gf_Y71k_ZC_2d_VC_Kwoh8_A_4a856ce235.png 750w, https://cms-assets.abletech.nz/thumbnail_1c_Gg_C_Gf_Y71k_ZC_2d_VC_Kwoh8_A_4a856ce235.png 245w" data-zooming-width="1000" data-zooming-height="607" loading="lazy" width="1000" height="607"></figure>
</div>
<p><em>New lever on the front brakes shuts off the motor</em></p>
<p>We attached the Bafang gear brake lever to the front brake because it has a cut-off switch built into it. This means that when you apply the front brake, the motor automatically cuts out. This prevents the motor powering the rider over the handle bars.</p>
<p>Next we mounted the controller and LCD display onto the front crossbar. We plugged-in all the cables and tidied things up with cable-ties, being careful to leave plenty of movement for both general use and turning extremes.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jsd3whnfh0zi0cy1f7ilpxs8" alt="Cabling and connectors are tucked up inside the loom" data-big=https://cms-assets.abletech.nz/large_1m7_BJ_Yecxo_Eson_QF_Fdn32lw_7f00879fe8.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1m7_BJ_Yecxo_Eson_QF_Fdn32lw_7f00879fe8.png" srcset="https://cms-assets.abletech.nz/large_1m7_BJ_Yecxo_Eson_QF_Fdn32lw_7f00879fe8.png 1000w, https://cms-assets.abletech.nz/small_1m7_BJ_Yecxo_Eson_QF_Fdn32lw_7f00879fe8.png 500w, https://cms-assets.abletech.nz/medium_1m7_BJ_Yecxo_Eson_QF_Fdn32lw_7f00879fe8.png 750w, https://cms-assets.abletech.nz/thumbnail_1m7_BJ_Yecxo_Eson_QF_Fdn32lw_7f00879fe8.png 245w" data-zooming-width="1000" data-zooming-height="570" loading="lazy" width="1000" height="570"></figure>
</div>
<p><em>Cabling and connectors are tucked up inside the loom</em></p>
<p>We wanted the cable connections to be protected from the weather and from knocks. We used some <a href="https://www.jaycar.co.nz/search?text=split+loom+tube+cable+tidy&amp;CSRFToken=5301d090-950c-46d7-bf9e-cf99fea35f04" target="_blank" rel="noopener noreferrer">plastic split loom tubes purchased from Jaycar</a>, and put the cables/connectors inside it, to hide connectors and loose cables. This was particularly useful for the battery cable which is thick and has big connectors. To cover the end of the loom tube we found a 25mm round plastic furniture foot at Bunnings. It stops water running down the tube. The result is tidy and weather-resistant.</p>
<h3>Test ride</h3>
<p>After charging the battery pack, using the charger ordered with the kit, we took it for a test ride. It worked well the first time!</p>
<p>We’re still to sort-out the battery charge indicator on the display. It assumes a 48V battery and we used a 52V battery so the percentage charge indicator always shows the battery being more charged than it actually is. As an interim measure we turned on the battery voltage reading so you can see the actual battery voltage. We added a label to help figure-out how charged the battery is.</p>
<h3>Charging</h3>
<p>The charger is good but it has a reasonably noisy fan. It takes a few hours to recharge the battery. With daily use we’re charging the ebike about once a week.</p>
<h3>Reflections on the conversion</h3>
<p>One of the optional extras we bought was the <a href="http://luna-mate.com/bottom-bracket-removal-tool/" target="_blank" rel="noopener noreferrer">Luna Mate tool</a> for doing up the bolts to mount the motor. This was good because it was reasonably cheap and fits the new bottom bracket nut perfectly. After the first few weeks of use, the bike’s bottom bracket mount had loosened and it needed to be retightened using this tool. It’s a spanner, not a socket, so it can’t be attached to a torque wrench to tighten the bottom bracket nut up to the right torque level, so you have to guess the torque.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s99i3g433wi3ihhdiiooa5bi" alt="Specific tools made the job much easier" data-big=https://cms-assets.abletech.nz/large_1e_N5u6_RWUD_3c_w_M6d_Rgkj5w_ad003cf4e7.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 215px" src="https://cms-assets.abletech.nz/1e_N5u6_RWUD_3c_w_M6d_Rgkj5w_ad003cf4e7.png" srcset="https://cms-assets.abletech.nz/large_1e_N5u6_RWUD_3c_w_M6d_Rgkj5w_ad003cf4e7.png 1000w, https://cms-assets.abletech.nz/small_1e_N5u6_RWUD_3c_w_M6d_Rgkj5w_ad003cf4e7.png 500w, https://cms-assets.abletech.nz/medium_1e_N5u6_RWUD_3c_w_M6d_Rgkj5w_ad003cf4e7.png 750w, https://cms-assets.abletech.nz/thumbnail_1e_N5u6_RWUD_3c_w_M6d_Rgkj5w_ad003cf4e7.png 215w" data-zooming-width="1000" data-zooming-height="724" loading="lazy" width="1000" height="724"></figure>
</div>
<p><em>Specific tools made the job much easier</em></p>
<p>Having the right tools made the job really easy. When we needed to retighten the bottom bracket we purchased a crank arm puller because we’ll probably need to tighten these up again in the future.</p>
<p>The motor has a theoretical maximum output of around 750w and can be power-limited to the NZ electric bike assist power limit of 300w via the motor control display on the bike.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qxtuiqc47gjfv7dudntefkj5" alt="" data-big=https://cms-assets.abletech.nz/large_1r3b_Odcu_Gxc_ENNW_Rn_Xg_KETQ_13e7c445d8.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 216px" src="https://cms-assets.abletech.nz/1r3b_Odcu_Gxc_ENNW_Rn_Xg_KETQ_13e7c445d8.png" srcset="https://cms-assets.abletech.nz/large_1r3b_Odcu_Gxc_ENNW_Rn_Xg_KETQ_13e7c445d8.png 1000w, https://cms-assets.abletech.nz/small_1r3b_Odcu_Gxc_ENNW_Rn_Xg_KETQ_13e7c445d8.png 500w, https://cms-assets.abletech.nz/medium_1r3b_Odcu_Gxc_ENNW_Rn_Xg_KETQ_13e7c445d8.png 750w, https://cms-assets.abletech.nz/thumbnail_1r3b_Odcu_Gxc_ENNW_Rn_Xg_KETQ_13e7c445d8.png 216w" data-zooming-width="1000" data-zooming-height="722" loading="lazy" width="1000" height="722"></figure>
</div>
<p>We were pleased that we bought a good quality conversion kit. It was well put together, came with everything we needed, and it has a warranty. For something so heavy, the shipping costs of returning items effectively negate any warranty for a kit from China.</p>
<p>The ebike is quick, reliable and beautiful to ride. It’s a pleasure to use and the kids love borrowing it. The conversion was a fairly easy project and it’s been a great Christmas gift that is used every day.</p>
<p><em>~Jo Wilson</em></p>
<h3>Read more:</h3>
<ul>
<li>
<p><a href="https://vimeo.com/253342627" target="_blank" rel="noopener noreferrer">Take a video-tour of the ebike</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/etrixie-vlog">Watch a video of another electric conversion: Volkswagen Beetle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/etrixie-part-one">Journey start to finish on the VW Beetle conversion starting here</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Electrifying the vintage VW Beetle</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Listen to Kathryn Ryan’s RNZ interview with Carl Penwarden or read the RNZ article below.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="obd37fkfpaba4xuhi6k5464x" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_1_Er_Y_Lxn5cz_RM_Fj_H8c_WQQ_55d123ac9a.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Er_Y_Lxn5cz_RM_Fj_H8c_WQQ_55d123ac9a.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_Er_Y_Lxn5cz_RM_Fj_H8c_WQQ_55d123ac9a.png 245w" data-zooming-width="245" data-zooming-height="42" loading="lazy" width="245" height="42"></figure>
</div>
<p><em><a href="https://www.rnz.co.nz/audio/player?audio_id=201856496" target="_blank" rel="noopener noreferrer">Listen to Carl Penwarden on Nine to Noon</a></em></p>
<p>Volkswagens have been a lifelong love affair for Carl Penwarden. So much so that he’s just brought his 1965 Beetle, named Trixie, into the current day by converting it to 100 percent electric.</p>
<p>He began the mission late last year, stripping the old vehicle of its motor and getting stuck into the mechanics, and managing to document the entire journey with his camera.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="f8hvhg22905yjeu9212hprzw" alt="" data-big=https://cms-assets.abletech.nz/medium_0_hq_OF_Wvz_B5ko7pk_N_fb6ee6513f.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_hq_OF_Wvz_B5ko7pk_N_fb6ee6513f.jpeg" srcset="https://cms-assets.abletech.nz/small_0_hq_OF_Wvz_B5ko7pk_N_fb6ee6513f.jpeg 500w, https://cms-assets.abletech.nz/medium_0_hq_OF_Wvz_B5ko7pk_N_fb6ee6513f.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_hq_OF_Wvz_B5ko7pk_N_fb6ee6513f.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kllpws6dgn56wktweehurzef" alt="" data-big=https://cms-assets.abletech.nz/medium_0_v_Nxw_V7sv77y_Paxr_W_0700f9e01e.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_v_Nxw_V7sv77y_Paxr_W_0700f9e01e.jpeg" srcset="https://cms-assets.abletech.nz/small_0_v_Nxw_V7sv77y_Paxr_W_0700f9e01e.jpeg 500w, https://cms-assets.abletech.nz/medium_0_v_Nxw_V7sv77y_Paxr_W_0700f9e01e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_v_Nxw_V7sv77y_Paxr_W_0700f9e01e.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l9eg0v7jmn75k7obcqvgfay1" alt="" data-big=https://cms-assets.abletech.nz/medium_0_N_Na_LSPCEYO_2_RS_5_Ke_20dfa49a98.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_N_Na_LSPCEYO_2_RS_5_Ke_20dfa49a98.jpeg" srcset="https://cms-assets.abletech.nz/small_0_N_Na_LSPCEYO_2_RS_5_Ke_20dfa49a98.jpeg 500w, https://cms-assets.abletech.nz/medium_0_N_Na_LSPCEYO_2_RS_5_Ke_20dfa49a98.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_N_Na_LSPCEYO_2_RS_5_Ke_20dfa49a98.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
</div>
<p>It came about from wanting to find a way to make the car relevant for his young daughter, Abby, after contemplating whether or not the petrol car would be relevant by the time she learns to drive.</p>
<p>His grandfather and father had Volkswagens and now he has upgraded his 1965 Beetle, named Trixie, converting it to 100 percent electric.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q1zyrgw45h7vukjzvuthwls3" alt="" data-big=https://cms-assets.abletech.nz/medium_0_8u_L_Rkl_Qg9m4_MQG_0_T_75628168f7.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_8u_L_Rkl_Qg9m4_MQG_0_T_75628168f7.jpeg" srcset="https://cms-assets.abletech.nz/small_0_8u_L_Rkl_Qg9m4_MQG_0_T_75628168f7.jpeg 500w, https://cms-assets.abletech.nz/medium_0_8u_L_Rkl_Qg9m4_MQG_0_T_75628168f7.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_8u_L_Rkl_Qg9m4_MQG_0_T_75628168f7.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k3ntyocvubdh34jjweevuci5" alt="" data-big=https://cms-assets.abletech.nz/medium_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg" srcset="https://cms-assets.abletech.nz/small_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg 500w, https://cms-assets.abletech.nz/medium_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k3ntyocvubdh34jjweevuci5" alt="" data-big=https://cms-assets.abletech.nz/medium_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg" srcset="https://cms-assets.abletech.nz/small_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg 500w, https://cms-assets.abletech.nz/medium_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_l_Uf_G1_H_Yex6s_Xa_F_608eaf9cf9.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
</div>
<p>Penwarden, who is an electrical engineer, began late last year stripping the old motor out of the vehicle and getting stuck into the mechanics — and documenting the entire journey.</p>
<p>His dad helped him essentially rebuild his first Beetle, a 1958 model, from the ground up.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="aik2208oi0fq5rurl2maob2a" alt="" data-big=https://cms-assets.abletech.nz/small_0_Ka_Ag_M1_E_Cpb_Ao_Pze6_59714d3830.jpeg sizes="(min-width: 768px) 375px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/0_Ka_Ag_M1_E_Cpb_Ao_Pze6_59714d3830.jpeg" srcset="https://cms-assets.abletech.nz/small_0_Ka_Ag_M1_E_Cpb_Ao_Pze6_59714d3830.jpeg 375w, https://cms-assets.abletech.nz/thumbnail_0_Ka_Ag_M1_E_Cpb_Ao_Pze6_59714d3830.jpeg 117w" data-zooming-width="375" data-zooming-height="500" loading="lazy" width="375" height="500"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="iub9na6a5ym8bz0kidfi9x53" alt="" data-big=https://cms-assets.abletech.nz/medium_0_O6_J_Kr3_O_Kvid3_JDUN_7a2b398dac.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_O6_J_Kr3_O_Kvid3_JDUN_7a2b398dac.jpeg" srcset="https://cms-assets.abletech.nz/small_0_O6_J_Kr3_O_Kvid3_JDUN_7a2b398dac.jpeg 500w, https://cms-assets.abletech.nz/medium_0_O6_J_Kr3_O_Kvid3_JDUN_7a2b398dac.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_O6_J_Kr3_O_Kvid3_JDUN_7a2b398dac.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
</div>
<p>Now he found he wanted to find a way to make the car relevant for his young daughter, Abby, after contemplating whether petrol cars would even exist by the time she learnt to drive.</p>
<p>He said he bought the electrical components from a US company and then kitted out the car according to New Zealand modification standards.</p>
<p>“A great thing about a Beetle is that they are a relatively simple car.</p>
<p>“They have mechanical breaks, mechanical steering, so the systems that had to be converted to electric were a relatively small number.”</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="geqslg6szssdcr050xdha9q1" alt="" data-big=https://cms-assets.abletech.nz/medium_0_If2_KR_65d_Bk_H_NV_Yl_d29b7f5ed1.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_If2_KR_65d_Bk_H_NV_Yl_d29b7f5ed1.jpeg" srcset="https://cms-assets.abletech.nz/small_0_If2_KR_65d_Bk_H_NV_Yl_d29b7f5ed1.jpeg 500w, https://cms-assets.abletech.nz/medium_0_If2_KR_65d_Bk_H_NV_Yl_d29b7f5ed1.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_If2_KR_65d_Bk_H_NV_Yl_d29b7f5ed1.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
</div>
<p>He said he installed 37 battery cells that are connected to a 53-kilowatt AC electric motor — which is slightly more powerful than the original engine.</p>
<p>Overall the car is about 200kg heavier than before it was modified, but Volkswagens are pretty light vehicles so it still weighs under a tonne, Penwarden says.</p>
<p>And while the car has lost that distinctive Beetle sound — a kind of rough metallic purr — he says it means he can now actually hold a conversation with his daughter while driving.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="r9wny6ofs8ary6o3xbf2wsj9" alt="" data-big=https://cms-assets.abletech.nz/medium_0_Ew_D1m_S7bh_Qq0ooq_G_20a373bc8e.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_Ew_D1m_S7bh_Qq0ooq_G_20a373bc8e.jpeg" srcset="https://cms-assets.abletech.nz/small_0_Ew_D1m_S7bh_Qq0ooq_G_20a373bc8e.jpeg 500w, https://cms-assets.abletech.nz/medium_0_Ew_D1m_S7bh_Qq0ooq_G_20a373bc8e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_0_Ew_D1m_S7bh_Qq0ooq_G_20a373bc8e.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
</div>
<p><a href="https://vimeo.com/216595978" target="_blank" rel="noopener noreferrer">Watch a video walk-through of the electric beetle.</a></p>
<p>Each time he charges Trixie it costs about $2.50 and gets him 130km around town.</p>
<p>A dollar-for-dollar comparison to 91 petrol comes out at about 83km per litre, whereas a normal car gets about 7km to 10km per litre, Penwarden says.</p>
<p>Trixie takes about 9 hours to charge, but can be plugged into a standard power point just about anywhere, Penwarden says.</p>
<p>“It’s so much fun to drive now, it feels a lot nicer.</p>
<p>“I was away this weekend and when I got home … I said to my wife ‘I’ve just got to take Trixie for a bit of a drive just to put a smile on my face.”</p>
<p><em>Originally published at <a href="http://www.radionz.co.nz/national/programmes/ninetonoon/audio/201856496/electrifying-the-vintage-vw-beetle" target="_blank" rel="noopener noreferrer">www.radionz.co.nz</a> on August 28, 2017.</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Enspiral Dev Academy meetup</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>TypeScript/JavaScri­pt/Node.js­ workshop</h2>
<p>A big thank you to <a href="https://twitter.com/ndorfin" target="_blank" rel="noopener noreferrer">Shaun O'Connell</a> from Abletech for giving a Tech Talk recently. Shaun’s been enjoying the latest Enspiral Dev Academy meetups and gave us some feedback on what he found interesting and useful after the first one about using TypeScript.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k1oc1v0ze9lb1n9oy9qfd14a" alt="" data-big=https://cms-assets.abletech.nz/large_1_Vhiv_Uk_E9_Nr2b6_VK_8_AK_Ag6g_1a715bc60f.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Vhiv_Uk_E9_Nr2b6_VK_8_AK_Ag6g_1a715bc60f.png" srcset="https://cms-assets.abletech.nz/large_1_Vhiv_Uk_E9_Nr2b6_VK_8_AK_Ag6g_1a715bc60f.png 1000w, https://cms-assets.abletech.nz/small_1_Vhiv_Uk_E9_Nr2b6_VK_8_AK_Ag6g_1a715bc60f.png 500w, https://cms-assets.abletech.nz/medium_1_Vhiv_Uk_E9_Nr2b6_VK_8_AK_Ag6g_1a715bc60f.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Vhiv_Uk_E9_Nr2b6_VK_8_AK_Ag6g_1a715bc60f.png 245w" data-zooming-width="1000" data-zooming-height="632" loading="lazy" width="1000" height="632"></figure>
</div>
<p>Shaun explained that Zoltan from Enspiral Dev Academy (EDA) is running through a great programme. He encouraged us to go along to learn more.</p>
<p>Check out the <a href="https://www.meetup.com/Enspiral-Dev-Academy-Meetup/" target="_blank" rel="noopener noreferrer">EDA meetup dates and details</a> or top up on your tech with another Abletech Tech Talk:</p>
<ul>
<li>
<p><a href="https://stories.abletech.nz/article/elixir-for-addressfinder" target="_blank" rel="noopener noreferrer">Elixir</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/rails-girls-workshops">Running a Rails Girls weekend</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/check-out-turbolinks">Turbolinks</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/controlling-midi-hardware-from-the-browser">Running MIDI hardware from your browser</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Dominion Post — Motoring Plus</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The electric conversion of Carl Penwarden’s 1965 VW Beetle was featured in this weekend’s newspapers.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lmaursv94jj89th9o4rk019x" alt="" data-big=https://cms-assets.abletech.nz/large_1_Az6_Ex3o_Eagw_Dycrr_sbptw_9c25b26a98.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 214px" src="https://cms-assets.abletech.nz/1_Az6_Ex3o_Eagw_Dycrr_sbptw_9c25b26a98.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Az6_Ex3o_Eagw_Dycrr_sbptw_9c25b26a98.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Az6_Ex3o_Eagw_Dycrr_sbptw_9c25b26a98.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Az6_Ex3o_Eagw_Dycrr_sbptw_9c25b26a98.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Az6_Ex3o_Eagw_Dycrr_sbptw_9c25b26a98.jpeg 214w" data-zooming-width="1000" data-zooming-height="728" loading="lazy" width="1000" height="728"></figure>
</div>
<p>Read the <a href="http://www.stuff.co.nz/motoring/customs-classics/93586450/turning-trixie-from-a-vdub-into-an-edub" target="_blank" rel="noopener noreferrer">article in the Dominion Post</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ywadqsx5eklituhtulxw1ylp" alt="" data-big=https://cms-assets.abletech.nz/small_1d3k_K_Rh_K_ew26bc1ig5a_S_Xg_993d937852.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1d3k_K_Rh_K_ew26bc1ig5a_S_Xg_993d937852.png" srcset="https://cms-assets.abletech.nz/small_1d3k_K_Rh_K_ew26bc1ig5a_S_Xg_993d937852.png 500w, https://cms-assets.abletech.nz/thumbnail_1d3k_K_Rh_K_ew26bc1ig5a_S_Xg_993d937852.png 245w" data-zooming-width="500" data-zooming-height="281" loading="lazy" width="500" height="281"></figure>
</div>
<p>Watch a <a href="https://abletech.nz/article/etrixie-vlog">video of the eDub</a> and read through the steps involved in converting the VW from a petrol motor to 100% electric.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Carbon Zero electricity</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The Abletech office is now running on 100% renewable electricity</h2>
<p>We’re proud to be using renewable and carboNZero certified electricity, supplied by Ecotricity, at our Abletech headquarters.</p>
<p>Each year, we’re expecting to avoid carbon offsets of 14,812kgs / CO2 from electricity.</p>
<p>Switching was easy; Ecotricity’s quote was convincing and they handled the rest.</p>
<h3>Why did we switch to Ecotricity?</h3>
<ul>
<li>
<p>100% renewable energy</p>
</li>
<li>
<p>Competitive pricing</p>
</li>
<li>
<p>Save CO2</p>
</li>
<li>
<p>100% Kiwi owned and 50% community owned</p>
</li>
</ul>
<h3>Where does Ecotricity’s electricity come from?</h3>
<p><a href="https://ecotricity.co.nz/" target="_blank" rel="noopener noreferrer">Ecotricity</a> sources electricity from wind, hydro and solar electricity generation.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ti9wygjlorgqagjk8kjdrytj" alt="Electricity emissions"  sizes="" src="https://cms-assets.abletech.nz/1c_X7ia_Ujj6j_BCQG_Amg_UM_Cd_Q_1251b85011.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>Electricity emissions</em></p>
<h3>Read more</h3>
<ul>
<li>
<p>How we figure out Abletech’s <a href="https://abletech.nz/article/abletech-and-co2-emissions-growing-our-understanding">CO2 emissions</a></p>
</li>
<li>
<p>Do <a href="https://abletech.nz/article/ev-q-a-do-the-cars-cost-more-than-petrol-cars">EV cars cost more than petrol cars</a>?</p>
</li>
<li>
<p>Adding <a href="https://abletech.nz/article/etrixie-vlog">electric power to a VW Beetle</a></p>
</li>
<li>
<p>Adding <a href="https://abletech.nz/article/electric-bike-conversion">electric power to a bicycle</a></p>
</li>
<li>
<p>Keeping <a href="https://abletech.nz/article/ecocentre">warm and sustainable</a> in winter</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Content Security Policy</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Find out more about the Content Security Policy. It allows website owners to declare approved content origins that browsers should be allowed to load</h2>
<p>The Content Security Policy is a relatively new mechanism to restrict outgoing requests and resources, that a web page refers to.</p>
<p>It’s a relatively new http header, that has some security benefits, if you’re in a secure domain.</p>
<p>There’s a lot of complexity to the Policy — find out relevant parts in the 10 minute video above, from <a href="https://addressfinder.nz/" target="_blank" rel="noopener noreferrer">AddressFinder</a> Technical Lead, <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel Ramsay</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qlr08rmdf34vhblt4x2us4nh" alt="" data-big=https://cms-assets.abletech.nz/large_1ce4ka_z_C2_I_Jx_AH_Nd_Z4j8w_A_155cd1d863.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1ce4ka_z_C2_I_Jx_AH_Nd_Z4j8w_A_155cd1d863.png" srcset="https://cms-assets.abletech.nz/large_1ce4ka_z_C2_I_Jx_AH_Nd_Z4j8w_A_155cd1d863.png 1000w, https://cms-assets.abletech.nz/small_1ce4ka_z_C2_I_Jx_AH_Nd_Z4j8w_A_155cd1d863.png 500w, https://cms-assets.abletech.nz/medium_1ce4ka_z_C2_I_Jx_AH_Nd_Z4j8w_A_155cd1d863.png 750w, https://cms-assets.abletech.nz/thumbnail_1ce4ka_z_C2_I_Jx_AH_Nd_Z4j8w_A_155cd1d863.png 245w" data-zooming-width="1000" data-zooming-height="620" loading="lazy" width="1000" height="620"></figure>
</div>
<h3>Read more from Nigel:</h3>
<ul>
<li>
<p><a href="https://abletech.nz/resource/how-to-configure-vs-code-to-format-elixir-code/">How to configure VS code to format Elixir code</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/adding-docker-to-a-rails-application">Adding Docker to a Rails app</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Controlling MIDI hardware from the browser</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Check out this Abletech Tech Talk on how to control MIDI hardware from your browser. Kalo’s built a web-based, matrix-style MIDI sequencer. Watch the video for the details, and the sounds 🎶</h2>
<h3>WebStep</h3>
<p>The purpose of Kalo’s app is to control external hardware synthesizers or samplers. WebStep should work fine with <em>any</em> MIDI enabled synthesizer or sampler that’s visible to your operating system.</p>
<h3>How does it work?</h3>
<p>MIDI is a messaging protocol so you hook-up devices, to each other, and send MIDI Messages over that link.</p>
<p>MIDI allows for an instrument/device to use up to 16 channels; messages are sent to a device and target a specific channel.</p>
<p>Messages are able to communicate all sorts of things, for example:</p>
<ul>
<li>
<p>NoteOn / NoteOff</p>
</li>
<li>
<p>Synthesizer parameter changes</p>
</li>
<li>
<p>Timing information (MIDI Clock)</p>
</li>
</ul>
<p>Messages are hexadecimal and made up of three bytes.</p>
<h3>Tech stuff</h3>
<ul>
<li>
<p>WebMIDI API</p>
</li>
<li>
<p>React / Redux</p>
</li>
<li>
<p>JS / ES6 + Babel + Webpack</p>
</li>
<li>
<p>Node</p>
</li>
<li>
<p>Gulp</p>
</li>
<li>
<p>UI Library: material-ui</p>
</li>
</ul>
<h3>Conclusions</h3>
<ul>
<li>
<p>React is great!</p>
</li>
<li>
<p>Redux is great!</p>
</li>
<li>
<p>The WebMIDI API is great!</p>
</li>
<li>
<p>Timing is hard</p>
</li>
<li>
<p>Webpack config isn’t much fun</p>
</li>
<li>
<p>I need to learn about styling and layouts</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tfpv0eq58xi5b9uq1kxrurx7" alt="" data-big=https://cms-assets.abletech.nz/large_1q_Pa_V0gux_Ekjm9_Yb_Ysm_P_Wjg_0cd300aa0b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1q_Pa_V0gux_Ekjm9_Yb_Ysm_P_Wjg_0cd300aa0b.jpeg" srcset="https://cms-assets.abletech.nz/large_1q_Pa_V0gux_Ekjm9_Yb_Ysm_P_Wjg_0cd300aa0b.jpeg 1000w, https://cms-assets.abletech.nz/small_1q_Pa_V0gux_Ekjm9_Yb_Ysm_P_Wjg_0cd300aa0b.jpeg 500w, https://cms-assets.abletech.nz/medium_1q_Pa_V0gux_Ekjm9_Yb_Ysm_P_Wjg_0cd300aa0b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1q_Pa_V0gux_Ekjm9_Yb_Ysm_P_Wjg_0cd300aa0b.jpeg 245w" data-zooming-width="1000" data-zooming-height="598" loading="lazy" width="1000" height="598"></figure>
</div>
<h3>Future plans</h3>
<p><strong>Version 2</strong></p>
<ul>
<li>
<p>UI re-design to accommodate new features</p>
</li>
<li>
<p>Additional grid tools</p>
</li>
<li>
<p>Optimisations — overhaul data structure and data/component bindings</p>
</li>
<li>
<p>Per-grid swing settings and step resolution (polyrhythms!)</p>
</li>
</ul>
<p><strong>Version 3 and beyond…</strong></p>
<ul>
<li>
<p>Firebase Auth and Persistence (save sessions, grid presets etc)</p>
</li>
<li>
<p>WebAudio API &gt;&gt;Synthesizer Engine</p>
</li>
<li>
<p>Longer, more varied sequences</p>
</li>
</ul>
<p>Head over to Kalo’s <a href="https://github.com/kalopilato/webstep_midi_sequencer" target="_blank" rel="noopener noreferrer">WebStep github page</a> for more detail on his web-based, matrix-style MIDI sequencer.</p>
<p>Catch another Abletech Tech Talk on <a href="https://abletech.nz/article/rails-girls-workshops">Rails Girls</a>, <a href="https://abletech.nz/article/check-out-turbolinks">Turbolinks</a> or <a href="https://abletech.nz/article/elixir-for-addressfinder">Learning Elixir</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>❤️ Could you restart a heart right now?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Where is your nearest AED? There’s an app for that ⚡</h2>
<h3>Where can I find an AED?</h3>
<p>Locate your nearest AED free here:</p>
<ul>
<li>
<p><a href="https://aedlocations.co.nz/" target="_blank" rel="noopener noreferrer">AED Locations website</a></p>
</li>
<li>
<p><a href="https://itunes.apple.com/nz/app/aed-locations/id424094430?mt=8" target="_blank" rel="noopener noreferrer">AED Locations app on iTunes</a></p>
</li>
<li>
<p><a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations" target="_blank" rel="noopener noreferrer">Google Play app</a></p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pqmant821zzkm38h7oqou2nl" alt="" data-big=https://cms-assets.abletech.nz/large_172_YM_Tj_Cn_Iq1_BKWQ_Fo_Dt_Yg_f7e5ef98c9.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 216px" src="https://cms-assets.abletech.nz/172_YM_Tj_Cn_Iq1_BKWQ_Fo_Dt_Yg_f7e5ef98c9.png" srcset="https://cms-assets.abletech.nz/large_172_YM_Tj_Cn_Iq1_BKWQ_Fo_Dt_Yg_f7e5ef98c9.png 1000w, https://cms-assets.abletech.nz/small_172_YM_Tj_Cn_Iq1_BKWQ_Fo_Dt_Yg_f7e5ef98c9.png 500w, https://cms-assets.abletech.nz/medium_172_YM_Tj_Cn_Iq1_BKWQ_Fo_Dt_Yg_f7e5ef98c9.png 750w, https://cms-assets.abletech.nz/thumbnail_172_YM_Tj_Cn_Iq1_BKWQ_Fo_Dt_Yg_f7e5ef98c9.png 216w" data-zooming-width="1000" data-zooming-height="722" loading="lazy" width="1000" height="722"></figure>
</div>
<h3>What’s an AED?</h3>
<p>An Automated External Defibrillators (AED) deliver a short, powerful electric shock to help a heart regain its natural rhythm.</p>
<p>An AED is used to help anyone whose heart suddenly stops pumping. A person in cardiac arrest will fall unconscious and will not be breathing normally. Seconds count. An AED will increase someone’s chance of survival by up to 80% if applied immediately.</p>
<p>AEDs can be used with no training. The machine will tell you what to do and it will not allow a shock to be delivered unless appropriate.</p>
<p>Find out more about <a href="http://restartaheart.co.nz/" target="_blank" rel="noopener noreferrer">Restart a Heart Day</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wnojwtrhbeno2u4i9kawd116" alt="" data-big=https://cms-assets.abletech.nz/small_1_UW_6_MV_3_Uo_I6_B1_Bqmwh_Gd_NGA_c0897f424f.png sizes="(min-width: 768px) 500px, (min-width: 640px) 170px" src="https://cms-assets.abletech.nz/1_UW_6_MV_3_Uo_I6_B1_Bqmwh_Gd_NGA_c0897f424f.png" srcset="https://cms-assets.abletech.nz/small_1_UW_6_MV_3_Uo_I6_B1_Bqmwh_Gd_NGA_c0897f424f.png 500w, https://cms-assets.abletech.nz/thumbnail_1_UW_6_MV_3_Uo_I6_B1_Bqmwh_Gd_NGA_c0897f424f.png 170w" data-zooming-width="500" data-zooming-height="458" loading="lazy" width="500" height="458"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Automate with Selenium</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Track your personal spending and ease the task of budgeting</h2>
<p>Cam had a problem with his budgeting software. It doesn’t support banks in New Zealand, and the banks don’t give access to internal APIs.</p>
<p>His new self-built system gets his bank transactions, and imports them for classification, so he can see what he spends and where.</p>
<p>Check out Cam’s demo of his Selenium Raspberry Pi automation that helps him manage his personal banking data.</p>
<p>Credit for the code goes to <a href="https://github.com/jordancrawfordnz/anz-goodbudget-automated-import" target="_blank" rel="noopener noreferrer">Jordan Crawford</a>.</p>
<p>Read some Vim articles from Cameron:</p>
<ul>
<li>
<p><a href="https://abletech.nz/resource/get-rid-of-your-vim-plugin-manager/">Get rid of your Vim plugin manager</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/a-quick-replace-in-vim/">A quick replace in Vim</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/vims-most-magical-autocomplete/">Vim’s most magical autocomplete</a></p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hck5phxlcfxb1jcj386rlolg" alt="" data-big=https://cms-assets.abletech.nz/large_17s_J_Zy4t_Q_Df7prpe_Hxcgop_Q_83bb5ce0de.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/17s_J_Zy4t_Q_Df7prpe_Hxcgop_Q_83bb5ce0de.png" srcset="https://cms-assets.abletech.nz/large_17s_J_Zy4t_Q_Df7prpe_Hxcgop_Q_83bb5ce0de.png 1000w, https://cms-assets.abletech.nz/small_17s_J_Zy4t_Q_Df7prpe_Hxcgop_Q_83bb5ce0de.png 500w, https://cms-assets.abletech.nz/medium_17s_J_Zy4t_Q_Df7prpe_Hxcgop_Q_83bb5ce0de.png 750w, https://cms-assets.abletech.nz/thumbnail_17s_J_Zy4t_Q_Df7prpe_Hxcgop_Q_83bb5ce0de.png 245w" data-zooming-width="1000" data-zooming-height="604" loading="lazy" width="1000" height="604"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>An alternative to service objects</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Let’s broaden our Rails toolbox</h2>
<p><strong>Service objects are overused</strong>. They have become the default solution for any new features in a Rails codebase. They’re also hard to talk about as they mean different things for different people. Here is how I define them:</p>
<ul>
<li>
<p>Any class which has one public <code>#perform</code> or <code>#call</code> method</p>
</li>
<li>
<p>Any class whose name describes an action</p>
</li>
<li>
<p>Example: <code>PublishApprovedPost.perform(post: @post)</code></p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jetdwj0sud3wt3ejkrm3qwb2" alt="Photo by [Dmitry Ratushny](https://unsplash.com/@ratushny?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/hope?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)" data-big=https://cms-assets.abletech.nz/large_1g_hz_Ftzjd0_Qn_H_u_Ugh3hmw_0786546d1b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 235px" src="https://cms-assets.abletech.nz/1g_hz_Ftzjd0_Qn_H_u_Ugh3hmw_0786546d1b.jpeg" srcset="https://cms-assets.abletech.nz/large_1g_hz_Ftzjd0_Qn_H_u_Ugh3hmw_0786546d1b.jpeg 1000w, https://cms-assets.abletech.nz/small_1g_hz_Ftzjd0_Qn_H_u_Ugh3hmw_0786546d1b.jpeg 500w, https://cms-assets.abletech.nz/medium_1g_hz_Ftzjd0_Qn_H_u_Ugh3hmw_0786546d1b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1g_hz_Ftzjd0_Qn_H_u_Ugh3hmw_0786546d1b.jpeg 235w" data-zooming-width="1000" data-zooming-height="663" loading="lazy" width="1000" height="663"></figure>
</div>
<p><em>Photo by <a href="https://unsplash.com/@ratushny?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Dmitry Ratushny</a> on <a href="https://unsplash.com/s/photos/hope?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Unsplash</a></em></p>
<p>The intent of this article is to broaden our options as Rails developers and describe an alternative to using service objects with ActiveModel::Model on Plain Old Ruby Objects (POROs). This is a reliable but undervalued approach that should be part of every Rails developer’s toolbox. This article outlines this method in further detail.</p>
<h2>The feature</h2>
<p>The feature we’re going to implement is a simple <strong>publish/unpublish</strong> action on a Post model.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s2g6blpyesxww8c2tup65ry0" alt=""  sizes="" src="https://cms-assets.abletech.nz/1_E_Ox_El6ic_MW_mr_Puo_Z_f0_CQ_57a0527f0f.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p>Nothing fancy here a <code>Post</code> can be <code>:published</code> and can belong to a <code>Publisher</code>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="m76br2iz1m85dmdhlcvwlzrg" alt="" data-big=https://cms-assets.abletech.nz/medium_1hfg8_Fglmh_Rao_YY_0a32_Kd_Q_ad3ea3aa45.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1hfg8_Fglmh_Rao_YY_0a32_Kd_Q_ad3ea3aa45.png" srcset="https://cms-assets.abletech.nz/small_1hfg8_Fglmh_Rao_YY_0a32_Kd_Q_ad3ea3aa45.png 500w, https://cms-assets.abletech.nz/medium_1hfg8_Fglmh_Rao_YY_0a32_Kd_Q_ad3ea3aa45.png 750w, https://cms-assets.abletech.nz/thumbnail_1hfg8_Fglmh_Rao_YY_0a32_Kd_Q_ad3ea3aa45.png 245w" data-zooming-width="750" data-zooming-height="388" loading="lazy" width="750" height="388"></figure>
</div>
<h2>The Service Object Way</h2>
<p>Let’s start by describing how we would tackle this with service objects. We would probably add <code>#new_publication</code>, <code>#publish</code> and <code>#unpublish</code> routes to the <code>:post</code> resources. Then create two new services: <strong>PublishPost</strong> &amp; <strong>UnpublishPost</strong></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dw15f2mpwhuhmmdya8ioxp3s" alt="" data-big=https://cms-assets.abletech.nz/medium_1a_Ie_O_Zz_Qp_Pn3vm6_C_Umb_Md_FA_882c267ac9.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1a_Ie_O_Zz_Qp_Pn3vm6_C_Umb_Md_FA_882c267ac9.png" srcset="https://cms-assets.abletech.nz/small_1a_Ie_O_Zz_Qp_Pn3vm6_C_Umb_Md_FA_882c267ac9.png 500w, https://cms-assets.abletech.nz/medium_1a_Ie_O_Zz_Qp_Pn3vm6_C_Umb_Md_FA_882c267ac9.png 750w, https://cms-assets.abletech.nz/thumbnail_1a_Ie_O_Zz_Qp_Pn3vm6_C_Umb_Md_FA_882c267ac9.png 245w" data-zooming-width="750" data-zooming-height="370" loading="lazy" width="750" height="370"></figure>
</div>
<h2>The dangers</h2>
<h3>Form Errors</h3>
<p>The first thing that comes to mind is how errors are handled and displayed in the view. You’ll most likely have to tweak your <code>_form.html.erb</code> partial to display errors appropriately because the service object doesn't match the Rails approach like ActiveRecord does. To handle errors, you might:</p>
<ul>
<li>
<p>Create a view or a form object</p>
</li>
<li>
<p>Proxy errors to <code>@post</code> and reference <code>@post.errors</code> in the view</p>
</li>
<li>
<p>Loop through a custom array of errors in the view</p>
</li>
<li>
<p>Include <code>ActiveModel::Validations</code> to your service</p>
</li>
</ul>
<p>With time, devs using service objects will probably end up with a solution that they’ll apply on all their views. From experience, this is rarely the case and multiple implementations of error handling or forms are spread across the codebase. Devs spend time going against Rails just to accomodate the use of service objects.</p>
<h3>Controller routes</h3>
<p>I admit this one is not inherently a service object flaw. You could use service objects with dedicated RESTful routes. I generally see this implementation paired with service objects for some reason. The <code>posts_controller</code> now has three more actions and is likely to grow when you want to archive, review or approve a post. This is likely to make the controller difficult to maintain in the future.</p>
<p><strong>Idea</strong>: Routes are not just for ActiveRecord classes. The number of controllers in the codebase is not a one-to-one mapping with the number of ActiveRecord classes in the codebase. Multiple controllers can be used for one ActiveRecord or PORO.</p>
<h3>Hidden Code</h3>
<p>We now have one service for each action which makes it hard to update the code in all the relevant places. Updating the publish service could require updating the unpublish service which is easily forgotten.</p>
<p><strong>Idea</strong>: Nothing stops a service class having multiple methods. Even the <strong>Command Design Pattern</strong>, which feels similar to service objects, does consider providing a pair of <code>#execute</code> &amp; <code>#undo</code> methods. The single responsibility principle is pushed to its extreme making it hard to maintain code and giving a false sense of decoupling.</p>
<h3>Reusability</h3>
<p>The trap of services is to think that they are DRY and reusable. The idea is that a service is so good at doing one thing that it can be reused in other services. This is where the nightmare starts. Those services become bloated by conditions and edge cases once reused. After a while, you end up with a lengthy perform method with nested ifs. This trap is too easy to fall into.</p>
<p><strong>Idea</strong>: Service classes share the same interface and could use polymorphism. Nothing stops a factory from presenting the right type of service to its client. That said, we tend to handle all use cases in one single class.</p>
<h2>REST &amp; ActiveModel to the rescue.</h2>
<p>This new implementation comes down to three things:</p>
<ul>
<li>
<p><strong>ActiveModel::Model</strong></p>
</li>
<li>
<p>Nouns over verbs</p>
</li>
<li>
<p>REST only: <code>[:index, :new, :create, :edit, :update, :destroy]</code></p>
</li>
</ul>
<p>Instead of publishing and unpublishing a post, we consider creating a publication and destroying a publication for a post.</p>
<ul>
<li>
<p><code>PublishPost#perform</code> becomes <code>Publication#create</code></p>
</li>
<li>
<p><code>UnpublishPost#perform</code> becomes <code>Publication#destroy</code></p>
</li>
</ul>
<h2>The Model — Publication</h2>
<p>First, there is this idea of a <strong>Publication</strong>. So let’s create that class in <code>app/models/publication.rb</code>. The publication takes one argument, a post, and is accessible through <code>Post#publication</code>. That class inherits <code>ActiveModel::Model</code> which provides all the methods to behave like an ActiveRecord. Links and names between routes, views and controllers are all handled via the module. It even handles I18n out of the box.</p>
<ul>
<li>
<p>The Publication has two methods <code>#create</code> &amp; <code>#destroy</code> which are next to each other instead of separate classes</p>
</li>
<li>
<p>The publication has its own validations system</p>
</li>
<li>
<p>The publication can be used in form views without worrying of hardcoding routes, partial names or use a custom view form</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k7yao8w2tyqg55xys9jyqeah" alt="" data-big=https://cms-assets.abletech.nz/medium_1_Lbz_Rw_Y1_PTJ_Is_U1k5_Yr_D_Fw_953361c9b3.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 196px" src="https://cms-assets.abletech.nz/1_Lbz_Rw_Y1_PTJ_Is_U1k5_Yr_D_Fw_953361c9b3.png" srcset="https://cms-assets.abletech.nz/small_1_Lbz_Rw_Y1_PTJ_Is_U1k5_Yr_D_Fw_953361c9b3.png 500w, https://cms-assets.abletech.nz/medium_1_Lbz_Rw_Y1_PTJ_Is_U1k5_Yr_D_Fw_953361c9b3.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Lbz_Rw_Y1_PTJ_Is_U1k5_Yr_D_Fw_953361c9b3.png 196w" data-zooming-width="750" data-zooming-height="597" loading="lazy" width="750" height="597"></figure>
</div>
<h2>The routes</h2>
<p>To keep things RESTful we then update our routes to use publication the namespace. Following Rails conventions between the ActiveModel and the routes makes it easy to configure. Beautiful.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="plrnxkl172fbxfknbeovc16v" alt="" data-big=https://cms-assets.abletech.nz/medium_1t_If65_O_Up_P_Tx_Bf97_Ro_Lxujw_e7a240a10b.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1t_If65_O_Up_P_Tx_Bf97_Ro_Lxujw_e7a240a10b.png" srcset="https://cms-assets.abletech.nz/small_1t_If65_O_Up_P_Tx_Bf97_Ro_Lxujw_e7a240a10b.png 500w, https://cms-assets.abletech.nz/medium_1t_If65_O_Up_P_Tx_Bf97_Ro_Lxujw_e7a240a10b.png 750w, https://cms-assets.abletech.nz/thumbnail_1t_If65_O_Up_P_Tx_Bf97_Ro_Lxujw_e7a240a10b.png 245w" data-zooming-width="750" data-zooming-height="58" loading="lazy" width="750" height="58"></figure>
</div>
<h2>The controller</h2>
<p>Nothing fancy here, it takes 2 minutes to read and the controller is not overloaded with other custom methods. The controller breathes Rails conventions and we can barely distinguish it from a controller generated through the <code>rails generate scaffold</code> command.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cvmivb5g1zyhe0j7159m471t" alt="" data-big=https://cms-assets.abletech.nz/medium_1gtym_P_Vc6_H7e_PUW_6q_15o_Tg_2fc8e39ae2.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 233px" src="https://cms-assets.abletech.nz/1gtym_P_Vc6_H7e_PUW_6q_15o_Tg_2fc8e39ae2.png" srcset="https://cms-assets.abletech.nz/small_1gtym_P_Vc6_H7e_PUW_6q_15o_Tg_2fc8e39ae2.png 500w, https://cms-assets.abletech.nz/medium_1gtym_P_Vc6_H7e_PUW_6q_15o_Tg_2fc8e39ae2.png 750w, https://cms-assets.abletech.nz/thumbnail_1gtym_P_Vc6_H7e_PUW_6q_15o_Tg_2fc8e39ae2.png 233w" data-zooming-width="750" data-zooming-height="501" loading="lazy" width="750" height="501"></figure>
</div>
<h3>The view</h3>
<p>The new publication page uses the form helper and errors without requiring fancy hacks. It recognises which route to submit the form and easily matches publication attributes with input fields. No hardcoded form that we then need to parse in the controller or another form object.</p>
<p>The lengthy block in the view (the errors) is a straight copy-paste from a <code>rails generate scaffold</code> generated view.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="oob1vmizp0k1y0wos8zhzphb" alt="" data-big=https://cms-assets.abletech.nz/medium_1gt_Ziwq_M_Ogdi_Jz_1_Nh_Pnvn_A_faf88c4f16.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1gt_Ziwq_M_Ogdi_Jz_1_Nh_Pnvn_A_faf88c4f16.png" srcset="https://cms-assets.abletech.nz/small_1gt_Ziwq_M_Ogdi_Jz_1_Nh_Pnvn_A_faf88c4f16.png 500w, https://cms-assets.abletech.nz/medium_1gt_Ziwq_M_Ogdi_Jz_1_Nh_Pnvn_A_faf88c4f16.png 750w, https://cms-assets.abletech.nz/thumbnail_1gt_Ziwq_M_Ogdi_Jz_1_Nh_Pnvn_A_faf88c4f16.png 245w" data-zooming-width="750" data-zooming-height="438" loading="lazy" width="750" height="438"></figure>
</div>
<h2>Conclusion</h2>
<p>With the ActiveModel implementation, the view (and form), the controller and the model collaborate the same way ActiveRecords would: with perfect harmony. It is elegant, it is simple to understand, it feels right. Why are we not seeing more of this and less of service objects?</p>
<h3>Plot Twist</h3>
<p>To be honest the advice given in this article can also be implemented with service objects:</p>
<ul>
<li>
<p>Nothing stops us from sticking to REST routes with service objects</p>
</li>
<li>
<p>Nothing stops us from including ActiveModel in service objects</p>
</li>
<li>
<p>Nothing stops us from having more than one public method in service objects</p>
</li>
</ul>
<blockquote>
<h4>To some extent, the Publication model is probably closer to the <strong>Command Design Pattern</strong> than a service object.</h4>
</blockquote>
<h3>It is too simple</h3>
<p>The problem with examples like this one is that they get discarded straight away by senior developers who argue they are too simple to address the complexity of production requirements.</p>
<p>While this might be a natural instinct it may not help you grow as a developer. I would challenge developers to persevere and not fall back on service objects too soon. Just because you’ve hit a hurdle with ActiveModel doesn’t mean it’s impossible. The majority of Rails codebases aren’t that special and yours is probably no exception.</p>
<h3>Further Reading</h3>
<p>Service objects… Some see them as best practice, others as an anti-pattern. They are well established in the Rails community but developers are now increasingly challenging them.</p>
<ul>
<li>
<p><a href="https://www.reddit.com/r/rails/comments/itivdn/where_did_concept_of_service_object_come_from/" target="_blank" rel="noopener noreferrer">Reddit: Where did the concept of service object come from?</a></p>
</li>
<li>
<p><a href="https://www.reddit.com/r/rails/comments/jvxhew/is_command_pattern_a_common_thing_in_the_industry/" target="_blank" rel="noopener noreferrer">Reddit: Is command pattern a common thing in the industry?</a></p>
</li>
<li>
<p><a href="https://www.codewithjason.com/rails-service-objects/" target="_blank" rel="noopener noreferrer">Jason Swett: Beware of “service objects” in Rails</a></p>
</li>
</ul>
<p><em>This article was orginally posted on <a href="https://alexbarret.com/blog/2020/service-object-alternative/" target="_blank" rel="noopener noreferrer">alexandre barret’s personal blog</a></em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>An introduction to Elixir</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>A guided refactoring of a beginner’s module</h2>
<p>Find out more about Elixir by sitting in on this Abletech tech talk from <a href="https://twitter.com/nigelramsay" target="_blank" rel="noopener noreferrer">Nigel</a> and David at Abletech.</p>
<p>This presentation covers some of the Elixir Design Patterns, and a guided refactoring of a beginner’s module into a more Elixir native style.</p>
<p>David and Matt K were new to Elixir. They went through an exercise, with Nigel, where they refactored some Elixir code. Sit in on a video where they talk through how it works:</p>
<p>This talk was also delivered at a recent <a href="https://www.meetup.com/Wellington-Elixir-User-Group/" target="_blank" rel="noopener noreferrer">Wellington Elixir User Group meetup</a>. These meetups are normally on the second Wednesday of each month. If you’re keen to meet up with others who are interested in Elixir then this is the group for you.</p>
<h3>Other recent tech talks at Abletech</h3>
<ul>
<li>
<p><a href="https://abletech.nz/about/#cameron-fowler">Cameron</a> talking about <a href="https://abletech.nz/article/automate-with-selenium">tracking personal spending using Selenium</a></p>
</li>
<li>
<p>Alexandre gives a <a href="https://abletech.nz/article/websockets-multi-player-game">demo of a multi-player game prototype that uses WebSockets</a></p>
</li>
<li>
<p>Find out more about the <a href="https://abletech.nz/article/content-security-policy">Content Security Policy</a> with <a href="https://abletech.nz/about/#nigel-ramsay">Nigel</a></p>
</li>
</ul>
<h3>More about Elixir</h3>
<ul>
<li>
<p>How to <a href="https://abletech.nz/resource/how-to-configure-vs-code-to-format-elixir-code">configure VS Code to format Elixir code</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/optimising-docker-for-mac-and-elixir">Optimising Docker for Mac and Elixir</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/elixir-meetup-at-abletech/">Elixir meetup</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/the-elixir-equivalent-of-rspecs-focus-true">Elixir equivalent of Rspec’s focus: true</a></p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k2kmkqamus9l1n3schzwn8uc" alt="" data-big=https://cms-assets.abletech.nz/large_1qpvm_OVM_Yp_6_L_Tu4_Ew5h77w_f1217d6846.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1qpvm_OVM_Yp_6_L_Tu4_Ew5h77w_f1217d6846.png" srcset="https://cms-assets.abletech.nz/large_1qpvm_OVM_Yp_6_L_Tu4_Ew5h77w_f1217d6846.png 1000w, https://cms-assets.abletech.nz/small_1qpvm_OVM_Yp_6_L_Tu4_Ew5h77w_f1217d6846.png 500w, https://cms-assets.abletech.nz/medium_1qpvm_OVM_Yp_6_L_Tu4_Ew5h77w_f1217d6846.png 750w, https://cms-assets.abletech.nz/thumbnail_1qpvm_OVM_Yp_6_L_Tu4_Ew5h77w_f1217d6846.png 245w" data-zooming-width="1000" data-zooming-height="581" loading="lazy" width="1000" height="581"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>App matches Clients with Consultants</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Abletecher Matt Lee built an app for Planit Software Testing. It generates a specific CV to match clients with consultants</h2>
<p><a href="https://abletech.nz/about/#matt-lee">Matt</a> purpose-built the appliction for Planit Software Testing for his Honours project towards his Software Engineering degree at Victoria University of Wellington. He recently showcased his work to industry professionals and academics from the School of Engineering and Computer Science.</p>
<h3>The client</h3>
<p>Planit helps people test applications they’ve developed. Their consultants do independent evaluations. They give advice on processes and technologies used.</p>
<p>Planit want to let their clients select a consultant whose skills and experience best match their needs. The client will be able to choose the consultant they most want to work with.</p>
<p>To align with Planit’s international business model, the app needs to use the existing Planit format, be accurate, and scalable.</p>
<h3>The app</h3>
<p>Matt quickly got up-to-speed with Planit’s business model and existing technology. Next he researched profile generation and document creation. Then Matt built an app that can search and manage consultant profiles. The search information generates a CV, in a specific Planit format, that Planit can use in all their branches as far afield as India and the UK.</p>
<p>Matt also researched how to optimise accuracy. To maintain Planit’s business reputation, the match between client and consultant had to be accurate. It also had to be scalable; Planit have over 1000 consultants worldwide.</p>
<p>Planit are really happy with the product. Their consultants like it too. Planit are looking at deploying the app at their Wellington office initially, then at other branches.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uy97nlle0ftfsci5bbg6huih" alt="" data-big=https://cms-assets.abletech.nz/large_1_Rz_XBE_Jqv_Af1lzri_enrqog_870da33681.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Rz_XBE_Jqv_Af1lzri_enrqog_870da33681.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Rz_XBE_Jqv_Af1lzri_enrqog_870da33681.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Rz_XBE_Jqv_Af1lzri_enrqog_870da33681.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Rz_XBE_Jqv_Af1lzri_enrqog_870da33681.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Rz_XBE_Jqv_Af1lzri_enrqog_870da33681.jpeg 245w" data-zooming-width="1000" data-zooming-height="593" loading="lazy" width="1000" height="593"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hbsnj6ywzbwo4x1kdf302vyn" alt="" data-big=https://cms-assets.abletech.nz/large_1g_Lzkvgf_Aa_B_V_Rnb_Ied5_F9g_af50f5bd93.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 201px" src="https://cms-assets.abletech.nz/1g_Lzkvgf_Aa_B_V_Rnb_Ied5_F9g_af50f5bd93.jpeg" srcset="https://cms-assets.abletech.nz/large_1g_Lzkvgf_Aa_B_V_Rnb_Ied5_F9g_af50f5bd93.jpeg 1000w, https://cms-assets.abletech.nz/small_1g_Lzkvgf_Aa_B_V_Rnb_Ied5_F9g_af50f5bd93.jpeg 500w, https://cms-assets.abletech.nz/medium_1g_Lzkvgf_Aa_B_V_Rnb_Ied5_F9g_af50f5bd93.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1g_Lzkvgf_Aa_B_V_Rnb_Ied5_F9g_af50f5bd93.jpeg 201w" data-zooming-width="1000" data-zooming-height="776" loading="lazy" width="1000" height="776"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wtxypgt6veayw6t41roucq83" alt="" data-big=https://cms-assets.abletech.nz/large_1bruzw3_H3_Q_Ic_HIN_Rg_Fi2_ZLA_bdb42383da.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 211px" src="https://cms-assets.abletech.nz/1bruzw3_H3_Q_Ic_HIN_Rg_Fi2_ZLA_bdb42383da.jpeg" srcset="https://cms-assets.abletech.nz/large_1bruzw3_H3_Q_Ic_HIN_Rg_Fi2_ZLA_bdb42383da.jpeg 1000w, https://cms-assets.abletech.nz/small_1bruzw3_H3_Q_Ic_HIN_Rg_Fi2_ZLA_bdb42383da.jpeg 500w, https://cms-assets.abletech.nz/medium_1bruzw3_H3_Q_Ic_HIN_Rg_Fi2_ZLA_bdb42383da.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1bruzw3_H3_Q_Ic_HIN_Rg_Fi2_ZLA_bdb42383da.jpeg 211w" data-zooming-width="1000" data-zooming-height="741" loading="lazy" width="1000" height="741"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wc4u41jgukx1ixw8cu574lxe" alt="CV Generation & Matching Application for Planit Software Testing" data-big=https://cms-assets.abletech.nz/large_1y_PU_Nesn5j_Gj_KX_Ii_P_g_Rk_A_8cd2e02c75.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 214px" src="https://cms-assets.abletech.nz/1y_PU_Nesn5j_Gj_KX_Ii_P_g_Rk_A_8cd2e02c75.jpeg" srcset="https://cms-assets.abletech.nz/large_1y_PU_Nesn5j_Gj_KX_Ii_P_g_Rk_A_8cd2e02c75.jpeg 1000w, https://cms-assets.abletech.nz/small_1y_PU_Nesn5j_Gj_KX_Ii_P_g_Rk_A_8cd2e02c75.jpeg 500w, https://cms-assets.abletech.nz/medium_1y_PU_Nesn5j_Gj_KX_Ii_P_g_Rk_A_8cd2e02c75.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1y_PU_Nesn5j_Gj_KX_Ii_P_g_Rk_A_8cd2e02c75.jpeg 214w" data-zooming-width="1000" data-zooming-height="729" loading="lazy" width="1000" height="729"></figure>
</div>
<p><em>CV Generation &amp; Matching Application for Planit Software Testing</em></p>
<h3>The presentation</h3>
<p>The presentation of the app was held in the Alan MacDiarmid Building at VUW’s Kelburn Campus. Attendees were welcomed by the Dean of Engineering — Prof Dale Carnegie. There were a number of final year presentations showcasing topics such as visualising road traffic data, a virtual reality simulation of radiation therapy and a historic site tool.</p>
<p>Matt says it was an honour to be chosen to present his project to academics, and business people, who work in engineering and computer science. He says it was also nerve-wracking. Thankfully everyone was very impressed, and supportive, and he’s pleased he got the opportunity to showcase his work.</p>
<p><a href="https://abletech.nz/about/#carl-penwarden">Carl Penwarden</a> was delighted to be in the audience to witness Matt’s superb presentation skills. Here at Abletech we’re impressed with the depth of Matt’s research and the success of the app he built. Most of all we’re super-happy to now have Matt on the Abletech team long-term 😁</p>
<p>Read more:</p>
<ul>
<li>
<p>Matt’s <a href="https://abletech.nz/article/my-first-month-at-abletech">first month as an intern</a> at Abletech</p>
</li>
<li>
<p>Some of the <a href="https://abletech.nz/article/interning-at-abletech">tech Matt’s been using</a> at Abletech</p>
</li>
<li>
<p>How Matt’s now <a href="https://abletech.nz/article/the-future-of-tech">helping Abletech find awesome interns</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>A quick replace in Vim</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Sometimes you just don’t want to type what you are trying to replace. In Vim, if you have set incsearch set in your .vimrc you can use ctrl-lto autocomplete the next character of your search.</h2>
<p>Combine this with another feature, where if you do a substitution with a blank regular expression, Vim will use your last search term. What does this look like? Well, a fast and effortless replacement:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ahu1twc3pr15dy3met7yiluw" alt=""  sizes="" src="https://cms-assets.abletech.nz/1j_P9h3_BO_2u_OH_Ge3_N4l_Bk_ZA_2abfd7993a.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p>To recap:</p>
<ol>
<li>
<p>Set incremental search in your <code>~/.vimrc</code> by adding <code>set incsearch</code>.</p>
</li>
<li>
<p>Search for the term you want to replace with <code>/</code> using <code>ctrl-l</code> to complete the full match.</p>
</li>
<li>
<p>Use a blank substitution to replace items matching the last search <code>:s//new_text/g</code> (the <code>g</code> will replace all occurrences found on that line).</p>
</li>
</ol>
<p>Happy Vimming!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Adding Docker to a Rails application</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>I will explain why Docker is a good choice for development of Rails applications. Next, I will give a step-by-step walkthrough of adding Docker to a fairly standard Rails application.</h2>
<p>This content is based on a <a href="https://www.meetup.com/wellrailed/events/235889298/" target="_blank" rel="noopener noreferrer">presentation that I gave at WellRailed</a>, a Wellington, New Zealand based Ruby on Rails meetup in December 2016.</p>
<h2>What is Docker?</h2>
<p>Docker is a platform that allows you to run containers. These containers, when compared to traditional VMs, are a much faster and more efficient mechanism for isolating the different parts of your application from each other.</p>
<p>This image from IBM gives a good comparison:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vb2aaa5b47lplq6pbktvjmv4" alt="" data-big=https://cms-assets.abletech.nz/large_1_Cjs3k_Kij_Ov_JO_3u_DQ_7zddmw_648773d1b6.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 211px" src="https://cms-assets.abletech.nz/1_Cjs3k_Kij_Ov_JO_3u_DQ_7zddmw_648773d1b6.png" srcset="https://cms-assets.abletech.nz/large_1_Cjs3k_Kij_Ov_JO_3u_DQ_7zddmw_648773d1b6.png 1000w, https://cms-assets.abletech.nz/small_1_Cjs3k_Kij_Ov_JO_3u_DQ_7zddmw_648773d1b6.png 500w, https://cms-assets.abletech.nz/medium_1_Cjs3k_Kij_Ov_JO_3u_DQ_7zddmw_648773d1b6.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Cjs3k_Kij_Ov_JO_3u_DQ_7zddmw_648773d1b6.png 211w" data-zooming-width="1000" data-zooming-height="740" loading="lazy" width="1000" height="740"></figure>
</div>
<h2>Why even bother?</h2>
<ol>
<li>
<p>Applications often have <strong>aged dependencies</strong>. You may need to run an older version of Ruby, or a specific version of Postgresql and/or Redis.</p>
</li>
<li>
<p><strong>Matching Operating System</strong>. It is common for applications to be developed using Mac OSX and then deployed onto Linux. Docker allows us to develop using a matching operating system, and identify any OS differences, before we deploy.</p>
</li>
<li>
<p><strong>Version differences</strong>. You may need to run multiple applications on your development computer. Application X may use Postgresql 9.6 and Application Y may use Postgresql 9.1. Is there an equivalent to RVM for Postgresql or Redis etc?</p>
</li>
<li>
<p>The “<strong>I don’t want MongoDB”</strong> on my laptop problem. Some application dependency choices are often best forgotten. With Docker you don’t need to have infrequently used dependencies continuously running in the background of your laptop.</p>
</li>
</ol>
<h2>Other reasons for Docker</h2>
<ol>
<li>
<p><strong>Declared dependencies</strong>. This will allow you to run a consistent development environment with both your production environment, as well as the other developers on your team.</p>
</li>
<li>
<p>From <strong>Zero to Running in a few minutes</strong>. No longer do you need to follow a set of step-by-step configuration instructions.</p>
</li>
<li>
<p><strong>Less database bloat</strong>. Over time, older database instances and similar artifacts will be scattered over your laptop. These consume space, and can be difficult to track down. With Docker volumes, it is very easy to purge unused container artifacts, and keep disk space consumption to a minimum.</p>
</li>
<li>
<p><strong>Quicker laptop boot times</strong>. If you solely use Docker, you no longer need to have a collection of databases, data stores and search engines being started by OSX/Linux/Windows on boot.</p>
</li>
</ol>
<h2>Goals for using Docker</h2>
<p>A fast start from ZERO in 3 steps:</p>
<ol>
<li>
<p>Checkout fresh project from Git</p>
</li>
<li>
<p>Edit/check an environment file</p>
</li>
<li>
<p>Start the app</p>
</li>
</ol>
<p>And when finished development:</p>
<ol>
<li>
<p>Stop the app</p>
</li>
<li>
<p>Nothing else is running and consuming resources</p>
</li>
</ol>
<h2>How to add Docker to a Rails application</h2>
<h3>Dependencies</h3>
<p>First off, we should itemise the dependencies in this application:</p>
<ol>
<li>
<p>Ruby</p>
</li>
<li>
<p>Postgres</p>
</li>
<li>
<p>Redis</p>
</li>
<li>
<p>Sidekiq</p>
</li>
</ol>
<p>Before starting, you should download Docker for your OS. It is free and called <a href="https://www.docker.com/community-edition" target="_blank" rel="noopener noreferrer">Docker Community Edition</a> these days.</p>
<h3>Configure the Postgresql Dependency</h3>
<p>Docker requires us to define the different dependencies on the application, and this is done in the <code>docker-compose.yml</code> file. This file lives in the root directory of your Rails application.</p>
<p>For our Postgresql dependency, we will define the following</p>
<p><em>image</em> — this the container definition that is downloaded from <a href="https://www.google.com/url?q=https%3A%2F%2Fhub.docker.com%2F&amp;sa=D&amp;sntz=1&amp;usg=AFQjCNEIFV77zorBSLW9CFwdIWzMlZSi2w" target="_blank" rel="noopener noreferrer">https://hub.docker.com/</a></p>
<p>*container — *this can be thought of as the VM that runs, and is based on the downloaded image.</p>
<p>*volume — *this is the persistent storage which Postgresql will store the data files.</p>
<p>Here is the Postgresql section within the new <code>docker-compose.yml</code> file:</p>
<pre><code class="hljs"><span class="hljs-symbol">version:</span> <span class="hljs-string">&#x27;3&#x27;</span>
<span class="hljs-symbol">services:</span>
<span class="hljs-symbol">  postgresql:</span>
<span class="hljs-symbol">    image:</span> postgres:<span class="hljs-number">9.4</span><span class="hljs-number">.1</span>
<span class="hljs-symbol">    ports:</span>
      - <span class="hljs-string">&quot;5432:5432&quot;</span>
<span class="hljs-symbol">    volumes:</span>
      - postgresql-data:<span class="hljs-keyword">/var/</span>lib<span class="hljs-keyword">/postgresql/</span>data
<span class="hljs-symbol">volumes:</span>
  postgresql-data: <span class="hljs-punctuation">{</span><span class="hljs-punctuation">}</span>`
</code></pre>
<h3>Try starting Postgresql</h3>
<p>With the <code>docker-compose.yml</code> file saved, we can start up the database by issuing this command:</p>
<p><code>docker-compose up</code></p>
<p>See if it works by adjusting the <code>database.yml</code> file:</p>
<pre><code class="hljs"><span class="hljs-symbol">development:</span>
<span class="hljs-symbol">  adapter:</span> postgresql
<span class="hljs-symbol">  encoding:</span> unicode
<span class="hljs-symbol">  pool:</span> <span class="hljs-number">5</span>
<span class="hljs-symbol">  database:</span> adjuster_development
<span class="hljs-symbol">  url:</span> <span class="hljs-params">&lt;%= ENV[&#x27;POSTGRESQL_URL&#x27;] || &#x27;`postgresql://postgres@localhost:<span class="hljs-number">5432</span>`&#x27; %&gt;</span>
<span class="hljs-symbol">test:</span>
<span class="hljs-symbol">  adapter:</span> postgresql
<span class="hljs-symbol">  encoding:</span> unicode
<span class="hljs-symbol">  pool:</span> <span class="hljs-number">5</span>
<span class="hljs-symbol">  database:</span> adjuster_test
<span class="hljs-symbol">  url:</span> <span class="hljs-params">&lt;%= ENV[&#x27;POSTGRESQL_URL&#x27;] || &#x27;`postgresql://postgres@localhost:<span class="hljs-number">5432</span>`&#x27; %&gt;</span>`
</code></pre>
<p>Next, try creating the database:</p>
<p><code>rake db:create</code></p>
<p>Next, run a few tests to see it working:</p>
<pre><code class="hljs">rake

<span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span>..FFFFFFF<span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span>.
</code></pre>
<p>This fails due to the missing Redis container. Let’s fix that</p>
<p>Add to <code>docker-compose.yml</code> file:</p>
<pre><code class="hljs"><span class="hljs-symbol">redis:</span>
<span class="hljs-symbol">  image:</span> redis:<span class="hljs-number">2.8</span>
<span class="hljs-symbol">  ports:</span>
    - <span class="hljs-string">&quot;6379:6379&quot;</span>
<span class="hljs-symbol">  volumes:</span>
    - redis-data:<span class="hljs-keyword">/var/</span>lib<span class="hljs-keyword">/redis/</span>data`
<span class="hljs-symbol">
volumes:</span>
  redis-data: <span class="hljs-punctuation">{</span><span class="hljs-punctuation">}</span>
</code></pre>
<p>Then restart docker-compose:</p>
<p><code>docker-compose down; docker-compose up</code></p>
<p>Update Rails application <code>sidekiq.rb</code> to use the new Redis container:</p>
<pre><code class="hljs"><span class="hljs-title class_">Sidekiq</span>.configure_server <span class="hljs-keyword">do</span> |config|
  config.redis = {
    <span class="hljs-symbol">url:</span> <span class="hljs-string">&quot;redis://<span class="hljs-subst">#{<span class="hljs-title class_">ENV</span>[<span class="hljs-string">&#x27;REDIS_HOST&#x27;</span> || <span class="hljs-string">&#x27;127.0.0.1&#x27;</span>]}</span>:<span class="hljs-subst">#{<span class="hljs-title class_">ENV</span>[<span class="hljs-string">&#x27;REDIS_HOST&#x27;</span>]}</span>/12&quot;</span>
  }
<span class="hljs-keyword">end</span>

<span class="hljs-title class_">Sidekiq</span>.configure_client <span class="hljs-keyword">do</span> |config|
  config.redis = {
    <span class="hljs-symbol">url:</span> <span class="hljs-string">&quot;redis://<span class="hljs-subst">#{<span class="hljs-title class_">ENV</span>[<span class="hljs-string">&#x27;REDIS_HOST&#x27;</span> || <span class="hljs-string">&#x27;127.0.0.1&#x27;</span>]}</span>:<span class="hljs-subst">#{<span class="hljs-title class_">ENV</span>[<span class="hljs-string">&#x27;REDIS_HOST&#x27;</span>]}</span>/12&quot;</span>
  }
<span class="hljs-keyword">end</span>
</code></pre>
<p>Run the tests a second time:</p>
<pre><code class="hljs">rake

<span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span><span class="hljs-params">...</span>.
</code></pre>
<p>Win!</p>
<h3>Run Rails in a container</h3>
<p>We’ve been running Rails from the host operating system, in my case Mac OSX. Now we want to try getting Rails running within a new web container, as defined in the docker-compose.yml file.</p>
<p>Make the following additions:</p>
<pre><code class="hljs"><span class="hljs-attribute">web</span><span class="hljs-punctuation">:</span>
  <span class="hljs-attribute">build</span><span class="hljs-punctuation">:</span> <span class="hljs-string">.</span>
  <span class="hljs-attribute">ports</span><span class="hljs-punctuation">:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;3000:3000&quot;</span>
  <span class="hljs-attribute">volumes</span><span class="hljs-punctuation">:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">bundle-cache:/bundle</span>
  <span class="hljs-attribute">depends_on</span><span class="hljs-punctuation">:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">redis</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">postgresql</span>
  <span class="hljs-attribute">command</span><span class="hljs-punctuation">:</span> <span class="hljs-string">bin/docker_web`</span>

<span class="hljs-attribute">volumes</span><span class="hljs-punctuation">:</span>
  <span class="hljs-attribute">bundle-cache</span><span class="hljs-punctuation">:</span> <span class="hljs-string">{}</span>
</code></pre>
<p>Add a <code>.env</code> file:</p>
<pre><code class="hljs"><span class="hljs-attr">POSTGRESQL_URL</span>=`postgresql://postgres@postgresql:<span class="hljs-number">5432</span>`
<span class="hljs-attr">REDIS_ADDRESS</span>=redis
</code></pre>
<p>Add the new <code>.env</code> file to your <code>.gitignore</code> file.</p>
<p>Next, add a <code>Dockerfile</code> that describes the dependencies and installation steps of your Rails app:</p>
<pre><code class="hljs"><span class="hljs-keyword">FROM</span> ruby:<span class="hljs-number">2.2</span>.<span class="hljs-number">1</span>

<span class="hljs-comment"># Set an environment variable to store where the app is installed to </span>
<span class="hljs-comment"># inside of the Docker image.</span>
<span class="hljs-keyword">ENV</span> BUNDLE_PATH /bundle
<span class="hljs-keyword">ENV</span> LANG C.UTF-<span class="hljs-number">8</span>
<span class="hljs-keyword">ENV</span> INSTALL_PATH /app

<span class="hljs-keyword">RUN</span><span class="language-bash"> <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main&quot;</span> &gt; /etc/apt/sources.list.d/pgdg.list</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8</span>

<span class="hljs-comment"># Install dependencies:</span>
<span class="hljs-comment"># - build-essential: To ensure certain gems can be compiled</span>
<span class="hljs-comment"># - bundler: ensure most recent version is installed</span>
<span class="hljs-comment"># - nodejs: Compile assets</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; apt-get install -qq -y build-essential nodejs postgresql-client-9.5 --fix-missing --no-install-recommends</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> gem install bundler</span>

<span class="hljs-keyword">RUN</span><span class="language-bash"> curl -sL https://deb.nodesource.com/setup_6.x | bash</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> apt-get install -qq -y nodejs</span>

<span class="hljs-comment"># This sets the context of where commands will be ran in and is </span>
<span class="hljs-comment"># documented on Docker&#x27;s website extensively.</span>

<span class="hljs-keyword">RUN</span><span class="language-bash"> <span class="hljs-built_in">mkdir</span> -p <span class="hljs-variable">$INSTALL_PATH</span></span>
<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> <span class="hljs-variable">$INSTALL_PATH</span></span>
<span class="hljs-keyword">ADD</span><span class="language-bash"> . <span class="hljs-variable">$INSTALL_PATH</span></span>
</code></pre>
<p>Next, add the launch script in <code>bin/docker_web</code></p>
<pre><code class="hljs"><span class="hljs-meta">#! /bin/bash</span>
<span class="hljs-built_in">export</span> RAILS_ENV=development
bundle check || bundle install — <span class="hljs-built_in">jobs</span>=10

<span class="hljs-comment"># Initialise development database</span>
FILE=<span class="hljs-variable">$INSTALL_PATH</span>/tmp/database_initialised_development.txt
<span class="hljs-keyword">if</span> [ ! -f <span class="hljs-string">&quot;<span class="hljs-variable">$FILE</span>&quot;</span> ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Creating and loading development databases&quot;</span>
  RAILS_ENV=development bundle <span class="hljs-built_in">exec</span> rake db:setup
  <span class="hljs-built_in">touch</span> <span class="hljs-string">&quot;<span class="hljs-variable">$FILE</span>&quot;</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Initialise test database</span>
FILE=<span class="hljs-variable">$INSTALL_PATH</span>/tmp/database_initialised_test.txt
<span class="hljs-keyword">if</span> [ ! -f <span class="hljs-string">&quot;<span class="hljs-variable">$FILE</span>&quot;</span> ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Creating and loading test databases&quot;</span>
  RAILS_ENV=<span class="hljs-built_in">test</span> bundle <span class="hljs-built_in">exec</span> rake db:create db:schema:load
  <span class="hljs-built_in">touch</span> <span class="hljs-string">&quot;<span class="hljs-variable">$FILE</span>&quot;</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;Migrating and refreshing reference data&quot;</span>
bundle <span class="hljs-built_in">exec</span> rake db:migrate
RAILS_ENV=<span class="hljs-built_in">test</span> bundle <span class="hljs-built_in">exec</span> rake db:migrate

npm install

<span class="hljs-built_in">rm</span> -f tmp/pids/server.pid
bundle <span class="hljs-built_in">exec</span> rails s -p 3000 -b ‘0.0.0.0’
</code></pre>
<p>Don’t forget to make this executable:</p>
<p><code>chmod +x bin/docker_web</code></p>
<p>Time to try out the application:</p>
<pre><code class="hljs">docker-compose down
docker-compose up
open [http:<span class="hljs-regexp">//</span><span class="hljs-number">0.0</span>.<span class="hljs-number">0.0</span>:<span class="hljs-number">3000</span><span class="hljs-regexp">/`](http:/</span><span class="hljs-regexp">/0.0.0.0:3000/</span>)
</code></pre>
<p>With a little luck, the application should be running.</p>
<h3>Adding Sidekiq</h3>
<p>Next step is to add the Sidekiq worker. Add the configuration to the <code>docker-compose.yml</code>** **file:</p>
<pre><code class="hljs"><span class="hljs-attribute">sidekiq</span><span class="hljs-punctuation">:</span>
  <span class="hljs-attribute">build</span><span class="hljs-punctuation">:</span> <span class="hljs-string">.</span>
  <span class="hljs-attribute">volumes</span><span class="hljs-punctuation">:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">.:/app</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">bundle-cache:/bundle</span>
  <span class="hljs-attribute">depends_on</span><span class="hljs-punctuation">:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">redis</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">postgresql</span>
  <span class="hljs-attribute">command</span><span class="hljs-punctuation">:</span> <span class="hljs-string">bin/docker_sidekiq</span>
</code></pre>
<p>Add the Sidekiq boot script in <code>bin/docker_sidekiq</code></p>
<pre><code class="hljs"><span class="hljs-section">#! /bin/bash</span>

export RAILS<span class="hljs-emphasis">_ENV=development

bundle check

if (($? &gt; 0)); then
  echo &#x27;<span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">***&#x27;
  echo &#x27;Await web container to install updated gems, then restart Docker&#x27;
  echo &#x27;**</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span><span class="hljs-strong">****</span>*&#x27;
  exit 1
fi

bundle exec sidekiq
</span></code></pre>
<p>Don’t forget to make this executable:</p>
<p><code>chmod +x bin/docker_sidekiq</code></p>
<p>Time to try out the application again:</p>
<pre><code class="hljs">docker-compose <span class="hljs-meta">down</span>
docker-compose <span class="hljs-meta">up</span><span class="hljs-string">`
</span></code></pre>
<p>You should see the Sidekiq application also startup and connect to Redis.</p>
<h2>Debugging with Docker</h2>
<p>Docker can be a little slow on OSX. I believe this is related to how the file system is mounted within the Docker containers.</p>
<p>What you can do, is use Docker to run all of the non-web portions of your application. Then start Rails within the host operating system. We will often run our specs within the host OS too.</p>
<h3>Using Pry</h3>
<p>If you need to do some debugging within your Rails application inside of the container, you can use the pry-remote gem, and then when you hit a breakpoint:</p>
<pre><code class="hljs">docker-compose <span class="hljs-built_in">exec</span> web bash
pry-remote
</code></pre>
<h2>Running multiple containers in parallel</h2>
<p>Occasionally, you may need to run a few different Rails applications at the same time. For example, when debugging an data sharing fault on the AddressFinder system, we might want both the AddressFinder API application and the AddressFinder Portal application running together on the same laptop.</p>
<p>This can pose a challenge, as both apps may be configured to expose their ports on <code>127.0.0.1</code> and you will end up with a clash.</p>
<p>On solution is to attach all the ports for <em>App #1</em> to <code>127.0.0.1</code> and the ports for <em>App #2</em> to <code>127.0.0.2</code>. To achieve this, you’ll first need to have create the <code>127.0.0.2</code> address as a <em>loopback alias</em>. We have written a tiny gem that will automate this — it’s called the <a href="https://github.com/abletech/loopback_alias" target="_blank" rel="noopener noreferrer">loopback_alias gem</a>.
<a href="https://github.com/AbleTech/loopback_alias" target="_blank" rel="noopener noreferrer"><strong>AbleTech/loopback_alias</strong>
<em>loopback_alias - Creates private 127.0.0.x IP address aliases on OSX</em>github.com</a></p>
<p>You would then update your <code>docker-compose.yml</code> file to refer to the new IP address:</p>
<pre><code class="hljs"><span class="hljs-symbol">postgresql:</span>
<span class="hljs-symbol">  image:</span> postgres:<span class="hljs-number">9.4</span><span class="hljs-number">.1</span>
<span class="hljs-symbol">  ports:</span>
    - <span class="hljs-string">&quot;$HOST_IP:5432:5432&quot;</span>
<span class="hljs-symbol">  volumes:</span>
    - postgresql-data:<span class="hljs-keyword">/var/</span>lib<span class="hljs-keyword">/postgresql/</span>data`
</code></pre>
<p>and add this line to your <code>.env</code> file:</p>
<pre><code class="hljs"><span class="hljs-attr">HOST_IP</span>=<span class="hljs-number">127.0</span>.<span class="hljs-number">0.2</span>
</code></pre>
<h2>Other useful containers</h2>
<h3>Mailcatcher</h3>
<p>We make good use of the Mailcatcher gem in development, and it is very easy to drop this into your stack. Just add this to your <code>docker-compose.yml</code>.</p>
<pre><code class="hljs"><span class="hljs-params">mailcatcher:</span>
  <span class="hljs-params">image:</span> schickling<span class="hljs-symbol">/mailcatcher</span>
  <span class="hljs-params">ports:</span>
    <span class="hljs-operator">-</span> <span class="hljs-string">&quot;1080:1080&quot;</span>
    <span class="hljs-operator">-</span> <span class="hljs-string">&quot;1025:1025&quot;</span>
</code></pre>
<p>and the following to your <code>development.rb</code> file:</p>
<pre><code class="hljs">config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { 
  address: <span class="hljs-keyword">ENV</span>[<span class="hljs-string">&#x27;SMTP_ADDRESS&#x27;</span>] || <span class="hljs-string">&#x27;127.0.0.1&#x27;</span>
  port: <span class="hljs-keyword">ENV</span>[<span class="hljs-string">&#x27;SMTP_PORT&#x27;</span>] || <span class="hljs-number">1025</span> 
}
</code></pre>
<h3>Middleman</h3>
<p>We’ve defined Docker configuration for all of our static websites that are generated with the Middleman gem.</p>
<h3>Procfile</h3>
<p>If you are declaring some background tasks in your <code>Procfile</code> for running in <code>production</code>, then these processes could also be added as Docker containers for running in <code>development</code>. You would do this in a similar manner to how the Sidekiq process is configured (above).</p>
<h4>Final words</h4>
<blockquote>
<h4>We’ve found adding Docker to be the biggest productivity gain we’ve had for some time at <a href="https://abletech.nz/">Abletech</a>. Our team are able to quickly get going on new projects, and we know we’re all developing within a consistent environment.</h4>
</blockquote>
<p>Team members, of varying experience with infrastructure, have been able to checkout Docker-based applications and become productive without needing someone to help.</p>
<p>This year, we look forward to exploring the possibilities of using Docker in <code>production</code> as tools such as <em>Docker Swam</em> and <em>Kubernetes</em> become more mature.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Abletech and CO2 emissions — growing our understanding</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Late last year I documented our first attempt to understand Abletech’s contribution to CO2 emissions. Seeking to both understand, and measure, our contributions, has been an enlightening process.</h2>
<p>One area we omitted from our first attempt is the contribution that comes from Cloud Hosting, the majority of which, for Abletech, is on AWS in Sydney.</p>
<p>To help us understand this better we have drawn on Merrin Macleod’s excellent talk at <a href="https://rubyconf.org.au/2019" target="_blank" rel="noopener noreferrer">Ruby Conf AU</a> on ‘Environment Variables’. She looked into how cloud hosting contributes to CO2e, and specifically addressed our region of the world. I highly recommend taking a look at her talk <a href="https://www.youtube.com/watch?v=N4hiqGwTRBU" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>Based on Merrin’s estimates, we have made our first attempt to better quantify how our hosting arrangements contribute to our carbon emissions and they are significant! The graph below shows our tonnes of CO2e since we started measuring a few months ago.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jy20pc6dyfyduwxvsmhnzqrq" alt="" data-big=https://cms-assets.abletech.nz/small_1_Jw_A6_E_Zg_F_Ct_R_Tr_OS_3_Ao_Ql5_Q_3d824ddff1.png sizes="(min-width: 768px) 500px, (min-width: 640px) 237px" src="https://cms-assets.abletech.nz/1_Jw_A6_E_Zg_F_Ct_R_Tr_OS_3_Ao_Ql5_Q_3d824ddff1.png" srcset="https://cms-assets.abletech.nz/small_1_Jw_A6_E_Zg_F_Ct_R_Tr_OS_3_Ao_Ql5_Q_3d824ddff1.png 500w, https://cms-assets.abletech.nz/thumbnail_1_Jw_A6_E_Zg_F_Ct_R_Tr_OS_3_Ao_Ql5_Q_3d824ddff1.png 237w" data-zooming-width="500" data-zooming-height="329" loading="lazy" width="500" height="329"></figure>
</div>
<p>Now we have this new understanding of our CO2e sources we are better armed to look at the options we have to reduce, or offset, our emissions. Thank you Merrin for helping to grow our understanding!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="adjl4d9v4t0ehlu6jzrg05vy" alt="" data-big=https://cms-assets.abletech.nz/large_1_Yi0j_Un_Uh5mlz_New_Do_Ile3_Q_69718ac645.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Yi0j_Un_Uh5mlz_New_Do_Ile3_Q_69718ac645.png" srcset="https://cms-assets.abletech.nz/large_1_Yi0j_Un_Uh5mlz_New_Do_Ile3_Q_69718ac645.png 1000w, https://cms-assets.abletech.nz/small_1_Yi0j_Un_Uh5mlz_New_Do_Ile3_Q_69718ac645.png 500w, https://cms-assets.abletech.nz/medium_1_Yi0j_Un_Uh5mlz_New_Do_Ile3_Q_69718ac645.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Yi0j_Un_Uh5mlz_New_Do_Ile3_Q_69718ac645.png 245w" data-zooming-width="1000" data-zooming-height="566" loading="lazy" width="1000" height="566"></figure>
</div>
<h3>Read more</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/abletech-and-co2-emissions">We started tracking our CO2 emissions</a></p>
</li>
<li>
<p>We moved to <a href="https://abletech.nz/article/carbon-zero-electricity">100% renewable electricity</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>2017 Australian Ruby Conference</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Abletech developers spent a few days in Melbourne for the annual Ruby Conference. There were some good speakers and we enjoyed the chance to hang out with other Rubyists.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="n4wuap1sj5pvgy5i38ygwppn" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0j_Viiij_R_Vr_R56_DHYK_4dcd18a0e8.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0j_Viiij_R_Vr_R56_DHYK_4dcd18a0e8.png" srcset="https://cms-assets.abletech.nz/thumbnail_0j_Viiij_R_Vr_R56_DHYK_4dcd18a0e8.png 245w" data-zooming-width="245" data-zooming-height="132" loading="lazy" width="245" height="132"></figure>
</div>
<p>Over 500 people attended this year’s <a href="http://rubyconf.org.au/2017" target="_blank" rel="noopener noreferrer">RubyConfAU</a>. Fourteen of them were from <a href="https://abletech.nz/">Abletech</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qdvvmkgvbzmetnspuh5vyyos" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_N93g08_RUW_1_XW_7_Rw_W_5c971e6e5c.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_N93g08_RUW_1_XW_7_Rw_W_5c971e6e5c.png" srcset="https://cms-assets.abletech.nz/thumbnail_0_N93g08_RUW_1_XW_7_Rw_W_5c971e6e5c.png 245w" data-zooming-width="245" data-zooming-height="120" loading="lazy" width="245" height="120"></figure>
</div>
<p><strong>Speakers</strong></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jylg2y3kadb21bd4rny0b747" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_U_Moj06_Kb_p5_V_fya_62ab253bf1.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_U_Moj06_Kb_p5_V_fya_62ab253bf1.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_U_Moj06_Kb_p5_V_fya_62ab253bf1.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
</div>
<ul>
<li><a href="https://gist.github.com/pat/be9514a1ead8c08d8437170b0d167dcf" target="_blank" rel="noopener noreferrer">Pat Allen: Open Source: Power and the Passion</a></li>
</ul>
<p>Pat talked about our expectations with open source code. He discussed how we could acknowledge the humans behind it by allocating resources to give back. Abletechers described Pat’s talk as “wonderfully earnest, pragmatic &amp; noble”.</p>
<ul>
<li><a href="https://twitter.com/glasnt" target="_blank" rel="noopener noreferrer">Katie McLaughlin: The Power and Responsibility of Unicode Adoption</a></li>
</ul>
<p>Katie spoke about the history and different cultural interpretations of emoji. She discussed tools that help both client-side and server-side rendering of emoji, the Unicode standard, and emoji accessibility in web applications ✨</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="oia0jjyxj8z29mxuf1e7fmhb" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_A8_T9_Mq_Dpk_Sb8_Dq_Sd_dfaf9ca1a6.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_A8_T9_Mq_Dpk_Sb8_Dq_Sd_dfaf9ca1a6.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_A8_T9_Mq_Dpk_Sb8_Dq_Sd_dfaf9ca1a6.jpg 245w" data-zooming-width="245" data-zooming-height="61" loading="lazy" width="245" height="61"></figure>
</div>
<ul>
<li>
<p><a href="https://twitter.com/hiro_asari" target="_blank" rel="noopener noreferrer">あさり Hiro Asari: Ruby, HTTP/2 and You</a></p>
</li>
<li>
<p><a href="https://twitter.com/tenderlove" target="_blank" rel="noopener noreferrer">Aaron Patterson: Defragging Ruby</a></p>
</li>
<li>
<p><a href="https://twitter.com/barrettclark" target="_blank" rel="noopener noreferrer">Barrett Clark: Simple and awesome database tricks</a></p>
</li>
<li>
<p><a href="https://twitter.com/madlep" target="_blank" rel="noopener noreferrer">Julian Doherty: Functional Programming for the Anxious Developer</a></p>
</li>
</ul>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q4rx1rkvnb4h3hcqyhvmwzjs" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0r_Wwn3_V4q_Gkd9njk_e3b2351305.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0r_Wwn3_V4q_Gkd9njk_e3b2351305.png" srcset="https://cms-assets.abletech.nz/thumbnail_0r_Wwn3_V4q_Gkd9njk_e3b2351305.png 245w" data-zooming-width="245" data-zooming-height="104" loading="lazy" width="245" height="104"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vl5t3a3os1c9zw3ehsesxe74" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_ATEI_2_Quzz_W7_Eb48_C_912e18f1e5.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_ATEI_2_Quzz_W7_Eb48_C_912e18f1e5.png" srcset="https://cms-assets.abletech.nz/thumbnail_0_ATEI_2_Quzz_W7_Eb48_C_912e18f1e5.png 245w" data-zooming-width="245" data-zooming-height="152" loading="lazy" width="245" height="152"></figure>
</div>
<p><strong>Hack Day at Envato</strong></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="eqj4139bz1eo6hyy0at5fgm4" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0ke3_Ce_Bxqq_My_Db_Ld_S_cfefcffd98.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0ke3_Ce_Bxqq_My_Db_Ld_S_cfefcffd98.png" srcset="https://cms-assets.abletech.nz/thumbnail_0ke3_Ce_Bxqq_My_Db_Ld_S_cfefcffd98.png 245w" data-zooming-width="245" data-zooming-height="105" loading="lazy" width="245" height="105"></figure>
</div>
<p>A number of Abletechers went to <a href="https://envato.com/" target="_blank" rel="noopener noreferrer">Envato</a> and played around with a few new things including Elixir, Docker/Elixir and Kubernetes.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="gw6hnqtideikzjyjqjoh4rg5" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Vmv_T_U_i2rm_XSQZS_9e14f2445d.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Vmv_T_U_i2rm_XSQZS_9e14f2445d.png" srcset="https://cms-assets.abletech.nz/thumbnail_0_Vmv_T_U_i2rm_XSQZS_9e14f2445d.png 245w" data-zooming-width="245" data-zooming-height="103" loading="lazy" width="245" height="103"></figure>
</div>
<p><strong>Elixir</strong></p>
<p>A current hot topic of conversation in the Ruby community is <a href="http://elixir-lang.org/" target="_blank" rel="noopener noreferrer">Elixir</a>. It’s a language that builds upon the matured Erlang VM so there’s a wealth of tooling and libraries.</p>
<p>Andrew Pett likes it, “it’s highly concurrent, it’s fast and uses a functional approach”.</p>
<p>Ross thinks Elixir has promise but is still a bit of a corner case. “It’ll beat the pants off Rails for speed but it would seldom be worth giving up all the other advantages of Rails for it.” Ross notes the advantages of Rails include security, conventions, maturity, community and gem landscape. “If the goal of a good stack is to rapidly and efficiently meet the changing technology needs of a business, I can’t go past Rails.”</p>
<p>Elixir is a good choice to enhance parts of an application that require speed or concurrency. Watch this space!</p>
<p><strong>Play</strong></p>
<p>There was kayaking up the Yarra River, bowling, rock-climbing, shopping trips to IKEA and the Queen Victoria Market. The coffee was strong, gelato cold and the pizzas very long.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pabfs7z3417x7d0nyttrv8hk" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_092_L5_BN_Iwmmm_Wt_Ets_6b83c299d5.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/092_L5_BN_Iwmmm_Wt_Ets_6b83c299d5.png" srcset="https://cms-assets.abletech.nz/thumbnail_092_L5_BN_Iwmmm_Wt_Ets_6b83c299d5.png 245w" data-zooming-width="245" data-zooming-height="125" loading="lazy" width="245" height="125"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="o5u4rekmvyv0pucpt1es92x5" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0xv0a_Is_Be_Un7f_Vc_Hq_ecd95bbd04.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0xv0a_Is_Be_Un7f_Vc_Hq_ecd95bbd04.png" srcset="https://cms-assets.abletech.nz/thumbnail_0xv0a_Is_Be_Un7f_Vc_Hq_ecd95bbd04.png 245w" data-zooming-width="245" data-zooming-height="111" loading="lazy" width="245" height="111"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="a152ldf36opuofvgmfniijnj" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_M_TN_54t_EQ_Mz0_I_Zrn_83e452aef0.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_M_TN_54t_EQ_Mz0_I_Zrn_83e452aef0.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_M_TN_54t_EQ_Mz0_I_Zrn_83e452aef0.jpg 245w" data-zooming-width="245" data-zooming-height="123" loading="lazy" width="245" height="123"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="elbtiqwuo5yygazezccd43hw" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_04w66tq5_U5_A9z_Sj_S7_778c715530.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/04w66tq5_U5_A9z_Sj_S7_778c715530.png" srcset="https://cms-assets.abletech.nz/thumbnail_04w66tq5_U5_A9z_Sj_S7_778c715530.png 245w" data-zooming-width="245" data-zooming-height="119" loading="lazy" width="245" height="119"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l34frbk71q715qibk2mi7npm" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_04_YS_8j_Zz_Q_Ml1k_Bhiq_2f5b88e2ca.png sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/04_YS_8j_Zz_Q_Ml1k_Bhiq_2f5b88e2ca.png" srcset="https://cms-assets.abletech.nz/thumbnail_04_YS_8j_Zz_Q_Ml1k_Bhiq_2f5b88e2ca.png 245w" data-zooming-width="245" data-zooming-height="128" loading="lazy" width="245" height="128"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>16 Months Remote, In Review</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>I lived 30% of my working life 13,000km from home</h2>
<p>Last year, my partner and I moved to Canada, mainly to explore the other side of the world from New Zealand. We wanted to be away one or two years, but we didn’t have funds to do that without working.</p>
<p>Fortunately, Abletech was keen to have their second go at having someone work remotely.</p>
<h2>Getting started</h2>
<p>I had been working at Abletech for about 18 months, and I’d worked with people from Abletech for around three-and-a-half years. While I knew I was working with some of the best people I know, I still wasn’t sure how remote work would <em><strong>work</strong></em>. I did some reading on the subject, and found many contradictory opinions and experiences.</p>
<p>After a bit of discussion, we came up with three things that would be necessary in order to make remote work as much of a success as possible:</p>
<ul>
<li>
<p><strong>Join a co-working space</strong> — there was a prevalent opinion that doing so would help with staying focused and engaged in work, while also keeping some face to face interaction in my daily life</p>
</li>
<li>
<p><strong>Organise work pre-emptively</strong> — this would be important to keep me busy</p>
</li>
<li>
<p><strong>Catch up weekly</strong> — so that we can iterate on any problems we encountered on either side</p>
</li>
</ul>
<h2>Projects</h2>
<p>During my time in Calgary, I worked with three different clients with very different setups;</p>
<ol>
<li>
<p><strong>Large team</strong> of Wellington-based front-end developers, doing Angular in a kanban stream fashion</p>
</li>
<li>
<p><strong>Small team</strong> of (mostly) Wellington-based developers, working on Ruby microservices, using scrum</p>
</li>
<li>
<p><strong>Medium-sized team</strong> of dispersed developers, working on Elixir microservices and React frontends, using <a href="https://www.scaledagileframework.com/" target="_blank" rel="noopener noreferrer">SAFE</a></p>
</li>
</ol>
<p>One important commonality between all three of these projects is that I’d had previous exposure to the parties involved.</p>
<p><strong>The large team</strong>
Interacting with this team was a breeze. We used Slack, in real-time, and were able to communicate clearly and promptly. Initially I had difficulty keeping busy but we resolved this by improving the story pipeline for our team. Out of the three clients this one had the most amount of friction, due to their networking which resulted in a functional, but slow working environment.</p>
<p><strong>The small team</strong>
I had previously worked with this team and overall it was good after some teething trouble. I was working with some codebases I was familiar with, and some that I was not. Unfortunately the ones I was not familiar with, were the ones that I needed to spend the most time in. To add further pain, the expert in this codebase left this client’s company about a week after I started the work.</p>
<p>It was difficult to get up-to-date with the business logic as I had to find people who could answer my questions. My timezone was offset by a few hours so doing this proved a bit challenging. Once I’d figured out this information, I slid into a good routine. Their scrum backlog was well maintained which made the workflow slick.</p>
<p><strong>The medium-sized team</strong>
Projects with this team were my main source of work for the 16 months I worked remotely. We covered a number of different projects. Initially I wasn’t at all familiar with the codebases, or business logic. I spent more time getting information, and clarifying requirements, than expected. Also, I had some teething issues in getting enough work queued-up for Fridays, which were Saturdays in New Zealand.</p>
<p>We tracked these issues with weekly catch-ups and things improved as time went on.</p>
<h2>Processes</h2>
<h3><strong>The bad</strong></h3>
<p>As alluded to above, not everything ran smoothly all the time. Issues usually boiled down to <em>running out of ready work</em> and becoming <em>too blocked to continue</em>. These issues would usually be quick to resolve in-person, in-timezone. But doing so remotely, in a different timezone, was challenging at times.</p>
<p>My solution to these issues ended up being ‘time leniency’ on both sides. In order to oil the engine, so to speak, I would spend a small amount of time, late at night, answering questions, asking questions, and ensuring there was work ready to continue the following morning. Sometimes this night-work wasn’t necessary. Other times it was a five-minute job or a half-hour meeting at 10pm. This was a fair solution for me because the advantages of working remotely outweighed this inconvenience.</p>
<h3><strong>The good</strong></h3>
<p>By spending that time making sure I was ready for the next day, I was setting myself up for convenience and success. Usually I could start work at a time of day that suited my partner and I. If I had to run errands in the morning, or if I needed a little extra sleep that day, I would simply start a little later. If I was feeling fresh in the morning, I’d get started early and finish early, so that I had more time to spend in the evening relaxing and exploring the city. Not only did the flexibility leave me feeling much healthier in terms of work/life balance, it was a balance that I could adjust on the fly as necessary.</p>
<p>Abletech was very happy for me to take holidays and explore. The main purpose of the trip was to travel around Canada &amp; USA. We took multiple trips totalling around three months. Our last roadtrip lasted about one-and-a-half months and spanned the whole east coast of North America. This was a once in a lifetime experience for us. I couldn’t have done it without Abletech being generous with the time it took.</p>
<h3><strong>Ups and downs balanced</strong></h3>
<p>By operating out of a co-working space, I was also able to meet local people and become more involved in Calgary. It was much easier to feel ‘at home’, in a working sense, by having somewhere to go each day.</p>
<p>The timezone difference between Calgary and New Zealand varied between four and six hours (plus a day) depending on the time of year. While introducing this gap added challenges, it became less of an issue as all parties became used to it. When the gap was only four hours a day, I could work a little later in the day, and have very similar hours to those in the office in New Zealand. That was pretty comfortable.</p>
<p>After getting past teething issues in each project, remote work operated very smoothly for me. The advantages outweighed the disadvantages considerably. I wouldn’t hesitate to do it again.</p>
<h3><strong>Recommendations</strong></h3>
<p>My suggestions for optimising working remotely would go back to the three initial points I made in this article. But stick to them fiercely. The better you can apply them, the better the outcome in my view.*</p>
<ul>
<li>Disclaimer: Your mileage will definitely vary.</li>
</ul>
<p>It’s helpful to know the people you’ll be working with, before you start working remotely. It may have been more difficult for me if I was not familiar with my three teams.</p>
<p>Communication is very important. It could potentially cause trouble if you don’t get it right. A word or an emoji gone awry could be an issue, especially with people you don’t know.</p>
<h2>My experience as a whole</h2>
<p>Having now worked remotely for around 30% of my work-life I can say it’s been a time of good learning for me. It was an extremely valuable experience and allowed me to grow in ways that I didn’t expect it to.</p>
<p>I’ve become more confident about making decisions within my realm, and more confident about deferring decisions that need to be discussed further.</p>
<p>I value communication more. I’m now more comfortable talking to those I do know, as well as those I don’t know. I’m an introvert so this is an accomplishment-and-a-half for me.</p>
<p>I know more about how I like to work, what drives me, what motivates me, what keeps me back, and how to deal with these things.</p>
<p>That’s not to say that working remotely caused all those improvements but I believe it sped up the process. I’ve matured as a developer and as a person.</p>
<p>In terms of travel, we were able to see a significant amount of the west and east coasts of North America. We saw the Canadian Rocky Mountains and surrounding areas. We tried new things like snow-mobiling, skiing and snow shoeing, and had a blast. These are memories I’ll cherish forever.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="i1l2yb6lfi49ge4dacqwhz7h" alt="Somewhere in the wild, Golden, BC, Canada" data-big=https://cms-assets.abletech.nz/large_1_Jx_Faq_Fno15_D81_Ni586_Wvg_ea93c8510b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Jx_Faq_Fno15_D81_Ni586_Wvg_ea93c8510b.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Jx_Faq_Fno15_D81_Ni586_Wvg_ea93c8510b.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Jx_Faq_Fno15_D81_Ni586_Wvg_ea93c8510b.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Jx_Faq_Fno15_D81_Ni586_Wvg_ea93c8510b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Jx_Faq_Fno15_D81_Ni586_Wvg_ea93c8510b.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Somewhere in the wild, Golden, BC, Canada</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tta8sjdw8qu5xypnv5hb844v" alt="Peggy’s Cove, Nova Scotia" data-big=https://cms-assets.abletech.nz/large_1g5_WD_Xy_R_If_Cgvf4esy_Gz2_Gw_e84ec77d4d.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1g5_WD_Xy_R_If_Cgvf4esy_Gz2_Gw_e84ec77d4d.jpeg" srcset="https://cms-assets.abletech.nz/large_1g5_WD_Xy_R_If_Cgvf4esy_Gz2_Gw_e84ec77d4d.jpeg 1000w, https://cms-assets.abletech.nz/small_1g5_WD_Xy_R_If_Cgvf4esy_Gz2_Gw_e84ec77d4d.jpeg 500w, https://cms-assets.abletech.nz/medium_1g5_WD_Xy_R_If_Cgvf4esy_Gz2_Gw_e84ec77d4d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1g5_WD_Xy_R_If_Cgvf4esy_Gz2_Gw_e84ec77d4d.jpeg 245w" data-zooming-width="1000" data-zooming-height="486" loading="lazy" width="1000" height="486"></figure>
</div>
<p><em>Peggy’s Cove, Nova Scotia</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rhoza2xto645wi633f2j2lul" alt="Sulphur Mountain, Banff" data-big=https://cms-assets.abletech.nz/large_16v_Uob_C_Ry_P_Yf_AKD_Zm_e5w_LQ_94363da8b7.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/16v_Uob_C_Ry_P_Yf_AKD_Zm_e5w_LQ_94363da8b7.jpeg" srcset="https://cms-assets.abletech.nz/large_16v_Uob_C_Ry_P_Yf_AKD_Zm_e5w_LQ_94363da8b7.jpeg 1000w, https://cms-assets.abletech.nz/small_16v_Uob_C_Ry_P_Yf_AKD_Zm_e5w_LQ_94363da8b7.jpeg 500w, https://cms-assets.abletech.nz/medium_16v_Uob_C_Ry_P_Yf_AKD_Zm_e5w_LQ_94363da8b7.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_16v_Uob_C_Ry_P_Yf_AKD_Zm_e5w_LQ_94363da8b7.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Sulphur Mountain, Banff</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h2hcsxj81hp9y9k2ln3poz4o" alt="Lake Louise" data-big=https://cms-assets.abletech.nz/large_10q_Z1_Xbq0p_IJ_Dfn_Rq_RMZ_Tfw_f54fb0f91a.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/10q_Z1_Xbq0p_IJ_Dfn_Rq_RMZ_Tfw_f54fb0f91a.jpeg" srcset="https://cms-assets.abletech.nz/large_10q_Z1_Xbq0p_IJ_Dfn_Rq_RMZ_Tfw_f54fb0f91a.jpeg 1000w, https://cms-assets.abletech.nz/small_10q_Z1_Xbq0p_IJ_Dfn_Rq_RMZ_Tfw_f54fb0f91a.jpeg 500w, https://cms-assets.abletech.nz/medium_10q_Z1_Xbq0p_IJ_Dfn_Rq_RMZ_Tfw_f54fb0f91a.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_10q_Z1_Xbq0p_IJ_Dfn_Rq_RMZ_Tfw_f54fb0f91a.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Lake Louise</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="cteae6rr3be6wm8ttwxt6v8y" alt="Zion National Park" data-big=https://cms-assets.abletech.nz/large_1_Dfb7n74_w_Qngclruls1_n_A_5ad2280f18.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Dfb7n74_w_Qngclruls1_n_A_5ad2280f18.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Dfb7n74_w_Qngclruls1_n_A_5ad2280f18.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Dfb7n74_w_Qngclruls1_n_A_5ad2280f18.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Dfb7n74_w_Qngclruls1_n_A_5ad2280f18.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Dfb7n74_w_Qngclruls1_n_A_5ad2280f18.jpeg 245w" data-zooming-width="1000" data-zooming-height="375" loading="lazy" width="1000" height="375"></figure>
</div>
<p><em>Zion National Park</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>10 Reasons why — Ruby on Rails</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Eleven years ago we were involved in the emergence of a brand new technology: Ruby on Rails.</h2>
<p>In July 2006, Abletech wrote the following blog post introducing and explaining Ruby on Rails. The article looks at the thinking behind adopting Ruby on Rails, and the advantages and disadvantages of using it.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ok0e1tddk54d7gijacc051xi" alt="Abletech back in the old days" data-big=https://cms-assets.abletech.nz/medium_1_AG_81_S_Aa_RGM_9wr_y0_Yvywcw_cd3e128952.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_AG_81_S_Aa_RGM_9wr_y0_Yvywcw_cd3e128952.jpeg" srcset="https://cms-assets.abletech.nz/small_1_AG_81_S_Aa_RGM_9wr_y0_Yvywcw_cd3e128952.jpeg 500w, https://cms-assets.abletech.nz/medium_1_AG_81_S_Aa_RGM_9wr_y0_Yvywcw_cd3e128952.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_AG_81_S_Aa_RGM_9wr_y0_Yvywcw_cd3e128952.jpeg 208w" data-zooming-width="750" data-zooming-height="563" loading="lazy" width="750" height="563"></figure>
</div>
<p><em>Abletech back in the old days</em></p>
<h3>July 2006:</h3>
<p>Your development team has been frustrating you, projects start and four weeks later the development team is still developing the framework, your budget is running out.</p>
<blockquote>
<h4>You want productivity but just don’t know how to get it.</h4>
</blockquote>
<p>Is there an answer? One of your developers keeps mentioning this great framework called ‘Ruby on Rails’. Is this the answer?</p>
<h2>This article discusses the positives and the negatives in moving to this new technology</h2>
<ol>
<li>
<p>Ruby on Rails provides a consistent approach to building web applications with an out of the box architecture. Traditionally starting a new web application is a fairly heavy weight process, you typically need to survey and choose your various software components to solve the common architectural problems of persistence, logging, build scripts, application configuration, web tier components and workflow. With the Rails framework these decisions are already made for you, so you can get on to understanding the business problem and quickly build a working system. You become productive in minutes not weeks or months.</p>
</li>
<li>
<p>In a Rails application, a pragmatic philosophy of convention over configuration is taken, this is apparent in all layers of the architecture with the highest productivity gains noticeable in the relationship between the model and the database. Once the developer understands the rules and constraints, Rails magically connects your view to your controller and model, and your model to the database. You don’t need generators or specialised tools to manage this, it all just works.</p>
</li>
<li>
<p>Unlike other productive web scripting languages, Ruby is a fully featured object-oriented language. Ruby also adds additional power with mix-ins modules which contain independent code to inject into your classes, blocks and closures simplifying client code behaviour. Its dynamic nature gives it power beyond static languages such as .NET and java, and the benefits are apparent by how the Rails framework has been put together itself.</p>
</li>
<li>
<p>Unlike other web templating technologies, the templating technology built into Rails can be used to generate web pages, emails, xml documents or any text document that requires dynamic content.</p>
</li>
<li>
<p>Rails includes a well thought out object relationship mapping tool, ActiveRecord, which provides your answer to database persistence. Your model is seamlessly persisted to the database. Transactions, inheritance models, validation, and caching have all been thought out and are production ready. With Rails you become a lot closer to the structure of the database than traditional object-oriented development methodologies. This is a good thing as over time as the database will no doubt end up being your project’s most valuable asset.</p>
</li>
<li>
<p>Rails includes support for a variety of web technologies. Every web application needs email integration at some point and Rails provides an out of the box smart solution, and as with other Rails technologies it gives you the complete package down to configuration in development, test and production environments. Ruby on Rails also supports web services, the integration with Rails due to the dynamic nature of Ruby is simply, clean and seamless. If you are moving into the Web 2.0 space, Rails provides a rich abstracted interface to implementing AJAX operations.</p>
</li>
<li>
<p>Generally software projects do not mature if at all to the point of having a solid foundation to perform database migrations and rollbacks between environments and across development systems. However with the Rails framework you will be delighted with the implementation of database migrations for applying and rolling back database changes. You enter your update and rollback scripts in Ruby, Rails understands the current version and can move forwards or backwards to any database version.</p>
</li>
<li>
<p>For development productivity, the shorter the gap between the change and test cycle the better. In Rails, changes are reflexed immediately within the runtime environment so developers can quickly iterate between fix and test cycles without any expensive redeploys. Ruby code is also easily testable. Methods and objects can be replaced at runtime so software components can easily be tested without resorting to external tools or generators.</p>
</li>
<li>
<p>Getting started with Rails is easy as generators will propel you along. An experienced Rails developer will also become aware of numerous idioms available within the Rails framework that shared the amount of code a developer need write. Overall less code to write means lower complexity, higher productivity and less bugs.</p>
</li>
<li>
<p>Ruby has been around for a long time, the Rails framework which has deservedly propelled Ruby into the spotlight has hit version 2.3 and is not only production ready but now well supported in the community and a stack of resource available on the web. Ruby and the Rails framework is open source and well supported by a clever team of contributors.</p>
</li>
</ol>
<h3>So what are some of the cons?</h3>
<ol>
<li>
<p>If you take time to follow the Ruby examples and tutorials it may give you a false sense of productivity. They typically follow the formula of creating a database model, configuring a connection to the database and joining it up to the model controller and view by use of the generators. This is all very simple involving a dozen or so lines of code. In the real world however you will be working at higher level of complexity and will need to understand multiple facets of Ruby and the Rails framework to be productive in churning out business functionality. You will need to invest in getting up to speed with the language and framework. As Ruby is a dynamic language, more automated testing is required. Your developers will need to become more disciplined and rigorous in creating unit tests as part of their development process.</p>
</li>
<li>
<p>If the type of development you are doing is glueing together existing systems or building back end systems, be aware that Rails is optimized for building web applications, your host system or enterprise database may not have the integration module you require for Ruby UPDATE (2010) — however JRuby is now maturing and can plug the gap by leveraging legacy java libraries and provide a lower cost to more enjoyable path to legacy integration. DSLs can be engineered to remove the laborious code java developers are use to writing.</p>
</li>
</ol>
<p>After considering the pros and cons, my advice would be that if your business or application has tight timelines, you want a more powerful web application for your buck with alone with inbuilt tools which remove the pain and setup cost of an IT project to seriously suggest considering investment into the Ruby on Rails framework.</p>
<p><a href="https://abletech.nz/">That was then, this is now!</a></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Remember: Sandbox & Release Often</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>By NIAID (Swine Flu Strain Virus Particles) CC via Wikimedia Commons</h2>
<p>The latest <a href="https://www.theguardian.com/technology/2017/jun/27/petya-ransomware-cyber-attack-who-what-why-how" target="_blank" rel="noopener noreferrer">petra ramsomware virus</a> (<em>which is currently doing a global sweep disabling unpatched windows systems</em>) is a good reminder of the anti-patterns that are prevalent within IT across many large enterprises whom take a risk adverse approach to software development and deployment. It is a result of many bad smells including a slow release cycle, and lack of sandboxing separating the applications from underlying technologies. This cascades into the infrastructure team creating dependency hell and a lack of timely security updates.</p>
<h3>Don’t build for a System Platform.</h3>
<p>Your software should not be tied to the platform, use of <em>(even)</em> java, ruby, or erlang separates you from the software system and the physical platform that sits on — meaning OS version and type can vary without affecting your software runtime.</p>
<h3>Never build for a versioned browser.</h3>
<p>Building for a browser version is a ticking time bomb and will increase and eventually prevent you from applying system level updates. If you build for Chrome, this auto-updates so version patching at the browser level becomes a thing of the past.</p>
<h3>Always practice Continuous Deployment.</h3>
<p>Don’t be scared of deployments. Deploy small changes often. Lots of small deploys will keep your systems alive, iron out the small errors that clog up your exception reporting systems and allow for a much more active, alive, and finger on the pulse approach to the running of your software. Holding back on deploys from perceived risks is <strong>a no-win scenario</strong> and spirals into very infrequent large deploys that have risk all over them and are expensive to conduct.</p>
<h3>Integration Test Suite.</h3>
<p>Always build and maintain an integrated test suite that is integrated into the development and deployment process. This allows you to trust your software so that small releases won’t cause you unnecessary stress — and is an enabler of <em>continuous deployment</em>.</p>
<p>Any software that does not have an active and full test suite is basically legacy and should be in the pipeline for replacement.</p>
<h2>The Future is now</h2>
<p>I am particularly excited by serverless technology. This has the potential to isolate the runtime of the app from the server, so serverside security updates become greatly simplified. Watch this space.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Static Website Generators: Ultimate Caching?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>A love story</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="guzlypsoasdmnqp8w19fvaln" alt="*Static files handle large traffic easily. Photo cred: Sergey Pesterev (Unsplash)*" data-big=https://cms-assets.abletech.nz/large_1k_Dv1_G6_M5_Jof_E_Vml_Rfqevt_A_3a8936e7a5.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1k_Dv1_G6_M5_Jof_E_Vml_Rfqevt_A_3a8936e7a5.png" srcset="https://cms-assets.abletech.nz/large_1k_Dv1_G6_M5_Jof_E_Vml_Rfqevt_A_3a8936e7a5.png 1000w, https://cms-assets.abletech.nz/small_1k_Dv1_G6_M5_Jof_E_Vml_Rfqevt_A_3a8936e7a5.png 500w, https://cms-assets.abletech.nz/medium_1k_Dv1_G6_M5_Jof_E_Vml_Rfqevt_A_3a8936e7a5.png 750w, https://cms-assets.abletech.nz/thumbnail_1k_Dv1_G6_M5_Jof_E_Vml_Rfqevt_A_3a8936e7a5.png 245w" data-zooming-width="1000" data-zooming-height="400" loading="lazy" width="1000" height="400"></figure>
</div>
<p><strong>Static files handle large traffic easily. Photo cred: Sergey Pesterev (Unsplash)</strong></p>
<p>People generally see Static Website Generators (SWG) as solutions for blogs and company/marketing websites. They’ve, however, also been considered as an alternative way of building the web.</p>
<p>In this article, we’ll discuss the relevance of a SWG as a web application solution. A side project I worked on: (<strong>Bravo-Welly)</strong> will help demonstrate the points made. We’ll conclude with a possible way of building modern media or listing apps with a hybrid stack including a static website generator.</p>
<p>First, go have a look at the awesome project!
<a href="https://bravo-welly.nz/" target="_blank" rel="noopener noreferrer"><strong>Bravo Welly</strong>
<em>Wellington Festivals Made Simple</em>bravo-welly.nz</a></p>
<p>Here is the technology behind Bravo-Welly:</p>
<ul>
<li>
<p><a href="https://middlemanapp.com/" target="_blank" rel="noopener noreferrer"><strong>Middleman</strong></a> (Static Website Generator)</p>
</li>
<li>
<p><strong>Github pages</strong> (Free Static Hosting)</p>
</li>
<li>
<p><a href="https://github.com/turbolinks/turbolinks" target="_blank" rel="noopener noreferrer"><strong>Turbolinks</strong></a> &amp; <strong>Jquery</strong> (Javascript Libraries)</p>
</li>
<li>
<p><strong>Google Maps</strong> &amp; **Facebook SDK **(3rd Party Libraries)</p>
</li>
<li>
<p><strong>Cloudflare</strong> (Content Delivery Network)</p>
</li>
</ul>
<blockquote>
<p>The main goal of the project was to learn how to create a website that feels <strong>fast,</strong> for <strong>cheap</strong>.</p>
</blockquote>
<h3><strong>CHEAP</strong></h3>
<p>Static websites only use storage which costs nothing to host. Several services like Github even offer to host your files for free. In the end, this project <strong>could have been completely free</strong> if I did not decided to use a custom domain for it.</p>
<p>As a result the total price of that project was <strong>$29.79</strong> (excluding dev time).</p>
<h3><strong>FAST</strong></h3>
<p><strong>Caching</strong> is intimidating. It feels costly, difficult to get right and as a developer I have yet to code an application with a caching strategy. However if you are serious on speed then bite the bullet and just do it.</p>
<p><strong>Middleman</strong> in my eyes represents the simplest caching solution. You can’t beat static files. It is reliable, easy to understand, fast to serve and easy to scale.</p>
<p>Load testing the app with 100 concurrent users during 6000 requests with a delay of 1 second per request shows no failed transactions.</p>
<pre><code class="hljs"><span class="hljs-string">`siege</span> <span class="hljs-string">-r</span> <span class="hljs-number">20</span> <span class="hljs-string">-c</span> <span class="hljs-number">100</span> <span class="hljs-string">-d</span> <span class="hljs-number">1</span> <span class="hljs-string">-i</span> <span class="hljs-string">-f</span> <span class="hljs-string">~/urls.txt`</span>

<span class="hljs-attr">Transactions:</span>             <span class="hljs-number">6198 </span><span class="hljs-string">hits</span>
<span class="hljs-attr">Availability:</span>             <span class="hljs-number">100.00</span> <span class="hljs-string">%</span>
<span class="hljs-attr">Elapsed time:</span>             <span class="hljs-number">128.01</span> <span class="hljs-string">secs</span>
<span class="hljs-attr">Data transferred:</span>         <span class="hljs-number">350.98</span> <span class="hljs-string">MB</span>
<span class="hljs-attr">Response time:</span>            <span class="hljs-number">1.77</span> <span class="hljs-string">secs</span>
<span class="hljs-attr">Transaction rate:</span>         <span class="hljs-number">48.42</span> <span class="hljs-string">trans/sec</span>
<span class="hljs-attr">Throughput:</span>               <span class="hljs-number">2.74</span> <span class="hljs-string">MB/sec</span>
<span class="hljs-attr">Concurrency:</span>              <span class="hljs-number">85.54</span>
<span class="hljs-attr">Successful transactions:</span>  <span class="hljs-number">6198</span>
<span class="hljs-attr">Failed transactions:</span>      <span class="hljs-number">0</span>
<span class="hljs-attr">Longest transaction:</span>      <span class="hljs-number">8.83</span>
<span class="hljs-attr">Shortest transaction:</span>     <span class="hljs-number">0.05</span>

<span class="hljs-string">ab</span> <span class="hljs-string">-n</span> <span class="hljs-number">6000</span> <span class="hljs-string">-c</span> <span class="hljs-number">100</span> <span class="hljs-string">https://bravo-welly.nz/list/</span>

<span class="hljs-attr">Concurrency Level:</span>      <span class="hljs-number">100</span>
<span class="hljs-attr">Time taken for tests:</span>   <span class="hljs-number">74.139</span> <span class="hljs-string">seconds</span>
<span class="hljs-attr">Complete requests:</span>      <span class="hljs-number">6000</span>
<span class="hljs-attr">Failed requests:</span>        <span class="hljs-number">0</span>
<span class="hljs-attr">Total transferred:</span>      <span class="hljs-number">204727990</span> <span class="hljs-string">bytes</span>
<span class="hljs-attr">HTML transferred:</span>       <span class="hljs-number">201612000</span> <span class="hljs-string">bytes</span>
<span class="hljs-attr">Requests per second:</span>    <span class="hljs-number">80.93</span> [<span class="hljs-comment">#/sec] (mean)</span>
<span class="hljs-attr">Time per request:</span>       <span class="hljs-number">1235.651</span> [<span class="hljs-string">ms</span>] <span class="hljs-string">(mean)</span>
<span class="hljs-attr">Time per request:</span>       <span class="hljs-number">12.357</span> [<span class="hljs-string">ms</span>] <span class="hljs-string">(mean</span>, <span class="hljs-string">across</span> <span class="hljs-string">all</span> <span class="hljs-string">concurrent</span> <span class="hljs-string">requests)</span>
<span class="hljs-attr">Transfer rate:</span>          <span class="hljs-number">2696.69</span> [<span class="hljs-string">Kbytes/sec</span>] <span class="hljs-string">received</span>

<span class="hljs-string">Connection</span> <span class="hljs-string">Times</span> <span class="hljs-string">(ms)</span>
              <span class="hljs-string">min</span>  <span class="hljs-string">mean</span>[<span class="hljs-string">+/-sd</span>] <span class="hljs-string">median</span>   <span class="hljs-string">max</span>
<span class="hljs-attr">Connect:</span>       <span class="hljs-number">55</span>  <span class="hljs-number">549</span> <span class="hljs-number">493.2</span>    <span class="hljs-number">458</span>    <span class="hljs-number">5648</span>
<span class="hljs-attr">Processing:</span>   <span class="hljs-number">241</span>  <span class="hljs-number">663</span> <span class="hljs-number">235.1</span>    <span class="hljs-number">633</span>    <span class="hljs-number">3270</span>
<span class="hljs-attr">Waiting:</span>      <span class="hljs-number">206</span>  <span class="hljs-number">451</span> <span class="hljs-number">217.6</span>    <span class="hljs-number">435</span>    <span class="hljs-number">3094</span>
<span class="hljs-attr">Total:</span>        <span class="hljs-number">338</span> <span class="hljs-number">1212 </span><span class="hljs-number">554.5</span>   <span class="hljs-number">1117    </span><span class="hljs-number">6513</span>
</code></pre>
<p><strong>Turbolinks</strong> has a bad press among developers however I highly recommend using it if you are serious about performance. Applications like Basecamp, Github or Shopify have a rendering time below 100ms and all use a library like Turbolinks to achieve this. There is probably a reason.
<a href="https://stories.abletech.nz/check-out-turbolinks-2abbef3241d" target="_blank" rel="noopener noreferrer"><strong>Check out Turbolinks</strong>
*Abletecher Alexandre Barret has been using Turbolinks. Sit in on this Abletech Tech Talk to find out more.*stories.abletech.nz</a></p>
<blockquote>
<h4><strong>Middleman + Turbolinks</strong> represents the simplest caching strategy possible to implement. You provide caching on both back and front ends. The website feels blasting fast.</h4>
<h4>Boom! Users are happy.</h4>
</blockquote>
<p><strong>Cloudflare</strong> in this project was not really relevant as it was mainly used to get the SSL done on a custom domain. Github pages do not provide https for custom domains. In addition, Bravo-Welly is only relevant in New Zealand and a CDN was not necessary in this case. However a CDN is a must if you want to increase your response time for users around the world. Just get in the habit of doing it.</p>
<p>Finally, it is somehow <strong>poetic</strong> to think that you can create a simple application using only HTML, CSS and Javascript files. After all this is the web in its simplest form.</p>
<blockquote>
<h4>However using a Static Website Generator comes with its drawbacks</h4>
</blockquote>
<h3>Build Time</h3>
<p>The more content and pages you need to create the more time it will take to build the website. The build time will increase linearly with the number of pages to build.</p>
<p>Bravo-Welly is a small project and has only 153 files to build. On my local machine middleman builds them in 31 seconds which is approximately** 5 pages per second.**</p>
<p>If build time increases, switching to a faster generator like <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> or implementing partial builds could be possible solutions. You do not need a complete build each time you deploy to production and you could select the files to create.</p>
<h3>Dynamic Content</h3>
<p>Displaying dynamic content with a static generator website seems counter intuitive; difficult but not impossible.</p>
<p>For example, in Bravo-Welly, each restaurant page has a review/comment section at the end of the page handled via the Facebook <strong>comments plugin</strong>. This has the benefits of delegating the social interaction between users to a third party that handles storing and displaying all the messages.</p>
<p>Also nothing stops you from performing <strong>XMLHttpRequest</strong> to get the dynamic content through an API and display it on the page.</p>
<p>Another idea is to use the <a href="https://firebase.google.com/" target="_blank" rel="noopener noreferrer"><strong>Firebase</strong></a> service to access data that gets updated on the fly with websockets. <strong>Potentially</strong>, this could be an elegant solution for progressive web apps.**</p>
<h3>Modification</h3>
<p>Any modification to content that is supposed to be static is not trivial. There is no administration panel like with a Content Management System (CMS). It requires dev knowledge on how the data is used, where it is stored and sometimes how templating works.</p>
<p>In addition, Middleman does not have any partial builds and a small change in the data used to generate the pages triggers a complete rebuild which can be slow and annoying.</p>
<blockquote>
<h4>Why not have the best of both worlds: dynamic edition and static rendering?</h4>
</blockquote>
<p>I will conclude on what I think could be an interesting stack for apps with high traffic and a high number of pages with few updates after publication. I am thinking of media apps (online newspaper), event apps (Bravo-Welly), listing apps (Shop, Ebay like, Housing).</p>
<p>Considering the work done on Bravo-Welly, I imagine that the number of edits for each page is way lower than the number of views requested for that same page.</p>
<h3>Dynamic Edition</h3>
<p>Then, we could think of having a typical ‘administration app’ that manages the content required to create all the pages via a database. This ‘administration app’ would live on a subdomain and handle a low traffic of users willing to create and update pages just like a CMS.</p>
<p>For example, a journalist writing an article for an online newspaper, an agent creating a listing for a new house to sell or an event organiser on Bravo-Welly entering burger details for a restaurant.</p>
<h3>Static Publishing</h3>
<p>On the other hand, a SWG app would be responsible for building all the pages. To create those, the SWG app would access the same database as the ‘administration app’ and use that data in templates to generate the static files, store them and serve them.</p>
<p>This could be done in bulk updates or via scheduled jobs. Live updates might not be needed and a complete rebuild every 15 minutes could be good enough.</p>
<h3>Benefits?</h3>
<p>Scaling an application with more servers can become really expensive while <strong>storage</strong> is <strong>really cheap, easy to serve and performant</strong> on a high traffic of users. It feels more work overall but also more practical and reliable once the page generation is mastered.</p>
<h3>Anyway</h3>
<p>I am pretty sure the idea is not old and has already been used in some applications. The work done on Bravo-Welly made me think that this could actually work pretty well if the app had an ‘infinite’ number of festivals with an ‘infinite’ number of events to display .</p>
<p>I could not find any companies that explicitly use that system. If you do, please comment about it, I would be keen to see a practical example of that. There are, however, some people on the internet dedicated to make this happening with what is called <strong>JAMstack</strong>. If you are interested to see how static websites can change the way you program, have a look at those resources:</p>
<ul>
<li>
<p><a href="https://www.staticgen.com/" target="_blank" rel="noopener noreferrer">https://www.staticgen.com/</a>: List of SWGs</p>
</li>
<li>
<p><a href="https://www.quora.com/What-is-the-concept-behind-JAMstack" target="_blank" rel="noopener noreferrer">https://www.quora.com/What-is-the-concept-behind-JAMstack</a></p>
</li>
<li>
<p><a href="https://headlesscms.org/" target="_blank" rel="noopener noreferrer">https://headlesscms.org/</a>: A List of Content Management Systems for JAMstack Sites</p>
</li>
<li>
<p><a href="https://jamstack.org/" target="_blank" rel="noopener noreferrer">https://jamstack.org/</a>: Modern web development architecture based on client-side JavaScript, reusable APIs, and prebuilt Markup.</p>
</li>
</ul>
<p>At the end it can also be unpractical and just easier to do caching right, but the idea feels interesting enough to me to write a post and share it with you.</p>
<h3>Thanks for reading till the end that was a long post.</h3>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>The Elixir equivalent of Rspec’s focus: true</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Rspec’s focus: true can be used to just run a subset of specs. To achieve something similar with Elixir, there are two main approaches that I have used.</h2>
<h2>Line number targeting</h2>
<p>You can specify the test’s line number from the command line. Here’s an example:</p>
<pre><code class="hljs"><span class="hljs-meta prompt_">$ </span><span class="language-bash">mix <span class="hljs-built_in">test</span> <span class="hljs-built_in">test</span>/address_builder/au/import/gnaf_importer_test.exs:41</span>
</code></pre>
<p>This will run just the test on <code>line 41</code> of the file.</p>
<h2>Targeting with tags</h2>
<p>You can use <em>tags</em> to mark a test (or multiple tests) to be run. Here’s an example:</p>
<pre><code class="hljs"><span class="hljs-variable">@tag :</span>focus
test <span class="hljs-string">&quot;with mixed case&quot;</span> do
  assert StringUtil.<span class="hljs-built_in">titleize</span>(<span class="hljs-string">&quot;weLLington&quot;</span>) == <span class="hljs-string">&quot;Wellington&quot;</span>
end
</code></pre>
<p>You can then run your test with the <code>--only</code> option:</p>
<pre><code class="hljs"><span class="hljs-meta prompt_">$ </span><span class="language-bash">mix <span class="hljs-built_in">test</span> --only focus</span>
</code></pre>
<p>It’s also compatible with the <code>[mix_test_watch</code>](https://github.com/lpil/mix-test.watch) package, so any edits will be retested straight away.</p>
<pre><code class="hljs">$ mix test<span class="hljs-selector-class">.watch</span> <span class="hljs-attr">--only</span> focus
</code></pre>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nuop5ii5civ590yscnpbht4n" alt=""  sizes="" src="https://cms-assets.abletech.nz/1_I_Zx_H_Ex_b_VF_Ju1_F2p_QFPZ_Vg_a0ec8cda85.png" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<h2>Further reading</h2>
<ul>
<li>
<p>Learn more from the official <a href="https://hexdocs.pm/ex_unit/ExUnit.html#configure/1-options" target="_blank" rel="noopener noreferrer">ExUnit configuration options</a>.</p>
</li>
<li>
<p>Learn more about <a href="https://medium.com/@pomodoro_cc/easily-skip-exunit-tests-in-elixir-31c26b516146" target="_blank" rel="noopener noreferrer">skipping tests</a> in this article by Pomodoro.</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>From the archives: Go Bag Competition</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>We held an Emergency Preparedness competition, back in 2015. Prepare yourself for a similar event in September 2018: Show Your Go!</h2>
<p>We prepared and judged Go Bags for getting home in a civil defence emergency.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="it7jz0ofsiyl8mkzh4ob98r5" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0c_Bs_Bus_Odorrz2_V6u_0d4d5b5b65.jpg sizes="(min-width: 640px) 217px" src="https://cms-assets.abletech.nz/0c_Bs_Bus_Odorrz2_V6u_0d4d5b5b65.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0c_Bs_Bus_Odorrz2_V6u_0d4d5b5b65.jpg 217w" data-zooming-width="217" data-zooming-height="156" loading="lazy" width="217" height="156"></figure>
</div>
<h3><strong>Far and wide</strong></h3>
<p>The Abletech team live all over the place. Lately we’ve been thinking about what we would do in an emergency and how we’d get home.</p>
<p>Since the competition was announced there was a flurry of deliveries to the office. Survival blankets, first aid kits, radios and torches were ordered as we stocked up our <a href="https://t.umblr.com/redirect?z=http%3A%2F%2Fwellington.govt.nz%2F%7E%2Fmedia%2Fabout-wellington%2Femergency-management%2Ffiles%2Fgrabbag.pdf&amp;t=YmVhZGI1ZDg4Y2U2ODkzM2Q0MTZkYzcxNDg1Y2RmYTUyZTQ3NWYxMSxRUTJpTkJzeQ%3D%3D&amp;b=t%3AmFzV7dCqxbQyGJBzPvbo4w&amp;p=http%3A%2F%2Fblog.abletech.nz%2Fpost%2F122473388248%2Fgo-bag-competition" target="_blank" rel="noopener noreferrer">bags</a>.</p>
<p>The competition motivated us to get our <a href="https://t.umblr.com/redirect?z=http%3A%2F%2Fwww.getprepared.org.nz%2Fgetaway-kits&amp;t=YWEyYWNiNzhhNWU0Y2YwYTU4NGNhMzU3NmRjMDQyOWRiYmZlMzI5NixRUTJpTkJzeQ%3D%3D&amp;b=t%3AmFzV7dCqxbQyGJBzPvbo4w&amp;p=http%3A%2F%2Fblog.abletech.nz%2Fpost%2F122473388248%2Fgo-bag-competition" target="_blank" rel="noopener noreferrer">go bags</a> up to date. The judge was announced as Michael Wilson who has a keen interest in Civil Defence readiness and who was free that day :)</p>
<h2>Not all bags are equal</h2>
<p>The bags contained all sorts of surprises. Who would’ve thought that Nat carried a very sharp saw in her back pack? There was water, food, paracord, first aid kits, gloves, tools, chocolate, glow sticks, radios and torches.</p>
<h3><strong>Bear Grylls award for the most survival-ready bag</strong></h3>
<p>This award went to Andy French, who not only lives way over in Days Bay but is completely prepared to get there. The judge said Andy was “well prepared” and had “thought out his situation carefully”.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="f64i5a43vi3io2zl8htmr3wj" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_Gmc0vt_PU_Cf744t_QQ_401cd951b8.jpg sizes="(min-width: 640px) 173px" src="https://cms-assets.abletech.nz/0_Gmc0vt_PU_Cf744t_QQ_401cd951b8.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_Gmc0vt_PU_Cf744t_QQ_401cd951b8.jpg 173w" data-zooming-width="173" data-zooming-height="156" loading="lazy" width="173" height="156"></figure>
</div>
<h3><strong>Just The Basics award</strong></h3>
<p>This award was presented to Alex who doesn’t have far to go and doesn’t need much, but has enough to sort himself out perfectly well.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="plj9r5jzdpkjpef6h9dtawuy" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_02_L9p2it2_I_Nd_R7s_NT_8bbd013f9b.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/02_L9p2it2_I_Nd_R7s_NT_8bbd013f9b.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_02_L9p2it2_I_Nd_R7s_NT_8bbd013f9b.jpg 245w" data-zooming-width="245" data-zooming-height="156" loading="lazy" width="245" height="156"></figure>
</div>
<h3><strong>Paris Hilton award</strong></h3>
<p>This award was earned by Nat who surprised everyone with her generous supplies that included items both for herself and her friends. She won special mention for having a torch with the most bling.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kqrj155nblnvy8u275z4kkef" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0a_P_Dfu_NW_Ot_T_Pw_Pc98_5cf840deab.jpg sizes="(min-width: 640px) 174px" src="https://cms-assets.abletech.nz/0a_P_Dfu_NW_Ot_T_Pw_Pc98_5cf840deab.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0a_P_Dfu_NW_Ot_T_Pw_Pc98_5cf840deab.jpg 174w" data-zooming-width="174" data-zooming-height="156" loading="lazy" width="174" height="156"></figure>
</div>
<h3><strong>MacGyver award</strong></h3>
<p>This award was well deserved by Ross who had possibly the most compact yet useful kit. He got special mention for including <a href="https://t.umblr.com/redirect?z=https%3A%2F%2Fsugru.com%2F&amp;t=ODAzYTgwMjlkMzAyZjRiNTUyN2I4NjBmNjM4NDc5YjY0MDM3ZmE1YyxRUTJpTkJzeQ%3D%3D&amp;b=t%3AmFzV7dCqxbQyGJBzPvbo4w&amp;p=http%3A%2F%2Fblog.abletech.nz%2Fpost%2F122473388248%2Fgo-bag-competition" target="_blank" rel="noopener noreferrer">Sugru</a> in his bag. Whatever the situation Ross is likely to be able to fix it with the contents of his go bag.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="khfyd7bm8tin1f43541q2ksy" alt="" data-big=https://cms-assets.abletech.nz/small_0k_H6_Ey_F_Df_c7ij_FPD_760f52e1e8.jpg sizes="(min-width: 768px) 497px, (min-width: 640px) 155px" src="https://cms-assets.abletech.nz/0k_H6_Ey_F_Df_c7ij_FPD_760f52e1e8.jpg" srcset="https://cms-assets.abletech.nz/small_0k_H6_Ey_F_Df_c7ij_FPD_760f52e1e8.jpg 497w, https://cms-assets.abletech.nz/thumbnail_0k_H6_Ey_F_Df_c7ij_FPD_760f52e1e8.jpg 155w" data-zooming-width="497" data-zooming-height="500" loading="lazy" width="497" height="500"></figure>
</div>
<h3><strong>Special mention</strong></h3>
<p>There was a special award for having an extra item not usually seen in an emergency go bag. This award went to Marcus who had an entire kit for making espresso coffee, in his go bag.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uh5lu9dadn8cwhxsjicqf6ka" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_07_Pp_Oc_Ij_LH_Hgk_Y7vt_6ff7eaad2c.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/07_Pp_Oc_Ij_LH_Hgk_Y7vt_6ff7eaad2c.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_07_Pp_Oc_Ij_LH_Hgk_Y7vt_6ff7eaad2c.jpg 245w" data-zooming-width="245" data-zooming-height="149" loading="lazy" width="245" height="149"></figure>
</div>
<h3><strong>So, what does surviving mean to you?</strong></h3>
<p>How will you get to your friends/family/home/safe place/<a href="https://t.umblr.com/redirect?z=http%3A%2F%2Fwww.getprepared.org.nz%2Fcivil-defence-centres%2FWellington&amp;t=ZTE4M2JiMDkyYzFlYjdkMDIyYjA0ZmRmNjhkN2U5ZWZlMzliNDNhYixRUTJpTkJzeQ%3D%3D&amp;b=t%3AmFzV7dCqxbQyGJBzPvbo4w&amp;p=http%3A%2F%2Fblog.abletech.nz%2Fpost%2F122473388248%2Fgo-bag-competition" target="_blank" rel="noopener noreferrer">shelter</a> if you’re at work when an <a href="https://t.umblr.com/redirect?z=http%3A%2F%2Fwww.gns.cri.nz%2FHome%2FIOF%2FIt-s-Our-Fault&amp;t=YmM2Y2I0ZjNlMTA3M2U0N2U2NTNiNTE1Y2JiMTZkNjI2MTA1OTRiMixRUTJpTkJzeQ%3D%3D&amp;b=t%3AmFzV7dCqxbQyGJBzPvbo4w&amp;p=http%3A%2F%2Fblog.abletech.nz%2Fpost%2F122473388248%2Fgo-bag-competition" target="_blank" rel="noopener noreferrer">emergency</a> hits?</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="fdokg3v4yrdtsuytl5pr0l8p" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0p_Q3f4_Aw6_R_Qo_rht_adb9859774.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0p_Q3f4_Aw6_R_Qo_rht_adb9859774.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0p_Q3f4_Aw6_R_Qo_rht_adb9859774.jpg 245w" data-zooming-width="245" data-zooming-height="115" loading="lazy" width="245" height="115"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>React On Rails with Devise: Two Ways</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>React On Rails with Devise: Two Ways</h2>
<p>Hi, I’m Kate! I’ve been working as a web development intern at <a href="https://abletech.nz/">Abletech</a> for the last two months. Recently I’ve been investigating using React together with Ruby on Rails. My task was to discover common ‘gotchas’ and figure out what it would take to use React in our next project.</p>
<p>As a noob with a little experience with React, and even less with Rails, I ran into a bunch of errors and lost time trying to mush info from different tutorials together until I understood. I thought I’d write a few tutorials/explanations to help other baby devs become awesome!</p>
<p>This will be a two part tutorial series, exploring different ways to combine React with Rails.</p>
<p><strong>We are going to be making an app with basic devise functionality. A user can sign in or sign up, change their password, logout or delete their account.</strong> It’s not the sexiest example, I know. But it will give you a foundation in both React and Rails, as you’ll be passing around state and props, rewiring some Devise controllers and making api calls within a React component.</p>
<p>You can look at the <a href="https://github.com/katenorquay/react-rails-devise-tutorial" target="_blank" rel="noopener noreferrer"><strong>full repo here</strong>.</a></p>
<p>We’ll start with the most simple example: <a href="https://github.com/reactjs/react-rails" target="_blank" rel="noopener noreferrer"><strong>The react-rails gem</strong></a>. We’ll be rendering our components on the client side, without the use of a flux pattern to keep it easy. We’re going to use ES5 and Rails 5.0.2. (ES6 will be used in the second tutorial)</p>
<h3><strong>React-Rails Gem</strong></h3>
<p>The react-rails gem is a great place to start, particularly if you are new to React. The setup is pretty minimal and will be familiar for rails enthusiasts. React components are added to your assets/javascripts folder, and the JSX (that weird mix of javascript and html in your components) is transformed using the ruby babel transpiler, so it ends up in the asset pipeline, just like if you were writing coffeescript for your Rails project. Basically it’s all the goodness of React without all the horror of webpack config.</p>
<p>This light-weight gem is a great way to experiment, but in a larger application it might end up holding you back. If you’re interested in going further with React, lookout for my next tutorial with the more powerful <a href="https://github.com/shakacode/react_on_rails" target="_blank" rel="noopener noreferrer"><strong>react_on_rails gem</strong></a>.</p>
<h3><strong>Getting Started</strong></h3>
<ol>
<li>
<p>Get the gems. We’re going to need Devise, and react-rails. Bundle install them.</p>
</li>
<li>
<p>Generate a new controller called ‘Welcome’. This is going to be the root of our application. You can do this by running the command <code>rails generate controller Welcome.</code>This will create a corresponding Welcome folder inside the views. Inside this folder add a file <em>index.html.erb</em>.</p>
</li>
<li>
<p>We need to make the index route our root by adding the line <code>root: to =&gt; ‘welcome#index’</code> to the <em>routes.rb</em> file, inside the config folder. Double check that your* welcomes_controller.rb* file has an empty method called index.</p>
</li>
<li>
<p>Create a Devise model for a User. I won’t linger on this one as Devise has it’s own <a href="https://github.com/plataformatec/devise" target="_blank" rel="noopener noreferrer">getting started section on Github </a>written by smarter people than me.</p>
</li>
<li>
<p>Migrate the database with: <code>rails db:migrate</code></p>
</li>
<li>
<p>Install react with the command <code>rails g react:install</code>. This causes a bunch of great stuff to happen:</p>
</li>
</ol>
<ul>
<li>
<p><em>react</em> and <em>react_ujs</em> are added to your <em>application.js</em> file</p>
</li>
<li>
<p>A <em>components</em> directory is created in *app/assets/javascripts. *This is where we write our React components. The file <em>components.js</em> is also added here. This requires our components tree.</p>
</li>
</ul>
<ol start="5">
<li>Now we’ll write our first React component. Inside the components directory make a file called Welcome.jsx. Inside it write this:</li>
</ol>
<pre><code class="hljs"><span class="hljs-keyword">var</span> <span class="hljs-title class_">Welcome</span> = <span class="hljs-title class_">React</span>.<span class="hljs-title function_">createClass</span>({
  <span class="hljs-attr">render</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
   <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
  }
})
</code></pre>
<ol start="6">
<li>Lastly, go to <em>views/welcome/index</em> and add:</li>
</ol>
<p><code>&lt;%= react_component(‘Welcome’) &gt;</code></p>
<p>Start your server and go to your localhost to check out your new component!</p>
<blockquote>
<p><strong>Cheeky Aside</strong>
If you want to pass values from your controllers into your React components you can do so with: <code>&lt;%= react_component(‘Welcome’, props: @object) &gt;</code> the same way you pass values to your rails views.</p>
</blockquote>
<p>So there you have it! The simplest React component ever. Don’t worry, now that we’re sure everything works we’re going to add some actual functionality so this app isn’t completely pathetic.</p>
<h3><strong>Writing Components!</strong></h3>
<p>A blank page with ‘Welcome’ on it is pretty awesome, but it’s not exactly what we want. Let’s break the UI down into a story.</p>
<blockquote>
<p>The user arrives on the login page and logs in. If they are a new user we want them to be able to change to the sign up page. Once a user is logged in they should see a form that allows them to edit their password. When logged in there should be buttons for signing out or deleting the account.</p>
</blockquote>
<p>So that sounds like we have three possible pages or components inside of our main Welcome component*: *<strong>Login</strong>, <strong>Signup</strong> and <strong>Edit</strong>. We will also have two more components within the Edit component: <strong>Delete</strong> and <strong>Logout.</strong></p>
<p>As we have multiple pages to our app we’re going to need to keep track of the page we’re on, and when something changes we’ll need to re-render the page with the new content. This is a great moment to learn about React and state.</p>
<p><strong>What is State?</strong></p>
<p><em>Literally</em>: a javascript object with some component specific data in it. <em>Conceptually:</em> a description of your component at a point in time.*</p>
<p>Basically a state is a javascript object with keys and properties. The state changes as the user moves through the app, in response to their actions. Changes to the state cause your React components and their children to re-render. If this makes you concerned about speed, <a href="https://stackoverflow.com/questions/24718709/reactjs-does-render-get-called-any-time-setstate-is-called" target="_blank" rel="noopener noreferrer">read this</a> explanation of React and the Virtual DOM.</p>
<p>Let’s go back to our Welcome component and make some changes.</p>
<pre><code class="hljs"><span class="hljs-keyword">var</span> <span class="hljs-title class_">Welcome</span> = <span class="hljs-title class_">React</span>.<span class="hljs-title function_">createClass</span>({

 <span class="hljs-attr">getInitialState</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> {
   <span class="hljs-attr">page</span>: ‘login’,
  }
 },

 <span class="hljs-attr">changePage</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params">newPage</span>) {
  <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">setState</span>({
   <span class="hljs-attr">page</span>: newPage
  })
 },

 <span class="hljs-attr">render</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">switch</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">state</span>.<span class="hljs-property">page</span>) {
   <span class="hljs-keyword">case</span> ‘login’:
    <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Login</span> <span class="hljs-attr">changePage</span>=<span class="hljs-string">{this.changePage}/</span>&gt;</span></span>
   <span class="hljs-keyword">case</span> ‘signup’:
    <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Signup</span> <span class="hljs-attr">changePage</span>=<span class="hljs-string">{this.changePage}/</span>&gt;</span></span>
   <span class="hljs-keyword">case</span> ‘edit’:
    <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Edit</span> <span class="hljs-attr">changePage</span>=<span class="hljs-string">{this.changePage}/</span>&gt;</span></span>
  }
 }
});
</code></pre>
<p><strong>What’s going on here?</strong></p>
<ul>
<li>
<p>We are defining a function called getInitialState, and setting the page to ‘login’.</p>
</li>
<li>
<p>We also have a changePage function that takes newPage as a param and updates the state. Every time <code>this.setState</code> is called React re-renders the components and their children.</p>
</li>
<li>
<p>We have our render function, which now contains a switch statement. It checks the value of ‘page’ inside the state, and chooses the component to render based on that value. You can check this works by changing the value of page to ‘signup’ or ‘edit’. <strong>Make sure you have defined React components, and that they are returning something inside the render function</strong>, otherwise you’ll get mountains of errors.</p>
</li>
<li>
<p>Finally, we are passing the changePage function down to our child components, so they are able to update the page value in the application state. This function will be available to the children through <code>this.props</code></p>
</li>
</ul>
<blockquote>
<p><strong>Gotchas</strong>
Make sure that your react components have a capital letter as the class name. React uses capital letters to find components and lower case for html tags. If you’re missing a capital letter your components won’t render and there will be no error.
Want to add class based styling to your component? You need to add classes with the keyword <code>className</code> , as class is a reserved word in React.</p>
</blockquote>
<p>Now write your Login, Signup and Edit components. See if you can include buttons that update the state using the change page function.</p>
<pre><code class="hljs"><span class="hljs-keyword">var</span> <span class="hljs-title class_">Login</span> = <span class="hljs-title class_">React</span>.<span class="hljs-title function_">createClass</span>({

 <span class="hljs-attr">render</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
   <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">’email’</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">’email’/</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">’password’</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">’password’/</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span>=&gt;</span>this.props.changePage(‘signup’)}&gt;Sign   Up!<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
   )
  }
});
</code></pre>
<p>We pull the changePage function out of <code>this.props</code> and assign it to the onClick value of our button.We also have a form with a submit button that currently does nothing. We’ll get to that.</p>
<p>Now we have an app that can change pages based on the state. At this point it would be great to update our Devise controllers, so we can begin wiring up the backend with the front.</p>
<h3><strong>Updating Devise</strong></h3>
<p>Basically all we need to do is change Devise so that it responds to json requests, and doesn’t attempt to redirect to a Devise view. There is a lot of information about how to do this, and in my opinion a lot of it is overly complicated. We’re going to keep it simple. Don’t change your application.rb, or start mucking around in the initialisers config like I did.</p>
<p>We are only going to update two of the controllers.</p>
<ol>
<li>
<p>Create a <a href="https://github.com/katenorquay/react-rails-devise-tutorial/blob/master/app/controllers/registrations_controller.rb" target="_blank" rel="noopener noreferrer"><em>registrations_controller.rb</em></a> and <a href="https://github.com/katenorquay/react-rails-devise-tutorial/blob/master/app/controllers/sessions_controller.rb" target="_blank" rel="noopener noreferrer"><em>sessions_controller.rb</em></a>. Copy and paste the the linked code. Or type it out if that’s your jam. Notice we are inheriting from the appropriate Devise controllers, rather than the ApplicationController.</p>
</li>
<li>
<p>Update <em>routes.rb</em>. You need this line:</p>
</li>
</ol>
<pre><code class="hljs">devise_for <span class="hljs-meta">:users</span>, <span class="hljs-params">controllers:</span> { <span class="hljs-params">registrations:</span> ‘registrations’, <span class="hljs-params">sessions:</span> ‘sessions’ }
</code></pre>
<ol start="3">
<li>If it’s not already there add <code>protect_from_forgery with: :null_session</code> in your application controller. This will mean we need to send a CSRF token when accessing routes that need authentication which keeps the app secure.</li>
</ol>
<h3>Ajax Requests</h3>
<p>If those routes are working the last thing we have to do is use ajax to tie our app together. Let’s look at an example for logging in.</p>
<pre><code class="hljs"><span class="hljs-keyword">var</span> <span class="hljs-title class_">Login</span> = <span class="hljs-title class_">React</span>.<span class="hljs-title function_">createClass</span>({

 <span class="hljs-title function_">handleLogin</span>(<span class="hljs-params">e</span>) {
  e.<span class="hljs-title function_">preventDefault</span>()
  <span class="hljs-keyword">var</span> that = <span class="hljs-variable language_">this</span>
  <span class="hljs-keyword">var</span> userInfo = {
   <span class="hljs-attr">user</span>: {
    <span class="hljs-attr">email</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(‘email’).<span class="hljs-property">value</span>,
    <span class="hljs-attr">password</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(‘password’).<span class="hljs-property">value</span>
   }
  }
  $.<span class="hljs-title function_">ajax</span>({
   <span class="hljs-attr">type</span>: “<span class="hljs-variable constant_">POST</span>”,
   <span class="hljs-attr">url</span>: “<span class="hljs-attr">http</span>:<span class="hljs-comment">//localhost:3000/users/sign_in”,</span>
   <span class="hljs-attr">dataType</span>: <span class="hljs-string">&#x27;json&#x27;</span>,
   <span class="hljs-attr">data</span>: userInfo,
   <span class="hljs-attr">error</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) {
    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(error)
   },
   <span class="hljs-attr">success</span>: <span class="hljs-keyword">function</span> (<span class="hljs-params">res</span>) {
    that.<span class="hljs-property">props</span>.<span class="hljs-title function_">changePage</span>(‘edit’)
    that.<span class="hljs-property">props</span>.<span class="hljs-title function_">updateCurrentUser</span>(res.<span class="hljs-property">email</span>)
   },
  })
 },

<span class="hljs-attr">render</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
   <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">’email’</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">’email’/</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">’password’</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">’password’/</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{this.handleLogin}</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span>=&gt;</span>this.props.changePage(‘signup’)}&gt;Sign Up!<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>              
  )
 }
});
</code></pre>
<p>Now when our submit button is clicked, it calls a function within the Login class called handleLogin.</p>
<p>We’ve also added another function to the props. ‘updateCurrentUser’ is defined inside the Welcome component. It updates a currentUser property in the state to an email. Then we pass the function into the child Login function.</p>
<pre><code class="hljs">case ‘login’:
 <span class="hljs-keyword">return</span> &lt;Login
         changePage={<span class="hljs-keyword">this</span>.changePage}
         updateCurrentUser={<span class="hljs-keyword">this</span>.updateCurrentUser} /&gt;
</code></pre>
<p>When we hit the success callback in our ajax request our changePage and updateCurrentUser functions are called, the state is updated, and the components are re-rendered.</p>
<p>When you are writing your requests for your delete and edit account routes remember you’ll need your CSRF token. Something like this will do the trick:</p>
<pre><code class="hljs">$<span class="hljs-string">.ajaxSetup</span><span class="hljs-params">({
 headers:
 { ‘X-CSRF-TOKEN’: $(‘meta[<span class="hljs-attr">name</span>=”csrf-token”]’)</span><span class="hljs-string">.attr</span><span class="hljs-params">(‘content’)</span> }
});
</code></pre>
<blockquote>
<p><strong>Gotchas</strong>
That <code>e.preventDefault()</code> line is pretty important. If you miss it your form will try to submit and the page will be refreshed, meaning your function won’t run and state won’t update. Basically nothing will happen and it will be horrifying.</p>
</blockquote>
<h3><strong>The End</strong></h3>
<p>This should be enough to get you started with React. <a href="https://github.com/katenorquay/react-rails-devise-tutorial" target="_blank" rel="noopener noreferrer">**Or if it’s not, here’s the repo.</a>** You know how to pass state and props around between child and parent components, and how to fragment the UI into bite sized pieces. You are probably starting to see how communication between large chains of components could get messy. You might be wondering what would happen if a component tried to talk to its sibling rather than a parent or child. Keep an eye out for my next tutorial, where i’ll use the react_on_rails gem with redux to find a (semi) elegant way to deal with those questions.</p>
<h3><strong>If you’re in the mood for more</strong></h3>
<p>You probably noticed that my ajax error callbacks simply console.log errors, which isn’t particularly helpful. Use React to create a local state for the components, and update the UI with an error message if our ajax request fails. One trick I find useful for doing this is defining a class for styling with a turnery statement:</p>
<pre><code class="hljs">var errorClass = this.<span class="hljs-keyword">state</span>.loginUnsuccessful ? ‘’ : ‘hidden&#x27;
</code></pre>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Integrating third-party provider: Kratos</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>How to write an application that integrates Kratos in Go</h2>
<p>Building applications is hard, and developers need to maximise productivity, so they can focus their efforts on solving business problems. One way of achieving this is to use services provided by third-party providers. Read on for David Oram’s process of integrating Kratos in Go.</p>
<h2>Using configurable services</h2>
<p>This allows developers to offload essential application responsibilities; for example by using database services from AWS. This trend has been with us for many years, and has seen the rise in many services such as compute services, file storage and hosting. The same shift has been seen with application level services, where it has become more and more attractive to integrate services together to build business applications.</p>
<p>A common requirement for modern web based applications, is the provision for users to register, login, logout and manage their own accounts. This is where <a href="https://www.ory.sh/kratos" target="_blank" rel="noopener noreferrer">Kratos</a> comes in. Kratos is an open source project that help takes the burden off providing these services in your own applications.</p>
<h3>Benefits of integrating your app with Kratos</h3>
<ul>
<li>
<p>Providing the benefits of keeping up with security best practices</p>
</li>
<li>
<p>BYO user interface. Kratos allows you to define the look’n’feel of the UI. The user interface is implemented in your application, giving you full control</p>
</li>
<li>
<p>BYO data model. The identity data model that Kratos uses is provided by your application</p>
</li>
<li>
<p>Long term support. Kratos project is supported by some big players in the open source space such as ThoughtWorks and the Raspberry Pi project</p>
</li>
</ul>
<blockquote>
<p>This article explains how to write an application <code>kratos-selfservice-ui-go</code>, that integrates with Kratos, written in <a href="https://golang.org/" target="_blank" rel="noopener noreferrer">Go</a>. Source code for <code>kratos-selfservice-ui-go</code> is available on <a href="https://github.com/davidoram/kratos-selfservice-ui-go" target="_blank" rel="noopener noreferrer">github</a></p>
</blockquote>
<p>The structure of the application is relatively straightforward, and based on the <a href="https://github.com/ory/kratos-selfservice-ui-node" target="_blank" rel="noopener noreferrer">Kratos self service example application written in Node</a>. It provides the following self service UI pages:</p>
<ul>
<li>
<p>Registration</p>
</li>
<li>
<p>Login</p>
</li>
<li>
<p>Logout</p>
</li>
<li>
<p>User settings:</p>
</li>
</ul>
<p>— Update profile</p>
<p>— Change password</p>
<p>— Password reset</p>
<p>Once a user is logged in, they get access to the following additional page:</p>
<ul>
<li>Dashboard</li>
</ul>
<h2>Architecture</h2>
<p>For convenience a demonstration system can be run using Docker compose.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vxej1lw38lahwvoyj92w34di" alt="Architecture overview" data-big=https://cms-assets.abletech.nz/large_051_B_4_F_5_W_Lk_Mrkd_R_71a7f066ea.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/051_B_4_F_5_W_Lk_Mrkd_R_71a7f066ea.png" srcset="https://cms-assets.abletech.nz/large_051_B_4_F_5_W_Lk_Mrkd_R_71a7f066ea.png 1000w, https://cms-assets.abletech.nz/small_051_B_4_F_5_W_Lk_Mrkd_R_71a7f066ea.png 500w, https://cms-assets.abletech.nz/medium_051_B_4_F_5_W_Lk_Mrkd_R_71a7f066ea.png 750w, https://cms-assets.abletech.nz/thumbnail_051_B_4_F_5_W_Lk_Mrkd_R_71a7f066ea.png 245w" data-zooming-width="1000" data-zooming-height="580" loading="lazy" width="1000" height="580"></figure>
</div>
<p><em>Architecture overview</em></p>
<h3>The components have these functions</h3>
<ul>
<li>
<p><code>[traefik</code>](https://traefik.io/traefik/) reverse proxy presents the Kratos and self service app together as a <em>single unified website</em> running under one host. This is important because Kratos and the self-service app share state through cookies</p>
</li>
<li>
<p><code>kratos-migrate</code> service (not shown on the diagram) creates a <a href="https://www.sqlite.org/" target="_blank" rel="noopener noreferrer">sqlite</a> database for Kratos, and runs database migrations</p>
</li>
<li>
<p><code>kratos</code> is the Kratos server</p>
</li>
<li>
<p><code>[mailhog</code>](https://github.com/mailhog/MailHog) is a self-contained email server which presents a simple web UI, as well as an API which is useful for integration testing</p>
</li>
<li>
<p><code>kratos-selfservice-ui-go</code> is our Go sample application. It integrates with Kratos, presenting all the UI pages needed by Kratos, as well as providing a Dashboard page that can be accessed only when the user is logged in</p>
</li>
</ul>
<h2>Running the sample</h2>
<p>To run the sample you will need <code>[Docker](https://www.docker.com/) </code>installed, then run:</p>
<pre><code class="hljs"><span class="hljs-attribute">make quickstart</span>
</code></pre>
<p>After a few seconds you should be able to navigate to the website at <a href="http://localhost/" target="_blank" rel="noopener noreferrer">http://localhost/</a> and you should see a page like this:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="x54g3pi3fmb7qan4i1ztdwiu" alt="" data-big=https://cms-assets.abletech.nz/large_0_OG_3_1_Nz_R6_Uattby1_4cf3a65ca5.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_OG_3_1_Nz_R6_Uattby1_4cf3a65ca5.jpg" srcset="https://cms-assets.abletech.nz/large_0_OG_3_1_Nz_R6_Uattby1_4cf3a65ca5.jpg 1000w, https://cms-assets.abletech.nz/small_0_OG_3_1_Nz_R6_Uattby1_4cf3a65ca5.jpg 500w, https://cms-assets.abletech.nz/medium_0_OG_3_1_Nz_R6_Uattby1_4cf3a65ca5.jpg 750w, https://cms-assets.abletech.nz/thumbnail_0_OG_3_1_Nz_R6_Uattby1_4cf3a65ca5.jpg 245w" data-zooming-width="1000" data-zooming-height="361" loading="lazy" width="1000" height="361"></figure>
</div>
<h2>Registration</h2>
<p>Clicking on the <a href="http://localhost/auth/registration" target="_blank" rel="noopener noreferrer">Registration</a> button starts the registration flow.</p>
<p>The registration web handler code from <code>handlers/registration.go</code> is as follows:</p>
<pre><code class="hljs"><span class="hljs-comment">// Registration directs the user to a page where they can sign-up or</span>
<span class="hljs-comment">// register to use the site</span>
func (rp RegistrationParams) Registration(w http.ResponseWriter, r *http.Request) {

	<span class="hljs-comment">// Start the login flow with Kratos if required</span>
	flow := r.URL.<span class="hljs-keyword">Query</span>().<span class="hljs-built_in">Get</span>(<span class="hljs-string">&quot;flow&quot;</span>)
	<span class="hljs-keyword">if</span> flow == <span class="hljs-string">&quot;&quot;</span> {
		<span class="hljs-keyword">log</span>.Printf(<span class="hljs-string">&quot;No flow ID found in URL, initializing login flow, redirect to %s&quot;</span>, rp.FlowRedirectURL)
		http.Redirect(w, r, rp.FlowRedirectURL, http.StatusMovedPermanently)
		<span class="hljs-keyword">return</span>
	}

	<span class="hljs-comment">// Call Kratos to retrieve the login form</span>
	params := public.NewGetSelfServiceRegistrationFlowParams()
	params.SetID(flow)
	<span class="hljs-keyword">log</span>.<span class="hljs-keyword">Print</span>(<span class="hljs-string">&quot;Calling Kratos API to get self service registration&quot;</span>)
	res, <span class="hljs-keyword">err</span> := api_client.PublicClient().Public.GetSelfServiceRegistrationFlow(params)
	<span class="hljs-keyword">if</span> <span class="hljs-keyword">err</span> != nil {
		<span class="hljs-keyword">log</span>.Printf(<span class="hljs-string">&quot;Error getting self service registration flow %v, redirecting to /&quot;</span>, <span class="hljs-keyword">err</span>)
		http.Redirect(w, r, <span class="hljs-string">&quot;/&quot;</span>, http.StatusMovedPermanently)
		<span class="hljs-keyword">return</span>
	}
	dataMap := map[string]interface{}{
		<span class="hljs-string">&quot;config&quot;</span>:      res.GetPayload().Methods[<span class="hljs-string">&quot;password&quot;</span>].Config,
		<span class="hljs-string">&quot;flow&quot;</span>:        flow,
		<span class="hljs-string">&quot;fs&quot;</span>:          rp.FS,
		<span class="hljs-string">&quot;pageHeading&quot;</span>: <span class="hljs-string">&quot;Registration&quot;</span>,
	}

	<span class="hljs-keyword">if</span> <span class="hljs-keyword">err</span> = GetTemplate(registrationPage).Render(<span class="hljs-string">&quot;layout&quot;</span>, w, r, dataMap); <span class="hljs-keyword">err</span> != nil {
		ErrorHandler(w, r, <span class="hljs-keyword">err</span>)
	}
}
</code></pre>
<p>The handler is typical of the integration points that your app will have in Kratos. First it checks for the <code>flow</code> URL parameter, and if not present redirects the browser to the Kratos endpoint <code>http://127.0.0.1/self-service/registration/browser</code> which starts registration inside Kratos, and then redirects straight back to this handler in your app.</p>
<p>The next step is to call the <code>NewGetSelfServiceRegistrationFlowParams</code> Kratos API, passing the <code>flow</code> parameter, which will return a data structure representing the form structure that Kratos is expecting.</p>
<p>The data structure is as follows:</p>
<pre><code class="hljs">{
  <span class="hljs-string">&quot;action&quot;</span>: <span class="hljs-string">&quot;http://127.0.0.1/self-service/registration/methods/password?flow=373bdce4-7e06-4fad-a7a1-f5366f3bc509&quot;</span>,
  <span class="hljs-string">&quot;method&quot;</span>: <span class="hljs-string">&quot;POST&quot;</span>,
  <span class="hljs-string">&quot;fields&quot;</span>: [
    {
      <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;csrf_token&quot;</span>,
      <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;hidden&quot;</span>,
      <span class="hljs-string">&quot;required&quot;</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-string">&quot;value&quot;</span>: <span class="hljs-string">&quot;Xut/HbFdJTR2rNXsnWpznRVMezSyS7b+NUibcwIRR03+06Ag+yui/hgGaxdRM6XocWFBT7PRl4NZpdXR0pw7LA==&quot;</span>
    },
    {
      <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;password&quot;</span>,
      <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;password&quot;</span>,
      <span class="hljs-string">&quot;required&quot;</span>: <span class="hljs-literal">true</span>
    },
    {
      <span class="hljs-string">&quot;name&quot;</span>: <span class="hljs-string">&quot;traits.email&quot;</span>,
      <span class="hljs-string">&quot;type&quot;</span>: <span class="hljs-string">&quot;email&quot;</span>
    },
    <span class="hljs-string">...</span>
  ]
}
</code></pre>
<p>It may not be obvious immediately, but this data structure has been designed so that it is deliberately easy to convert into an HTML form.</p>
<p>You simply contsruct an HTML <code>&amp;lt;form&amp;gt;</code> element with the <code>action</code> and <code>method</code> attributes, and then iterate over the <code>fields</code>, turning each one into an <code>&amp;lt;input&amp;gt;</code> element with the attributes and <code>value</code> provided. The only decision you need to make are the order that the fields are displayed on the form, and any styling you want to apply.</p>
<p>Here is the resulting form:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="oo56to5ti7vx27ibsub2s3oa" alt="" data-big=https://cms-assets.abletech.nz/large_0_E_Rf_G_Dwm_Hgfi_DJ_6_Cb_43928cf4ce.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 218px" src="https://cms-assets.abletech.nz/0_E_Rf_G_Dwm_Hgfi_DJ_6_Cb_43928cf4ce.jpg" srcset="https://cms-assets.abletech.nz/large_0_E_Rf_G_Dwm_Hgfi_DJ_6_Cb_43928cf4ce.jpg 1000w, https://cms-assets.abletech.nz/small_0_E_Rf_G_Dwm_Hgfi_DJ_6_Cb_43928cf4ce.jpg 500w, https://cms-assets.abletech.nz/medium_0_E_Rf_G_Dwm_Hgfi_DJ_6_Cb_43928cf4ce.jpg 750w, https://cms-assets.abletech.nz/thumbnail_0_E_Rf_G_Dwm_Hgfi_DJ_6_Cb_43928cf4ce.jpg 218w" data-zooming-width="1000" data-zooming-height="716" loading="lazy" width="1000" height="716"></figure>
</div>
<p>I’ve deliberately entered a weak password as part of my registration. When I submitted the form, Kratos responded with the same data structure as before, but contained validation errors. We can present that to the user and allow them to respond.</p>
<p>Errors are <a href="https://www.ory.sh/kratos/docs/concepts/ui-user-interface#messages" target="_blank" rel="noopener noreferrer">coded</a> for easy translation, in this case <code>4000005</code> along with response text that described the error condition:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="r5qmhw6xcbn01w8mk0a9cls7" alt="" data-big=https://cms-assets.abletech.nz/large_0h_Vrr1_HSCBI_Xo_Ls8_J_2f9ece6a9f.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 201px" src="https://cms-assets.abletech.nz/0h_Vrr1_HSCBI_Xo_Ls8_J_2f9ece6a9f.jpg" srcset="https://cms-assets.abletech.nz/large_0h_Vrr1_HSCBI_Xo_Ls8_J_2f9ece6a9f.jpg 1000w, https://cms-assets.abletech.nz/small_0h_Vrr1_HSCBI_Xo_Ls8_J_2f9ece6a9f.jpg 500w, https://cms-assets.abletech.nz/medium_0h_Vrr1_HSCBI_Xo_Ls8_J_2f9ece6a9f.jpg 750w, https://cms-assets.abletech.nz/thumbnail_0h_Vrr1_HSCBI_Xo_Ls8_J_2f9ece6a9f.jpg 201w" data-zooming-width="1000" data-zooming-height="776" loading="lazy" width="1000" height="776"></figure>
</div>
<p>Correcting that error, and resubmitting the form, leads to a successful registration:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ejrvjqkoh1u8lkimyypnth84" alt="" data-big=https://cms-assets.abletech.nz/large_0g_Rn_P_a_Mc_Nz8_N3s_782a8c92f9.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0g_Rn_P_a_Mc_Nz8_N3s_782a8c92f9.jpg" srcset="https://cms-assets.abletech.nz/large_0g_Rn_P_a_Mc_Nz8_N3s_782a8c92f9.jpg 1000w, https://cms-assets.abletech.nz/small_0g_Rn_P_a_Mc_Nz8_N3s_782a8c92f9.jpg 500w, https://cms-assets.abletech.nz/medium_0g_Rn_P_a_Mc_Nz8_N3s_782a8c92f9.jpg 750w, https://cms-assets.abletech.nz/thumbnail_0g_Rn_P_a_Mc_Nz8_N3s_782a8c92f9.jpg 245w" data-zooming-width="1000" data-zooming-height="428" loading="lazy" width="1000" height="428"></figure>
</div>
<p>Kratos has some flexibility around what happens next, in my case I have configured Kratos to send a registration email asking the user to confirm their email address, but this is not required in order to login. By opening <a href="http://localhost:8025/" target="_blank" rel="noopener noreferrer">mailhog</a> we can view the email:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uaevpo70v25h57my3547dc7y" alt="" data-big=https://cms-assets.abletech.nz/large_0_F8_Dsk_Wzx_Mssdqgcb_c76bb86cc8.jpg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_F8_Dsk_Wzx_Mssdqgcb_c76bb86cc8.jpg" srcset="https://cms-assets.abletech.nz/large_0_F8_Dsk_Wzx_Mssdqgcb_c76bb86cc8.jpg 1000w, https://cms-assets.abletech.nz/small_0_F8_Dsk_Wzx_Mssdqgcb_c76bb86cc8.jpg 500w, https://cms-assets.abletech.nz/medium_0_F8_Dsk_Wzx_Mssdqgcb_c76bb86cc8.jpg 750w, https://cms-assets.abletech.nz/thumbnail_0_F8_Dsk_Wzx_Mssdqgcb_c76bb86cc8.jpg 245w" data-zooming-width="1000" data-zooming-height="460" loading="lazy" width="1000" height="460"></figure>
</div>
<p>After registration, the user is automatically logged in to the site.</p>
<p>Clicking on the <a href="http://127.0.0.1/dashboard" target="_blank" rel="noopener noreferrer">Dashboard</a> link passes the request through the KratoAuthMiddleware. The purpose of this middleware is to restrict access to pages that require the user to be logged in.</p>
<p>The <code>KratoAuthMiddleware</code> calls the Kratos <code>[WhoAmI</code>](https://www.ory.sh/kratos/docs/reference/api#check-who-the-current-http-session-belongs-to) endpoint with the <code>csrf_token</code> and <code>ory_kratos_session</code> cookies. <code>WhoAmiI</code> returns a <code>Session</code> object which contains a wealth of information about the logged in user.</p>
<p>Calling the <code>WhoAmiI</code> API every time a protected page is accessed, is the <a href="https://github.com/ory/kratos/discussions/1207" target="_blank" rel="noopener noreferrer">recommended approach</a>, to verifying that the session is still active. This means that Kratos has responsibility for managing the session lifecycle, see <a href="https://www.ory.sh/kratos/docs/reference/configuration" target="_blank" rel="noopener noreferrer">configuration</a> (refer to <code>session.lifespan</code>). On my laptop running everything locally this call only takes 3-8 ms, but it will be worth checking performance in your scenario with a production-sized database of users.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tyysthqr6pnhc2un1yvv5dah" alt="" data-big=https://cms-assets.abletech.nz/large_0e3_R_fnw_PX_Uykvfsl_6251d551cf.png sizes="(min-width: 1280px) 979px, (min-width: 768px) 489px, (min-width: 1024px) 734px, (min-width: 640px) 153px" src="https://cms-assets.abletech.nz/0e3_R_fnw_PX_Uykvfsl_6251d551cf.png" srcset="https://cms-assets.abletech.nz/large_0e3_R_fnw_PX_Uykvfsl_6251d551cf.png 979w, https://cms-assets.abletech.nz/small_0e3_R_fnw_PX_Uykvfsl_6251d551cf.png 489w, https://cms-assets.abletech.nz/medium_0e3_R_fnw_PX_Uykvfsl_6251d551cf.png 734w, https://cms-assets.abletech.nz/thumbnail_0e3_R_fnw_PX_Uykvfsl_6251d551cf.png 153w" data-zooming-width="979" data-zooming-height="1000" loading="lazy" width="979" height="1000"></figure>
</div>
<p>The other functions of the application are pretty self explanatory:</p>
<ul>
<li>
<p><a href="http://127.0.0.1/auth/login" target="_blank" rel="noopener noreferrer">Login</a> and <a href="http://127.0.0.1/auth/logout" target="_blank" rel="noopener noreferrer">Logout</a> provide these functions</p>
</li>
<li>
<p><a href="http://127.0.0.1/auth/settings" target="_blank" rel="noopener noreferrer">Update Profile</a> presents a page with two forms; one to update your password, and the other to update name and email address</p>
</li>
<li>
<p>There is also an <a href="http://127.0.0.1/auth/recovery" target="_blank" rel="noopener noreferrer">Account recovery</a> page, linked from the login page, for the situation when the user forgets their password</p>
</li>
</ul>
<h2>Conclusions</h2>
<p>Once you understand the general flow of the Kratos interactions, the integration work is relatively straightforward, and Kratos provides a lot of functionality and flexibility.</p>
<blockquote>
<h4>It’s a very attractive option for solving application authentication</h4>
</blockquote>
<p>Before starting it’s worth taking some time to look at all the <a href="https://www.ory.sh/kratos/docs/reference/configuration" target="_blank" rel="noopener noreferrer">configuration options</a> that Kratos provides. This will allow you to evaluate how you might migrate existing user data from another system into Kratos, or how to implement a system for your back office that would allow a helpdesk user to find users in the system and help them with login issues.</p>
<p>There is <em>much</em> more to Kratos than is covered here. I encourage you to look through the <a href="https://www.ory.sh/kratos/docs/" target="_blank" rel="noopener noreferrer">Kratos docs</a> to learn more.</p>
<h2>Implementation notes</h2>
<p>The application experiments with a few interesting features that you might use in a production quality Go app:</p>
<ul>
<li>The app is written in <a href="https://golang.org/" target="_blank" rel="noopener noreferrer">Go</a> version 1.16</li>
</ul>
<p>— All html templates are <a href="https://golang.org/pkg/embed/" target="_blank" rel="noopener noreferrer">embedded</a> in the binary. This means the entire app is contained in a single executable</p>
<p>— The Gorilla toolkit provides the URL multiplexor, useful middleware for logging and error handling, and session handling modules</p>
<p>— The <a href="https://github.com/benbjohnson/hashfs" target="_blank" rel="noopener noreferrer">HashFS</a> library allows static assets (CSS, JS) to be correctly cached by appending SHA256 hashes to filenames</p>
<p>— Kratos supply a Go client api, which makes development easier</p>
<ul>
<li>I experimented with <a href="https://tailwindcss.com/" target="_blank" rel="noopener noreferrer">Tailwind CSS</a>. I don’t normally get much chance to play with the look’n’feel of the app, so this was a fun part of the project for me</li>
</ul>
<p>— Note that I haven’t applied any steps to minimise the CSS, but I would expect that to be relatively straightforward</p>
<ul>
<li>
<p>There is a small amount of Javascript (opening and closing the menu), for which I used <a href="https://stimulus.hotwire.dev/" target="_blank" rel="noopener noreferrer">Stimulus</a>, as a simpler alternative to using something like React</p>
</li>
<li>
<p>The Docker file uses a multi-stage build to compile the app, and then produce a smaller Docker image (approx 18 Mb)</p>
</li>
<li>
<p>A comprehensive test suite for the website is written using <a href="https://www.cypress.io/" target="_blank" rel="noopener noreferrer">Cypress</a>. I focussed on integration tests, rather than a lot of unit tests, because that’s what this project was all about. The tests cover most if not all of the integration points</p>
</li>
</ul>
<p>— The application uses <code>data-cy</code> attributes as test hooks, as recommended in the <a href="https://docs.cypress.io/guides/references/best-practices#Selecting-Elements" target="_blank" rel="noopener noreferrer">cypress best practices</a></p>
<p>— Tests use the <code>mailhog</code> API to check programatically if emails are sent correctly.</p>
<p>— Tests run in Docker, so no need to install cypress</p>
<p>— It’s easy to run tests against newer (or older) browser releases, by altering the base image in the Dockerfile</p>
<p>— It’s easy to run the tests in headless or interactive mode. The interactive tests use a browser bundled with the <a href="https://github.com/cypress-io/cypress-docker-images#cypress-docker-images-" target="_blank" rel="noopener noreferrer">cypress-included</a> images, which means that it does not interfere or clash with the browser running on my laptop. See the <code>make cypress</code> target to see how that runs in conjunction with an <a href="https://en.wikipedia.org/wiki/X_Window_System" target="_blank" rel="noopener noreferrer">X</a> server running on my laptop</p>
<p>— On the downside, an extra step is required to include the custom node modules required by the tests in the docker image — see <code>make cypress-docker</code></p>
<h3>Running the tests in headless mode:</h3>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="er8s85ydntlr89ol7f7jx48i" alt="Watch a video of running the tests in headless mode" data-big=https://cms-assets.abletech.nz/large_12c_L9nx_Xc_r_Ul9_Puuevx22_A_3f0940bbd6.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 230px" src="https://cms-assets.abletech.nz/12c_L9nx_Xc_r_Ul9_Puuevx22_A_3f0940bbd6.png" srcset="https://cms-assets.abletech.nz/large_12c_L9nx_Xc_r_Ul9_Puuevx22_A_3f0940bbd6.png 1000w, https://cms-assets.abletech.nz/small_12c_L9nx_Xc_r_Ul9_Puuevx22_A_3f0940bbd6.png 500w, https://cms-assets.abletech.nz/medium_12c_L9nx_Xc_r_Ul9_Puuevx22_A_3f0940bbd6.png 750w, https://cms-assets.abletech.nz/thumbnail_12c_L9nx_Xc_r_Ul9_Puuevx22_A_3f0940bbd6.png 230w" data-zooming-width="1000" data-zooming-height="679" loading="lazy" width="1000" height="679"></figure>
</div>
<p><em>Watch a video of running the tests in headless mode</em></p>
<p>Thanks for reading! I hope you find this guide useful as you use third-party services in your applications.</p>
<h3><strong>Read more from Abletech</strong></h3>
<ul>
<li>
<p>Kate on <a href="https://abletech.nz/resource/hi-im-kate">React on Rails with Devise</a></p>
</li>
<li>
<p>Alex on <a href="https://abletech.nz/resource/pragmatic-refactoring/">refactoring code</a></p>
</li>
<li>
<p>Kate on <a href="https://abletech.nz/resource/design-for-developers/">design for developers</a></p>
</li>
<li>
<p>Alex on <a href="https://abletech.nz/resource/testing-activerecord-concerns/">testing ActiveRecord concerns</a></p>
</li>
<li>
<p>Marcus on <a href="https://abletech.nz/resource/rails-invalidauthenticitytoken-around-the-edges/">best-practice security</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Elixir Learnings #1: First Learnings</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p><em>TL;DR: Use — no-halt with <code>mix run</code> to keep an app with non-blocking processes running!</em></p>
<p>Recently I re-read <a href="https://pragprog.com/titles/elixir16/programming-elixir-1-6/" target="_blank" rel="noopener noreferrer">Programming Elixir by Dave Thomas</a>, a book that I read over a year ago when it was in an earlier state. At the time when I first read it, I remember being extremely excited by Elixir, and sharing my excitement with colleagues around me. Unfortunately Elixir wasn’t well known in my community at that point, and although at the time I played with Elixir for a while (pretty bad slack/IRC relay here: <a href="https://github.com/aspett/slackirx" target="_blank" rel="noopener noreferrer">https://github.com/aspett/slackirx</a>), I soon lost motivation, without opportunity to work with it during the day.</p>
<p>I attended RubyConf AU this year, and Elixir was popping up in most of the talks, both as a solution to some problems that people have been facing in Ruby world, and as a quip about so many people moving to Elixir.
This resparked some discussion about Elixir in the local community, and as such I decided to re-read the Programming Elixir book and get back into it.</p>
<p>One of the first things I’ve tried tackling since, is rewriting a little Ruby script I wrote a couple of months ago. The script is simple; every minute, call an API, filter the results, and output some formatted data to a CSV file.</p>
<p>My Elixir (still work in progress) solution, after reading about all this OTP (Open Telecom Platform) goodness, is as follows:</p>
<p>A supervisor which supervises two workers</p>
<ul>
<li>
<p>A ticker task</p>
</li>
<li>
<p>A GenServer class which handles ticks</p>
</li>
</ul>
<p>Which looks a bit like</p>
<pre><code class="hljs"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">App</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> <span class="hljs-title class_">Application</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start</span></span>(_type, _args) <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">import</span> <span class="hljs-title class_">Supervisor</span>.<span class="hljs-title class_">Spec</span>

    children = [
      worker(<span class="hljs-title class_">App</span>.<span class="hljs-title class_">Worker</span>, []),
      worker(<span class="hljs-title class_">Task</span>, [<span class="hljs-title class_">App</span>.<span class="hljs-title class_">Worker</span>.tick/<span class="hljs-number">0</span>])
    ]

    opts = [<span class="hljs-symbol">strategy:</span> <span class="hljs-symbol">:one_for_one</span>]
    <span class="hljs-title class_">Supervisor</span>.start_link(children, opts)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Pretty simple — the task is just calling my <code>tick</code> function, as follows</p>
<pre><code class="hljs"><span class="hljs-selector-tag">def</span> <span class="hljs-selector-tag">tick</span> <span class="hljs-selector-tag">do</span>
  <span class="hljs-selector-tag">GenServer</span><span class="hljs-selector-class">.cast</span>(__MODULE__, :tick)

  :<span class="hljs-selector-tag">timer</span><span class="hljs-selector-class">.sleep</span>(<span class="hljs-variable">@interval</span>)
  <span class="hljs-selector-tag">tick</span>
<span class="hljs-selector-tag">end</span>
</code></pre>
<p>where <code>@interval</code> is a module attribute with a value of 60000 (60 seconds).</p>
<p>And finally, the function which handles tick calls to the process, a bit like:</p>
<pre><code class="hljs">def handle_cast(:tick, <span class="hljs-keyword">state</span>) do
  fetch_data
  |&gt; filter_data <span class="hljs-comment"># A list of things to be written out</span>
  |&gt; Enum.each(&amp;write_to_file/<span class="hljs-number">1</span>)

  {:noreply, <span class="hljs-keyword">state</span>}
end
</code></pre>
<p>Awesome! Time to boot it up with <code>mix run</code>.
Huh. Why doesn’t it do anything? It just compiles, and exits.</p>
<p>Well, here’s my learning! If nothing else, this is a note to self. While developing small OTP apps like this locally, there’s a key option to <code>mix run</code>, which is <code>--no-halt</code>! The need for this stems out of having no blocking processes, resulting in the execution of the application stopping immediately after startup, even though there is a timer running.</p>
<p>Read part two about <a href="https://abletech.nz/resource/elixir-learnings-2-simple-caching">Elixir and Simple Caching</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Elixir Learnings #2: Simple caching</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <p>Recently I’ve been working on a side project to relay messages between two messaging services, starting with Slack and IRC.</p>
<p>I started this project as a pure Elixir application so that I could use some newly learnt Elixir knowledge without having to dive into Phoenix.</p>
<p>Eventually, I want to add in a web component in order to monitor and configure the application dynamically.</p>
<p>To model this problem as simply as possible, I came up with the concept of a Pipeline and a Location. A Pipeline connects two Locations and can be one-way or two-way. At first this was not persisted, and the application would boot off of a list of these pipelines. However, when taking a first pass at introducing persistence, I noticed that upon each message dispatch from one location to the other, the application needed to query the database for the pipeline. Not ideal. As this project is a bit of a learning project for me, the obvious following step to reduce these queries is to add caching.</p>
<p>In Ruby-land, when thinking of caching key-value data, I’d often look to a solution like <a href="https://redis.io/" target="_blank" rel="noopener noreferrer">Redis</a> or <a href="https://memcached.org/" target="_blank" rel="noopener noreferrer">Memcached</a>, but not Elixir or Erlang — no.
The first result when googling “elixir cache” was a library called <a href="https://github.com/zackehh/cachex" target="_blank" rel="noopener noreferrer">Cachex</a>:</p>
<blockquote>
<p>Cachex is an extremely fast in-memory key/value store with support for many useful features</p>
</blockquote>
<p>Sweet! I thought. I checked out the README, and getting it running was as simple as adding it as a dependency, and adding a worker to my application</p>
<pre><code class="hljs"><span class="hljs-function"><span class="hljs-title">worker</span><span class="hljs-params">(Cachex, [:pipeline_lookups, []])</span></span>
</code></pre>
<p>Caching was now in my application, and accessible with a very easy to use API.</p>
<pre><code class="hljs"><span class="hljs-comment"># Insert into the cache</span>
Cachex.<span class="hljs-keyword">set</span>(:pipeline_lookups, location.<span class="hljs-built_in">id</span>, location.pipeline)
<span class="hljs-comment"># And get it back out</span>
{:ok, pipeline} = Cachex.<span class="hljs-keyword">get</span>(:pipeline_lookups, location.<span class="hljs-built_in">id</span>)
</code></pre>
<p>Too easy. Way too easy. I decided to jump into the Github repository and read a little into how it worked. Cachex, as it turns out, is a (very sweet) wrapper around *ETS: Erlang Term Storage- *something I was in fact already using in my application.</p>
<p>Cachex was more than I needed, even though its API was nice. My objective is learning Elixir and Erlang, so, just as fast, out Cachex went. Because I’d wrapped Cachex in a module, it was just a matter of refactoring the internals to use the also simple ETS.</p>
<pre><code class="hljs"># Insert into ETS
<span class="hljs-meta">:ets.insert(:pipeline_lookups,</span> { location.id, location.pipeline })
# And get it back out
<span class="hljs-meta">:ets.lookup(:pipeline_lookups,</span> location.id)
</code></pre>
<p>Being able to take advantage of the existing Erlang and Elixir ecosystem has so far been wonderful. There have been numerous times where I’ve wanted some abstraction or functionality, and found that it was already implemented in Erlang years ago.</p>
<p>Needless to say, I’m enjoying my adventure in taking this application from nothing to a fully fledge OTP app with a web interface.
Lots to do- I’d better get back to it!</p>
<p>Read my <a href="https://abletech.nz/resource/elixir-learnings-1/">first post about my first learnings with Elixir</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>EV Q&A: Sounds eco-friendly in that it doesn’t use fossil fuel, but what about the batteries?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Electric vehicles don’t use fossil fuel but what about the Lithium and Cobalt? How long do batteries last? Can batteries be recycled?</h2>
<h2>Eco-friendly?</h2>
<p>Cars are not eco-friendly. Both fossil-fuel and electric powered vehicles require <strong>large amounts of energy</strong> to mine and process the raw materials. The by-products of the mining and manufacturing process are localised pollution (smog, chemicals) and global pollution (carbon emissions).</p>
<p>If the world is going to survive the climate crisis, then <strong>fewer cars</strong> and more public and ‘active’ transport is the answer.</p>
<p>At the moment, our <strong>cities and lifestyle expect car ownership</strong>. <a href="https://www.zerocarbonchallenge.nz/" target="_blank" rel="noopener noreferrer">Changes are being made</a> to reduce our dependence on the car, but while that transition is still happening, choosing an electric car instead of a fossil-fuelled car is a <strong>cleaner choice</strong>.</p>
<h2>What about Lithium and Cobalt?</h2>
<p>The most common batteries used in electric cars are <a href="https://en.wikipedia.org/wiki/Lithium-ion_battery" target="_blank" rel="noopener noreferrer">18650</a>s. As you can see below, they look similar to a standard AA sized battery — just a little larger.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pmwodquchqyttcpafs2n4cnz" alt="Common AA battery and the 18650 lithium ion battery" data-big=https://cms-assets.abletech.nz/thumbnail_1op_Ck_N8t_FA_Tdosa_Cg_MH_Hiog_5f9b13179d.png sizes="(min-width: 640px) 97px" src="https://cms-assets.abletech.nz/1op_Ck_N8t_FA_Tdosa_Cg_MH_Hiog_5f9b13179d.png" srcset="https://cms-assets.abletech.nz/thumbnail_1op_Ck_N8t_FA_Tdosa_Cg_MH_Hiog_5f9b13179d.png 97w" data-zooming-width="97" data-zooming-height="156" loading="lazy" width="97" height="156"></figure>
</div>
<p><em>Common AA battery and the 18650 lithium ion battery</em></p>
<p>These batteries are found in laptop computers, electric drills and more. Depending on the need, anywhere between a handful (laptops) and several 1000 (electric cars, buses) of these are packaged together.</p>
<p>The composition of a battery in a Tesla Model S is:</p>
<ul>
<li>
<p>Lithium</p>
</li>
<li>
<p>Cobalt</p>
</li>
<li>
<p>Aluminium</p>
</li>
<li>
<p>Graphite</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="yukshzphblv2r56l17r6r5tz" alt="" data-big=https://cms-assets.abletech.nz/small_1_UT_Ccuw_LT_5_I_Avc_S_Qzq_NTSA_094a5b3628.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_UT_Ccuw_LT_5_I_Avc_S_Qzq_NTSA_094a5b3628.png" srcset="https://cms-assets.abletech.nz/small_1_UT_Ccuw_LT_5_I_Avc_S_Qzq_NTSA_094a5b3628.png 500w, https://cms-assets.abletech.nz/thumbnail_1_UT_Ccuw_LT_5_I_Avc_S_Qzq_NTSA_094a5b3628.png 245w" data-zooming-width="500" data-zooming-height="278" loading="lazy" width="500" height="278"></figure>
</div>
<p><strong>Lithium</strong> is an abundant element, with the <a href="https://en.wikipedia.org/wiki/Lithium#Reserves" target="_blank" rel="noopener noreferrer">main sources</a> being South America, China and Australia. It is primarily extracted from <a href="https://en.wikipedia.org/wiki/Lithium#Extraction" target="_blank" rel="noopener noreferrer">brine pools</a>. The main environmental impact of lithium extraction is the <a href="https://www.kitco.com/ind/Albrecht/2014-12-16-How-Green-is-Lithium.html" target="_blank" rel="noopener noreferrer">large volume of water</a> used — often in places where water is scarce.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uzhb4ks4o9tx4ix3dakcw008" alt="" data-big=https://cms-assets.abletech.nz/small_1h_T6y_Vh_N_Kv_H3h_Qfy_WP_1_Gyyw_50a2ef73c0.png sizes="(min-width: 768px) 500px, (min-width: 640px) 234px" src="https://cms-assets.abletech.nz/1h_T6y_Vh_N_Kv_H3h_Qfy_WP_1_Gyyw_50a2ef73c0.png" srcset="https://cms-assets.abletech.nz/small_1h_T6y_Vh_N_Kv_H3h_Qfy_WP_1_Gyyw_50a2ef73c0.png 500w, https://cms-assets.abletech.nz/thumbnail_1h_T6y_Vh_N_Kv_H3h_Qfy_WP_1_Gyyw_50a2ef73c0.png 234w" data-zooming-width="500" data-zooming-height="333" loading="lazy" width="500" height="333"></figure>
</div>
<p><strong>Cobalt</strong> is a rare metal with most reserves in (DRC) Congo, which has about half the global production. The main issue with production are so called <a href="https://en.wikipedia.org/wiki/Cobalt#Democratic_Republic_of_the_Congo" target="_blank" rel="noopener noreferrer">Artisanal miners </a>— these are small scale, independent groups that use rudimentary techniques for mining the cobalt. Safety and child labour concerns have been raised.</p>
<p>Manufacturers are seeking new sources for the cobalt used in batteries. For example, Tesla in partnership with Panasonic use cobalt <a href="https://insideevs.com/news/336482/tesla-has-found-yet-another-safe-haven-for-cobalt/" target="_blank" rel="noopener noreferrer">sourced from the Philippines</a>.</p>
<p>Aluminium and graphite are common elements, with mining raising fewer environmental or political concerns.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l77we6lwkr1bxol79ev8l6bw" alt="A worker sorts through used lead acid batteries, Kenya" data-big=https://cms-assets.abletech.nz/thumbnail_1x2g76e_Up1_WZAE_Vr_Jgna_KTA_b666b8f3f1.jpeg sizes="(min-width: 640px) 117px" src="https://cms-assets.abletech.nz/1x2g76e_Up1_WZAE_Vr_Jgna_KTA_b666b8f3f1.jpeg" srcset="https://cms-assets.abletech.nz/thumbnail_1x2g76e_Up1_WZAE_Vr_Jgna_KTA_b666b8f3f1.jpeg 117w" data-zooming-width="117" data-zooming-height="156" loading="lazy" width="117" height="156"></figure>
</div>
<p><em>A worker sorts through used lead acid batteries, Kenya</em></p>
<h3>Lead acid batteries</h3>
<p>The standard battery in a **fossil-fuel car **is the <a href="https://en.wikipedia.org/wiki/Automotive_battery#Environmental_impact" target="_blank" rel="noopener noreferrer">lead-acid battery</a>. There are excellent recycling programmes for these batteries, with 99% of them recycled. Unfortunately, these batteries are part of the global waste trade from industrialised countries to developing countries.</p>
<p>When the recycling is performed incorrectly in unregulated environments, lead contamination is the result — estimates are of <a href="https://www.worstpolluted.org/projects_reports/display/65" target="_blank" rel="noopener noreferrer">12 million people affected</a>.</p>
<h2>How long do batteries last?</h2>
<p>The capacity of an electric car’s battery will slowly reduce over its lifetime. The rate of the degradation will depend on a number of factors, such as:</p>
<ul>
<li>
<p>Does it have a cooling system? Cooling systems help the battery avoid high temperatures that cause pre-mature ageing. They also can keep the battery warm during a cold winter</p>
</li>
<li>
<p>Keeping the charge level between 20% and 80% — if the battery spends a large quantity of time below 20% or above 80% charge level, then some ageing will occur</p>
</li>
<li>
<p>Does the battery have a modern chemistry? For example, some older Nissan Leafs had batteries that aged poorly</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="da6i7c0kz6r1osmi01yf2glj" alt="[Average battery health](https://dashboard.flipthefleet.org/reports/view/6dh9y#) by year of manufacture (NZ vehicles)" data-big=https://cms-assets.abletech.nz/thumbnail_1_V_Rb_TM_Kt_SSB_8_GF_Cp859x_F_Kw_45e4c296e2.png sizes="(min-width: 640px) 188px" src="https://cms-assets.abletech.nz/1_V_Rb_TM_Kt_SSB_8_GF_Cp859x_F_Kw_45e4c296e2.png" srcset="https://cms-assets.abletech.nz/thumbnail_1_V_Rb_TM_Kt_SSB_8_GF_Cp859x_F_Kw_45e4c296e2.png 188w" data-zooming-width="188" data-zooming-height="156" loading="lazy" width="188" height="156"></figure>
</div>
<p><em><a href="https://dashboard.flipthefleet.org/reports/view/6dh9y#" target="_blank" rel="noopener noreferrer">Average battery health</a> by year of manufacture (NZ vehicles)</em></p>
<p>In the chart above, you can see how the batteries age. Notice that the older 2011/2012/2013 vehicles are doing poorly, while the vehicles from 2014 onwards have stabilised.</p>
<p>Elon Musk, the <a href="https://www.express.co.uk/life-style/cars/1118689/Tesla-Model-3-battery-life-electric-car-Elon-Musk" target="_blank" rel="noopener noreferrer">Tesla CEO has stated</a> that the batteries in the latest Model 3 car should last between 300,000 and 500,000 kms.</p>
<p>Our 2016 Nissan Leaf has a battery health of 91% which matches the NZ average. I would expect this figure to slowly creep towards 80% at approximately 10 years old. At that stage, the expected range will be around 130 km, which is perfectly fine for a city car.</p>
<h2>Can batteries be recycled?</h2>
<p>Batteries contain valuable elements such as lithium and cobalt. When our battery does eventually reach the end of its life, it will be recycled with those elements being used for new batteries.</p>
<p>But before that happens, what is more likely is a tired battery will be <a href="https://www.greentechmedia.com/articles/read/the-road-to-a-thriving-second-life-ev-battery-market" target="_blank" rel="noopener noreferrer">re-used as a house battery</a>. Even with only 50% capacity, this could provide 15 kWh of energy to power a house equipped with solar panels.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ht0fb57wmul2qzsmn5krr1x2" alt="" data-big=https://cms-assets.abletech.nz/small_0_b_mk_O_Kb0_KWR_4_O_Dz_ca51cf9b66.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_b_mk_O_Kb0_KWR_4_O_Dz_ca51cf9b66.jpg" srcset="https://cms-assets.abletech.nz/small_0_b_mk_O_Kb0_KWR_4_O_Dz_ca51cf9b66.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0_b_mk_O_Kb0_KWR_4_O_Dz_ca51cf9b66.jpg 245w" data-zooming-width="500" data-zooming-height="283" loading="lazy" width="500" height="283"></figure>
</div>
<p>I expect that the batteries in our Nissan Leaf will still be actively used in 2036, this being 20 years after their manufacture.</p>
<h2>The batteries are okay</h2>
<p>There have been environmental, political and reliability issues in the history of battery manufacture. These concerns have been addressed by manufactures with new material sources and improved chemistries.</p>
<p>The life of a modern battery is expected to be long with both vehicle and home energy applications. Eventually, the old batteries will be recycled into new batteries, reducing the need for further mining.</p>
<h2>More articles from the EV Q&amp;A series</h2>
<ul>
<li>
<p><a href="https://abletech.nz/article/traveling-to-palmy-in-an-electric-vehicle">Travelling to Palmy in an Electric Vehicle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-do-the-cars-cost-more-than-petrol-cars">EV Q&amp;A — Do the cars cost more than petrol cars?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-charging-do-you-need-a-special-plug-at-home">EV Q&amp;A — Charging: do you need a special plug at home?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-if-i-go-up-a-hill-do-i-gain-power-when-i-go-down">EV Q&amp;A — If I go up a hill, do I gain power when I go down?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-is-this-ev-thing-just-a-fad-or-is-it-here-to-stay-got-evidence">EV Q&amp;A — Is this EV thing just a fad?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-is-it-gutless-on-the-hills">EV Q&amp;A — Is it gutless on the hills?</a></p>
</li>
<li>
<p><a href="mailto:nigel@ramsay.org.nz" target="_blank" rel="noopener noreferrer">Submit a question</a> for my EV Q&amp;A series</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>The guide to integrating Google MyMaps onto your website with React</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>A step-by-step guide to putting your custom map onto your website</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bv7m9lzlswkjgnskkq23yeoq" alt="" data-big=https://cms-assets.abletech.nz/medium_165_O1_Drn_B_Pk0v6_Af95g0_JAA_c84bbdc7b4.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/165_O1_Drn_B_Pk0v6_Af95g0_JAA_c84bbdc7b4.jpeg" srcset="https://cms-assets.abletech.nz/small_165_O1_Drn_B_Pk0v6_Af95g0_JAA_c84bbdc7b4.jpeg 500w, https://cms-assets.abletech.nz/medium_165_O1_Drn_B_Pk0v6_Af95g0_JAA_c84bbdc7b4.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_165_O1_Drn_B_Pk0v6_Af95g0_JAA_c84bbdc7b4.jpeg 245w" data-zooming-width="750" data-zooming-height="443" loading="lazy" width="750" height="443"></figure>
</div>
<p>If you want to put the custom map you’ve created with MyMaps onto a website you have two options:</p>
<ol>
<li>
<p>Simple — Embed it</p>
</li>
<li>
<p>Advanced — Use the Maps Javascript API</p>
</li>
</ol>
<p>This article will go over how to do the latter.</p>
<h2>Why read this?</h2>
<ul>
<li>
<p>It allows you to update your map on one service (Google MyMaps) and your website will automatically update to reflect these changes. 0 code changes required. In a client/developer partnership, often we want to give the client total control over their content, this does exactly that. You just provide the implementation.</p>
</li>
<li>
<p>Many solutions online stated you need a web server to host your KML layer files. I found a way in which you don’t, lowering the expertise required to get this working.</p>
</li>
<li>
<p>A lot of the implementation information online is contradictory and confusing. As a result, I thought it’d be nice to have a guide that made it as easy as 1,2,3.</p>
</li>
</ul>
<p>So, follow along to find out more!</p>
<p><strong>Precursor:</strong> We will be using React’s no-build app configuration — <a href="https://github.com/facebook/create-react-app" target="_blank" rel="noopener noreferrer">create-react-app</a> and the <a href="https://github.com/tomchentw/react-google-maps" target="_blank" rel="noopener noreferrer">‘react-google-maps’ </a>library which wraps the Google Maps Javascript API. As a result, you will need to have, or create, a Google Maps API Key. You can do this <a href="https://developers.google.com/maps/documentation/embed/get-api-key" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h2>A technical overview</h2>
<p>I’ve been working on a project at Abletech, a software development company, that is very good at creating geospatial solutions. A client wanted their custom map to be integrated into their website.</p>
<p>This integration uses a feature provided by Google MyMaps that allows you to export the maps to a ‘NetworkLinked’ KML or KMZ file.</p>
<blockquote>
<p>KML or Keyhole Markup Language is a form of XML for expressing geographic data. KMZ is a KML zipped.</p>
</blockquote>
<p>Hidden inside this KML file is a link that points to the maps dataset. We can use this with some other tricks to get our data to seamlessly update without you having to touch a single line of code, neat eh?</p>
<p>The only drawback is that Google caches the data of your map. When I tested this at the end of 2018, it would update the data in about an hour. At the beginning of 2019, about 10 minutes. The conclusion? Data refresh rates vary and you can’t control it. Unfortunately, setting cache expiry headers don’t affect it. Therefore if you need automatic, live updates, then this solution may not be for you.</p>
<h2>Let’s begin!</h2>
<p>To begin, we need a custom map created in MyMaps. If you would like to create your own, <a href="https://www.google.co.nz/maps/d/u/0/" target="_blank" rel="noopener noreferrer">go here</a> to do so. I’d recommend you do this to prove to yourself that the data is updating. However, for the purposes of this tutorial, I will be using the community created <a href="https://www.google.com/maps/d/u/0/viewer?hl=en&amp;mid=1R8Rw7tZTGw3YHKBTNBOh-LKU5sw&amp;ll=-3.81666561775622e-14%2C136.72924899999995&amp;z=1" target="_blank" rel="noopener noreferrer">ShipWrecks</a> map. This map is a living documentation of many of the shipwrecks that have been found all over the world.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="usacz3tocurlu8i54j6rvdla" alt="Reported Shipwrecks around Europe" data-big=https://cms-assets.abletech.nz/large_1_A_Jdyr_Sga_VP_1_Bi_H55r55_Srw_7c72433575.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 216px" src="https://cms-assets.abletech.nz/1_A_Jdyr_Sga_VP_1_Bi_H55r55_Srw_7c72433575.png" srcset="https://cms-assets.abletech.nz/large_1_A_Jdyr_Sga_VP_1_Bi_H55r55_Srw_7c72433575.png 1000w, https://cms-assets.abletech.nz/small_1_A_Jdyr_Sga_VP_1_Bi_H55r55_Srw_7c72433575.png 500w, https://cms-assets.abletech.nz/medium_1_A_Jdyr_Sga_VP_1_Bi_H55r55_Srw_7c72433575.png 750w, https://cms-assets.abletech.nz/thumbnail_1_A_Jdyr_Sga_VP_1_Bi_H55r55_Srw_7c72433575.png 216w" data-zooming-width="1000" data-zooming-height="723" loading="lazy" width="1000" height="723"></figure>
</div>
<p><em>Reported Shipwrecks around Europe</em></p>
<h2>Step One: Obtaining the KMZ file</h2>
<p>The first step in integrating MyMaps with your website is to download the KML/KMZ version of the map. If you’re using the Shipwrecks map and following along, go to this <a href="https://www.google.com/maps/d/u/0/viewer?hl=en&amp;mid=1R8Rw7tZTGw3YHKBTNBOh-LKU5sw&amp;ll=55.71178941916161%2C5.447250450964816&amp;z=5" target="_blank" rel="noopener noreferrer">LINK</a>, click the hamburger button and then ‘Download KML’.</p>
<p>If you have created your own MyMap, similarly, click the hamburger icon next to your map’s title (as below)</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bmgogtlaj1u2yn98m0gonspr" alt="" data-big=https://cms-assets.abletech.nz/large_1_No7i_KD_63o_L6qvx_V_Wy_R9q5_Q_5a0441dbd5.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_No7i_KD_63o_L6qvx_V_Wy_R9q5_Q_5a0441dbd5.png" srcset="https://cms-assets.abletech.nz/large_1_No7i_KD_63o_L6qvx_V_Wy_R9q5_Q_5a0441dbd5.png 1000w, https://cms-assets.abletech.nz/small_1_No7i_KD_63o_L6qvx_V_Wy_R9q5_Q_5a0441dbd5.png 500w, https://cms-assets.abletech.nz/medium_1_No7i_KD_63o_L6qvx_V_Wy_R9q5_Q_5a0441dbd5.png 750w, https://cms-assets.abletech.nz/thumbnail_1_No7i_KD_63o_L6qvx_V_Wy_R9q5_Q_5a0441dbd5.png 245w" data-zooming-width="1000" data-zooming-height="478" loading="lazy" width="1000" height="478"></figure>
</div>
<p>Irrespective of map choice, you will get the following popup.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="p0bt2vf89sektp6upgbcxsus" alt="" data-big=https://cms-assets.abletech.nz/medium_1q3_FZR_6c_GYX_Ea_FZDPB_Lk_T_Fw_d7bbb34824.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1q3_FZR_6c_GYX_Ea_FZDPB_Lk_T_Fw_d7bbb34824.png" srcset="https://cms-assets.abletech.nz/small_1q3_FZR_6c_GYX_Ea_FZDPB_Lk_T_Fw_d7bbb34824.png 500w, https://cms-assets.abletech.nz/medium_1q3_FZR_6c_GYX_Ea_FZDPB_Lk_T_Fw_d7bbb34824.png 750w, https://cms-assets.abletech.nz/thumbnail_1q3_FZR_6c_GYX_Ea_FZDPB_Lk_T_Fw_d7bbb34824.png 245w" data-zooming-width="750" data-zooming-height="374" loading="lazy" width="750" height="374"></figure>
</div>
<p>Even if you haven’t used custom markers on your MyMap you **MUST NOT **select the ‘Export to a .KML file’ button as the map’s data will not load.</p>
<p>You <strong>MUST</strong> select ‘Keep data up to date with network link KML’. By having this option selected, it will allow for automatic data updates.</p>
<h2>Step Two: Extracting the correct KML file</h2>
<p>You have downloaded a NetworkLinked KMZ file in step one. We need to get the URL that links to the Maps dataset. This is a little hidden. Here’s how to get it:</p>
<ol>
<li>
<p>Rename the extension from KMZ to ZIP and then unzip it.</p>
</li>
<li>
<p>You should note that a KML file will have been extracted. If you open this file with a text editor it should look something like this:</p>
</li>
</ol>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tem6h9bzzi45dz8m4ka1zi3b" alt="Networklinked KML File" data-big=https://cms-assets.abletech.nz/large_1ql_RW_Vsuln8wtdv_Tz_Af_AT_Hw_f65b70594f.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1ql_RW_Vsuln8wtdv_Tz_Af_AT_Hw_f65b70594f.png" srcset="https://cms-assets.abletech.nz/large_1ql_RW_Vsuln8wtdv_Tz_Af_AT_Hw_f65b70594f.png 1000w, https://cms-assets.abletech.nz/small_1ql_RW_Vsuln8wtdv_Tz_Af_AT_Hw_f65b70594f.png 500w, https://cms-assets.abletech.nz/medium_1ql_RW_Vsuln8wtdv_Tz_Af_AT_Hw_f65b70594f.png 750w, https://cms-assets.abletech.nz/thumbnail_1ql_RW_Vsuln8wtdv_Tz_Af_AT_Hw_f65b70594f.png 245w" data-zooming-width="1000" data-zooming-height="358" loading="lazy" width="1000" height="358"></figure>
</div>
<p><em>Networklinked KML File</em></p>
<ol start="2">
<li>You will note there is a &lt;href&gt; tag and inside that, a URL. This is the URL to the dataset of your map. Copy this URL to your clipboard. We will be using it in Step 3.</li>
</ol>
<p><strong>Note:</strong> Feel free to put this link into your browser. If you do, it will download another KMZ file that you will need to rename to a ZIP file and unzip. Once unzipped, open the doc.kml file that unzipped and you will note the data in this file reflects the up to date data on your map. Below is an example of what some of the Shipwrecks data looks like.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="wiqn8qs9eaguupuhv0warz11" alt="Shipwrecks Map Data in containing KML file" data-big=https://cms-assets.abletech.nz/large_1_R5k1k_W_Hi_BRD_Dq_F7_Jcdf_ARA_931ec8829d.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_R5k1k_W_Hi_BRD_Dq_F7_Jcdf_ARA_931ec8829d.png" srcset="https://cms-assets.abletech.nz/large_1_R5k1k_W_Hi_BRD_Dq_F7_Jcdf_ARA_931ec8829d.png 1000w, https://cms-assets.abletech.nz/small_1_R5k1k_W_Hi_BRD_Dq_F7_Jcdf_ARA_931ec8829d.png 500w, https://cms-assets.abletech.nz/medium_1_R5k1k_W_Hi_BRD_Dq_F7_Jcdf_ARA_931ec8829d.png 750w, https://cms-assets.abletech.nz/thumbnail_1_R5k1k_W_Hi_BRD_Dq_F7_Jcdf_ARA_931ec8829d.png 245w" data-zooming-width="1000" data-zooming-height="400" loading="lazy" width="1000" height="400"></figure>
</div>
<p><em>Shipwrecks Map Data in containing KML file</em></p>
<p>Great, we have that link on our clipboard, we’re finally ready to do some coding!</p>
<h2>Step Three: Google Maps API Integration</h2>
<p>For this section, I’m going to make the assumption you have a blank slate of Create-React-App installed. Next step: install the react-google-maps library which wraps the Google Maps Javascript API.</p>
<pre><code class="hljs">npm <span class="hljs-keyword">install</span> react-google-maps
</code></pre>
<p>Once completed, open up that directory in your code editor of choice. In your src/ directory you will have an App.js file with some template rendering. Remove this so your App.js looks like this:</p>
<pre><code class="hljs"><span class="hljs-keyword">import</span> <span class="hljs-type">React</span>, { <span class="hljs-type">Component</span> } from ‘react’;
<span class="hljs-keyword">import</span> ‘./<span class="hljs-type">App</span>.css’;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">App</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Component</span> </span>{
  render() {
    <span class="hljs-keyword">return</span> (
      <span class="hljs-literal">null</span>
    );
  }
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-type">App</span>;
</code></pre>
<p>Next, create a folder called <em>‘components’</em> in the /src folder and another folder inside the components folder called <em>‘Map’</em>. Inside this Map folder, create two files called ‘index.js’ and ‘Map.js’</p>
<p>Paste the following code into your index.js file.</p>
<pre><code class="hljs"><span class="hljs-keyword">export</span> { <span class="hljs-keyword">default</span> } <span class="hljs-keyword">from</span> ‘./<span class="hljs-built_in">Map</span>’
</code></pre>
<p>Your Map.js file needs a basic structure, let’s make it return the age-old classic Hello World.</p>
<pre><code class="hljs"><span class="hljs-keyword">import</span> React, { Component } <span class="hljs-keyword">from</span> ‘react’;

const <span class="hljs-built_in">Map</span> = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
 <span class="hljs-keyword">return</span> (
  &lt;h1&gt;Hello World&lt;/h1&gt;
 )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-built_in">Map</span>
</code></pre>
<p>Your App.js needs to render this ‘Map’, modify your App.js to look like this:</p>
<pre><code class="hljs"><span class="hljs-keyword">import</span> <span class="hljs-type">React</span>, { <span class="hljs-type">Component</span> } from &#x27;react&#x27;;
<span class="hljs-keyword">import</span> &#x27;./<span class="hljs-type">App</span>.css&#x27;;

<span class="hljs-keyword">import</span> <span class="hljs-type">Map</span> from &#x27;./components/<span class="hljs-type">Map</span>&#x27;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">App</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Component</span> </span>{
  render() {
    <span class="hljs-keyword">return</span> (
      &lt;<span class="hljs-type">Map</span>/&gt;
    );
  }
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-type">App</span>;
</code></pre>
<p>Great, you now have something showing. If you’re following along using the Shipwrecks example map, copy this entire code block into your Map.js file.</p>
<pre><code class="hljs"><span class="hljs-keyword">import</span> <span class="hljs-title class_">React</span>, { <span class="hljs-title class_">Component</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;react&#x27;</span>;
<span class="hljs-keyword">import</span> { compose, withProps } <span class="hljs-keyword">from</span> <span class="hljs-string">&quot;recompose&quot;</span>
<span class="hljs-keyword">const</span> {
  withScriptjs,
  withGoogleMap,
  <span class="hljs-title class_">GoogleMap</span>,
  <span class="hljs-title class_">KmlLayer</span>,
} = <span class="hljs-built_in">require</span>(<span class="hljs-string">&quot;react-google-maps&quot;</span>);

<span class="hljs-keyword">const</span> <span class="hljs-title class_">Map</span> = <span class="hljs-title function_">compose</span>(
  <span class="hljs-title function_">withProps</span>({
    <span class="hljs-attr">googleMapURL</span>: <span class="hljs-string">&quot;https://maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_API_KEY_HERE&quot;</span>,
    <span class="hljs-attr">loadingElement</span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">height:</span> `<span class="hljs-attr">100</span>%` }} /&gt;</span></span>,
    <span class="hljs-attr">containerElement</span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">height:</span> `<span class="hljs-attr">100vh</span>` }} /&gt;</span></span>,
    <span class="hljs-attr">mapElement</span>: <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">height:</span> `<span class="hljs-attr">100</span>%` }} /&gt;</span></span>,
  }),
  withScriptjs,
  withGoogleMap
)(<span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span>
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">GoogleMap</span>
    <span class="hljs-attr">defaultZoom</span>=<span class="hljs-string">{2}</span>
    <span class="hljs-attr">defaultCenter</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">lat:</span> <span class="hljs-attr">40.23164130704827</span>, <span class="hljs-attr">lng:</span> <span class="hljs-attr">15.088623999999982</span> }}
    <span class="hljs-attr">mapTypeId</span>= <span class="hljs-string">{</span>&quot;<span class="hljs-attr">terrain</span>&quot;}
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">KmlLayer</span>
      <span class="hljs-attr">url</span>=<span class="hljs-string">{</span>&quot;<span class="hljs-attr">http:</span>//<span class="hljs-attr">www.google.com</span>/<span class="hljs-attr">maps</span>/<span class="hljs-attr">d</span>/<span class="hljs-attr">u</span>/<span class="hljs-attr">0</span>/<span class="hljs-attr">kml</span>?<span class="hljs-attr">mid</span>=<span class="hljs-string">1R8Rw7tZTGw3YHKBTNBOh-LKU5sw</span>&quot; + &quot;&amp;<span class="hljs-attr">ver</span>=<span class="hljs-string">&quot; + generateRandom()}
      options={{ preserveViewport: true }}
    /&gt;
  &lt;/GoogleMap&gt;</span></span></span>
);

<span class="hljs-keyword">function</span> <span class="hljs-title function_">generateRandom</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">random</span>() * <span class="hljs-number">10000000000000000</span>
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Map</span>
</code></pre>
<p>If you’re not using the shipwrecks file, copy the block of code anyway. You will just need to make 1 modification to make it work.</p>
<ol>
<li>Replace the URL that looks like <code>http://www.google.com/maps/d/u/0/kml?mid=1R8Rw7tZTGw3YHKBTNBOh-LKU5sw</code> *with the URL I got you to copy in step two. Please make sure the ‘&amp;ver=’ + generateRandom() has not been removed.</li>
</ol>
<p>Awesome, now you will need to put in your Google Maps enabled API key. You can retrieve this from the Google Cloud Console. Once you have this, you can replace the text that says <code>YOUR_GOOGLE_API_KEY_HERE</code> with your key.</p>
<p>Now all you need to do is alter your terrain type by changing ‘*mapTypeId= {“terrain”}’ *to another type, or just remove it. You will likely also need to change your latitude, longitude and zoom levels to best fit your data. Here’s a trick for doing this:</p>
<p>Go to the Shipwrecks MyMap <a href="https://www.google.com/maps/d/u/0/viewer?hl=en&amp;mid=1R8Rw7tZTGw3YHKBTNBOh-LKU5sw&amp;ll=-3.81666561775622e-14%2C136.72924899999995&amp;z=1" target="_blank" rel="noopener noreferrer">here</a> or to your own map and look at the end of the URL you will see your latitude, longitude and zoom levels. If you move your map around you will see these values update too! Hence, an easy way to get the perfect zoom and latitude/longitude values for your map.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pf5f294oho5hila8bq39hco4" alt="" data-big=https://cms-assets.abletech.nz/medium_1_Q_Pits6_Qu_B_Aok_W_Jli_FTZR_4_A_c9ca6df065.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Q_Pits6_Qu_B_Aok_W_Jli_FTZR_4_A_c9ca6df065.png" srcset="https://cms-assets.abletech.nz/small_1_Q_Pits6_Qu_B_Aok_W_Jli_FTZR_4_A_c9ca6df065.png 500w, https://cms-assets.abletech.nz/medium_1_Q_Pits6_Qu_B_Aok_W_Jli_FTZR_4_A_c9ca6df065.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Q_Pits6_Qu_B_Aok_W_Jli_FTZR_4_A_c9ca6df065.png 245w" data-zooming-width="750" data-zooming-height="107" loading="lazy" width="750" height="107"></figure>
</div>
<h2>What this code does:</h2>
<p>This code uses the React Google Maps library to create a Map that then, adds the KML map data layer on top of it and displays it for you. It displays the map to 100% width and height of your containing div. In this example, this being the entire webpage.</p>
<p>A side note, the wise-eyed among you might have noticed the generateRandom() function appended to the end of the KmlLayer URL. This function is used for generating a cache-busting parameter. Simply, Google caches your data. This works by their servers saying ‘hey, have we received this URL in the last X amount of time? If so, send back the unchanged data instead of this new up to date data we have because it saves us time and resources in making duplicate queries that happen close together.</p>
<p>Adding a ‘?ver = RandomNumberHere’ parameter on the end of the URL means that the URL will be different every time (assuming the same number isn’t generated twice). Thus, when their servers check to see if they have seen this URL before, they haven’t, and thus, returns the up to date data. Without this function, I was not able to get the website to update seamlessly with the changes on Google MyMaps, so it’s imperative that you use this.</p>
<p>Other than this, all done! I hope this guide was clear enough. Feel free to ask any questions if you have any!</p>
<h2>Useful Documentation:</h2>
<ul>
<li>
<p><a href="https://github.com/tomchentw/react-google-maps" target="_blank" rel="noopener noreferrer">https://github.com/tomchentw/react-google-maps</a></p>
</li>
<li>
<p><a href="https://developers.google.com/maps/documentation/javascript/examples/layer-kml" target="_blank" rel="noopener noreferrer">https://developers.google.com/maps/documentation/javascript/examples/layer-kml</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Seeking the perfect pour</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>On a quest: Cam and Marcus</h2>
<p>As with many Wellingtonians we take our coffee very seriously — it seems in the tech industry even more so. Perhaps because there are analogies with making coffee and creating software. Both processes involving engineering, art and aesthetics. Hmmm — maybe I am getting a little too philosophical and all I really need is just that next caffeine shot to help solve why that spec is failing.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="syqy5ndbzdzn72qle1rnybeq" alt="" data-big=https://cms-assets.abletech.nz/large_1_Oy_F4_Yh_H_Ln8_Lrs_B8d_OU_3_K4_Q_f97100192b.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Oy_F4_Yh_H_Ln8_Lrs_B8d_OU_3_K4_Q_f97100192b.png" srcset="https://cms-assets.abletech.nz/large_1_Oy_F4_Yh_H_Ln8_Lrs_B8d_OU_3_K4_Q_f97100192b.png 1000w, https://cms-assets.abletech.nz/small_1_Oy_F4_Yh_H_Ln8_Lrs_B8d_OU_3_K4_Q_f97100192b.png 500w, https://cms-assets.abletech.nz/medium_1_Oy_F4_Yh_H_Ln8_Lrs_B8d_OU_3_K4_Q_f97100192b.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Oy_F4_Yh_H_Ln8_Lrs_B8d_OU_3_K4_Q_f97100192b.png 245w" data-zooming-width="1000" data-zooming-height="535" loading="lazy" width="1000" height="535"></figure>
</div>
<p>At <a href="https://abletech.nz/">Abletech</a>, we also take training seriously - and not just in the tech space. Several of our staff have been lucky enough to attend <a href="http://www.mojocoffee.co.nz/training/" target="_blank" rel="noopener noreferrer">advanced barista training</a> from a client of ours 😀. The following has been recorded from these sessions and contains notes taken by Cameron Fowler and myself in our quest to reach the zen like state of being able to consistently pour a perfect flat white.</p>
<h2>Grinding</h2>
<p>We use a <a href="http://www.mazzer.co.nz/catalog/doser-grinders/super-jolly" target="_blank" rel="noopener noreferrer">Mazzer</a> grinder — I also have one of these at home and they are great so these notes are for myself and others with the same grinder. If you are setting up — start by turning the blades down (slowly with the grinder on) until you hear them just hitting the metal and then wind back a quarter turn. Getting it fine-tuned after that is more trial and error to get 30 seconds of extraction. Each notch-turn results in roughly five-seconds extraction time. Moving towards smaller numbers yields a finer grind.</p>
<h2>Tamping</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mmca1290ebghfg13qafgky71" alt="" data-big=https://cms-assets.abletech.nz/large_16vkg_Xg0_U76ls_Hbc_D8_Mz_Lv_A_73c3c22736.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/16vkg_Xg0_U76ls_Hbc_D8_Mz_Lv_A_73c3c22736.jpeg" srcset="https://cms-assets.abletech.nz/large_16vkg_Xg0_U76ls_Hbc_D8_Mz_Lv_A_73c3c22736.jpeg 1000w, https://cms-assets.abletech.nz/small_16vkg_Xg0_U76ls_Hbc_D8_Mz_Lv_A_73c3c22736.jpeg 500w, https://cms-assets.abletech.nz/medium_16vkg_Xg0_U76ls_Hbc_D8_Mz_Lv_A_73c3c22736.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_16vkg_Xg0_U76ls_Hbc_D8_Mz_Lv_A_73c3c22736.jpeg 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<p>It is important to tamp directly down — with an even spread of grind so that the tamp is even across the puck which helps prevent channelling.</p>
<p>The amount of pressure applied should be about 6 kg — but consistency between shots is most important here — so that your grind, tamp-level and pressure give a consistent extraction time.</p>
<h2>Extraction</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kjxa2ru11d77huukevqmmiob" alt="" data-big=https://cms-assets.abletech.nz/large_1_IT_kv_BV_aqhw_Z_Zvv3d_A9y_A_9fc395c7f2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_IT_kv_BV_aqhw_Z_Zvv3d_A9y_A_9fc395c7f2.jpeg" srcset="https://cms-assets.abletech.nz/large_1_IT_kv_BV_aqhw_Z_Zvv3d_A9y_A_9fc395c7f2.jpeg 1000w, https://cms-assets.abletech.nz/small_1_IT_kv_BV_aqhw_Z_Zvv3d_A9y_A_9fc395c7f2.jpeg 500w, https://cms-assets.abletech.nz/medium_1_IT_kv_BV_aqhw_Z_Zvv3d_A9y_A_9fc395c7f2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_IT_kv_BV_aqhw_Z_Zvv3d_A9y_A_9fc395c7f2.jpeg 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<p>A perfect pour is glorious to watch. You should see drips forming and coming out after around five seconds. In 10 seconds you should start to see a flow. After about 30 seconds you should have about 30 ml of espresso. At this point a slight colour change will begin to occur as you have extracted the full body and solids of the coffee.</p>
<p>You basically are after a 3:2 to 2:1 ratio of espresso of grounds to extracted espresso — so if you start with 18 grams of coffee — your shot will be 30 mls.</p>
<p>The experts at Mojo recommend the house blend should be a larger shot size of 20g beans — which will extract to 36g.</p>
<p>Drips at beginning give the Sweetness — flow towards the end adds the bitterness. You need both for rounded full bodied flavour.</p>
<p>For full blends, like the Mojo house blend, you may want a few more grams of coffee (20g) to slow the extraction and extract all the flavours.</p>
<h2>Milk</h2>
<p>Temperature wise — you are looking to get the milk to 62 degrees C — You want to get a whirlpool (swirly thing going) in the milk jug. At 62 degrees you should only be able to touch the side of the jug for a few seconds — so rest your hand on the side as the milk heats up until it is the desired temperature.</p>
<p>The larger milk jugs actually make it easier to get a good milk made — but the wastage is a tradeoff to perfecting a good milk in a small milk jug. This makes a good reason to make your work colleague or client a coffee at the same time.</p>
<p>Before you start — clear the wand of water by turning the steam on high for a brief few seconds.</p>
<p>When stretching don’t let the wand separate from the milk — keep it below the milk level and rise it up as the milk rises.</p>
<p>Don’t be afraid of opening the steam on quickly — with tip 2 cm under the surface of the milk. Angle the tip of the wand so that the milk will be forced in a circular motion around the jug. If noise gets loud as the milk rises, then you need to lift the wand up higher in the jug. The sound should be a nice static sound.</p>
<p>Stretch milk more for cappuccino — less for a Flat White. If you imagine the jug as a cup, then the milk at the end of the stretch should resemble the type of drink you are making, in regards to the proportion that is foam.</p>
<h2>Pouring</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nyct12qhczc5msmlq4g3rtvf" alt="" data-big=https://cms-assets.abletech.nz/large_1h_M92_Ca_R_Miest_Dh6_MGY_4a_Wg_f90d7b7be7.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1h_M92_Ca_R_Miest_Dh6_MGY_4a_Wg_f90d7b7be7.jpeg" srcset="https://cms-assets.abletech.nz/large_1h_M92_Ca_R_Miest_Dh6_MGY_4a_Wg_f90d7b7be7.jpeg 1000w, https://cms-assets.abletech.nz/small_1h_M92_Ca_R_Miest_Dh6_MGY_4a_Wg_f90d7b7be7.jpeg 500w, https://cms-assets.abletech.nz/medium_1h_M92_Ca_R_Miest_Dh6_MGY_4a_Wg_f90d7b7be7.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1h_M92_Ca_R_Miest_Dh6_MGY_4a_Wg_f90d7b7be7.jpeg 245w" data-zooming-width="1000" data-zooming-height="333" loading="lazy" width="1000" height="333"></figure>
</div>
<p>Before pouring — you can give the coffee shot a quick swirl which helps mix the espresso and get a consistent colour.</p>
<p>Pour 1/3 of cup up slowly with a circular pour so the crema is mixed with milk.</p>
<p>The final pour is very aggressive, starting quick and finishing slow with the jug close to the cup and milk surface to release the foam.</p>
<p>Get the heart shape mastered before moving to more complex patterns: by keeping the hand still in pour, aim to pour 2/3 back from the edge of the cup at the correct speed. Remember start quick, finish slow.</p>
<p>A poor shot will cause bubbles on the top of the milk where the cream is.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s2xgvipc463hanh0o5f6pxkj" alt="" data-big=https://cms-assets.abletech.nz/large_1_Uugt29g_S_By_R6_Jago_T1jq7g_12a2c7b532.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Uugt29g_S_By_R6_Jago_T1jq7g_12a2c7b532.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Uugt29g_S_By_R6_Jago_T1jq7g_12a2c7b532.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Uugt29g_S_By_R6_Jago_T1jq7g_12a2c7b532.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Uugt29g_S_By_R6_Jago_T1jq7g_12a2c7b532.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Uugt29g_S_By_R6_Jago_T1jq7g_12a2c7b532.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="g4yufdiwgq81un6n8mi2anet" alt="" data-big=https://cms-assets.abletech.nz/large_12_QC_Nc_C_Iu_Gg9_Ha5_Sr_Ipj_B6_Q_ae6206ead6.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/12_QC_Nc_C_Iu_Gg9_Ha5_Sr_Ipj_B6_Q_ae6206ead6.jpeg" srcset="https://cms-assets.abletech.nz/large_12_QC_Nc_C_Iu_Gg9_Ha5_Sr_Ipj_B6_Q_ae6206ead6.jpeg 1000w, https://cms-assets.abletech.nz/small_12_QC_Nc_C_Iu_Gg9_Ha5_Sr_Ipj_B6_Q_ae6206ead6.jpeg 500w, https://cms-assets.abletech.nz/medium_12_QC_Nc_C_Iu_Gg9_Ha5_Sr_Ipj_B6_Q_ae6206ead6.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_12_QC_Nc_C_Iu_Gg9_Ha5_Sr_Ipj_B6_Q_ae6206ead6.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="he17rg2atpureg7mr1teg159" alt="Practice heaps and enjoy the process as well as the result" data-big=https://cms-assets.abletech.nz/large_1_ZU_Kgly_Mv_i91uj4fbi_E_Pfw_026ef80245.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_ZU_Kgly_Mv_i91uj4fbi_E_Pfw_026ef80245.jpeg" srcset="https://cms-assets.abletech.nz/large_1_ZU_Kgly_Mv_i91uj4fbi_E_Pfw_026ef80245.jpeg 1000w, https://cms-assets.abletech.nz/small_1_ZU_Kgly_Mv_i91uj4fbi_E_Pfw_026ef80245.jpeg 500w, https://cms-assets.abletech.nz/medium_1_ZU_Kgly_Mv_i91uj4fbi_E_Pfw_026ef80245.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_ZU_Kgly_Mv_i91uj4fbi_E_Pfw_026ef80245.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Practice heaps and enjoy the process as well as the result</em></p>
<h2>Good luck!</h2>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>RubyConf 2018</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Excellent speakers with a wide range of topics at the Australian Ruby conference</h2>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hvudp6p5ka73mzd1vsyxywkq" alt="" data-big=https://cms-assets.abletech.nz/large_1ccm_Dt7enk_Gjd_Pb3_E_Tein5g_bd7550cb1a.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1ccm_Dt7enk_Gjd_Pb3_E_Tein5g_bd7550cb1a.jpeg" srcset="https://cms-assets.abletech.nz/large_1ccm_Dt7enk_Gjd_Pb3_E_Tein5g_bd7550cb1a.jpeg 1000w, https://cms-assets.abletech.nz/small_1ccm_Dt7enk_Gjd_Pb3_E_Tein5g_bd7550cb1a.jpeg 500w, https://cms-assets.abletech.nz/medium_1ccm_Dt7enk_Gjd_Pb3_E_Tein5g_bd7550cb1a.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1ccm_Dt7enk_Gjd_Pb3_E_Tein5g_bd7550cb1a.jpeg 245w" data-zooming-width="1000" data-zooming-height="481" loading="lazy" width="1000" height="481"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="athqy8gogwolhs9u5qma4m63" alt="" data-big=https://cms-assets.abletech.nz/large_141r_Rdlb_Zyz_6cpg_Dtn_CPA_1d243f7cb5.jpeg sizes="(min-width: 1280px) 952px, (min-width: 768px) 476px, (min-width: 1024px) 714px, (min-width: 640px) 148px" src="https://cms-assets.abletech.nz/141r_Rdlb_Zyz_6cpg_Dtn_CPA_1d243f7cb5.jpeg" srcset="https://cms-assets.abletech.nz/large_141r_Rdlb_Zyz_6cpg_Dtn_CPA_1d243f7cb5.jpeg 952w, https://cms-assets.abletech.nz/small_141r_Rdlb_Zyz_6cpg_Dtn_CPA_1d243f7cb5.jpeg 476w, https://cms-assets.abletech.nz/medium_141r_Rdlb_Zyz_6cpg_Dtn_CPA_1d243f7cb5.jpeg 714w, https://cms-assets.abletech.nz/thumbnail_141r_Rdlb_Zyz_6cpg_Dtn_CPA_1d243f7cb5.jpeg 148w" data-zooming-width="952" data-zooming-height="1000" loading="lazy" width="952" height="1000"></figure>
</div>
<p>This year’s RubyConf AU was held in Doltone House at Jones Bay Wharf, Sydney. Here’s a review of my highlights:</p>
<h3>Sandi Metz</h3>
<p>Sandi was the keynote speaker and she was excellent. She referred to findings from psychological studies into working in a team. She suggested you can change your experience in the team by changing yourself.</p>
<blockquote>
<p>Fear is the background noise of our lives but it doesn’t matter. Within it and alongside it you are also good enough</p>
</blockquote>
<p>The ‘you are good enough’ phrase was a popular sentiment repeated throughout the conference. She also talked about the ‘unhappiness of software developers’ and shared common causes of dev unhappiness:</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bs9o4lg3jw77g2c5l5bk8kaw" alt="" data-big=https://cms-assets.abletech.nz/large_11g_Xq_NWYUH_Xl9_W_Av9vb_VT_Ww_bc2cbf9678.jpeg sizes="(min-width: 1280px) 953px, (min-width: 768px) 477px, (min-width: 1024px) 715px, (min-width: 640px) 149px" src="https://cms-assets.abletech.nz/11g_Xq_NWYUH_Xl9_W_Av9vb_VT_Ww_bc2cbf9678.jpeg" srcset="https://cms-assets.abletech.nz/large_11g_Xq_NWYUH_Xl9_W_Av9vb_VT_Ww_bc2cbf9678.jpeg 953w, https://cms-assets.abletech.nz/small_11g_Xq_NWYUH_Xl9_W_Av9vb_VT_Ww_bc2cbf9678.jpeg 477w, https://cms-assets.abletech.nz/medium_11g_Xq_NWYUH_Xl9_W_Av9vb_VT_Ww_bc2cbf9678.jpeg 715w, https://cms-assets.abletech.nz/thumbnail_11g_Xq_NWYUH_Xl9_W_Av9vb_VT_Ww_bc2cbf9678.jpeg 149w" data-zooming-width="953" data-zooming-height="1000" loading="lazy" width="953" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rkh2wsqtnkjqwzbaby6vkupt" alt="Sandi Metz" data-big=https://cms-assets.abletech.nz/large_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 182px" src="https://cms-assets.abletech.nz/1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg" srcset="https://cms-assets.abletech.nz/large_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 1000w, https://cms-assets.abletech.nz/small_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 500w, https://cms-assets.abletech.nz/medium_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1u_Yv_Ju_U_6x_Vii_H0p_G_Jn_N_Cp_A_fbd02cf5da.jpeg 182w" data-zooming-width="1000" data-zooming-height="856" loading="lazy" width="1000" height="856"></figure>
</div>
<p><em>Sandi Metz</em></p>
<p>Follow Sandi on Twitter: <a href="https://twitter.com/sandimetz" target="_blank" rel="noopener noreferrer">https://twitter.com/sandimetz</a></p>
<h3>Mario Kart</h3>
<p>Michael Morris selected members of the audience to play Mario Kart and he conducted a live demo. The players’ performance was live-streamed on a graph:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="j5qfxhr12onl8xpymq87xsay" alt="Michael Morris — Mario Kart" data-big=https://cms-assets.abletech.nz/large_17fthn_Qx8_Ks_K_Gz64q6_Tbz_CQ_b81167b040.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 188px" src="https://cms-assets.abletech.nz/17fthn_Qx8_Ks_K_Gz64q6_Tbz_CQ_b81167b040.jpeg" srcset="https://cms-assets.abletech.nz/large_17fthn_Qx8_Ks_K_Gz64q6_Tbz_CQ_b81167b040.jpeg 1000w, https://cms-assets.abletech.nz/small_17fthn_Qx8_Ks_K_Gz64q6_Tbz_CQ_b81167b040.jpeg 500w, https://cms-assets.abletech.nz/medium_17fthn_Qx8_Ks_K_Gz64q6_Tbz_CQ_b81167b040.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_17fthn_Qx8_Ks_K_Gz64q6_Tbz_CQ_b81167b040.jpeg 188w" data-zooming-width="1000" data-zooming-height="829" loading="lazy" width="1000" height="829"></figure>
</div>
<p><em>Michael Morris — Mario Kart</em></p>
<h3>Humans &amp; Archaelogy</h3>
<p>In a previous life Eleanor Kiefel Haggerty translated Ancient Greek texts. She gave a great talk on exploring the surprising similarities between archaeology and programming to understand developer decision-making in 2018.</p>
<blockquote>
<p>Our decisions, for better or worse, are preserved</p>
</blockquote>
<p>Eleanor covered the importance of writing good commit messages and talked about code leaving traces, a sort of cultural history, in the way it records an individual’s presence and decisions. She discussed the importance of creating history that is flat and readable.</p>
<h3>Why hire juniors?</h3>
<p>Ryan Bigg talked about how companies around the world are growing their own best and brightest talent. He has started a Junior Engineering Program at Envato in Melbourne where he’s growing eight engineers through a structured program.</p>
<p>He covered how to interview, mentor and work with junior developers. He said that juniors make teams better. Importantly, he also said that juniors give seniors the opportunity to grow in patience.</p>
<p>Ryan stated that skill diversity outperforms homogeneity.</p>
<h3>Netflix</h3>
<p>Lauren Tan is a Senior Full Stack Engineer at Netflix and she spoke about how Ruby helps the ‘teams behind the streams’ create and produce billions of dollars of original content.</p>
<p>Lauren talked about how Netflix built their first studio app in Ruby and how they use micro-services to increase productivity. She covered the different tools they use for activities such as deployment, authentication, internal identity service and security.</p>
<h3>Time in Ruby — how UTC evolved</h3>
<p>Most Ruby conferences seem to include a talk related to handling time in Ruby. This one suggested using ISO8601. We were reminded to use time libraries where possible. We also learned that the term UTC has evolved from various versions. UTC is the acronym for Universal Coordinated Time. Originally, the French called it TUC, and the English called it CUT, so finally a compromise was made to call it UTC!</p>
<h3>Technical Writing</h3>
<p>Alaina Kafkes encouraged us all to get writing. She suggested that Ruby’s fate rests on people who write well and gave tips for getting started with technical writing.</p>
<ul>
<li>
<p><strong>Brainstorming</strong> — you don’t need to be an expert. Write about how you solved a technical problem and what you learned about it. Don’t feel you only have to write about code</p>
</li>
<li>
<p><strong>Writing</strong> — break it into small sections. Use code snippets and gifs in addition to words. Add a learn more button</p>
</li>
<li>
<p><strong>Editing</strong> — hone it. Ask friends to read it and provide feedback</p>
</li>
<li>
<p><strong>Publish</strong> — use a platform like Medium or dev.to for building your readership. Leverage popular technical writing outlets and have a call to action for people to interact with your writing eg. have your contact details in the footer</p>
</li>
<li>
<p><strong>Reflect</strong> — be available to respond to respectful commentators and strive for consistency as you build your published writings</p>
</li>
</ul>
<p>Alaina went through these five stages in detail, talking through the issues that can occur at each stage and how to overcome them.</p>
<h3>The good bad bug</h3>
<p>Jessica Rudder looked at approaching code in a way that allows us to fail in the best way. She has a passion for clean code and talked about what she calls the ‘good bad bug’. She says that programming is filled with bugs that were actually features and that we can tame our inner perfectionists while still writing great code. Jessica explained the importance of finding a safe place to fail publicly.</p>
<h3>Technology and politics</h3>
<p>Wellingtonian Merrin Macleod gave an excellent talk about how politics and technology can not be separated. She looked at some of the technologies that people are using to shift political realities, and examined the political ideas that underpin the technologies we use, and build, every day.</p>
<p>Merrin’s viewpoint was that technology is political because technology is created by humans.</p>
<h3>Other talks:</h3>
<ul>
<li>
<p>Machine Learning</p>
</li>
<li>
<p>Crystal vs Ruby</p>
</li>
<li>
<p>Use of operators and other language features in various languages. Eg. how ‘===’ operates differently across various languages.</p>
</li>
<li>
<p>Techniques to operate your app at scale. You can use the Scientist gem to see the performance of your old code vs new code.</p>
</li>
<li>
<p>Using Ruby with other protocols</p>
</li>
<li>
<p>Dyslexia and developing</p>
</li>
<li>
<p>Distributed Tracing</p>
</li>
</ul>
<p>Read more about the <a href="https://www.rubyconf.org.au/2018/speakers" target="_blank" rel="noopener noreferrer">speakers</a> on the conference website.</p>
<p>It was great to see familiar Ruby faces at the conference. My only complaint was that the Doltone House seating was uncomfortable! The food was amazing throughout the conference and the closing event, amongst the tiered pagodas in the Chinese Friendship Garden, was fantastic. The Chinese Garden Party involved sharing canapés, drinks and Messina gelato as the sun set.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mv5dq6c0730e5o69gyndv3aa" alt="" data-big=https://cms-assets.abletech.nz/large_14s93_Aq9t_H_Lf_Vw_S_Serrz_Hw_7e2ffad975.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 214px" src="https://cms-assets.abletech.nz/14s93_Aq9t_H_Lf_Vw_S_Serrz_Hw_7e2ffad975.jpeg" srcset="https://cms-assets.abletech.nz/large_14s93_Aq9t_H_Lf_Vw_S_Serrz_Hw_7e2ffad975.jpeg 1000w, https://cms-assets.abletech.nz/small_14s93_Aq9t_H_Lf_Vw_S_Serrz_Hw_7e2ffad975.jpeg 500w, https://cms-assets.abletech.nz/medium_14s93_Aq9t_H_Lf_Vw_S_Serrz_Hw_7e2ffad975.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_14s93_Aq9t_H_Lf_Vw_S_Serrz_Hw_7e2ffad975.jpeg 214w" data-zooming-width="1000" data-zooming-height="730" loading="lazy" width="1000" height="730"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lgx3jc4whl5fgu3jyfljvaqn" alt="" data-big=https://cms-assets.abletech.nz/large_1_x_Es_R_Ddvr_DCA_0_W31_Xz_OX_2_A_e237099411.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_x_Es_R_Ddvr_DCA_0_W31_Xz_OX_2_A_e237099411.jpeg" srcset="https://cms-assets.abletech.nz/large_1_x_Es_R_Ddvr_DCA_0_W31_Xz_OX_2_A_e237099411.jpeg 1000w, https://cms-assets.abletech.nz/small_1_x_Es_R_Ddvr_DCA_0_W31_Xz_OX_2_A_e237099411.jpeg 500w, https://cms-assets.abletech.nz/medium_1_x_Es_R_Ddvr_DCA_0_W31_Xz_OX_2_A_e237099411.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_x_Es_R_Ddvr_DCA_0_W31_Xz_OX_2_A_e237099411.jpeg 245w" data-zooming-width="1000" data-zooming-height="453" loading="lazy" width="1000" height="453"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rp4g1n7us368hkeppepg9xzr" alt="" data-big=https://cms-assets.abletech.nz/large_1cp6fu_Q7_S4s_J_Tw_UX_4_SU_5_Qr_Q_e8a80461b4.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 164px" src="https://cms-assets.abletech.nz/1cp6fu_Q7_S4s_J_Tw_UX_4_SU_5_Qr_Q_e8a80461b4.jpeg" srcset="https://cms-assets.abletech.nz/large_1cp6fu_Q7_S4s_J_Tw_UX_4_SU_5_Qr_Q_e8a80461b4.jpeg 1000w, https://cms-assets.abletech.nz/small_1cp6fu_Q7_S4s_J_Tw_UX_4_SU_5_Qr_Q_e8a80461b4.jpeg 500w, https://cms-assets.abletech.nz/medium_1cp6fu_Q7_S4s_J_Tw_UX_4_SU_5_Qr_Q_e8a80461b4.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1cp6fu_Q7_S4s_J_Tw_UX_4_SU_5_Qr_Q_e8a80461b4.jpeg 164w" data-zooming-width="1000" data-zooming-height="951" loading="lazy" width="1000" height="951"></figure>
</div>
<p>A big thanks to all the amazing people who organised RubyConf AU 2018.</p>
<h3>More from Abletech</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/abletech-developers-spent-a-few-days-in-melbourne-for-the-annual-ruby-conference">RubyConf AU 2017</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ruby-conf-in-wellington">Kiwi Ruby</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/national-javascript-conference">NZ Javascript Conf</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/css-and-js">CSS and JS conferences in Melbourne 2018</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Pragmatic Refactoring</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Book review: 99 Bottles of OOP</h2>
<h2>A great refactoring book</h2>
<p>Last year, I read the amazing <a href="https://sandimetz.com/99bottles" target="_blank" rel="noopener noreferrer">*99 Bottles of OOP</a>* by Sandi Metz, Katrina Owen &amp; TJ Stankus. The book explores OOP concepts and how to refactor code while being one <code>cmd + z</code> away from green tests. It teaches “practical techniques for getting things done that lead, naturally and inevitably, to beautiful code”, <strong>by changing one line at a time</strong>.</p>
<p>That’s right you read that properly.</p>
<p>For every single line of code that changes your tests should remain green. If they fail, then undo the change and try again. How is this possible? <em>99 Bottles of OOP</em> explains the technique thoroughly. It is a bit tricky at the beginning but just like any technique, it becomes simpler over time. Here is a gist:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mqj1kgkm3afp6iw5nqzzb69o" alt="*Refactor code one change at a time with [retest](https://github.com/AlexB52/retest).*"  sizes="" src="https://cms-assets.abletech.nz/1_Wh_OB_3oa_H5y_Y89_Eb_GU_69_S1_A_9e2ee676eb.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><strong>Refactor code one change at a time with <a href="https://github.com/AlexB52/retest" target="_blank" rel="noopener noreferrer">retest</a>.</strong></p>
<h2>The methodology</h2>
<p>The book is an ode to <a href="https://martinfowler.com/articles/preparatory-refactoring-example.html" target="_blank" rel="noopener noreferrer">‘preparatory refactoring’</a> by introducing a new feature for the 99 bottles song. How can we replace any references of <code>6 bottles</code> to <code>1 six-pack</code> in the song? Simple to understand yet not trivial.</p>
<p>The authors go through the change following the <code>open/close principle</code>: Code is open to a new requirement when you can meet that new requirement without changing existing code. It is best expressed with this quote from Kent Beck.</p>
<blockquote>
<p><em>For each desired change, make the change easy (warning: this may be hard), then make the easy change. <a href="https://twitter.com/KentBeck/status/250733358307500032?s=20" target="_blank" rel="noopener noreferrer">Kent Beck</a></em></p>
</blockquote>
<h2>Make the change easy</h2>
<p>The first step to great refactoring is a good test coverage to increase confidence that new changes are preserving the code functionality.</p>
<p>Good test coverage doesn’t mean loads of tests and the book is a good example of that. <em>99 bottles of OOP</em> handles the entire refactoring with one simple yet reliable integration test: the code must be able to print out the full song after every new code change. One assertion on a single hardcoded string of 100 lines with no dependencies on the code being tested.</p>
<p>Developers tend to write a lot of coupled unit tests while shying away from slow integration tests or feature tests. I find it fascinating that the code in this book can be refactored with only this specific integration test . During the refactoring, the authors create new classes but no new tests. New classes are considered private and don’t deserve any tests (just yet). Mind-blowing.</p>
<h2>Keep calm and carry on</h2>
<p>The authors tell you to trust the process even if the code seems far from being open to change. Keep calm and continue refactoring, ultimately the code will reach a state where the new feature can be introduced. This refactoring phase is more of a mechanical process as it doesn’t require a vision or sparks of ingenuity. It looks like this:</p>
<ol>
<li>
<p>Change one line</p>
</li>
<li>
<p>Run the tests</p>
</li>
<li>
<p>If the tests fail, undo the change</p>
</li>
<li>
<p>Go back to step 1</p>
</li>
</ol>
<p>The authors teach simple effective rules to help identify areas requiring refactoring like the Flocking rule. Then you repeat simple known atomic changes from Martin Fowler’s book <a href="https://www.refactoring.com/" target="_blank" rel="noopener noreferrer">refactoring</a> and your code will become ready for change. <strong>Always.</strong></p>
<h2>Make the easy change</h2>
<p>Only once the code is open do you go back to boring TDD:</p>
<ul>
<li>
<p>Make the tests fail by updating your test</p>
</li>
<li>
<p>Make the change</p>
</li>
<li>
<p>Make the tests pass</p>
</li>
<li>
<p>gg 2ez</p>
</li>
</ul>
<h2>Make it easy to understand</h2>
<p>It’s only at that point that the authors write unit tests to cover most, but not all, newly introduced classes. That is right, every class doesn’t need unit tests when they can still be considered too small or private. This section of the book uses insightful methods to write tests that document and teach future devs how to use the new classes effectively. As tests are written, classes are still being refactored. Amazing chapter.</p>
<h2>The infamous test</h2>
<p>Finally comes my only point of disagreement with the authors of this great book. They decide after this journey to delete the integration test. They cover the entire refactoring with one single test only to… remove it without an ounce of remorse. Not even a thank you, Marie Kondo would be ashamed.</p>
<p>While not being a unit test, it should be kept. It was testing the only class already in use in the fictive codebase. The test makes sure that printing the 99 bottles of beer song still works which is supposedly an important part of the existing business rules. RIP integration test.</p>
<h2>Conclusion</h2>
<p>The book is in its 2nd edition. Reading it feels like drinking from a water hose. You can expect to spill most of it but will catch refreshing bits here and there.</p>
<p>It’s one of those books, like <a href="https://www.poodr.com/" target="_blank" rel="noopener noreferrer"><em>POODR</em></a>, which needs to be read a few times to start internalising the concepts properly. Just like any skill, theory is not enough. Read the book, practise, read the book again, practise again… Trust the process and keep looping. Every time you’ll have new AHA! moments.</p>
<p>I recommend this book to developers with any level of experience and especially to senior developers who haven’t refreshed their OOP basics for at least two years. This book will challenge your deeply ingrained practices and this exercise alone will make you a better developer. Who knows, you might even fall in love with factories again.</p>
<p>The book covers those topics:</p>
<ul>
<li>
<p>Recognizing when code is “good enough”</p>
</li>
<li>
<p>Getting the best value from Test-Driven Development (TDD)</p>
</li>
<li>
<p>Doing proper refactoring, not random “rehacktoring”</p>
</li>
<li>
<p>Locating concepts buried in code</p>
</li>
<li>
<p>Finding names that convey deeper meaning</p>
</li>
<li>
<p>Safely altering code by following the “Flocking Rules”</p>
</li>
<li>
<p>Simplifying new additions with the Open/Closed Principle</p>
</li>
<li>
<p>Avoiding conditionals by obeying the Liskov Substitution Principle</p>
</li>
<li>
<p>Making targeted improvements by reducing Code Smells</p>
</li>
<li>
<p>Improving changeability with polymorphism</p>
</li>
<li>
<p>Manufacturing role-playing objects using Factories</p>
</li>
<li>
<p>Hedging against uncertainty by loosening coupling</p>
</li>
<li>
<p>Developing a programming aesthetic</p>
</li>
</ul>
<h2>Retest gem</h2>
<p>This book gave me the incentive to create a gem that facilitates this type of extreme refactoring: <a href="https://github.com/AlexB52/retest" target="_blank" rel="noopener noreferrer">retest</a>.</p>
<p>Retest is a small command-line tool to help you refactor code and works with every Ruby project (ruby, rspec, rails, rake). Designed to be dev-centric and project independent, it can be used on the fly. No Gemfile updates, no commits to a repo, no configuration files (like Guard) required to start refactoring. The gif at the start shows how to install and use the gem.</p>
<pre><code class="hljs"><span class="hljs-variable">$ </span>gem install retest
<span class="hljs-variable">$ </span>retest --auto

<span class="hljs-comment"># Start refactoring by making a change and save the file</span>
</code></pre>
<p>If you’re interested in reading <a href="https://sandimetz.com/99bottles" target="_blank" rel="noopener noreferrer"><em>99 Bottles of OOP</em></a> while going through the changes yourself this gem is for you. It will rerun the infamous integration test after every change you make.</p>
<p><em>Originally published at <a href="https://alexbarret.com/blog/" target="_blank" rel="noopener noreferrer">https://alexbarret.com</a></em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>EV Q&A — Is this EV thing just a fad or is it here to stay? Got evidence?</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>What evidence proves it’s here to stay?</h2>
<p>There’s a trend of transitioning from petrol and diesel vehicles, to vehicles powered by electricity. In this post, I’ll report on the growth rates of EV sales in NZ and similar overseas markets.</p>
<h2>What is happening in New Zealand?</h2>
<p>There seem to be more and more electric cars on the roads. I wonder if this is just a Wellington thing, or is it happening in other centres? The Ministry of Transport maintain an interesting <a href="https://www.transport.govt.nz/mot-resources/vehicle-fleet-statistics/monthly-electric-and-hybrid-light-vehicle-registrations/" target="_blank" rel="noopener noreferrer">page of EV statistics</a>, including their popularity by region.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ggq93tctmsrrhmvbd7ptro5k" alt="[MoT](https://www.transport.govt.nz/mot-resources/vehicle-fleet-statistics/monthly-electric-and-hybrid-light-vehicle-registrations/): Total ownership rates" data-big=https://cms-assets.abletech.nz/medium_1_Z56_AX_3l_rlk_Hvw_Gv6j_D_Fu_Q_2eba11a3df.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 215px" src="https://cms-assets.abletech.nz/1_Z56_AX_3l_rlk_Hvw_Gv6j_D_Fu_Q_2eba11a3df.png" srcset="https://cms-assets.abletech.nz/small_1_Z56_AX_3l_rlk_Hvw_Gv6j_D_Fu_Q_2eba11a3df.png 500w, https://cms-assets.abletech.nz/medium_1_Z56_AX_3l_rlk_Hvw_Gv6j_D_Fu_Q_2eba11a3df.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Z56_AX_3l_rlk_Hvw_Gv6j_D_Fu_Q_2eba11a3df.png 215w" data-zooming-width="750" data-zooming-height="544" loading="lazy" width="750" height="544"></figure>
</div>
<p><em><a href="https://www.transport.govt.nz/mot-resources/vehicle-fleet-statistics/monthly-electric-and-hybrid-light-vehicle-registrations/" target="_blank" rel="noopener noreferrer">MoT</a>: Total ownership rates</em></p>
<p>This is quite interesting, as it shows that the 4 ‘original’ main centres are fairly equal in adoption rates (0.3–0.4%) while the ‘new’ centres of Tauranga (BOP) and Hamilton (Waikato) are lagging (0.1–0.2%). Note: this is the percentage of EVs for the total population of cars (not incoming vehicle registrations).</p>
<p>But, the original question is regarding growth rates — and if EVs have a long term future in New Zealand.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jytjf9gscum8ew7lp0lk7jla" alt="" data-big=https://cms-assets.abletech.nz/medium_1_J_Cjm1c1_DY_Oo_APTV_Iurly_Jg_07fbc6f2bb.png sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_J_Cjm1c1_DY_Oo_APTV_Iurly_Jg_07fbc6f2bb.png" srcset="https://cms-assets.abletech.nz/small_1_J_Cjm1c1_DY_Oo_APTV_Iurly_Jg_07fbc6f2bb.png 500w, https://cms-assets.abletech.nz/medium_1_J_Cjm1c1_DY_Oo_APTV_Iurly_Jg_07fbc6f2bb.png 750w, https://cms-assets.abletech.nz/thumbnail_1_J_Cjm1c1_DY_Oo_APTV_Iurly_Jg_07fbc6f2bb.png 245w" data-zooming-width="750" data-zooming-height="357" loading="lazy" width="750" height="357"></figure>
</div>
<p>Positive growth was certainly happening during 2014–2018, but it seems to have slowed in 2019. Looking at the chart, there appears to be a repeating pattern of a slower summer with a higher growth rate in winter and spring.</p>
<h2>What is happening overseas?</h2>
<p>Everyone seems to mention Norway as the global leader in EV adoption. According to NPR, <a href="https://www.npr.org/2019/04/02/709131281/electric-cars-hit-record-in-norway-making-up-nearly-60-of-sales-in-march" target="_blank" rel="noopener noreferrer">58% of new car sales in Norway are electric</a> (as at March 2019). The Norwegian government have had incentives for many years. They also penalise vehicles with high emissions, and this has contributed to the impressive adoption rates.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="jd0stkncp1hfj95nubkzz75l" alt="Source: [Wikipedia](https://en.wikipedia.org/wiki/Plug-in_electric_vehicles_in_Norway)" data-big=https://cms-assets.abletech.nz/large_1_WJ_6_HK_Vo_J1_C_Tx_J01kkuj_Ncg_98b9fea35b.png sizes="(min-width: 1280px) 962px, (min-width: 768px) 481px, (min-width: 1024px) 721px, (min-width: 640px) 150px" src="https://cms-assets.abletech.nz/1_WJ_6_HK_Vo_J1_C_Tx_J01kkuj_Ncg_98b9fea35b.png" srcset="https://cms-assets.abletech.nz/large_1_WJ_6_HK_Vo_J1_C_Tx_J01kkuj_Ncg_98b9fea35b.png 962w, https://cms-assets.abletech.nz/small_1_WJ_6_HK_Vo_J1_C_Tx_J01kkuj_Ncg_98b9fea35b.png 481w, https://cms-assets.abletech.nz/medium_1_WJ_6_HK_Vo_J1_C_Tx_J01kkuj_Ncg_98b9fea35b.png 721w, https://cms-assets.abletech.nz/thumbnail_1_WJ_6_HK_Vo_J1_C_Tx_J01kkuj_Ncg_98b9fea35b.png 150w" data-zooming-width="962" data-zooming-height="1000" loading="lazy" width="962" height="1000"></figure>
</div>
<p><em>Source: <a href="https://en.wikipedia.org/wiki/Plug-in_electric_vehicles_in_Norway" target="_blank" rel="noopener noreferrer">Wikipedia</a></em></p>
<p>Norway has a lot of similarities to New Zealand — hydro power, mountainous terrain, and a similar population. The main difference is their wealth with a <a href="https://en.wikipedia.org/wiki/List_of_countries_by_GDP_%28PPP%29_per_capita" target="_blank" rel="noopener noreferrer">GDP per capita</a> of US$74,000 compared with New Zealand’s US$40,000.</p>
<p>What about the United Kingdom? They have a much closer GDP of US$45,000, so what are their EV adoption rates like?</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hgwj37o3btuiiupuox7ba7m1" alt="Source: [Wikipedia](https://en.wikipedia.org/wiki/Electric_car_use_by_country)" data-big=https://cms-assets.abletech.nz/large_1_Oot0c6lt_QYF_Yl_Bb_OZRWD_5_A_d7de2d3b80.png sizes="(min-width: 1280px) 949px, (min-width: 768px) 474px, (min-width: 1024px) 712px, (min-width: 640px) 148px" src="https://cms-assets.abletech.nz/1_Oot0c6lt_QYF_Yl_Bb_OZRWD_5_A_d7de2d3b80.png" srcset="https://cms-assets.abletech.nz/large_1_Oot0c6lt_QYF_Yl_Bb_OZRWD_5_A_d7de2d3b80.png 949w, https://cms-assets.abletech.nz/small_1_Oot0c6lt_QYF_Yl_Bb_OZRWD_5_A_d7de2d3b80.png 474w, https://cms-assets.abletech.nz/medium_1_Oot0c6lt_QYF_Yl_Bb_OZRWD_5_A_d7de2d3b80.png 712w, https://cms-assets.abletech.nz/thumbnail_1_Oot0c6lt_QYF_Yl_Bb_OZRWD_5_A_d7de2d3b80.png 148w" data-zooming-width="949" data-zooming-height="1000" loading="lazy" width="949" height="1000"></figure>
</div>
<p><em>Source: <a href="https://en.wikipedia.org/wiki/Electric_car_use_by_country" target="_blank" rel="noopener noreferrer">Wikipedia</a></em></p>
<p>As you can see, the UK has a new sales rate of 2.5% which is higher than NZ. Note that the percentages here are for new ‘car’ sales rather than the ‘light’ vehicles that the Ministry of Transport figures mentioned.</p>
<h2>New Zealand is a laggard</h2>
<p>We are much slower in the uptake of electric vehicles in New Zealand when compared to similar countries overseas. Other countries have used generous incentive (and dis-incentive) programmes to promote uptake.</p>
<p>The incentives in NZ are minor, and include:</p>
<ul>
<li>
<p>reduced ACC charges</p>
</li>
<li>
<p>cheaper vehicle registration</p>
</li>
<li>
<p>no road user charges</p>
</li>
</ul>
<p>These incentives contribute to the lower operating costs of an EV, but do not address the upfront purchase hurdle. With cheaper electric power and lower maintenance costs, an EV already has a massive saving in everyday operating costs. I’d like to see a focus on purchase incentives (and dis-incentives for dirty vehicles) rather than operating costs.</p>
<h2>The future</h2>
<p>In May 2016, the New Zealand Government set a target of <a href="https://www.transport.govt.nz/multi-modal/climatechange/electric-vehicles/" target="_blank" rel="noopener noreferrer">64,000 electric vehicles by 2021</a>. We are currently at 13,659 so we have a long way to go. Overseas data would suggest that growth will continue.</p>
<p>As New Zealand does not have a vehicle manufacturing industry, we will inevitably end up with whatever the world produces. A <a href="https://about.bnef.com/electric-vehicle-outlook/" target="_blank" rel="noopener noreferrer">Bloomberg report</a> ‘Electric Vehicle Outlook’ shows that in 10 years time, around a quarter of all new car sales will be electric.</p>
<p>Tony Seba from Stanford University is even more ambitious about the <a href="https://www.youtube.com/watch?v=2b3ttqYDwF0" target="_blank" rel="noopener noreferrer">rise of the electric car</a> with his prediction that the demise of fossil fuels (petrol, diesel, etc) will occur in about a decade.</p>
<h2>More supply</h2>
<p>Every week, another manufacturer announces another new EV. Many of these (including the <a href="https://www.driven.co.nz/news/news/revealed-new-zealand-tesla-model-3-pricing-to-start-at-73-900/" target="_blank" rel="noopener noreferrer">Tesla model 3</a>) are <a href="https://driveelectric.org.nz/individuals/ev-models-and-where-to-buy/" target="_blank" rel="noopener noreferrer">for sale now</a>. The prices continue to slide slowly downwards, and the size and range of the batteries continues to grow. In fact, with 64 kWh batteries becoming commonplace on new models (range 400–500 km), it’s likely that battery growth is no longer required.</p>
<p>We will also see an increased supply of used electric vehicles available to purchase from NZ company fleets. These are typically leased for 3 years, and then sold locally. This will add to the current main source of second hand EVs, which is Japan.</p>
<h2>Just a fad?</h2>
<p>Overseas evidence would suggest that the popularity of electric vehicles will continue to rise in New Zealand. We may continue our sluggish pace for a few years, or follow the higher rates of other <a href="https://lmgtfy.com/?s=d&amp;q=OECD" target="_blank" rel="noopener noreferrer">OECD</a> countries if we get a purchase incentive programme.</p>
<p>Eventually, petrol and diesel vehicles will have reduced international availability. The costs of running these ‘fossil fuel’ vehicles will <a href="https://www.treehugger.com/cars/norwegian-oil-demand-finally-peaking-thanks-electric-cars.html" target="_blank" rel="noopener noreferrer">slowly rise</a>, as the fixed costs of operating the fuel distribution network is shared among a smaller group.</p>
<p>Electric vehicles are not a fad, they are the future. A future that cannot include fossil fuels if global temperature rise is to be kept below 1.5℃.</p>
<h3>Read more</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/traveling-to-palmy-in-an-electric-vehicle">Travelling to Palmy in an Electric Vehicle</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-do-the-cars-cost-more-than-petrol-cars">EV Q&amp;A — Do the cars cost more than petrol cars?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-charging-do-you-need-a-special-plug-at-home">EV Q&amp;A — Charging: do you need a special plug at home?</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/ev-q-a-if-i-go-up-a-hill-do-i-gain-power-when-i-go-down">EV Q&amp;A — If I go up a hill, do I gain power when I go down?</a></p>
</li>
<li>
<p><a href="mailto:nigel@ramsay.org.nz" target="_blank" rel="noopener noreferrer">Submit a question</a> for my EV Q&amp;A series.</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part six</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The New Fuel</h2>
<p>As mentioned in <a href="https://stories.abletech.nz/etrixie-part-five-6429e81c3b0d#.gbwbg1m1s" target="_blank" rel="noopener noreferrer">part five</a> we have a new AC electric motor which is controlled by an AC motor controller. The AC motor controller will ‘invert’ the DC battery supply to power the motor. So the new fuel is electrons!</p>
<p>In eTrixie’s case the new fuel is 37 LiFePo4 cells (Lithium Iron Phosphate). The cells are nominally 3.2 volt /180 Ah each. When all these are joined, in series, they create a 120 volt battery delivering about 22kWh of power.</p>
<p>Each cell weighs 5.6kg. These will be distributed, in two banks, in the old fuel tank position:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lmjbmv2jm1s8s10c37wk8cwl" alt="" data-big=https://cms-assets.abletech.nz/large_1_E_Qivg_Vy_NRZ_1_ME_80j0_U_Os_Lw_8649701cbd.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_E_Qivg_Vy_NRZ_1_ME_80j0_U_Os_Lw_8649701cbd.jpeg" srcset="https://cms-assets.abletech.nz/large_1_E_Qivg_Vy_NRZ_1_ME_80j0_U_Os_Lw_8649701cbd.jpeg 1000w, https://cms-assets.abletech.nz/small_1_E_Qivg_Vy_NRZ_1_ME_80j0_U_Os_Lw_8649701cbd.jpeg 500w, https://cms-assets.abletech.nz/medium_1_E_Qivg_Vy_NRZ_1_ME_80j0_U_Os_Lw_8649701cbd.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_E_Qivg_Vy_NRZ_1_ME_80j0_U_Os_Lw_8649701cbd.jpeg 245w" data-zooming-width="1000" data-zooming-height="333" loading="lazy" width="1000" height="333"></figure>
</div>
<p>and 25 on the luggage tray behind the back passenger seat:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ql1wwkhg4a6a8dk0gxjohwq5" alt="" data-big=https://cms-assets.abletech.nz/large_1l3_H9n_Fl_Zn_P_Lp_So_R_Ken_QZQ_b1426c5b6e.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1l3_H9n_Fl_Zn_P_Lp_So_R_Ken_QZQ_b1426c5b6e.jpeg" srcset="https://cms-assets.abletech.nz/large_1l3_H9n_Fl_Zn_P_Lp_So_R_Ken_QZQ_b1426c5b6e.jpeg 1000w, https://cms-assets.abletech.nz/small_1l3_H9n_Fl_Zn_P_Lp_So_R_Ken_QZQ_b1426c5b6e.jpeg 500w, https://cms-assets.abletech.nz/medium_1l3_H9n_Fl_Zn_P_Lp_So_R_Ken_QZQ_b1426c5b6e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1l3_H9n_Fl_Zn_P_Lp_So_R_Ken_QZQ_b1426c5b6e.jpeg 245w" data-zooming-width="1000" data-zooming-height="500" loading="lazy" width="1000" height="500"></figure>
</div>
<p>A local engineering company did a fantastic job building boxes to house the batteries. At the same time they worked with the LVVTA Standard to ensure the boxes meet certification requirements. The standards are well explained we were able to build and install the boxes according to these standards.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="sp62sxef6t39rxtu0ef2ajlk" alt="Front box with some cells in place" data-big=https://cms-assets.abletech.nz/large_1vjvo8_U_5hkm0c3bzmb_GU_Xg_48e58f3313.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1vjvo8_U_5hkm0c3bzmb_GU_Xg_48e58f3313.jpeg" srcset="https://cms-assets.abletech.nz/large_1vjvo8_U_5hkm0c3bzmb_GU_Xg_48e58f3313.jpeg 1000w, https://cms-assets.abletech.nz/small_1vjvo8_U_5hkm0c3bzmb_GU_Xg_48e58f3313.jpeg 500w, https://cms-assets.abletech.nz/medium_1vjvo8_U_5hkm0c3bzmb_GU_Xg_48e58f3313.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1vjvo8_U_5hkm0c3bzmb_GU_Xg_48e58f3313.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Front box with some cells in place</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ye1ukdz22uykliz8omm6i5jw" alt="Rear box with cells in place" data-big=https://cms-assets.abletech.nz/large_1i9r_I_Gs_QK_5y_Nlzf2f7l_Bw_Q_3d782cbc02.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1i9r_I_Gs_QK_5y_Nlzf2f7l_Bw_Q_3d782cbc02.jpeg" srcset="https://cms-assets.abletech.nz/large_1i9r_I_Gs_QK_5y_Nlzf2f7l_Bw_Q_3d782cbc02.jpeg 1000w, https://cms-assets.abletech.nz/small_1i9r_I_Gs_QK_5y_Nlzf2f7l_Bw_Q_3d782cbc02.jpeg 500w, https://cms-assets.abletech.nz/medium_1i9r_I_Gs_QK_5y_Nlzf2f7l_Bw_Q_3d782cbc02.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1i9r_I_Gs_QK_5y_Nlzf2f7l_Bw_Q_3d782cbc02.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Rear box with cells in place</em></p>
<p>Now that the batteries have a home — the next step is to wire everything together! I’ll cover that in part seven.</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part four</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The VW Beetle conversion to electric power is coming together. In part four there’s a flywheel upgrade and a new clutch.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="avsq1wos4r3p1mh7zmhf7tm3" alt="" data-big=https://cms-assets.abletech.nz/small_0_Kgc_ZE_1sh_Hs_CO_3_EJ_698c17f674.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0_Kgc_ZE_1sh_Hs_CO_3_EJ_698c17f674.jpg" srcset="https://cms-assets.abletech.nz/small_0_Kgc_ZE_1sh_Hs_CO_3_EJ_698c17f674.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0_Kgc_ZE_1sh_Hs_CO_3_EJ_698c17f674.jpg 245w" data-zooming-width="500" data-zooming-height="185" loading="lazy" width="500" height="185"></figure>
</div>
<p>**Motor
**Next up in the eTrixie project was to put the new motor in temporarily, just to see what it looked like! The new motor fits onto the existing VW transaxle bell housing with a beautifully CNC milled aluminium adaptor which bolts the motor to the gearbox.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="flzk0ems6ejipmxb6sp4hj4d" alt="Motor temporarily in place" data-big=https://cms-assets.abletech.nz/small_0_4v9_PYPSJ_Ke_Bb_Ub_c622fb0b48.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 197px" src="https://cms-assets.abletech.nz/0_4v9_PYPSJ_Ke_Bb_Ub_c622fb0b48.jpg" srcset="https://cms-assets.abletech.nz/small_0_4v9_PYPSJ_Ke_Bb_Ub_c622fb0b48.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0_4v9_PYPSJ_Ke_Bb_Ub_c622fb0b48.jpg 197w" data-zooming-width="500" data-zooming-height="395" loading="lazy" width="500" height="395"></figure>
</div>
<p><em>Motor temporarily in place</em></p>
<p>**Flywheel
**Another change for the electric motor is the change to a light weight aluminium flywheel. I no longer need a starter motor so a simpler and lighter flywheel replaces the old one.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="nggpmsy93feyoj9lb2urkm0x" alt="Shiny new flywheel added" data-big=https://cms-assets.abletech.nz/small_03z_WS_Ur5_SN_7dhos_8_6388d50ae7.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 167px" src="https://cms-assets.abletech.nz/03z_WS_Ur5_SN_7dhos_8_6388d50ae7.jpg" srcset="https://cms-assets.abletech.nz/small_03z_WS_Ur5_SN_7dhos_8_6388d50ae7.jpg 500w, https://cms-assets.abletech.nz/thumbnail_03z_WS_Ur5_SN_7dhos_8_6388d50ae7.jpg 167w" data-zooming-width="500" data-zooming-height="466" loading="lazy" width="500" height="466"></figure>
</div>
<p><em>Shiny new flywheel added</em></p>
<p><strong>Clutch</strong>
Then a new clutch is added. As I mentioned in part three, there will still be gears, and reversing will be handled by the gear box (rather than reversing the electric motor, which is easy to do).</p>
<p>With the new flywheel and clutch added the electric motor is ready for action.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dn238re5g22yk5nkt5pgkx4w" alt="Clutch in place" data-big=https://cms-assets.abletech.nz/small_02m_OYN_0dn_Bz_OSZ_17_df7fa85b7c.jpg sizes="(min-width: 768px) 362px, (min-width: 640px) 113px" src="https://cms-assets.abletech.nz/02m_OYN_0dn_Bz_OSZ_17_df7fa85b7c.jpg" srcset="https://cms-assets.abletech.nz/small_02m_OYN_0dn_Bz_OSZ_17_df7fa85b7c.jpg 362w, https://cms-assets.abletech.nz/thumbnail_02m_OYN_0dn_Bz_OSZ_17_df7fa85b7c.jpg 113w" data-zooming-width="362" data-zooming-height="500" loading="lazy" width="362" height="500"></figure>
</div>
<p><em>Clutch in place</em></p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part five</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>AC/DC</h2>
<p>In this update about converting the VW Beetle, to 100% electric power, we look at the choice of electric motor.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qtvtyz8ao7vb9gl49n5hy9if" alt="eTrixie’s motor controller" data-big=https://cms-assets.abletech.nz/small_0e_Bu3l_Gj_B62_Rz_Dtt_E_671ad75a6e.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 214px" src="https://cms-assets.abletech.nz/0e_Bu3l_Gj_B62_Rz_Dtt_E_671ad75a6e.jpg" srcset="https://cms-assets.abletech.nz/small_0e_Bu3l_Gj_B62_Rz_Dtt_E_671ad75a6e.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0e_Bu3l_Gj_B62_Rz_Dtt_E_671ad75a6e.jpg 214w" data-zooming-width="500" data-zooming-height="365" loading="lazy" width="500" height="365"></figure>
</div>
<p><em>eTrixie’s motor controller</em></p>
<p><strong>Three-phase AC induction motors are almost the perfect machine!</strong></p>
<p>They are lightweight, simple, efficient and close to maintenance free. They produce a rotating magnetic field in the motor case (stator) with magnets created by winding the wires in such a way to form magnetic poles.</p>
<p>The rotating magnetic field of the stator then induces a current, and magnetic fields in the rotor, which drives the shaft of the motor. The only real downside is the speed of the motor shaft is fixed by the frequency of the AC power supply and the number of poles in the stator winding.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="s7p2hhdvzg7idn123kxoguux" alt="" data-big=https://cms-assets.abletech.nz/medium_0_Yd2pizfc5m_MX_5_D_I_e182961812.png sizes="(min-width: 768px) 330px, (min-width: 1024px) 495px, (min-width: 640px) 103px" src="https://cms-assets.abletech.nz/0_Yd2pizfc5m_MX_5_D_I_e182961812.png" srcset="https://cms-assets.abletech.nz/small_0_Yd2pizfc5m_MX_5_D_I_e182961812.png 330w, https://cms-assets.abletech.nz/medium_0_Yd2pizfc5m_MX_5_D_I_e182961812.png 495w, https://cms-assets.abletech.nz/thumbnail_0_Yd2pizfc5m_MX_5_D_I_e182961812.png 103w" data-zooming-width="495" data-zooming-height="750" loading="lazy" width="495" height="750"></figure>
</div>
<p><strong>But how can an AC motor work in a battery powered car?</strong></p>
<p>Well, power electronics have saved the day! eTrixie has a <a href="http://www.evwest.com/catalog/product_info.php?cPath=1&amp;products_id=103" target="_blank" rel="noopener noreferrer">Curtis 1238 650 Amp, 96 Volt</a> motor controller. This critical component takes in DC power from the battery and ‘inverts’ it to output 3-phase AC power to the AC motor. But the real magic is that the power electronics can vary the frequency of the AC power supplied and so control the speed of the motor.</p>
<p>The combination of the motor controller and AC-motor together provide a near perfect combination.</p>
<p>The other thing the controller does is facilitate regenerative braking. I’ve added a transducer to the brake line which is connected to the controller so it knows when and how hard I’m braking. When the brake is pressed the controller will ‘reverse’ the function of the motor and will convert the rolling momentum of eTrixie into electrical power — the motor will become a generator and also help brake the car — free energy!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="v0i5fzgo8js398p15z4f9aj6" alt="Master cylinder with the brake sensor inserted between the cylinder and the brake-light switch" data-big=https://cms-assets.abletech.nz/small_0_5_Qa_Bq_Ca_NGKJD_6_YT_e6b1c9a0d4.jpg sizes="(min-width: 768px) 500px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_5_Qa_Bq_Ca_NGKJD_6_YT_e6b1c9a0d4.jpg" srcset="https://cms-assets.abletech.nz/small_0_5_Qa_Bq_Ca_NGKJD_6_YT_e6b1c9a0d4.jpg 500w, https://cms-assets.abletech.nz/thumbnail_0_5_Qa_Bq_Ca_NGKJD_6_YT_e6b1c9a0d4.jpg 208w" data-zooming-width="500" data-zooming-height="375" loading="lazy" width="500" height="375"></figure>
</div>
<p><em>Master cylinder with the brake sensor inserted between the cylinder and the brake-light switch</em></p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>eTrixie — part eight</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The Loom</h2>
<p>In addition to the giant battery cables discussed in part seven, there’s also smaller new wiring required.</p>
<p>Firstly there is a loom of wires to run from the motor controller to various places in the car:</p>
<ul>
<li>
<p>Charger port</p>
</li>
<li>
<p>Contactor box</p>
</li>
<li>
<p>Controller gauge and buttons</p>
</li>
<li>
<p>Battery monitor</p>
</li>
<li>
<p>Throttle pedal</p>
</li>
<li>
<p>Brake sensor</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uor51o6lr240vxpr6bgwxxgq" alt="Wiring loom and other electrical components ready for use" data-big=https://cms-assets.abletech.nz/large_1uv_Bxi_Y1obe8l_R0_Pg_UQCOQA_cd78b960cb.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1uv_Bxi_Y1obe8l_R0_Pg_UQCOQA_cd78b960cb.jpeg" srcset="https://cms-assets.abletech.nz/large_1uv_Bxi_Y1obe8l_R0_Pg_UQCOQA_cd78b960cb.jpeg 1000w, https://cms-assets.abletech.nz/small_1uv_Bxi_Y1obe8l_R0_Pg_UQCOQA_cd78b960cb.jpeg 500w, https://cms-assets.abletech.nz/medium_1uv_Bxi_Y1obe8l_R0_Pg_UQCOQA_cd78b960cb.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1uv_Bxi_Y1obe8l_R0_Pg_UQCOQA_cd78b960cb.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Wiring loom and other electrical components ready for use</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hcxgpox4dioyt9fby6ef8p7o" alt="Loom now distributed through the car, under back-seat, and to front of car" data-big=https://cms-assets.abletech.nz/medium_1_Mye2jeel_P4_Zuvg_A_Qvhcoy_Q_3a386630c4.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_Mye2jeel_P4_Zuvg_A_Qvhcoy_Q_3a386630c4.jpeg" srcset="https://cms-assets.abletech.nz/small_1_Mye2jeel_P4_Zuvg_A_Qvhcoy_Q_3a386630c4.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Mye2jeel_P4_Zuvg_A_Qvhcoy_Q_3a386630c4.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Mye2jeel_P4_Zuvg_A_Qvhcoy_Q_3a386630c4.jpeg 156w" data-zooming-width="750" data-zooming-height="750" loading="lazy" width="750" height="750"></figure>
</div>
<p><em>Loom now distributed through the car, under back-seat, and to front of car</em></p>
<p>The main meeting point for the new wiring is under the back where the contractor box is located. This box contains the 12v and 120v relays along with the shunt. The ignition switch is now wired to turn on relays to power up the 12v system and also supply main battery voltage to the motor controller. The motor controller, when it is ready, pulls the contactor to power up the motor.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="m7gna6t9x14bj3y7j0ca0rn3" alt="Contactor box" data-big=https://cms-assets.abletech.nz/large_1cw_Dv3a_VPP_Gu_Dk_Tbt_Mhp_Hmg_99b45af9e2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1cw_Dv3a_VPP_Gu_Dk_Tbt_Mhp_Hmg_99b45af9e2.jpeg" srcset="https://cms-assets.abletech.nz/large_1cw_Dv3a_VPP_Gu_Dk_Tbt_Mhp_Hmg_99b45af9e2.jpeg 1000w, https://cms-assets.abletech.nz/small_1cw_Dv3a_VPP_Gu_Dk_Tbt_Mhp_Hmg_99b45af9e2.jpeg 500w, https://cms-assets.abletech.nz/medium_1cw_Dv3a_VPP_Gu_Dk_Tbt_Mhp_Hmg_99b45af9e2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1cw_Dv3a_VPP_Gu_Dk_Tbt_Mhp_Hmg_99b45af9e2.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Contactor box</em></p>
<p>The shunt (bottom of picture above) is a very precise low valued resistor, which allows the battery monitor to accurately measure all current in and out of the battery. The shunt is connected to the battery monitor with a twisted pair of wires.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="gfejcrhl3sb9mlakip0knsgb" alt="Dash mounted battery monitor and glove box concealed controller gauge" data-big=https://cms-assets.abletech.nz/medium_1g_B_Xl_T_Hvysr_W_Fr6_QGY_Ruwng_71f6966c46.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1g_B_Xl_T_Hvysr_W_Fr6_QGY_Ruwng_71f6966c46.jpeg" srcset="https://cms-assets.abletech.nz/small_1g_B_Xl_T_Hvysr_W_Fr6_QGY_Ruwng_71f6966c46.jpeg 500w, https://cms-assets.abletech.nz/medium_1g_B_Xl_T_Hvysr_W_Fr6_QGY_Ruwng_71f6966c46.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1g_B_Xl_T_Hvysr_W_Fr6_QGY_Ruwng_71f6966c46.jpeg 156w" data-zooming-width="750" data-zooming-height="750" loading="lazy" width="750" height="750"></figure>
</div>
<p><em>Dash mounted battery monitor and glove box concealed controller gauge</em></p>
<p>The only visible sign of the electric upgrade is the battery gauge, which replaces the old petrol gauge. This device, when calibrated, keeps track of the state of the battery so I know how much charge is remaining to power the motor.</p>
<p>I’ve hidden the motor controller gauge, programming button and power/regen mode switch away in the glove box — so they are accessible but don’t change the look of the VW’s spartan dash!</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="x7pwqdazjo06kr1xqdpt5chu" alt="Charge port (left) and AVC2 (right)" data-big=https://cms-assets.abletech.nz/medium_1_C7dy_Zb_QN_Gw_W_Ur0_Po0_Wwb_Cg_7d75409d2d.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 156px" src="https://cms-assets.abletech.nz/1_C7dy_Zb_QN_Gw_W_Ur0_Po0_Wwb_Cg_7d75409d2d.jpeg" srcset="https://cms-assets.abletech.nz/small_1_C7dy_Zb_QN_Gw_W_Ur0_Po0_Wwb_Cg_7d75409d2d.jpeg 500w, https://cms-assets.abletech.nz/medium_1_C7dy_Zb_QN_Gw_W_Ur0_Po0_Wwb_Cg_7d75409d2d.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_C7dy_Zb_QN_Gw_W_Ur0_Po0_Wwb_Cg_7d75409d2d.jpeg 156w" data-zooming-width="750" data-zooming-height="750" loading="lazy" width="750" height="750"></figure>
</div>
<p><em>Charge port (left) and AVC2 (right)</em></p>
<p>Finally, there is some wiring and a small relay device to support the Mains AC charge port. Trixie has a standard J1772 charge port, these ports don’t just receive mains electricity, they also have a couple of additional pins;</p>
<ul>
<li>
<p>Proximity detection — which is wired to the motor controller to prevent the car driving off while connected to the charger</p>
</li>
<li>
<p>Control pilot — which is a communication line between the AC supply and battery monitor, it can be used to cut the AC supply if an over charge condition occurred.</p>
</li>
</ul>
<p>In Trixie, these extra pins are wired to the Active Vehicle Control Module (AVC2) which monitors the pilot signal and interfaces with the battery monitor and motor controller.</p>
<p><strong>Read more about the conversion:</strong></p>
<ul>
<li>
<p>Electric certification in <a href="https://abletech.nz/article/etrixie-part-one">part one</a></p>
</li>
<li>
<p>Power and brakes in <a href="https://abletech.nz/article/etrixie-part-two">part two</a></p>
</li>
<li>
<p>Removal of petrol components in <a href="https://abletech.nz/article/etrixie-part-three">part three</a></p>
</li>
<li>
<p>Flywheel and clutch upgrades in <a href="https://abletech.nz/article/etrixie-part-four">part four</a></p>
</li>
<li>
<p>The AC induction motor in <a href="https://abletech.nz/article/etrixie-part-five">part five</a></p>
</li>
<li>
<p>New Fuel in <a href="https://abletech.nz/article/etrixie-part-six">part six</a></p>
</li>
<li>
<p>High Currents in <a href="https://abletech.nz/article/etrixie-part-seven">part seven</a></p>
</li>
<li>
<p>The Loom in <a href="https://abletech.nz/article/etrixie-part-eight">part eight</a></p>
</li>
<li>
<p>Keeping it cool in <a href="https://abletech.nz/article/etrixie-part-nine">part nine</a></p>
</li>
<li>
<p>Putting it all together in <a href="https://abletech.nz/article/etrixie-part-ten">part 10</a> (including a video of the first drive!)</p>
</li>
<li>
<p>Bottom balancing and battery management systems in <a href="https://abletech.nz/article/etrixie-part-11">part 11</a></p>
</li>
<li>
<p>Getting the certification examination in <a href="https://abletech.nz/article/etrixie-part-12">part 12</a></p>
</li>
<li>
<p>eTrixie the Movie! <a href="https://abletech.nz/article/etrixie-vlog">Video walk through</a></p>
</li>
<li>
<p>A DIY EV in the real world in the <a href="https://abletech.nz/article/etrixie-a-diy-ev-in-the-real-world">final part</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>WebSockets multi-player game</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Live game prototype demo</h2>
<p>Alex has been working on a game prototype that mimics Jackbox TV games. At our recent Tech Talk he gave us a demo and showed us what he’s built so far.</p>
<p>At this stage the app is in a basic raw form with no styling, but it works. We had multi-players involved in Alexandre’s live demo.</p>
<h3>This version of Apples to Apples works like this:</h3>
<ul>
<li>
<p>The main page keeps track of scores and shows a question for each round</p>
</li>
<li>
<p>Each round has a randomly assigned judge. All other players are participants</p>
</li>
<li>
<p>Participants submit their answer based on the current question</p>
</li>
<li>
<p>When everyone has submitted an answer, the judge decides on which answer is best</p>
</li>
<li>
<p>A point is given to the participant who submitted the judge’s preferred answer</p>
</li>
<li>
<p>Rounds proceed until someone reaches three points</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t6z8n5w5r1drjbaje2g76v53" alt="Watch a live demo of the prototype" data-big=https://cms-assets.abletech.nz/large_1x_J_Dq_Q2_Akr_KEF_8_H_Eim_V_Vq5_A_e97f765926.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1x_J_Dq_Q2_Akr_KEF_8_H_Eim_V_Vq5_A_e97f765926.png" srcset="https://cms-assets.abletech.nz/large_1x_J_Dq_Q2_Akr_KEF_8_H_Eim_V_Vq5_A_e97f765926.png 1000w, https://cms-assets.abletech.nz/small_1x_J_Dq_Q2_Akr_KEF_8_H_Eim_V_Vq5_A_e97f765926.png 500w, https://cms-assets.abletech.nz/medium_1x_J_Dq_Q2_Akr_KEF_8_H_Eim_V_Vq5_A_e97f765926.png 750w, https://cms-assets.abletech.nz/thumbnail_1x_J_Dq_Q2_Akr_KEF_8_H_Eim_V_Vq5_A_e97f765926.png 245w" data-zooming-width="1000" data-zooming-height="490" loading="lazy" width="1000" height="490"></figure>
</div>
<p><em>Watch a live demo of the prototype</em></p>
<p>To avoid refreshing pages manually we use websockets. The main page subscribed to a game channel while players subscribed a player channel. For the game to flow naturally each channel gives live instructions on what to do next to the players and main page.</p>
<h3>Check out more recent Tech Talks from our Friday afternoons at Abletech</h3>
<ul>
<li>
<p><a href="https://abletech.nz/article/automate-with-selenium">Automate personal bank transactions with Selenium</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/addressfinder-dashboard">Building an in-office dashboard</a></p>
</li>
<li>
<p><a href="https://abletech.nz/article/content-security-policy">The Content Security Policy</a></p>
</li>
<li>
<p><a href="https://abletech.nz/resource/how-to-configure-vs-code-to-format-elixir-code/">How to configure VS code to format Elixir code</a></p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Seven Sharp Spike</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>If you don’t have this app, you really should get it.</h2>
<p>Spike in AED Locations visitors after the Seven Sharp team highlights how to save a life using the AED Locations app.</p>
<blockquote>
<p>The AED app is free and easy to use, it shows the locations of all the nearest AEDs.</p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="q807ugg0o3bn5ct2gmvep1bc" alt="We all need this app on our phones: aedlocations.co.nz" data-big=https://cms-assets.abletech.nz/large_1jq8_X_Svy_R_Ej_7_r_Wolr_Tgf_A_b41b5f6797.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1jq8_X_Svy_R_Ej_7_r_Wolr_Tgf_A_b41b5f6797.png" srcset="https://cms-assets.abletech.nz/large_1jq8_X_Svy_R_Ej_7_r_Wolr_Tgf_A_b41b5f6797.png 1000w, https://cms-assets.abletech.nz/small_1jq8_X_Svy_R_Ej_7_r_Wolr_Tgf_A_b41b5f6797.png 500w, https://cms-assets.abletech.nz/medium_1jq8_X_Svy_R_Ej_7_r_Wolr_Tgf_A_b41b5f6797.png 750w, https://cms-assets.abletech.nz/thumbnail_1jq8_X_Svy_R_Ej_7_r_Wolr_Tgf_A_b41b5f6797.png 245w" data-zooming-width="1000" data-zooming-height="592" loading="lazy" width="1000" height="592"></figure>
</div>
<p><em>We all need this app on our phones: aedlocations.co.nz</em></p>
<p>Kiwis got busy after watching Seven Sharp, downloading the life-saving app. Find out more about <a href="https://abletech.nz/article/aed-locations-video">the app</a>, <a href="https://abletech.nz/portfolio/from-idea-to-app">why we built the app</a>, and <a href="https://abletech.nz/article/sam-spends-summer-streamlining-%ef%b8%8f%ef%b8%8fsite/">improvements we’ve made</a> to the app.</p>
<blockquote>
<p>The death rate after a sudden cardiac arrest is three times higher than the annual road toll and if it happens outside of a hospital the chances of surviving are only ten percent.
<a href="https://www.tvnz.co.nz/shows/seven-sharp/clips/could-you-save-the-life-of-someone-who-is-suffering-from-cardiac-arrest" target="_blank" rel="noopener noreferrer"><strong>Watch Seven Sharp Could you save the life of someone who is suffering from cardiac arrest? | TVNZ…</strong> <em>Outside of hospital, the chances of survival are only 10 per cent.</em> www.tvnz.co.nz</a></p>
</blockquote>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Sam Spends Summer Streamlining ❤️️ Site</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The all new AED Locations website is now live, thanks to Sam. Check it out on your mobile, or desktop.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="l03xnxhzafby1wijk4mff9m8" alt=""  sizes="" src="https://cms-assets.abletech.nz/0ci_D_Pxa9_A0_rx_OLJL_3d3b050932.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<h2>“It’s saving me hundreds of keystrokes every day”</h2>
<p><em>Gareth Jenkins, Paramedic and Resuscitation Trainer at Auckland Hospital</em></p>
<p>Sam Gard has spent her university summer holidays working on a website that shows heart-attack bystanders where their closest Automated External Defibrillator (AED) is. She worked on the AED Locations app built by Abletech <a href="https://abletech.nz/portfolio/from-idea-to-app/">back in 2010</a>.</p>
<p><strong>The Problem</strong></p>
<p>The manual entry required to add AED locations was time consuming. Gareth Jenkins receives an email when someone wants to register an AED on the site. He gets a flood of these emails. It creates a lot of work for him because the emails are unstructured and the data can have gaps in it.</p>
<p>We also wanted to simplify the website user’s experience, taking into account the stress of an urgent heart-attack scene.</p>
<p><strong>The Solution</strong></p>
<p>Sam’s job was to streamline the email process and to automate as much as possible. This involved creating a standard set of data to input. Gareth could then review and accept the data, instead of querying details and inputting them.</p>
<p>Sam also worked out how to clarify the information for the bystander needing an AED urgently.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rdit38vm7sgz1ac6hz1pziev" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_D_Crf_KH_Qln87z_Xo_KV_1ff1c7736c.jpg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0_D_Crf_KH_Qln87z_Xo_KV_1ff1c7736c.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0_D_Crf_KH_Qln87z_Xo_KV_1ff1c7736c.jpg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<h2>“Now I get an automated message and simply click on a link. It’s pre-populated. I check details, add a region and the new AED is published”</h2>
<p><em>Gareth Jenkins, Paramedic and Resuscitation Trainer at Auckland Hospital</em></p>
<p><strong>What did Sam do?</strong></p>
<p>Sam built the system and tested it. This was a technical build for Sam, but she also played a design role. She considered the data being captured and how it could be used best.</p>
<p>Sam introduced an <em>Add Location Form</em> where public can enter their data into MapMySites. Gareth says, “as people can enter more detail and opening hours it’s saving me hundreds of keystrokes a day”.</p>
<p>With good data on the opening hours of locations, Sam designed a feature that displays the current availability of the AED.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="asg6t2cue6ezlciz1a26zaza" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0vfos1yq_VL_Nu_IX_9_FS_fbfda156f2.jpg sizes="(min-width: 640px) 245px" src="https://cms-assets.abletech.nz/0vfos1yq_VL_Nu_IX_9_FS_fbfda156f2.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0vfos1yq_VL_Nu_IX_9_FS_fbfda156f2.jpg 245w" data-zooming-width="245" data-zooming-height="122" loading="lazy" width="245" height="122"></figure>
</div>
<p>“We kept thinking about what is important in an emergency,” says Sam. “We made the information simple and clear for someone who is distracted.”</p>
<p>“Users can be located automatically or they can type an address. The app shows your closest ten defibrillators and gives driving directions for getting it.”</p>
<p>Sam enjoys the Abletech way of working. “There’s a real team approach so I got to work on a team with an experienced designer and a highly skilled front-end developer.”</p>
<p>Sam fitted in well with the collaborative approach that Abletech uses. Within a team there will be a number of areas of specialty such as User Experience, Geospatial Specialisation, Front-End Engineering or Mobile Development.</p>
<p>The new website works well on a mobile where people can use the website or the app.</p>
<p><strong>Features:</strong></p>
<ul>
<li>
<p>The site offers a ‘use my current location’ option which uses a GPS location to find nearby AEDs without having to type in an address. It shows you the 10 nearest AEDs to your current location. You can also type in your address if you want to.</p>
</li>
<li>
<p>Suggested driving directions are provided. This is useful especially if you’re in an unfamiliar area.</p>
</li>
<li>
<p>You can search by address, suburb, or a point of interest. This is helpful in an unfamiliar area where you may not know street names.</p>
</li>
<li>
<p>Phone numbers are available for locations, along with opening hours.</p>
</li>
<li>
<p>Maps have been improved visually. The graphics capability of your device are utilised to zoom in and out smoothly.</p>
</li>
<li>
<p>Information has been simplified for aid the user.</p>
</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ytz7ojefcyczvef3lq33nzqp" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0a_DT_5_Ta_OV_3s_Abtw_5495f8d405.jpg sizes="(min-width: 640px) 208px" src="https://cms-assets.abletech.nz/0a_DT_5_Ta_OV_3s_Abtw_5495f8d405.jpg" srcset="https://cms-assets.abletech.nz/thumbnail_0a_DT_5_Ta_OV_3s_Abtw_5495f8d405.jpg 208w" data-zooming-width="208" data-zooming-height="156" loading="lazy" width="208" height="156"></figure>
</div>
<p>During her second internship at Abletech, Sam has learnt a lot about maps. She’s gained experience in Geographic Information Systems (GIS) data and techniques. This year Sam’s had a chance to apply more of her university design training, contributing to the design elements of this AED project.</p>
<p>She’s also gained experience with a new JavaScript capability called ES6. This JavaScript upgrade adds many new and significant features to the language.</p>
<p>That’s a summer well-spent! Thanks for spending time with us at Abletech again Sam. We wish you well for uni this year.</p>
<p>Read more about Sam from <a href="https://abletech.nz/article/web-developer-student/">her first summer job at Abletech</a>.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="v9x6k5czb4ff6lb8nubworrj" alt="" data-big=https://cms-assets.abletech.nz/thumbnail_0_YWC_18_Z_Phf_Yko_Qb1_D_a62db23cdf.png sizes="(min-width: 640px) 239px" src="https://cms-assets.abletech.nz/0_YWC_18_Z_Phf_Yko_Qb1_D_a62db23cdf.png" srcset="https://cms-assets.abletech.nz/thumbnail_0_YWC_18_Z_Phf_Yko_Qb1_D_a62db23cdf.png 239w" data-zooming-width="239" data-zooming-height="156" loading="lazy" width="239" height="156"></figure>
</div>
<p><strong>Want to know more about AED Locations?</strong></p>
<p>An AED can increase someone’s chance of survival by up to 80% if applied immediately. AED Locations was built to help speed up your ability to find a defibrillator when you need it.</p>
<p>Wherever you are, find your closest Automated External Defibrillator (AED) in seconds.</p>
<p>Save this AED Locations website to your home screen now, or download the <a href="https://apps.apple.com/nz/app/aed-locations/id424094430" target="_blank" rel="noopener noreferrer">iPhone app</a> or the <a href="https://play.google.com/store/apps/details?id=com.abletech.aedlocations&amp;hl=en_NZ&amp;gl=US" target="_blank" rel="noopener noreferrer">Android app</a>. All the technology, including the hosting, is provided by <a href="https://abletech.nz">Abletech</a>.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Rails Girls SUPERCHARGED⚡️</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>It’s a RubyFest! Wellington is hosting two Ruby events back-to-back: Rails Girls Supercharged and Kiwi Ruby.</h2>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="psbegr4boj495ld5gus8gbch" alt="" data-big=https://cms-assets.abletech.nz/small_1e_U3hqr2exx_LKTNEJ_936w8_Q_6a5f64358a.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1e_U3hqr2exx_LKTNEJ_936w8_Q_6a5f64358a.jpeg" srcset="https://cms-assets.abletech.nz/small_1e_U3hqr2exx_LKTNEJ_936w8_Q_6a5f64358a.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1e_U3hqr2exx_LKTNEJ_936w8_Q_6a5f64358a.jpeg 245w" data-zooming-width="500" data-zooming-height="250" loading="lazy" width="500" height="250"></figure>
</div>
<p>For the first time ever Rails Girls Wellington are running an event designed for people who no longer feel like beginners.</p>
<p>Traditionally, the annual Rails Girls workshops are friendly, supportive introductions designed for women who want to get started with tech. These workshops give participants both the tools, and a community, to build their ideas, skills and confidence.</p>
<p>But this year Rails Girls are branching out with an advanced <a href="http://railsgirls.com/wellington" target="_blank" rel="noopener noreferrer">Supercharged event</a>! And it’s great timing with <a href="https://kiwi.ruby.nz/" target="_blank" rel="noopener noreferrer">Kiwi Ruby conference</a> the following weekend. People coming to Wellington for the conference can arrive early and help with, or attend, the Supercharged event too.</p>
<h2>Dates</h2>
<h3>Rails Girls Supercharged⚡️</h3>
<p><strong>Sat 28th — Sun 29th October | Enspiral Dev Academy, Wellington, NZ</strong></p>
<h3>Kiwi Ruby</h3>
<p><strong>Thurs 2nd — Fri 3rd November | Te Papa, Wellington, NZ</strong></p>
<h2>Rails Girls 2017 Supercharged ⚡️</h2>
<p>This Rails Girls event takes you one step further.</p>
<p><strong>Rails Girls Supercharged</strong> is a two-day FREE workshop for all people with non-binary gender identities, or who identify as girls or women, with basic experience using Ruby on Rails. The aim is to help you deepen your knowledge by building on top of your first website, or a similar beginner application. It’s a fun and supportive environment where it’s OK to make mistakes and our wonderful coaches will help you navigate through any tricky parts. Along the way you’ll meet great people, eat yummy food, and listen to inspiring talks.</p>
<p>Spaces are limited and applications close <strong>Sunday 15 October</strong>, <strong>11pm</strong>. To register, please fill out the attendee <a href="https://docs.google.com/forms/d/e/1FAIpQLSciZmPXzArdOqzRYVwlhMxPlyxxK-dzJdvj_PfpzsfDnLm8hQ/viewform" target="_blank" rel="noopener noreferrer">application form</a>.</p>
<p>If you already feel like you’re not a Ruby on Rails beginner anymore, the organisers are looking for passionate coaches. Are you willing to volunteer some time to share your love of programming &amp; tech with those that want to learn? You’ll get a limited edition shirt too! Apply to be a coach <a href="https://docs.google.com/forms/d/e/1FAIpQLSdCosZcwQ9b8UTv04IMN3BJLPgeMc11owOOOgwsspVOaYpUFA/viewform" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>Check out the <a href="http://railsgirls.com/wellington" target="_blank" rel="noopener noreferrer">Rails Girls Wellington site</a> for more about the Supercharged event.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="mcsz889x8t5z4uqfseg8jgt8" alt="" data-big=https://cms-assets.abletech.nz/large_1r_Aa_IH_Syxnbk_Mom9o_Pl0n_Xg_40dc3f0416.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1r_Aa_IH_Syxnbk_Mom9o_Pl0n_Xg_40dc3f0416.png" srcset="https://cms-assets.abletech.nz/large_1r_Aa_IH_Syxnbk_Mom9o_Pl0n_Xg_40dc3f0416.png 1000w, https://cms-assets.abletech.nz/small_1r_Aa_IH_Syxnbk_Mom9o_Pl0n_Xg_40dc3f0416.png 500w, https://cms-assets.abletech.nz/medium_1r_Aa_IH_Syxnbk_Mom9o_Pl0n_Xg_40dc3f0416.png 750w, https://cms-assets.abletech.nz/thumbnail_1r_Aa_IH_Syxnbk_Mom9o_Pl0n_Xg_40dc3f0416.png 245w" data-zooming-width="1000" data-zooming-height="334" loading="lazy" width="1000" height="334"></figure>
</div>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Necessity > invention</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Innovation born from lockdown</h2>
<p>During covid lockdown we maintained business as usual, working from home. Life went on but we missed our team tradition of gift giving.</p>
<p>Babies were born. Milestones were hit. Appreciations were awarded. We usually celebrate with gifts but had to change things up. Enter the invention of SOS Café.</p>
<p>We gave SOS vouchers and they were redeemed post-lockdown. Recipients could choose their vouchers then look forward to supporting local once it became possible.</p>
<h3>SOS Café</h3>
<p>David Downs launched SOS Café in March 2020 so people could purchase vouchers online. These vouchers embodied the promise of a future visit to cafés and restaurants. Our developers chose to support favourites like Whistling Sisters, La Cabrita, Havana and Soul Shack.</p>
<p>Was it Plato who said necessity is the mother of invention? The initiative supported 2,500 businesses across NZ which sold over $2,000,000 in gift vouchers.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kx0oep9h4g3pst27z44yxl3e" alt="" data-big=https://cms-assets.abletech.nz/large_1u_S_Cm_Dn_O_Uyv44_Zt_S60r9spw_9d4d482086.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1u_S_Cm_Dn_O_Uyv44_Zt_S60r9spw_9d4d482086.png" srcset="https://cms-assets.abletech.nz/large_1u_S_Cm_Dn_O_Uyv44_Zt_S60r9spw_9d4d482086.png 1000w, https://cms-assets.abletech.nz/small_1u_S_Cm_Dn_O_Uyv44_Zt_S60r9spw_9d4d482086.png 500w, https://cms-assets.abletech.nz/medium_1u_S_Cm_Dn_O_Uyv44_Zt_S60r9spw_9d4d482086.png 750w, https://cms-assets.abletech.nz/thumbnail_1u_S_Cm_Dn_O_Uyv44_Zt_S60r9spw_9d4d482086.png 245w" data-zooming-width="1000" data-zooming-height="630" loading="lazy" width="1000" height="630"></figure>
</div>
<h3>SOS Café awarded</h3>
<p>At the NZ Food Awards 2020, SOS Café won the Food Heroes Innovators Award.</p>
<blockquote>
<h4>An amazing initiative that provided much needed cash and support to local cafés — Food Awards Judges</h4>
</blockquote>
<p>Congratulations SOS. Well deserved. Thanks for helping us as an Abletech team but also for your role in supporting our team of five million!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Lowering carbon emissions</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Price comparison of electricity and gas</h2>
<p>Nigel’s household is on a mission to lower their carbon emissions. They’ve changed their central heating. It’s been a full month now — check out the numbers.</p>
<p><a href="https://nigel.ramsay.org.nz/blog/price-comparison-of-electricity-and-gas" target="_blank" rel="noopener noreferrer"><strong>Price Comparison of Electricity and Gas - Nigel Ramsay</strong> <em>We have now had our first full month with the new heat-pump based heating system. In this post, I will compare the raw…</em> nigel.ramsay.org.nz</a></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rjqtcbe2ndf3mkijfvvex10j" alt="" data-big=https://cms-assets.abletech.nz/medium_1_W_Vjhqgn6n_FH_Fajp_Mzi7_WCQ_fb3bff3fda.jpeg sizes="(min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 212px" src="https://cms-assets.abletech.nz/1_W_Vjhqgn6n_FH_Fajp_Mzi7_WCQ_fb3bff3fda.jpeg" srcset="https://cms-assets.abletech.nz/small_1_W_Vjhqgn6n_FH_Fajp_Mzi7_WCQ_fb3bff3fda.jpeg 500w, https://cms-assets.abletech.nz/medium_1_W_Vjhqgn6n_FH_Fajp_Mzi7_WCQ_fb3bff3fda.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_W_Vjhqgn6n_FH_Fajp_Mzi7_WCQ_fb3bff3fda.jpeg 212w" data-zooming-width="750" data-zooming-height="553" loading="lazy" width="750" height="553"></figure>
</div>
<p><a href="https://www.abletech.nz/article/gas-to-electric-zero-carbon-household">Read more about their system here.</a></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Cycle commuting</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>The Abletech bike room is bursting at the seams</h2>
<p>Biking to work is increasingly popular with our developers. Electric bikes reduce the barrier of Wellington hills.</p>
<p>Abletechers use both regular and electric bikes for getting around, and for commuting. Nearly half our Wellington team are biking.</p>
<p>Electric bikes are a great alternative to cars and buses. They’re carbon-friendly, cheaper and get us out in the fresh air. Ebikes give a bit of exercise without us arriving dripping in sweat, they’re faster than any other vehicle and still get the endorphins flowing.</p>
<p>Wellington City Council generously provided hi-vis gear to make sure we’re visible.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="v0dzxyh17dws609wtb3ug8k8" alt="" data-big=https://cms-assets.abletech.nz/large_1_B9_YT_Lu_Piza_4_Mzsx63l_Le_A_8889dc84dd.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_B9_YT_Lu_Piza_4_Mzsx63l_Le_A_8889dc84dd.jpeg" srcset="https://cms-assets.abletech.nz/large_1_B9_YT_Lu_Piza_4_Mzsx63l_Le_A_8889dc84dd.jpeg 1000w, https://cms-assets.abletech.nz/small_1_B9_YT_Lu_Piza_4_Mzsx63l_Le_A_8889dc84dd.jpeg 500w, https://cms-assets.abletech.nz/medium_1_B9_YT_Lu_Piza_4_Mzsx63l_Le_A_8889dc84dd.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_B9_YT_Lu_Piza_4_Mzsx63l_Le_A_8889dc84dd.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="pkkebvv1ipglpc1y8w55l5y0" alt="" data-big=https://cms-assets.abletech.nz/large_1blu8l_MA_Idpx_Mh_I_Pmi4u_Z_Og_bc1d5fd256.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1blu8l_MA_Idpx_Mh_I_Pmi4u_Z_Og_bc1d5fd256.jpeg" srcset="https://cms-assets.abletech.nz/large_1blu8l_MA_Idpx_Mh_I_Pmi4u_Z_Og_bc1d5fd256.jpeg 1000w, https://cms-assets.abletech.nz/small_1blu8l_MA_Idpx_Mh_I_Pmi4u_Z_Og_bc1d5fd256.jpeg 500w, https://cms-assets.abletech.nz/medium_1blu8l_MA_Idpx_Mh_I_Pmi4u_Z_Og_bc1d5fd256.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1blu8l_MA_Idpx_Mh_I_Pmi4u_Z_Og_bc1d5fd256.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="qg822uxdufzuo8e422a2ux3p" alt="" data-big=https://cms-assets.abletech.nz/large_1y_VK_hln_KQALIK_8_I0_Egr_LYQ_54f125f8dc.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1y_VK_hln_KQALIK_8_I0_Egr_LYQ_54f125f8dc.jpeg" srcset="https://cms-assets.abletech.nz/large_1y_VK_hln_KQALIK_8_I0_Egr_LYQ_54f125f8dc.jpeg 1000w, https://cms-assets.abletech.nz/small_1y_VK_hln_KQALIK_8_I0_Egr_LYQ_54f125f8dc.jpeg 500w, https://cms-assets.abletech.nz/medium_1y_VK_hln_KQALIK_8_I0_Egr_LYQ_54f125f8dc.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1y_VK_hln_KQALIK_8_I0_Egr_LYQ_54f125f8dc.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dxns0zo28ey6xwndfrlvlcr7" alt="" data-big=https://cms-assets.abletech.nz/large_1_S_Qiq_J3i_Kn1_PFII_Vz_L_Qt_Bzw_df32ff4ada.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_S_Qiq_J3i_Kn1_PFII_Vz_L_Qt_Bzw_df32ff4ada.jpeg" srcset="https://cms-assets.abletech.nz/large_1_S_Qiq_J3i_Kn1_PFII_Vz_L_Qt_Bzw_df32ff4ada.jpeg 1000w, https://cms-assets.abletech.nz/small_1_S_Qiq_J3i_Kn1_PFII_Vz_L_Qt_Bzw_df32ff4ada.jpeg 500w, https://cms-assets.abletech.nz/medium_1_S_Qiq_J3i_Kn1_PFII_Vz_L_Qt_Bzw_df32ff4ada.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_S_Qiq_J3i_Kn1_PFII_Vz_L_Qt_Bzw_df32ff4ada.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="y4g83s4ed7amfc3nd1gn809y" alt="" data-big=https://cms-assets.abletech.nz/large_18he76g9a_Z_Cw33_HEG_7utt3w_a8c8ce8a91.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/18he76g9a_Z_Cw33_HEG_7utt3w_a8c8ce8a91.jpeg" srcset="https://cms-assets.abletech.nz/large_18he76g9a_Z_Cw33_HEG_7utt3w_a8c8ce8a91.jpeg 1000w, https://cms-assets.abletech.nz/small_18he76g9a_Z_Cw33_HEG_7utt3w_a8c8ce8a91.jpeg 500w, https://cms-assets.abletech.nz/medium_18he76g9a_Z_Cw33_HEG_7utt3w_a8c8ce8a91.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_18he76g9a_Z_Cw33_HEG_7utt3w_a8c8ce8a91.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="t1539ix06b0w59xyxaz03o46" alt="" data-big=https://cms-assets.abletech.nz/large_11ste4f_ERGR_XBY_Yvywaa_Nw_bfc463da70.jpeg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/11ste4f_ERGR_XBY_Yvywaa_Nw_bfc463da70.jpeg" srcset="https://cms-assets.abletech.nz/large_11ste4f_ERGR_XBY_Yvywaa_Nw_bfc463da70.jpeg 750w, https://cms-assets.abletech.nz/small_11ste4f_ERGR_XBY_Yvywaa_Nw_bfc463da70.jpeg 375w, https://cms-assets.abletech.nz/medium_11ste4f_ERGR_XBY_Yvywaa_Nw_bfc463da70.jpeg 563w, https://cms-assets.abletech.nz/thumbnail_11ste4f_ERGR_XBY_Yvywaa_Nw_bfc463da70.jpeg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
</div>
<h3>Read more about cycle commuting</h3>
<ul>
<li>
<p>Tips for <a href="http://www.gw.govt.nz/getting-started/" target="_blank" rel="noopener noreferrer">getting started</a></p>
</li>
<li>
<p>Wellington City Council is <a href="https://bikethere.org.nz/im-not-sure-about-commuting-by-bike/" target="_blank" rel="noopener noreferrer">big on cycling and have a great website</a></p>
</li>
<li>
<p>Wellington Regional Council have lots of <a href="https://can.org.nz/article/everything-you-need-to-know-about-cycle-commuting-in-wellington" target="_blank" rel="noopener noreferrer">info on cycle commuting</a></p>
</li>
<li>
<p>Convert your <a href="https://abletech.nz/article/electric-bike-conversion">standard bike to electric DIY</a>!</p>
</li>
<li>
<p>Bike from <a href="https://abletech.nz/article/team-abletech-cycling-the-length-of-new-zealand">Cape Reinga to Bluff</a>!</p>
</li>
</ul>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Check out Turbolinks</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Abletecher Alexandre Barret has been using Turbolinks. Sit in on this Abletech Tech Talk to find out more.</h2>
<p>Watch the video or read the summary below.</p>
<p><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item vimeo-player" type="text/html" width="640" height="390" src="https://player.vimeo.com/video/218879269" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="xvy8b4aloiqiifvrnfs3yv7i" alt="Alexandre Barret" data-big=https://cms-assets.abletech.nz/large_1_Gbt_GY_04t_E1_BQI_2_Up_F0_9w_6165bd45fb.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Gbt_GY_04t_E1_BQI_2_Up_F0_9w_6165bd45fb.png" srcset="https://cms-assets.abletech.nz/large_1_Gbt_GY_04t_E1_BQI_2_Up_F0_9w_6165bd45fb.png 1000w, https://cms-assets.abletech.nz/small_1_Gbt_GY_04t_E1_BQI_2_Up_F0_9w_6165bd45fb.png 500w, https://cms-assets.abletech.nz/medium_1_Gbt_GY_04t_E1_BQI_2_Up_F0_9w_6165bd45fb.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Gbt_GY_04t_E1_BQI_2_Up_F0_9w_6165bd45fb.png 245w" data-zooming-width="1000" data-zooming-height="636" loading="lazy" width="1000" height="636"></figure>
</div>
<p><em>Alexandre Barret</em></p>
<h3>Turbolinks 5</h3>
<p>With Turbolinks 5 you get the performance benefits of a single-page application without the added complexity of a client-side JavaScript framework. Alex recommends watching Nate Berkopec’s YouTube video which is where he gained a lot of his knowledge. Check out links in the <strong>Resources</strong> below.</p>
<h3>Turbolinks Features</h3>
<ul>
<li>
<p>Optimises navigation automatically</p>
</li>
<li>
<p>No server-side co-operation necessary</p>
</li>
<li>
<p>Respects the web</p>
</li>
<li>
<p>Supports mobile apps</p>
</li>
</ul>
<p>Shopify, GitHub and Basecamp all use Turbolinks libraries and are really quick — under 100ms rendering.</p>
<p>Turbolinks helps with improving the time for HTML Rendering: Parse HTML, Create, Render tree, Evaluate JavaScript, Paint.</p>
<h2>View Over the Wire</h2>
<p>Turbolinks intercepts full page URL requests or <em>visits</em>, (e.g. clicking on a link for a new page), and replaces it with an asynchronous request for the view over XMLHTTPRequest. It then replaces the &lt;body&gt; of the current page’s HTML with the requested page’s selective contents.</p>
<h3>What is a visit?</h3>
<ul>
<li>
<p>Changing browser history</p>
</li>
<li>
<p>Issuing a network request (ajax)</p>
</li>
<li>
<p>Restoring a copy of the page from cache</p>
</li>
<li>
<p>Rendering final response</p>
</li>
</ul>
<h3>What it doesn’t do</h3>
<ul>
<li>
<p>Throw away an entire JavaScript runtime</p>
</li>
<li>
<p>Throw away the entire DOM</p>
</li>
<li>
<p>Reset the JS global scope</p>
</li>
<li>
<p>Parse CSS or evaluate JS from HEAD</p>
</li>
</ul>
<h3>Cool Features</h3>
<ul>
<li>
<p>One JavaScript runtime</p>
</li>
<li>
<p>data-turbolinks-permanent</p>
</li>
</ul>
<h3>Gotchas</h3>
<ul>
<li>
<p>Implementation — good luck with legacy app</p>
</li>
<li>
<p>Write your JavaScript differently — Event delegation, Make transformation idempotent, Global scope, no $(document).ready()</p>
</li>
<li>
<p>Extra care with third party libraries — Google Analytics, Facebook SDK, Google Maps</p>
</li>
</ul>
<h3>Resources</h3>
<ul>
<li>
<p>Information shared from Nate Berkopec’s <a href="https://www.youtube.com/watch?v=eBccDerJPJE&amp;t=11s" target="_blank" rel="noopener noreferrer">YouTube resource</a></p>
</li>
<li>
<p>Alex’s slides are available at <a href="https://slides.com/alexandrebarret/turbolinks-5/live#/" target="_blank" rel="noopener noreferrer">Slides.com</a></p>
</li>
<li>
<p><a href="https://github.com/turbolinks/turbolinks" target="_blank" rel="noopener noreferrer">Turbolinks</a> documentation</p>
</li>
<li>
<p>Examples: <a href="https://alexb52.github.io/turbolinks_101_off/" target="_blank" rel="noopener noreferrer">Turbolinks OFF</a>, <a href="https://alexb52.github.io/turbolinks_101_on/" target="_blank" rel="noopener noreferrer">Turbolinks ON</a>, <a href="https://bravo-welly.nz/" target="_blank" rel="noopener noreferrer">Bravo Welly</a></p>
</li>
</ul>
<p>Alex demonstrated Turbolinks for us and showed what it adds, he also gave responses to questions at the end of his Tech Talk.</p>
<p>Read more from Alex about <a href="https://stories.abletech.nz/implementing-facebook-sdk-with-turbolinks-5-df4e36cbd86f" target="_blank" rel="noopener noreferrer">Implementing Facebook SDK with Turbolinks 5</a>.</p>
<p>Catch another <a href="https://abletech.nz/article/elixir-for-addressfinder">Abletech Tech Talk</a> — this one’s on trialling Elixir.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>4 tips for working from home</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Practical ways to do it well</h2>
<p>We’ve been working from home, or working remotely, for a few years now. Are you moving into a work from home phase? Here are some things we’ve learnt.</p>
<p>We’ve had wins and losses as our people have worked away from our <a href="https://abletech.nz/">Abletech</a> headquarters. Here are our top tips. (Scroll down to read more from our very remote workers.)</p>
<p>These changes, methods and ideas can improve the experience for you, your employer and your colleagues.</p>
<h3><strong>Work / Life Separation</strong></h3>
<p>It’s crucial to maintain a separation between work and life when you’re not going in to the office for a long period. Use these guidelines to get you on track to a healthy balance.</p>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="byvanyupm5orsa8vipl9iu4p" alt="" data-big=https://cms-assets.abletech.nz/large_1_Oo_Q_Qd6l_G0_Bj_U_7_Qq_RJ_Jr_Ew_6e5ab122aa.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_Oo_Q_Qd6l_G0_Bj_U_7_Qq_RJ_Jr_Ew_6e5ab122aa.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Oo_Q_Qd6l_G0_Bj_U_7_Qq_RJ_Jr_Ew_6e5ab122aa.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Oo_Q_Qd6l_G0_Bj_U_7_Qq_RJ_Jr_Ew_6e5ab122aa.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Oo_Q_Qd6l_G0_Bj_U_7_Qq_RJ_Jr_Ew_6e5ab122aa.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Oo_Q_Qd6l_G0_Bj_U_7_Qq_RJ_Jr_Ew_6e5ab122aa.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ei3cnyb6ttqq3j1f9820lyk9" alt="" data-big=https://cms-assets.abletech.nz/large_1_Kmd9o_D1_Lr_Tq_PAD_Fb_Zib_Wvw_7fc3ab6456.jpeg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/1_Kmd9o_D1_Lr_Tq_PAD_Fb_Zib_Wvw_7fc3ab6456.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Kmd9o_D1_Lr_Tq_PAD_Fb_Zib_Wvw_7fc3ab6456.jpeg 750w, https://cms-assets.abletech.nz/small_1_Kmd9o_D1_Lr_Tq_PAD_Fb_Zib_Wvw_7fc3ab6456.jpeg 375w, https://cms-assets.abletech.nz/medium_1_Kmd9o_D1_Lr_Tq_PAD_Fb_Zib_Wvw_7fc3ab6456.jpeg 563w, https://cms-assets.abletech.nz/thumbnail_1_Kmd9o_D1_Lr_Tq_PAD_Fb_Zib_Wvw_7fc3ab6456.jpeg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="aq1k6m28zenoozykzrn0bnxt" alt="Working from home Karori style and Vancouver style" data-big=https://cms-assets.abletech.nz/large_1h_Hnf_Exx_HF_Te4_Ot_S3_LT_Bv8w_9a86afc3a1.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1h_Hnf_Exx_HF_Te4_Ot_S3_LT_Bv8w_9a86afc3a1.jpeg" srcset="https://cms-assets.abletech.nz/large_1h_Hnf_Exx_HF_Te4_Ot_S3_LT_Bv8w_9a86afc3a1.jpeg 1000w, https://cms-assets.abletech.nz/small_1h_Hnf_Exx_HF_Te4_Ot_S3_LT_Bv8w_9a86afc3a1.jpeg 500w, https://cms-assets.abletech.nz/medium_1h_Hnf_Exx_HF_Te4_Ot_S3_LT_Bv8w_9a86afc3a1.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1h_Hnf_Exx_HF_Te4_Ot_S3_LT_Bv8w_9a86afc3a1.jpeg 208w" data-zooming-width="1000" data-zooming-height="750" loading="lazy" width="1000" height="750"></figure>
</div>
<p><em>Working from home Karori style and Vancouver style</em></p>
<h2>1. Harness your habits</h2>
<p>We all have habits. Be aware of yours and take control. This is your chance to decide what your rhythm will be. Make your habits great.</p>
<ul>
<li>
<p>Set work hours and break hours, and try to stick to them</p>
</li>
<li>
<p>If possible, assign one work space in your house, ideally one room — don’t make this your bedroom</p>
</li>
<li>
<p>Work at a desk or table — working on your bed is not a sustainable position physically or mentally</p>
</li>
<li>
<p>Maintain your daily routine; shower, dress, make-up — anything you would normally do on an office day, continue. This will help you feel like you’re still ‘going to work’, and coming home</p>
</li>
<li>
<p>Take breaks. You might find it easier to get into the flow while working from home, and you may neglect normal needs. Regularly get up, walk around, drink water, etc</p>
</li>
<li>
<p>Go for a walk outside at lunch</p>
</li>
<li>
<p>Don’t do chores and other normal home activities during work hours, or break hours. This is important for keeping a separation between work time and relax time. This will both impact your productivity and long term sustainability</p>
</li>
<li>
<p>Try new things. While habits are important, variety is too</p>
</li>
</ul>
<h2>2. Communicate well and often</h2>
<ul>
<li>
<p>Don’t hesitate to send people messages. People can respond in their own time: You’re not interrupting them</p>
</li>
<li>
<p>Conversely, don’t feel the need to respond immediately if you’re in the flow. It’s easy to be distracted by messages: balance your own work and replying to others</p>
</li>
<li>
<p>Post in a ‘Watercooler’ instant messaging channel everyday! The best way to avoid feeling isolated in comparison to being in the office is to <em>continue</em> the daily small talks. It might be weird and awkward to start with; battle on — it’ll be easier if everyone is doing it</p>
</li>
<li>
<p>Audio/Video calls are a great way to collaborate. Some people may want to be involved in these a majority of their day, and some may not. Individuals will be productive in different ways</p>
</li>
<li>
<p>Respect that everyone will be productive with varying levels of communication. Let others know if you are struggling with the level of communication</p>
</li>
</ul>
<h2>3. Equipment matters</h2>
<ul>
<li>
<p>Speakers + Microphone is the least ideal setup for voice chats. Audio feedback is the bane of remote collaborative work — try get hold of some headphones, airpods, anything where the audio and microphone are isolated</p>
</li>
<li>
<p>Try to emulate your office setup — it’ll be familiar, and you’ll be coming back to it at some point</p>
</li>
</ul>
<h2>4. Deal with issues</h2>
<p>Issues will crop up and they’re better tackled sooner rather than later. Communication is key. Talk to your employers, colleagues, friends. Seek out advice from people with experience: it’s unlikely you’re the first person to experience any problem, and it’s unlikely you’ll be the first to solve it! The more we share, the better experience we’ll all have.</p>
<p>Those are our four top tips to get you humming. It’s taken us years to refine those! They work. May you enjoy your new location and the advantages that working from home will offer you.</p>
<h3>Read more</h3>
<ul>
<li>
<p><a href="https://stories.abletech.nz/16-months-remote-in-review-65eae2acc49d" target="_blank" rel="noopener noreferrer">Getting set up to work remotely</a></p>
</li>
<li>
<p><a href="https://stories.abletech.nz/a-day-in-the-life-of-an-abletech-developer-90d554e1ebc7" target="_blank" rel="noopener noreferrer">Working from snowy Canada when your team’s in NZ</a></p>
</li>
<li>
<p><a href="https://stories.abletech.nz/abletech-vancouver-84dd8ca8c7b3" target="_blank" rel="noopener noreferrer">Remote working — a tour</a></p>
</li>
</ul>
<div class="image-wrapper multiple" style="height: min(260, calc(100vw / 3)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="y0a7lesub65u96w6ta3ytby9" alt="" data-big=https://cms-assets.abletech.nz/large_1_C2jla_A_Xk_K1f5reh_Bppi3g_e2f4129eec.jpeg sizes="(min-width: 1280px) 750px, (min-width: 768px) 375px, (min-width: 1024px) 563px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/1_C2jla_A_Xk_K1f5reh_Bppi3g_e2f4129eec.jpeg" srcset="https://cms-assets.abletech.nz/large_1_C2jla_A_Xk_K1f5reh_Bppi3g_e2f4129eec.jpeg 750w, https://cms-assets.abletech.nz/small_1_C2jla_A_Xk_K1f5reh_Bppi3g_e2f4129eec.jpeg 375w, https://cms-assets.abletech.nz/medium_1_C2jla_A_Xk_K1f5reh_Bppi3g_e2f4129eec.jpeg 563w, https://cms-assets.abletech.nz/thumbnail_1_C2jla_A_Xk_K1f5reh_Bppi3g_e2f4129eec.jpeg 117w" data-zooming-width="750" data-zooming-height="1000" loading="lazy" width="750" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="maxd48dzkccsuzrb3ydjte59" alt="" data-big=https://cms-assets.abletech.nz/large_1_V9_S_Xr_E_Ip_OA_09_Vq_Or_Wr1_Wg_28bddc042b.jpeg sizes="(min-width: 1280px) 749px, (min-width: 768px) 374px, (min-width: 1024px) 562px, (min-width: 640px) 117px" src="https://cms-assets.abletech.nz/1_V9_S_Xr_E_Ip_OA_09_Vq_Or_Wr1_Wg_28bddc042b.jpeg" srcset="https://cms-assets.abletech.nz/large_1_V9_S_Xr_E_Ip_OA_09_Vq_Or_Wr1_Wg_28bddc042b.jpeg 749w, https://cms-assets.abletech.nz/small_1_V9_S_Xr_E_Ip_OA_09_Vq_Or_Wr1_Wg_28bddc042b.jpeg 374w, https://cms-assets.abletech.nz/medium_1_V9_S_Xr_E_Ip_OA_09_Vq_Or_Wr1_Wg_28bddc042b.jpeg 562w, https://cms-assets.abletech.nz/thumbnail_1_V9_S_Xr_E_Ip_OA_09_Vq_Or_Wr1_Wg_28bddc042b.jpeg 117w" data-zooming-width="749" data-zooming-height="1000" loading="lazy" width="749" height="1000"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="glt2dpaivjwrj5tsikl0klu1" alt="Working from home looks different for each of us" data-big=https://cms-assets.abletech.nz/large_1_Hmjc_No_J3_WD_2_Pu_WJ_Xs1m_A_b4211248ba.jpeg sizes="(min-width: 1280px) 565px, (min-width: 768px) 282px, (min-width: 1024px) 423px, (min-width: 640px) 88px" src="https://cms-assets.abletech.nz/1_Hmjc_No_J3_WD_2_Pu_WJ_Xs1m_A_b4211248ba.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Hmjc_No_J3_WD_2_Pu_WJ_Xs1m_A_b4211248ba.jpeg 565w, https://cms-assets.abletech.nz/small_1_Hmjc_No_J3_WD_2_Pu_WJ_Xs1m_A_b4211248ba.jpeg 282w, https://cms-assets.abletech.nz/medium_1_Hmjc_No_J3_WD_2_Pu_WJ_Xs1m_A_b4211248ba.jpeg 423w, https://cms-assets.abletech.nz/thumbnail_1_Hmjc_No_J3_WD_2_Pu_WJ_Xs1m_A_b4211248ba.jpeg 88w" data-zooming-width="565" data-zooming-height="1000" loading="lazy" width="565" height="1000"></figure>
</div>
<p><em>Working from home looks different for each of us</em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Get rid of your Vim plugin manager</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Get rid of your Vim plugin manager</h2>
<p>With the advent of Vim 8.0, there is now a native way to add plugins (now called packages), so you can get rid of your Vundle/Pathogen/<code>&amp;lt;insert flavour here&amp;gt;</code> package manager at long last.</p>
<p>I’ve been a happy user of Vundle for a number of years now, but the chance to simplify my configuration is always welcome.</p>
<p>If you’ve used Pathogen before, you will find Vim 8's native package management intuitive.</p>
<p>Simply place your package in a directory path like so:</p>
<pre><code class="hljs">~<span class="hljs-regexp">/.vim/pa</span>ck/&lt;<span class="hljs-keyword">package</span> group&gt;<span class="hljs-regexp">/start/</span>&lt;your <span class="hljs-keyword">package</span>&gt;
</code></pre>
<p>What is that <code>start</code> folder? I’m glad you asked… There are two possible folders you can have at that tier.</p>
<ol>
<li>
<p>The <code>start</code> folder contains packages that are loaded when Vim starts up.</p>
</li>
<li>
<p>The <code>opt</code> folder contains “optional” package that are not loaded, but can be optionally loaded using <code>:packadd packagename</code> inside Vim.</p>
</li>
</ol>
<h3>But I miss Vundle…</h3>
<p>If managing your packages using git submodules doesn’t sound fun (and it’s not, in my opinion) with a few lines of bash we can get a script that will install or update your packages. Just like Vundle but faster!</p>
<p>I always find an example easiest, so check out the below. The two methods at the top are the entire package manager install and update functionality!</p>
<p>On my machine, this file lives at: <code>~/.vim/pack/install.sh</code> and running it updates existing packages and installs new ones. It is very fast! Adding a new package is a single line in this file, so in my mind, I’ve lost none of Vundle’s convenience.</p>
<pre><code class="hljs"><span class="hljs-comment">#!/usr/bin/env bash</span>

<span class="hljs-comment"># Create new folder in ~/.vim/pack that contains a start folder and cd into it.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Arguments:</span>
<span class="hljs-comment">#   package_group, a string folder name to create and change into.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Examples:</span>
<span class="hljs-comment">#   set_group syntax-highlighting</span>
<span class="hljs-comment">#</span>
<span class="hljs-keyword">function</span> set_group () {
  package_group=<span class="hljs-variable">$1</span>
  path=<span class="hljs-string">&quot;$HOME/.vim/pack/$package_group/start&quot;</span>
  mkdir -p <span class="hljs-string">&quot;$path&quot;</span>
  cd <span class="hljs-string">&quot;$path&quot;</span> || <span class="hljs-keyword">exit</span>
}

<span class="hljs-comment"># Clone or update a git repo in the current directory.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Arguments:</span>
<span class="hljs-comment">#   repo_url, a URL to the git repo.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Examples:</span>
<span class="hljs-comment">#   package https://github.com/tpope/vim-endwise.git</span>
<span class="hljs-comment">#</span>
<span class="hljs-keyword">function</span> package () {
  repo_url=<span class="hljs-variable">$1</span>
  expected_repo=$(basename <span class="hljs-string">&quot;$repo_url&quot;</span> .git)
  <span class="hljs-keyword">if</span> [ -d <span class="hljs-string">&quot;$expected_repo&quot;</span> ]; then
    cd <span class="hljs-string">&quot;$expected_repo&quot;</span> || <span class="hljs-keyword">exit</span>
    result=$(git pull --force)
    echo <span class="hljs-string">&quot;$expected_repo: $result&quot;</span>
  <span class="hljs-keyword">else</span>
    echo <span class="hljs-string">&quot;$expected_repo: Installing...&quot;</span>
    git clone -q <span class="hljs-string">&quot;$repo_url&quot;</span>
  fi
}

(
set_group ruby
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/tpope/</span>vim-rails.git &amp;
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/tpope/</span>vim-rake.git &amp;
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/tpope/</span>vim-bundler.git &amp;
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/tpope/</span>vim-endwise.git &amp;
wait
) &amp;

(
set_group syntax
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/kchmck/</span>vim-coffee-script.git &amp;
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/tpope/</span>vim-markdown.git &amp;
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/ap/</span>vim-css-color.git &amp;
wait
) &amp;

(
set_group colorschemes
package https:<span class="hljs-regexp">//gi</span>thub.com<span class="hljs-regexp">/altercation/</span>vim-colors-solarized.git &amp;
wait
) &amp;

wait
</code></pre>
<p>There is a little bit of bash that may be unfamiliar:</p>
<ul>
<li>
<p><code>( &amp;lt;set_group and package invocations&amp;gt; ) &amp;</code> This starts a new subshell, so we can fetch all these packages with haste!</p>
</li>
<li>
<p><code>wait</code> the magic keyword that tells bash to wait until all subshells have finished before continuing. Bash will only wait for subshells that are a direct child of the current process, hence why we need this call in each of our subshells to wait for each <code>package</code> call to finish before proceeding.</p>
</li>
</ul>
<p>Well, I hope this inspires you to direct your package manager and move to a more native Vim experience! Reducing dependancies always feels good.</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Design for Developers</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h3><em>A simple guide to quickly improve your designs</em></h3>
<p>Design and development are sometimes thought of as two very opposite disciplines — one is creative and one is logical. Developers can get stuck in this trap, avoiding design because they ‘aren’t creative’ and getting paralysed when they inevitably have to make a minor design decision.</p>
<p>I wrote this blog to help demystify design for developers, so they can understand designers’ craft and motivations. I also wanted to show that logic and patterns that are just as fundamental to good design as creativity. There are systemic approaches we can take to improve our design work.</p>
<p>To give a concrete example we will work on improving the design for this recipe for Belgian waffles. Let’s say this is our first attempt at designing the screen:</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zub47agoofx7prn6nfbkp92p" alt="" data-big=https://cms-assets.abletech.nz/large_1_Q_Lny9_ILB_5h_Mn1_TK_s_e_ULA_4d8016f816.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Q_Lny9_ILB_5h_Mn1_TK_s_e_ULA_4d8016f816.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Q_Lny9_ILB_5h_Mn1_TK_s_e_ULA_4d8016f816.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Q_Lny9_ILB_5h_Mn1_TK_s_e_ULA_4d8016f816.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Q_Lny9_ILB_5h_Mn1_TK_s_e_ULA_4d8016f816.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Q_Lny9_ILB_5h_Mn1_TK_s_e_ULA_4d8016f816.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<p>To make our improvements we are going to learn about and apply each of the major design principles.</p>
<ol>
<li>
<p>Contrast</p>
</li>
<li>
<p>Hierarchy</p>
</li>
<li>
<p>Balance</p>
</li>
<li>
<p>Alignment</p>
</li>
<li>
<p>Repetition</p>
</li>
</ol>
<h2>Contrast</h2>
<blockquote>
<p>the difference between two or more things</p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="opsdk0v9s9rda0kiibmqmtkd" alt="" data-big=https://cms-assets.abletech.nz/large_16t0_YU_Eku_vb_OO_8_T0_Bp_Rckw_49810a5c54.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/16t0_YU_Eku_vb_OO_8_T0_Bp_Rckw_49810a5c54.jpeg" srcset="https://cms-assets.abletech.nz/large_16t0_YU_Eku_vb_OO_8_T0_Bp_Rckw_49810a5c54.jpeg 1000w, https://cms-assets.abletech.nz/small_16t0_YU_Eku_vb_OO_8_T0_Bp_Rckw_49810a5c54.jpeg 500w, https://cms-assets.abletech.nz/medium_16t0_YU_Eku_vb_OO_8_T0_Bp_Rckw_49810a5c54.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_16t0_YU_Eku_vb_OO_8_T0_Bp_Rckw_49810a5c54.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<p>Creating points of difference in a design can help a viewer to break down information, or create a mood. In the image on the left there is contrast in the colour choice — black and white, the heading size and the ‘movement’ of the heading, compared with the strict grid layout of the other information.</p>
<p>If we compare the designs of the two books on the right ‘Resurrecting Jesus’ has a more direct and aggressive feel to it, while ‘Active Hope’ is more soothing. The contrast between the stripes and the background helps create this feeling.</p>
<p>Looking back at our design with contrast in mind we decide to colour the recipe instructions to help them stand out from the background. We also make our heading larger to create distinction between our heading and instructions.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ozkh0lp8fft29jill8gtnkmk" alt="" data-big=https://cms-assets.abletech.nz/large_12_QR_0_4_Vr_X5_O_Kbv0_Vw_UJE_3w_9b384b4285.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/12_QR_0_4_Vr_X5_O_Kbv0_Vw_UJE_3w_9b384b4285.jpeg" srcset="https://cms-assets.abletech.nz/large_12_QR_0_4_Vr_X5_O_Kbv0_Vw_UJE_3w_9b384b4285.jpeg 1000w, https://cms-assets.abletech.nz/small_12_QR_0_4_Vr_X5_O_Kbv0_Vw_UJE_3w_9b384b4285.jpeg 500w, https://cms-assets.abletech.nz/medium_12_QR_0_4_Vr_X5_O_Kbv0_Vw_UJE_3w_9b384b4285.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_12_QR_0_4_Vr_X5_O_Kbv0_Vw_UJE_3w_9b384b4285.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<h2>Hierarchy</h2>
<blockquote>
<p>Arrangement of things in order of importance</p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="f6avm7veba4qodjs8k3zkske" alt="[Finance app](https://dribbble.com/shots/14038579-Dark-UI-for-finance-App), [Plants app](https://dribbble.com/shots/14167159-Plants-App-UX-UI-Design)" data-big=https://cms-assets.abletech.nz/large_1_JCT_50zqs_Q4_O_Bm3_J_Mg_Oi9_Rw_aad5efa49b.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_JCT_50zqs_Q4_O_Bm3_J_Mg_Oi9_Rw_aad5efa49b.jpeg" srcset="https://cms-assets.abletech.nz/large_1_JCT_50zqs_Q4_O_Bm3_J_Mg_Oi9_Rw_aad5efa49b.jpeg 1000w, https://cms-assets.abletech.nz/small_1_JCT_50zqs_Q4_O_Bm3_J_Mg_Oi9_Rw_aad5efa49b.jpeg 500w, https://cms-assets.abletech.nz/medium_1_JCT_50zqs_Q4_O_Bm3_J_Mg_Oi9_Rw_aad5efa49b.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_JCT_50zqs_Q4_O_Bm3_J_Mg_Oi9_Rw_aad5efa49b.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<p><em><a href="https://dribbble.com/shots/14038579-Dark-UI-for-finance-App" target="_blank" rel="noopener noreferrer">Finance app</a>, <a href="https://dribbble.com/shots/14167159-Plants-App-UX-UI-Design" target="_blank" rel="noopener noreferrer">Plants app</a></em></p>
<p>Hierarchy is all about recognising the most important parts of a design and drawing attention to them. It is very important in web design as it helps the user decide how to interact with a page.</p>
<p>In the financial app on the left we know that the amount of money received, and whether the balance is trending up or down, are the most important due to their emphasis. In the store on the right the design draws us in by emphasising the photo of the wall planter. We know that the height, temperature and pot information is secondary.</p>
<p>We look at our design and put ourselves in our user’s shoes — which parts of the recipe are likely to be more important to them? We decide that the photo, and the information about prep time and servings is critical to help our user decide if they want to make this recipe, so we give them priority. We also move the bookmark button up the screen as it seems likely our user will want to save recipes for later</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="m1gj9kv4vblbovnm4th5h7sz" alt="" data-big=https://cms-assets.abletech.nz/large_1f2t_A_Zs_T_Jet_U_Mp4yjjr1r2_A_8ff3069ec9.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1f2t_A_Zs_T_Jet_U_Mp4yjjr1r2_A_8ff3069ec9.jpeg" srcset="https://cms-assets.abletech.nz/large_1f2t_A_Zs_T_Jet_U_Mp4yjjr1r2_A_8ff3069ec9.jpeg 1000w, https://cms-assets.abletech.nz/small_1f2t_A_Zs_T_Jet_U_Mp4yjjr1r2_A_8ff3069ec9.jpeg 500w, https://cms-assets.abletech.nz/medium_1f2t_A_Zs_T_Jet_U_Mp4yjjr1r2_A_8ff3069ec9.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1f2t_A_Zs_T_Jet_U_Mp4yjjr1r2_A_8ff3069ec9.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<h2>Balance</h2>
<blockquote>
<p>Having the right amount of something</p>
</blockquote>
<p>There are two ways to create balance: symmetry and asymmetry</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="k7yg9776v52wbv4tzhv0lphg" alt="[Flight Booking App](https://dribbble.com/shots/5851328-Flight-Booking-App)" data-big=https://cms-assets.abletech.nz/large_1v7f_U_Yu_Wax_UBBX_Hg9_Kqd_CBA_05b5c95e7d.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1v7f_U_Yu_Wax_UBBX_Hg9_Kqd_CBA_05b5c95e7d.png" srcset="https://cms-assets.abletech.nz/large_1v7f_U_Yu_Wax_UBBX_Hg9_Kqd_CBA_05b5c95e7d.png 1000w, https://cms-assets.abletech.nz/small_1v7f_U_Yu_Wax_UBBX_Hg9_Kqd_CBA_05b5c95e7d.png 500w, https://cms-assets.abletech.nz/medium_1v7f_U_Yu_Wax_UBBX_Hg9_Kqd_CBA_05b5c95e7d.png 750w, https://cms-assets.abletech.nz/thumbnail_1v7f_U_Yu_Wax_UBBX_Hg9_Kqd_CBA_05b5c95e7d.png 208w" data-zooming-width="1000" data-zooming-height="748" loading="lazy" width="1000" height="748"></figure>
</div>
<p><em><a href="https://dribbble.com/shots/5851328-Flight-Booking-App" target="_blank" rel="noopener noreferrer">Flight Booking App</a></em></p>
<p>This Flight Booking App is using symmetry to help the user breakdown information. Arranging flight departure time symmetrically opposite the arrival time makes it easy to understand the interface. For this reason symmetry is popular in web design.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="b1uu6k1i8qmlj1ed674bi9td" alt="[Bare Minimum Editorial](https://dribbble.com/shots/9334394-Bare-Minimum-Editorial), [Made of Two Posters](https://www.behance.net/gallery/23930261/Made-of-Two-Posters)" data-big=https://cms-assets.abletech.nz/large_1_Vlq1n_Wd_M_Rw_UTF_8_Z_5gi_CFA_02b63d031c.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Vlq1n_Wd_M_Rw_UTF_8_Z_5gi_CFA_02b63d031c.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Vlq1n_Wd_M_Rw_UTF_8_Z_5gi_CFA_02b63d031c.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Vlq1n_Wd_M_Rw_UTF_8_Z_5gi_CFA_02b63d031c.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Vlq1n_Wd_M_Rw_UTF_8_Z_5gi_CFA_02b63d031c.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Vlq1n_Wd_M_Rw_UTF_8_Z_5gi_CFA_02b63d031c.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<p><em><a href="https://dribbble.com/shots/9334394-Bare-Minimum-Editorial" target="_blank" rel="noopener noreferrer">Bare Minimum Editorial</a>, <a href="https://www.behance.net/gallery/23930261/Made-of-Two-Posters" target="_blank" rel="noopener noreferrer">Made of Two Posters</a></em></p>
<p>Asymmetry can also be used to create more dramatic effects. The design on the left uses asymmetry to create a high-end feel, while the right has a sense of speed and movement.</p>
<h2>Alignment</h2>
<blockquote>
<p>To arrange things in a straight line</p>
</blockquote>
<p>Alignment is another useful principle to help you organise your designs.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tyxvp4c625p968488hyqarin" alt="[Magazine template](https://ksioks.com/print-templates/fashion-lifestyle-magazine-template-3530)" data-big=https://cms-assets.abletech.nz/large_1_Z38_BC_42j7_H4_wq_B_Lskvaqg_cdc88df79c.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 236px" src="https://cms-assets.abletech.nz/1_Z38_BC_42j7_H4_wq_B_Lskvaqg_cdc88df79c.png" srcset="https://cms-assets.abletech.nz/large_1_Z38_BC_42j7_H4_wq_B_Lskvaqg_cdc88df79c.png 1000w, https://cms-assets.abletech.nz/small_1_Z38_BC_42j7_H4_wq_B_Lskvaqg_cdc88df79c.png 500w, https://cms-assets.abletech.nz/medium_1_Z38_BC_42j7_H4_wq_B_Lskvaqg_cdc88df79c.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Z38_BC_42j7_H4_wq_B_Lskvaqg_cdc88df79c.png 236w" data-zooming-width="1000" data-zooming-height="661" loading="lazy" width="1000" height="661"></figure>
</div>
<p><em><a href="https://ksioks.com/print-templates/fashion-lifestyle-magazine-template-3530" target="_blank" rel="noopener noreferrer">Magazine template</a></em></p>
<p>Looking at this design, at first it may appear that things have been laid out randomly. In reality there is a strong grid system at play — each of the elements snaps to a column. Even the pull quotes and the page information at the bottom align with a column.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="h20egunafd2icvswwpfzm33z" alt="" data-big=https://cms-assets.abletech.nz/large_1_U6j_J_Wwi_F_Yb2p_U_Ntny_Hl5_QQ_78db6f04d0.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 236px" src="https://cms-assets.abletech.nz/1_U6j_J_Wwi_F_Yb2p_U_Ntny_Hl5_QQ_78db6f04d0.png" srcset="https://cms-assets.abletech.nz/large_1_U6j_J_Wwi_F_Yb2p_U_Ntny_Hl5_QQ_78db6f04d0.png 1000w, https://cms-assets.abletech.nz/small_1_U6j_J_Wwi_F_Yb2p_U_Ntny_Hl5_QQ_78db6f04d0.png 500w, https://cms-assets.abletech.nz/medium_1_U6j_J_Wwi_F_Yb2p_U_Ntny_Hl5_QQ_78db6f04d0.png 750w, https://cms-assets.abletech.nz/thumbnail_1_U6j_J_Wwi_F_Yb2p_U_Ntny_Hl5_QQ_78db6f04d0.png 236w" data-zooming-width="1000" data-zooming-height="662" loading="lazy" width="1000" height="662"></figure>
</div>
<p>When I was learning to code my mentors would refuse to look at my code until I had the indentation correct. The grid system is the design equivalent of correcting your indentation — my design mentors would insist on seeing the grid guidelines, and checking whether your elements snapped to them before helping. If they didn’t you would either change the design, or you would modify your grid, adding and subtracting columns and rows.</p>
<p>Using a grid system can prevent you from just moving elements around on a page, trying to decide what looks good based on feel. It gives you a concrete system to follow.</p>
<p>If you aren’t sure where to start with grids I would suggest creating a few different ones — three columns by three rows, four by four and five by five for example. Snap your elements to your grid and see which works the best.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hxliwunt61knwu2gaou8au07" alt="[RAW — MAGAZINE by Ignacio Brito](https://www.behance.net/gallery/14447579/RAW-MAGAZINE)" data-big=https://cms-assets.abletech.nz/large_1_Tyuxk_Y3_Mu4w1_WUN_5l_Um_NGA_e151ad15a9.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 216px" src="https://cms-assets.abletech.nz/1_Tyuxk_Y3_Mu4w1_WUN_5l_Um_NGA_e151ad15a9.png" srcset="https://cms-assets.abletech.nz/large_1_Tyuxk_Y3_Mu4w1_WUN_5l_Um_NGA_e151ad15a9.png 1000w, https://cms-assets.abletech.nz/small_1_Tyuxk_Y3_Mu4w1_WUN_5l_Um_NGA_e151ad15a9.png 500w, https://cms-assets.abletech.nz/medium_1_Tyuxk_Y3_Mu4w1_WUN_5l_Um_NGA_e151ad15a9.png 750w, https://cms-assets.abletech.nz/thumbnail_1_Tyuxk_Y3_Mu4w1_WUN_5l_Um_NGA_e151ad15a9.png 216w" data-zooming-width="1000" data-zooming-height="724" loading="lazy" width="1000" height="724"></figure>
</div>
<p><em><a href="https://www.behance.net/gallery/14447579/RAW-MAGAZINE" target="_blank" rel="noopener noreferrer">RAW — MAGAZINE by Ignacio Brito</a></em></p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rr1hc97g4cqc4wlt7dbyl4xx" alt="" data-big=https://cms-assets.abletech.nz/large_1_J_We_K_Nq2axoy5_Md44nyv_Ysw_a4512813da.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 216px" src="https://cms-assets.abletech.nz/1_J_We_K_Nq2axoy5_Md44nyv_Ysw_a4512813da.png" srcset="https://cms-assets.abletech.nz/large_1_J_We_K_Nq2axoy5_Md44nyv_Ysw_a4512813da.png 1000w, https://cms-assets.abletech.nz/small_1_J_We_K_Nq2axoy5_Md44nyv_Ysw_a4512813da.png 500w, https://cms-assets.abletech.nz/medium_1_J_We_K_Nq2axoy5_Md44nyv_Ysw_a4512813da.png 750w, https://cms-assets.abletech.nz/thumbnail_1_J_We_K_Nq2axoy5_Md44nyv_Ysw_a4512813da.png 216w" data-zooming-width="1000" data-zooming-height="723" loading="lazy" width="1000" height="723"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="msv67wdbmwjke8hck2mo14sv" alt="" data-big=https://cms-assets.abletech.nz/large_14n_Mo_U_Qkf_Qq_YG_Anvw_U47bc_Q_f723bf35f0.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 215px" src="https://cms-assets.abletech.nz/14n_Mo_U_Qkf_Qq_YG_Anvw_U47bc_Q_f723bf35f0.png" srcset="https://cms-assets.abletech.nz/large_14n_Mo_U_Qkf_Qq_YG_Anvw_U47bc_Q_f723bf35f0.png 1000w, https://cms-assets.abletech.nz/small_14n_Mo_U_Qkf_Qq_YG_Anvw_U47bc_Q_f723bf35f0.png 500w, https://cms-assets.abletech.nz/medium_14n_Mo_U_Qkf_Qq_YG_Anvw_U47bc_Q_f723bf35f0.png 750w, https://cms-assets.abletech.nz/thumbnail_14n_Mo_U_Qkf_Qq_YG_Anvw_U47bc_Q_f723bf35f0.png 215w" data-zooming-width="1000" data-zooming-height="725" loading="lazy" width="1000" height="725"></figure>
</div>
<p>To help our users easily understand the recipe we decide on a balance of symmetry and align our elements.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lqvu6wirbo614m1k89yoe23z" alt="" data-big=https://cms-assets.abletech.nz/large_1thwy_P4i9zy_W_Mcyh4_Mq_V2_A_47dff31c9e.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1thwy_P4i9zy_W_Mcyh4_Mq_V2_A_47dff31c9e.jpeg" srcset="https://cms-assets.abletech.nz/large_1thwy_P4i9zy_W_Mcyh4_Mq_V2_A_47dff31c9e.jpeg 1000w, https://cms-assets.abletech.nz/small_1thwy_P4i9zy_W_Mcyh4_Mq_V2_A_47dff31c9e.jpeg 500w, https://cms-assets.abletech.nz/medium_1thwy_P4i9zy_W_Mcyh4_Mq_V2_A_47dff31c9e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1thwy_P4i9zy_W_Mcyh4_Mq_V2_A_47dff31c9e.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<h2>Repetition</h2>
<blockquote>
<p>Recurrence of two or more things</p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="levd9lzmfasw1wlp27low061" alt="[Duda](https://mindsparklemag.com/design/duda/)" data-big=https://cms-assets.abletech.nz/large_1_Vrd_LMOZ_II_0_V7k_FGOG_0h_Q_e22e5771fe.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Vrd_LMOZ_II_0_V7k_FGOG_0h_Q_e22e5771fe.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Vrd_LMOZ_II_0_V7k_FGOG_0h_Q_e22e5771fe.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Vrd_LMOZ_II_0_V7k_FGOG_0h_Q_e22e5771fe.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Vrd_LMOZ_II_0_V7k_FGOG_0h_Q_e22e5771fe.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Vrd_LMOZ_II_0_V7k_FGOG_0h_Q_e22e5771fe.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<p><em><a href="https://mindsparklemag.com/design/duda/" target="_blank" rel="noopener noreferrer">Duda</a></em></p>
<p>Repetition is useful for creating a brand identity. In the Duda design the thick line from the logo has been repeatedly used to create borders and create interest. Along with the colour palette this creates a cohesive brand style. The clock poster plays with repetition and contrast to create a striking image.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zvf6afmehe36lgnf679b449s" alt="[Web layout ideas](https://www.isharearena.com/ui-design/web-layout-ideas-for-inspiration/)" data-big=https://cms-assets.abletech.nz/small_1_QJ_Vid_B9a_HHS_Tovda_Ilo97_A_418dd02c2d.jpeg sizes="(min-width: 768px) 500px, (min-width: 640px) 208px" src="https://cms-assets.abletech.nz/1_QJ_Vid_B9a_HHS_Tovda_Ilo97_A_418dd02c2d.jpeg" srcset="https://cms-assets.abletech.nz/small_1_QJ_Vid_B9a_HHS_Tovda_Ilo97_A_418dd02c2d.jpeg 500w, https://cms-assets.abletech.nz/thumbnail_1_QJ_Vid_B9a_HHS_Tovda_Ilo97_A_418dd02c2d.jpeg 208w" data-zooming-width="500" data-zooming-height="375" loading="lazy" width="500" height="375"></figure>
</div>
<p><em><a href="https://www.isharearena.com/ui-design/web-layout-ideas-for-inspiration/" target="_blank" rel="noopener noreferrer">Web layout ideas</a></em></p>
<p>Repetition can also include context. For example, users expect the menu to be in the top corner of the page, and for a magnifying glass icon to allow them to search. These are common website interactions that users will expect your design to repeat.</p>
<p>With repetition in mind we move the back button to its ‘normal’ place in the top left corner. We make our icons and fonts a consistent size and style. We divide up the recipe instructions with numbers and lines.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="bi62jj30oz4cngk76o58h5nu" alt="" data-big=https://cms-assets.abletech.nz/large_1_G_Oy7_N_Tw0_Vg1d24_D_Ntew0g_946f90b5f9.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_G_Oy7_N_Tw0_Vg1d24_D_Ntew0g_946f90b5f9.jpeg" srcset="https://cms-assets.abletech.nz/large_1_G_Oy7_N_Tw0_Vg1d24_D_Ntew0g_946f90b5f9.jpeg 1000w, https://cms-assets.abletech.nz/small_1_G_Oy7_N_Tw0_Vg1d24_D_Ntew0g_946f90b5f9.jpeg 500w, https://cms-assets.abletech.nz/medium_1_G_Oy7_N_Tw0_Vg1d24_D_Ntew0g_946f90b5f9.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_G_Oy7_N_Tw0_Vg1d24_D_Ntew0g_946f90b5f9.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<p>Finally, we give our design a polish by going back over our principles. We decide the pink header isn’t adding anything and doesn’t deserve so much hierarchy. We add a background colour to create further contrast between the serving and prep time information and the instructions.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="iy2kgw782xly773dry6pig2g" alt="" data-big=https://cms-assets.abletech.nz/large_1z_Q_Aipei6_Vw_H_Lx_U_Kz99c_RGA_c4e3967ad6.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1z_Q_Aipei6_Vw_H_Lx_U_Kz99c_RGA_c4e3967ad6.jpeg" srcset="https://cms-assets.abletech.nz/large_1z_Q_Aipei6_Vw_H_Lx_U_Kz99c_RGA_c4e3967ad6.jpeg 1000w, https://cms-assets.abletech.nz/small_1z_Q_Aipei6_Vw_H_Lx_U_Kz99c_RGA_c4e3967ad6.jpeg 500w, https://cms-assets.abletech.nz/medium_1z_Q_Aipei6_Vw_H_Lx_U_Kz99c_RGA_c4e3967ad6.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1z_Q_Aipei6_Vw_H_Lx_U_Kz99c_RGA_c4e3967ad6.jpeg 245w" data-zooming-width="1000" data-zooming-height="613" loading="lazy" width="1000" height="613"></figure>
</div>
<p>Our final design is much easier to understand and interact with, and more attractive than our original. Considering design principles is an easy way to level up your designs.</p>
<p>If you want to continue improving your design skills I would encourage you to start by noticing design around you. Don’t just think ‘that looks nice’, try to break down how the design principles are being used. When you start creating, don’t be afraid to copy. Obviously you can’t copy every element of another design, but you can combine a variety of influences to create something unique.</p>
<p>Happy designing!</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Māori cultural competency</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Strengthening our understanding, knowledge and integrity</h2>
<p>Our team had a fantastic day with Tania Te Whenua who tailored a Tū Tangata training workshop for us. She included Māori language and customary concepts as they form part of NZ’s shared cultural heritage and identity.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kwl9jb9ifenrbmaakp1vu0vn" alt="" data-big=https://cms-assets.abletech.nz/large_1jp_G0b_Obm_JC_Um04u_O_Tb944_A_6a9dd11305.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1jp_G0b_Obm_JC_Um04u_O_Tb944_A_6a9dd11305.jpeg" srcset="https://cms-assets.abletech.nz/large_1jp_G0b_Obm_JC_Um04u_O_Tb944_A_6a9dd11305.jpeg 1000w, https://cms-assets.abletech.nz/small_1jp_G0b_Obm_JC_Um04u_O_Tb944_A_6a9dd11305.jpeg 500w, https://cms-assets.abletech.nz/medium_1jp_G0b_Obm_JC_Um04u_O_Tb944_A_6a9dd11305.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1jp_G0b_Obm_JC_Um04u_O_Tb944_A_6a9dd11305.jpeg 245w" data-zooming-width="1000" data-zooming-height="558" loading="lazy" width="1000" height="558"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="fa4kniugcz0dptej68eflpzt" alt="" data-big=https://cms-assets.abletech.nz/large_1s_FOC_4_Z3_Q_y_U_Arkgjf_Ytp_A_cdfe0b18f2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1s_FOC_4_Z3_Q_y_U_Arkgjf_Ytp_A_cdfe0b18f2.jpeg" srcset="https://cms-assets.abletech.nz/large_1s_FOC_4_Z3_Q_y_U_Arkgjf_Ytp_A_cdfe0b18f2.jpeg 1000w, https://cms-assets.abletech.nz/small_1s_FOC_4_Z3_Q_y_U_Arkgjf_Ytp_A_cdfe0b18f2.jpeg 500w, https://cms-assets.abletech.nz/medium_1s_FOC_4_Z3_Q_y_U_Arkgjf_Ytp_A_cdfe0b18f2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1s_FOC_4_Z3_Q_y_U_Arkgjf_Ytp_A_cdfe0b18f2.jpeg 245w" data-zooming-width="1000" data-zooming-height="443" loading="lazy" width="1000" height="443"></figure>
</div>
<p>We appreciated Tania’s friendly, informative style and we quickly gained confidence as we put our language and understanding into practice. She was great at building context for various beliefs and values. Her stories and active teaching style deepened our understanding of New Zealand’s unique culture and identity.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rdzbo3csql92t6gy1dm8p8lr" alt="" data-big=https://cms-assets.abletech.nz/large_1_W04_x_Pd_T_Ms_I6c_Ugzbnd_Exg_6cd4d2a07e.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_W04_x_Pd_T_Ms_I6c_Ugzbnd_Exg_6cd4d2a07e.jpeg" srcset="https://cms-assets.abletech.nz/large_1_W04_x_Pd_T_Ms_I6c_Ugzbnd_Exg_6cd4d2a07e.jpeg 1000w, https://cms-assets.abletech.nz/small_1_W04_x_Pd_T_Ms_I6c_Ugzbnd_Exg_6cd4d2a07e.jpeg 500w, https://cms-assets.abletech.nz/medium_1_W04_x_Pd_T_Ms_I6c_Ugzbnd_Exg_6cd4d2a07e.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_W04_x_Pd_T_Ms_I6c_Ugzbnd_Exg_6cd4d2a07e.jpeg 245w" data-zooming-width="1000" data-zooming-height="636" loading="lazy" width="1000" height="636"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="uxxw08o57ybzi0b51nt4ma22" alt="" data-big=https://cms-assets.abletech.nz/large_1_SA_8_D7ath8_EQ_Pn_Xe_Kw_BLKBQ_0af36fd8e2.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_SA_8_D7ath8_EQ_Pn_Xe_Kw_BLKBQ_0af36fd8e2.jpeg" srcset="https://cms-assets.abletech.nz/large_1_SA_8_D7ath8_EQ_Pn_Xe_Kw_BLKBQ_0af36fd8e2.jpeg 1000w, https://cms-assets.abletech.nz/small_1_SA_8_D7ath8_EQ_Pn_Xe_Kw_BLKBQ_0af36fd8e2.jpeg 500w, https://cms-assets.abletech.nz/medium_1_SA_8_D7ath8_EQ_Pn_Xe_Kw_BLKBQ_0af36fd8e2.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_SA_8_D7ath8_EQ_Pn_Xe_Kw_BLKBQ_0af36fd8e2.jpeg 245w" data-zooming-width="1000" data-zooming-height="612" loading="lazy" width="1000" height="612"></figure>
</div>
<p>At Abletech we collaborate with a wide variety of clients and are always looking for ways to make authentic personal connections. Our team is grateful to Tania and <a href="https://www.tewhenua.maori.nz/" target="_blank" rel="noopener noreferrer">Te Whenua Consulting</a> for spending the day with us and facilitating our ongoing commitment to continuous improvement and knowledge sharing.</p>
<p>#TeamValues #ContinuousImprovement</p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Tools, tips & tricks</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>Pushing the limits of our toolsets</h2>
<p>Our dev team recently sat down to share our favourite tips, tricks and tools. This resulted in great conversations and everyone went away with more tools, and productivity or workflow tips &amp; tricks, on their tool-belt. We would like to share these here so you can benefit too.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ulkb67rf0cb0qem6g4tbg0sf" alt="" data-big=https://cms-assets.abletech.nz/large_1_Rb_Q_Qzxy_S8_V_Tgbf_We_Ok_B_Shg_821e2c2d92.jpeg sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_Rb_Q_Qzxy_S8_V_Tgbf_We_Ok_B_Shg_821e2c2d92.jpeg" srcset="https://cms-assets.abletech.nz/large_1_Rb_Q_Qzxy_S8_V_Tgbf_We_Ok_B_Shg_821e2c2d92.jpeg 1000w, https://cms-assets.abletech.nz/small_1_Rb_Q_Qzxy_S8_V_Tgbf_We_Ok_B_Shg_821e2c2d92.jpeg 500w, https://cms-assets.abletech.nz/medium_1_Rb_Q_Qzxy_S8_V_Tgbf_We_Ok_B_Shg_821e2c2d92.jpeg 750w, https://cms-assets.abletech.nz/thumbnail_1_Rb_Q_Qzxy_S8_V_Tgbf_We_Ok_B_Shg_821e2c2d92.jpeg 245w" data-zooming-width="1000" data-zooming-height="496" loading="lazy" width="1000" height="496"></figure>
</div>
<h2>Some of our favourite tools (ordered by popularity)</h2>
<h3><a href="https://cli.github.com/" target="_blank" rel="noopener noreferrer">Github-CLI</a></h3>
<p><code>github-cli</code> is a must-have for any terminal-based developer that uses Github. It aims to bring all the things you need from the github web interface, right to your terminal.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kwvhv1cwc3kbqoxdpx50glza" alt="Using `gh pr status’ to check for relevant PR information" data-big=https://cms-assets.abletech.nz/large_1_SF_QB_Vx_RFO_St_Iu_XC_29lv_Q_416a33a8a1.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_SF_QB_Vx_RFO_St_Iu_XC_29lv_Q_416a33a8a1.png" srcset="https://cms-assets.abletech.nz/large_1_SF_QB_Vx_RFO_St_Iu_XC_29lv_Q_416a33a8a1.png 1000w, https://cms-assets.abletech.nz/small_1_SF_QB_Vx_RFO_St_Iu_XC_29lv_Q_416a33a8a1.png 500w, https://cms-assets.abletech.nz/medium_1_SF_QB_Vx_RFO_St_Iu_XC_29lv_Q_416a33a8a1.png 750w, https://cms-assets.abletech.nz/thumbnail_1_SF_QB_Vx_RFO_St_Iu_XC_29lv_Q_416a33a8a1.png 245w" data-zooming-width="1000" data-zooming-height="466" loading="lazy" width="1000" height="466"></figure>
</div>
<p><em>Using `gh pr status’ to check for relevant PR information</em></p>
<h3><a href="https://github.com/ohmyzsh/ohmyzsh" target="_blank" rel="noopener noreferrer">Oh My Zsh</a></h3>
<p>Oh My Zsh is a framework for managing your <code>zsh</code> configuration and has many <a href="https://github.com/ohmyzsh/ohmyzsh/wiki/Plugins" target="_blank" rel="noopener noreferrer">plugins</a>/<a href="https://zshthem.es/all/" target="_blank" rel="noopener noreferrer">themes</a>, for example, the git plugin, which you can configure to show you information about the status of the current repository such as uncommitted files, unpushed commits and which branch you are on. Oh My Zsh makes these plugins/themes far easier to install and manage than if you were to do it manually.</p>
<h3><a href="https://github.com/wting/autojump" target="_blank" rel="noopener noreferrer">Autojump</a></h3>
<p><code>autojump</code>’s claim to fame is ‘a faster way to navigate your filesystem’, it allows you to start typing a path and jump there automatically, it does this based on frequency, recency and current locality. The more you use it, the more it learns and the faster and more accurate it becomes.</p>
<h3><a href="https://github.com/asdf-vm/asdf" target="_blank" rel="noopener noreferrer">ASDF</a></h3>
<p><code>asdf</code> is a version management tool for installing and managing software. The list of <a href="https://asdf-vm.com/#/plugins-all?id=plugin-list" target="_blank" rel="noopener noreferrer">plugins</a> <code>asdf</code> supports includes most programming languages and lots of other tools such as docker and terraform. One of the most impressive things about <code>asdf</code> is that you can specify directory dependent versions, this means you can seamlessly use two different versions of a programming language across projects.</p>
<h3><a href="https://www.google.com/sheets/about/" target="_blank" rel="noopener noreferrer">Google Sheets</a></h3>
<p>In the right hands, google sheets can be a more powerful tool than you may think. Our resident sheets whizz Sooz uses it as a colour-coded planning tool to see an overview of what is going on at any point in time.</p>
<h3><a href="https://fishshell.com/" target="_blank" rel="noopener noreferrer">Fish</a></h3>
<p><code>fish</code>** **is a shell which can be used as an alternative to other popular shells such as <code>zsh</code> and <code>bash</code></p>
<blockquote>
<p>Finally, a command line shell for the 90s</p>
</blockquote>
<p>although <code>fish</code> was released in 2005 their tagline is a play on the fact that the other two previously mentioned shells were released in the 80s. Fish aims to be a modern shell with ‘sane scripting’ that just works out of the box. Some of the features include:</p>
<ul>
<li>Tab completion to display options complete with man page entries</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="dcamsj08g7nmes5melzi2ib7" alt="" data-big=https://cms-assets.abletech.nz/large_1_A_Uqtf1ck_V9_Pdnj28_Top_Now_a36948910e.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1_A_Uqtf1ck_V9_Pdnj28_Top_Now_a36948910e.png" srcset="https://cms-assets.abletech.nz/large_1_A_Uqtf1ck_V9_Pdnj28_Top_Now_a36948910e.png 1000w, https://cms-assets.abletech.nz/small_1_A_Uqtf1ck_V9_Pdnj28_Top_Now_a36948910e.png 500w, https://cms-assets.abletech.nz/medium_1_A_Uqtf1ck_V9_Pdnj28_Top_Now_a36948910e.png 750w, https://cms-assets.abletech.nz/thumbnail_1_A_Uqtf1ck_V9_Pdnj28_Top_Now_a36948910e.png 245w" data-zooming-width="1000" data-zooming-height="326" loading="lazy" width="1000" height="326"></figure>
</div>
<ul>
<li>Autocompletion suggestions based on past history and current directory</li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="kqcdpu6vwypa9r4co7rar0cs" alt="" data-big=https://cms-assets.abletech.nz/small_1jc_Gi_Qvsh0_Rk9l_Kf_ULK_u_Xw_1e76586d2f.png sizes="(min-width: 768px) 500px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1jc_Gi_Qvsh0_Rk9l_Kf_ULK_u_Xw_1e76586d2f.png" srcset="https://cms-assets.abletech.nz/small_1jc_Gi_Qvsh0_Rk9l_Kf_ULK_u_Xw_1e76586d2f.png 500w, https://cms-assets.abletech.nz/thumbnail_1jc_Gi_Qvsh0_Rk9l_Kf_ULK_u_Xw_1e76586d2f.png 245w" data-zooming-width="500" data-zooming-height="96" loading="lazy" width="500" height="96"></figure>
</div>
<ul>
<li>Web-based configurator accessed by running <code>fish_config</code></li>
</ul>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="rcbkmf9uhb923pie1loi8vui" alt="" data-big=https://cms-assets.abletech.nz/large_1o_XVAY_49_Btv_DR_Lzf6_YEB_6_A_cb732be3a7.png sizes="(min-width: 1280px) 1000px, (min-width: 768px) 500px, (min-width: 1024px) 750px, (min-width: 640px) 245px" src="https://cms-assets.abletech.nz/1o_XVAY_49_Btv_DR_Lzf6_YEB_6_A_cb732be3a7.png" srcset="https://cms-assets.abletech.nz/large_1o_XVAY_49_Btv_DR_Lzf6_YEB_6_A_cb732be3a7.png 1000w, https://cms-assets.abletech.nz/small_1o_XVAY_49_Btv_DR_Lzf6_YEB_6_A_cb732be3a7.png 500w, https://cms-assets.abletech.nz/medium_1o_XVAY_49_Btv_DR_Lzf6_YEB_6_A_cb732be3a7.png 750w, https://cms-assets.abletech.nz/thumbnail_1o_XVAY_49_Btv_DR_Lzf6_YEB_6_A_cb732be3a7.png 245w" data-zooming-width="1000" data-zooming-height="561" loading="lazy" width="1000" height="561"></figure>
</div>
<ul>
<li>
<p>Included vim keybinding integration (if you’re into that)</p>
</li>
<li>
<p>Great <a href="https://fishshell.com/docs/current/index.html" target="_blank" rel="noopener noreferrer">documentation</a></p>
</li>
</ul>
<h3><a href="https://github.com/junegunn/fzf" target="_blank" rel="noopener noreferrer">FZF</a></h3>
<p><code>fzf</code> is a command-line fuzzy finder tool which allows you to search through a list of input. Because you can pipe in any input and utilise the output however you like, the possibilities are almost endless. It even supports previews, which can contain whatever you want whether it be git commit messages or file contents. A favourite here at Abletech is one of the shell extensions that come with fzf, Ctrl+r, which launches fzf with your command history.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="u8vbhcqiy59faglwjljqdpdz" alt="Using fzf to search the files in a git project with a scrollable preview"  sizes="" src="https://cms-assets.abletech.nz/1_Ly_Bqgp_Jv_Raezrl_Ax_Hl_YTUA_73d163d5c5.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>Using fzf to search the files in a git project with a scrollable preview</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="w6tnf8elhumdit2ieirwihyd" alt="This helper views `git log` history, previews the commits and prints the hash of the commit selected"  sizes="" src="https://cms-assets.abletech.nz/1cu74bgy_Ge8r_Vrqkcm7kb_BA_05252534c8.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>This helper views <code>git log</code> history, previews the commits and prints the hash of the commit selected</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="tmyzuqxlwbkyrddj4cjajmre" alt="This helper displays the available branches, previews the commit history and switches to the branch selected"  sizes="" src="https://cms-assets.abletech.nz/1_Tktg_Y14sb_VZ_5v_Gehb_WV_Qcw_599008378d.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>This helper displays the available branches, previews the commit history and switches to the branch selected</em></p>
<h3><a href="https://github.com/ansible/ansible" target="_blank" rel="noopener noreferrer">Ansible</a></h3>
<p><code>ansible</code> is an IT automation system which can be used for automating small tasks or automating a complete setup of your workstation. When you have to set up a new computer, you can be up and running within minutes.</p>
<h3><a href="https://storybook.js.org/" target="_blank" rel="noopener noreferrer">Storybook</a></h3>
<p>Storybook is a UI component building tool for frontend developers that supports many languages. An advantage of using Storybook is that it is easy to mock without having to set up a backend and can handle large datasets otherwise impossible locally.</p>
<h3><a href="https://github.com/tmux/tmux" target="_blank" rel="noopener noreferrer">Tmux</a></h3>
<p><code>tmux</code> is a terminal multiplexer, this means you can create multiple terminals to be accessed and controlled from a single screen, you can even close the window and then carry on from where you left off at a later date. If you need to run the same command in multiple different files or directory you can use synchronized mode which does the same thing in as many different terminals as you want.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="z53nqmphcvea69ztnlvw8y7s" alt="Running `git status` in all nine terminals. (A more useful example would be doing deployments simultaneously)"  sizes="" src="https://cms-assets.abletech.nz/1d_Bl_P1_F_Fu_Mq1_Gkvh_Fpty59w_b34db6c9a9.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>Running <code>git status</code> in all nine terminals. (A more useful example would be doing deployments simultaneously)</em></p>
<h3><a href="https://github.com/vim/vim" target="_blank" rel="noopener noreferrer">Vim</a>/<a href="https://github.com/neovim/neovim" target="_blank" rel="noopener noreferrer">Neovim</a></h3>
<p>Vim (VI Improved) is a terminal-based text editor which has a long history, it was released in 1991 as a replacement for an older editor called VI which dates back to 1976. For something to survive that long it’s got to having something going for it right? And that it does. It is a powerful text manipulation tool that many swear by but it has a notoriously steep learning curve as it is nothing like what you’re used to. Neovim is a fork of vim that has an emphasis on extensibility, usability and is a drop-in replacement for vim. Neovim adds a few speed improvements, quality of life changes and sensible default settings. I would highly suggest using <a href="https://github.com/junegunn/vim-plug" target="_blank" rel="noopener noreferrer">vim-plug</a> for all your plugin needs.</p>
<h3><a href="https://github.com/philc/vimium" target="_blank" rel="noopener noreferrer">Vimium</a></h3>
<p>Vimium is a browser extension to add vim keybindings to your browser. Even if you don’t use vim, this extension is still worth downloading as it allows you to navigate in your browser using your keyboard.</p>
<h3><a href="https://github.com/BurntSushi/ripgrep" target="_blank" rel="noopener noreferrer">Ripgrep</a></h3>
<p><code>ripgrep</code> is a command-line tool that recursively searches for a regex pattern and can be used on file names or file contents. It pairs excellently with fzf. For example, the following command will search the <code>~/Documents</code> directory recursively for any match of <code>result</code> and then let you select an option in <code>fzf</code>. The last part just filters the output to print the filename which can be used to open the file in your favourite text editor. Once the options show up in <code>fzf</code> you can even filter the search further.</p>
<blockquote>
<p><code>rg 'result' ~/Documents/ | fzf | awk '{split($0,a,&quot;:&quot;); print a[1]}'</code></p>
</blockquote>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="hs818e6vb0166dh04rpyacnj" alt="An example of using the command mentioned above"  sizes="" src="https://cms-assets.abletech.nz/1_CU_1_Omv_Eq_G3_N6on_Nm_G4_ON_Qw_9d04f97534.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>An example of using the command mentioned above</em></p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="zeeutt9usidvilrbdn614u33" alt="This does the same as above but shows a scrollable preview and opens directly to the selected line in the file"  sizes="" src="https://cms-assets.abletech.nz/1_Y_Fuz_6_Zit_CS_41_E_Pnd_Ix_0g_02b4f4e253.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>This does the same as above but shows a scrollable preview and opens directly to the selected line in the file</em></p>
<h3><a href="https://www.jetbrains.com/ruby/" target="_blank" rel="noopener noreferrer">Rubymine</a></h3>
<p>Rubymine is an IDE for ruby and rails development that comes with great defaults and many additional plugins available for download. Things like test runners, git helpers, project search/replace and project navigation all come standard with fantastic integration. It really can be your one-stop shop for development if you make it so.</p>
<h3><a href="https://www.alfredapp.com/" target="_blank" rel="noopener noreferrer">Alfred</a></h3>
<p>Alfred is a replacement for the launcher in Mac OS. It includes extra features such as hotkeys, text expansion, custom actions and web searching.</p>
<h3><a href="https://github.com/BoostIO/Boostnote" target="_blank" rel="noopener noreferrer">Boostnote</a></h3>
<p>Boostnote is a markdown editor marketed as a note-taking app for programmers. It has the ability to take screenshots and have code snippets within notes.</p>
<h3><a href="https://todo.microsoft.com/tasks/" target="_blank" rel="noopener noreferrer">Microsoft todo</a></h3>
<p>Microsoft todo is a todo list application that features a daily planner, task management and shared todo lists which can be used together with co-workers or family members.</p>
<h3><a href="https://github.com/ranger/ranger" target="_blank" rel="noopener noreferrer">Ranger</a></h3>
<p><code>ranger</code> is a command-line file explorer the utilises vim keybindings. It is a very useful tool for exploring directories and previewing/managing files from the terminal.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ui6bis6ral2pwkjr45zhn1cp" alt="Navigation with previews and opening a file using ranger"  sizes="" src="https://cms-assets.abletech.nz/1_U_Abq_Vq_K_Ob_A3_Fc_BQ_2x_Koq_Gg_7355ec98a2.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>Navigation with previews and opening a file using ranger</em></p>
<h3><a href="https://github.com/tmuxinator/tmuxinator" target="_blank" rel="noopener noreferrer">Tmuxinator</a></h3>
<p><code>tmuxinator</code> is a tool to manage <code>tmux</code> sessions and create reusable layouts. I have a separate tmuxinator configuration file for each project where each configuration opens a unique set of <code>tmux</code> windows/panes in a specific directory.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ijtcyng6t2sg4yp5sq7047nx" alt="This script allows me to select a project using fzf and tmuxinator. Once a project is selected it will either connect to the existing session or create a new one. It opens a default layout which includes two windows, the first of which has two panes with "  sizes="" src="https://cms-assets.abletech.nz/1_S8azhbuu_Jylf_D9_Aiv5_Bpw_13222c36cf.gif" srcset=""  loading="lazy" width="0" height="0"></figure>
</div>
<p><em>This script allows me to select a project using fzf and tmuxinator. Once a project is selected it will either connect to the existing session or create a new one. It opens a default layout which includes two windows, the first of which has two panes with the first being ranger the file explorer mentioned above.</em></p>
<h2>VS Code</h2>
<p><a href="https://code.visualstudio.com/" target="_blank" rel="noopener noreferrer">VS Code</a> is a fan favourite among the devs at Abletech, just a ‘few’ of their favourite extensions include:</p>
<ul>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" target="_blank" rel="noopener noreferrer">Settings Sync</a>: sync your VS Code settings to a git repo</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory" target="_blank" rel="noopener noreferrer">Git History</a>: pretty git log</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=felipecaputo.git-project-manager" target="_blank" rel="noopener noreferrer">Git Project Manager</a>: switch between git projects</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=howardzuo.vscode-git-tags" target="_blank" rel="noopener noreferrer">Git Tags</a>: manage git tags</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=letmaik.git-tree-compare" target="_blank" rel="noopener noreferrer">Git Tree Compare</a>: compare git branches</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github" target="_blank" rel="noopener noreferrer">GitHub Pull Requests and Issues</a>: open PR’s and issues from VS Code</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens" target="_blank" rel="noopener noreferrer">GitLens</a>: inline git blame annotations among other things</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=ziyasal.vscode-open-in-github" target="_blank" rel="noopener noreferrer">Open in GitHub</a>: jumps to the current file/line in GitHub</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=Dennitz.vscode-generact" target="_blank" rel="noopener noreferrer">Generact</a>: generates react components using existing components as templates</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=patricklee.vsnotes" target="_blank" rel="noopener noreferrer">VSNotes</a>: a markdown based note taking extension which syncs to a git repo</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=hangxingliu.vscode-coding-tracker" target="_blank" rel="noopener noreferrer">Coding Tracker</a>: keeps track of dev time by repo/file</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=ChakrounAnas.turbo-console-log" target="_blank" rel="noopener noreferrer">Turbo Console Log</a>: insert logging statements for variables, can delete all generated logging in one command</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=suming.react-proptypes-generate" target="_blank" rel="noopener noreferrer">React PropTypes Generate</a>: generate prop types declaration from component signature</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client" target="_blank" rel="noopener noreferrer">REST Client</a>: in-editor HTTP client</p>
</li>
<li>
<p><a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker" target="_blank" rel="noopener noreferrer">Docker</a>: makes it easy to build, manage, and deploy containerized applications</p>
</li>
</ul>
<h2>Voted most used/useful tips and tricks</h2>
<ol>
<li>
<p>Use <code>/gif &lt;phrase&gt;</code> to post gifs in slack, this one is a bit hit or miss as you just have to shoot your shot and hope slack selects an appropriate gif for your use of the phrase.</p>
</li>
<li>
<p>Add git/shell aliases for your most common git/shell commands, this can be a big time-saver, one of my favourites is shortening <code>docker-compose run web test</code> which I use often to run tests to <code>dcrt</code> and naming folders which I find my myself navigating to a lot such as alias <code>cd ~/Documents/Client/Project1</code> to simply <code>project1</code> so I can navigate there from anywhere in one command.</p>
</li>
<li>
<p>Replace manual/repetitive tasks with scripts</p>
</li>
<li>
<p>Create separate profiles in your browser for separate projects, when you switch profiles it will switch your bookmarks, history, open tabs and which sites you are logged in to (with which account), this can be useful if you switch projects often. Most browsers support a variation on this functionality and some support syncing these profiles across computers.</p>
</li>
<li>
<p>Inspect react component in developer tools to identify it</p>
</li>
<li>
<p>Monthly reflection of new shortcuts/productivity tips. There are always new ways of improving your workflow which makes it a good idea to have a monthly reflection on what’s working for you and what else is working for others. If we all combine our productivity tools everyone will benefit.</p>
</li>
<li>
<p>Using Ctrl+Tab to switch files quickly in VS Code</p>
</li>
<li>
<p>Create shortcuts for running tests or set up automatic triggers so you have no excuse to not be running those tests!</p>
</li>
</ol>
<h2>Conclusion</h2>
<p>Hopefully, you have found something useful in this article and maybe you will even start using it every day. If you have something to share too, make sure you let us know.</p>
<div class="image-wrapper" style="height: min(620, calc(100vw / 1)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="lcv3x37n2yd3zau8fh6z62jx" alt="A comic that feels just a little bit too accurate [https://xkcd.com/1319/](https://xkcd.com/1319/)" data-big=https://cms-assets.abletech.nz/medium_1_J7_Py_O7_KL_Gb_Ug6j_Hr6_SQ_Xkg_694a724071.png sizes="(min-width: 768px) 494px, (min-width: 1024px) 741px, (min-width: 640px) 154px" src="https://cms-assets.abletech.nz/1_J7_Py_O7_KL_Gb_Ug6j_Hr6_SQ_Xkg_694a724071.png" srcset="https://cms-assets.abletech.nz/small_1_J7_Py_O7_KL_Gb_Ug6j_Hr6_SQ_Xkg_694a724071.png 494w, https://cms-assets.abletech.nz/medium_1_J7_Py_O7_KL_Gb_Ug6j_Hr6_SQ_Xkg_694a724071.png 741w, https://cms-assets.abletech.nz/thumbnail_1_J7_Py_O7_KL_Gb_Ug6j_Hr6_SQ_Xkg_694a724071.png 154w" data-zooming-width="741" data-zooming-height="750" loading="lazy" width="741" height="750"></figure>
</div>
<p><em>A comic that feels just a little bit too accurate <a href="https://xkcd.com/1319/" target="_blank" rel="noopener noreferrer">https://xkcd.com/1319/</a></em></p>
 ]]></content>
    </entry>
  
    
    <entry>
      <title>Sign language & mental health</title>
      <link href="https://abletech.nz."/>
      <updated></updated>
      <id>https://abletech.nz.</id>
      <content type="html"><![CDATA[ <h2>It’s awareness week for both</h2>
<p>Two important weeks are marked this week. Deaf Aotearoa marks NZ Sign Language. The Mental Health Foundation of NZ marks Mental Health Awareness.</p>
<h3>Sign Language</h3>
<p>Our Covid-19 updates have been great for raising the profile of sign language. Although English is most frequently used, our official languages in NZ are actually Māori and Sign Language. If you’re keen to learn more check out the Deaf Awareness and Sign Language courses available through <a href="https://www.deaf.org.nz/" target="_blank" rel="noopener noreferrer">Deaf Aotearoa</a>.</p>
<h3>Mental Health</h3>
<p>We’ve also been encouraged to think more about mental health throughout the pandemic. Do you know that 80% of us have either had personal experience of mental illness or know of others who have? Read inspiring wellbeing stories on the <a href="https://www.mhaw.nz/" target="_blank" rel="noopener noreferrer">Mental Health Awareness</a> site.</p>
<div class="image-wrapper multiple" style="height: min(390, calc(100vw / 2)">
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="ldsazwzvefywsszx31o2cgiv" alt="" data-big=https://cms-assets.abletech.nz/medium_1p2_YGIPN_Yi_W_Xpo_H_Rvy_Vh6w_a689b86888.png sizes="(min-width: 768px) 333px, (min-width: 1024px) 499px, (min-width: 640px) 104px" src="https://cms-assets.abletech.nz/1p2_YGIPN_Yi_W_Xpo_H_Rvy_Vh6w_a689b86888.png" srcset="https://cms-assets.abletech.nz/small_1p2_YGIPN_Yi_W_Xpo_H_Rvy_Vh6w_a689b86888.png 333w, https://cms-assets.abletech.nz/medium_1p2_YGIPN_Yi_W_Xpo_H_Rvy_Vh6w_a689b86888.png 499w, https://cms-assets.abletech.nz/thumbnail_1p2_YGIPN_Yi_W_Xpo_H_Rvy_Vh6w_a689b86888.png 104w" data-zooming-width="499" data-zooming-height="750" loading="lazy" width="499" height="750"></figure>
<figure style="flex: 1;"><img class="zooming-image" onclick="" id="vrna81uzr7jp1j8ex3ezatoc" alt="" data-big=https://cms-assets.abletech.nz/small_1v_Pdjw6p90wm_Nt_Dw_Q_Gn_Pk_CQ_dd2aeff3ff.png sizes="(min-width: 768px) 367px, (min-width: 640px) 115px" src="https://cms-assets.abletech.nz/1v_Pdjw6p90wm_Nt_Dw_Q_Gn_Pk_CQ_dd2aeff3ff.png" srcset="https://cms-assets.abletech.nz/small_1v_Pdjw6p90wm_Nt_Dw_Q_Gn_Pk_CQ_dd2aeff3ff.png 367w, https://cms-assets.abletech.nz/thumbnail_1v_Pdjw6p90wm_Nt_Dw_Q_Gn_Pk_CQ_dd2aeff3ff.png 115w" data-zooming-width="367" data-zooming-height="500" loading="lazy" width="367" height="500"></figure>
</div>
<p>#MHAWNZ #NZSLweek</p>
 ]]></content>
    </entry>
  
</feed>
