<?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>Ruby on Damian Galarza | Software Engineering &amp; AI Consulting</title><link>https://www.damiangalarza.com/tags/ruby/</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/ruby/feed.xml" rel="self" type="application/rss+xml"/><item><title>Ruby LSP Now Has Official Claude Code Support</title><link>https://www.damiangalarza.com/posts/2026-03-13-ruby-lsp-claude-code/</link><pubDate>Fri, 13 Mar 2026 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2026-03-13-ruby-lsp-claude-code/</guid><description>Ruby LSP now has official Claude Code support. Install the plugin to give Claude go to definition, find references, and call hierarchy across your Ruby project.</description><content:encoded><![CDATA[<p>Up until now, Claude Code has understood your Ruby codebase the same way grep does. It reads files, searches for patterns, and infers structure from text. That works surprisingly well for many tasks. But it has a ceiling.</p>
<p>Ask Claude to explain a Rails model and it reads through the file, piecing together what the associations mean, inferring what the enum values map to, and hoping it catches all the scopes. It&rsquo;s reading text and making educated guesses about structure. Ask it to find all callers of a private method before you delete it and it&rsquo;s doing a best-effort text scan that can miss dynamic calls or alias references.</p>
<p>That ceiling just got raised. Ruby LSP now has official support in Claude Code.</p>
<h2 id="what-is-a-language-server-protocol">What Is a Language Server Protocol</h2>
<p>LSP is a standard that separates language intelligence from the editor. Before LSP, every editor had to implement its own understanding of every language. After LSP, you write the intelligence once as a language server, and any editor that speaks the protocol gets the benefits.</p>
<p>The language server sits alongside your code and maintains a live semantic model of your project. It tracks definitions, references, and symbol relationships across your entire project. When your editor asks &ldquo;where is this method defined?&rdquo; or &ldquo;what are all the callers of this function?&rdquo;, the server answers in milliseconds with accurate, complete information.</p>
<p><a href="https://github.com/Shopify/ruby-lsp">Ruby LSP</a> is Shopify&rsquo;s language server for Ruby. It integrates with Standard Ruby for diagnostics. In codebases that use <a href="https://sorbet.org/">Sorbet</a> or <a href="https://github.com/soutaro/steep">Steep</a>, those tools run as separate language servers and provide type information independently of Ruby LSP. Beyond the core server, there&rsquo;s a growing community of add-on developers building extensions for it. <a href="https://github.com/johansenja/awesome-ruby-lsp">awesome-ruby-lsp</a> is a good starting point if you want to see what&rsquo;s available.</p>
<h2 id="what-you-get">What You Get</h2>
<p>With Ruby LSP connected, Claude gains semantic code intelligence instead of relying on text search. Here&rsquo;s what that means in practice.</p>
<p><strong>Go to definition.</strong> Claude can jump directly to where a method, class, or constant is defined, including inside gems. No more manually tracking down gem source paths.</p>
<p><strong>Find all references.</strong> Before a refactor, Claude can ask the language server for every reference to a symbol across your entire project. Dynamic calls included, because the LSP understands Ruby&rsquo;s metaprogramming patterns well enough to handle common cases.</p>
<p><strong>Call hierarchy.</strong> Claude can trace the full call chain for a method, both incoming callers and outgoing calls, across your entire project. That&rsquo;s useful when you need to understand the impact of a change before making it.</p>
<p><strong>Hover documentation.</strong> Claude can pull the YARD documentation for any method without having to search for it manually.</p>
<p>The difference shows up most in refactoring. When you ask Claude to rename a method or change a signature, it can now ask the LSP for the complete reference list before making a single change. That&rsquo;s the difference between a confident refactor and a hopeful one.</p>
<h2 id="installing-ruby-lsp-in-claude-code">Installing Ruby LSP in Claude Code</h2>
<p>You&rsquo;ll need Ruby LSP installed as a gem first:</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>gem install ruby-lsp
</span></span></code></pre></div><p>If you&rsquo;re working in a bundled project, add it to your <code>Gemfile</code> in the development group:</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>group <span style="color:#a6e3a1">:development</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  gem <span style="color:#a6e3a1">&#34;ruby-lsp&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>Then install the plugin from inside Claude Code:</p>
<pre tabindex="0"><code>/plugin install ruby-lsp@claude-plugins-official
</code></pre><p>That&rsquo;s it. The next time you start a Claude Code session in a Ruby project, the LSP server starts alongside it. You don&rsquo;t need to configure anything. Install it once and it works.</p>
<h2 id="what-changes-in-practice">What Changes in Practice</h2>
<p>Here&rsquo;s what it looks like in a real session. I asked Claude about a model in a Rails app:</p>
<pre tabindex="0"><code>❯ Use ruby-lsp and tell me about the membership model

⏺ Searched for 1 pattern (ctrl+o to expand)

