<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Seth Tucker</title>
    <description>The latest articles on DEV Community by Seth Tucker (@crimson-knight).</description>
    <link>https://dev.to/crimson-knight</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F355228%2Fd3bcff21-679d-49d4-ab19-0c2f34a02b43.jpeg</url>
      <title>DEV Community: Seth Tucker</title>
      <link>https://dev.to/crimson-knight</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crimson-knight"/>
    <language>en</language>
    <item>
      <title>An Introduction To App Development With Advanced Vibe Coding</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Sat, 17 May 2025 15:42:22 +0000</pubDate>
      <link>https://dev.to/crimson-knight/an-introduction-to-app-development-with-advanced-vibe-coding-joc</link>
      <guid>https://dev.to/crimson-knight/an-introduction-to-app-development-with-advanced-vibe-coding-joc</guid>
      <description>&lt;p&gt;Written Date: 5/17/2025&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Need This Process
&lt;/h2&gt;

&lt;p&gt;For the last 2 years I've been using AI to drive most of my development efforts. It wasn't called "vibe coding" then, but it was just as frowned upon. The phrase "AI slop" was coined and tossed around willy nilly. Under every rock and around every corner you would find an expert ready to jump out and say that AI was overhyped. It was tenuous time to be using AI for helping you code.&lt;/p&gt;

&lt;p&gt;I will admit that the concerns brought forward were true then and are still mostly true now. However, there were these moments of brilliance that would shine through. These felt like samples of what was to come in the future, something that we would eventually consider to be just plain normal.&lt;/p&gt;

&lt;p&gt;I've held on to the inspiration and hope that came from those moments of clarity and brilliance. My wish was to find a way to make them happen consistently. That's a thing you'll come to discover about AI, consistency is the hard part. It's easy to make something incredible happen once. Can you make make it happen two times? Five times? What about nine out of ten times? Can you make AI work consistently 100% of the time?&lt;/p&gt;

&lt;p&gt;The five layer design approach is the wisdom I've been able to bring together from thousands of hours of trial and error from the past several years. It is in no way complete. Consider this the first assembly line for the Model T. There will be many improvements and refinements to this process as time goes on. I expect there will be competing schools of theory emerging at some point. We'll see whose idea wins 😉.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What's The Point?
&lt;/h2&gt;

&lt;p&gt;This question is easy. The point is to create a process the produces a more consistent performance from AI models. If we were talking about people, we would refer to this as "gaining experience and building expertise". However, AI models don't learn the same way people do. They only imitate human behavior. Because of this, we need a process that consistently represents the kind of process that we as humans naturally develop during our experiences.&lt;/p&gt;

&lt;p&gt;This is how I came to define the 5 layers. It defines a process that can be performed over and over, with adjustments and customizations, to yield a consistently successful outcome using AI models as the main work horse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Slaughtering Sacred Cows
&lt;/h2&gt;

&lt;p&gt;From here on out I'll only be discussing the concept of the five layers and how to vibe code your way through them from a high-level perspective. That's because the code does not matter.&lt;/p&gt;

&lt;p&gt;Code as art is no longer important. It never really was, but most engineers who come from a pre-AI era have gone through a period of time where their code was their art. The vibe coding era is the beginning of the death of boutique code as art.&lt;/p&gt;

&lt;p&gt;I will slaughter many sacred cows as I wrote about this. It's important to do. It's a resetting of the first principles that is required in order for software development to move forward as an industry.&lt;/p&gt;

&lt;p&gt;I look forward to seeing what you build. Vibe on my friend.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Advanced Vibe Coding</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Sun, 11 May 2025 13:44:19 +0000</pubDate>
      <link>https://dev.to/crimson-knight/advanced-vibe-coding-560i</link>
      <guid>https://dev.to/crimson-knight/advanced-vibe-coding-560i</guid>
      <description>&lt;p&gt;These days, you can't exist as a developer without hearing about vibe coding. It's the cool new thing that lets "technically dangerous" people write code.&lt;/p&gt;

&lt;p&gt;Personally, I've been vibing since November 2023 when GPT 3.5 was launched. Not in the same willy-nilly "throw it against the wall and see what sticks" approach. That's hard no for me.&lt;/p&gt;

&lt;p&gt;There's too many valid and real problems with the spaghetti wall approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Models don’t understand the big picture, then make design decisions that hurt more later (or spiral into a death loop right now)&lt;/li&gt;
&lt;li&gt;When the models start out with a clear objective, they still get lost (dazed and confused) as complexity grows&lt;/li&gt;
&lt;li&gt;Models do still succeed, but the code is disorganized. So when you try adding to it, the model starts breaking it’s own work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many more, but these are the biggest points that everyone will experience as they attempt using AI to help with coding. If you're traditionally vibing, you'll feel every point here &lt;em&gt;much&lt;/em&gt; sooner than later.&lt;/p&gt;

&lt;p&gt;Even though context windows have multiplied in size since GPT 3.5, the same problems are still plaguing us. This means we need &lt;em&gt;new&lt;/em&gt; and different tools in our proverbial tool belt in order to effectively build with AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing: Advanced Vibe Coding
&lt;/h2&gt;

&lt;p&gt;The premise here is simple: have a structured approach that allows you to maintain the correct context throughout your project as you work through building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F831rl26lp2pzuqei37f5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F831rl26lp2pzuqei37f5.jpeg" alt="Advanced Vibe Coding Main Points" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Right Model, Right Task
&lt;/h3&gt;

&lt;p&gt;This probably feels obvious, but it's not. Large language models are the first time we can experience tech that can present as being good at something, but it actually doesn't understand what it's doing. Have you ever seen a person work who strongly believed they knew what they were doing, but to your experienced eye they clearly did &lt;em&gt;not&lt;/em&gt; know what they were doing? The same thing applies to large language models. Don't stick to a single model for anything, you know have a team. A &lt;strong&gt;mixture of experts&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Have A Clear Outcome In Mind
&lt;/h3&gt;

&lt;p&gt;Most of your disappointment is going to come from having an expectation that is unmet. You'll dictate something to the model and it'll implement it and you'll quickly realize that what you meant and what it did are not the same, but at the same time the model was still "correct" in what it did. This is a very human problem that we now get to share with our technology. This problem goes away as teams work together and build experience, but models don't build experience like this. You'll grow this skill over time and through experience. This is extremely dependent on your communication style and the context in which you are working. Embrace your uniqueness, have some patience and chill.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Work A Process.
&lt;/h3&gt;

&lt;p&gt;You need to work a thought process on behalf of the model. Reasoning models are great, but the experience of working with an LLM is still very much like having an incredibly smart but immature junior. There isn't much thinking things through or planning, it's prompt -&amp;gt; output. Reasoning models are better, but those reasoning tokens take away context from the big picture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your job is to guide the model through a process that maintains the bigger from the perspective that is necessary to implement the feature&lt;/strong&gt;. That perspective varies depending on which point of the development process you are in. As humans, this is what we develop as part of our "expertise". Models don't have that, they have knowledge. This is the first time we are experiencing working with something that is so deeply knowledgable but wildly inexperienced.&lt;/p&gt;

&lt;p&gt;Use your wisdom to guide the models use of knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Process - 5 Layer App Development
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3my4guokrme5em4uf3l.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3my4guokrme5em4uf3l.jpeg" alt="The 5 Layers" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The process I came up with is what I call "5 Layer App Development", or "5 Layers" for short.&lt;/p&gt;

&lt;p&gt;If you're an experienced developer, this will not teach you anything that you don't already know. It will organize the way we normally think about working on a feature into a set of clearly purposeful steps so that we can guide a model successfully.&lt;/p&gt;

