<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ruby &#8211; Other Things</title>
	<atom:link href="https://blog.adamzolo.com/category/ruby/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.adamzolo.com</link>
	<description>Blog about Things by Adam Zolotarev</description>
	<lastBuildDate>Sat, 09 May 2026 14:00:17 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>
	<item>
		<title>Using tools for LLM&#8217;s instead of asking</title>
		<link>https://blog.adamzolo.com/claude-api-tool-use-vs-prompting/</link>
					<comments>https://blog.adamzolo.com/claude-api-tool-use-vs-prompting/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Tue, 17 Feb 2026 03:26:32 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">https://blog.adamzolo.com/?p=1106</guid>

					<description><![CDATA[I have a Rails app that sends user-provided text to Claude for analysis and displays structured results in the UI. The response needs to be JSON so I can render it. However longer inputs sometimes would generate errors. Longer inputs meant longer system prompts and longer responses. The logs showed: Analysis failed: expected ',' or&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/claude-api-tool-use-vs-prompting/" title="Continue reading &#8216;Using tools for LLM&#8217;s instead of asking&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>I have a Rails app that sends user-provided text to Claude for analysis and displays structured results in the UI. The response needs to be JSON so I can render it.</p>
<p>However longer inputs sometimes would generate errors. Longer inputs meant longer system prompts and longer responses.</p>
<p>The logs showed:</p>
<pre><code>Analysis failed: expected ',' or '}' after object value</code></pre>
<p>Claude was generating valid-looking JSON that wasn&#8217;t actually valid. A dropped comma deep in a large response object. The longer the response, the more likely this happened.</p>
<h2>The Old Approach: Prompt Engineering + Defensive Parsing</h2>
<p>My system prompt included a 28-line block demanding JSON output:</p>
<pre><code class="language-ruby">SYSTEM_PROMPT = &lt;&lt;~PROMPT
  ...
  CRITICAL INSTRUCTIONS:
  - You MUST ALWAYS respond with valid JSON. No exceptions. No explanations outside JSON.
  - NEVER respond with plain text - always use the JSON format.

  You MUST ALWAYS respond in JSON format with the following structure (no exceptions):
  {
    "score": 0-100,
    "level": "low|medium|high",
    "summary": "Brief overall assessment",
    "items": [
      {
        "title": "Issue name",
        "description": "What's wrong",
        "severity": "low|medium|high"
      }
    ],
    "recommendations": [
      "Specific actionable suggestion 1",
      "Specific actionable suggestion 2"
    ]
  }
PROMPT</code></pre>
<p>Despite all the shouting in the prompt, Claude would sometimes:</p>
<ul>
<li>Wrap the JSON in markdown code fences (<code>```json ... ```</code>)</li>
<li>Add explanatory text after the closing brace</li>
<li>Drop commas in deeply nested objects on long responses</li>
<li>Return plain text when it decided the input wasn&#8217;t suitable for analysis</li>
</ul>
<p>So I built a pipeline of defensive code to handle all of this.</p>
<p><strong>Step 1: Extract JSON from whatever Claude returned.</strong> A brace-matching parser that stripped markdown fences, found the first <code>{</code>, tracked nesting depth while respecting string escaping, and separated trailing notes:</p>
<pre><code class="language-ruby">def extract_json_and_note(text)
  text = text.strip
  if text.start_with?("```")
    text = text.sub(/A```(?:json|JSON)?s*/, "").sub(/s*```z/, "").strip
  end

  start_idx = text.index("{")
  return [text, nil, false] if start_idx.nil?

  brace_count = 0
  in_string = false
  escape_next = false

  text[start_idx..].each_char.with_index do |char, idx|
    if escape_next
      escape_next = false
      next
    end
    case char
    when "\" then escape_next = true if in_string
    when '"'  then in_string = !in_string unless escape_next
    when "{"  then brace_count += 1 unless in_string
    when "}"
      brace_count -= 1 unless in_string
      if brace_count == 0
        end_idx = start_idx + idx
        json_text = text[start_idx..end_idx]
        note = text[(end_idx + 1)..].strip.presence
        return [json_text, note, true]
      end
    end
  end

  [text, nil, false]
end</code></pre>
<p><strong>Step 2: Normalize missing fields</strong> because Claude might omit arrays for edge cases:</p>
<pre><code class="language-ruby">def normalize_response!(result)
  result["score"] ||= 0
  result["level"] ||= "unknown"
  result["summary"] ||= "Analysis complete"
  result["items"] ||= []
  result["recommendations"] ||= []
  result["score"] = result["score"].to_i if result["score"].is_a?(String)
end</code></pre>
<p><strong>Step 3: Validate the structure</strong> because even after parsing, I couldn&#8217;t trust it:</p>
<pre><code class="language-ruby">def validate_response_structure!(result)
  required_keys = %w[score level summary items recommendations]
  missing_keys = required_keys - result.keys
  raise "Invalid response structure: missing keys #{missing_keys.join(', ')}" if missing_keys.any?

  score = result["score"]
  unless score.is_a?(Integer) &amp;&amp; score &gt;= 0 &amp;&amp; score &lt;= 100
    raise "Invalid score: must be integer 0-100, got #{score.inspect}"
  end

  %w[items recommendations].each do |key|
    unless result[key].is_a?(Array)
      raise "Invalid #{key}: expected array, got #{result[key].class}"
    end
  end
end</code></pre>
<p>All of this existed because I was asking an LLM to format its own output as JSON via natural language instructions. I was writing a fragile parser for a format the model was never <em>constrained</em> to produce.</p>
<h2>The Fix: Tool Use</h2>
<p>Anthropic&#8217;s tool use API (also called function calling) lets you define a JSON schema that Claude <em>must</em> conform to. Instead of asking Claude to output JSON, you tell the API: &#8220;call this function with these typed parameters.&#8221; Claude&#8217;s response is guaranteed to match the schema.</p>
<p>Here&#8217;s the schema definition:</p>
<pre><code class="language-ruby">ANALYSIS_TOOL = {
  name: "analyze",
  description: "Return the structured analysis results",
  input_schema: {
    type: "object",
    required: ["score", "level", "summary", "items", "recommendations"],
    properties: {
      score: { type: "integer", description: "Overall score from 0 (safe) to 100 (dangerous)" },
      level: { type: "string", enum: ["low", "medium", "high"] },
      summary: { type: "string", description: "Brief overall assessment" },
      items: {
        type: "array",
        items: {
          type: "object",
          required: ["title", "description", "severity"],
          properties: {
            title:       { type: "string" },
            description: { type: "string" },
            severity:    { type: "string", enum: ["low", "medium", "high"] }
          }
        }
      },
      recommendations: { type: "array", items: { type: "string" } }
    }
  }
}.freeze</code></pre>
<p>The API call adds two parameters:</p>
<pre><code class="language-ruby">response = client.messages.create(
  model: model,
  max_tokens: max_tokens,
  system: [{ type: "text", text: system_prompt }],
  messages: [{ role: "user", content: user_message }],
  tools: [ANALYSIS_TOOL],
  tool_choice: { type: "tool", name: "analyze" }
)</code></pre>
<p><code>tools:</code> defines the schema. <code>tool_choice:</code> with <code>type: "tool"</code> forces Claude to use it — no chance of returning prose instead.</p>
<p>Response extraction is three lines:</p>
<pre><code class="language-ruby">tool_block = response.content.find { |b| b.type.to_s == "tool_use" }
raise "No tool_use block in response" unless tool_block
result = tool_block.input.transform_keys(&amp;:to_s)</code></pre>
<p>That&#8217;s it. <code>tool_block.input</code> is already a parsed hash. No <code>JSON.parse</code>, no brace matching, no markdown stripping, no comma repair.</p>
<h2>The Result</h2>
<p><strong>Deleted:</strong> ~160 lines from the service, ~250 lines from tests..</p>
<p><strong>Added:</strong> ~30 lines for the schema definition, 2 parameters on the API call, 3 lines of response extraction.</p>
<p>The system prompt shrank too. The 28 lines of &#8220;YOU MUST RESPOND IN JSON&#8221; instructions disappeared entirely. The prompt now focuses on <em>what</em> to analyze, not <em>how</em> to format the output.</p>
<p>The user message went from <code>"Analyze this and respond with JSON only:"</code> to just <code>"Analyze this:"</code>.</p>
<h2>When Should You Use This?</h2>
<p>Any time you want structured output from an LLM. If you&#8217;re writing regex to fix JSON commas, building brace-matching parsers, or adding &#8220;RESPOND IN JSON ONLY&#8221; to your prompts: switch to tool use. The schema is self-documenting, the output is guaranteed valid, and you delete code instead of writing it.</p>
<p>The one caveat: tool use constrains the <em>structure</em> but not the <em>content</em>. Claude can still put whatever it wants in a string field. You still need to validate that a score is in a sensible range or that enum values match your expectations. But &#8220;validate the values&#8221; is a much smaller problem than &#8220;parse arbitrary text that might be JSON.&#8221;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/claude-api-tool-use-vs-prompting/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building Clausy: A Contract Analysis Tool with Rails 8 and Claude AI</title>
		<link>https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/</link>
					<comments>https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Sun, 08 Feb 2026 21:07:19 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">https://blog.adamzolo.com/?p=1090</guid>

					<description><![CDATA[I just launched https://clausyapp.com, a web app that uses AI to analyze contracts and highlight potential issues. You upload a PDF/images or paste text, and Claude AI reads through it to find things like unlimited liability clauses, auto-renewal terms, or aggressive IP assignment language. Why I Built This I&#8217;ve signed a enough contracts over the&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/" title="Continue reading &#8216;Building Clausy: A Contract Analysis Tool with Rails 8 and Claude AI&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I just launched https://clausyapp.com, a web app that uses AI to analyze contracts and highlight potential issues. You upload a PDF/images or paste text, and Claude AI reads through it to find things like unlimited liability clauses, auto-renewal terms, or aggressive IP assignment language.</p>



<h2 class="wp-block-heading">Why I Built This</h2>



<p class="wp-block-paragraph">I&#8217;ve signed a enough contracts over the years, and I was never quite sure if I was missing something important buried in the legal language. I&#8217;d skim through them, but let&#8217;s be honest – I didn&#8217;t understand half of it. Getting a lawyer to review every contract is also just not something I&#8217;m going to do, unless it&#8217;s something really big.</p>



<p class="wp-block-paragraph">I figured: AI is pretty good at reading and understanding text now. And based on how many of these AI contract analysis tools are out there, it&#8217;s the new TODO app in the age of AI.</p>



<h2 class="wp-block-heading">The Stack</h2>



<p class="wp-block-paragraph">I went with a Rails 8 monolith because I wanted something I could ship quickly and maintain solo:</p>



<ul class="wp-block-list">
<li>Rails 8 with Hotwire (Turbo + Stimulus)</li>



<li>Anthropic&#8217;s Claude API</li>



<li>Solid Queue for background jobs with priority queues (paid users get faster processing)</li>



<li>Solid Cache for caching and rate limits</li>



<li>Stripe for subscriptions and billing</li>



<li>Tesseract OCR for extracting text from scanned images (JPG, PNG, WebP, HEIC)</li>



<li>Kamal for deployment</li>
</ul>



<p class="wp-block-paragraph">Everything runs in Docker containers. No separate frontend framework, no microservices. Just a straightforward Rails app that does one thing well.</p>



<h2 class="wp-block-heading">Technical Security Challenges</h2>



<ol class="wp-block-list">
<li><strong>File Processing Security</strong>
<ul class="wp-block-list">
<li>Magic byte validation – Don&#8217;t trust file extensions. I check the actual file signature to verify it&#8217;s really a PDF or DOCX.</li>



<li>Size limits – DOCX files are zip archives, so I enforce size limits before decompression to prevent zip bombs.</li>



<li>Immediate deletion – Original files are deleted right after text extraction. No long-term storage of sensitive documents.</li>



<li>Command injection prevention – Only use safe extraction tools, never shell out with user-provided filenames.</li>
</ul>
</li>



<li><strong>Server Access</strong>
<ul class="wp-block-list">
<li>Firewall at the provider level </li>



<li>Firewall at the node level (ufw)</li>



<li>ssh through certs only, limit access to specific IP&#8217;s</li>



<li>Cloudflare</li>
</ul>
</li>



<li><strong>Application </strong>Security
<ul class="wp-block-list">
<li>Devise authentication &#8211; Industry-standard auth framework</li>



<li>CSRF protection &#8211; Rails CSRF tokens on all POST/PUT/PATCH/DELETE requests</li>



<li>UUID-based URLs &#8211; Guest contracts use UUIDs (prevents enumeration attacks)</li>



<li>Rate Limits (Rack::Attack)</li>



<li>CSP Policy
<ul class="wp-block-list">
<li>No unsafe-eval &#8211; Prevents eval() attacks</li>



<li>Whitelisted script sources &#8211; Only self, HTTPS, Stripe, Cloudflare allowed</li>



<li>No object embeds &#8211; object_src :none blocks Flash/plugin attacks</li>



<li>Nonce-based scripts &#8211; Importmap scripts use session-based nonces</li>



<li>HTTPS enforced &#8211; All resources loaded over HTTPS</li>
</ul>
</li>



<li>Input Validation</li>
</ul>
</li>



<li>Fraud Prevention
<ul class="wp-block-list">
<li>Email history tracking &#8211; SHA256 email hashing &#8211; Email hashes stored, not plain emails</li>
</ul>
</li>



<li>Payment Security
<ul class="wp-block-list">
<li>Stripe webhook verification &#8211; Signature validation on all webhook events</li>



<li>No card storage &#8211; Stripe handles all payment details</li>
</ul>
</li>



<li>Secret Management
<ul class="wp-block-list">
<li>Rails credentials &#8211; All secrets in encrypted credentials.yml.enc</li>
</ul>
</li>



<li>XSS Prevention
<ul class="wp-block-list">
<li>Automatic HTML escaping</li>



<li>CSP headers &#8211; Content Security Policy blocks inline scripts</li>
</ul>
</li>



<li>Transport Security
<ul class="wp-block-list">
<li>HTTPS everywhere &#8211; All resources loaded over HTTPS</li>



<li>Secure cookies &#8211; Session cookies marked secure in production</li>



<li>HSTS headers &#8211; Forces HTTPS connections</li>
</ul>
</li>



<li>DoS Prevention
<ul class="wp-block-list">
<li>Job queues &#8211; Background processing prevents request timeouts</li>



<li>Priority queues &#8211; Paid users get separate high-priority queue</li>



<li>Rate limiting &#8211; Comprehensive rate limits across all endpoints</li>



<li>Query optimization &#8211; Indexed queries prevent slow lookups</li>
</ul>
</li>
</ol>



<h2 class="wp-block-heading">Try it</h2>



<p class="wp-block-paragraph">Demo (no signup): <a href="https://clausyapp.com/contracts/new?demo=hn">https://clausyapp.com/contracts/new?demo=hn</a></p>



<p class="wp-block-paragraph">Full app: <a href="https://clausyapp.com">https://clausyapp.com</a></p>



<p class="wp-block-paragraph">It&#8217;s not legal advice – I&#8217;m very explicit about that – but it can help you spot things you might want to ask a lawyer about.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Proxy Sentry JS requests to the self-hosted server behind a firewall</title>
		<link>https://blog.adamzolo.com/proxy-sentry-js-requests-to-the-self-hosted-server-behind-a-firewall/</link>
					<comments>https://blog.adamzolo.com/proxy-sentry-js-requests-to-the-self-hosted-server-behind-a-firewall/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Fri, 23 Oct 2020 18:48:51 +0000</pubDate>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=950</guid>

					<description><![CDATA[Tech: Rails Problem: you have a self-hosted Sentry server behind a firewall and you want to report your frontend errors. One way to accomplish it is by modifying Sentry dsn to send it to your backend and then proxying them to the Sentry server. First, let&#8217;s set up a new route: It has to follow&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/proxy-sentry-js-requests-to-the-self-hosted-server-behind-a-firewall/" title="Continue reading &#8216;Proxy Sentry JS requests to the self-hosted server behind a firewall&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Tech: Rails</p>



<p class="wp-block-paragraph">Problem: you have a self-hosted Sentry server behind a firewall and you want to report your frontend errors.</p>



<p class="wp-block-paragraph">One way to accomplish it is by modifying Sentry dsn to send it to your backend and then proxying them to the Sentry server.</p>



<p class="wp-block-paragraph">First, let&#8217;s set up a new route:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
post &#039;frontend_errors/api/:project_id/store&#039;, to: &#039;frontend_errors#create&#039;
</pre></div>


<p class="wp-block-paragraph">It has to follow a specific pattern to work with the Sentry frontend library. The only thing you can change in the above is <code>frontend_errors</code> &#8211; pick whatever name you want. The code above will expect you to have a FrontendErrorsController.</p>



<p class="wp-block-paragraph">Now, the FrontEndErrorsController needs to redirect to your actual Sentry server in the format that Sentry expects. Let&#8217;s create a new class to handle it:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
class SentryProxy
  # This could be different based on your Sentry version.
  # Look into raven-sentry gem codebase if this doesn&#039;t work
  # Look for http_transport.rb files - https://github.com/getsentry/sentry-ruby/blob/f6625bd12fa5ef86e4ce6a1515e8a8171cea9ece/sentry-ruby/lib/sentry/transport/http_transport.rb
  PROTOCOL_VERSION = &#039;5&#039;
  USER_AGENT = &quot;raven-ruby/#{Raven::VERSION}&quot;

  def initialize(body:, sentry_dsn:)
    @body = body
    @sentry_dsn = sentry_dsn
  end

  def post_to_sentry
    return if @sentry_dsn.blank?

    sentry_connection.post do |faraday|
      faraday.body = @body
    end
  end

  private

  def sentry_connection
    Faraday.new(url: sentry_post_url) do |faraday|
      faraday.headers&#x5B;&#039;X-Sentry-Auth&#039;] = generate_auth_header
      faraday.headers&#x5B;:user_agent] = &quot;sentry-ruby/#{Raven::VERSION}&quot;
      faraday.adapter(Faraday.default_adapter)
    end
  end

  def sentry_post_url
    key, url = @sentry_dsn.split(&#039;@&#039;)
    path, project_id = url.split(&#039;/&#039;)
    http_prefix, _keys = key.split(&#039;//&#039;)

    &quot;#{http_prefix}//#{path}/api/#{project_id}/store/&quot;
  end

  def generate_auth_header
    now = Time.now.to_i.to_s
    public_key, secret_key = @sentry_dsn.split(&#039;//&#039;).second.split(&#039;@&#039;).first.split(&#039;:&#039;)

    fields = {
      &#039;sentry_version&#039; =&gt; PROTOCOL_VERSION,
      &#039;sentry_client&#039; =&gt; USER_AGENT,
      &#039;sentry_timestamp&#039; =&gt; now,
      &#039;sentry_key&#039; =&gt; public_key,
      &#039;sentry_secret&#039; =&gt; secret_key
    }
    &#039;Sentry &#039; + fields.map { |key, value| &quot;#{key}=#{value}&quot; }.join(&#039;, &#039;)
  end
end
</pre></div>


<p class="wp-block-paragraph">Now in your controller you can call it like this (assumes you can get your sentry_dsn on the backend):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
def create
  SentryProxy.new(body: request.body.read, sentry_dsn: sentry_dsn).post_to_sentry

  head(:no_content)
end
</pre></div>


<p class="wp-block-paragraph">And to make sure your frontend is properly configured, first import Sentry frontend libraries, then initialize them using:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
 Sentry.init({
    dsn: `${window.location.protocol}//public_key@${window.location.host}/frontend_errors/0`});
</pre></div>


<p class="wp-block-paragraph"><code>public_key</code> is supposed to be&#8230; your public key. You have to supply it in the dsn even if you&#8217;re getting the dsn key on the backend, otherwise, the Sentry frontend library will throw errors. 0 is the project id &#8211; the same idea, you have to supply it for the Sentry frontend to properly parse it. It doesn&#8217;t have to be real, as we&#8217;re reconstructing the Sentry url on the backend, and you can get proper keys/project id on the backend.</p>



<p class="wp-block-paragraph">This should do it. Now you can configure Sentry frontend library to capture all errors, capture specific exceptions or messages.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/proxy-sentry-js-requests-to-the-self-hosted-server-behind-a-firewall/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Using the same redis instance for Rails cache and non-cache entries</title>
		<link>https://blog.adamzolo.com/using-the-same-redis-instance-for-rails-cache-and-non-cache-entries/</link>
					<comments>https://blog.adamzolo.com/using-the-same-redis-instance-for-rails-cache-and-non-cache-entries/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Fri, 16 Oct 2020 16:05:25 +0000</pubDate>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=947</guid>

					<description><![CDATA[Redis docs: https://redis.io/topics/lru-cache OS: Ubuntu 18.04 LTS When you need to use redis for cache and non-cache entries (e.g., ActionCable, Sidekiq&#8230;), the recommended approach is to create a separate redis instance. However, if you want a simpler setup, or just can&#8217;t get another instance for reasons, there is an option to use the same redis&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/using-the-same-redis-instance-for-rails-cache-and-non-cache-entries/" title="Continue reading &#8216;Using the same redis instance for Rails cache and non-cache entries&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Redis docs: <a rel="noreferrer noopener" href="https://redis.io/topics/lru-cache" target="_blank">https://redis.io/topics/lru-cache</a></p>



<p class="wp-block-paragraph">OS: Ubuntu 18.04 LTS</p>



<p class="wp-block-paragraph">When you need to use redis for cache and non-cache entries (e.g., ActionCable, Sidekiq&#8230;), the recommended approach is to create a separate redis instance. However, if you want a simpler setup, or just can&#8217;t get another instance for reasons, there is an option to use the same redis instance for multiple uses.</p>



<p class="wp-block-paragraph">We need to make sure that Redis will not evict our important data (e.g., Sidekiq), while at the same time evicting old cache entries. We could use any of the volatile eviction policies:</p>



<ul class="wp-block-list"><li>volatile-lru&nbsp;&#8211; remove least recently used keys where expiry is set</li><li>volatile-random &#8211; removes keys at random where expiry is set</li><li>volatile-ttl&nbsp;&#8211; evict keys with an&nbsp;<strong>expire set</strong>, and try to evict keys with a shorter time to live (TTL) first</li><li>volatile-lfu (starting with Redis 4.0) &#8211; evict using approximated LFU among the keys with an expire set.</li></ul>



<p class="wp-block-paragraph">To set up the eviction policy on your redis instance, edit your <code>/etc/systemd/system/redis.conf</code> and set these parameters:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
maxmemory 100mb
maxmemory-policy volatile-lfu
</pre></div>


<p class="wp-block-paragraph">Then in your Rails config update your store to use redis cache store, if not using already:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
  config.cache_store = :redis_cache_store, {
    url: ENV.fetch(&#039;REDIS_URL&#039;, &#039;redis://localhost:6379&#039;),
    expires_in: 24.hours
  }
</pre></div>


<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/using-the-same-redis-instance-for-rails-cache-and-non-cache-entries/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GPG Key Encryption in Ruby/Rails</title>
		<link>https://blog.adamzolo.com/gpg-key-encryption-in-ruby-rails/</link>
					<comments>https://blog.adamzolo.com/gpg-key-encryption-in-ruby-rails/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Thu, 27 Aug 2020 15:36:39 +0000</pubDate>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=939</guid>

					<description><![CDATA[To import the public key in ruby: To encrypt data with a public key for a given recipient:]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">To import the public key in ruby:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
EncryptionError = Class.new(StandardError)

result, stderr, status = Open3.capture3(&quot;gpg --import #{@key_path}&quot;)
raise EncryptionError.new(stderr_data) unless status.success?
</pre></div>


<p class="wp-block-paragraph">To encrypt data with a public key for a given recipient:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
pgp_encrypt_command = &quot;gpg -ear #{recipient} --always-trust --trust-model always --local-user #{recipient} --default-key #{recipient}&quot;

encrypted_data, stderr_data, status = Open3.capture3(pgp_encrypt_command, stdin_data: data)
    raise EncryptionError.new(stderr_data) unless status.success?
</pre></div>]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/gpg-key-encryption-in-ruby-rails/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Using Azurite with Active Storage</title>
		<link>https://blog.adamzolo.com/using-azurite-with-active-storage/</link>
					<comments>https://blog.adamzolo.com/using-azurite-with-active-storage/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Tue, 02 Jun 2020 19:57:22 +0000</pubDate>
				<category><![CDATA[Active Storage]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Azurite]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=919</guid>

					<description><![CDATA[Install Azurite in your preferred way: npm install azurite Install Microsoft Azure Storage Explorer Create some directory to run azurite from: `~/azurite` Add storage.yml configuration for azurite (using the default dev account and key): Update development.rb to use azurite_emulator: Start azurite from the directory you created for azurite: azurite --location ~/azurite --debug ~/azurite/debug.log Start Azure&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/using-azurite-with-active-storage/" title="Continue reading &#8216;Using Azurite with Active Storage&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Install <a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite" target="_blank" rel="noreferrer noopener">Azurite</a> in your preferred way: <code>npm install azurite</code></p>



<p class="wp-block-paragraph">Install <a href="https://azure.microsoft.com/en-us/features/storage-explorer/" target="_blank" rel="noreferrer noopener">Microsoft Azure Storage Explorer</a></p>



<p class="wp-block-paragraph">Create some directory to run azurite from: `~/azurite`</p>



<p class="wp-block-paragraph">Add <code>storage.yml</code> configuration for azurite (using the default dev account and key):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; title: ; notranslate">
azurite_emulator:
  service: AzureStorage
  storage_account_name: &#039;devstoreaccount1&#039;
  storage_access_key: &#039;Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==&#039;
  container: &#039;container-name&#039;
  storage_blob_host: &#039;http://127.0.0.1:10000/devstoreaccount1&#039;
</pre></div>


<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Update <code>development.rb</code> to use azurite_emulator: </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
config.active_storage.service = :azurite_emulator
</pre></div>


<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Start azurite from the directory you created for azurite:  <code>azurite --location ~/azurite --debug ~/azurite/debug.log</code></p>



<p class="wp-block-paragraph">Start Azure Storage Explorer, connect to local emulator, and create <code>container-name</code> blob container &#8211; the same container name you specified in the <code>storage.yml</code> file.</p>



<p class="wp-block-paragraph">Start uploading to Azurite.</p>



<h2 class="wp-block-heading">Note for Rails 5.2</h2>



<p class="wp-block-paragraph">Some changes have not been backported as of this post, and you have to monkey-patch ActiveStorage file as described here &#8211; <a href="http://www.garytaylor.blog/index.php/2019/01/30/rails-active-storage-and-azure-beyond-config/">http://www.garytaylor.blog/index.php/2019/01/30/rails-active-storage-and-azure-beyond-config/</a> &#8211; this allows us to work with azurite locally.</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">If you want to use the newer <code>azure-storage-blob</code> instead of the deprecated <code>azure-storage</code> and you&#8217;re on Rails 5.2, you have to do a bit more monkey-patching &#8211; otherwise, you&#8217;ll start getting <a href="https://stackoverflow.com/questions/62045971/no-such-file-to-load-azure-storage-rb">No such file to load — azure/storage.rb</a>&#8220;:</p>



<p class="wp-block-paragraph">Add two empty files: <code>lib/azure/storage/core/auth/shared_access_signature.rb</code>, and <code>lib/azure/storage.rb</code></p>



<p class="wp-block-paragraph">Add this to config/initializers/active_storage_6_patch.rb (this is the current master version of the ActiveStorage module):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: ruby; title: ; notranslate">
require &quot;azure/storage/blob&quot;
require &#039;active_storage/service/azure_storage_service&#039;
module ActiveStorage
  # Wraps the Microsoft Azure Storage Blob Service as an Active Storage service.
  # See ActiveStorage::Service for the generic API documentation that applies to all services.
  class Service::AzureStorageService &lt; Service
    attr_reader :client, :container, :signer

    def initialize(storage_account_name:, storage_access_key:, container:, public: false, **options)
      @client = Azure::Storage::Blob::BlobService.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key, **options)
      @signer = Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key)
      @container = container
      @public = public
    end

    def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
      instrument :upload, key: key, checksum: checksum do
        handle_errors do
          content_disposition = content_disposition_with(filename: filename, type: disposition) if disposition &amp;&amp; filename

          client.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type, content_disposition: content_disposition)
        end
      end
    end

    def download(key, &amp;block)
      if block_given?
        instrument :streaming_download, key: key do
          stream(key, &amp;block)
        end
      else
        instrument :download, key: key do
          handle_errors do
            _, io = client.get_blob(container, key)
            io.force_encoding(Encoding::BINARY)
          end
        end
      end
    end

    def download_chunk(key, range)
      instrument :download_chunk, key: key, range: range do
        handle_errors do
          _, io = client.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
          io.force_encoding(Encoding::BINARY)
        end
      end
    end

    def delete(key)
      instrument :delete, key: key do
        client.delete_blob(container, key)
      rescue Azure::Core::Http::HTTPError =&gt; e
        raise unless e.type == &quot;BlobNotFound&quot;
        # Ignore files already deleted
      end
    end

    def delete_prefixed(prefix)
      instrument :delete_prefixed, prefix: prefix do
        marker = nil

        loop do
          results = client.list_blobs(container, prefix: prefix, marker: marker)

          results.each do |blob|
            client.delete_blob(container, blob.name)
          end

          break unless marker = results.continuation_token.presence
        end
      end
    end

    def exist?(key)
      instrument :exist, key: key do |payload|
        answer = blob_for(key).present?
        payload&#x5B;:exist] = answer
        answer
      end
    end

    def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
      instrument :url, key: key do |payload|
        generated_url = signer.signed_uri(
          uri_for(key), false,
          service: &quot;b&quot;,
          permissions: &quot;rw&quot;,
          expiry: format_expiry(expires_in)
        ).to_s

        payload&#x5B;:url] = generated_url

        generated_url
      end
    end

    def headers_for_direct_upload(key, content_type:, checksum:, filename: nil, disposition: nil, **)
      content_disposition = content_disposition_with(type: disposition, filename: filename) if filename

      { &quot;Content-Type&quot; =&gt; content_type, &quot;Content-MD5&quot; =&gt; checksum, &quot;x-ms-blob-content-disposition&quot; =&gt; content_disposition, &quot;x-ms-blob-type&quot; =&gt; &quot;BlockBlob&quot; }
    end

    private
      def private_url(key, expires_in:, filename:, disposition:, content_type:, **)
        signer.signed_uri(
          uri_for(key), false,
          service: &quot;b&quot;,
          permissions: &quot;r&quot;,
          expiry: format_expiry(expires_in),
          content_disposition: content_disposition_with(type: disposition, filename: filename),
          content_type: content_type
        ).to_s
      end

      def public_url(key, **)
        uri_for(key).to_s
      end


      def uri_for(key)
        client.generate_uri(&quot;#{container}/#{key}&quot;)
      end

      def blob_for(key)
        client.get_blob_properties(container, key)
      rescue Azure::Core::Http::HTTPError
        false
      end

      def format_expiry(expires_in)
        expires_in ? Time.now.utc.advance(seconds: expires_in).iso8601 : nil
      end

      # Reads the object for the given key in chunks, yielding each to the block.
      def stream(key)
        blob = blob_for(key)

        chunk_size = 5.megabytes
        offset = 0

        raise ActiveStorage::FileNotFoundError unless blob.present?

        while offset &lt; blob.properties&#x5B;:content_length]
          _, chunk = client.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1)
          yield chunk.force_encoding(Encoding::BINARY)
          offset += chunk_size
        end
      end

      def handle_errors
        yield
      rescue Azure::Core::Http::HTTPError =&gt; e
        case e.type
        when &quot;BlobNotFound&quot;
          raise ActiveStorage::FileNotFoundError
        when &quot;Md5Mismatch&quot;
          raise ActiveStorage::IntegrityError
        else
          raise
        end
      end
  end
end
</pre></div>]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/using-azurite-with-active-storage/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>An error occurred while installing nokogiri, and Bundler cannot continue&#8230;</title>
		<link>https://blog.adamzolo.com/an-error-occurred-while-installing-nokogiri-and-bundler-cannot-continue/</link>
					<comments>https://blog.adamzolo.com/an-error-occurred-while-installing-nokogiri-and-bundler-cannot-continue/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Sun, 25 Nov 2018 11:40:01 +0000</pubDate>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=792</guid>

					<description><![CDATA[This is only one of the reasons why this may happen &#8211; Xcode Command Line Tools (CLT) are not installed. This may happen after you upgrade you macOS version. To fix, run this on the command line: this should trigger a popup with the invitation to install Xcode CLT.]]></description>
										<content:encoded><![CDATA[<p>This is only one of the reasons why this may happen &#8211; Xcode Command Line Tools (CLT) are not installed. This may happen after you upgrade you macOS version.</p>
<p>To fix, run this on the command line:</p>
<pre class="brush: plain; title: ; notranslate">
xcode-select --install
</pre>
<p>this should trigger a popup with the invitation to install Xcode CLT.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/an-error-occurred-while-installing-nokogiri-and-bundler-cannot-continue/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Auto-Format Ruby Code</title>
		<link>https://blog.adamzolo.com/auto-format-ruby-code/</link>
					<comments>https://blog.adamzolo.com/auto-format-ruby-code/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Thu, 19 Apr 2018 19:04:44 +0000</pubDate>
				<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=741</guid>

					<description><![CDATA[Install Rufo gem &#8211; https://github.com/ruby-formatter/rufo gem install rufo Create .rufo either in your project or home directory if you want to override default settings trailing_commas false quote_style :single align_chained_calls true Install a plugin for your favorite editor. Here&#8217;s one for VSCode &#8211; https://marketplace.visualstudio.com/items?itemName=siliconsenthil.rufo-vscode]]></description>
										<content:encoded><![CDATA[<p>Install Rufo gem &#8211; <a href="https://github.com/ruby-formatter/rufo" rel="noopener" target="_blank">https://github.com/ruby-formatter/rufo</a></p>
<pre class="brush: plain; title: ; notranslate">
gem install rufo
</pre>
<p>Create .rufo either in your project or home directory if you want to override default settings</p>
<pre class="brush: plain; title: ; notranslate">
trailing_commas false
quote_style :single
align_chained_calls true
</pre>
<p>Install a plugin for your favorite editor. Here&#8217;s one for VSCode &#8211; <a href="https://marketplace.visualstudio.com/items?itemName=siliconsenthil.rufo-vscode" rel="noopener" target="_blank">https://marketplace.visualstudio.com/items?itemName=siliconsenthil.rufo-vscode</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/auto-format-ruby-code/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Setting up VS Code with Rails, Elixir, JavaScript</title>
		<link>https://blog.adamzolo.com/setting-up-vs-code-with-rails-elixir-javascript/</link>
					<comments>https://blog.adamzolo.com/setting-up-vs-code-with-rails-elixir-javascript/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Sat, 11 Nov 2017 00:44:33 +0000</pubDate>
				<category><![CDATA[Development Setup]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=700</guid>

					<description><![CDATA[Let&#8217;s make sure we can start VS Code from the terminal: Command + Shift + P Type Shell Select Command : Install code in PATH Extensions Rails Better Haml Cucumber (Gherkin) Full Support Ruby Ruby Solargraph ruby-rubocop JavaScript ESLint Prettier Git Git Lens Elixir vscode-elixir Other stuff pgFormatter Project Manager Bracket Pair Colorizer Personal Settings&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/setting-up-vs-code-with-rails-elixir-javascript/" title="Continue reading &#8216;Setting up VS Code with Rails, Elixir, JavaScript&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>Let&#8217;s make sure we can start VS Code from the terminal:</p>
<p>Command + Shift + P<br />
Type Shell<br />
 Select Command : Install code in PATH </p>
<h2>Extensions</h2>
<h3>Rails</h3>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=karunamurti.haml" rel="noopener" target="_blank">Better Haml</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=alexkrechik.cucumberautocomplete" rel="noopener" target="_blank">Cucumber (Gherkin) Full Support</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby" rel="noopener" target="_blank">Ruby</a>
<li><a href="https://marketplace.visualstudio.com/items?itemName=castwide.solargraph" rel="noopener" target="_blank">Ruby Solargraph</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=misogi.ruby-rubocop" rel="noopener" target="_blank">ruby-rubocop</a></li>
</ul>
</li>
<h3>JavaScript</h3>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint" rel="noopener" target="_blank">ESLint</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" rel="noopener" target="_blank">Prettier</a></li>
</ul>
<h3>Git</h3>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens" rel="noopener" target="_blank">Git Lens</a></li>
</ul>
<h3>Elixir</h3>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=mjmcloug.vscode-elixir" rel="noopener" target="_blank">vscode-elixir</a></li>
</ul>
<h3>Other stuff</h3>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=bradymholt.pgformatter" rel="noopener" target="_blank">pgFormatter</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager" rel="noopener" target="_blank">Project Manager</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer" rel="noopener" target="_blank">Bracket Pair Colorizer
<p></a></li>
</ul>
<h3>Personal Settings</h3>
<pre class="brush: plain; title: ; notranslate">
&quot;editor.formatOnSave&quot;: true,
  &quot;editor.fontLigatures&quot;: true,
  &quot;editor.fontFamily&quot;: &quot;FiraCode-Retina&quot;,
  &quot;editor.fontSize&quot;: 18,
  &quot;editor.renderIndentGuides&quot;: true,
  &quot;files.exclude&quot;: {
    &quot;**/.git&quot;: true,
    &quot;**/node_modules&quot;: true,
    &quot;**/bower_components&quot;: true,
    &quot;**/tmp&quot;: true,
    &quot;tmp/**&quot;: true,
    &quot;**/vendor&quot;: true,
    &quot;vendor&quot;: true,
    &quot;.bundle&quot;: true,
    &quot;.github&quot;: true,
    &quot;.sass-cache&quot;: true,
    &quot;features/reports&quot;: true
  },

  &quot;editor.tabSize&quot;: 2,
  &quot;prettier.singleQuote&quot;: true,
  &quot;workbench.colorTheme&quot;: &quot;Monokai&quot;,
  &quot;window.zoomLevel&quot;: 0,
  &quot;editor.renderWhitespace&quot;: &quot;boundary&quot;,
  &quot;editor.renderControlCharacters&quot;: true,

  &quot;ruby.lint&quot;: {
    &quot;rubocop&quot;: true,
    &quot;ruby&quot;: true,
    &quot;fasterer&quot;: true,
    &quot;reek&quot;: false,
    &quot;ruby-lint&quot;: false
  },
  &quot;editor.quickSuggestions&quot;: {
    &quot;strings&quot;: true
  },

  &quot;cucumberautocomplete.steps&quot;: &#x5B;
    &quot;features/step_definitions/*.rb&quot;,
    &quot;features/step_definitions/**/*.rb&quot;,
    &quot;features/step_definitions/**/**/*.rb&quot;
  ],
  &quot;cucumberautocomplete.syncfeatures&quot;: &quot;features/*feature&quot;
</pre>
<p>Some common exclusions for .solagraph.yml (can place it in the root of your project)</p>
<pre class="brush: plain; title: ; notranslate">
---
include:
- &quot;app/**/*.rb&quot;
- &quot;lib/**/*.rb&quot;
- &quot;engines/engine_name/app/**/*.rb&quot;
- &quot;engines/engine_name/lib/**/*.rb&quot;
- &quot;config/**/*.rb&quot;
exclude:
- app/javascript/**/*
- node_modules/**/**
- spec/**/*
- test/**/*
- vendor/**/*
- &quot;.bundle/**/*&quot;
- .bundle/**/*
- uploads/**/*
- .bundle/**/*
- .git/**/*
- engines/engine_name/.bundle/**/*
- engines/engine_name/vendor/**/*
- coverage/**/*
require: &#x5B;]
domains: &#x5B;]
reporters:
- rubocop
- require_not_found
plugins: &#x5B;]
require_paths: &#x5B;]
max_files: 5000
plugins:
- runtime

</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/setting-up-vs-code-with-rails-elixir-javascript/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>RSpec, Action Cable, and Capybara (As of Rails 5.1.2)</title>
		<link>https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/</link>
					<comments>https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Tue, 04 Jul 2017 03:19:28 +0000</pubDate>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=628</guid>

					<description><![CDATA[Gems in Gemfile: group :test, :development do gem 'database_cleaner' gem &#34;rspec-rails&#34;, &#34;~&#62; 3.6.0&#34; gem 'selenium-webdriver' end group :test do gem &#34;capybara&#34;, &#34;~&#62; 2.14.0&#34; end rails_helper: config.use_transactional_fixtures = false config.before(:suite) do DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.strategy = :transaction end config.before(:each, js: true) do DatabaseCleaner.strategy = :truncation end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end #Puma&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/" title="Continue reading &#8216;RSpec, Action Cable, and Capybara (As of Rails 5.1.2)&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>Gems in Gemfile:</p>
<pre class="brush: ruby; title: ; notranslate">
group :test, :development do
  gem 'database_cleaner'
  gem &quot;rspec-rails&quot;, &quot;~&gt; 3.6.0&quot;
  gem 'selenium-webdriver'
end

group :test do
  gem &quot;capybara&quot;, &quot;~&gt; 2.14.0&quot;
end
</pre>
<p>rails_helper:</p>
<pre class="brush: ruby; title: ; notranslate">
config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  #Puma makes it possible to run RSpec with ActionCable
  Capybara.server = :puma

  Capybara.register_driver :selenium_chrome do |app|
    Capybara::Selenium::Driver.new(app, browser: :chrome)
  end
  Capybara.javascript_driver = :selenium_chrome

</pre>
<p>And, the <a href="https://sites.google.com/a/chromium.org/chromedriver/" target="_blank">driver</a>:</p>
<pre class="brush: plain; title: ; notranslate">
brew install chromedriver
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