⏺ LSP(operation: &#34;documentSymbol&#34;, file: &#34;app/models/membership.rb&#34;)
  ⎿  Found 6 symbols (ctrl+o to expand)

⏺ Read 1 file (ctrl+o to expand)

⏺ Now let me get hover info on a few key symbols:

⏺ LSP(operation: &#34;hover&#34;, symbol: &#34;enum&#34;, in: &#34;app/models/membership.rb&#34;)
  ⎿  Hover info available (ctrl+o to expand)

⏺ LSP(operation: &#34;findReferences&#34;, symbol: &#34;Membership&#34;, in: &#34;app/models/membership.rb&#34;)
  ⎿  Found 24 references across 12 files (ctrl+o to expand)

⏺ Here&#39;s a summary of the Membership model:

  app/models/membership.rb

  A join model representing a user&#39;s membership in a tenant (organization).

  Key traits:
  - acts_as_tenant :tenant — multi-tenant scoped via ActsAsTenant
  - belongs_to :tenant, belongs_to :user
  - Roles (integer enum): member (1), admin (2), owner (3)
  - scope :owners — filters members with the owner role
  - Validations: user_id must be unique per tenant (one membership per user per org), role must be present

  Used in 12 files across:
  - Controllers: invitations, memberships, organization settings
  - Services: AcceptInvitation, ConfirmUserEmail
  - Specs: model spec, tenant isolation spec, system specs

  The model is minimal — it&#39;s purely a pivot between User and Tenant with role-based access control baked in.
</code></pre><p>That&rsquo;s a thorough summary. But I was curious what actually came from the LSP versus what Claude found by reading the file. So I asked:</p>
<pre tabindex="0"><code>❯ What information did you get from the lsp vs your own search

⏺ Good question. Here&#39;s the breakdown:

  From LSP:
  - documentSymbol — gave me the class name, method names, and line numbers without reading the file
    (associations, scope, validations)
  - hover on enum — confirmed it&#39;s ActiveRecord::Enum#enum with a link to the gem source definition
  - findReferences — found all 24 references across 12 files without me doing any grep search

  From my own file read (Read tool):
  - The actual source code content — the enum values (member: 1, admin: 2, owner: 3), the acts_as_tenant
    call, the uniqueness scope on the validation (scope: :tenant_id)

  In practice:
  - LSP gave me structure and cross-references (what symbols exist, where they&#39;re used project-wide)
  - The file read gave me content (the actual values and implementation details)

  The documentSymbol result notably omitted the acts_as_tenant call and the enum values — it only surfaced
  Rails macro calls it recognized as meaningful symbols. So LSP alone would have left me with an incomplete
  picture of the model.
</code></pre><p>That second turn is the honest answer. LSP and file reads are complementary. The LSP gives Claude structure and cross-references instantly: what symbols exist, where they&rsquo;re called across the project, without spending context window tokens on file searches. The file read fills in the content: actual values, implementation details, anything the symbol index doesn&rsquo;t capture. Together they produce a more complete picture than either alone.</p>
<p>That kind of structural understanding also changes how refactoring works. When you ask Claude to rename a class or change how something is called across the project, it queries the LSP for the complete reference list before making a single change. It isn&rsquo;t guessing at usage patterns. It knows them.</p>
<p>One thing worth knowing: Ruby LSP works well without a type system. In my testing without Sorbet or Steep, the heuristic inference still produced accurate results for associations, method definitions, and references. If your codebase does use Sorbet or Steep, those type checkers can run alongside the LSP to improve accuracy further, but they&rsquo;re not required to get value from this integration.</p>
<p>Text search got Claude surprisingly far. Semantic understanding gets it the rest of the way.</p>
<p>If you want help getting Claude Code dialed in for your Ruby workflow, whether that&rsquo;s 1:1 or across your team, <a href="/claude-code/">take a look at how I work with developers and teams</a>.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a href="https://github.com/Shopify/ruby-lsp">Ruby LSP on GitHub</a></li>
<li><a href="https://shopify.github.io/ruby-lsp/">Ruby LSP documentation</a></li>
<li><a href="https://github.com/johansenja/awesome-ruby-lsp">awesome-ruby-lsp</a></li>
<li><a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol specification</a></li>
<li><a href="https://code.claude.com/docs/en/discover-plugins">Claude Code plugins documentation</a></li>
</ul>
]]></content:encoded></item><item><title>How to Fix LLM Date and Time Issues in Production</title><link>https://www.damiangalarza.com/posts/2026-01-07-llm-date-time-context-production/</link><pubDate>Wed, 07 Jan 2026 00:00:00 -0500</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2026-01-07-llm-date-time-context-production/</guid><description>LLMs don't have access to the current date, causing issues in time-based analysis. Here's how to fix date and time handling in production LLM systems with explicit context.</description><content:encoded><![CDATA[<p>I was recently working on a project to generate summarized reporting using the Anthropic Claude API. What looked good at first eventually revealed some odd behavior in production. This post explains the problem we ran into and how we resolved it.</p>
<h2 id="the-problem">The Problem</h2>
<p>The following is adapted from a real production system but generalized for this post.</p>
<p>Take a theoretical SaaS application. The goal: generate a report of users who have low activity and identify those who are likely to churn. Let&rsquo;s take a look at an example prompt:</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;anthropic&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">require</span> <span style="color:#a6e3a1">&#39;json&#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">ChurnRiskAnalyzer</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">SYSTEM_PROMPT</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb;font-weight:bold">&lt;&lt;~</span><span style="color:#f9e2af">PROMPT</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">You</span> are a customer success analyst<span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Your</span> job is to analyze user engagement
</span></span><span style="display:flex;"><span>    data <span style="color:#89dceb;font-weight:bold">and</span> identify customers at risk of churning<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:#f9e2af">When</span> analyzing users, identify which users are recently converted <span style="color:#f9e2af">AND</span> at high
</span></span><span style="display:flex;"><span>    risk of churning due to low engagement<span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Pay</span> special attention <span style="color:#a6e3a1">to</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">-</span> <span style="color:#f9e2af">Login</span> frequency relative to their plan type
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">-</span> <span style="color:#f9e2af">Feature</span> adoption breadth
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">-</span> <span style="color:#f9e2af">Time</span> since trial conversion
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">For</span> each user, <span style="color:#a6e3a1">state</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#fab387">1</span><span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Days</span> since conversion
</span></span><span style="display:flex;"><span>    <span style="color:#fab387">2</span><span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Whether</span> they qualify as a <span style="color:#a6e3a1">&#34;recent conversion&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#fab387">3</span><span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Your</span> churn risk assessment <span style="color:#89dceb;font-weight:bold">and</span> reasoning
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">PROMPT</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">initialize</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@client</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">Anthropic</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Client</span><span style="color:#89dceb;font-weight:bold">.</span>new
</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">analyze</span>(low_engagement_users)
</span></span><span style="display:flex;"><span>    user_prompt <span style="color:#89dceb;font-weight:bold">=</span> build_user_prompt(low_engagement_users)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    response <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f5e0dc">@client</span><span style="color:#89dceb;font-weight:bold">.</span>messages<span style="color:#89dceb;font-weight:bold">.</span>create(
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">model</span>: <span style="color:#a6e3a1">&#39;claude-sonnet-4-20250514&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">max_tokens</span>: <span style="color:#fab387">1024</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#89dceb">system</span>: <span style="color:#f9e2af">SYSTEM_PROMPT</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">messages</span>: <span style="color:#89dceb;font-weight:bold">[</span>
</span></span><span style="display:flex;"><span>        { <span style="color:#a6e3a1">role</span>: <span style="color:#a6e3a1">&#39;user&#39;</span>, <span style="color:#a6e3a1">content</span>: user_prompt }
</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></span><span style="display:flex;"><span>    response<span style="color:#89dceb;font-weight:bold">.</span>content<span style="color:#89dceb;font-weight:bold">.</span>first<span style="color:#89dceb;font-weight:bold">.</span>text
</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">build_user_prompt</span>(users)
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">&lt;&lt;~</span><span style="color:#f9e2af">PROMPT</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f9e2af">Analyze</span> the following low<span style="color:#89dceb;font-weight:bold">-</span>engagement users from the past <span style="color:#fab387">30</span> <span style="color:#a6e3a1">days</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#6c7086;font-style:italic">#{JSON.pretty_generate(users)}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">PROMPT</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"># Example usage</span>
</span></span><span style="display:flex;"><span>analyzer <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">ChurnRiskAnalyzer</span><span style="color:#89dceb;font-weight:bold">.</span>new
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>low_engagement_users <span style="color:#89dceb;font-weight:bold">=</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:#a6e3a1">engagement_id</span>: <span style="color:#a6e3a1">&#39;eng_001&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">last_login</span>: <span style="color:#a6e3a1">&#39;2025-12-28&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">logins_past_30_days</span>: <span style="color:#fab387">2</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">features_used</span>: <span style="color:#89dceb;font-weight:bold">[</span><span style="color:#a6e3a1">&#39;dashboard&#39;</span><span style="color:#89dceb;font-weight:bold">]</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">user</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#89dceb">id</span>: <span style="color:#a6e3a1">&#39;usr_4821&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">email</span>: <span style="color:#a6e3a1">&#39;sarah@acme.co&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">plan</span>: <span style="color:#a6e3a1">&#39;pro&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">trial_converted_at</span>: <span style="color:#a6e3a1">&#39;2025-02-15&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">company</span>: <span style="color:#a6e3a1">&#39;Acme Corp&#39;</span>
</span></span><span style="display:flex;"><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:#a6e3a1">engagement_id</span>: <span style="color:#a6e3a1">&#39;eng_002&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">last_login</span>: <span style="color:#a6e3a1">&#39;2025-12-20&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">logins_past_30_days</span>: <span style="color:#fab387">1</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">features_used</span>: <span style="color:#89dceb;font-weight:bold">[]</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">user</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#89dceb">id</span>: <span style="color:#a6e3a1">&#39;usr_9174&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">email</span>: <span style="color:#a6e3a1">&#39;mike@newstartup.io&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">plan</span>: <span style="color:#a6e3a1">&#39;pro&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">trial_converted_at</span>: <span style="color:#a6e3a1">&#39;2025-12-01&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e3a1">company</span>: <span style="color:#a6e3a1">&#39;NewStartup&#39;</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:#89dceb;font-weight:bold">]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">puts</span> analyzer<span style="color:#89dceb;font-weight:bold">.</span>analyze(low_engagement_users)
</span></span></code></pre></div><p>Let&rsquo;s assume the date is December 29th, 2025.</p>
<p>In this example we have two users with low engagement. <code>sarah@acme.co</code> converted back in February 2025 and has 2 logins in the past 30 days. <code>mike@newstartup.io</code> converted December 1st, 2025 and has just 1 login.</p>
<p>The expected behavior: flag Mike as at risk since he converted recently and has minimal engagement. What actually happened: both Mike and Sarah were flagged. Let&rsquo;s look at why.</p>
<h2 id="lack-of-guidance">Lack of guidance</h2>
<p>In the first version of our system prompt, we mention that we want to include recent conversions—but we never define what &ldquo;recent&rdquo; means. This leaves it up to the model to decide, which leads to non-deterministic and confusing results. The fix is to provide explicit guidance:</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">ChurnRiskAnalyzer</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">SYSTEM_PROMPT</span> <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb;font-weight:bold">&lt;&lt;~</span><span style="color:#f9e2af">PROMPT</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">You</span> are a customer success analyst<span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Your</span> job is to analyze user engagement
</span></span><span style="display:flex;"><span>    data <span style="color:#89dceb;font-weight:bold">and</span> identify customers at risk of churning<span style="color:#89dceb;font-weight:bold">.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    A <span style="color:#a6e3a1">&#34;recent conversion&#34;</span> is defined as a user who converted from
</span></span><span style="display:flex;"><span>    trial to paid within the past <span style="color:#fab387">30</span> days<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:#f9e2af">When</span> analyzing users, identify which users are recently converted <span style="color:#f9e2af">AND</span> at high
</span></span><span style="display:flex;"><span>    risk of churning due to low engagement<span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Pay</span> special attention <span style="color:#a6e3a1">to</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">-</span> <span style="color:#f9e2af">Login</span> frequency relative to their plan type
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">-</span> <span style="color:#f9e2af">Feature</span> adoption breadth
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">-</span> <span style="color:#f9e2af">Time</span> since trial conversion
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">For</span> each user, <span style="color:#a6e3a1">state</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#fab387">1</span><span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Days</span> since conversion
</span></span><span style="display:flex;"><span>    <span style="color:#fab387">2</span><span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Whether</span> they qualify as a <span style="color:#a6e3a1">&#34;recent conversion&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#fab387">3</span><span style="color:#89dceb;font-weight:bold">.</span> <span style="color:#f9e2af">Your</span> churn risk assessment <span style="color:#89dceb;font-weight:bold">and</span> reasoning
</span></span><span style="display:flex;"><span><span style="color:#f9e2af">PROMPT</span>
</span></span></code></pre></div><p>Now the model has explicit guidance on what makes a &ldquo;recent conversion.&rdquo; But we can&rsquo;t stop here.</p>
<h2 id="providing-a-reference-date">Providing a reference date</h2>
<p>We&rsquo;ve updated the prompt to provide explicit guidance on what makes a recent conversion, but there&rsquo;s still one problem—the model doesn&rsquo;t know what the current date is. LLMs have no system clock access; they only know what you tell them. One way to resolve this is to provide the date as part of the prompt:</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">build_user_prompt</span>(users)
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">&lt;&lt;~</span><span style="color:#f9e2af">PROMPT</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f9e2af">Today</span><span style="color:#f38ba8">&#39;</span>s date <span style="color:#a6e3a1">is</span>: <span style="color:#6c7086;font-style:italic">#{Date.today.iso8601}.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#f9e2af">Analyze</span> the following low<span style="color:#89dceb;font-weight:bold">-</span>engagement users from the past <span style="color:#fab387">30</span> <span style="color:#a6e3a1">days</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#6c7086;font-style:italic">#{JSON.pretty_generate(users)}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">PROMPT</span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>Now the model has everything it needs for accurate reporting. Running this updated version correctly excludes Sarah, who signed up months ago, and flags only Mike.</p>
<h2 id="conclusion">Conclusion</h2>
<p>When working with LLMs and time-sensitive data:</p>
<ol>
<li><strong>Be explicit about definitions</strong> - Don&rsquo;t assume the model interprets terms like &ldquo;recent&rdquo; the same way you do.</li>
<li><strong>Always provide the current date</strong> - LLMs have no awareness of real-time; include today&rsquo;s date in your prompt.</li>
<li><strong>Test with edge cases</strong> - Run your prompts with data that spans different time periods to catch these issues early.</li>
</ol>
<p>These might seem like small details, but in production systems where accuracy matters, they make the difference between useful analysis and misleading results. Subtle errors like these erode trust quickly.</p>
<hr>
<p>Building LLM features into a Rails app and running into issues like this? My book <a href="/building-llm-applications/">Building LLM Applications in Rails</a> covers the patterns that hold up in production.</p>
]]></content:encoded></item><item><title>How Buoy Software is Redefining Rails Development with ViewComponents</title><link>https://www.damiangalarza.com/posts/2024-09-05-react-to-view-components/</link><pubDate>Thu, 05 Sep 2024 00:00:00 -0400</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2024-09-05-react-to-view-components/</guid><description>Discover how Buoy Software improved their Rails app by transitioning from React and GraphQL to ViewComponents. Learn about performance gains and simplified development.</description><content:encoded><![CDATA[<p>This post was originally posted on the <a href="https://buoy.blog/2024/09/05/react-to-view-components.html">Buoy Software blog</a>.</p>
<p>In the ever-evolving world of web development, choosing the right technology
stack is crucial for building efficient and maintainable applications. We moved away
from React and GraphQL, opting instead for traditional REST APIs and Rails-based
views, leveraging the power of the <a href="https://viewcomponent.org/">ViewComponents gem</a>.</p>
<h2 id="the-initial-tech-stack-react-and-graphql">The initial tech stack: React and GraphQL</h2>
<p>Buoy Software&rsquo;s journey began with a mobile application built using React Native
and powered by a GraphQL API in a Ruby on Rails application. The success of this
project led us to adopt similar technologies for our web application, resulting
in a React based front-end using webpacker. This approach allowed for code reuse
and consistency across platforms, which seemed like an ideal solution at the time.
For our small team, it was easier to maintain a shared pattern when moving
between the React Native application and our web application.</p>
<h2 id="buoy-hackathon-2023">Buoy Hackathon 2023</h2>
<p>Buoy is a fully remote company and we have an annual hackathon where we get the
entire company together to spend time together in person. This is typically 2-3
days where we work on various hackathon projects pitched by the team. One team
pitched an idea to move our stack away from React and GraphQL to Rails views.
One of the things we enjoyed about React though, was the component system for
building UIs.</p>
<h2 id="challenges-faced-with-react-and-graphql-in-rails">Challenges faced with React and GraphQL in Rails</h2>
<p>While starting off with GraphQL and React worked very well for our small team to
bootstrap our web application, over time we were presented with challenges that
made us rethink our approach.</p>
<h3 id="inherent-complexity-within-the-application">Inherent complexity within the application</h3>
<p>Simple tasks which could easily be accomplished in a traditional Ruby on Rails
application started to become more and more complex. A simple feature which might
only need a REST controller and some views in a traditional Ruby on Rails application
would instead need GraphQL types, fields, mutations and resolvers. We leveraged
Typescript on the front-end which meant we needed to generate types for all of
our queries. Then we needed to create complex React components to bring the
whole thing together.</p>
<p>This complexity also appears when attempting to debug any issues that might
arise. With so many places to look across the stack, finding the root cause of a
problem is not straightforward.</p>
<h3 id="the-illusion-of-sharing-an-api">The illusion of sharing an API</h3>
<p>When kicking things off, we had grand visions of a single source of truth
GraphQL API that powered our donor&rsquo;s mobile app as well as our donor management
system in one single API. Time would prove otherwise. As it turned out APIs that
the mobile application relied on were fairly different from those that the donor
management system required. The ones that were similar required vastly different
permission scopes. We ended up spending more time trying to build a shared API
rather than building dedicated APIs as needed.</p>
<h3 id="complexity-in-onboarding-new-team-members">Complexity in onboarding new team members</h3>
<p>At Buoy we believe in hiring full-stack software engineers. This proved more
difficult with our complex framework for building out front-ends. While this
wasn&rsquo;t an impossible task to get engineers up and running, it has proven to be
a learning curve for sure.</p>
<h2 id="understanding-viewcomponents-in-ruby-on-rails">Understanding ViewComponents in Ruby on Rails</h2>
<h3 id="what-are-viewcomponents">What are ViewComponents?</h3>
<p>ViewComponents is a framework for building reusable, testable, and encapsulated
view components in Ruby on Rails. Inspired by similar concepts in modern JavaScript
frameworks such as React, ViewComponents allows developers to create modular,
object-oriented views that can be easily composed and reused across an application.
Think React components written in Ruby and used in Rails views.</p>
<p>To get started install the gem via <code>gem install view_components</code> or add it to
your Bundle file. Now, take the following React component:</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-js" data-lang="js"><span style="display:flex;"><span><span style="color:#cba6f7">import</span> React from <span style="color:#a6e3a1">&#39;react&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">import</span> PropTypes from <span style="color:#a6e3a1">&#39;prop-types&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">const</span> VARIANT_STYLES <span style="color:#89dceb;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  info<span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;bg-blue-100 text-blue-800 border-blue-500&#39;</span>,
</span></span><span style="display:flex;"><span>  success<span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;bg-green-100 text-green-800 border-green-500&#39;</span>,
</span></span><span style="display:flex;"><span>  warning<span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;bg-yellow-100 text-yellow-800 border-yellow-500&#39;</span>,
</span></span><span style="display:flex;"><span>  error<span style="color:#89dceb;font-weight:bold">:</span> <span style="color:#a6e3a1">&#39;bg-red-100 text-red-800 border-red-500&#39;</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">const</span> Banner <span style="color:#89dceb;font-weight:bold">=</span> ({ variant <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#39;info&#39;</span>, message, onClose }) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">return</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">&lt;</span>div className<span style="color:#89dceb;font-weight:bold">=</span>{<span style="color:#a6e3a1">`p-4 border-l-4 </span><span style="color:#a6e3a1">${</span>VARIANT_STYLES[variant]<span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">`</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>div className<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;flex justify-between items-center&#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>p<span style="color:#89dceb;font-weight:bold">&gt;</span>{message}<span style="color:#89dceb;font-weight:bold">&lt;</span><span style="color:#f38ba8">/p&gt;</span>
</span></span><span style="display:flex;"><span>        {onClose <span style="color:#89dceb;font-weight:bold">&amp;&amp;</span> (
</span></span><span style="display:flex;"><span>          <span style="color:#89dceb;font-weight:bold">&lt;</span>button
</span></span><span style="display:flex;"><span>            onClick<span style="color:#89dceb;font-weight:bold">=</span>{onClose}
</span></span><span style="display:flex;"><span>            className<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;text-gray-500 hover:text-gray-700&#34;</span>
</span></span><span style="display:flex;"><span>            aria<span style="color:#89dceb;font-weight:bold">-</span>label<span style="color:#89dceb;font-weight:bold">=</span><span style="color:#a6e3a1">&#34;Close&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#89dceb;font-weight:bold">&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#89dceb;font-weight:bold">&amp;</span>times;
</span></span><span style="display:flex;"><span>          <span style="color:#89dceb;font-weight:bold">&lt;</span><span style="color:#f38ba8">/button&gt;</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">/div&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#89dceb;font-weight:bold">&lt;</span><span style="color:#f38ba8">/div&gt;</span>
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Banner.propTypes <span style="color:#89dceb;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  variant<span style="color:#89dceb;font-weight:bold">:</span> PropTypes.oneOf([<span style="color:#a6e3a1">&#39;info&#39;</span>, <span style="color:#a6e3a1">&#39;success&#39;</span>, <span style="color:#a6e3a1">&#39;warning&#39;</span>, <span style="color:#a6e3a1">&#39;error&#39;</span>]),
</span></span><span style="display:flex;"><span>  message<span style="color:#89dceb;font-weight:bold">:</span> PropTypes.string.isRequired,
</span></span><span style="display:flex;"><span>  onClose<span style="color:#89dceb;font-weight:bold">:</span> PropTypes.func,
</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">export</span> <span style="color:#cba6f7">default</span> Banner;
</span></span></code></pre></div><p>To replicate this with ViewComponents we&rsquo;ll want the following. First we&rsquo;ll want
to create a new directory within our <code>app</code> folder called <code>app/components</code>. The
common way to build components is to have 2 files, one is the class to hold any
logic behind our component and the other is the erb template. For the example
above we&rsquo;ll have 2 new files.</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/components/banner_component.rb</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">class</span> <span style="color:#f9e2af">BannerComponent</span> <span style="color:#89dceb;font-weight:bold">&lt;</span> <span style="color:#f9e2af">ViewComponent</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">Base</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f9e2af">VARIANT_STYLES</span> <span style="color:#89dceb;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">info</span>: <span style="color:#a6e3a1">&#34;bg-blue-100 text-blue-800 border-blue-500&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">success</span>: <span style="color:#a6e3a1">&#34;bg-green-100 text-green-800 border-green-500&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">warning</span>: <span style="color:#a6e3a1">&#34;bg-yellow-100 text-yellow-800 border-yellow-500&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e3a1">error</span>: <span style="color:#a6e3a1">&#34;bg-red-100 text-red-800 border-red-500&#34;</span>
</span></span><span style="display:flex;"><span>  }<span style="color:#89dceb;font-weight:bold">.</span>freeze
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#cba6f7">def</span> <span style="color:#89b4fa">initialize</span>(<span style="color:#a6e3a1">variant</span>: <span style="color:#a6e3a1">:info</span>, <span style="color:#a6e3a1">message</span>:, <span style="color:#a6e3a1">closable</span>: <span style="color:#cba6f7">false</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@variant</span> <span style="color:#89dceb;font-weight:bold">=</span> variant
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@message</span> <span style="color:#89dceb;font-weight:bold">=</span> message
</span></span><span style="display:flex;"><span>    <span style="color:#f5e0dc">@closable</span> <span style="color:#89dceb;font-weight:bold">=</span> closable
</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">variant_class</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">VARIANT_STYLES</span><span style="color:#89dceb;font-weight:bold">[</span><span style="color:#f5e0dc">@variant</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></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">attr_reader</span> <span style="color:#a6e3a1">:variant</span>, <span style="color:#a6e3a1">:message</span>, <span style="color:#a6e3a1">:closable</span>
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>Paired with an erb template in <code>app/components/banner_component.html.erb</code></p>
<pre tabindex="0"><code class="language-erb" data-lang="erb">&lt;!-- app/components/banner_component.html.erb --&gt;
&lt;div class=&#34;p-4 border-l-4 &lt;%= variant_class %&gt;&#34;&gt;
  &lt;div class=&#34;flex justify-between items-center&#34;&gt;
    &lt;p&gt;&lt;%= message %&gt;&lt;/p&gt;
    &lt;% if closable %&gt;
      &lt;%= button_to &#34;&amp;times;&#34;.html_safe,
                    nil,
                    method: :get,
                    class: &#34;text-gray-500 hover:text-gray-700&#34;,
                    aria: { label: &#34;Close&#34; },
                    data: { action: &#34;click-&gt;banner#close&#34; } %&gt;
    &lt;% end %&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre><p>We can then render our component in a view like so:</p>
<pre tabindex="0"><code>&lt;%= render(BannerComponent.new(variant: :success, message: &#34;Operation completed successfully!&#34;, closable: true)) %&gt;
</code></pre><h3 id="testing-our-component">Testing our component</h3>
<p>To test our component we can utilize 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:#6c7086;font-style:italic"># spec/components/banner_component_spec.rb</span>
</span></span><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><span style="color:#f9e2af">RSpec</span><span style="color:#89dceb;font-weight:bold">.</span>describe <span style="color:#f9e2af">BannerComponent</span>, <span style="color:#a6e3a1">type</span>: <span style="color:#a6e3a1">:component</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>  it <span style="color:#a6e3a1">&#34;renders each variant with the appropriate CSS classes&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">BannerComponent</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">VARIANT_STYLES</span><span style="color:#89dceb;font-weight:bold">.</span>each <span style="color:#cba6f7">do</span> <span style="color:#89dceb;font-weight:bold">|</span>variant, expected_class<span style="color:#89dceb;font-weight:bold">|</span>
</span></span><span style="display:flex;"><span>      component <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">BannerComponent</span><span style="color:#89dceb;font-weight:bold">.</span>new(<span style="color:#a6e3a1">variant</span>: variant, <span style="color:#a6e3a1">message</span>: <span style="color:#a6e3a1">&#34;Test message&#34;</span>)
</span></span><span style="display:flex;"><span>      render_inline(component)
</span></span><span style="display:flex;"><span>      expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_css(<span style="color:#a6e3a1">&#34;div.border-l-4.</span><span style="color:#a6e3a1">#{</span>expected_class<span style="color:#a6e3a1">}</span><span style="color:#a6e3a1">&#34;</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>  it <span style="color:#a6e3a1">&#34;renders the message&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    message <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#34;This is a test message&#34;</span>
</span></span><span style="display:flex;"><span>    component <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">BannerComponent</span><span style="color:#89dceb;font-weight:bold">.</span>new(<span style="color:#a6e3a1">variant</span>: <span style="color:#a6e3a1">:info</span>, <span style="color:#a6e3a1">message</span>: message)
</span></span><span style="display:flex;"><span>    render_inline(component)
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_text(message)
</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>  it <span style="color:#a6e3a1">&#34;renders a close button when closable is true&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    component <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">BannerComponent</span><span style="color:#89dceb;font-weight:bold">.</span>new(<span style="color:#a6e3a1">variant</span>: <span style="color:#a6e3a1">:info</span>, <span style="color:#a6e3a1">message</span>: <span style="color:#a6e3a1">&#34;Test&#34;</span>, <span style="color:#a6e3a1">closable</span>: <span style="color:#cba6f7">true</span>)
</span></span><span style="display:flex;"><span>    render_inline(component)
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>to have_button(<span style="color:#a6e3a1">class</span>: <span style="color:#a6e3a1">&#34;text-gray-500 hover:text-gray-700&#34;</span>, <span style="color:#a6e3a1">text</span>: <span style="color:#a6e3a1">&#34;×&#34;</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>  it <span style="color:#a6e3a1">&#34;does not render a close button when closable is false&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    component <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">BannerComponent</span><span style="color:#89dceb;font-weight:bold">.</span>new(<span style="color:#a6e3a1">variant</span>: <span style="color:#a6e3a1">:info</span>, <span style="color:#a6e3a1">message</span>: <span style="color:#a6e3a1">&#34;Test&#34;</span>, <span style="color:#a6e3a1">closable</span>: <span style="color:#cba6f7">false</span>)
</span></span><span style="display:flex;"><span>    render_inline(component)
</span></span><span style="display:flex;"><span>    expect(page)<span style="color:#89dceb;font-weight:bold">.</span>not_to have_button(<span style="color:#a6e3a1">class</span>: <span style="color:#a6e3a1">&#34;text-gray-500 hover:text-gray-700&#34;</span>, <span style="color:#a6e3a1">text</span>: <span style="color:#a6e3a1">&#34;×&#34;</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>  it <span style="color:#a6e3a1">&#34;applies the correct variant class&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    component <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">BannerComponent</span><span style="color:#89dceb;font-weight:bold">.</span>new(<span style="color:#a6e3a1">variant</span>: <span style="color:#a6e3a1">:success</span>, <span style="color:#a6e3a1">message</span>: <span style="color:#a6e3a1">&#34;Test&#34;</span>)
</span></span><span style="display:flex;"><span>    expect(component<span style="color:#89dceb;font-weight:bold">.</span>variant_class)<span style="color:#89dceb;font-weight:bold">.</span>to eq(<span style="color:#a6e3a1">&#34;bg-green-100 text-green-800 border-green-500&#34;</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>Note our use of the <code>type: component</code> here.</p>
<h3 id="benefits-of-viewcomponents-over-react-components">Benefits of ViewComponents over React components</h3>
<ol>
<li>
<p>Server-side rendering: ViewComponents are rendered on the server, reducing the
client-side JavaScript payload and improving initial page load times.</p>
</li>
<li>
<p>Seamless integration: As a native Rails solution, ViewComponents integrates
smoothly with existing Rails patterns and helpers. No more jumping through hoops
fighting with a JavaScript build tool like webpacker for simple tasks.</p>
</li>
<li>
<p>Testability: ViewComponents can be unit tested in isolation, improving overall
code quality and maintainability using the tools our team was already comfortable
in, RSPec.</p>
</li>
</ol>
<h2 id="buoy-softwares-journey-to-adopting-viewcomponents">Buoy Software’s Journey to Adopting ViewComponents</h2>
<p>Transitioning an existing Rails application to ViewComponents required careful
planning. Our application had grown quite large by the time we decided to move
away from React so finding a way to incrementally make this change was of the
utmost importance.</p>
<p>Our team began by identifying key areas where React components could be replaced
with ViewComponents. At Buoy we leverage feature flags which were a great tool for
this. We began by identifying the lowest risk area to introduce our change and
began there. The team gradually refactored these components, ensuring that the
application’s functionality remained intact throughout the process.</p>
<p>To ensure we did not find ourselves in a migration that stopped feature development
for months we took an iterative strategy to roll out the changes. As we made
changes to the codebase to update existing features we took it as an opportunity
to do a migration to ViewComponents.</p>
<p>To further ensure value was being provided by this migration we paired it with a
redesign of our application which moved us to the
<a href="https://tailwindcss.com/">Tailwind</a> CSS framework. This
allowed us to get buy in from all stakeholders including leadership.</p>
<p>We are still not completely done with our migration, but we have found it to be
quite successful and are excited to finish our transition.</p>
<h2 id="advantages-of-traditional-rails-architecture">Advantages of Traditional Rails Architecture</h2>
<p>We&rsquo;ve already seen many advantages as a result of our move away from React to a
more traditional Rails application.</p>
<h3 id="simplified-development-workflow">Simplified development workflow</h3>
<p>Our initial hypothesis for this change was that we could drastically reduce
software development time for tasks by moving away from React to traditional
Rails views. We can confidently say that this has been true. Features that might
have taken weeks now take days. We no longer need to worry about ensuring we
have experts in both React and Rails.</p>
<p>With a unified codebase, we no longer need to worry about about context switching
between Ruby and JavaScript nearly as much. We can leverage the tools we&rsquo;ve all
grown comfortable with such as testing with RSpec rather than Jest.</p>
<h3 id="improved-collaboration-and-onboarding">Improved collaboration and onboarding</h3>
<p>Within our React based application, debugging or simply trying to understand how
something worked required traversing a maze of code. Do I need to look at the
GraphQL layer? Is it the React application? Local state management in JavaScript?
We now have a more traditional application where it&rsquo;s easier to find things.
New engineers on the team can get up to speed much faster now which means
providing value quickly.</p>
<h2 id="conclusion-and-future-outlook">Conclusion and Future Outlook</h2>
<p>One of Buoy&rsquo;s core values is to prefer simplicity over complexity. We&rsquo;ve found
that this change embodies this as we now have a much simpler architecture leading
to better outcomes overall. By embracing server-side rendering and leveraging
the power of ViewComponents, we were able to create a more performant,
maintainable, and developer-friendly application.</p>
<p>As web development continues to evolve, it&rsquo;s crucial to periodically reassess
your technology choices. What works for one project or team may not be the best
solution for another. Buoy&rsquo;s experience serves as a reminder that there&rsquo;s no
one-size-fits-all approach in our industry.</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>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>Simple Sinatra Service Proxy</title><link>https://www.damiangalarza.com/posts/2012-04-02-simple-sinatra-service-proxy/</link><pubDate>Mon, 02 Apr 2012 00:00:00 +0000</pubDate><author>Damian Galarza</author><guid>https://www.damiangalarza.com/posts/2012-04-02-simple-sinatra-service-proxy/</guid><content:encoded><![CDATA[<p>Recently I came across the situation where I would need to communicate with a third party API on the front
end via JavaScript and some AJAX requests. Unfortunately JavaScript does not allow ajax requests across different
domains which caused an issue for us since the service does not support a JSONP service to get around this.</p>
<p>Instead, I set out to build a back end service which would consume our third party API and we could interact
with on the front end. However, I did not want to spend a great deal of time on building this midpoint between
the services.</p>
<h3 id="ruby-and-sinatra-to-the-rescue">Ruby and Sinatra to the Rescue</h3>
<p>Armed with the task at hand I set out to build the simplest approach possible to begin consuming our third party
API as quickly as possible. I started out with Rails and a look into ActiveResource but quickly found that there was
to much overhead and complexity for what I needed. With this in mind I decided to move onto a simple Sinatra application which
would aim to mimic the third party services in API methods and forward requests for us.</p>
<p>For our test case, let&rsquo;s look at the Twitter API. Note, Twitter does in fact support a JSONP service so this isn&rsquo;t really necessary.
We are simply using it as a test case for the post.</p>
<h4 id="fetching-a-users-timeline">Fetching a user&rsquo;s timeline</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:#89dceb">require</span> <span style="color:#a6e3a1">&#39;sintra&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">require</span> <span style="color:#a6e3a1">&#39;net/http&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>get <span style="color:#a6e3a1">&#34;/1/statuses/user_timeline&#34;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    uri <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">URI</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">HTTP</span><span style="color:#89dceb;font-weight:bold">.</span>build(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">:host</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> api<span style="color:#89dceb;font-weight:bold">.</span>twitter<span style="color:#89dceb;font-weight:bold">.</span>com,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">:path</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> request<span style="color:#89dceb;font-weight:bold">.</span>path_info,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">:query</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> request<span style="color:#89dceb;font-weight:bold">.</span>query_string
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    content_type <span style="color:#a6e3a1">&#39;application/json&#39;</span>, <span style="color:#a6e3a1">:charset</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;utf-8&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">Net</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">HTTP</span><span style="color:#89dceb;font-weight:bold">.</span>get(uri)
</span></span><span style="display:flex;"><span><span style="color:#cba6f7">end</span>
</span></span></code></pre></div><p>In order to fetch a user&rsquo;s timeline we will need to make a request to the following API path: <code>/1/statuses/user_timeline</code>. Along with this path
the API documentation by twitter specifies that we can send various parameters along in the query string. What we have done here is we created a path which matches
twitter&rsquo;s and from there we access the request information provided by Sinatra. We build a URI using the API URL, <code>request.path</code>, and <code>request.query</code>. This will allow us
to quickly forward the request to Twitter&rsquo;s API through a NET::HTTP.get request.</p>
<p>Now that we&rsquo;ve done that we can either mount our Sinatra app somewhere in the same domain or proxy requests to it on another domain.  In my case, I&rsquo;ve set up a simple
proxy through Apache at /services.</p>
<pre tabindex="0"><code>&lt;IfModule mod_proxy.c&gt;
    ProxyRequests Off
    ProxyVia On

    &lt;Proxy *&gt;
        AddDefaultCharset off
        Order deny, allow
        Allow from all
    &lt;/Proxy&gt;

    ProxyPass /services http://127.0.0.1:9393
    ProxyPassReverse /services http://127.0.0.1:9393
&lt;/IfModule&gt;
</code></pre><p>So assuming I have my Sinatra application up and running on port 9393. I now have a proxy which will forward all requests to /services in my web application
to the Sinatra application. This will then forward all requests to the Twitter API and return the results. Visit the <a href="http://httpd.apache.org/docs/2.0/mod/mod_proxy.html">mod_proxy</a> documentation
for more information about HTTP proxies with Apache.</p>
<h4 id="drying-up-our-application">Drying up our application</h4>
<p>In our case right now we have a single method that we are forwarding, but what if we wanted to support more API methods provided by twitter.  Well we could go ahead and write out
each method and its block, or we could take advantage of the flexibility we have available to us.</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;sinatra&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#89dceb">require</span> <span style="color:#a6e3a1">&#39;net/http&#39;</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">api_request</span>(path, query_string)
</span></span><span style="display:flex;"><span>    uri <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#f9e2af">URI</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">HTTP</span><span style="color:#89dceb;font-weight:bold">.</span>build(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">:host</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> api<span style="color:#89dceb;font-weight:bold">.</span>twitter<span style="color:#89dceb;font-weight:bold">.</span>com,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">:path</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> path,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e3a1">:query</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> query_string
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f9e2af">Net</span><span style="color:#89dceb;font-weight:bold">::</span><span style="color:#f9e2af">HTTP</span><span style="color:#89dceb;font-weight:bold">.</span>get uri
</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>request_handler <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#89dceb;font-weight:bold">-&gt;</span> <span style="color:#cba6f7">do</span>
</span></span><span style="display:flex;"><span>    content_type <span style="color:#89dceb;font-weight:bold">=</span> <span style="color:#a6e3a1">&#39;application/json&#39;</span>, <span style="color:#a6e3a1">:charset</span> <span style="color:#89dceb;font-weight:bold">=&gt;</span> <span style="color:#a6e3a1">&#39;utf-8&#39;</span>
</span></span><span style="display:flex;"><span>    api_request request<span style="color:#89dceb;font-weight:bold">.</span>path_info, request<span style="color:#89dceb;font-weight:bold">.</span>query_string
</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:#a6e3a1">%w{
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    /1/statues/user_timeline
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    /1/statuses/mentions
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">    /1/statuses/retweeted_by_me
</span></span></span><span style="display:flex;"><span><span style="color:#a6e3a1">}</span><span style="color:#89dceb;font-weight:bold">.</span>each { <span style="color:#89dceb;font-weight:bold">|</span>route<span style="color:#89dceb;font-weight:bold">|</span> get route, <span style="color:#89dceb;font-weight:bold">&amp;</span>request_handler }
</span></span></code></pre></div>]]></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></channel></rss>