<?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: Peter Solnica</title>
    <description>The latest articles on DEV Community by Peter Solnica (@solnic).</description>
    <link>https://dev.to/solnic</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%2F100887%2F20d4de4e-251c-4dfa-9c67-8152f1c7223d.jpeg</url>
      <title>DEV Community: Peter Solnica</title>
      <link>https://dev.to/solnic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/solnic"/>
    <language>en</language>
    <item>
      <title>Introducing Elixir Drops</title>
      <dc:creator>Peter Solnica</dc:creator>
      <pubDate>Wed, 25 Oct 2023 08:38:45 +0000</pubDate>
      <link>https://dev.to/solnic/introducing-elixir-drops-15jd</link>
      <guid>https://dev.to/solnic/introducing-elixir-drops-15jd</guid>
      <description>&lt;p&gt;A few years ago my Ruby friends asked me if it would be possible to port some of the &lt;a href="https://dry-rb.org"&gt;dry-rb&lt;/a&gt; libraries to Elixir. I remember some early community attempts at porting dry-validation specifically, I did my best to support the effort but back then my Elixir knowledge was too basic and I was too busy with other work.&lt;/p&gt;

&lt;p&gt;Fast forward to today and &lt;strong&gt;I'm happy to announce the very first release of Elixir Drops&lt;/strong&gt;! 🎉 In this article I'll introduce you to the concepts of the library and show you how you could use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contracts with schemas
&lt;/h2&gt;

&lt;p&gt;One of the core features of Drops is its Contract extension with the Schema DSL. It allows you to define the exact shape of the data that your system relies on and data domain validation rules that are applied to type-safe data that was processed by the schema.&lt;/p&gt;

&lt;p&gt;There are multiple benefits of this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Casting and validating data using schemas is very precise, producing detailed error messages when things go wrong&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schemas give you &lt;strong&gt;type safety&lt;/strong&gt; at the boundaries of your system - you can apply a schema to external input and be 100% sure it's safe to work with&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's very easy to see the shape of data, making it easier to reason about your system as a whole&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can restrict and limit larger data structures, reducing them to simpler representations that your system needs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It's easy to convert &lt;em&gt;schemas&lt;/em&gt; to other representations ie for documentation purposes or export them to JSON Schema format&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schemas capture both the structure and the types of data, making it easy to reuse them across your codebase&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your &lt;strong&gt;domain validation rules&lt;/strong&gt; become simple and focused on their essential logic as they apply to data that meets type requirements enforced by schemas&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This of course sounds very abstract, so here's a simple example of a data contract that defines a schema for a new user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Drops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:filled?&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;()&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;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;age:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# {:ok,&lt;/span&gt;
&lt;span class="c1"&gt;#  %{&lt;/span&gt;
&lt;span class="c1"&gt;#    name: "Jane Doe",&lt;/span&gt;
&lt;span class="c1"&gt;#    age: 42&lt;/span&gt;
&lt;span class="c1"&gt;#  }}&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{})&lt;/span&gt;