&lt;p&gt;The five layers are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Jargon Feature Stories&lt;/li&gt;
&lt;li&gt;Generic Feature Stories&lt;/li&gt;
&lt;li&gt;Technical Feature Stories&lt;/li&gt;
&lt;li&gt;Pseudo Code&lt;/li&gt;
&lt;li&gt;Actual Code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They are ordered from least descriptive of the technical implementation to the most descriptive of the technical implementation.&lt;/p&gt;

&lt;p&gt;Greenfield applications start at Layer 2. As an app matures you'll begin working from Layer 1. This just happens, as it's entirely normal to begin using jargon to describe the larger complex features of your application. I'm not going to give an in-depth explanation of the process in this post, I will do that in a follow-up post. It's complex enough that it's worthy of possibly a series.&lt;/p&gt;

&lt;p&gt;Most of your work is going to be during layers 1 &amp;amp; 2. If you properly define the feature you're trying to implement (or the bug you're trying to squash) then your involvement will largely end during layer  3. If you're familiar with agile stories at all, then you'll easily be able to write feature stories in layers 1 &amp;amp; 2.&lt;/p&gt;

&lt;p&gt;"Why not call them user stories?"&lt;/p&gt;

&lt;p&gt;Because not everything is about a human user anymore. Now that we have MCP and internet capable models, we are going to see a rise of AI users being the "third consumer". I'll write about this later in another post.&lt;/p&gt;

&lt;p&gt;Layer 3 is where we get technical. It's where we identify the tech we have, if it's capable of performing the feature we are implementing and how to address any limitations of the tech stack we have.&lt;/p&gt;

&lt;p&gt;Layer 4 is basically like using a Chain-of-Thought (CoT) prompting strategy. Feed in the refined feature story, provide the technical capabilities from layer 3 and have your model make the outline of it's implementation plan.&lt;/p&gt;

&lt;p&gt;Layer 5 is pretty straight forward... but in case you missed it, have the model read from the layer 4 description and implement the actual code. The larger the implementation steps necessary, the more you'll benefit from breaking this down into modular chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Layer Rules
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2bi674at7xqrmhzcxq5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2bi674at7xqrmhzcxq5.jpeg" alt="5 Layer Rules" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rules for working through the layers are straightforward (for now). There's a lot more to it, but you don't &lt;em&gt;have&lt;/em&gt; to know the nuance to be effective and see benefits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start by working "down".
&lt;/h3&gt;

&lt;p&gt;Start at the highest layer that applies. This is always layer 1 or 2. &lt;/p&gt;

&lt;h3&gt;
  
  
  Focus on 1 feature at a time
&lt;/h3&gt;

&lt;p&gt;Scope creep anyone? You're working from top to bottom and then eventually back up the layers. You're traversing a loop of productivity. Working "up" requires more than just some ideas, so that's outside the scope of this post 😏&lt;/p&gt;

&lt;h3&gt;
  
  
  Always use git commits (at least) between layers
&lt;/h3&gt;

&lt;p&gt;Commit often. Use a branch, stay organized and commit, commit, commit. You'll naturally find ways to write meaningfully informative commit messages because you can tie it back to the task the model was performing for the layer you were in.&lt;/p&gt;

&lt;p&gt;Example: "Refined layer 2 feature story for 'x'. Saving before beginning layer 3 analysis".&lt;/p&gt;

&lt;p&gt;Do you feel like a mad scientist yet? 😆&lt;/p&gt;

&lt;h3&gt;
  
  
  Guide the model
&lt;/h3&gt;

&lt;p&gt;You're job here is to offload the nuanced cognitive load that comes from planning, execution and reflection. Making plans is hard and decision intense. We have a limited amount of decisions we can make in a day. Let the model think for you. Let the model use it's infinite decision making capabilities to handle the mundane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Have Fun
&lt;/h3&gt;

&lt;p&gt;I shouldn't have to tell you this, but here I am. It's "vibe" coding for a reason. Feel the vibes man! If you're not feeling the vibes, it's a huge red flag that you're not thinking straight. Frustrated, angry or otherwise negative emotions block freeform thinking. If you're stuck, feeling anything other than in the zone, then reset yourself (and probably start a fresh chat with the model).&lt;/p&gt;

&lt;p&gt;Good Vibez Only&lt;br&gt;
-- Seth, The Crimson Knight&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
    </item>
    <item>
      <title>Integrating Jennifer &amp; Amber v1.0+</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Mon, 04 Dec 2023 13:09:23 +0000</pubDate>
      <link>https://dev.to/crimson-knight/integrating-jennifer-amber-v10-1e7h</link>
      <guid>https://dev.to/crimson-knight/integrating-jennifer-amber-v10-1e7h</guid>
      <description>&lt;p&gt;To get the full experience with Jennifer, we'll first want to install &lt;code&gt;sam&lt;/code&gt;. This is Jennifer's CLI tool that has generators for creating models, managing the db creation/resetting, migrations, etc. that aren't currently able to be added quickly and easily into the Amber CLI. So this gives users a way to still have the CLI conveniences of a framework. The nice part is that once &lt;code&gt;sam&lt;/code&gt; is installed and working, this also means Amber will work.&lt;/p&gt;

&lt;p&gt;First, add the &lt;code&gt;sam&lt;/code&gt; shard to your &lt;code&gt;shards.yml&lt;/code&gt;, it should be added under the "development dependencies" sections like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;development_dependencies:
  ameba:
    github: crystal-ameba/ameba
    version: ~&amp;gt; 1.5.0
  sam:
    github: imdrasil/sam.cr
    version: ~&amp;gt; 0.5.0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, remove the entry for &lt;code&gt;granite&lt;/code&gt; and &lt;code&gt;citrine-i18n&lt;/code&gt;, and add the shards for Jennifer and your database adapter of choice. Your &lt;code&gt;dependencies&lt;/code&gt; section should look like this on a brand new Amber app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies:
  amber:
    github: amberframework/amber
    version: 1.4.1 # Make sure to use the latest version of Amber here

  quartz_mailer:
    github: amberframework/quartz-mailer
    version: ~&amp;gt; 0.8.0

  jasper_helpers:
    github: amberframework/jasper-helpers
    version: ~&amp;gt; 1.2.2 

  jennifer:
    github: imdrasil/jennifer.cr
    version: "~&amp;gt; 0.13.0"

  pg:
    github: will/crystal-pg
    version: ~&amp;gt; 0.26.0

  # Uncomment to use MySQL. Note: on MySQL v8, this shard does not support the v8 authentication protocol, you'll want to run:
  # `mysql --default-auth=mysql_native_password` for MySQL to work properly
  #mysql:
    #github: crystal-lang/crystal-mysql
    #version: ~&amp;gt; 0.14.0

  # Uncomment to use sqlite
  #jennifer_sqlite3_adapter:
    #github: imdrasil/jennifer_sqlite3_adapter
    #version: "~&amp;gt; 0.4.0"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save your &lt;code&gt;shard.yml&lt;/code&gt; file at this point.&lt;/p&gt;

&lt;p&gt;Next, create a new file in your projects root folder called &lt;code&gt;override.shard.yml&lt;/code&gt; and save the following into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i18n:
  github: crimson-knight/i18n.cr
  branch: master

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now install your shards by running &lt;code&gt;shards install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You may get something like the warning below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shard "ameba" may be incompatible with Crystal 1.10.1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can usually safely ignore this warning, or adjust the version down until the warning goes away. Eitherway, it's safe to move on.&lt;/p&gt;

