<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tutorial on Damian Galarza | Software Engineering &amp; AI Consulting</title><link>https://www.damiangalarza.com/tags/tutorial/</link><description>Recent posts from Damian Galarza | Software Engineering &amp; AI Consulting</description><generator>Hugo</generator><language>en-us</language><managingEditor>Damian Galarza</managingEditor><atom:link href="https://www.damiangalarza.com/tags/tutorial/feed.xml" rel="self" type="application/rss+xml"/><item><title>Claude Code Tutorial: Build Your First Skill in 10 Minutes</title><link>https://www.damiangalarza.com/videos/2026-01-20-claude-code-tutorial-build-your-first-skill-in-10-minutes/</link><pubDate>Tue, 20 Jan 2026 13:15:01 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/videos/2026-01-20-claude-code-tutorial-build-your-first-skill-in-10-minutes/</guid><description>Claude Code tutorial: Learn how to build your first Claude Code skill—a reusable workflow that automates test-driven development. One markdown file, and Claude</description><content:encoded><![CDATA[<p>Claude Code tutorial: Learn how to build your first Claude Code skill—a reusable workflow that automates test-driven development. One markdown file, and Claude follows your TDD process every time.</p>
<p>In this step-by-step tutorial, I show you how to create a Claude Code skill from scratch. You&rsquo;ll learn how Claude Code skills work, where they live, and how to write instructions that make Claude follow proper red-green-refactor TDD.</p>
]]></content:encoded></item><item><title>Build Efficient MCP Servers: Three Design Principles</title><link>https://www.damiangalarza.com/posts/2025-11-06-build-efficient-mcp-servers-three-design-principles/</link><pubDate>Thu, 06 Nov 2025 00:00:00 -0500</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2025-11-06-build-efficient-mcp-servers-three-design-principles/</guid><description>Three design principles for context-efficient MCP servers: filter at source, pre-aggregate data, work creatively. Real reductions: 746k→262 tokens.</description><content:encoded><![CDATA[<p>Recently I had an idea: what would it be like to interact with my YNAB budget via Claude Code using natural language? I wanted to be able to ask questions like &ldquo;How much did I spend on groceries last month?&rdquo; or &ldquo;What categories am I overspending in?&rdquo; and get accurate answers without digging through the app.</p>
<p>I found some existing YNAB MCPs, but most were inactive with limited features. This seemed like a good opportunity to learn MCP design from scratch. What followed was a deep dive into context efficiency that changed how I think about building AI tools.</p>
<h2 id="understanding-model-context-protocols-mcps">Understanding Model Context Protocols (MCPs)</h2>
<p>The Model Context Protocol (MCP) is a standardized way to extend language models with external capabilities. Unlike traditional APIs where you write code to call endpoints, MCP servers allow AI models to discover and use tools autonomously. The protocol defines how models can:</p>
<ul>
<li><strong>Call tools</strong> - Execute functions that interact with external systems</li>
<li><strong>Read resources</strong> - Access files, databases, or other data sources</li>
<li><strong>Receive prompts</strong> - Get specialized instructions for specific tasks</li>
</ul>
<p>When you build an MCP server, you&rsquo;re essentially creating a set of capabilities that any MCP-compatible AI assistant (like Claude) can use. The model sees your tool descriptions, understands what they do, and calls them as needed to fulfill user requests.</p>
<h2 id="understanding-context-windows">Understanding Context Windows</h2>
<p>Before we dive into how to make our MCPs more efficient, it&rsquo;s important to understand what we&rsquo;re trying to optimize. When working with LLMs, the context window is the amount of content that the model can &ldquo;pay attention&rdquo; to at one time. Each model has a limit to the size of its context window. For example, Claude Sonnet 4.5&rsquo;s context window is about 200,000 tokens.</p>
<h3 id="what-is-a-token">What is a Token?</h3>
<p>When you send text to an LLM, it doesn&rsquo;t process words one at a time. Instead, text is broken into <strong>tokens</strong>—the fundamental units that language models read and generate. A token typically represents 3-4 characters, or roughly 0.75 words in English.</p>
<p>For API responses and JSON data (which is what MCPs work with), tokenization looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{<span style="color:#cba6f7">&#34;name&#34;</span>: <span style="color:#a6e3a1">&#34;Checking&#34;</span>}           <span style="color:#6c7086;font-style:italic">// ~7 tokens
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#a6e3a1">&#34;transfer_payee_id&#34;</span>            <span style="color:#6c7086;font-style:italic">// ~5 tokens
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>{<span style="color:#cba6f7">&#34;balance&#34;</span>: <span style="color:#fab387">125000</span>}            <span style="color:#6c7086;font-style:italic">// ~6 tokens
</span></span></span></code></pre></div><p>The tokenizer breaks JSON into chunks: brackets, keys, values, and punctuation all consume tokens. Every field name has a cost. Field names like <code>&quot;debt_escrow_amounts&quot;</code> and <code>&quot;direct_import_in_error&quot;</code> cost ~4-6 tokens each. When you return an API response with 18 fields per object, you&rsquo;re paying the token cost for every field name, every time—even when the model doesn&rsquo;t need them.</p>
<h3 id="why-token-efficiency-matters">Why Token Efficiency Matters</h3>
<p>Given the limited size of the context window, it&rsquo;s critical to consider how much you&rsquo;re placing inside it. Models work best when they have good context, but there&rsquo;s a balance to strike:</p>
<ul>
<li>
<p><strong>Context limits are hard boundaries</strong>: Claude Sonnet 4.5&rsquo;s 200k token limit sounds generous until you realize a naive MCP returning a year of transactions can consume 746,800 tokens—nearly 4x the entire context window. Your tool call would fail before the model could even process it.</p>
</li>
<li>
<p><strong>Real sessions are already crowded</strong>: In typical Claude Code sessions, MCP tool definitions, system prompts, and memory can consume 50-60% of the context window before you&rsquo;ve had a single conversation. Every inefficient tool response eats into precious space needed for reasoning and multi-turn conversations.</p>
</li>
<li>
<p><strong>Noise degrades performance</strong>: Providing superfluous data doesn&rsquo;t just waste tokens—it forces the model to parse irrelevant fields, increasing the chance of errors or confusion. A focused 262-token summary outperforms a noisy 4,890-token dump of raw data.</p>
</li>
</ul>
<p>The goal isn&rsquo;t to minimize tokens at all costs. It&rsquo;s to give the model exactly what it needs, nothing more, nothing less. Let&rsquo;s look at how this plays out in practice.</p>
<h3 id="the-naive-approach-direct-api-wrapping">The Naive Approach: Direct API Wrapping</h3>
<p>When I started building the YNAB MCP, I didn&rsquo;t yet appreciate how much token efficiency would matter. I started with what seemed obvious: create a thin wrapper around the existing YNAB Python SDK. Each MCP tool would correspond to one API endpoint, passing through the full response. This is a common pattern I&rsquo;ve seen in many MCP implementations.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Naive approach: Just wrap the SDK</span>
</span></span><span style="display:flex;"><span><span style="color:#89b4fa;font-weight:bold">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">async</span> <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">get_budget</span>(budget_id: <span style="color:#89dceb">str</span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">str</span>:
</span></span><span style="display:flex;"><span>    response <span style="color:#89dceb;font-weight:bold">=</span> ynab_client<span style="color:#89dceb;font-weight:bold">.</span>budgets<span style="color:#89dceb;font-weight:bold">.</span>get_budget(budget_id)
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">return</span> json<span style="color:#89dceb;font-weight:bold">.</span>dumps(response<span style="color:#89dceb;font-weight:bold">.</span>data<span style="color:#89dceb;font-weight:bold">.</span>budget)  <span style="color:#6c7086;font-style:italic"># Return everything</span>
</span></span></code></pre></div><p>This approach works, technically. The model gets access to the data. But there&rsquo;s a critical problem: <strong>API responses are designed for applications, not for AI context windows.</strong></p>
<p>Traditional applications can process, filter, and cache data efficiently, so APIs return comprehensive data structures optimized for completeness, not token efficiency. A single endpoint might return thousands of fields because the API designers don&rsquo;t know which specific fields your application needs.</p>
<p>AI models work differently. Every byte consumes precious context window space—space you could use for reasoning, conversation history, or additional tool calls. When you blindly pass through full API responses, you&rsquo;re asking the model to pay the &ldquo;context tax&rdquo; for data it might not even need. Worse, the model has to analyze and determine which parts of that data are actually relevant—a cognitive load that can lead to errors or missed information.</p>
<p>To put this in perspective: tool results compete with everything else for context space. In a real Claude Code session (visible via <code>/context</code>), I saw the context window at 118k/<a href="https://docs.anthropic.com/en/docs/about-claude/models">200k tokens</a> (59%)—before I&rsquo;d even started a conversation. MCP tool definitions alone consumed 47.9k tokens (24%), system tools used 17.3k tokens (9%), custom agents took 2.4k tokens, and memory files added another 2.3k tokens. That&rsquo;s 59% of the context window used just by the environment.</p>
<p>A naive MCP that returns 30k tokens for a budget overview would push that to 74% in a single tool call—leaving just 52k tokens for the actual conversation, reasoning, and additional tool calls. Every inefficient tool response eats into the space you need for multi-turn conversations.</p>
<p>This changed how I approached the design: MCPs need to be context-aware intermediaries, not transparent proxies. The question shifted from &ldquo;How do I expose this API to the model?&rdquo; to &ldquo;What does the model actually need to help the user?&rdquo;</p>
<h2 id="three-design-principles-for-context-efficient-mcps">Three Design Principles for Context-Efficient MCPs</h2>
<p>One of the first things I wanted to do was check my budget overview - see my accounts, categories, and how I&rsquo;m tracking for the current month. A straightforward use case that any budgeting tool should support.</p>
<p>My initial thought was to create tools that directly wrapped the YNAB API endpoints. Let&rsquo;s take the accounts endpoint as an example. Here&rsquo;s what the API returns:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">&#34;accounts&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;id&#34;</span>: <span style="color:#a6e3a1">&#34;3fa85f64-5717-4562-b3fc-2c963f66afa6&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;name&#34;</span>: <span style="color:#a6e3a1">&#34;Checking Account&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;type&#34;</span>: <span style="color:#a6e3a1">&#34;checking&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;on_budget&#34;</span>: <span style="color:#fab387">true</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;closed&#34;</span>: <span style="color:#fab387">false</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;note&#34;</span>: <span style="color:#a6e3a1">&#34;Primary checking&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;balance&#34;</span>: <span style="color:#fab387">125000</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;cleared_balance&#34;</span>: <span style="color:#fab387">120000</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;uncleared_balance&#34;</span>: <span style="color:#fab387">5000</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;transfer_payee_id&#34;</span>: <span style="color:#a6e3a1">&#34;...&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;direct_import_linked&#34;</span>: <span style="color:#fab387">true</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;direct_import_in_error&#34;</span>: <span style="color:#fab387">false</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;last_reconciled_at&#34;</span>: <span style="color:#a6e3a1">&#34;2025-11-05T18:27:20.140Z&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;debt_original_balance&#34;</span>: <span style="color:#fab387">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;debt_interest_rates&#34;</span>: {},
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;debt_minimum_payments&#34;</span>: {},
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;debt_escrow_amounts&#34;</span>: {},
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">&#34;deleted&#34;</span>: <span style="color:#fab387">false</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#6c7086;font-style:italic">// ... 46 more accounts
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>    ]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>For <strong>my 47 accounts</strong>, this API response contains <strong>18 fields per account</strong>. Many of these fields are irrelevant for typical budget questions:</p>
<ul>
<li>Debt interest rates and minimum payments (only relevant for debt accounts)</li>
<li>Direct import status (internal system state)</li>
<li>Cleared vs uncleared balance breakdown (too granular for overview)</li>
<li>Transfer payee IDs (internal references)</li>
<li>Last reconciliation date (accounting detail)</li>
</ul>
<p>When you&rsquo;re answering budget questions, these internal bookkeeping details just create noise.</p>
<p>A naive wrapper would return all 18 fields × 47 accounts = <strong>9,960 tokens</strong>.</p>
<p>But here&rsquo;s what I actually need to answer questions like &ldquo;What&rsquo;s my checking account balance?&rdquo; or &ldquo;How much do I have across all accounts?&rdquo;:</p>
<ul>
<li>Account name</li>
<li>Account type</li>
<li>Balance</li>
<li>Whether it&rsquo;s on-budget</li>
<li>Whether it&rsquo;s closed</li>
</ul>
<p>That&rsquo;s it. Just 6 fields. Here&rsquo;s the filtered implementation:</p>
<p><strong>For accounts (47 accounts):</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># src/ynab_mcp/ynab_client.py</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">async</span> <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">get_accounts</span>(<span style="color:#89dceb">self</span>, budget_id: <span style="color:#89dceb">str</span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">list</span>[<span style="color:#89dceb">dict</span>[<span style="color:#89dceb">str</span>, Any]]:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;&#34;&#34;Get all accounts for a budget.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    response <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">self</span><span style="color:#89dceb;font-weight:bold">.</span>client<span style="color:#89dceb;font-weight:bold">.</span>accounts<span style="color:#89dceb;font-weight:bold">.</span>get_accounts(budget_id)
</span></span><span style="display:flex;"><span>    accounts <span style="color:#89dceb;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">for</span> account <span style="color:#89dceb;font-weight:bold">in</span> response<span style="color:#89dceb;font-weight:bold">.</span>data<span style="color:#89dceb;font-weight:bold">.</span>accounts:
</span></span><span style="display:flex;"><span>        <span style="color:#6c7086;font-style:italic"># Skip deleted accounts entirely</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">if</span> account<span style="color:#89dceb;font-weight:bold">.</span>deleted:
</span></span><span style="display:flex;"><span>            <span style="color:#cba6f7">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#6c7086;font-style:italic"># Return only the fields the model actually needs</span>
</span></span><span style="display:flex;"><span>        accounts<span style="color:#89dceb;font-weight:bold">.</span>append({
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;id&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>id,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;name&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>name,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;type&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>type,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;on_budget&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>on_budget,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;closed&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>closed,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;balance&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>balance <span style="color:#89dceb;font-weight:bold">/</span> <span style="color:#fab387">1000</span> <span style="color:#cba6f7">if</span> account<span style="color:#89dceb;font-weight:bold">.</span>balance <span style="color:#cba6f7">else</span> <span style="color:#fab387">0</span>,
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">return</span> accounts
</span></span></code></pre></div><p><strong>Filtered approach (6 essential fields):</strong> 3,451 tokens for 47 accounts</p>
<p><strong>Reduction: 65.4%</strong> by removing 12 unnecessary fields that the model doesn&rsquo;t need for typical budget questions.</p>
<h3 id="the-full-budget-overview">The Full Budget Overview</h3>
<p>Of course, checking accounts is just one part of viewing your budget. A complete budget overview requires three tool calls:</p>
<p><strong>Naive approach (no filtering):</strong></p>
<ul>
<li>Accounts (all fields): 9,960 tokens</li>
<li>Categories (all, including hidden): 12,445 tokens</li>
<li>Monthly summary (estimated): ~8,000 tokens</li>
<li><strong>Total: ~30,405 tokens</strong></li>
</ul>
<p><strong>Context-efficient approach:</strong></p>
<ul>
<li>Accounts (filtered): 3,451 tokens</li>
<li>Categories (visible only): 8,620 tokens</li>
<li>Monthly budget summary: 6,808 tokens</li>
<li><strong>Total: ~18,879 tokens</strong></li>
</ul>
<p><strong>Workflow reduction: 38% fewer tokens</strong> for the same functionality - a complete picture of my budget for the current month. This leaves plenty of room in Claude&rsquo;s context window for conversation history, reasoning, and additional tool calls.</p>
<p>But it&rsquo;s not just about saving tokens. By filtering out unnecessary data, I&rsquo;m also <strong>improving model accuracy</strong>. When Claude doesn&rsquo;t see fields like <code>debt_escrow_amounts</code> or <code>direct_import_in_error</code>, it can&rsquo;t get confused by them or incorrectly incorporate them into calculations. The model focuses on exactly what matters: account names, balances, and budget status.</p>
<p>The key insight: <strong>the model doesn&rsquo;t need to see all the data to work with it effectively</strong>. In fact, it works <em>better</em> with less data. By doing the filtering in the tool layer, I kept the context window lean while maintaining full functionality and improving reliability.</p>
<p>The filtering techniques I&rsquo;d learned from accounts and categories immediately paid off when I tackled the next challenge: helping Claude categorize uncategorized transactions.</p>
<h2 id="categorizing-transactions">Categorizing Transactions</h2>
<p>One of the workflows I wanted help with was taking uncategorized transactions and suggesting categories for them. This would help me ensure that my budget was accurate and up-to-date. To do this I created a tool that would fetch all uncategorized transactions from YNAB and get a list of the categories available in the budget.</p>
<p>On the first pass I noticed something unusual. Claude was recommending categories that were hidden in my budget - old categories I no longer used but hadn&rsquo;t deleted. I quickly realized that the YNAB REST API doesn&rsquo;t provide a way to exclude hidden categories in the API call itself. That meant I had two options:</p>
<ol>
<li>Include instructions in my tool description telling Claude to ignore hidden categories</li>
<li>Filter them out in the tool code before returning data to Claude</li>
</ol>
<p>I chose option 2. Here&rsquo;s why: every instruction you add to a tool description consumes context window. More importantly, it puts the burden of filtering on the model. This means we&rsquo;re knowingly giving the model more data than it needs, forcing it to do more work and potentially allowing the model to make mistakes.</p>
<p>It&rsquo;s worth emphasizing: tool descriptions themselves consume context tokens. In my Claude Code session, MCP tool definitions consumed 47.9k tokens (24% of the context window) before any tools were even called. Every line of documentation, every parameter description, every usage instruction adds up. This creates a tension: you want clear, helpful descriptions, but verbose documentation eats into the space available for actual tool results and conversation.</p>
<p>The solution isn&rsquo;t to write minimal descriptions—clarity matters. Instead, keep descriptions focused on what the tool does and its parameters, and handle behavior rules (like &ldquo;ignore hidden items&rdquo;) in your implementation code rather than in lengthy instructions. Instead, I implemented filtering at the tool layer:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#cba6f7">def</span> <span style="color:#89b4fa">_filter_categories</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb">self</span>, categories: <span style="color:#89dceb">list</span>[<span style="color:#89dceb">dict</span>[<span style="color:#89dceb">str</span>, Any]], include_hidden: <span style="color:#89dceb">bool</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#fab387">False</span>
</span></span><span style="display:flex;"><span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">list</span>[<span style="color:#89dceb">dict</span>[<span style="color:#89dceb">str</span>, Any]]:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;&#34;&#34;Filter categories to exclude hidden/deleted ones by default.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    filtered <span style="color:#89dceb;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">for</span> category <span style="color:#89dceb;font-weight:bold">in</span> categories:
</span></span><span style="display:flex;"><span>        <span style="color:#6c7086;font-style:italic"># Always skip deleted categories</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">if</span> category<span style="color:#89dceb;font-weight:bold">.</span>get(<span style="color:#a6e3a1">&#34;deleted&#34;</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#cba6f7">continue</span>
</span></span><span style="display:flex;"><span>        <span style="color:#6c7086;font-style:italic"># Skip hidden categories unless explicitly included</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">if</span> <span style="color:#89dceb;font-weight:bold">not</span> include_hidden <span style="color:#89dceb;font-weight:bold">and</span> category<span style="color:#89dceb;font-weight:bold">.</span>get(<span style="color:#a6e3a1">&#34;hidden&#34;</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#cba6f7">continue</span>
</span></span><span style="display:flex;"><span>        filtered<span style="color:#89dceb;font-weight:bold">.</span>append(category)
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">return</span> filtered
</span></span></code></pre></div><p>Then I exposed this as a parameter in the tool:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#89b4fa;font-weight:bold">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">async</span> <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">get_categories</span>(budget_id: <span style="color:#89dceb">str</span>, include_hidden: <span style="color:#89dceb">bool</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#fab387">False</span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">str</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;&#34;&#34;Get all categories for a budget.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    Args:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        budget_id: The ID of the budget (use &#39;last-used&#39; for default budget)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        include_hidden: Include hidden categories and groups (default: False)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    Returns:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        JSON string with category groups and categories
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    &#34;&#34;&#34;</span>
</span></span></code></pre></div><p>This approach gave me the best of both worlds: by default, Claude only sees active categories, but I can still access hidden categories when needed (like when I wanted to identify old balances that needed cleanup). This saved <strong>30.7% of tokens</strong> per category list request (from 12,445 to 8,620 tokens by filtering out 69 hidden categories) while improving accuracy.</p>
<h3 id="design-principle-1-filter-at-the-source">Design Principle #1: Filter at the Source</h3>
<p>Do data filtering in your tool code rather than relying on prompt instructions. This saves tokens and prevents errors.</p>
<h2 id="historical-spending-analysis">Historical Spending Analysis</h2>
<p>Next, I wanted to be able to ask questions about my historical spending. Questions like &ldquo;How much did I spend on groceries last month?&rdquo; or &ldquo;What categories am I overspending in?&rdquo; would be really useful.</p>
<p>My first instinct was to create a tool that fetched all transactions for a date range and let Claude analyze them. But I quickly realized this approach had serious problems.</p>
<p>To illustrate, let me show you the real numbers for my 2024 transactions:</p>
<ul>
<li><strong>Total transactions in 2024:</strong> 3,456</li>
<li><strong>Fields per transaction:</strong> 14 (id, date, amount, memo, account_name, payee_name, category_name, cleared, approved, etc.)</li>
<li><strong>Average per transaction:</strong> ~216 tokens</li>
</ul>
<p>Now extrapolate this to common queries:</p>
<ul>
<li><strong>1 month</strong> of transactions (~284 txns): ~61,368 tokens</li>
<li><strong>3 months</strong> of transactions (~852 txns): ~184,106 tokens</li>
<li><strong>6 months</strong> of transactions (~1,704 txns): ~368,213 tokens</li>
<li><strong>1 year</strong> of transactions (3,456 txns): <strong>~746,800 tokens</strong></li>
</ul>
<p>The problems with this approach:</p>
<ol>
<li><strong>Token usage</strong>: A full year query would consume <strong>746,800 tokens</strong> - that&rsquo;s 3.7x larger than Claude Sonnet 4.5&rsquo;s entire 200k context window! You literally couldn&rsquo;t fit a year of transactions in a single request.</li>
<li><strong>Speed</strong>: Transferring and parsing thousands of transaction objects is slow</li>
<li><strong>Analysis burden</strong>: Claude would need to group, sum, and calculate averages on raw data</li>
<li><strong>Wasted context</strong>: Most of those 14 fields per transaction aren&rsquo;t relevant to &ldquo;how much did I spend?&rdquo;</li>
</ol>
<p>Even a modest 3-month query would consume 184k tokens - using 92% of the available context window just for raw transaction data. This leaves almost no room for Claude to maintain conversation history, reason about the results, or make additional tool calls to answer follow-up questions.</p>
<p>Instead, I realized these calculations could easily be handled in the tool layer. Here&rsquo;s what the aggregation logic looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#cba6f7">async</span> <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">get_category_spending_summary</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb">self</span>,
</span></span><span style="display:flex;"><span>    budget_id: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    category_id: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    since_date: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    until_date: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    include_graph: <span style="color:#89dceb">bool</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#fab387">True</span>,
</span></span><span style="display:flex;"><span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">dict</span>[<span style="color:#89dceb">str</span>, Any]:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;&#34;&#34;Get spending summary for a category over a date range.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Fetch transactions from API</span>
</span></span><span style="display:flex;"><span>    result <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#cba6f7">await</span> <span style="color:#89dceb">self</span><span style="color:#89dceb;font-weight:bold">.</span>_make_request_with_retry(<span style="color:#a6e3a1">&#34;get&#34;</span>, url, params<span style="color:#89dceb;font-weight:bold">=</span>params)
</span></span><span style="display:flex;"><span>    txn_data <span style="color:#89dceb;font-weight:bold">=</span> result[<span style="color:#a6e3a1">&#34;data&#34;</span>][<span style="color:#a6e3a1">&#34;transactions&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Aggregate in tool layer</span>
</span></span><span style="display:flex;"><span>    total_spent <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#fab387">0</span>
</span></span><span style="display:flex;"><span>    transaction_count <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#fab387">0</span>
</span></span><span style="display:flex;"><span>    monthly_totals <span style="color:#89dceb;font-weight:bold">=</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">for</span> txn <span style="color:#89dceb;font-weight:bold">in</span> txn_data:
</span></span><span style="display:flex;"><span>        <span style="color:#6c7086;font-style:italic"># Filter by category and date range</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">if</span> txn<span style="color:#89dceb;font-weight:bold">.</span>get(<span style="color:#a6e3a1">&#34;category_id&#34;</span>) <span style="color:#89dceb;font-weight:bold">!=</span> category_id:
</span></span><span style="display:flex;"><span>            <span style="color:#cba6f7">continue</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">if</span> txn[<span style="color:#a6e3a1">&#34;date&#34;</span>] <span style="color:#89dceb;font-weight:bold">&gt;</span> until_date:
</span></span><span style="display:flex;"><span>            <span style="color:#cba6f7">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#6c7086;font-style:italic"># YNAB stores amounts in milliunits (e.g., $125.00 = 125000)</span>
</span></span><span style="display:flex;"><span>        amount <span style="color:#89dceb;font-weight:bold">=</span> txn[<span style="color:#a6e3a1">&#34;amount&#34;</span>] <span style="color:#89dceb;font-weight:bold">/</span> <span style="color:#fab387">1000</span> <span style="color:#cba6f7">if</span> txn<span style="color:#89dceb;font-weight:bold">.</span>get(<span style="color:#a6e3a1">&#34;amount&#34;</span>) <span style="color:#cba6f7">else</span> <span style="color:#fab387">0</span>
</span></span><span style="display:flex;"><span>        total_spent <span style="color:#89dceb;font-weight:bold">+=</span> amount
</span></span><span style="display:flex;"><span>        transaction_count <span style="color:#89dceb;font-weight:bold">+=</span> <span style="color:#fab387">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#6c7086;font-style:italic"># Build monthly breakdown</span>
</span></span><span style="display:flex;"><span>        month_key <span style="color:#89dceb;font-weight:bold">=</span> txn[<span style="color:#a6e3a1">&#34;date&#34;</span>][:<span style="color:#fab387">7</span>]  <span style="color:#6c7086;font-style:italic"># YYYY-MM</span>
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">if</span> month_key <span style="color:#89dceb;font-weight:bold">not</span> <span style="color:#89dceb;font-weight:bold">in</span> monthly_totals:
</span></span><span style="display:flex;"><span>            monthly_totals[month_key] <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#fab387">0</span>
</span></span><span style="display:flex;"><span>        monthly_totals[month_key] <span style="color:#89dceb;font-weight:bold">+=</span> amount
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Calculate average per month</span>
</span></span><span style="display:flex;"><span>    num_months <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">len</span>(monthly_totals) <span style="color:#cba6f7">if</span> monthly_totals <span style="color:#cba6f7">else</span> <span style="color:#fab387">1</span>
</span></span><span style="display:flex;"><span>    average_per_month <span style="color:#89dceb;font-weight:bold">=</span> total_spent <span style="color:#89dceb;font-weight:bold">/</span> num_months <span style="color:#cba6f7">if</span> num_months <span style="color:#89dceb;font-weight:bold">&gt;</span> <span style="color:#fab387">0</span> <span style="color:#cba6f7">else</span> <span style="color:#fab387">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Return only the summary</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;category_id&#34;</span>: category_id,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;date_range&#34;</span>: {<span style="color:#a6e3a1">&#34;start&#34;</span>: since_date, <span style="color:#a6e3a1">&#34;end&#34;</span>: until_date},
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;total_spent&#34;</span>: total_spent,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;transaction_count&#34;</span>: transaction_count,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;average_per_month&#34;</span>: average_per_month,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;monthly_breakdown&#34;</span>: [
</span></span><span style="display:flex;"><span>            {<span style="color:#a6e3a1">&#34;month&#34;</span>: month, <span style="color:#a6e3a1">&#34;spent&#34;</span>: amount}
</span></span><span style="display:flex;"><span>            <span style="color:#cba6f7">for</span> month, amount <span style="color:#89dceb;font-weight:bold">in</span> <span style="color:#89dceb">sorted</span>(monthly_totals<span style="color:#89dceb;font-weight:bold">.</span>items())
</span></span><span style="display:flex;"><span>        ],
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>The impact was dramatic. Let me show you a real example from my implementation:</p>
<p><strong>Scenario:</strong> Analyze 6 months of spending for a single category (22 transactions)</p>
<ul>
<li><strong>Before (returning raw transactions)</strong>: 4,890 tokens</li>
<li><strong>After (pre-aggregated summary)</strong>: 262 tokens</li>
<li><strong>Reduction</strong>: 94.6%</li>
</ul>
<p>The aggregated response includes:</p>
<ul>
<li>Total spent</li>
<li>Average per month</li>
<li>Transaction count</li>
<li>Monthly breakdown (array of {month, amount} objects)</li>
</ul>
<p>That&rsquo;s everything Claude needs to answer questions like &ldquo;Am I spending more on groceries this year than last year?&rdquo; without having to receive, parse, and aggregate dozens of individual transaction records.</p>
<h3 id="design-principle-2-pre-aggregate-data">Design Principle #2: Pre-Aggregate Data</h3>
<p>Pre-calculate aggregations, summaries, and statistics in your tool code. Return insights, not raw data. This keeps your context window lean while still giving the model everything it needs to help users.</p>
<p>While filtering and aggregation solved the token efficiency problem, I ran into a different challenge: the API itself had limitations.</p>
<h2 id="building-tools-for-unsupported-actions">Building Tools for Unsupported Actions</h2>
<p>While working on the workflow to have Claude help me categorize transactions, I realized I needed a way to split a transaction across multiple categories. For example, a Costco purchase might include $150 of groceries, $50 of household items, and $30 of gas.</p>
<p>Unfortunately, the YNAB API does not provide a way to convert an existing transaction into a split transaction. The API only allows creating NEW transactions with splits. This was a real limitation - but it presented an opportunity to think creatively about tool design.</p>
<p>Instead of telling users &ldquo;sorry, the API doesn&rsquo;t support this,&rdquo; I created a tool that works within the API&rsquo;s constraints:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#89b4fa;font-weight:bold">@mcp.tool</span>()
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">async</span> <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">prepare_split_for_matching</span>(
</span></span><span style="display:flex;"><span>    budget_id: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    transaction_id: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    subtransactions: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">str</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;&#34;&#34;Prepare a split transaction to match with an existing imported transaction.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    This tool fetches an existing transaction&#39;s details and creates a new UNAPPROVED split
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    transaction with the same date, amount, account, and payee. You can then manually match
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    them together in the YNAB web or mobile UI.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    Workflow:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        1. This tool fetches the existing transaction details
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        2. Creates a new unapproved split transaction with those details
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        3. You manually match them in the YNAB UI
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        4. YNAB merges them into one split transaction
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    Note:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        - The new split is created as UNAPPROVED for manual matching
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">        - The sum of subtransaction amounts should equal the original transaction amount
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    &#34;&#34;&#34;</span>
</span></span></code></pre></div><p>The implementation:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#cba6f7">async</span> <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">prepare_split_for_matching</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb">self</span>,
</span></span><span style="display:flex;"><span>    budget_id: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    transaction_id: <span style="color:#89dceb">str</span>,
</span></span><span style="display:flex;"><span>    subtransactions: <span style="color:#89dceb">list</span>[<span style="color:#89dceb">dict</span>[<span style="color:#89dceb">str</span>, Any]],
</span></span><span style="display:flex;"><span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">dict</span>[<span style="color:#89dceb">str</span>, Any]:
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Fetch the original transaction details</span>
</span></span><span style="display:flex;"><span>    original <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#cba6f7">await</span> <span style="color:#89dceb">self</span><span style="color:#89dceb;font-weight:bold">.</span>get_transaction(budget_id, transaction_id)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6c7086;font-style:italic"># Create a new split transaction with the same details but unapproved</span>
</span></span><span style="display:flex;"><span>    new_split <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#cba6f7">await</span> <span style="color:#89dceb">self</span><span style="color:#89dceb;font-weight:bold">.</span>create_split_transaction(
</span></span><span style="display:flex;"><span>        budget_id<span style="color:#89dceb;font-weight:bold">=</span>budget_id,
</span></span><span style="display:flex;"><span>        account_id<span style="color:#89dceb;font-weight:bold">=</span>original[<span style="color:#a6e3a1">&#34;account_id&#34;</span>],
</span></span><span style="display:flex;"><span>        date<span style="color:#89dceb;font-weight:bold">=</span>original[<span style="color:#a6e3a1">&#34;date&#34;</span>],
</span></span><span style="display:flex;"><span>        amount<span style="color:#89dceb;font-weight:bold">=</span>original[<span style="color:#a6e3a1">&#34;amount&#34;</span>],
</span></span><span style="display:flex;"><span>        subtransactions<span style="color:#89dceb;font-weight:bold">=</span>subtransactions,
</span></span><span style="display:flex;"><span>        payee_name<span style="color:#89dceb;font-weight:bold">=</span>original<span style="color:#89dceb;font-weight:bold">.</span>get(<span style="color:#a6e3a1">&#34;payee_name&#34;</span>),
</span></span><span style="display:flex;"><span>        memo<span style="color:#89dceb;font-weight:bold">=</span>original<span style="color:#89dceb;font-weight:bold">.</span>get(<span style="color:#a6e3a1">&#34;memo&#34;</span>),
</span></span><span style="display:flex;"><span>        cleared<span style="color:#89dceb;font-weight:bold">=</span>original<span style="color:#89dceb;font-weight:bold">.</span>get(<span style="color:#a6e3a1">&#34;cleared&#34;</span>, <span style="color:#a6e3a1">&#34;uncleared&#34;</span>),
</span></span><span style="display:flex;"><span>        approved<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#fab387">False</span>,  <span style="color:#6c7086;font-style:italic"># Key: create as unapproved for matching</span>
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;original_transaction&#34;</span>: original,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;new_split_transaction&#34;</span>: new_split,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">&#34;instructions&#34;</span>: (
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;A new unapproved split transaction has been created. &#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;Go to YNAB and manually match these two transactions together. &#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e3a1">&#34;Look for the match indicator in the YNAB UI.&#34;</span>
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>This solution works because YNAB has a built-in &ldquo;matching&rdquo; feature where it can merge a manually-entered transaction with an imported one. By creating the split as unapproved, YNAB&rsquo;s UI will detect the duplicate and offer to match them. When you accept the match, the imported transaction becomes a proper split transaction.</p>
<p>Is this ideal? No - I&rsquo;d prefer a direct API endpoint. But it&rsquo;s a pragmatic solution that works within the constraints of the underlying platform while still providing value to the user.</p>
<h3 id="design-principle-3-work-within-api-constraints">Design Principle #3: Work Within API Constraints</h3>
<p>When an API doesn&rsquo;t support something directly, look for workflows that combine available operations to achieve the desired outcome.</p>
<h2 id="real-world-token-reduction-results">Real-World Token Reduction Results</h2>
<p>Here&rsquo;s a summary of the optimizations applied across the YNAB MCP, with real measured token counts:</p>
<table>
  <thead>
      <tr>
          <th>Tool/Workflow</th>
          <th>Naive Approach</th>
          <th>Optimized</th>
          <th>Reduction</th>
          <th>Technique Applied</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Accounts</strong></td>
          <td>9,960 tokens (18 fields)</td>
          <td>3,451 tokens (6 fields)</td>
          <td>65.4%</td>
          <td>Field filtering</td>
      </tr>
      <tr>
          <td><strong>Categories</strong></td>
          <td>12,445 tokens (all)</td>
          <td>8,620 tokens (visible)</td>
          <td>30.7%</td>
          <td>Default filtering + opt-in</td>
      </tr>
      <tr>
          <td><strong>Budget Overview</strong></td>
          <td>~30,405 tokens</td>
          <td>~18,879 tokens</td>
          <td>38%</td>
          <td>Combined filtering</td>
      </tr>
      <tr>
          <td><strong>Category Spending (6mo)</strong></td>
          <td>4,890 tokens (raw txns)</td>
          <td>262 tokens (summary)</td>
          <td>94.6%</td>
          <td>Pre-aggregation</td>
      </tr>
      <tr>
          <td><strong>Year of Transactions</strong></td>
          <td>746,800 tokens</td>
          <td>262 tokens</td>
          <td>99.96%</td>
          <td>Pre-aggregation</td>
      </tr>
  </tbody>
</table>
<h3 id="when-to-apply-each-technique">When to Apply Each Technique</h3>
<p><strong>Field Filtering</strong> (accounts, categories)</p>
<ul>
<li>Use when: API returns many fields, but only subset is needed for common queries</li>
<li>Savings: Moderate (30-65%)</li>
<li>Complexity: Low - simple field selection</li>
<li>Example: Remove debt details from non-debt accounts, skip internal IDs like <code>transfer_payee_id</code>, drop reconciliation timestamps
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Instead of returning all 18 fields, return only what matters</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">return</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;id&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>id,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;name&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>name,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;balance&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>balance <span style="color:#89dceb;font-weight:bold">/</span> <span style="color:#fab387">1000</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;on_budget&#34;</span>: account<span style="color:#89dceb;font-weight:bold">.</span>on_budget
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
</ul>
<p><strong>Default Filtering with Parameters</strong> (hidden categories)</p>
<ul>
<li>Use when: Some data is rarely needed but occasionally useful</li>
<li>Savings: Moderate (30-40%)</li>
<li>Complexity: Low - add optional boolean parameter</li>
<li>Example: Hide deleted/archived items by default, expose via <code>include_deleted</code> flag</li>
</ul>
<p><strong>Pre-aggregation</strong> (spending analysis)</p>
<ul>
<li>Use when: Model would need to compute summaries from raw data</li>
<li>Savings: High (90-99%)</li>
<li>Complexity: Medium - requires aggregation logic</li>
<li>Example: Return monthly totals instead of individual transactions</li>
</ul>
<p><strong>Creative Workarounds</strong> (split transactions)</p>
<ul>
<li>Use when: API doesn&rsquo;t support desired operation directly</li>
<li>Savings: Enables new functionality (not about tokens)</li>
<li>Complexity: High - requires understanding API constraints</li>
<li>Example: Multi-step workflows that achieve goals indirectly</li>
</ul>
<h3 id="how-to-measure-your-own-mcp">How to Measure Your Own MCP</h3>
<p>You might be wondering: how did I get these specific numbers? Here&rsquo;s my methodology—and how you can apply it to your own MCPs.</p>
<p>All the numbers in this post came from real measurements. Here&rsquo;s how I validated the optimizations, and how you can do the same for your MCP:</p>
<h4 id="1-set-up-token-counting">1. Set Up Token Counting</h4>
<p>Install <a href="https://github.com/openai/tiktoken">tiktoken</a>, OpenAI&rsquo;s tokenizer library (Claude uses a similar tokenization scheme):</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install tiktoken
</span></span></code></pre></div><p>Create a helper function to count tokens:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#94e2d5">import</span> <span style="color:#fab387">tiktoken</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">def</span> <span style="color:#89b4fa">count_tokens</span>(text: <span style="color:#89dceb">str</span>) <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#89dceb">int</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#34;&#34;&#34;Count tokens using tiktoken&#39;s cl100k_base encoding.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    encoding <span style="color:#89dceb;font-weight:bold">=</span> tiktoken<span style="color:#89dceb;font-weight:bold">.</span>get_encoding(<span style="color:#a6e3a1">&#34;cl100k_base&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">return</span> <span style="color:#89dceb">len</span>(encoding<span style="color:#89dceb;font-weight:bold">.</span>encode(text))
</span></span></code></pre></div><h4 id="2-measure-api-responses">2. Measure API Responses</h4>
<p>Create a script that fetches data both ways and compares:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#94e2d5">import</span> <span style="color:#fab387">json</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Get raw API response</span>
</span></span><span style="display:flex;"><span>raw_response <span style="color:#89dceb;font-weight:bold">=</span> api<span style="color:#89dceb;font-weight:bold">.</span>get_accounts(budget_id)
</span></span><span style="display:flex;"><span>raw_json <span style="color:#89dceb;font-weight:bold">=</span> json<span style="color:#89dceb;font-weight:bold">.</span>dumps(raw_response, indent<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#fab387">2</span>)
</span></span><span style="display:flex;"><span>raw_tokens <span style="color:#89dceb;font-weight:bold">=</span> count_tokens(raw_json)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Get your filtered response</span>
</span></span><span style="display:flex;"><span>filtered_response <span style="color:#89dceb;font-weight:bold">=</span> your_mcp_tool<span style="color:#89dceb;font-weight:bold">.</span>get_accounts(budget_id)
</span></span><span style="display:flex;"><span>filtered_json <span style="color:#89dceb;font-weight:bold">=</span> json<span style="color:#89dceb;font-weight:bold">.</span>dumps(filtered_response, indent<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#fab387">2</span>)
</span></span><span style="display:flex;"><span>filtered_tokens <span style="color:#89dceb;font-weight:bold">=</span> count_tokens(filtered_json)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Compare</span>
</span></span><span style="display:flex;"><span>reduction <span style="color:#89dceb;font-weight:bold">=</span> ((raw_tokens <span style="color:#89dceb;font-weight:bold">-</span> filtered_tokens) <span style="color:#89dceb;font-weight:bold">/</span> raw_tokens) <span style="color:#89dceb;font-weight:bold">*</span> <span style="color:#fab387">100</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">print</span>(<span style="color:#f38ba8">f</span><span style="color:#a6e3a1">&#34;Raw: </span><span style="color:#a6e3a1">{</span>raw_tokens<span style="color:#a6e3a1">:</span><span style="color:#a6e3a1">,</span><span style="color:#a6e3a1">}</span><span style="color:#a6e3a1"> tokens&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#89dceb">print</span>(<span style="color:#f38ba8">f</span><span style="color:#a6e3a1">&#34;Filtered: </span><span style="color:#a6e3a1">{</span>filtered_tokens<span style="color:#a6e3a1">:</span><span style="color:#a6e3a1">,</span><span style="color:#a6e3a1">}</span><span style="color:#a6e3a1"> tokens&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#89dceb">print</span>(<span style="color:#f38ba8">f</span><span style="color:#a6e3a1">&#34;Reduction: </span><span style="color:#a6e3a1">{</span>reduction<span style="color:#a6e3a1">:</span><span style="color:#a6e3a1">.1f</span><span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">%&#34;</span>)
</span></span></code></pre></div><h4 id="3-test-real-workflows">3. Test Real Workflows</h4>
<p>Don&rsquo;t just measure individual tools - measure complete workflows users will perform:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Simulate a budget overview workflow</span>
</span></span><span style="display:flex;"><span>accounts <span style="color:#89dceb;font-weight:bold">=</span> your_mcp<span style="color:#89dceb;font-weight:bold">.</span>get_accounts(budget_id)
</span></span><span style="display:flex;"><span>categories <span style="color:#89dceb;font-weight:bold">=</span> your_mcp<span style="color:#89dceb;font-weight:bold">.</span>get_categories(budget_id, include_hidden<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#fab387">False</span>)
</span></span><span style="display:flex;"><span>summary <span style="color:#89dceb;font-weight:bold">=</span> your_mcp<span style="color:#89dceb;font-weight:bold">.</span>get_budget_summary(budget_id, current_month)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>total_tokens <span style="color:#89dceb;font-weight:bold">=</span> (
</span></span><span style="display:flex;"><span>    count_tokens(json<span style="color:#89dceb;font-weight:bold">.</span>dumps(accounts)) <span style="color:#89dceb;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>    count_tokens(json<span style="color:#89dceb;font-weight:bold">.</span>dumps(categories)) <span style="color:#89dceb;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>    count_tokens(json<span style="color:#89dceb;font-weight:bold">.</span>dumps(summary))
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">print</span>(<span style="color:#f38ba8">f</span><span style="color:#a6e3a1">&#34;Budget overview workflow: </span><span style="color:#a6e3a1">{</span>total_tokens<span style="color:#a6e3a1">:</span><span style="color:#a6e3a1">,</span><span style="color:#a6e3a1">}</span><span style="color:#a6e3a1"> tokens&#34;</span>)
</span></span></code></pre></div><p>This revealed that my budget overview workflow uses ~19k tokens - well within Claude&rsquo;s <a href="https://docs.anthropic.com/en/docs/about-claude/models">200k context window</a> with room to spare.</p>
<h4 id="4-watch-for-runtime-warnings">4. Watch for Runtime Warnings</h4>
<p>Claude Code will warn you when tool responses exceed ~10k tokens. If you see these warnings frequently, it&rsquo;s a signal to investigate:</p>
<blockquote>
<p>⚠️ Large MCP response (~12.5k tokens), this can fill up context quickly.</p></blockquote>
<p>These warnings helped me identify which tools needed optimization.</p>
<h4 id="5-validate-correctness">5. Validate Correctness</h4>
<p>Token reduction means nothing if your tools return incorrect data. Always verify:</p>
<ul>
<li>Does the filtered data answer the user&rsquo;s questions?</li>
<li>Are calculations accurate? (spot-check aggregations against raw data)</li>
<li>Does pagination work correctly? (ensure you&rsquo;re not computing on partial datasets)</li>
</ul>
<p>The goal isn&rsquo;t to minimize tokens at all costs - it&rsquo;s to return exactly what the model needs, nothing more, nothing less.</p>
<h2 id="limitations-and-trade-offs">Limitations and Trade-offs</h2>
<p>This context-efficient approach works well for YNAB, but it&rsquo;s not without limitations. Before applying these patterns to your own MCP, consider these trade-offs:</p>
<p><strong>Pre-aggregation assumes query patterns.</strong> If users ask questions that need raw transaction details (like &ldquo;show me the memo for my largest grocery purchase&rdquo;), the aggregated data won&rsquo;t help. You&rsquo;ll need additional tools that return raw data for those cases.</p>
<p><strong>Filtering loses flexibility.</strong> By removing fields, you can&rsquo;t answer questions that need those fields without making additional API calls. The key is knowing your use cases. For budget analysis and categorization, these trade-offs are worth it. For transaction-level forensics, you might need different tools.</p>
<p><strong>Caching complexity.</strong> Pre-computed aggregations need invalidation strategies when data changes. If your underlying data updates frequently, you&rsquo;ll need to think carefully about cache freshness and when to recompute.</p>
<p><strong>Development overhead.</strong> Writing aggregation logic and filtering code is more work than simple pass-through wrappers. You&rsquo;re trading implementation time for runtime efficiency. For frequently-used tools, this is usually worth it.</p>
<p>The goal isn&rsquo;t to optimize every tool to the extreme. It&rsquo;s to identify the high-impact workflows—the ones users will perform repeatedly—and optimize those intelligently.</p>
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li><strong>Context is expensive</strong>: In real Claude Code sessions, MCP tool definitions can consume 24% of the 200k context window before any tools are called</li>
<li><strong>Measure everything</strong>: Use tiktoken to count tokens on API responses before and after optimization</li>
<li><strong>Filter proactively</strong>: Removing 12 unnecessary fields from 47 accounts reduced tokens by 65.4%</li>
<li><strong>Aggregate strategically</strong>: Pre-computing spending summaries reduced a 6-month query by 94.6%</li>
<li><strong>Design for the model</strong>: Ask &ldquo;What does the model need?&rdquo; not &ldquo;What does the API provide?&rdquo;</li>
<li><strong>Default to minimal data</strong>: Return only what&rsquo;s necessary by default, with optional parameters for edge cases</li>
<li><strong>Validate with real workflows</strong>: Test complete user flows, not just individual tools, to understand cumulative token impact</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Building a context-efficient MCP requires a mindset shift: design for what the model needs, not just what the API provides. The three principles I learned—filter at the source, compute in tools, and work within constraints creatively—apply to any MCP wrapping an external API.</p>
<p>Through careful design, the YNAB MCP achieves dramatic efficiency:</p>
<ul>
<li>Budget overview: 38% reduction (30k → 19k tokens)</li>
<li>Spending analysis: 94.6% reduction (4.9k → 262 tokens)</li>
<li>Category filtering: 30.7% reduction (12.4k → 8.6k tokens)</li>
</ul>
<p>These aren&rsquo;t theoretical—they&rsquo;re real measurements from actual usage. Context is expensive, and every token matters when you&rsquo;re building tools for multi-turn conversations.</p>
<p>If you&rsquo;re building an MCP, start by asking: &ldquo;What does the model actually need to answer the user&rsquo;s question?&rdquo; Not &ldquo;What does the API return?&rdquo; That mindset shift makes all the difference.</p>
<h2 id="further-reading">Further Reading</h2>
<p>If you want to dive deeper into MCP development and context optimization:</p>
<ul>
<li><a href="https://spec.modelcontextprotocol.io/">Model Context Protocol Specification</a> - Official MCP spec and documentation</li>
<li><a href="https://www.anthropic.com/engineering/code-execution-with-mcp">Code Execution with MCP</a> - Anthropic&rsquo;s engineering blog on building with MCP</li>
<li><a href="https://youtu.be/-uW5-TaVXu4?si=GBCXG3Q5QcfEdvnJ">Most devs don&rsquo;t understand how context windows work</a> - Deep dive into context window fundamentals and practical management strategies</li>
<li><a href="https://api.ynab.com/">YNAB API Documentation</a> - The API this MCP wraps</li>
<li><a href="https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview">Anthropic&rsquo;s Prompt Engineering Guide</a> - Understanding context windows and token efficiency</li>
</ul>
<p>The full YNAB MCP implementation is available at <a href="https://github.com/dgalarza/ynab-mcp-dgalarza">github.com/dgalarza/ynab-mcp-dgalarza</a> if you want to dive deeper into the code. I&rsquo;d love to hear about the context optimization techniques you&rsquo;ve discovered in your own MCP projects.</p>
<hr>
<p>Building MCP servers or designing Claude Code workflows for your team? I help engineers get this right from the start. <a href="/claude-code/">Learn more</a>.</p>
]]></content:encoded></item><item><title>The AI Prompt I Wish I Had While Documenting SaMD Systems in Rails</title><link>https://www.damiangalarza.com/posts/2025-06-22-generate-sds-docs-samd-ai/</link><pubDate>Sat, 21 Jun 2025 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2025-06-22-generate-sds-docs-samd-ai/</guid><description>How AI could have helped me generate FDA audit-ready SDS documentation faster while building regulated software.</description><content:encoded><![CDATA[<p>At Buoy Software, I led the design and development of our first <strong>Software as a Medical Device (SaMD)</strong>, which was my first experience operating within an FDA-regulated environment. It was a great learning experience, but it came with a lot of heavy documentation. One of the most time-consuming parts was compiling the Design History File (DHF) — the set of artifacts that describe how the system was built and tested. A central piece of that file is the Software Design Specification (SDS), which describes the behavior, design, and rationale for each component in the system.</p>
<p>For every Rails class we shipped, we had to trace requirements, detail business logic and interfaces, and map out risk controls — all in a format that is audit-ready for FDA review.</p>
<p>This post kicks off a series called ‘AI Prompts I Wish I Had’, sharing prompts that could’ve made our engineering workflows smoother while building regulated software.</p>
<p>The goal of this series is to offer <strong>practical, high-context AI prompts</strong> that help teams move faster toward an FDA submission — without compromising quality, traceability, or compliance.</p>
<hr>
<h2 id="-the-prompt">🧠 The Prompt</h2>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold"># SDS Documentation Prompt
</span></span></span><span style="display:flex;"><span><span style="color:#fab387;font-weight:bold"></span>
</span></span><span style="display:flex;"><span>You are a SaMD software engineer generating regulatory documentation.
</span></span><span style="display:flex;"><span>I will provide you with one or more Ruby/Rails classes used in a regulated
</span></span><span style="display:flex;"><span>Software as a Medical Device (SaMD) product.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Based on the code I provide, generate a <span style="font-weight:bold">**Software Design Specification (SDS)**</span> entry suitable
</span></span><span style="display:flex;"><span>for inclusion in a Design History File (DHF).
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Your output should include:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">1.</span> <span style="font-weight:bold">**Component Name**</span>  
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">2.</span> <span style="font-weight:bold">**Purpose / Role in the System**</span>  
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">3.</span> <span style="font-weight:bold">**Description of Logic / Behavior**</span> – Provide detailed explanations including:
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Specific business rules with exact criteria and thresholds  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> All possible status values/enums with definitions of what each represents  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Step-by-step process flows with decision points  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Data validation rules and constraints  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Error handling and edge cases  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">4.</span> <span style="font-weight:bold">**External Interfaces**</span> – Include:
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Specific API endpoints and GraphQL mutations/queries  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Database table names and key fields  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Session storage keys and data structures  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Third-party service integrations  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">5.</span> <span style="font-weight:bold">**How It Satisfies Specific SRS Requirements**</span> – Expand on:
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Security controls with implementation details  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Audit logging mechanisms and what data is captured  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Regulatory compliance features  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Data integrity safeguards  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">6.</span> <span style="font-weight:bold">**Design Considerations**</span> – Detail:
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Security flags and their enforcement mechanisms  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Session state management and validation logic  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Performance optimizations and their rationale  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Safety mechanisms and fail-safes  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">7.</span> <span style="font-weight:bold">**Traceability Notes**</span> – Include:
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Specific risk control implementations  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Audit trail mechanisms (PaperTrail, analytics events, etc.)  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Logging infrastructure for regulatory compliance  
</span></span><span style="display:flex;"><span>   <span style="color:#cba6f7">-</span> Known technical limitations with business impact  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">**Additional Requirements:**</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">-</span> **No abbreviations or “etc.”** – List all status values, rules, and conditions explicitly  
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">-</span> **Implementation details** – Show actual code snippets for critical security or compliance logic  
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">-</span> **Business rule explanations** – Explain the medical/regulatory rationale behind complex rules  
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">-</span> **Security deep-dive** – Cover authentication, authorization, session management, and data protection  
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">-</span> **Audit compliance** – Document all logging, tracking, and audit trail mechanisms required for regulatory review
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Format the output in Markdown with headers for each section. Be comprehensive, technical, and audit-ready.
</span></span></code></pre></div><p><strong>Example usage:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>The system we are documenting is located within <span style="color:#a6e3a1">`packs/authentication`</span>.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>I want documentation covering the login and session management flow for users.  
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>How does the system validate credentials?
</span></span><span style="display:flex;"><span>How is the session established and managed?
</span></span><span style="display:flex;"><span>What audit trails are recorded for access attempts?
</span></span></code></pre></div><p>Like anything generated with AI it requires a careful review, but this prompt can help you quickly generate a starting point for a comprehensive SDS entry that meets FDA requirements. It ensures that you cover all necessary aspects of the system&rsquo;s design and behavior, making it easier to compile your Design History File.</p>
<p>Let me know if this is helpful — and if you want me to share the next prompt in this series.</p>
<p>If you&rsquo;re building a SaMD or working in a regulated domain and want help tuning your dev workflows with AI, <a href="/services/">let&rsquo;s talk</a>.</p>
]]></content:encoded></item><item><title>Acceptance Tests with Subdomains</title><link>https://www.damiangalarza.com/posts/2016-10-10-acceptance-tests-with-subdomains/</link><pubDate>Mon, 10 Oct 2016 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2016-10-10-acceptance-tests-with-subdomains/</guid><description>How to use subdomains in your feature specs.</description><content:encoded><![CDATA[<p>This post was originally published on the <a href="https://thoughtbot.com/blog/acceptance-tests-with-subdomains">thoughtbot blog</a>.</p>
<p>You&rsquo;re excited about building a new application which allows users to sign up and host their own blog. You decide that each blog will have their own space by providing a subdomain.</p>
<p>Let&rsquo;s start off with a feature spec.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#89dceb">require</span> <span style="color:#a6e3a1">&#34;rails_helper&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>feature <span style="color:#a6e3a1">&#34;user views a blog&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  scenario <span style="color:#a6e3a1">&#34;homepage&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>     blog <span style="color:#89dceb;font-weight:bold">=</span> create(
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">:blog</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">subdomain</span>: <span style="color:#a6e3a1">&#34;bobloblaw&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">title</span>: <span style="color:#a6e3a1">&#34;Bob Loblaw&#39;s Law Blog&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">description</span>: <span style="color:#a6e3a1">&#34;Welcome to my new blog.&#34;</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    visit root_path
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_content blog<span style="color:#89dceb;font-weight:bold">.</span>title
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_content blog<span style="color:#89dceb;font-weight:bold">.</span>description
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>In our app we render the blog homepage using the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># config/routes.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>routes<span style="color:#89dceb;font-weight:bold">.</span>draw <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  root <span style="color:#a6e3a1">to</span>: <span style="color:#a6e3a1">&#34;blogs#show&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># app/controllers/blogs_controller.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">BlogsController</span> <span style="color:#89dceb;font-weight:bold">&lt;</span> <span style="color:#f9e2af">ApplicationController</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">show</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@blog</span> <span style="color:#89dceb;font-weight:bold">=</span> current_blog
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">private</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">current_blog</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@_current_blog</span> <span style="color:#89dceb;font-weight:bold">||=</span> <span style="color:#f9e2af">Blog</span><span style="color:#89dceb;font-weight:bold">.</span>find_by(<span style="color:#a6e3a1">subdomain</span>: request<span style="color:#89dceb;font-weight:bold">.</span>subdomains<span style="color:#89dceb;font-weight:bold">.</span>first)
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">&lt;!-- app/views/blogs/show.html.erb --&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">h1</span>&gt;<span style="color:#f38ba8">&lt;</span>%= @blog.title %&gt;&lt;/<span style="color:#cba6f7">h1</span>&gt;
</span></span><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">p</span>&gt;<span style="color:#f38ba8">&lt;</span>%= @blog.description %&gt;&lt;/<span style="color:#cba6f7">p</span>&gt;
</span></span></code></pre></div><p>In order to visit the homepage via a subdomain in our test we need to set the <code>app_host</code> property for Capybara. We could try to use <code>myblog.localhost</code> but Rails will think that localhost is the top level domain and therefore won&rsquo;t see myblog as a subdomain. Instead we&rsquo;ll use a fake host name <code>example.com</code>. We can set it by adding the following to our spec before calling <code>visit</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>app_host <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#34;http://myblog.example.com&#34;</span>
</span></span></code></pre></div><p>If we run the test with the default Capybara driver, <code>rack-test</code> it should be green.  <code>rack-test</code> interacts directly with Rack which means it never uses the external URL. If we need to use a JavaScript driver however we will need to use an actual accessible URL. Add the <code>:js</code> metadata to the scenario and you should see a failure occur.</p>
<p>In order to accommodate a driver like Selenium or capybara-webkit we&rsquo;ll need to do some more work. To start, we will not be able to use our fake host <code>example.com</code>. Instead we need a host name which will point to <code>127.0.0.1</code>. There is one readily available to us for use through <code>lvh.me</code>. Its DNS records are set up so that <code>lvh.me</code> and all of its subdomains resolve to your local machine at <code>127.0.0.1</code>.</p>
<p>So update <code>app_host</code> from <code>http://myblog.example.com</code> to <code>http://myblog.lvh.me</code>. We&rsquo;re still not done yet though.</p>
<p>Next, we need to instruct Capybara to include the port number for the Capybara server in all requests to work correctly. We can do that by adding the following to <code>spec/rails_helper.rb</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>configure <span style="color:#cba6f7">do</span> <span style="color:#89dceb;font-weight:bold">|</span>config<span style="color:#89dceb;font-weight:bold">|</span>
</span></span><span style="display:flex;"><span>  config<span style="color:#89dceb;font-weight:bold">.</span>always_include_port <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#cba6f7">true</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>If you&rsquo;re using the capybara-webkit driver and configuring it to block all unknown URLs <a href="https://github.com/thoughtbot/suspenders/blob/master/templates/capybara_webkit.rb#L4">as we do in Suspenders</a> then you&rsquo;ll need to do one more thing. In the configuration for capybara-webkit you&rsquo;ll need to add the <code>lvh.me</code> host to the URL whitelist. If you&rsquo;re using a Suspenders based app then open up <code>spec/support/capybara_webkit.rb</code> or whichever file you have configured capybara-webkit in. Update the configuration to look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Webkit</span><span style="color:#89dceb;font-weight:bold">.</span>configure <span style="color:#cba6f7">do</span> <span style="color:#89dceb;font-weight:bold">|</span>config<span style="color:#89dceb;font-weight:bold">|</span>
</span></span><span style="display:flex;"><span>  config<span style="color:#89dceb;font-weight:bold">.</span>block_unknown_urls
</span></span><span style="display:flex;"><span>  config<span style="color:#89dceb;font-weight:bold">.</span>allow_url(<span style="color:#a6e3a1">&#34;myblog.lvh.me&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>This will allow Capybara to access our blog through <code>lvh.me</code> and not block it. With this in place we can run our tests and things should be green again.</p>
<h2 id="allowing-more-subdomains">Allowing more subdomains</h2>
<p>Things are working great with the above but we realize that we are coupled to the <code>myblog</code> subdomain within all of our tests. We will finish things off by making this more flexible.</p>
<p>Let&rsquo;s start by updating our capybara-webkit configuration to allow all subdomains on lvh.me and not just limiting it to <code>myblog</code>. We can do this by changing <code>myblog</code> to <code>*</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Webkit</span><span style="color:#89dceb;font-weight:bold">.</span>configure <span style="color:#cba6f7">do</span> <span style="color:#89dceb;font-weight:bold">|</span>config<span style="color:#89dceb;font-weight:bold">|</span><span style="color:#f38ba8">¬</span>
</span></span><span style="display:flex;"><span>  config<span style="color:#89dceb;font-weight:bold">.</span>block_unknown_urls<span style="color:#f38ba8">¬</span>
</span></span><span style="display:flex;"><span>  config<span style="color:#89dceb;font-weight:bold">.</span>allow_url(<span style="color:#a6e3a1">&#34;*.lvh.me&#34;</span>)<span style="color:#f38ba8">¬</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>Next, let&rsquo;s extract a helper method to make testing subdomains easier.</p>
<p>We&rsquo;ll add the following method to our feature spec:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#cba6f7">def</span> <span style="color:#89b4fa">visit_blog</span>(blog, path <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#39;/&#39;</span>)
</span></span><span style="display:flex;"><span>  app_host <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">URI</span><span style="color:#89dceb;font-weight:bold">.</span>join(<span style="color:#a6e3a1">&#34;http://</span><span style="color:#a6e3a1">#{</span>blog<span style="color:#89dceb;font-weight:bold">.</span>subdomain<span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">.lvh.me&#34;</span>, path)<span style="color:#89dceb;font-weight:bold">.</span>to_s
</span></span><span style="display:flex;"><span>  using_app_host(app_host) <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    visit path
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">def</span> <span style="color:#89b4fa">using_app_host</span>(host)
</span></span><span style="display:flex;"><span>  original_host <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>app_host
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>app_host <span style="color:#89dceb;font-weight:bold">=</span> host
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">yield</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">ensure</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>app_host <span style="color:#89dceb;font-weight:bold">=</span> original_host
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p><code>using_app_host</code> allows us to pass a host for Capybara to use and temporarily overrides the <code>app_host</code> rather then permanently setting it. Our use of <code>ensure</code> makes sure that the <code>app_host</code> is always set back to its original value regardless of exceptions being raised while <code>yield</code>ing the block. <code>visit_blog</code> allows us to pass an instance of a blog as well as a path to visit. By default, this path is the root of the blog.</p>
<p>So we can update our spec to look as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#89dceb">require</span> <span style="color:#a6e3a1">&#34;rails_helper&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>feature <span style="color:#a6e3a1">&#34;user views a blog&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  scenario <span style="color:#a6e3a1">&#34;homepage&#34;</span>, <span style="color:#a6e3a1">:js</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>     blog <span style="color:#89dceb;font-weight:bold">=</span> create(
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">:blog</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">subdomain</span>: <span style="color:#a6e3a1">&#34;bobloblaw&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">title</span>: <span style="color:#a6e3a1">&#34;Bob Loblaw&#39;s Law Blog&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">description</span>: <span style="color:#a6e3a1">&#34;Welcome to my new blog.&#34;</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    visit_blog blog
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_content blog<span style="color:#89dceb;font-weight:bold">.</span>title
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_content blog<span style="color:#89dceb;font-weight:bold">.</span>description
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">visit_blog</span>(blog, path <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#39;/&#39;</span>)
</span></span><span style="display:flex;"><span>    app_host <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">URI</span><span style="color:#89dceb;font-weight:bold">.</span>join(<span style="color:#a6e3a1">&#34;http://</span><span style="color:#a6e3a1">#{</span>blog<span style="color:#89dceb;font-weight:bold">.</span>subdomain<span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">.lvh.me&#34;</span>, path)<span style="color:#89dceb;font-weight:bold">.</span>to_s
</span></span><span style="display:flex;"><span>    using_app_host(app_host) <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>      visit path
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">using_app_host</span>(host)
</span></span><span style="display:flex;"><span>    original_host <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>app_host
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>app_host <span style="color:#89dceb;font-weight:bold">=</span> host
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">yield</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">ensure</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">Capybara</span><span style="color:#89dceb;font-weight:bold">.</span>app_host <span style="color:#89dceb;font-weight:bold">=</span> original_host
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>Getting Started with iOS Development</title><link>https://www.damiangalarza.com/posts/2015-06-12-getting-started-with-ios-development/</link><pubDate>Fri, 12 Jun 2015 00:00:00 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2015-06-12-getting-started-with-ios-development/</guid><description>Complete beginner's guide to iOS development with Swift. Learn Xcode setup, iOS development fundamentals, and start building iPhone apps with practical examples.</description><content:encoded><![CDATA[<p>Around April I started digging into iOS and Swift. I figured it would
be helpful to share my experience with others who may also want to learn it.
Note, this comes from the perspective of someone who already has a programming
background and previously spent time becoming familiar with Objective-C and
Apple&rsquo;s Cocoa Touch frameworks.</p>
<p>When I first got started, I decided to search for some online iOS with Swift
courses. I was hoping to find some courses on Code School or something similar.
Unfortunately, I couldn&rsquo;t find a course like this. The courses that I found
were focused around learning iOS with Objective-C. I decided if I was going to
start learning iOS, I wanted to learn it with the modern Swift language rather
than Objective-C.</p>
<p>That&rsquo;s not to say that having Objective-C knowledge isn&rsquo;t valuable. It&rsquo;s
especially important to have Objective-C experience to work with older
applications. For example, <a href="http://troposweather.com/">Tropos</a> is built with
Objective-C. In order to contribute to this project, I needed to think back to
my Objective-C learnings. The specifics around iOS development tend to be the
frameworks that Apple provides to its developers. These frameworks have great
documentation and are in both, Swift and Objective-C. If you&rsquo;re learning how to
use these libraries and the concepts behind iOS, it shouldn&rsquo;t be difficult to
apply them to Objective-C.</p>
<p>Initially I was disappointed to see that there weren&rsquo;t many resources to get
started in iOS from a beginner&rsquo;s perspective with Swift. Then after spending
some more time searching I found that Stanford University has a free course on
iTunes university titled <a href="https://itunes.apple.com/us/course/developing-ios-8-apps-swift/id961180099">Developing iOS 8 Apps with
Swift</a>.
This has been an immensely useful resource. Paul Hegarty is a great instructor.
He is really good at explaining the concepts and demonstrating things in real
time. I highly recommend checking it out.</p>
<p>These are a few of the important things I&rsquo;ve learned so far:</p>
<ul>
<li>
<p><strong><a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/">Read the iOS Human Interface
Guidelines</a></strong>
This may go without saying but Apple has done a ton of research behind the
user experience of iOS and its native applications. A fun fact that I learned
was the loading screen for an iOS application shouldn&rsquo;t be used as a splash
page. Instead, it should be a representation of the inactive application to
give the illusion of a quick load.</p>
</li>
<li>
<p><strong>Have a project idea</strong> While reading and following the Stanford online course
was immensely useful to start learning, I always feel that I need a concrete
project to work on to apply my learnings. Without this, I usually can&rsquo;t keep
sustained interest. I recommend coming up with a simple app idea to work on
while learning.</p>
</li>
<li>
<p><strong>Read through Apple&rsquo;s developer documentation</strong> While developing for iOS you
have a large number of frameworks provided by Apple at your disposal.
Apple also provides excellent documentation around all of these frameworks;
XCode even has a documentation browser built in. I recommend taking the time
to familiarize yourself with these documents and learn to navigate them, it
will be a huge benefit in the long run.</p>
</li>
<li>
<p><strong>Use the Apple developer forums</strong> I wasn&rsquo;t immediately aware of how useful
the Apple developer forums would be. I was running into trouble getting an
archive of my app sent to iTunes connect and spent hours if not days searching
for support. Most of my searching turned up various StackOverflow posts which
were not helpful, but after checking in the thoughtbot XCode slack channel I
found that someone else had recently run into a similar issue. They lead me to
the specific post on the Apple developer forums regarding the issue and I was
able to work around it in a few minutes. Lesson I learned; search the Apple
developer forums.</p>
</li>
</ul>
<h2 id="recommended-reading">Recommended Reading</h2>
<ul>
<li><a href="https://developer.apple.com/swift/resources/">Swift Programming Series (iBooks
Store)</a> This is the offical
Swift book by Apple. It is a great resource on getting started with learning
swift.</li>
<li><a href="http://www.amazon.com/Beginning-iPhone-Development-Swift-Exploring/dp/1484204107/ref=sr_1_1?ie=UTF8&amp;qid=1433530778&amp;sr=8-1&amp;keywords=iPhone+Development+with+Swift">Beginning iPhone Development with
Swift</a>
This is a great book to get started with learning iOS and Swift.</li>
<li><a href="http://www.raywenderlich.com/">RayWenderlich</a> Great blog with tutorials on
iOS and Swift</li>
<li><a href="http://www.appcoda.com/">AppCoda</a> Great resource for various tutorials</li>
<li><a href="https://robots.thoughtbot.com/tags/ios">Giant Robots Smashing into Other Giant
Robots</a> There may not be as many
beginner content here but there has been very useful blog posts on Swift and
functional programming in Swift.</li>
</ul>
<h2 id="design-resources">Design Resources</h2>
<ul>
<li><a href="http://iosdesign.ivomynttinen.com/">The iOS Design Guidelines</a> Guide to
design apps around the Apple HIG.</li>
<li><a href="http://www.teehanlax.com/tools/iphone/">iOS 8 GUI PSD (iPhone 6)</a> Template of
the GUI elements found in iOS 8</li>
<li><a href="http://martiancraft.com/blog/2014/09/vector-images-xcode6/">Using Vector Images in XCode
6</a> Turns out you
no longer have to generate several versions of an image asset to use within your
app. XCode supports vector images which it will scale appropriately for various
sizes. IE: @1x, @2x, @3x</li>
</ul>
]]></content:encoded></item><item><title>Take Control of Your HTTP Caching in Rails</title><link>https://www.damiangalarza.com/posts/2015-01-26-take-control-of-your-http-caching-in-rails/</link><pubDate>Mon, 26 Jan 2015 00:00:00 -0500</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2015-01-26-take-control-of-your-http-caching-in-rails/</guid><description>Get more control over HTTP caching in Rails.</description><content:encoded><![CDATA[<p>This post was originally published on the <a href="https://thoughtbot.com/blog/take-control-of-your-http-caching-in-rails">thoughtbot blog</a>.</p>
<p>The Rails <code>fresh_when</code> method is a powerful tool for conditionally caching resources via HTTP. However there are some pitfalls. For one, <code>fresh_when</code> only supports the default render flow in a controller; if a client&rsquo;s cache is not fresh, it will just render the related view. We cannot utilize things like <code>render json:</code>.</p>
<p>Fortunately, Rails provides us with more tools to work with HTTP conditional caching. Some of the basics behind HTTP conditional caching are assumed in this post. If you haven&rsquo;t already, or you just need a refresher take a look at <a href="/posts/2014-11-25-introduction-to-conditional-http-caching-with-rails/">Introduction to Conditional HTTP Caching with Rails</a>.</p>
<p>Let&rsquo;s work with a simple Rails blog application which renders posts as JSON.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># app/controllers/posts_controller.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">PostsController</span> <span style="color:#89dceb;font-weight:bold">&lt;</span> <span style="color:#f9e2af">ApplicationController</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">show</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@post</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Post</span><span style="color:#89dceb;font-weight:bold">.</span>find(params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:id</span><span style="color:#89dceb;font-weight:bold">]</span>)
</span></span><span style="display:flex;"><span>    render <span style="color:#a6e3a1">json</span>: <span style="color:#f5e0dc">@post</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># config/routes</span>
</span></span><span style="display:flex;"><span><span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>routes<span style="color:#89dceb;font-weight:bold">.</span>draw <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  resources <span style="color:#a6e3a1">:posts</span>, <span style="color:#a6e3a1">only</span>: <span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:show</span><span style="color:#89dceb;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>What if we wanted to allow the client to conditionally cache this content? Rails provides us with the <a href="http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F"><code>stale?</code></a> method which takes a resource and checks to see if the ETag provided by the client via the <code>If-None-Matches</code> header matches the one which represents the resource&rsquo;s current state. The <code>stale?</code> method can also work with the <code>If-Modified-Since</code> header. It will check if the resource&rsquo;s <code>updated_at</code> has been modified since the timestamp provided by <code>If-Modified-Since</code>.  To utilize this, we just need to wrap our <code>render</code> call with a call to <code>stale?</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">PostsController</span> <span style="color:#89dceb;font-weight:bold">&lt;</span> <span style="color:#f9e2af">ApplicationController</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">show</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@post</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Post</span><span style="color:#89dceb;font-weight:bold">.</span>find(params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:id</span><span style="color:#89dceb;font-weight:bold">]</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">if</span> stale?(<span style="color:#f5e0dc">@post</span>)
</span></span><span style="display:flex;"><span>      render <span style="color:#a6e3a1">json</span>: <span style="color:#f5e0dc">@post</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>If we make a request to the individual post we&rsquo;ll get something similar to the following response:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -i http://localhost:3000/posts/1
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#cba6f7">HTTP</span><span style="color:#89dceb;font-weight:bold">/</span><span style="color:#fab387">1.1</span> <span style="color:#fab387">200</span> <span style="color:#fab387">OK</span>
</span></span><span style="display:flex;"><span>Etag<span style="color:#89dceb;font-weight:bold">:</span> &#34;f049a9825a0239db3d01ead65a5ea522&#34;
</span></span><span style="display:flex;"><span>Last-Modified<span style="color:#89dceb;font-weight:bold">:</span> Tue, 02 Dec 2014 18:31:42 GMT
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#89dceb;font-weight:bold">:</span> application/json; charset=utf-8
</span></span><span style="display:flex;"><span>Cache-Control<span style="color:#89dceb;font-weight:bold">:</span> max-age=0, private, must-revalidate
</span></span><span style="display:flex;"><span>Server<span style="color:#89dceb;font-weight:bold">:</span> WEBrick/1.3.1 (Ruby/2.1.5/2014-11-13)
</span></span><span style="display:flex;"><span>Date<span style="color:#89dceb;font-weight:bold">:</span> Tue, 02 Dec 2014 18:31:48 GMT
</span></span><span style="display:flex;"><span>Content-Length<span style="color:#89dceb;font-weight:bold">:</span> 93
</span></span><span style="display:flex;"><span>Connection<span style="color:#89dceb;font-weight:bold">:</span> Keep-Alive
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{<span style="color:#cba6f7">&#34;post&#34;</span>:{<span style="color:#cba6f7">&#34;title&#34;</span>:<span style="color:#a6e3a1">&#34;Blog post 0&#34;</span>,<span style="color:#cba6f7">&#34;body&#34;</span>:<span style="color:#a6e3a1">&#34;lorem ipsum&#34;</span>,<span style="color:#cba6f7">&#34;created_at&#34;</span>:<span style="color:#a6e3a1">&#34;2014-11-12T15:30:34.025Z&#34;</span>}}<span style="color:#f38ba8">%</span>
</span></span></code></pre></div><p>If we take a look at our Rails server log, we&rsquo;ll see something similar to the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>Started GET &#34;/posts/1&#34; for 127.0.0.1 at 2014-12-02 13:38:18 -0500
</span></span><span style="display:flex;"><span>Processing by PostsController#show as */*
</span></span><span style="display:flex;"><span>  Parameters: {&#34;id&#34;=&gt;&#34;1&#34;}
</span></span><span style="display:flex;"><span>  Post Load (0.2ms)  SELECT  &#34;posts&#34;.* FROM &#34;posts&#34;  WHERE &#34;posts&#34;.&#34;id&#34; = $1
</span></span><span style="display:flex;"><span>LIMIT 1  [[&#34;id&#34;, 1]]
</span></span><span style="display:flex;"><span>Completed 200 OK in 1ms (Views: 0.3ms | ActiveRecord: 0.2ms)
</span></span></code></pre></div><p>Now if we make a request with the <code>If-None-Match</code> header set to the ETag that the server provided us with earlier:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -i -H <span style="color:#a6e3a1">&#39;If-None-Match: &#34;f049a9825a0239db3d01ead65a5ea522&#34;&#39;</span> http://localhost:3000/posts/1
</span></span></code></pre></div><p>We&rsquo;ll now receive a 304 Not Modified status for our request.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#cba6f7">HTTP</span><span style="color:#89dceb;font-weight:bold">/</span><span style="color:#fab387">1.1</span> <span style="color:#fab387">304</span> <span style="color:#fab387">Not Modified</span>
</span></span><span style="display:flex;"><span>Etag<span style="color:#89dceb;font-weight:bold">:</span> &#34;f049a9825a0239db3d01ead65a5ea522&#34;
</span></span><span style="display:flex;"><span>Last-Modified<span style="color:#89dceb;font-weight:bold">:</span> Tue, 02 Dec 2014 18:31:42 GMT
</span></span><span style="display:flex;"><span>Cache-Control<span style="color:#89dceb;font-weight:bold">:</span> max-age=0, private, must-revalidate
</span></span><span style="display:flex;"><span>Server<span style="color:#89dceb;font-weight:bold">:</span> WEBrick/1.3.1 (Ruby/2.1.5/2014-11-13)
</span></span><span style="display:flex;"><span>Date<span style="color:#89dceb;font-weight:bold">:</span> Tue, 02 Dec 2014 18:32:53 GMT
</span></span><span style="display:flex;"><span>Connection<span style="color:#89dceb;font-weight:bold">:</span> Keep-Alive
</span></span></code></pre></div><p>If we take a look at our Rails log we&rsquo;ll notice that the server skipped over the process of rendering the JSON all together:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>Started GET &#34;/posts/1&#34; for 127.0.0.1 at 2014-12-02 13:38:55 -0500
</span></span><span style="display:flex;"><span>Processing by PostsController#show as */*
</span></span><span style="display:flex;"><span>  Parameters: {&#34;id&#34;=&gt;&#34;1&#34;}
</span></span><span style="display:flex;"><span>  Post Load (0.2ms)  SELECT  &#34;posts&#34;.* FROM &#34;posts&#34;  WHERE &#34;posts&#34;.&#34;id&#34; = $1
</span></span><span style="display:flex;"><span>LIMIT 1  [[&#34;id&#34;, 1]]
</span></span><span style="display:flex;"><span>Completed 304 Not Modified in 1ms (ActiveRecord: 0.2ms)
</span></span></code></pre></div><p>In this trivial example our gain is not huge. However with more complicated JSON objects this can be a very useful. A common example is a larger JSON response built via <code>ActiveModel::Serializers</code>. Combined with key-based caching in ActiveModel::Serializers this can be a powerful tool. We can cache individual items with <a href="http://blog.remarkablelabs.com/2012/12/russian-doll-caching-cache-digests-rails-4-countdown-to-2013">russian doll caching</a>, and then cache the entire response via HTTP caching.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a href="https://thoughtbot.com/blog/fast-json-apis-in-rails-with-key-based-caches-and">Fast JSON APIs in Rails with Key-Based Caches and ActiveModel::Serializers</a></li>
<li><a href="https://thoughtbot.com/blog/how-to-evaluate-your-rails-json-api-for-performance-improvements">How to Evaluate Your Rails JSON API for Performance Improvements</a></li>
</ul>
]]></content:encoded></item><item><title>Introduction to Conditional HTTP Caching with Rails</title><link>https://www.damiangalarza.com/posts/2014-11-25-introduction-to-conditional-http-caching-with-rails/</link><pubDate>Tue, 25 Nov 2014 00:00:00 -0500</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2014-11-25-introduction-to-conditional-http-caching-with-rails/</guid><description>Learn how to implement HTTP conditional caching in Rails using fresh_when.</description><content:encoded><![CDATA[<p>This post was originally published on the <a href="https://thoughtbot.com/blog/introduction-to-conditional-http-caching-with-rails">thoughtbot blog</a>.</p>
<p>HTTP provides developers with a powerful set of tools to cache responses. Often times we don&rsquo;t want a client to blindly cache content that it has been given. We may not be able to rely on setting specific expiration headers either. Instead we need a way for the client to ask the server whether or not a resource has been updated.</p>
<p>HTTP provides us with the ability to do this with conditional caching. A client can make a request to the server and find out of the server has a new version of the resource available.</p>
<p>Rails provides us with tools to take advantage of this. Before we jump into Rails, let&rsquo;s discuss some of the common ways to check if a client&rsquo;s cache is fresh or stale.</p>
<h2 id="etags">ETags</h2>
<p>ETags, short for entity tags, are a common way to conditionally verify an HTTP cache. An ETag is a digest which represents the contents of a given resource.</p>
<p>When a response is returned by the server it will include an ETag to represent the resource&rsquo;s state as part of the HTTP response headers. Subsequent HTTP requests which want to know whether or not the resource has changed since the last request can send along the stored ETag via the <code>If-None-Match</code> header.</p>
<p>The server will then compare the current ETag for the resource to the one provided by the client. If the two ETags match, the client&rsquo;s cache is considered fresh and the server can respond with a &ldquo;304 Not Modified&rdquo; status.</p>
<p>If the resource has changed since the last time the client has requested the resource the server will respond with a new ETag and the updated response.</p>
<h2 id="last-modified-timestamp">Last-Modified Timestamp</h2>
<p>Another header that a server can supply is the <code>Last-Modified</code> header.</p>
<p>As the name implies, this will return a timestamp of when the resource was last modified. The client can then include an <code>If-Modified-Since</code> header with the <code>Last-Modified</code> timestamp value to ask the server if the resource has been modified since the previous request.</p>
<p>Again, if the resource has not been modified since the last request, the server can respond with a &ldquo;304 Not Modified&rdquo; status. Otherwise, it will return an updated representation of the resource and a new <code>Last-Modified</code> timestamp for next time.</p>
<h2 id="caching-with-fresh_when">Caching with fresh_when</h2>
<p>Now that we&rsquo;ve discussed the ETags and Last-Modified HTTP headers, let&rsquo;s take a look at how we might use them within our Rails application.</p>
<p>ActionController provides us with a powerful method called <a href="http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-fresh_when"><code>fresh_when</code></a>. It will use either an ETag or a Last-Modified timestamp to determine whether or not a resource is fresh or stale.</p>
<p>Imagine a simple Rails blog application with the following posts route / controller:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># app/controllers/posts_controller.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">PostsController</span> <span style="color:#89dceb;font-weight:bold">&lt;</span> <span style="color:#f9e2af">ApplicationController</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">show</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@post</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Post</span><span style="color:#89dceb;font-weight:bold">.</span>find(params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:id</span><span style="color:#89dceb;font-weight:bold">]</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># config/routes.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>routes<span style="color:#89dceb;font-weight:bold">.</span>draw <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  resources <span style="color:#a6e3a1">:posts</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>If we made a curl request to an individual post we might see something like:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -i http://localhost:3000/posts/1
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#cba6f7">HTTP</span><span style="color:#89dceb;font-weight:bold">/</span><span style="color:#fab387">1.1</span> <span style="color:#fab387">200</span> <span style="color:#fab387">OK</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#89dceb;font-weight:bold">:</span> text/html; charset=utf-8
</span></span><span style="display:flex;"><span>Etag<span style="color:#89dceb;font-weight:bold">:</span> &#34;4af7e17fc369c6d1af99f8994d8fd387&#34;
</span></span><span style="display:flex;"><span>Server<span style="color:#89dceb;font-weight:bold">:</span> WEBrick/1.3.1 (Ruby/2.1.2/2014-05-08)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#cba6f7">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#cba6f7">h1</span>&gt;Blog post 1&lt;/<span style="color:#cba6f7">h1</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#cba6f7">p</span>&gt;lorem ipsum&lt;/<span style="color:#cba6f7">p</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#cba6f7">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#cba6f7">html</span>&gt;
</span></span></code></pre></div><p>Notice that we have not done anything particularly noteworthy, yet if we take a look at the headers returned as a part of the response we&rsquo;ll see that Rails has sent along an ETag to represent the resource being returned.</p>
<p>If we keep making the same request via curl we&rsquo;ll see a different ETag value even though the resource has not been modified since our previous request. This goes against the notion of what an ETag would be used for.</p>
<p>We&rsquo;ll need to modify our controller to instruct it that a request is fresh when a post has not been modified since the last request.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># app/controllers/posts_controller.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">PostsController</span> <span style="color:#89dceb;font-weight:bold">&lt;</span> <span style="color:#f9e2af">ApplicationController</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">show</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@post</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Post</span><span style="color:#89dceb;font-weight:bold">.</span>find(params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:id</span><span style="color:#89dceb;font-weight:bold">]</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    fresh_when <span style="color:#f5e0dc">@post</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>Now if we make our previous request over again we&rsquo;ll see that our ETag no longer changes on repeated requests:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -i http://localhost:3000/posts/1
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#f38ba8">Content-Length: 667
</span></span></span><span style="display:flex;"><span><span style="color:#f38ba8">Content-Type: text/html; charset=utf-8
</span></span></span><span style="display:flex;"><span><span style="color:#f38ba8">Etag: &#34;534279bfc931d4236713095ffd3efb28&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f38ba8">Last-Modified: Wed, 12 Nov 2014 15:44:46 GMT
</span></span></span><span style="display:flex;"><span><span style="color:#f38ba8">Server: WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)
</span></span></span></code></pre></div><p>Upon closer inspection of the updated HTTP headers returned in the response, we&rsquo;ll notice that the server has also started returning a <code>Last-Modified</code> timestamp for the post resource.</p>
<p>Our work is not complete.</p>
<p>In order to use our cache we need to verify it against the server with the ETag or last modified timestamp with either the <code>If-None-Match</code> or <code>If-Modified-Since</code> headers respectively.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -i -H <span style="color:#a6e3a1">&#39;If-None-Match: &#34;534279bfc931d4236713095ffd3efb28&#34;&#39;</span> http://localhost:3000/posts/1
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#cba6f7">HTTP</span><span style="color:#89dceb;font-weight:bold">/</span><span style="color:#fab387">1.1</span> <span style="color:#fab387">304</span> <span style="color:#fab387">Not Modified</span>
</span></span><span style="display:flex;"><span>Etag<span style="color:#89dceb;font-weight:bold">:</span> &#34;534279bfc931d4236713095ffd3efb28&#34;
</span></span><span style="display:flex;"><span>Last-Modified<span style="color:#89dceb;font-weight:bold">:</span> Wed, 12 Nov 2014 15:44:46 GMT
</span></span><span style="display:flex;"><span>Server<span style="color:#89dceb;font-weight:bold">:</span> WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)
</span></span></code></pre></div><p>Notice we now receive a 304 Not Modified response, instructing our client that it can use its cached version of the content. Browsers will automatically send a long a stored ETag and Last-Modified timestamp with conditional caching headers for us so we do not actually have to do anything to use this in the browser.</p>
<p>Let&rsquo;s take a look at the similar request with the <code>If-Modified-Since</code> header:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -i -H <span style="color:#a6e3a1">&#39;If-Modified-Since: Wed, 12 Nov 2014 15:44:46 GMT&#39;</span> http://localhost:3000/posts/1
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#cba6f7">HTTP</span><span style="color:#89dceb;font-weight:bold">/</span><span style="color:#fab387">1.1</span> <span style="color:#fab387">304</span> <span style="color:#fab387">Not Modified</span>
</span></span><span style="display:flex;"><span>Etag<span style="color:#89dceb;font-weight:bold">:</span> &#34;534279bfc931d4236713095ffd3efb28&#34;
</span></span><span style="display:flex;"><span>Last-Modified<span style="color:#89dceb;font-weight:bold">:</span> Wed, 12 Nov 2014 15:44:46 GMT
</span></span><span style="display:flex;"><span>Server<span style="color:#89dceb;font-weight:bold">:</span> WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)
</span></span></code></pre></div><p>You&rsquo;ll notice that we get the same results, the post has not been modified since the last time and we receive a 304 Not Modified response.</p>
<h2 id="conclusion">Conclusion</h2>
<p>What have we gained through all of this? We are still making requests to our server to see if a resource has been modified before rendering.</p>
<p>To really understand what we&rsquo;ve achieved we can take a look at our Rails log before we had caching:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>Started GET &#34;/posts/1&#34; for 127.0.0.1 at 2014-11-12 11:33:57 -0500
</span></span><span style="display:flex;"><span>Processing by PostsController#show as */*
</span></span><span style="display:flex;"><span>  Parameters: {&#34;id&#34;=&gt;&#34;1&#34;}
</span></span><span style="display:flex;"><span>  Post Load (0.3ms)  SELECT  &#34;posts&#34;.* FROM &#34;posts&#34;  WHERE &#34;posts&#34;.&#34;id&#34; = $1
</span></span><span style="display:flex;"><span>LIMIT 1  [[&#34;id&#34;, 1]]
</span></span><span style="display:flex;"><span>  Rendered posts/show.html.erb within layouts/application (0.1ms)
</span></span><span style="display:flex;"><span>  Rendered application/_flashes.html.erb (0.0ms)
</span></span><span style="display:flex;"><span>  Rendered application/_analytics.html.erb (0.1ms)
</span></span><span style="display:flex;"><span>  Rendered application/_javascript.html.erb (16.1ms)
</span></span><span style="display:flex;"><span>Completed 200 OK in 35ms (Views: 33.4ms | ActiveRecord: 0.3ms)
</span></span></code></pre></div><p>We can see that our request takes about 33ms and has to render our posts/show template within the layout along with some other partials. We can also see that most of our time is spent within that rendering process. ActiveRecord took less than a millisecond to run.</p>
<p>Next, let&rsquo;s take a look at our log with conditional caching in place, and put it to use by sending over a <code>If-None-Match</code> header:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-plaintext" data-lang="plaintext"><span style="display:flex;"><span>Started GET &#34;/posts/1&#34; for 127.0.0.1 at 2014-11-12 11:39:52 -0500
</span></span><span style="display:flex;"><span>Processing by PostsController#show as */*
</span></span><span style="display:flex;"><span>  Parameters: {&#34;id&#34;=&gt;&#34;1&#34;}
</span></span><span style="display:flex;"><span>  Post Load (0.3ms)  SELECT  &#34;posts&#34;.* FROM &#34;posts&#34;  WHERE &#34;posts&#34;.&#34;id&#34; = $1
</span></span><span style="display:flex;"><span>LIMIT 1  [[&#34;id&#34;, 1]]
</span></span><span style="display:flex;"><span>Completed 304 Not Modified in 2ms (ActiveRecord: 0.3ms)
</span></span></code></pre></div><p>Notice we&rsquo;ve cut off our entire rendering process from the request. Since the client&rsquo;s cached response is fresh there is no reason for Rails to go through the rendering process. It can load in the post via ActiveRecord and verify if it has changed since the previous request.</p>
<p>Our requests went from taking about 33ms to complete to 2ms. That&rsquo;s about a 94% improvement in performance.  Not only is Rails skipping the rendering process, but our responses are now much lighter as they have no body to return.</p>
<p>This may not be much in our trivial example but for applications which have a lot of complicated logic behind rendering this can save lots of time.</p>
<p>Furthermore, these requests can be put in front of a public cache such as <code>Rack::Cache</code> to share a common cache for resources as opposed to a private cache which is only utilized by the user&rsquo;s browser.</p>
<h2 id="getting-more">Getting more</h2>
<p>Our simple <code>fresh_when</code> solution is not without its limitations.</p>
<p>It cannot handle user specific content. If we wanted to display anything different on a per-user basis (even if that&rsquo;s the name in the header), we won&rsquo;t be able to cache the content.</p>
<p>Another issue is that the cache is not being shared among users. Our performance gain is only realized when a single user looks at the same post over and over again.</p>
<p>Lastly, we&rsquo;re limited to a simple <code>render</code> call in our controller. What if we wanted something more customized like <code>render json:</code> or <code>render xml:</code>?  We&rsquo;ll build on top of what we&rsquo;ve learned next time to overcome these limitations and improve our caching even further.</p>
]]></content:encoded></item><item><title>Scaling JSON APIs in Rails using ActiveModel::Serializers, Key-Based Caching, and Rack::Cache</title><link>https://www.damiangalarza.com/posts/scaling-json-apis-in-rails/</link><pubDate>Sun, 03 Nov 2013 00:00:00 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/scaling-json-apis-in-rails/</guid><description>Learn proven strategies for scaling JSON APIs in Rails applications. Performance optimization techniques using ActiveModel::Serializers, key-based caching, and Rack::Cache with real-world examples.</description><content:encoded><![CDATA[<p>This summer at Canvas I worked on a project which was a very dynamic and front-end heavy. In order to build this we architected it such a manner that the front-end would communicate with our servers through a JSON API. The API was responsbile for feeding the data used by the Javascript application. With this in mind we wanted the API to respond as fast as possible and be able to handle scale.</p>
<h1 id="organizing-our-api">Organizing our API</h1>
<p>Building our API we set out to achieve a few goals:</p>
<ul>
<li>Make the API flexibile and decoupled from the front-end implementation</li>
<li>Leverage HTTP caching whereever possible</li>
<li>Ensure it could handle high scale</li>
</ul>
<p>Making the API decoupled from it&rsquo;s front-end implmentation ensured that any design or data usage changes made would not require a change in 2 areas of our code base, the JavaScript application and the Rails application / API.</p>
<p>Leveraging HTTP caching gave us 2 gains. The first is we wanted to leverage an individual user&rsquo;s caching in their browser, reducing the amount of data we needed to send back to the user. The 2nd gain, which in my opinion was the bigger gain was the ability to add a caching layer in front of the entire response body through Rack::Cache.</p>
<h2 id="activemodelserializers">ActiveModel::Serializers</h2>
<p>(ActiveModel::Serializers)[https://github.com/rails-api/active_model_serializers] is a great library for encapsulating object serialization for JSON APIs in Rails. It provides a great Object Oriented approach to serialization. If you haven&rsquo;t seen it before I highly recommend checking it out. Furthermore, it implements caching through Rails.cache to provide the ability to cache serialization of objects using (key based expiration)[http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works]. This provided us with some very nice, quick and easy caching of our serialized data. Thoughtbot has a great (article on ActiveModel::Serailziers and caching)[http://robots.thoughtbot.com/post/50091183897/fast-json-apis-in-rails-with-key-based-caches-and] so I&rsquo;m not going to get into the details of it.</p>
<p>With this caching we were able to get a great method of caching our serialization. However we wanted more. Our models have a lot of associations which can be re-used through Russian doll caching. We implemented (this pull request)[https://github.com/rails-api/active_model_serializers/pull/307] in or serializers in order to benefit from side-effect free russian doll caching. With this in mind we could reuse caches just as easily across requests when possbile.</p>
<h2 id="rackcache">Rack::Cache</h2>
<p>Leveraging Rack::Cache in front of the API was one of the biggest wins for us. Rails has Rack::Cache active in production mode by default. However if you aren&rsquo;t using HTTP caching properly you won&rsquo;t see the benfits. There are many articles on the details of the various HTTP caching mechanisms that can be used. From <code>cache-control</code> headers, to <code>etags</code> and <code>last-modified</code> time stamps. Before we jump into the details let me explain why Rack::Cache was so useful.</p>
<p>When leveraging russian doll caching for our serialization we were able to get very fast serialization for our objects. However this typically lead to lots of memcached requests for fetcihng the individual pieces of the cache. This was not optimal. Our architecture benefited from the fact that one parent object could be used to determine the freshess of a cache. Think of the classic todo list example. Where individual todos in a list are cached as part of the entire todo list. If any individual todo-item in the list expires, then the list itself is expired. So, if we were using ActiveModel::Serialziers with caching, it would hit a cache for each item in the todo list.</p>
<h2 id="outline">Outline</h2>
<ul>
<li>Weeks JSON public to all users</li>
<li>Picks public because it&rsquo;s route is built from user ID.</li>
<li>ActiveModel::Serializers</li>
<li>Rack::Cache</li>
</ul>
]]></content:encoded></item><item><title>CSS3 Transition Idiom</title><link>https://www.damiangalarza.com/posts/2012-04-02-css3-transition-idiom/</link><pubDate>Mon, 02 Apr 2012 02:58:25 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2012-04-02-css3-transition-idiom/</guid><content:encoded><![CDATA[<p>As more browsers are starting to support CSS3 transitions I&rsquo;ve found myself trying to take advantage of their
availability more and more. The issue often that comes with this the fact that many times, we need to support
transitions in browsers which do not support them through JavaScript. While some people question the
extra work being performed in order to get CSS transitions working, I feel that the effort isn&rsquo;t as great as it seems.  The benefits of using CSS transitions are great and I feel the effort is well worth it.</p>
<ul>
<li><strong>Smoother Experience</strong> CSS transitions offer a smoother experience when compared to JavaScript animations. Since everything is being handled natively by the browser engine, we can get much smoother transitions.</li>
<li><strong>Mobile Performance</strong> This subcategory to the first note. While CSS transitions will most always provide a smoother experience in regular browsers, mobile devices will benefit from this the most. Since mobile devices lack the power that our desktops have, taking advantage of the browser engine for transitions is a must.</li>
</ul>
<h2 id="gracefully-degrading">Gracefully Degrading</h2>
<p>In most cases front end developers will err on the side of progressive enhancement. That is, to provide users who support newer technologies an experience with these technologies and user&rsquo;s who do not support it
will be left behind, in a usable state just without the bells and whistles. I consider this following method to be graceful degradation instead. The reason behind this is that we will not be removing the transition effects
all together from the page, but rather the method that we are using to apply the transition will be different, degrading to JavaScript animations when CSS transitions are not available (More on this later).</p>
<h3 id="detecting-our-features">Detecting our features</h3>
<p>Moving forward with our graceful degradation, we must determine how to apply the transition effect for the user. This should almost never be done via browser agent detection but rather through feature detection. Instead of only serving
CSS transitions to certain versions of a browser we should instead detect whether or not the user supports our feature, in this case, CSS transitions. This can be done through JavaScript early on before trying to transition. One option is to
use the open source JavaScript library <a href="http://www.modernizr.com/">Modernizr</a>. Modernizr will detect the HTML5 and CSS3 features that the browser has and you have the option to populate your HTML tag with classes which describe support. Along with these CSS classes,
a Modernizr object is created which you can check with JavaScript to see features that are supported. Modernizr even supports the ability for you to customize the features it detects. If you plan on using a lot of feature detection, then I highly recommend using
Modernizr.</p>
<p>However, in some cases you may not need all of the features that Modernizr provides and it would be wasteful to add the bloat it provides or we simply may not be able to add Modernizr to our page. In this case we can detect features ourselves. For our transitions example we will need to detect whether or not a CSS3 property is available in the user&rsquo;s browser.  Since as of this writing the transition property is prefixed by the vendor we will need to run through and check each vendor prefix version of the transition property to see if the users&rsquo; browser supports it or not.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">// Check for CSS3 Transition Support
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#f38ba8">var</span> doc <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">document</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f38ba8">var</span> css_transitions <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f38ba8">function</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#f38ba8">var</span> el <span style="color:#89dceb;font-weight:bold">=</span> doc.createElement(<span style="color:#a6e3a1">&#39;div&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#f38ba8">var</span> vendors <span style="color:#89dceb;font-weight:bold">=</span> [<span style="color:#a6e3a1">&#39;&#39;</span>, <span style="color:#a6e3a1">&#39;Khtml&#39;</span>, <span style="color:#a6e3a1">&#39;Ms&#39;</span>, <span style="color:#a6e3a1">&#39;Moz&#39;</span>,<span style="color:#a6e3a1">&#39; Webkit&#39;</span>,<span style="color:#a6e3a1">&#39;O&#39;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">for</span> (<span style="color:#f38ba8">var</span> i <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#fab387">0</span>, len <span style="color:#89dceb;font-weight:bold">=</span> vendors.length; i <span style="color:#89dceb;font-weight:bold">&lt;</span> len; i<span style="color:#89dceb;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#f38ba8">var</span> prop <span style="color:#89dceb;font-weight:bold">=</span> vendors[i] <span style="color:#89dceb;font-weight:bold">+</span> <span style="color:#a6e3a1">&#39;Transition&#39;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">if</span> (prop <span style="color:#cba6f7">in</span> el.style) <span style="color:#cba6f7">return</span> <span style="color:#fab387">true</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">return</span> <span style="color:#fab387">false</span>;
</span></span><span style="display:flex;"><span>}();
</span></span></code></pre></div><p>First, we create a test element in memory to test against. Then we create an array of vendor prefixes <code>['', 'Khtml', 'Ms', 'Moz', 'Webkit', 'O']</code>. Note that our first entry is a blank string. This is so that when browsers have fully implemented the transition spec and vendor prefixes are no longer used, we detect the feature correctly. Now we loop through the array. For each iteration we create a property to test which is a combination of the vendor prefix and our base property, in this case Transition. So for example. we will test <code>MozTransition</code>.
We test to see if the property exists as part of the test elements style property. If this comes back true, then our browser supports CSS transitions. In the case of our example we execute our function immediately and store the result in a variable called <code>css_transitions</code>.</p>
<h3 id="putting-our-css_transitions-variable-to-use">Putting our css_transitions variable to use</h3>
<p>Now that we&rsquo;ve determined whether or not the user&rsquo;s browser supports CSS transitions we need to go put it to work. In this case, let&rsquo;s transition the opacity of an element:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">style</span> <span style="color:#89b4fa">type</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;text/css&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    .<span style="color:#f9e2af">transition-me</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">-o-</span><span style="color:#f38ba8">transition(opacity</span> <span style="color:#f38ba8">0.25s</span> <span style="color:#f38ba8">linear</span> <span style="color:#f38ba8">0s)</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">-moz-</span><span style="color:#f38ba8">transition(opacity</span> <span style="color:#f38ba8">0.25s</span> <span style="color:#f38ba8">linear</span> <span style="color:#f38ba8">0s)</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">-webkit-</span><span style="color:#f38ba8">transition(opacity</span> <span style="color:#f38ba8">0.25s</span> <span style="color:#f38ba8">linear</span> <span style="color:#f38ba8">0s)</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#cba6f7">-ms-</span><span style="color:#f38ba8">transition(opacity</span> <span style="color:#f38ba8">0.25s</span> <span style="color:#f38ba8">linear</span> <span style="color:#f38ba8">0s)</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f38ba8">transition(opacity</span> <span style="color:#f38ba8">0.25s</span> <span style="color:#f38ba8">linear</span> <span style="color:#f38ba8">0s)</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#cba6f7">style</span>&gt;
</span></span></code></pre></div><p>In this example we will use jQuery which provides us with a rather handy syntax for changing CSS properties on an element or calling an animation on an element.  The syntax for each
actually matches exactly when used which allows us to use a pretty simple idiom to transition with.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic">// Fade out .transition-me elements
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span><span style="color:#f38ba8">var</span> method <span style="color:#89dceb;font-weight:bold">=</span> (css_transitions) <span style="color:#89dceb;font-weight:bold">?</span> <span style="color:#a6e3a1">&#39;css&#39;</span> <span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;method&#39;</span>;
</span></span><span style="display:flex;"><span>$(<span style="color:#a6e3a1">&#39;.transition-me&#39;</span>)[method]({
</span></span><span style="display:flex;"><span>    opacity<span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#fab387">0</span>
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>If you visit the jQuery documentation both the <a href="http://api.jquery.com/css/">css</a> and <a href="http://api.jquery.com/animate/">animate</a> support a map syntax which allows us to send an object to the
method call with a property we want to transition as the key and the value to transition to as the value. With this in mind, we can simply call the appropriate method (css or transition) on
the element we want to transition.</p>
<h2 id="a-note-on-progressive-enhancement">A note on Progressive Enhancement</h2>
<p>While this particular post discusses how to apply graceful degradation to CSS transitions there is still room for some progressive enhancement. What I mean by this is there are certain times where
a transition or animation is key to the user&rsquo;s experience. Transitions help guide the user through changes on the page. There are times however where transitions are just applied to add a certain level
of eye candy. It is this class of transition that I would simply not provide in browsers which do not support it since they are not key to the user&rsquo;s experience with the page.</p>
]]></content:encoded></item><item><title>Bringing back the image map</title><link>https://www.damiangalarza.com/posts/2010-12-27-bringing-back-the-image-map/</link><pubDate>Mon, 27 Dec 2010 18:34:59 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2010-12-27-bringing-back-the-image-map/</guid><content:encoded><![CDATA[<p>I&rsquo;ve just finished working with an image map for the first time in a while. Now, you may be thinking, &ldquo;Why in the world would anyone want to touch an image map?&rdquo;, but they still do serve a purpose. Image maps are still in fact part of the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-map-element.html#image-map">HTML5 specification</a>.</p>
<h3 id="case-study">Case Study</h3>
<p>So why the image map? Take a look at the following image:</p>
<p><img src="/images/map-case-study.gif" alt="Case Study"></p>
<p>So our image above is triangular, and is intended to be clickable.  The expectation is that only the triangular area should be clickable. So in order to accomplish this, I&rsquo;ve decided to move forward with using an image map.</p>
<p>Things get a bit more complicated, though. The image above is actually repeated many times on a page. It acts as a flag to being a favorite of sorts. Furthermore, we are not simply sending the user to a page link, we are looking to use JavaScript events on the image map (and unobtrusively at that). This unobtrusive JavaScript must send an ajax request to the server depending on which instance of the image is actually being clicked. So we have our work cut out for us.</p>
<h3 id="setting-up-the-image-map">Setting up the image map</h3>
<p>Getting the image map set up is pretty simple. Let&rsquo;s start with defining the map:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">map</span> <span style="color:#89b4fa">name</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;my-map&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#cba6f7">area</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;fav-area&#34;</span> <span style="color:#89b4fa">shape</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">poly,</span> <span style="color:#89b4fa">coords</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;0,0, 46,0, 46,46&#34;</span> <span style="color:#89b4fa">href</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;#fav&#34;</span>&gt; 
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#cba6f7">map</span>&gt;
</span></span></code></pre></div><p>We&rsquo;ve defined a map named <code>my-map</code> with some coordinates. Note our shape is set to poly and the coordinates provided. In order to determine these coordinates I simply opened up Photoshop and grabbed the x and y coordinates of the 3 corners of the triangle. Along with that, since we have no intent on passing the user to an actual link we&rsquo;ve provided a dummy hash on the href.</p>
<h3 id="setting-up-the-image">Setting up the image</h3>
<p>So now the the map is defined we&rsquo;ll create an instance of the image and have it use the map we made:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">img</span> <span style="color:#89b4fa">src</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;/images/map-case-study.gif&#34;</span> <span style="color:#89b4fa">usemap</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;#my-map&#34;</span>&gt;
</span></span></code></pre></div><h3 id="adding-an-event-handler">Adding an event handler</h3>
<p>Adding an event handler is pretty straight forward as you might expect. I&rsquo;ll be using jQuery for the demonstration.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>$(<span style="color:#f38ba8">function</span>() {
</span></span><span style="display:flex;"><span>	$(<span style="color:#a6e3a1">&#39;map[name=my-map] #fave-area&#39;</span>).click(<span style="color:#f38ba8">function</span> (e) {
</span></span><span style="display:flex;"><span>		e.preventDefault();
</span></span><span style="display:flex;"><span>		alert(<span style="color:#a6e3a1">&#39;Clicked!&#39;</span>);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>So we can still target and add an event as you normally would to the area.</p>
<h3 id="multiple-instances-of-the-same-image-and-image-map">Multiple instances of the same image and image map</h3>
<p>Now that we have our image map set up, let&rsquo;s move forward with the more complicated setup.  Take the HTML structure below:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">div</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;node-1&#34;</span> <span style="color:#89b4fa">class</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;my-node&#34;</span>&gt;
</span></span><span style="display:flex;"><span>	&lt;<span style="color:#cba6f7">img</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;study-1&#34;</span> <span style="color:#89b4fa">src</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;/images/map-case-study.gif&#34;</span> <span style="color:#89b4fa">usemap</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;#my-map&#34;</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#cba6f7">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">div</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;node-2&#34;</span> <span style="color:#89b4fa">class</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;my-node&#34;</span>&gt;
</span></span><span style="display:flex;"><span>	&lt;<span style="color:#cba6f7">img</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;study-2&#34;</span> <span style="color:#89b4fa">src</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;/images/map-case-study.gif&#34;</span> <span style="color:#89b4fa">usemap</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;#my-map&#34;</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#cba6f7">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">div</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;node-3&#34;</span> <span style="color:#89b4fa">class</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;my-node&#34;</span>&gt;
</span></span><span style="display:flex;"><span>	&lt;<span style="color:#cba6f7">img</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;study-3&#34;</span> <span style="color:#89b4fa">src</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;/images/map-case-study.gif&#34;</span> <span style="color:#89b4fa">usemap</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;#my-map&#34;</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#cba6f7">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">map</span> <span style="color:#89b4fa">name</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;my-map&#34;</span>&gt;
</span></span><span style="display:flex;"><span>	&lt;<span style="color:#cba6f7">area</span> <span style="color:#89b4fa">id</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;fav-area&#34;</span> <span style="color:#89b4fa">shape</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">poly,</span> <span style="color:#89b4fa">coords</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;0,0, 46,0, 46,46&#34;</span> <span style="color:#89b4fa">href</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;#fav&#34;</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#cba6f7">map</span>&gt;
</span></span></code></pre></div><p>We have multiple nodes which aim to make use of the same case-study image and they all use the same image map to define the clickable area. Now the problem is, how do we determine which image was actually clicked?</p>
<p>Well first, we can make use of a DOM method called <code>elementFromPoint</code>. This method allows you to pass in x and y coordinates and retrieve the element that is found at the given coordinates. I never knew that this method existed until today and it&rsquo;s a pretty interesting find.</p>
<h3 id="putting-elementfrompoint-to-use">Putting elementFromPoint to use</h3>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>$(<span style="color:#f38ba8">function</span>() {
</span></span><span style="display:flex;"><span>	$(<span style="color:#a6e3a1">&#39;map[name=my-map] #fave-area&#39;</span>).click(<span style="color:#f38ba8">function</span> (e) {
</span></span><span style="display:flex;"><span>		e.preventDefault();
</span></span><span style="display:flex;"><span>		<span style="color:#f38ba8">var</span> trigger <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">document</span>.elementFromPoint(e.clientX, e.clientY);
</span></span><span style="display:flex;"><span>		alert(trigger.id);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>So we take the coordinates of the actual click event and pass it to the elementFromPoint function to grab the actual element the user was trying to click.  One problem still exists though: This works fine in Firefox and Safari but Chrome (and IE as some have reported) will still return the map area when using elementFromPoint.  In order to counter this, I&rsquo;ve found that disabling the image map before running elementFromPoint will then yield our expected element. Of course, once we&rsquo;re done finding the element we must re-enable the image map.</p>
<p>So how do we do this? Take a look at the final JavaScript:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>$(<span style="color:#f38ba8">function</span>() {
</span></span><span style="display:flex;"><span>	$(<span style="color:#a6e3a1">&#39;map[name=my-map] #fave-area&#39;</span>).click(<span style="color:#f38ba8">function</span> (e) {
</span></span><span style="display:flex;"><span>		e.preventDefault();
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#6c7086;font-style:italic">// Cache all instances of the image
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>		<span style="color:#f38ba8">var</span> $all_instances <span style="color:#89dceb;font-weight:bold">=</span> $(<span style="color:#a6e3a1">&#39;.my-node img&#39;</span>);
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#6c7086;font-style:italic">// Store the usemap in order to re-enable later
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>		<span style="color:#f38ba8">var</span> usemap <span style="color:#89dceb;font-weight:bold">=</span> $all_instances.attr(<span style="color:#a6e3a1">&#39;usemap&#39;</span>);
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#6c7086;font-style:italic">// Reset the use of an image map on all instances of it
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>		$all_instances.attr(<span style="color:#a6e3a1">&#39;usemap&#39;</span>, <span style="color:#a6e3a1">&#39;&#39;</span>);
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#6c7086;font-style:italic">// Grap the actual trigger we were looking for
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>		<span style="color:#f38ba8">var</span> trigger <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">document</span>.elementFromPoint(e.clientX, e.clientY);
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#6c7086;font-style:italic">// Re-enable the image map on all the instances we disabled it on
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>		$all_instances.attr(<span style="color:#a6e3a1">&#39;usemap&#39;</span>, usemap);
</span></span><span style="display:flex;"><span>		
</span></span><span style="display:flex;"><span>		<span style="color:#6c7086;font-style:italic">// Do something useful with the information
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>		alert(trigger.id);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div>]]></content:encoded></item><item><title>CSS Gradient Definitions</title><link>https://www.damiangalarza.com/posts/2010-12-23-css-gradient-definitions/</link><pubDate>Thu, 23 Dec 2010 03:26:19 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2010-12-23-css-gradient-definitions/</guid><content:encoded><![CDATA[<p>I&rsquo;ve just released an update to the <a href="http://gradients.glrzad.com">CSS3 Gradient Generator</a> which changes the way the code sample is generated for users to copy and paste.</p>
<p>The sample has moved to using the <code>background-image</code> property instead of the <code>background</code> property alone. This change has been made to resolve issues that occur when defining a background with the CSS based gradient but leaving out the other properties common to a background such as position or repeat.  When these attributes are left out, browsers define default values which can yield unexpected results with the CSS gradients.</p>
<p>So, the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#cba6f7">background</span><span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#cba6f7">-webkit-gradient</span><span style="color:#89dceb;font-weight:bold">(</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">linear</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">left</span> <span style="color:#cba6f7">bottom</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">left</span> <span style="color:#cba6f7">top</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">color-stop</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">0</span>.<span style="color:#f9e2af">33</span><span style="color:#89dceb;font-weight:bold">,</span> <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">99</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">168</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">125</span><span style="color:#89dceb;font-weight:bold">)),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">color-stop</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">0</span>.<span style="color:#f9e2af">67</span><span style="color:#89dceb;font-weight:bold">,</span> <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">129</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">202</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">163</span><span style="color:#89dceb;font-weight:bold">)),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">color-stop</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">0</span>.<span style="color:#f9e2af">84</span><span style="color:#89dceb;font-weight:bold">,</span> <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">155</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">243</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">196</span><span style="color:#89dceb;font-weight:bold">))</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">background</span><span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#cba6f7">-moz-linear-gradient</span><span style="color:#89dceb;font-weight:bold">(</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">center</span> <span style="color:#cba6f7">bottom</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">99</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">168</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">125</span><span style="color:#89dceb;font-weight:bold">)</span> <span style="color:#cba6f7">33</span><span style="color:#89dceb;font-weight:bold">%,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">129</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">202</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">163</span><span style="color:#89dceb;font-weight:bold">)</span> <span style="color:#cba6f7">67</span><span style="color:#89dceb;font-weight:bold">%,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">155</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">243</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">196</span><span style="color:#89dceb;font-weight:bold">)</span> <span style="color:#cba6f7">84</span><span style="color:#89dceb;font-weight:bold">%</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">);</span>
</span></span></code></pre></div><p>Becomes,</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#cba6f7">background-image</span><span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#cba6f7">-webkit-gradient</span><span style="color:#89dceb;font-weight:bold">(</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">linear</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">left</span> <span style="color:#cba6f7">bottom</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">left</span> <span style="color:#cba6f7">top</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">color-stop</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">0</span>.<span style="color:#f9e2af">33</span><span style="color:#89dceb;font-weight:bold">,</span> <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">99</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">168</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">125</span><span style="color:#89dceb;font-weight:bold">)),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">color-stop</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">0</span>.<span style="color:#f9e2af">67</span><span style="color:#89dceb;font-weight:bold">,</span> <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">129</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">202</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">163</span><span style="color:#89dceb;font-weight:bold">)),</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">color-stop</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">0</span>.<span style="color:#f9e2af">84</span><span style="color:#89dceb;font-weight:bold">,</span> <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">155</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">243</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">196</span><span style="color:#89dceb;font-weight:bold">))</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">);</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">background-image</span><span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#cba6f7">-moz-linear-gradient</span><span style="color:#89dceb;font-weight:bold">(</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">center</span> <span style="color:#cba6f7">bottom</span><span style="color:#89dceb;font-weight:bold">,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">99</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">168</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">125</span><span style="color:#89dceb;font-weight:bold">)</span> <span style="color:#cba6f7">33</span><span style="color:#89dceb;font-weight:bold">%,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">129</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">202</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">163</span><span style="color:#89dceb;font-weight:bold">)</span> <span style="color:#cba6f7">67</span><span style="color:#89dceb;font-weight:bold">%,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">rgb</span><span style="color:#89dceb;font-weight:bold">(</span><span style="color:#cba6f7">155</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">243</span><span style="color:#89dceb;font-weight:bold">,</span><span style="color:#cba6f7">196</span><span style="color:#89dceb;font-weight:bold">)</span> <span style="color:#cba6f7">84</span><span style="color:#89dceb;font-weight:bold">%</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">);</span>
</span></span></code></pre></div><p>The live sample on the page also makes use of background-image as well, so you can view that as another reference to this update as well.</p>
<p>Thanks to Avi Mahbubani for pointing out the issue and helping come to a resolution.</p>
]]></content:encoded></item><item><title>Using Uploadify with Rails 3 - Part 2 - Controller Example</title><link>https://www.damiangalarza.com/posts/2010-10-27-using-uploadify-with-rails-3-part-2-controller-example/</link><pubDate>Wed, 27 Oct 2010 02:08:08 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2010-10-27-using-uploadify-with-rails-3-part-2-controller-example/</guid><content:encoded><![CDATA[<p>Back in August <a href="http://www.glrzad.com/ruby-on-rails/using-uploadify-with-rails-3/" title="Using Uploadify with Rails 3">I posted a walkthrough</a> on working with a Flash based file uploader with Rails 3. After reading some comments I realized that a part was left out.  Setting up the controller to work with our upload. So this post is here as part 2 of &lsquo;Using Uploadify with Rails 3&rsquo;.</p>
<p>This sample is going to be using the <a href="http://github.com/thoughtbot/paperclip" title="paperclip by thoughtbot">paperclip gem by thoughtbot</a> to handle our upload.</p>
<h3 id="the-controller">The Controller</h3>
<p>So we&rsquo;ve gotten our file uploaded successfully, authentication is passing thanks to our previous work but what now? One of the first issues that you might run into is the content type for the file you uploaded.  Our uploadify upload will actually have the content_type set to &lsquo;application/octet-stream&rsquo; which is not totally accurate representation of what we&rsquo;re uploading.</p>
<p>As stated earlier, this example is using paperclip, which will need the true content_type to be set to accurately continue uploading the file.  To resolve the &lsquo;application/octet-stream&rsquo; issue we can use the mime-type gem and set the content_type of the file afterwards in the controller before trying to save the upload. Add the following to your gem file and run a bundle install:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span>gem <span style="color:#a6e3a1">&#39;mime-types&#39;</span>, <span style="color:#a6e3a1">:require</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;mime/types&#39;</span>
</span></span></code></pre></div><p>With this in place we can force our controller to set the content type of the upload to the proper value.</p>
<p>We can do this with the following code:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span>params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:Filedata</span><span style="color:#89dceb;font-weight:bold">].</span>content_type <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">MIME</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Types</span><span style="color:#89dceb;font-weight:bold">.</span>type_for(params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:Filedata</span><span style="color:#89dceb;font-weight:bold">].</span>original_filename)<span style="color:#89dceb;font-weight:bold">.</span>to_s
</span></span></code></pre></div><p>Below is a full code sample of our Paperclip based model, and the full create controller method for handling our upload.  I hope you&rsquo;ve found this post useful and please feel free to comment below with any questions, as I&rsquo;ll try to get to them as soon as possible.</p>
<h4 id="galleryimage-model">GalleryImage Model</h4>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">GalleryImage</span> <span style="color:#89dceb;font-weight:bold">&lt;</span> <span style="color:#f9e2af">ActiveRecord</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Base</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	belongs_to <span style="color:#a6e3a1">:gallery</span>
</span></span><span style="display:flex;"><span>	delegate <span style="color:#a6e3a1">:venue</span>, <span style="color:#a6e3a1">:to</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">:gallery</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#6c7086;font-style:italic"># Storing photos in /galleries/:venue_name/:gallery_id/...</span>
</span></span><span style="display:flex;"><span>	has_attached_file <span style="color:#a6e3a1">:photo</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e3a1">:styles</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> { <span style="color:#a6e3a1">:thumb</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#34;70x70&#34;</span>, <span style="color:#a6e3a1">:iphone_thumb</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#34;30x30#&#34;</span>, <span style="color:#a6e3a1">:iphone_full</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#34;320x480&#34;</span> },
</span></span><span style="display:flex;"><span>		<span style="color:#a6e3a1">:url</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;/galleries/:venue/:gallery/:id/:attachment/:style/:basename.:extension&#39;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e3a1">:path</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;:rails_root/public/galleries/:venue/:gallery/:id/:attachment/:style/:basename.:extension&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><h4 id="galleryimagescontroller">GalleryImagesController</h4>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#cba6f7">def</span> <span style="color:#89b4fa">create</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#f5e0dc">@gallery</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Gallery</span><span style="color:#89dceb;font-weight:bold">.</span>find(params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:gallery_id</span><span style="color:#89dceb;font-weight:bold">]</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#f5e0dc">@gallery_image</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">GalleryImage</span><span style="color:#89dceb;font-weight:bold">.</span>new
</span></span><span style="display:flex;"><span>	<span style="color:#f5e0dc">@gallery_image</span><span style="color:#89dceb;font-weight:bold">.</span>gallery <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f5e0dc">@gallery</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#6c7086;font-style:italic"># Associate the correct MIME type for the file since Flash will change it</span>
</span></span><span style="display:flex;"><span>	params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:Filedata</span><span style="color:#89dceb;font-weight:bold">].</span>content_type <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">MIME</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Types</span><span style="color:#89dceb;font-weight:bold">.</span>type_for(params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:Filedata</span><span style="color:#89dceb;font-weight:bold">].</span>original_filename)<span style="color:#89dceb;font-weight:bold">.</span>to_s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#f5e0dc">@gallery_image</span><span style="color:#89dceb;font-weight:bold">.</span>photo <span style="color:#89dceb;font-weight:bold">=</span> params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:Filedata</span><span style="color:#89dceb;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">if</span> <span style="color:#f5e0dc">@gallery_image</span><span style="color:#89dceb;font-weight:bold">.</span>save
</span></span><span style="display:flex;"><span>		render <span style="color:#a6e3a1">:json</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> { <span style="color:#a6e3a1">&#39;status&#39;</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;success&#39;</span>  }
</span></span><span style="display:flex;"><span>	<span style="color:#cba6f7">else</span>
</span></span><span style="display:flex;"><span>		render <span style="color:#a6e3a1">:json</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> { <span style="color:#a6e3a1">&#39;status&#39;</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;error&#39;</span> }
</span></span><span style="display:flex;"><span>	<span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>Using Uploadify with Rails 3</title><link>https://www.damiangalarza.com/posts/2010-08-25-using-uploadify-with-rails-3/</link><pubDate>Wed, 25 Aug 2010 02:29:53 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2010-08-25-using-uploadify-with-rails-3/</guid><content:encoded><![CDATA[<p>I recently worked on a couple of Rails projects which both implemented the ability to upload photos to the server.  One of these projects required the ability to upload multiple files at once and since HTML5 multi-file uploads aren&rsquo;t ready yet I needed another solution. So I decided to move forward using a Flash based uploader, specifically <a href="http://www.uploadify.com/">Uploadify</a>. Uploadify is a jQuery plugin which allows you to upload multiple files through a Flash based interface which provides helpful functionality like progress bars and multi-upload queuing, along with this, it&rsquo;s super easy to use.</p>
<p>Well that is until you&rsquo;re trying to work with a Rails application.  There are lots of resources around that go into the details of how to get this to work but the real issue that I had was that they were mostly for Rails 2.3.8 and my application is now using Rails 3 and the methods described in all of these articles don&rsquo;t completely apply to Rails 3.</p>
<h3>Why is this extra work needed?</h3>
<p>Before moving forward on how to get everything working let&rsquo;s take a look at why it&rsquo;s not working in the first place to better understand what we&rsquo;re dealing with and how we&rsquo;re fixing the problem.</p>
<p>Requests made via POST, PUT, DELETE all require a valid authentication token and session information in order to complete the request successfully in order to protect against Cross-site request forgery.  When building out forms using Rails form helpers, a hidden input is created which contains the authenticity token to prove the authenticity of the request along with your session cookie data.</p>
<p>This poses 2 problems for us using Flash to upload files.</p>
<p>First, Uploadify is not going to automatically send the authenticity token with the POST request. Second, Flash based requests do not send our session information along with the request. The first situation is pretty easy to fix while the session information requires a little more work on our part.</p>
<h3>Sending the Authentication_Token</h3>
<p>Let&rsquo;s start by sending the authenticity token along with the request. In previous versions of Rails we would have had to found a way to get the authenticity token to send but as part of Rails 3 usage of Unobtrusive JavaScript, it now includes a new helper method called <code>csrf_meta_tag</code>. This helper method will provide us with 2 meta tags, <strong>csrf-param</strong> and <strong>csrf-token</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">meta</span> <span style="color:#89b4fa">name</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;csrf-param&#34;</span> <span style="color:#89b4fa">content</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;authenticity_token&#34;</span>/&gt;
</span></span><span style="display:flex;"><span>&lt;<span style="color:#cba6f7">meta</span> <span style="color:#89b4fa">name</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;csrf-token&#34;</span> <span style="color:#89b4fa">content</span><span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;s2yuw7u24N9fni1m+Wynr595kTphEeMrNSWPAnMroLM=&#34;</span>/&gt;
</span></span></code></pre></div><p>Once we have the CSRF meta tags in place, we can move forward and add the authenticity to the parameters which are sent with Uploadify&rsquo;s request.  The JavaScript snippet below is an example of pulling the CSRF token information and passing it along with our Uploadify requests using the scriptData configuration parameter.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">&lt;</span>script type<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;text/javascript&#34;</span><span style="color:#89dceb;font-weight:bold">&gt;</span>
</span></span><span style="display:flex;"><span>$(<span style="color:#f38ba8">function</span> () {
</span></span><span style="display:flex;"><span>  <span style="color:#6c7086;font-style:italic">// Create an empty object to store our custom script data
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>  <span style="color:#f38ba8">var</span> uploadify_script_data <span style="color:#89dceb;font-weight:bold">=</span> {};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#6c7086;font-style:italic">// Fetch the CSRF meta tag data
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>  <span style="color:#f38ba8">var</span> csrf_token <span style="color:#89dceb;font-weight:bold">=</span> $(<span style="color:#a6e3a1">&#39;meta[name=csrf-token]&#39;</span>).attr(<span style="color:#a6e3a1">&#39;content&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#f38ba8">var</span> csrf_param <span style="color:#89dceb;font-weight:bold">=</span> $(<span style="color:#a6e3a1">&#39;meta[name=csrf-param]&#39;</span>).attr(<span style="color:#a6e3a1">&#39;content&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#6c7086;font-style:italic">// Now associate the data in the config, encoding the data safely
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>  uploadify_script_data[csrf_token] <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">encodeURI</span>(csrf_param);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#6c7086;font-style:italic">// Configure Uploadify
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>  $(<span style="color:#a6e3a1">&#39;#photo-upload&#39;</span>).uploadify({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#39;uploader&#39;</span> <span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;/assets/uploadify.swf&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#39;script&#39;</span> <span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;/photos/&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">&#39;scriptData&#39;</span> <span style="color:#89dceb;font-weight:bold">:</span> uploadify_script_data
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">&lt;</span><span style="color:#f38ba8">/script&gt;</span>
</span></span></code></pre></div><p>Uploadify allows you to pass extra data along with it&rsquo;s request using the scriptData parameter which accepts an object of properties to send with the request.  In our case we are fetching the CSRF and creating a new property in the scriptData object with the name of the CSRF token and setting it&rsquo;s value to the CSRF param.  Using this, we can pass along the authenticity token with the request.  It&rsquo;s important to note that we are encoding the authentication token value using encodeURI to escape any special characters so the data gets sent along to the server correctly.</p>
<p>I actually ran into an issue where even using encodeURI once was not enough and I needed to double encode because + signs were being converted into spaces.
So if you are continuing to run into InvalidAuthenticityToken error, check your log to see that the authenticity token is being correctly sent along. While trying to debug the problem a quick look into the log showed that I was receiving</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span>authenticity_token<span style="color:#89dceb;font-weight:bold">=&gt;</span><span style="color:#a6e3a1">&#34;3WB Cj0h7y4St5ZyvlYU xIEVT7yIIr6IRAhOOLCb5w=
</span></span></span></code></pre></div><p>The spaces in the above authenticity token should have been + signs instead.  In order to resolve this issue I ended up having to double encode the authenticity token before sending it along like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>uploadify_script_data[csrf_token] <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">encodeURI</span>(<span style="color:#89dceb">encodeURI</span>(csrf_param));
</span></span></code></pre></div><p>Now that we have the authenticity token being sent by Uploadify, let&rsquo;s move forward and get our session information passed along as well.</p>
<h3>Passing the Session Cookie</h3>
<p>One of the first steps to passing the session cookie along with our Uploadify requests is to add it to the scriptData like we did with the authentication_token. Unfortunately there is no quick way to handle this through JavaScript alone, we need to include it using some ERB to apply it.</p>
<p>In earlier versions of Rails the session cookie key could be accessed via: <code>ActionController::Base.session_options[:key]</code></p>
<p>As of Rails 3 though we can access the session cookie key using: <code>Rails.application.config.session_options[:key]</code></p>
<p>Below is a simple example of how to send the session cookie information in the scriptData.</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">&lt;</span>script type<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;text/javascript&#34;</span><span style="color:#89dceb;font-weight:bold">&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">&lt;%-</span> session_key <span style="color:#89dceb;font-weight:bold">=</span> Rails.application.config.session_options[<span style="color:#89dceb;font-weight:bold">:</span>key] <span style="color:#89dceb;font-weight:bold">-%&gt;</span>
</span></span><span style="display:flex;"><span>$(<span style="color:#f38ba8">function</span> () {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f38ba8">var</span> uploadify_script_data <span style="color:#89dceb;font-weight:bold">=</span> {};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#6c7086;font-style:italic">// Fetch the CSRF meta tag data
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>  <span style="color:#f38ba8">var</span> csrf_token <span style="color:#89dceb;font-weight:bold">=</span> $(<span style="color:#a6e3a1">&#39;meta[name=csrf-token]&#39;</span>).attr(<span style="color:#a6e3a1">&#39;content&#39;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#f38ba8">var</span> csrf_param <span style="color:#89dceb;font-weight:bold">=</span> $(<span style="color:#a6e3a1">&#39;meta[name=csrf-param]&#39;</span>).attr(<span style="color:#a6e3a1">&#39;content&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#6c7086;font-style:italic">// Now associate the data in the config, encoding the data safely
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>  uploadify_script_data[csrf_token] <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb">encodeURI</span>(csrf_param)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#6c7086;font-style:italic">// Associate the session information
</span></span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"></span>  uploadify_script_data[<span style="color:#a6e3a1">&#39;&lt;%= session_key %&gt;&#39;</span>] <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#39;&lt;%= cookies[session_key] %&gt;&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  $(<span style="color:#a6e3a1">&#39;#upload-photo&#39;</span>).uploadify({
</span></span><span style="display:flex;"><span>    script <span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;/photos/&#39;</span>,
</span></span><span style="display:flex;"><span>    scriptData <span style="color:#89dceb;font-weight:bold">:</span> uploadify_script_data
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span><span style="color:#89dceb;font-weight:bold">&lt;</span><span style="color:#f38ba8">/script&gt;</span>
</span></span></code></pre></div><p>Now that we have the session cookie data being passed with the request we&rsquo;ll need to move forward and have that information set with the headers of the Flash request, enter Middleware.</p>
<h3>Rack Middleware</h3>
<p>In order to get the session information to be associated with the request correctly we&rsquo;ll need to create some custom middleware which will intercept the requests from Flash and inject the cookie information into the header.</p>
<p>Some middleware has been around for accomplishing this but most are not compatible with Rails 3.  I&rsquo;ve posted a Gist on Github which has a working middleware for Rails 3 which I found on <a href="http://metautonomo.us/2010/07/09/uploadify-and-rails-3/">metautonomo.us</a> which we can use to accomplish our task.</p>
<p>Create a file in app/middleware/flash_session_cookie_middleware.rb creating the middleware directory if it doesn&rsquo;t already exist and add the following code in it:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#89dceb">require</span> <span style="color:#a6e3a1">&#39;rack/utils&#39;</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">FlashSessionCookieMiddleware</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">initialize</span>(app, session_key <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#39;_session_id&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@app</span> <span style="color:#89dceb;font-weight:bold">=</span> app
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@session_key</span> <span style="color:#89dceb;font-weight:bold">=</span> session_key
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">call</span>(env)
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">if</span> env<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">&#39;HTTP_USER_AGENT&#39;</span><span style="color:#89dceb;font-weight:bold">]</span> <span style="color:#89dceb;font-weight:bold">=~</span> <span style="color:#94e2d5">/^(Adobe|Shockwave) Flash/</span>
</span></span><span style="display:flex;"><span>      req <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Rack</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Request</span><span style="color:#89dceb;font-weight:bold">.</span>new(env)
</span></span><span style="display:flex;"><span>      env<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">&#39;HTTP_COOKIE&#39;</span><span style="color:#89dceb;font-weight:bold">]</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb;font-weight:bold">[</span> <span style="color:#f5e0dc">@session_key</span>,
</span></span><span style="display:flex;"><span>                             req<span style="color:#89dceb;font-weight:bold">.</span>params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#f5e0dc">@session_key</span><span style="color:#89dceb;font-weight:bold">]</span> <span style="color:#89dceb;font-weight:bold">].</span>join(<span style="color:#a6e3a1">&#39;=&#39;</span>)<span style="color:#89dceb;font-weight:bold">.</span>freeze <span style="color:#cba6f7">unless</span> req<span style="color:#89dceb;font-weight:bold">.</span>params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#f5e0dc">@session_key</span><span style="color:#89dceb;font-weight:bold">].</span>nil?
</span></span><span style="display:flex;"><span>      env<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">&#39;HTTP_ACCEPT&#39;</span><span style="color:#89dceb;font-weight:bold">]</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#34;</span><span style="color:#a6e3a1">#{</span>req<span style="color:#89dceb;font-weight:bold">.</span>params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">&#39;_http_accept&#39;</span><span style="color:#89dceb;font-weight:bold">]</span><span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">&#34;</span><span style="color:#89dceb;font-weight:bold">.</span>freeze <span style="color:#cba6f7">unless</span> req<span style="color:#89dceb;font-weight:bold">.</span>params<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">&#39;_http_accept&#39;</span><span style="color:#89dceb;font-weight:bold">].</span>nil?
</span></span><span style="display:flex;"><span>    <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@app</span><span style="color:#89dceb;font-weight:bold">.</span>call(env)
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>Once you&rsquo;ve created that file we&rsquo;ll need to add the middleware directory to our autoload&rsquo;s path if it isn&rsquo;t already. You can do this by modifying you&rsquo;re config/application.rb file and add the following in  the autoload section:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># Custom directories with classes and modules you want to be autoloadable.</span>
</span></span><span style="display:flex;"><span><span style="color:#6c7086;font-style:italic"># config.autoload_paths += %W(#{config.root}/</span>
</span></span><span style="display:flex;"><span>config<span style="color:#89dceb;font-weight:bold">.</span>autoload_paths <span style="color:#89dceb;font-weight:bold">+=</span> <span style="color:#a6e3a1">%W(</span><span style="color:#a6e3a1">#{</span>config<span style="color:#89dceb;font-weight:bold">.</span>root<span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">/app/middleware/)</span>
</span></span></code></pre></div><p>Once that&rsquo;s done we have one more step to do, inserting the middleware so that it will go to work when we need it.</p>
<p>Place the following code in config/initializers/session_store.rb</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>config<span style="color:#89dceb;font-weight:bold">.</span>middleware<span style="color:#89dceb;font-weight:bold">.</span>insert_before(
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">ActionDispatch</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Session</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">CookieStore</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">FlashSessionCookieMiddleware</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>config<span style="color:#89dceb;font-weight:bold">.</span>session_options<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:key</span><span style="color:#89dceb;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h4 id="update">Update</h4>
<p>If you are using ActiveRecord based session storage instead of cookie based session storage you will need the following intializer instead:</p>
<div class="highlight"><pre tabindex="0" style="color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:2;-o-tab-size:2;tab-size:2;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#f9e2af">SampleApp</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Application</span><span style="color:#89dceb;font-weight:bold">.</span>config<span style="color:#89dceb;font-weight:bold">.</span>session_store <span style="color:#a6e3a1">:active_record_store</span>, <span style="color:#a6e3a1">:key</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;_uploader_session&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>config<span style="color:#89dceb;font-weight:bold">.</span>middleware<span style="color:#89dceb;font-weight:bold">.</span>insert_before(
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>config<span style="color:#89dceb;font-weight:bold">.</span>session_store,
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">FlashSessionCookieMiddleware</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">Rails</span><span style="color:#89dceb;font-weight:bold">.</span>application<span style="color:#89dceb;font-weight:bold">.</span>config<span style="color:#89dceb;font-weight:bold">.</span>session_options<span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">:key</span><span style="color:#89dceb;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Thanks to reader <a href="http:/www.twitter.com/fromthought2web" target="_blank">@fromthought2web</a> for sharing his findings!</p>
<p>With all this in place you should now be ready to successfully upload files via Flash with your Rails project.</p>
<p>Good luck!</p>
<h3 id="updates">Updates</h3>
<dl>
	<dt>2012-04-03</dt>
	<dd>Included sample middleware when using ActiveRecord for session store, thanks to <a href="http://www.twitter.com/fromthought2web" target="_blank">@fromthought2web</a> for sharing his finds.</dd>
<pre><code>&lt;dt&gt;2010-08-26&lt;/dt&gt;
&lt;dd&gt;Resolved issue in code sample where session_key_name was being reference instead of session_key. Original sample updated&lt;/dd&gt;
&lt;dd&gt;`uploadify_script_data['&lt;%= session_key %&gt;'] = '&lt;%= cookies[session_key] %&gt;';`&lt;/dd&gt;
</code></pre>
</dl>
]]></content:encoded></item><item><title>About iPhone Web Applications</title><link>https://www.damiangalarza.com/posts/2009-07-27-about-iphone-web-applications/</link><pubDate>Mon, 27 Jul 2009 13:53:37 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2009-07-27-about-iphone-web-applications/</guid><content:encoded><![CDATA[<p>Let me start by this post by specifying that it actually originated from another post that I’m working on right now, but while writing it I went off on quite a tangent that I decided it was time to make it into a completely separate post, and start off with it. So this is part one of an iPhone web application piece that I am working on. While I am it, let me point out what the scope of this article is. This article is not about how to write an iPhone web application. It does not go into details on the syntax or use of technologies for the iPhone. It does explain some of the misconceptions between iPhone applications, iPhone targeted websites, and finally, iPhone Web Applications. I do plan to write more about the iPhone web application technologies and provide some examples and working code, but that is all currently beyond the scope of this article.</p>
<p>While researching on the topic over the last few weeks, I discovered what I believe is a misconception of an iPhone web application, and my goal is to hopefully, clear things up.</p>
<p>Let’s start by defining some of the possible ways of targeting an iPhone user, which actually diverges into two separate categories of its own.  The first and probably the most popular way of targeting an iPhone user is through an iPhone app, sold through the iTunes App store. Coding and compiling Objective C/Cocoa based native iPhone applications.</p>
<p>Yet there are still other ways of creating a custom user experience for a user on the iPhone.</p>
<h2>Mobile Web Sites and Web Applications</h2>
<p>With Smart Phones becoming so widespread companies are working harder towards providing users with access to their websites with mobile phone designed websites.</p>
<p>WAP (Wireless Application Protocol) sites have been around for several years; the WAP was established in 1998 and was created for less powerful, smaller phones that had limited web browsers. Today’s smart phones, however, are becoming more and more powerful and now provide users with a fully fledged web browser such as the iPhone’s mobile Safari.</p>
<p>So with this in mind, websites have two options. Leaving things alone and letting the phone’s browser do the work, which may work well in some cases; or create a smart phone targeted version of their website. One of the biggest issues though with leaving a website as is for mobile device users is the bandwidth that a user has. Sure, the page may render correctly and work just fine, but that doesn't mean it may not take a while to load all the resources used on the page. Many of those same resources just aren't needed on the mobile device.</p>
<p>Through the power of HTML 5, CSS 3, Javascript and CSS Webkit extensions a developer has incredible tools at their disposal to work with in order to make a mobile friendly page.</p>
<h2>Webkit</h2>
<p>One of the things I am most impressed with so far is the power that CSS3 and webkit extensions provide. More specifically, CSS based gradients, shadows, and animations. These are really amazing pieces of technology allowing to cut down on bandwidth issues created from using images for certain elements just because of a gradient or other effect, or eliminating the need of Flash or special javascript libraries for animations.</p>
<p>Let's take a closer look at the benefits of using CSS gradients to cut down on the number of images that are required to download. Perhaps you have a consistent gradient used on several buttons on a site. If you were to use images, you would have to re-cut each variation of button, which leads to longer page loads which is a major concern when working on a mobile device. Now with a CSS gradient, we can define it as a single class and apply it to any buttons that use the gradient, making much better use of our resources.</p>
<p>Finally, with these tools in hand we can diverge into two sections, the general purpose mobile website, or an iPhone web application. Now by no means do I mean to downplay the power of the mobile website, I simply want to talk about something that’s less known, and that’s the iPhone Web Application.</p>
<h2>So what is an iPhone Web Application</h2>
<p>An iPhone Web Application in its simplest form is actually a website which is created with the same technologies web developers are already familiar with, but are created with the goal of emulating a native application on the iPhone.</p>
<p>HTML5, CSS3, Javascript, and CSS Webkit extensions provide a developer with rich tools to create power applications for an iPhone. Users can bookmark a web page to their home screen and right there, they can see one of the first signs of a web application, a custom home screen icon. Just like the application icon created for the home screen for a native iPhone application, through the use of Meta tags a developer can define a custom icon to be shown on the home screen for their web application. Furthermore, the developer can define another Meta tag which can hide the whole Safari UI including the address bar, and the navigation bar at the bottom of the screen. These are only some of the simpler things a developer can do.</p>
<p>Through javascript alone a developer has access to many of the events that an iPhone application developer would have, such as touch events, gesture events orientation changes, viewport settings and more.</p>
<p>Furthermore, the local storage engine, powered by SQLite, allows local databases to be created from a web application, which can be accessed through javascript. A popular example of local database storage is Gmail's offline mode, which uses the local storage to keep emails stored locally.</p>
<p>Not only can we create a local database on the user’s phone, we can work with more powerful caching functionality through the use of Manifest files which we can use to define a set of files to cache on the phone which can then be accessed later if the user tries to access the web application without an internet connection. So a developer can specify to cache CSS files and images to make sure the user enjoys the same UI experience while offline just like a native application. Manifest caches and the SQLite local databases are what I consider some of the most important tools at a developer's belt when writing an iPhone web application because it really blurs the line between an iPhone application and an iPhone web application.</p>
<p>Stay tuned as I plan to get into some more specifics relating to all aspects of iPhone web application development.</p>]]></content:encoded></item></channel></rss>