&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ["name key must be present"]&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;age:&lt;/span&gt; &lt;span class="s2"&gt;"42"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ["age must be an integer", "name must be filled"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a closer look at what we did here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The contract defines a schema with two keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;required(:name)&lt;/code&gt; means that the input map is expected to have the key &lt;code&gt;:name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;string(:filled?)&lt;/code&gt; means that the &lt;code&gt;:name&lt;/code&gt; must be a non-empty string&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;optional(:age)&lt;/code&gt; means that the input &lt;em&gt;could&lt;/em&gt; have the key &lt;code&gt;:age&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;integer()&lt;/code&gt; means that the &lt;code&gt;:age&lt;/code&gt; must be an integer&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though this is a very basic example, notice that the library does quite a lot for you - it processes the input map into a new map that includes only the keys that you specified in the schema, it checks both &lt;strong&gt;the keys&lt;/strong&gt; and &lt;strong&gt;the values&lt;/strong&gt; according to your specifications. When things go wrong - it gives you nice error messages making it easy to see what went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type-safe Casting
&lt;/h2&gt;

&lt;p&gt;One of the unique features of Drops Schemas is type-safe casting. Schemas breaks down the process of casting and validating data into 3 steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Validate &lt;strong&gt;the original input values&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apply optional casting functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validate &lt;strong&gt;the output values&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's better to explain this in code though:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Drops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:filled?&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;match?:&lt;/span&gt; &lt;span class="sr"&gt;~r/\d+/&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;()&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;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;age:&lt;/span&gt; &lt;span class="s2"&gt;"42"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;age:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"oops"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ["cast error: age must be a string"]&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;age:&lt;/span&gt; &lt;span class="s2"&gt;"oops"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ["cast error: age must have a valid format"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that when the age input value cannot be casted according to our specification, we get a nice "cast error" message and we immediately know what went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain validation rules
&lt;/h2&gt;

&lt;p&gt;Contracts allow you to split data validation into schema validation and arbitrary domain validation rules that you can implement. Thanks to this approach we can focus on the essential logic in your rules as we don't have to worry about the types of values that the rules depend on.&lt;/p&gt;

&lt;p&gt;Let me explain this using a simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Drops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:filled?&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:filled?&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:password_confirmation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:filled?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;password:&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;password_confirmation:&lt;/span&gt; &lt;span class="n"&gt;password_confirmation&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;password_confirmation&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="ss"&gt;:password_confirmation&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"must match password"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="ss"&gt;:ok&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password:&lt;/span&gt; &lt;span class="s2"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password_confirmation:&lt;/span&gt; &lt;span class="s2"&gt;"secret"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# {:ok, %{name: "John", password: "secret", password_confirmation: "secret"}}&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password:&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password_confirmation:&lt;/span&gt; &lt;span class="s2"&gt;"secret"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ["password must be filled"]&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password:&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;password_confirmation:&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ["password_confirmation must match password"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we check whether &lt;code&gt;password&lt;/code&gt; and &lt;code&gt;password_confirmation&lt;/code&gt; match but first we define in our schema that they must be both non-empty strings. Notice that our domain validation rule &lt;strong&gt;is not applied at all&lt;/strong&gt; if the schema validation does not pass.&lt;/p&gt;

&lt;p&gt;As you can probably imagine, this type of logic could be easily implemented as a higher-level macro, something like &lt;code&gt;validate_confirmation_of :password&lt;/code&gt;. This is something that I'll most likely add to Drops in the near future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe map atomization
&lt;/h2&gt;

&lt;p&gt;Another very useful feature is support for atomizing input maps based on your schema definition. This means that the output map will include only the keys that you defined turned into atoms, in the case of string-based maps.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Drops&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;atomize:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:filled?&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;()&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;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Signup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conform&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;
  &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Jane Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"age"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# {:ok, %{name: "Jane Doe", age: 42}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  About the first release
&lt;/h2&gt;

&lt;p&gt;Today I'm releasing v0.1.0 of Drops and even though it's the first release, it already comes with a lot of features! It's already used in our backend system at &lt;a href="https://valued.app"&gt;valued.app&lt;/a&gt;, processing and validating millions of JSON payloads regularly.&lt;/p&gt;

&lt;p&gt;It is an early stage of development though and I encourage you to test it out and provide feedback! Here are some useful links where you can learn more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/solnic/drops"&gt;Repo on GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/users/solnic/projects/2"&gt;Project on GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/solnic/drops/discussions"&gt;Discussions on GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hex.pm/packages/drops/0.1.0"&gt;Package on hex.pm&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hexdocs.pm/drops/0.1.0/readme.html"&gt;Documentation on hexdocs.pm&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check it out and let me know what you think!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>opensource</category>
      <category>json</category>
      <category>webdev</category>
    </item>
    <item>
      <title>10 Years of Open Source</title>
      <dc:creator>Peter Solnica</dc:creator>
      <pubDate>Fri, 04 Jun 2021 17:40:40 +0000</pubDate>
      <link>https://dev.to/solnic/10-years-of-open-source-2le7</link>
      <guid>https://dev.to/solnic/10-years-of-open-source-2le7</guid>
      <description>&lt;p&gt;On June 4th, 2011 I released the first version of Virtus, a ruby gem that I extracted from the DataMapper project.&lt;/p&gt;

&lt;p&gt;I remember how I felt about Open Source back then, and I have to tell you that my perspective has changed &lt;strong&gt;a lot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;10 years, a freaking decade, is a lot of time...I've gone through a lot of ups-and-downs during that time, as my open-source contributions sky-rocketed, and that changing perspective is something I continuously think about. I don't know how many projects I contributed to, how many libraries I've written, I honestly do not know.&lt;/p&gt;

&lt;p&gt;This 10-year anniversary made me super emotional and I have a lot of thoughts about Open Source that I’d like to share.&lt;/p&gt;

&lt;p&gt;This will be bitter-sweet and probably too long. Please keep in mind that this is based on my own experience and I'll do my best not to generalize too much.&lt;/p&gt;

&lt;h2&gt;
  
  
  How my perspective changed
&lt;/h2&gt;

&lt;p&gt;I remember that in my mid 20s, when I was already working as a software developer, I had this concept in my head that Open Source is this amazing thing that everybody is doing. Maybe it sounds ridiculous but it is exactly how I used to see it. I'm a heavy-thinker and a natural problem-solver and on top of that I'm super impatient.&lt;/p&gt;

&lt;p&gt;This was a perfect mix to become an Open Source contributor because whenever something didn't work for me I was either working on a patch 10 seconds later, or I was already half-way through prototyping my own solution.&lt;/p&gt;

&lt;p&gt;This is exactly how I ended up contributing to DataMapper, then joining the core team, then releasing Virtus, then working on DataMapper 2.0, then turning it into &lt;a href="https://rom-rb.org"&gt;rom-rb&lt;/a&gt;, then joining &lt;a href="https://dry-rb.org"&gt;dry-rb&lt;/a&gt; and building 1.25 library / month on average for about 2 years or so to eventually join &lt;a href="https://hanamirb.org"&gt;Hanami&lt;/a&gt; team...and, yeah, it's been kinda nuts now when I look back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the most part I kept thinking "this is what everybody does" until I realized it's absolutely not the case.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first thing that hit me was the amount of work that's needed "just" to maintain a library. Even when you have a library that is used by, let's say, 27 people (which is a relatively low number) this can easily turn into 10+ hours of work per month, every month. If you have more than one library, and the total number of people using your stuff is now counted in hundreds or even thousands, this can easily get out of hand. There will obviously be downtime in various projects, especially the more mature ones -- but on average once you maintain something, it becomes super hard to find time to do the work that you &lt;em&gt;actually want to do&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is when things become difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the deal here?
&lt;/h2&gt;

&lt;p&gt;You're obviously responsible for managing your time and what you do and we could easily just say "well, nobody forces you to work on OSS" and end the discussion...but is it that simple?&lt;/p&gt;

&lt;p&gt;People think that Open Source is just this one big, global family. Thousands of people working on all these amazing libraries, frameworks and tools.There's this idealistic view of Open Source that was absolutely, clearly visible to me. I &lt;em&gt;feel&lt;/em&gt; like it's changing a bit these days but it's still there for sure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I keep meeting people who are almost apologetically telling me that they wish they had contributed more.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My question is: what's the deal here? What makes us think that we're obliged to do this?&lt;/p&gt;

&lt;p&gt;The way I see it is that we've created this impression ourselves as an industry. We kept telling ourselves that we need to build Open Source Software because it's the future of software (and it is!). We also kept convincing ourselves that this is how you can become a better developer (and you can!) and so some folks just double down on working harder.&lt;/p&gt;

&lt;p&gt;Then you have companies and their expectations. It used to be - and probably still is - very common to see job offers where "contributing to OSS" was mentioned as either a "nice to have" or even a requirement. I think it's a bad practice now, except for cases where the job really does involve contributing to OSS.&lt;/p&gt;

&lt;p&gt;I believe companies giving you extra props for OSS work contributed heavily to this notion of OSS as something that we're supposed to do.&lt;/p&gt;

&lt;p&gt;The question, however, remains unanswered. What’s the deal here? I feel like we can all try to come up with our own answers that will work for us at an individual level. It's very hard to come up with a universal answer though.&lt;/p&gt;

&lt;p&gt;When you have a product and a client using your product who's paying you to do your work, then the deal is simple - you work on what they need, and they pay you for it. But When it comes to Open Source this is extremely complicated and vague. What's the product? Who's the client? Can we even treat an open-source project like that? Or maybe the deal is that there is no deal and that's the beauty of it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's be real
&lt;/h2&gt;

&lt;p&gt;If you're a maintainer then this will probably ring some bells. If you're not and you're using OSS then this could be useful.&lt;/p&gt;

&lt;p&gt;Let's be real - we're not one big, global, family. Most of the work is done by a tiny little fraction of the global developer population. We like to think that we're all in this together but we're not.&lt;/p&gt;

&lt;p&gt;You can come up with great looking stats, showing that thousands of people are contributing to OSS projects -- but in reality the actual bulk of work, the countless hours spent on building core functionality, testing, fixing bugs, making performance improvements, supporting users, writing docs, responding to issues, dealing with pull requests and so on are done by maintainers.&lt;/p&gt;

&lt;p&gt;Let me also make it very clear: &lt;em&gt;all contributions count, they are important and highly appreciated&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But my point is to underscore the fact that when 1000 people did a little bit of work each, then you get significant results in total, but please realize that &lt;em&gt;the time&lt;/em&gt; that each person spent on their contributions cannot compare to the time that maintainers spend doing their work.&lt;/p&gt;

&lt;p&gt;To illustrate the type of situations maintainers have to deal with on a regular basis, here are some real world scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You get a bug report from somebody. You spend 3 hours debugging and coming up with a patch. You ask the user to test it out. The user never gets back to you.&lt;/li&gt;
&lt;li&gt;You get a feature request. You spend hours discussing it. You're going through source code figuring out how it could be implemented. You end up with a nice PoC. You ask the user if it would work well. The user never gets back to you.&lt;/li&gt;
&lt;li&gt;You get a Pull Request. You review it and it's looking good but you leave some comments because a couple of small things must be addressed. The user never gets back to you.&lt;/li&gt;
&lt;li&gt;You get a Pull Request out of the blue with an angry user trying to point out your mistakes. You do your best to be polite but the user keeps attacking you. You spend hours in total just to discuss it. You reach some level of consensus and then guess what, the user never gets back to you.&lt;/li&gt;
&lt;li&gt;There's this issue that somebody reported over a year ago and you want to address it eventually but you can't find time/energy/motivation/etc. to do it. It is marked as "help wanted" but people keep pinging you with comments like "any progress on this?". The uncomfortable sense of guilt starts to creep in even though it's not even your job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...and man I could go on.&lt;/p&gt;

&lt;p&gt;Here's something to think about: &lt;strong&gt;nobody has the time to work on Open Source&lt;/strong&gt;. Nobody. There is no time, yet we somehow do it. Many of these situations are caused by the fact that a user hasn’t enough time. In other cases it's the maintainer who hasn’t enough time. We struggle to push things forward and it's often &lt;em&gt;extremely unproductive&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sponsored Open Source Work
&lt;/h2&gt;

&lt;p&gt;It's very tempting to say that now we have all these platforms where Open Source maintainers can be financially supported therefore the problem is solved. Unfortunately, things are not that simple.&lt;/p&gt;

&lt;p&gt;First and foremost I believe that sponsored Open Source work is still a situation where it's hard to say what the deal is. Not to mention that you can only be successful at getting sponsorship if your project &lt;em&gt;is popular enough&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I think in OSS in general there's this long-tail of OSS maintainers doing very important work on lower-level libraries, but it's the type of work that’s not as visible as it would be in a more visible, top-level project... Not to mention that you need a project in the first place and just that is already a big time investment. Assuming that you do make it, and you do now have an OSS project that is used by a significant number of people you STILL need to invest a significant amount of time in order to build your presence.&lt;/p&gt;

&lt;p&gt;This is important -- nobody will discover you just because you published a project on GitHub. This is harsh reality but I think it's true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You &lt;em&gt;cannot assume&lt;/em&gt; that every OSS maintainer is willing to do the type of work that is needed to become sponsored.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As much as I appreciate GitHub Sponsors, I still think there should be a more aggressive shift towards sponsorship &lt;em&gt;from businesses&lt;/em&gt;, rather than individuals. Many people have shared with me &lt;a href="https://calebporzio.com/i-just-hit-dollar-100000yr-on-github-sponsors-heres-how-i-did-it"&gt;an article&lt;/a&gt; from an Open Source developer who quit his day job and is now making more than before, solely through GitHub Sponsorship. When I read this article, my first thought was "wow this is so awesome" but then I quickly realized what kind of an effort this actually was.&lt;/p&gt;

&lt;p&gt;This developer is clearly good at his game. He's a natural communicator and marketer, you can tell. &lt;strong&gt;Now think about how many people, realistically speaking, are able to do what he did?&lt;/strong&gt; I believe not that many. Consider people whose English is not their first language. Even this common “limitation” makes getting sponsors challenging -- because you need a convincing, well written intro on your landing page, and a native English speaker will most likely do a better job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We need companies sponsoring Open Source work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm very grateful for my individual supporters, but I am openly saying that I don't believe that this is how it should work. I hope people who support me really benefit from my work but at the same time it's kind of crazy that my libraries are used by Netflix or Apple yet they are not my sponsors. &lt;em&gt;This&lt;/em&gt; is what I would love to see change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The number of companies sponsoring OSS work should be greater than the number of individuals supporting OSS work&lt;/strong&gt;. I don't know what the numbers are now and it would be really interesting to see some stats.&lt;/p&gt;

&lt;p&gt;I know there are people sponsored by many companies, and I know that some companies hire OSS developers as full-time employees. This is fantastic, and I hope it's going to become the norm at some point. Right now it looks like we're going through some kind of a transition phase and I'm actually really optimistic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank You ❤️
&lt;/h2&gt;

&lt;p&gt;On a very personal level - I want to thank everybody from the Ruby community and all the people who helped me grow, who inspired me and who supported me through difficult times. Thank you for your trust and for putting up with me for so long 😆&lt;/p&gt;

&lt;p&gt;I know this post is bitter-sweet, but overall, what I actually want to say is that I'm very grateful. I've learned so much and I'm sure I will learn even more.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>community</category>
      <category>ruby</category>
      <category>programming</category>
    </item>
    <item>
      <title>The 5 Rules of Simple RSpec Tests</title>
      <dc:creator>Peter Solnica</dc:creator>
      <pubDate>Wed, 12 May 2021 12:51:48 +0000</pubDate>
      <link>https://dev.to/solnic/the-5-rules-of-simple-rspec-tests-59ib</link>
      <guid>https://dev.to/solnic/the-5-rules-of-simple-rspec-tests-59ib</guid>
      <description>&lt;p&gt;The 5 "rules" &lt;strong&gt;I try&lt;/strong&gt; to follow in order to write simple RSpec tests.&lt;/p&gt;

&lt;p&gt;Let's GO.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Max 2 levels of describe/context nesting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Everything above 2 is a code-smell&lt;/strong&gt; and causes alarm bells in my head to ring. The more levels of nesting you have, the harder it is to understand what a given example is doing. If you add before/after hooks to the mix, it'll become even worse.&lt;/p&gt;

&lt;p&gt;I often reduce nesting by simply using example descriptions like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;CreateUser&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:create_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with valid params"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns success"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Jane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"jane@doe.org"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_success&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with invalid params"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns failure when name is missing"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"jane@doe.org"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_failure&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns failure when email is missing"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Jane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_failure&lt;/span&gt;
    &lt;span class="k"&gt;end&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;Even though this could use more contexts and have attributes provided as per-context &lt;code&gt;let&lt;/code&gt;s, I still prefer the simpler form. &lt;strong&gt;There are cases where &lt;code&gt;let&lt;/code&gt; is helpful but I'm optimizing for having as little nesting levels as possible.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Top-down &lt;code&gt;let&lt;/code&gt; definitions
&lt;/h2&gt;

&lt;p&gt;When defining &lt;code&gt;let&lt;/code&gt; statements, I do my best to organize a spec in a way that you don't have to jump from an inner describe/context block to outer blocks in order to understand the spec setup. This means that if you're reading a spec from the top, every subsequent &lt;code&gt;let&lt;/code&gt; statement should ultimately lead you to a full understanding of the whole setup for a given spec example. &lt;strong&gt;A setup where an inner &lt;code&gt;let&lt;/code&gt; overrides an outter &lt;code&gt;let&lt;/code&gt; is a code-smell.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's an example of &lt;strong&gt;what I try to avoid&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;CreateUser&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:create_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with valid params"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns success"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Jane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"jane@doe.org"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_success&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with invalid params"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# THIS RIGHT HERE - BAD, VERY BAD `LET`&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:create_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;CreateUser&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="ss"&gt;some_custom: &lt;/span&gt;&lt;span class="s2"&gt;"stuff for this example"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns failure when name is missing"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"jane@doe.org"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_failure&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns failure when email is missing"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Jane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_failure&lt;/span&gt;
    &lt;span class="k"&gt;end&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;h2&gt;
  
  
  3. Meaningful example descriptions
&lt;/h2&gt;

&lt;p&gt;I've stopped using short DSL syntax years ago. I used to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;CreateUser&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;CreateUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with valid params"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;specify&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Jane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"jane@doe.org"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_success&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;You can streamline &lt;em&gt;a lot&lt;/em&gt; with RSpec's powerful DSL but I prefer to &lt;strong&gt;optimize for readability&lt;/strong&gt; and having nice descriptions is part of this. In general, I try to describe actual scenarios that can happen in the actual client code (as in, the code that will be using the thing I'm writing a spec for).&lt;/p&gt;

&lt;h2&gt;
  
  
  4. No mocking by default
&lt;/h2&gt;

&lt;p&gt;I've stopped obsessing about "pure unit testing". This topic is big enough to have a separate post or even a series of posts about it but I'll try to summarize it here.&lt;/p&gt;

&lt;p&gt;Using mocks in RSpec, even if you use verified doubles, is still prone to ending up with false-positivies. I feel uncomfortable every time I use a double, especially if it's not a verified double, and so I try to avoid mocking in general.&lt;/p&gt;

&lt;p&gt;There are situations where mocking is absolutely helpful and justified - a great example is some kind of a 3rd-party authorization system. You definitely want to have it mocked in various testing scenarios.&lt;/p&gt;

&lt;p&gt;If I were to come up with some guideline here, it would probably be this: &lt;strong&gt;use a mock only if using the real thing causes &lt;em&gt;at least&lt;/em&gt; one of the following problems&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes tests setup significantly more complex&lt;/li&gt;
&lt;li&gt;Makes tests significantly slower&lt;/li&gt;
&lt;li&gt;Causes &lt;em&gt;unwanted&lt;/em&gt; and/or &lt;em&gt;problematic&lt;/em&gt; side-effects in external systems (ie a file system)&lt;/li&gt;
&lt;li&gt;Depends on the internet connection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a lot that can be done to improve our "mocking situation" in the Ruby ecosystem in general. I've got some ideas how to achieve a truly safe and easy-to-work-with setup with mocks and stubs using our Hanami 2.0 setup, so stay tuned.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Higher tolerance to code duplication
&lt;/h2&gt;

&lt;p&gt;There's this pretty good advise that you shouldn't "dry up" your code until you have the same concept repeated in more than 3 places. This works well but not in tests. That's why I have a much higher tolerance to duplication in tests. Reducing duplication in tests makes your setup more complex and we don't want that. &lt;strong&gt;Simply be more wary when reducing duplication and make sure it's worth the extra complexity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is &lt;em&gt;especially&lt;/em&gt; relevant when you're using RSpec as it provides very powerful tools to reduce duplication. It's easy to overcomplicate your test suite when you go too far when trying to reduce duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Overall this really boils down to the famous "with great power comes great responsibility" quote, very aplicable here. RSpec has a lot of features but you don't have to use all of them all of the time. What I really appreciate about RSpec though is that it works so well out of the box and its core built-in features are very helpful. It's a powerful tool so it takes time to learn how to use it effectively!&lt;/p&gt;

&lt;p&gt;Last but not least, this is based on my personal experience with RSpec and your experience may be different. I'm more than happy to learn about your approach to writing simple and maintainable RSpec tests.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rspec</category>
      <category>tdd</category>
      <category>bdd</category>
    </item>
  </channel>
</rss>