&lt;p&gt;Next, we are going to change the file contents of the &lt;code&gt;config/database.cr&lt;/code&gt; file to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require "jennifer"
require "jennifer/adapter/postgres" # for PostgreSQL
# require "jennifer/adapter/mysql" for MySQL
# require "jennifer/adapter/sqlite" for SQLite

# This allows for easy test db use when running the `crystal spec` command
{% if @top_level.has_constant? "Spec" %}
  APP_ENV = "test"
{% else %}
  APP_ENV = ENV["APP_ENV"]? || "development"
{% end %}

Jennifer::Config.configure do |conf|
  conf.read("config/database.yml", APP_ENV)
  conf.from_uri(ENV["DATABASE_URI"]) if ENV.has_key?("DATABASE_URI")
  conf.pool_size = (ENV["DB_CONNECTION_POOL"] ||= "5").to_i
  conf.logger.level = APP_ENV == "development" ? Log::Severity::Debug : Log::Severity::Error
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll want to create the file containing our database connection details. This will look very similar to the database configuration in a Rails app, but don't be fooled! These are not identical :)&lt;/p&gt;

&lt;p&gt;Create the file &lt;code&gt;config/database.yml&lt;/code&gt; with the following contents, updated with your apps details. None of the databases need to be created yet, just put what you'd like your app databases to be called. If you use an existing database, it should still connect with the correct details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default: &amp;amp;default
  host: localhost
  user: user_name # On Mac, this is the results from a fresh terminal session when you run `whoami`
  password: user_password # On Mac, this is blank by default, use empty quotes '' for this.
  adapter: postgres # or `mysql`, or `sqlite` without quotes

development:
  &amp;lt;&amp;lt;: *default
  db: application_database_name_development

test:
  &amp;lt;&amp;lt;: *default
  db: application_database_name_test

production:
  &amp;lt;&amp;lt;: *default
  db: application_database_name_production

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we'll want to modify the &lt;code&gt;sam.cr&lt;/code&gt; file found in your projects root after installation the shards.&lt;/p&gt;

&lt;p&gt;The contents should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/crystal

require "./config/database.cr" # here load jennifer and all required configurations
require "sam"
load_dependencies "jennifer"

# Here you can define your tasks
# desc "with description to be used by help command"
# task "test" do
# puts "ping"
# end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be able to run: &lt;code&gt;crystal run sam.cr help&lt;/code&gt; from the projects root in your command line and see output something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; jennifer_config crystal sam.cr help
Name Description
------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------
help | Prints description for all tasks
generate:makefile | Generates makefile extension. Now command could be executed via `make sam your:command argument`
generate:migration | Generates migration template. Usage - generate:migration &amp;lt;migration_name&amp;gt;
generate:model | Generates model and migrations template. Usage - generate:model &amp;lt;ModelName&amp;gt;
db:migrate | Will call all pending migrations
db:step | Invoke next migration. Usage: db:step [&amp;lt;count&amp;gt;]
db:rollback | Rollback migration. Usage: db:rollback [v=&amp;lt;migration_exclusive_version&amp;gt; | &amp;lt;count_to_rollback&amp;gt;]
db:drop | Drops database
db:create | Creates database
db:seed | Populate database with default entities.
db:setup | Runs db:create, db:migrate and db:seed
db:version | Prints version of the last run migration
db:schema:load | Loads database structure from the structure.sql file

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've run into any errors, double check that you've saved all of the files with the most recent changes, and that there are no typos.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Important Note&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Check in the &lt;code&gt;src/&lt;/code&gt; folder to see if there is already a &lt;code&gt;models&lt;/code&gt; folder. As of Amber v1.4 the &lt;code&gt;new&lt;/code&gt; command does not create this folder and this is the destination for models being created by &lt;code&gt;generate:model&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once you create the &lt;code&gt;src/models&lt;/code&gt; folder, you should now be able to generate a model:&lt;/p&gt;

&lt;p&gt;I like to build sam once and then re-run it. If you change anything in the &lt;code&gt;sam.cr&lt;/code&gt; file, you'll need to recompile to pick up any changes outside of yml files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crysatl build sam.cr


./sam generate:model example_model name:string


./sam db:create


./sam db:migrate

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your database should now be setup and operational with basic generators for &lt;code&gt;Jennifer&lt;/code&gt; working.&lt;/p&gt;

&lt;p&gt;And, you're done! Your app should now work with Jennifer. The Amber CLI and &lt;code&gt;sam&lt;/code&gt; don't yet work together, but the controller and view generators should get you very close to a working initial template with only minor changes to make your app compile.&lt;/p&gt;

&lt;p&gt;Make sure to use same for creating/seeding/migrating your database.&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>crystallang</category>
      <category>amber</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building An AI Automated Print-On-Demand Business: An Experiment</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Wed, 26 Apr 2023 16:42:49 +0000</pubDate>
      <link>https://dev.to/crimson-knight/building-an-ai-automated-print-on-demand-business-an-experiment-12el</link>
      <guid>https://dev.to/crimson-knight/building-an-ai-automated-print-on-demand-business-an-experiment-12el</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Last month (March) I was sitting with a friend in our "idea factory" one afternoon while he was showing me &lt;a href="https://midjourney.com" rel="noopener noreferrer"&gt;Midjourney&lt;/a&gt;. I was blown away at how good AI art is getting. Both of us have marketing backgrounds, strongly focused on growth hacking. Even though I have a background in marketing, I'm not naturally a creative or artistic person. My little brother got that talent, I got an analytical mind.&lt;/p&gt;

&lt;p&gt;But, what if I could use something like Midjourney to create products?&lt;/p&gt;

&lt;p&gt;Could I automate everything?&lt;/p&gt;

&lt;p&gt;I dove in head first to start exploring my options and figuring things out. As a Rubyist, I naturally created my online store on Shopify. Just kidding, it's the incredible amount of integrations that Shopify has that made this a no-brainer for me.&lt;/p&gt;

&lt;p&gt;So, in true soloprenuer fashion, I also had to dive into YouTube to figure out what needs to be done. There are some fantastic channels out there with loads of content! However, I found one thing commonly true across nearly all of the videos out there: they undersell the level of effort required to start a print-on-demand business.&lt;/p&gt;

&lt;p&gt;There are a lot of subtle nuanced steps involved and frankly, a hell of a lot of copy+paste happening. As a developer, this is absolutely MADDENING!&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Model Outline
&lt;/h2&gt;

&lt;p&gt;Let's dive right into the business model outline here. To frame this model, there are two goals that I am working toward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;100% Automation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enough passive income to replace my salary entirely.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My first goal isn't entirely possible. If you're unfamiliar with Midjourney, this is when you learn that the bot you use to interact with to generate images is tied to the specific Discord user who signed up. Pretty smart, but that means I can't write my own bot to interact with it. I'm also not sure I want to program entirely randomized phrases (or full product descriptions) getting turned into images and placed on products for printing that I haven't laid eyes on. There's also some photo editing that has to happen, particularly upscaling the images to maximize the quality of the print.&lt;/p&gt;

&lt;p&gt;Here's a nice canvas outline showing, overly simplified, the steps required to create and publish a product:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y4ot9u4wha5mqh9s9as.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y4ot9u4wha5mqh9s9as.png" alt="screen shot showing 5 steps to creating print on demand products" width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 1 can't really be automated without using techniques that go against Discords TOS. Step 2 has interesting automation aspects. After hours of YouTube videos on how to use Photoshop, I found &lt;a href="https://www.youtube.com/watch?v=-t8zcTfiG7c&amp;amp;list=PLLUm60Ryfe92h__1f0YA7E-bGC9hd04ib&amp;amp;index=3&amp;amp;t=378s" rel="noopener noreferrer"&gt;this YouTube video&lt;/a&gt; for bulk-processing image mockups. This guy is using a Javascript automation extension that was created back in the mid-2000s to automate a lot of his mockup process. Cool!&lt;/p&gt;

&lt;p&gt;This isn't the kind of automation that I was hoping for, but I'll still take whatever I can get.&lt;/p&gt;

&lt;p&gt;For steps 3 and 4 I had to take a closer look at my service providers and determine if there is an API I can use. Shopify has a famous API, but does my print provider?&lt;/p&gt;

&lt;p&gt;For this experiment, I decided on using Printful. Of the three most popular, they are the most expensive but they offer the least amount of manual intervention for subtle nuanced customer service tasks. Also, the difference in price doesn't appear to make a meaningful difference until the scale of your store sales is very high. Since my goal is to optimize for automation I think Printful makes the most sense.&lt;/p&gt;

&lt;p&gt;Printful appears to have an API that is rather robust. The docs are pretty decent, better than some enterprise API's I've worked on. But, some context from their API is missing. For example, the API documentation shows that I can create a product template by providing the details of the location on a print product and a link to the image. But then how does positioning work? What if I want to use a repeating pattern? 🤔 After doing some searching I found that no one is writing about this API in public. Probably so they don't give away their secret sauce recipe 😏&lt;/p&gt;

&lt;p&gt;This product template creation is easily one of the most critical and cumbersome steps in this entire print-on-demand business workflow.&lt;/p&gt;

&lt;p&gt;In the next part of this series, I'll go through how I architect the solution and structure my code.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Crystal PCRE2 Upgrade Guide</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Mon, 06 Mar 2023 21:31:31 +0000</pubDate>
      <link>https://dev.to/crimson-knight/crystal-pcre2-upgrade-guide-58eo</link>
      <guid>https://dev.to/crimson-knight/crystal-pcre2-upgrade-guide-58eo</guid>
      <description>&lt;p&gt;It was announced recently that in Crystal 1.8.0 &amp;gt;= 2.0.0 the default Regex engine is going to be PCRE2.&lt;/p&gt;

&lt;p&gt;After doing some digging &lt;a href="https://github.com/PCRE2Project/pcre2/issues/51" rel="noopener noreferrer"&gt;into this thread on Github&lt;/a&gt; it appears that the original developers of PCRE don't remember what was done or aren't around for comment any more.&lt;/p&gt;

&lt;p&gt;Sadly, this looks like it's a case of bit rot and an abandoned open source project that was poorly documented. It appears the decision to move forward was with upgrading/rewriting the library is what allowed the current maintainers to fix the known issues while making some improvements. However there wasn't any documentation built along the way, which makes the transition a bit daunting because there is a possibility for undocumented breaking changes.&lt;/p&gt;

&lt;p&gt;I'm in a similar situation with the &lt;a href="https://amberframework.org/" rel="noopener noreferrer"&gt;Amber Framework&lt;/a&gt;. I'm not one of the original creators of Amber, I just took it over last year (2022). So I have a decent size code base to maintain with potential for breaking changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here's The Plan
&lt;/h2&gt;

&lt;p&gt;Here's how I'm going through and verifying the behavior does not change:&lt;/p&gt;

&lt;p&gt;Step 1. Identify all possible regex's used in the code base.&lt;/p&gt;

&lt;p&gt;Step 2. Create tests that uniquely test those regular expressions.&lt;/p&gt;

&lt;p&gt;Step 3. Switch to using PCRE2 in the tests and see if anything blows up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before We Begin
&lt;/h3&gt;

&lt;p&gt;Let's make sure we have PCRE2 installed on our systems. I work on a Mac, so I'll be using Homebrew to install PCRE2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install pcre2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1. Identifying Regexs
&lt;/h2&gt;

&lt;p&gt;My preferred editor is Visual Studio Code which has a handy feature that allows me to search with... regexs! Since it's easy to find the syntaxes for regular expressions in Crystal, I'll summarize them here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="sr"&gt;/123/&lt;/span&gt;  &lt;span class="c1"&gt;# Uses the slash regex literal `/` with an enclosing `/`&lt;/span&gt;
&lt;span class="c1"&gt;# Sometimes this syntax will appear with wrapping parentheses, sometimes without.&lt;/span&gt;
&lt;span class="s2"&gt;"abcd"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt; &lt;span class="sr"&gt;/abcd/&lt;/span&gt;
&lt;span class="s2"&gt;"abcd"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/abcd/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Converts the string into &lt;/span&gt;

&lt;span class="c1"&gt;# the percent regex literal and the valid delimiters &lt;/span&gt;
&lt;span class="c1"&gt;# which are: (), [], {}, &amp;lt;&amp;gt;, ||&lt;/span&gt;
&lt;span class="sr"&gt;%r((/))&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; /(\/)/&lt;/span&gt;
&lt;span class="sr"&gt;%r[[/]]&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; /[\/]/&lt;/span&gt;
&lt;span class="sr"&gt;%r{{/}}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; /{\/}/&lt;/span&gt;
&lt;span class="sr"&gt;%r&amp;lt;&amp;lt;/&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; /&amp;lt;\/&amp;gt;/&lt;/span&gt;
&lt;span class="sr"&gt;%r|/|&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; /\//&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us 7 combinations of how regular expressions can be found across the code bases.&lt;/p&gt;

&lt;p&gt;The reference material for how Visual Studio Code handles regular expressions left me a little frustrated at the lack of specificity or clear exploration path for additional resources. So, through some trial and error I managed to create this regular expression that properly matches all of the above syntaxes for Crystals regular expression syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="se"&gt;\/&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt; |&lt;span class="se"&gt;\(\/&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\/\)&lt;/span&gt;|%r&lt;span class="se"&gt;\(\(&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\)\)&lt;/span&gt;|%r&lt;span class="se"&gt;\[\[&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\]\]&lt;/span&gt;|%r&lt;span class="se"&gt;\{\{&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\}\}&lt;/span&gt;|%r&amp;lt;&amp;lt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;|%r&lt;span class="se"&gt;\|&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;|Regex.new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Import Note&lt;/strong&gt; there is a leading space at the beginning of this regex. This is important! Make sure you add that space if it is not present when you copy/paste.&lt;/p&gt;

&lt;p&gt;You can verify this is still working as expected by copying the regular expression syntax into a file in your project and using the project search feature with that expression. It should match both the &lt;code&gt;%r&lt;/code&gt; syntax and the commented output with the double forward slashes but ignore the &lt;code&gt;# =&amp;gt;&lt;/code&gt; text, the &lt;code&gt;Regex.new&lt;/code&gt; and the more vague double forward slash syntax &lt;code&gt;/.../&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When I ran this regex on the Amber code base (Amber v1.3) I got these results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikyznjpghbcp8byhtkdh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikyznjpghbcp8byhtkdh.png" alt="screen shot of 31 results in 10 files searched" width="463" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;31 results in 10 files. Not bad, this turned out to be more manageable than I first expected it would be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2. Testing The Found Regular Expressions
&lt;/h2&gt;

&lt;p&gt;I can pretty easily grab all of my results here by clicking on the "Open in editor" link just below the search fields in the project search field.&lt;/p&gt;

&lt;p&gt;The text document that pops up has just enough info to be dangerous. Let's copy that and make a new spec file. For Amber, I'm going to put this right in the root &lt;code&gt;spec&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Now it's time to de-dupe my results. The results aren't large, and I can see the same regex used in a &lt;code&gt;split()&lt;/code&gt; multiple times, otherwise everything looks unique. Now I'm down to 27 tests to make.&lt;/p&gt;

&lt;p&gt;I'm taking a couple of approaches due to patterns that start to appear in the regex's I'm seeing. We have a group that are unique in the total contents, but they are ultimately testing the beginning and ending of the same regex like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/amber/cli/recipes/recipe_fetcher_spec.cr:&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+mydefault\/app$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+mydefault\/controller$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+mydefault\/model$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+mydefault\/scaffold$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+mydefault\/.recipes\/lib\/amber_granite\/app$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+\.recipes\/lib\/amber_granite\/controller$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+\.recipes\/lib\/amber_granite\/model$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+\.recipes\/lib\/amber_granite\/scaffold$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll narrow this down to a test with &lt;code&gt;/.+\.test\/path$/&lt;/code&gt; which should be testing a string that has "test/path" in it's contents.&lt;/p&gt;

&lt;p&gt;As I worked my way through the regexs to test, I noticed a large grouping from a monkey patch module for the &lt;code&gt;String&lt;/code&gt; class. Personally, I'm not a fan of monkey patching and these regexs are pretty old with other methods from the std lib now available to do some of these same things. I also noticed there's already a spec specifically for those methods, so I'm going to skip making unique tests for them.&lt;/p&gt;

&lt;p&gt;All in all, I ended up with only 9 tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./spec_helper"&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Testing regular expressions"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="c1"&gt;# spec/amber/cli/commands/exec_spec.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#  43:         logs = `ls tmp/*_console_result.log`.strip.split(/\s/).sort&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verifies the regex splits on a space when using `/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;/`"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;string_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test string"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\s/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;
    &lt;span class="n"&gt;string_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;string_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# spec/amber/cli/recipes/recipe_fetcher_spec.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   21:           template.should match(/.+mydefault\/app$/)&lt;/span&gt;
  &lt;span class="c1"&gt;#   27:           template.should match(/.+mydefault\/controller$/)&lt;/span&gt;
  &lt;span class="c1"&gt;#   33:           template.should match(/.+mydefault\/model$/)&lt;/span&gt;
  &lt;span class="c1"&gt;#   39:           template.should match(/.+mydefault\/scaffold$/)&lt;/span&gt;
  &lt;span class="c1"&gt;#   57:           template.should match(/.+mydefault\/.recipes\/lib\/amber_granite\/app$/)&lt;/span&gt;
  &lt;span class="c1"&gt;#   77:           template.should match(/.+\.recipes\/lib\/amber_granite\/controller$/)&lt;/span&gt;
  &lt;span class="c1"&gt;#   86:           template.should match(/.+\.recipes\/lib\/amber_granite\/model$/)&lt;/span&gt;
  &lt;span class="c1"&gt;#   95:           template.should match(/.+\.recipes\/lib\/amber_granite\/scaffold$/)&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verifies the 1 or more of any starting character, ending with a set specific string"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="n"&gt;test_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blahblah/blah/bblahaaaa1233123412341234123423this/is/a/path"&lt;/span&gt;
   &lt;span class="n"&gt;test_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+this\/is\/a\/path$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_string2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blahblah4321341234!!!!.this/is/a/test/path"&lt;/span&gt;
    &lt;span class="n"&gt;test_string2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+\.this\/is\/a\/test\/path$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# spec/amber/pipes/static_spec.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   57:         response_true.body.should match(/index/)&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verfies a basic plain set of characters in a regex works"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# This test is so basic is probably could have been skipped, but I kept it for consistency sake&lt;/span&gt;
    &lt;span class="s2"&gt;"has the word index in it"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/index/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# spec/support/helpers/cli_helper.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   123:     route_table_text.split("\n").reject { |line| line =~ /(─┼─|═╦═|═╩═)/ }&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verifies the regex for removing box drawing characters"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;test_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"═╩═&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;here is a&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;test string with new lines&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;─┼─&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;another line&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;═╦═"&lt;/span&gt;

    &lt;span class="n"&gt;split_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;/(─┼─|═╦═|═╩═)/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;split_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;split_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"here is a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;split_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"another line"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# src/amber/cli/generators.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   216:      if name.match(/\A[a-zA-Z]/)&lt;/span&gt;
  &lt;span class="c1"&gt;# src/amber/cli/recipes/recipe.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   36:       if name.match(/\A[a-zA-Z]/)&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verifies a string starts with A-z (upper and lowercase)"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;string1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Apex Legends123123"&lt;/span&gt;
    &lt;span class="n"&gt;string2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"123Googal"&lt;/span&gt;
    &lt;span class="n"&gt;string3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lower case"&lt;/span&gt;

    &lt;span class="n"&gt;string1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\A[a-zA-Z]/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;string2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should_not&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\A[a-zA-Z]/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;string3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\A[a-zA-Z]/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# src/amber/cli/commands/pipelines.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   91:           pipes = pipes.split(/,\s*/).map(&amp;amp;.gsub(/[:\"]/, ""))&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verifies the string is split after a comma with multiple white spaces and removal of colons and quotes from the results"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;":split,    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;this&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,       string, properly"&lt;/span&gt;
    &lt;span class="n"&gt;final_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/,\s*/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/[:\"]/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;final_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;final_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"split"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;final_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"properly"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;final_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/:/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;final_array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\"/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


  &lt;span class="c1"&gt;# src/amber/cli/recipes/file_entries.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   50:           if /^(.+)\.lqd$/ =~ filename || /^(.+)\.liquid$/ =~ filename&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verifies the line beings with any character and ends up .ldq or .liquid"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;string1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blah_blah blah.lqd"&lt;/span&gt;
    &lt;span class="n"&gt;string2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blah blah blah.liquid"&lt;/span&gt;
    &lt;span class="n"&gt;string3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"liquid.blahblah"&lt;/span&gt;

    &lt;span class="n"&gt;string1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(.+)\.lqd$/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;string1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(.+)\.liquid$/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;string2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(.+)\.lqd$/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;string2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(.+)\.liquid$/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;string3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(.+)\.lqd$/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;string3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(.+)\.liquid$/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# src/amber/pipes/static.cr:&lt;/span&gt;
  &lt;span class="c1"&gt;#   191:         match = range.match(/bytes=(\d{1,})-(\d{0,})/)&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"verifies a &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Range&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; header has two sets of values separated by a hyphen with 1+ values before the hyphen and 0+ values after the hyphen"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;range1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bytes=1231234-12421341"&lt;/span&gt;
    &lt;span class="n"&gt;range2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bytes=0-1241234"&lt;/span&gt;

    &lt;span class="n"&gt;range1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/bytes=(\d{1,})-(\d{0,})/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;range2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/bytes=(\d{1,})-(\d{0,})/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything currently passes when running &lt;code&gt;crystal spec spec/pcre2_regex_upgrade_spec.cr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now I've ended up with only 9 tests. Not bad!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 Testing PCRE2 - Does It Explode?
&lt;/h2&gt;

&lt;p&gt;Thankfully this part is pretty easy. If you already have pcre2 installed, all you have to do is add a flag to our test command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crystal spec &lt;span class="nt"&gt;-Duse_pcre2&lt;/span&gt; spec/pcre2_regex_upgrade_spec.cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is now using the PCRE2 api instead of PCRE.&lt;/p&gt;

&lt;p&gt;Everything is still passing, that's great!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final results
&lt;/h2&gt;

&lt;p&gt;I decided to re-run the tests specifically across the entire code base by customization the &lt;code&gt;bin/amber_spec&lt;/code&gt; file to use the &lt;code&gt;-Duse_pcre2&lt;/code&gt; flag and was able to get a clean test run.&lt;/p&gt;

&lt;p&gt;So as best I can tell, Amber v1.3 will support the migration from PCRE -&amp;gt; PCRE2 without any hiccups.&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>amberframework</category>
      <category>upgrades</category>
      <category>maintenance</category>
    </item>
    <item>
      <title>Dual-booting Rails 7 &amp; Kemal (a Crystal framework)</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Fri, 29 Apr 2022 13:42:47 +0000</pubDate>
      <link>https://dev.to/crimson-knight/dual-booting-rails-7-kemal-a-crystal-framework-1j2p</link>
      <guid>https://dev.to/crimson-knight/dual-booting-rails-7-kemal-a-crystal-framework-1j2p</guid>
      <description>&lt;p&gt;View the source for this &lt;a href="https://github.com/crimson-knight/crystal-in-rails" rel="noopener noreferrer"&gt;on github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While working on a start up idea I have an interesting thought occurred to me: could I run Rails and a Crystal framework in parallel?&lt;/p&gt;

&lt;h2&gt;
  
  
  The "why" behind the idea
&lt;/h2&gt;

&lt;p&gt;My side project idea revolves around a snippet of JS code that triggers a chat bot interaction, so I have two end points this snippet will interact with as part of this. Every time a page loads with this snippet a request gets sent to my app. This means adding a single customer could mean adding hundreds of thousands of requests per month. My first thought was dual-booting Rails with Sinatra or even Roda. However I really want to adopt Crystal as much as possible because I believe the language has enormous potential, so why not try and create a developer experience that is just like working with two ruby frameworks at the same time? Could I use this as a strangler approach to slowly move individual end-point behavior from Rails into Crystal and get the significant performance boost a compiled language has to offer?&lt;/p&gt;

&lt;p&gt;For this article I'm going to walk you through step by step how to get Kemal running alongside your Rails app within the same directory to create a developer experience that feels like you're working on a pure Ruby app!&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Here's what we'll be using for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails 7 (latest)&lt;/li&gt;
&lt;li&gt;Ruby 3.1&lt;/li&gt;
&lt;li&gt;Crystal 1.4&lt;/li&gt;
&lt;li&gt;Kemal (latest, currently v1.2)&lt;/li&gt;
&lt;li&gt;Nginx (latest provided by brew)&lt;/li&gt;
&lt;li&gt;Nodemon (an NPM package for watching for file changes)&lt;/li&gt;
&lt;li&gt;Foreman (the Ruby gem!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no special compiling requirements for any of these dependencies, so use whatever package manager you have to install each of these. I highly recommend installing Nodemon globally with npm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 Create the Rails app
&lt;/h3&gt;

&lt;p&gt;Let's get our rails app started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new crystal_rails
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 Create the Crystal app
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crystal init app kemal_api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This initializes a minimal Crystal application inside of our Rails root. There's no issue here because Rails does not autoload any files outside of the /app and /lib directories. Now we have a few clean up steps to do here.&lt;/p&gt;

&lt;p&gt;Let's open the new &lt;code&gt;kemal_api/shards.yml&lt;/code&gt; file and add this snippet at the end of the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kemal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kemalcr/kemal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can delete these extra files in the &lt;code&gt;kemal_api&lt;/code&gt; directory: - README&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;license&lt;/li&gt;
&lt;li&gt;.editorconfig&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also need to remove the git repo that was intialized for this folder. Make sure you've &lt;code&gt;cd&lt;/code&gt;'d into the kemal_api root and run &lt;code&gt;rm -rf .git&lt;/code&gt; and now your version tracking will be the same for the rails app and this sub-directory.&lt;/p&gt;

&lt;p&gt;I recommend copying the contents from the new kemal_api/.gitignore into the .gitingnore file in your root, and modify the paths to be relative to the kemal_api directory like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/kemal_api/docs/
/kemal_api/lib/
/kemal_api/bin/
/kelam_api/.shards/
/kemal_api/*.dwarf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crystal compiles all of the apps dependencies into the /lib folder and crystal can auto generate documentation from your code into the /docs/ folder, so this will prevent adding hundreds of unnecessary files into version control.&lt;/p&gt;

&lt;p&gt;Now let's install the shards for Kemal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;kemal_api
shards &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you'll see a &lt;code&gt;shard.lock&lt;/code&gt; file in the crystal apps root directory.&lt;/p&gt;

&lt;p&gt;Time to add Kemal and give it a test route. Open the src/kemal_api.cr file and modify it to match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"kemal"&lt;/span&gt;

&lt;span class="c1"&gt;# TODO: Write documentation for `KemalApi`&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;KemalApi&lt;/span&gt;
  &lt;span class="no"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;

  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/api/v1/test"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# This will be the HTML body in the response.&lt;/span&gt;
    &lt;span class="s2"&gt;"Kemal works! This is our API endpoint :)"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="no"&gt;Kemal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice the syntax for declaring a route is nearly identical to Sinatra... because Kemal is basically the Sinatra of Crystal! That's why my mind went to using this framework ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 Configuring Rails
&lt;/h2&gt;

&lt;p&gt;Now we'll need to make a few small modifications in Rails to get both apps booting up at the same time.&lt;/p&gt;

&lt;p&gt;When Rails 7 is configured to use importmaps (the default) you won't have the &lt;code&gt;bin/dev&lt;/code&gt; command available. That's alright, we'll go ahead and add a Procfile.dev&lt;/p&gt;

&lt;p&gt;Back in the rails root, we need to modify or create our Procfile.dev and add a line for the new Kemal app we just setup.&lt;/p&gt;

&lt;p&gt;If you are using jsbundling, you'll already have this file and you'll need to modify the &lt;code&gt;web:&lt;/code&gt; entry and add the &lt;code&gt;api:&lt;/code&gt; entry. Leave the &lt;code&gt;css:&lt;/code&gt; entry alone so you can still get your styling! :)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: rails s -p 3001
api: cd kemal_api &amp;amp;&amp;amp; nodemon --exec crystal run src/kemal_api.cr --watch src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next let's add a basic root path page to load for our rails app.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rails g controller home&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I'm going to keep this path the way it is for now, so we can see error pages and the successful path in both Rails and Kemal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 Configuring Nginx
&lt;/h3&gt;

&lt;p&gt;To have both apps co-exist on the same domain and port, we'll be setting up Nginx to act as a reverse proxy just like you would in a prod environment.&lt;/p&gt;

&lt;p&gt;We'll need to edit the default Nginx server block and configs. If you don't know where this file is, you can run &lt;code&gt;nginx -t&lt;/code&gt; and Nginx will output the file path for you, along with doing a syntax check (very handy!)&lt;/p&gt;

&lt;p&gt;Here is how I've setup my local Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        # Paths to the Kemal application
        location /api/v1/ {
                proxy_pass              http://0.0.0.0:3000$request_uri;
        }

        # Everything else going to Rails
        location / {
                proxy_set_header Host   $http_host;
                proxy_pass              http://0.0.0.0:3001/$request_uri;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to restart or reload your Nginx service to get the updated configuration. On a mac with a homebrew installed Nginx this command is &lt;code&gt;brew services restart nginx&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 Testing our applications
&lt;/h3&gt;

&lt;p&gt;In your terminal you can run &lt;code&gt;foreman start -f Procfile.dev&lt;/code&gt; and both apps should now boot up.&lt;/p&gt;

&lt;p&gt;In your browser go to : &lt;a href="http://localhost/home/index" rel="noopener noreferrer"&gt;http://localhost/home/index&lt;/a&gt; and you'll see how auto generated controller view!&lt;/p&gt;

&lt;p&gt;Try going to the root path and you'll see the standard rails error page that the route does not exist.&lt;/p&gt;

&lt;p&gt;Then visit &lt;a href="http://localhost/api/v1/test" rel="noopener noreferrer"&gt;http://localhost/api/v1/test&lt;/a&gt; and you'll see our Kemal message appearing!&lt;/p&gt;

&lt;p&gt;Let's make a change in our kemal app by adding a route.&lt;/p&gt;

&lt;p&gt;I'm going to add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/api/v1/app-restart"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="s2"&gt;"Kemal should now automatically restart"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As soon as I save the file, I can check the terminal and see the the change was detected and is now being restarted. Excellent! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; If you have a firewall enabled on Mac, you will have to allow the outside connections every time it restarts. The way around this has been using the &lt;code&gt;bin/dev&lt;/code&gt; command that comes with Rails when jsbundling is chosen.&lt;/p&gt;

&lt;h1&gt;
  
  
  Success!
&lt;/h1&gt;

&lt;p&gt;Now you know how easy it is to start building high performance API's with Crystal right alongside your Rails application. If you found this intriguing please leave a comment! &lt;/p&gt;

&lt;p&gt;If there is enough interest, I'll try to write more about using an ORM within the Crystal app for interacting with your database.&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>rails</category>
      <category>tutorial</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Dealing with Postgres - ArgumentError: string contains null byte</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Wed, 08 Dec 2021 15:22:54 +0000</pubDate>
      <link>https://dev.to/crimson-knight/dealing-with-postgres-argumenterror-string-contains-null-byte-423g</link>
      <guid>https://dev.to/crimson-knight/dealing-with-postgres-argumenterror-string-contains-null-byte-423g</guid>
      <description>&lt;p&gt;Recently I was testing using Postgres instead of MySQL on a large Rails 6 application. I had created copies of the schemas and removed the MySQL specific attributes from the tables to load in the schema. &lt;/p&gt;

&lt;p&gt;All was well at first! I could run &lt;code&gt;rails db:create&lt;/code&gt; and &lt;code&gt;rails db:schema:load&lt;/code&gt; and everything would work perfectly. I could even boot up the app and make a user account.&lt;/p&gt;

&lt;p&gt;Time to run the tests!&lt;/p&gt;

&lt;p&gt;Out of 3000+ tests, nearly 2/3 failed with the same error...&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ArgumentError: string contains null byte&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Weird. What even is a null byte? A little research showed me a null byte would/should be an escaped character sequence &lt;code&gt;\u0000&lt;/code&gt;. I tried searching/grepping through the codebase and didn't find that anywhere. &lt;/p&gt;

&lt;p&gt;So what the hell is going on?&lt;/p&gt;

&lt;p&gt;Well I found another escaped character pattern, &lt;code&gt;\x00&lt;/code&gt;. This specific escaped character sequence is a hexadecimal color, but the double 0 means that the color it should represent has it's bytes set to 0, which is null. So Postgres would see this string coming in and translate it from hexadecimal to escaped unicode which just so happens to turn into &lt;code&gt;\u0000&lt;/code&gt; and raise the &lt;code&gt;string contains null byte&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;Grepping the code base revealed this &lt;code&gt;\x00&lt;/code&gt; pattern used in a few places through model callbacks. This was being used to delineate strings, so I changed the delimiter to &lt;code&gt;\n\n&lt;/code&gt; and now I no longer get the null byte error!&lt;/p&gt;

&lt;p&gt;Sometimes taking over an existing code base... you find awfully strange things!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Crystal for the curious Ruby on Rails Developer</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Wed, 11 Aug 2021 14:46:16 +0000</pubDate>
      <link>https://dev.to/crimson-knight/crystal-for-the-curious-ruby-on-rails-developer-1dc</link>
      <guid>https://dev.to/crimson-knight/crystal-for-the-curious-ruby-on-rails-developer-1dc</guid>
      <description>&lt;p&gt;Have you been hearing about the language &lt;a href="https://www.crystal-lang.org" rel="noopener noreferrer"&gt;Crystal&lt;/a&gt; lately and you're curious what it's all about? Well, I'm right there with you! Specifically I'm curious how Crystal can be used to create web applications that are highly performant but use less resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little about me
&lt;/h2&gt;

&lt;p&gt;My name is Seth and I'm a self-taught full-stack Ruby on Rails developer. My strong suites are mostly back-end and infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal of this article
&lt;/h2&gt;

&lt;p&gt;Since I'm self-taught, and because of the way I learn, I rely heavily on video tutorials to help me build my understanding of how Ruby, Javascript, HTML/CSS and infrastructure work together to create functional web applications. &lt;/p&gt;

&lt;p&gt;Crystal caught my attention because of it's similarity to Ruby, but with benefits of a compiled language that I think Ruby is naturally trying to move towards. The challenge I find myself having is... &lt;strong&gt;a lack of videos helping introduce the different frameworks and the language&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, I decided I'll take multiple Crystal web frameworks for a test drive and we'll build the same app in each one to explore how each one is different. I plan to personally highlight the differences I notice between the Crystal framework and Rails because most of my app development experience is with Rails. &lt;/p&gt;

&lt;h2&gt;
  
  
  Crystal Web Frameworks I'll Be Exploring
&lt;/h2&gt;

&lt;p&gt;Here's the list of frameworks I will be making videos for. I'll be creating the same simple blog app on each framework so that anyone following along can compare how each one. I'll update each list item with a link to the article specifically about the framework with links to the videos and the source code for anyone to clone/fork and play around with. So if you don't see a link yet, please check back in the future 😄&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://amberframework.org/" rel="noopener noreferrer"&gt;Amber&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.luckyframework.org/" rel="noopener noreferrer"&gt;Lucky&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spider-gazelle.net/" rel="noopener noreferrer"&gt;Spider Gazelle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kemalcr/kemal" rel="noopener noreferrer"&gt;Kemal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://athenaframework.org/" rel="noopener noreferrer"&gt;Athena&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order here is roughly based on the full featured-ness of the framework. I say that because from my current experience Lucky is more fully featured as an out of the box system, but  I'm already working with Amber currently and I'm more familiar with it so I plan to start there. Kemal and Athena are much lighter weight and are better compared to a Sinatra type framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blog Project Outline
&lt;/h2&gt;

&lt;p&gt;I'm rarely this structured, but a simple blog site is a good way to explore a frameworks ORM, controller and view layer, routing, simple user authentication and all of the basic CRUD actions.&lt;/p&gt;

&lt;p&gt;Since the purpose of this project is to explore the frameworks themselves I'll be using bootstrap for the front-end and making all of the projects look essentially the same with standard bootstrap styling. If I use any Javascript, it'll be through Webpack and using Stimulus or just plain Javascript (clearly I haven't decided yet).&lt;/p&gt;

&lt;h3&gt;
  
  
  Pages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Home: This will act as the index page for the posts, displaying up to 10 posts and paginating any additional posts.&lt;/li&gt;
&lt;li&gt;Sign Up&lt;/li&gt;
&lt;li&gt;Sign In&lt;/li&gt;
&lt;li&gt;Post: the individual post page displaying the post contents. Post pages will accept comments from non-signed in users.&lt;/li&gt;
&lt;li&gt;User Account: screen showing all of the posts belonging to the signed in user, with all of the CRUD actions for all the posts belonging to that user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All pages will have a sticky top nav with links to sign in/out and create/manage posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  And there you have it!
&lt;/h2&gt;

&lt;p&gt;I'll update this post as I finish the videos and posts with each framework. If this has helped you, or caused you to have more questions that I didn't answer, please let me know. I'd love to get your feedback!&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>webdev</category>
      <category>alwayslearning</category>
      <category>12monthsofmakes</category>
    </item>
    <item>
      <title>Amber 1.0.0rc2 &amp; Jennifer</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Wed, 21 Jul 2021 14:16:54 +0000</pubDate>
      <link>https://dev.to/crimson-knight/amber-1-0-0rc2-jennifer-2c13</link>
      <guid>https://dev.to/crimson-knight/amber-1-0-0rc2-jennifer-2c13</guid>
      <description>&lt;h1&gt;
  
  
  {UPDATE}
&lt;/h1&gt;

&lt;p&gt;Since writing this post, I became the owner of the i18n.cr repo and I've released a new version that includes some updates to the crystal version and soon you won't need the directions found here!&lt;/p&gt;

&lt;h1&gt;
  
  
  Back to the original programming...
&lt;/h1&gt;

&lt;p&gt;Hey there! I was recently trying to get up and running with Crystal 1.0.0+ and the new Amber 1.0.0rc2 with Jennifer 0.10.0, and I ran into a curious issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shard name &lt;span class="o"&gt;(&lt;/span&gt;i18n&lt;span class="o"&gt;)&lt;/span&gt; has ambiguous sources: &lt;span class="s1"&gt;'git: https://github.com/dare892/i18n.cr.git'&lt;/span&gt; and &lt;span class="s1"&gt;'git: https://github.com/techmagister/i18n.cr.git'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have a simple work around that will temporarily let you run the &lt;code&gt;shards install&lt;/code&gt; or &lt;code&gt;shards update&lt;/code&gt; commands to get your app up and running with Jennifer for your ORM!&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;shards.yml&lt;/code&gt; file change these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;citrine-i18n:
  github: dare892/citrine-i18n
  version: ~&amp;gt; 1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;i18n:
  github: techmagister/i18n.cr
  branch: master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And set your Jennifer shard config to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jennifer:
  github: imdrasil/jennifer.cr
  branch: master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now shards can install and update all of your shards, without any Crystal version error warnings or ambiguous source warnings, and &lt;code&gt;amber watch&lt;/code&gt; will compile and run the app in development.&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>amber</category>
      <category>amberframework</category>
      <category>jennifer</category>
    </item>
    <item>
      <title>How do I know if my server is big enough? DevOps For Beginners</title>
      <dc:creator>Seth Tucker</dc:creator>
      <pubDate>Fri, 18 Sep 2020 15:15:06 +0000</pubDate>
      <link>https://dev.to/crimson-knight/how-many-servers-do-you-need-devops-for-beginners-3dgb</link>
      <guid>https://dev.to/crimson-knight/how-many-servers-do-you-need-devops-for-beginners-3dgb</guid>
      <description>&lt;p&gt;As an intermediate level developer who came from an IT background, I've always enjoyed being able to whip up an application in Rails and then quickly deploy to my own server. There are tons of great tutorials on how to publish a Rails app to production on a single server. But when I was first reading through these tutorials, I was always left with a single question. &lt;strong&gt;How do I know I have everything setup to handle growth?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ultimately, that question left me sifting through the documentation for Phusion Passenger for days of my life, only to discover vague optimization advice. The optimization formulas were good, but not great. After taking my first project from a single server with only 3,000 visitors per day and little to no database usage, up to a load-balanced stack of 3 application servers, a separate database server that servers over 8,000 visitors per day that has multiple environments shared across all the servers and now has business-critical applications running, I decided it was time to share some tips and tricks.&lt;/p&gt;

&lt;h1&gt;
  
  
  First things first, how powerful does each individual server need to be?
&lt;/h1&gt;

&lt;p&gt;Well, like everything in development, &lt;em&gt;it depends&lt;/em&gt;. Woof, I hate that vagueness. So here are a few things to help understand server sizing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notice&lt;/strong&gt; I make some assumptions here. First, you're using Phusion Passenger or a similarly non-multithreaded application server (I'm looking at you Unicorn users). If you're using Puma, Nginx Unit, or a similarly multithreaded application server then you may see better performance with smaller safety margins for high demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much memory will you need?
&lt;/h2&gt;

&lt;p&gt;I hated trying to figure this out. It's very frustrating when you are first getting started with your own server because you don't really know. I also found out through plenty of trial and (painful) error that certain services in Rails spin up an entire copy of the application (I'm looking at you ActionCable).&lt;/p&gt;

&lt;p&gt;So here's some sizing information I found extremely helpful.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A single Rails app, doing basic CRUD functionality usually takes up 100-150mb of memory, per instance. I've seen that go up to 500mb when under heavy loads (like website scrapers making hundreds or thousands of requests per second). &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ActionCable spins up an entire application instance of Rails that is in addition to your main app. Using ActionCable? That means a single app with ActionCable will take up 200mb-300mb of memory to run a "single" instance of your app. Passenger by default runs 3 instances of your app. So that's an easy 600mb-900mb of memory of minimal usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A typical Rails instance can handle hundreds or even a few thousand requests per second by itself. I'm throwing this tidbit of information out there because after seeing the numbers above, you may be like me and have a panic of &lt;em&gt;what if I run out of memory because I get a traffic spike?&lt;/em&gt; Don't stress about it too much.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Forumula for determining total server memory needed.
&lt;/h2&gt;

&lt;p&gt;Now that you know the rough values, take this handy-dandy formula and apply it to each stage of your application that you want on your server.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TotalApplicationInstances * 225 = TheoreticalApplicationMemoryNeeded&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the above formula, the &lt;code&gt;TotalApplicationInstances&lt;/code&gt; should be the number of Rails services that will be running including each ActionCable instance.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
A typical Passenger app will have 3 primary instances, plus 3 additional services for ActionCable. So our formula would now be:&lt;br&gt;
&lt;code&gt;6 * 225 = 1350&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That means we should assume Rails needs 1,350mb of memory to function when under a heavy load evenly across all instances on each individual server. That's around 1,000 requests per minute with basic CRUD and view rendering with an average performance-optimized app.&lt;/p&gt;

&lt;p&gt;I typically like to have 2x the Rails app's memory available for all of the system processes. So that means I would typically aim for a 4gb Digital Ocean droplet that has 2 virtual CPUs for about $20/month. Not bad!&lt;/p&gt;

&lt;p&gt;Now if I'm making a cluster for an actual production site, I'd have this spread across 3 servers, bringing my monthly cost to around $60 for my servers.&lt;/p&gt;

&lt;p&gt;In fact, that's exactly how I've set up my latest project. 1 Digital Ocean managed load balancer, that round-robin distributes traffic to my web servers ($15/month, that's a no-brainer). I have 3 application servers that sit right at 30% or less CPU usage and under 40% memory usage during peak hours of the day (that's with around one hundred active users on our site at a time), and then we have a single managed Postgres database that all of the application servers connect to. The database is radically oversized, running at like $60/month.&lt;/p&gt;

&lt;p&gt;So for around $150/month, my application serves over 180,000 website visitors, and my servers include both production and staging environments on a single cluster (separated of course). Not bad! It isn't my business, but I can tell you it employs 10 full-time employees with about another dozen near-fulltime contractors. &lt;/p&gt;

&lt;p&gt;For anyone trying to wrap their head around infrastructure costs and scaling, I hope this really helps bring to light just how powerful a small server cluster can be! Don't fear your infrastructure, embrace it :)&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
