<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[ruby - Rado's Blog]]></title><description><![CDATA[ruby - Rado's Blog]]></description><link>https://blog.rstankov.com/</link><image><url>http://blog.rstankov.com/favicon.png</url><title>ruby - Rado&apos;s Blog</title><link>https://blog.rstankov.com/</link></image><generator>Ghost 1.8</generator><lastBuildDate>Wed, 10 Jun 2026 14:26:29 GMT</lastBuildDate><atom:link href="https://blog.rstankov.com/tag/ruby/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[My favourite Ruby on Rails engines]]></title><description><![CDATA[Engines is one of my favorite features in Ruby on Rails that distinguishes it from other web frameworks. Here are some cool ones...]]></description><link>https://blog.rstankov.com/top-8-ruby-on-rails-engines/</link><guid isPermaLink="false">63f9dfb5a71733000255c891</guid><category><![CDATA[ruby]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[Rails]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Sun, 26 Feb 2023 21:14:12 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1594734349446-4a2f752392d3?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMjc2N3wwfDF8c2VhcmNofDN8fHRyYWluJTIwZW5naW5lfGVufDB8fHx8MTY3NzQzMzg1OA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1594734349446-4a2f752392d3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjc2N3wwfDF8c2VhcmNofDN8fHRyYWluJTIwZW5naW5lfGVufDB8fHx8MTY3NzQzMzg1OA&ixlib=rb-4.0.3&q=80&w=1080" alt="My favourite Ruby on Rails engines"><p><a href="https://guides.rubyonrails.org/engines.html">Ruby on Rails Engines</a> is one of the most powerful and underestimated features. It distinguishes Rails from other web frameworks.</p>
<h2 id="whatengines">What engines?</h2>
<p>Engines are embeddable applications that can be integrated into your Ruby on Rails application. They are distributed as regular Ruby gems with models, controllers, and views.</p>
<p>The UI that comes with engines is incredibly useful, and most engines can be set up within 10 minutes, saving months of integration of microservices and external SaaS tools.</p>
<p>Here are the top eight Ruby on Rails engines:</p>
<ol>
<li><strong>Sidekiq</strong> - background jobs</li>
<li><strong>Blazer</strong> - business intelligence</li>
<li><strong>Lookbook</strong> - UI components previews</li>
<li><strong>Flipper</strong> - Feature toggles</li>
<li><strong>Split</strong> - A/B testing</li>
<li><strong>PGHero</strong> - PostgreSQL monitoring</li>
<li><strong>FastEntry</strong> - cache previewer</li>
<li><strong>GraphiQL</strong> - GraphQL IDE</li>
</ol>
<h3 id="1sidekiq">1) <a href="https://sidekiq.org/">Sidekiq</a></h3>
<p>Sidekiq is the de facto standard for background jobs in Ruby land. The engine provides a UI to monitor what is happening with your background jobs.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_sidekiq.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_sidekiq.png" alt="My favourite Ruby on Rails engines"></a>
<h3 id="2blazer">2) <a href="https://github.com/ankane/blazer">Blazer</a></h3>
<p>Blazer allows you to write SQL queries and build charts and dashboards from them, making it an excellent tool for business intelligence.</p>
<p>But even if you don't use it as a business intelligence tool. It is a great developer tool. It can help you see what is in your database in local or staging environments.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_blazer.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_blazer.png" alt="My favourite Ruby on Rails engines"></a>
<h3 id="3lookbook">3) <a href="https://lookbook.build/">Lookbook</a></h3>
<p><em>I found this recently.</em></p>
<p>If you use <a href="https://viewcomponent.org/">ViewComponents</a> to build UI elements instead of <a href="https://guides.rubyonrails.org/action_view_helpers.html">Action View Helpers</a>, you should use it. It builds on top of <a href="https://viewcomponent.org/guide/previews.html">previews</a> and wraps them in <a href="https://storybook.js.org/">Storybook</a> like interface.</p>
<p>However, if you have ever tried to set up <a href="https://storybook.js.org/">Storybook</a> and <a href="https://reactjs.org/">React</a> components, you have spent A LOT of time on it.</p>
<p>This gem takes about 10 minutes to set up, and the rest is just writing simple <a href="https://viewcomponent.org/guide/previews.html">previews</a> Ruby classes.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_lookbook.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_lookbook.png" alt="My favourite Ruby on Rails engines"></a>
<h3 id="4flipper">4) <a href="https://github.com/jnunemaker/flipper">Flipper</a></h3>
<p>Flipper provides infrastructure and UI for <a href="https://en.wikipedia.org/wiki/Feature_toggle">Feature toggle</a>.</p>
<p>I have <a href="https://blog.rstankov.com/feature-flags-in-react/">written</a> before how to integrate with React - <a href="https://blog.rstankov.com/feature-flags-in-react/">here</a>.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_flipper.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_flipper.png" alt="My favourite Ruby on Rails engines"></a>
<p>Flipper has a <a href="https://www.flippercloud.io/">paid version</a> with features like permissions and audit history.</p>
<h3 id="5split">5) <a href="https://github.com/splitrb/split">Split</a></h3>
<p><a href="https://github.com/splitrb/split">Split</a> provides infrastructure for <a href="https://en.wikipedia.org/wiki/A/B_testing">A/B testing</a>.</p>
<p>There are a lot of SaaS tools to do so; however, they are slowing your pages, cost a lot and take time integrate.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_split_2.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_split_2.png" alt="My favourite Ruby on Rails engines"></a>
<h3 id="6pghero">6) <a href="https://github.com/ankane/pghero">PGHero</a></h3>
<p>It provides a performance dashboard <a href="https://www.postgresql.org/">PostgreSQL</a>. It tells you things like - slow queries, unused indexes, table/index sizes, and more.</p>
<p>It comes from ? <a href="https://www.instacart.com">Instacard</a>, which makes also <a href="https://github.com/ankane/blazer">Blazer</a> and <a href="https://www.instacart.com/opensource">other wonderful open source projects</a>.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_pghero.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_pghero.png" alt="My favourite Ruby on Rails engines"></a>
<h3 id="7fastentry">7) <a href="https://rubygems.org/gems/fastentry">FastEntry</a></h3>
<p>Do you know what is in your cache? <a href="https://rubygems.org/gems/fastentry">FastEntry</a> shows you this. <em>Simple and useful.</em></p>
<p>I have found a secondary use for it in development. I use the same Redis instance for cache, <a href="https://sidekiq.org/">Sidekiq</a>, and application data storage. It shows you everything in this Redis instance.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_fast_entry.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_fast_entry.png" alt="My favourite Ruby on Rails engines"></a>
<h3 id="8graphiql">8) <a href="https://github.com/rmosolgo/graphiql-rails">GraphiQL</a></h3>
<p><em>Last but not least.</em> <a href="https://github.com/graphql/graphiql">GraphiQL</a> is IDE for <a href="https://graphql.org/">GraphQL</a>. This engine provides quick setup and integration.</p>
<a href="https://rstankov-blog.s3.amazonaws.com/rails_engine_graphiql.png" style="box-shadow: none;" target="_blank">
<img src="https://rstankov-blog.s3.amazonaws.com/rails_engine_graphiql.png" alt="My favourite Ruby on Rails engines"></a>
<h2 id="howtosetupandsecureengines">How to setup and secure engines?</h2>
<p>I mentioned that engines are easy to set up. But talk is cheap. Let me show you the code. ?</p>
<p>Let's install <a href="https://github.com/ankane/blazer">Blazer</a>, as it is one of the more complex ones to set up.</p>
<p><strong>Step 1</strong>: Add to Gemfile</p>
<pre><code>gem &quot;blazer&quot;
</code></pre>
<p><strong>Step 2</strong>: Bundle install &amp; database setup</p>
<pre><code>bundle install

rails generate blazer:install
rails db:migrate
</code></pre>
<p><strong>Step 3</strong>: Mount to the Rails application</p>
<pre><code class="language-ruby"># config/routes.rb
namespace :dev, constraints: Routes::AdminConstraint do
  mount Blazer::Engine, at: '/blazer'
  # ... other engines
end
</code></pre>
<p><em>Note:</em> <code>Routes::AdminConstraint</code> ensures that this namespace is only available for certain users.</p>
<p>A simplified version can look something like:</p>
<pre><code class="language-ruby">module Routes::AdminConstraint
  extend self

  def matches?(request)
    return false if request.session[:user_id].blank?

    User.find(request.session[:user_id]).admin?
  end
end
</code></pre>
<p><strong>In Production</strong>: Have database as ENV variable</p>
<pre><code>ENV[&quot;BLAZER_DATABASE_URL&quot;] = &quot;postgres://user:password@hostname:5432/database&quot;
</code></pre>
<p>That's it, you have a business intelligence tool ?</p>
<h2 id="conclusion">Conclusion</h2>
<p>Engines have saved me a lot of time <em>(and use of external services)</em> over the years. I'm constantly on the lookup for new useful ones  ?</p>
<p>If you have any questions or comments, you can ping me on  <a href="https://twitter.com/rstankov">Twitter</a> or <a href="https://mastodon.social/@rstankov">Mastodon</a>.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Product Hunt Architecture]]></title><description><![CDATA[How we combine React, Next.js, GraphQL, Apollo, and Ruby on Rails ]]></description><link>https://blog.rstankov.com/product-hunt-architecture/</link><guid isPermaLink="false">60ab88f99c99d10004cd34c0</guid><category><![CDATA[React]]></category><category><![CDATA[ruby]]></category><category><![CDATA[product hunt]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Mon, 24 May 2021 11:43:50 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1451976426598-a7593bd6d0b2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMjc2N3wwfDF8c2VhcmNofDZ8fGFyY2hpdGVjdHVyZXxlbnwwfHx8fDE2MjE4NTY0NDI&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1080" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1451976426598-a7593bd6d0b2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjc2N3wwfDF8c2VhcmNofDZ8fGFyY2hpdGVjdHVyZXxlbnwwfHx8fDE2MjE4NTY0NDI&ixlib=rb-1.2.1&q=80&w=1080" alt="Product Hunt Architecture"><p>Last year we <a href="https://www.producthunt.com/stories/product-hunt-s-new-ceo-josh-buckley">very</a> <a href="https://www.producthunt.com/stories/product-hunt-s-new-gm-ashley-higgins">eventful</a> for <a href="https://www.producthunt.com/">Product Hunt</a>. On technical level we closed large system migrations like fully switch to <a href="https://www.producthunt.com/stories/product-hunt-s-new-gm-ashley-higgins">Next.js</a> for frontend, moved to <a href="https://blog.rstankov.com/product-hunt-architecture/typescriptlang.org">TypeScript</a> and rewrote our mobile app with <a href="https://reactnative.dev/">React.Native</a>.</p>
<p>Here is how our technical architecture looks currently:</p>
<img src="https://rstankov-blog.s3.amazonaws.com/producthunt_architecture_2021.png" style="width: 100%" alt="Product Hunt Architecture">
<p>I have given detail <a href="https://rstankov.com/appearances">talks</a> about each section of our systems.</p>
<ul>
<li>Backend - Rails, GraphQL, AWS, Docker, PostgreSQL (<a href="https://speakerdeck.com/rstankov/3-years-graphql-at-product-hunt">slides</a> ,  <a href="https://www.youtube.com/watch?v=0Bwg03f_3KQ">video</a>)</li>
<li>Frontend - React, TypeScript, Next.js, Apollo (<a href="https://speakerdeck.com/rstankov/react-architecture-at-product-hunt">slides</a> ,  <a href="https://www.youtube.com/watch?v=n1SwPPfqLlI">video</a>)</li>
<li>Mobile - React Native, TypeScript, Apollo (<a href="https://speakerdeck.com/rstankov/react-native-architecture-at-product-hunt">slides</a>, <a href="https://youtu.be/REg_EVXStMo?t=5521">video</a>)</li>
</ul>
<p>Here is what a web request goes through to deliver a <a href="https://www.producthunt.com/posts/focused-task">page</a> from the site:</p>
<img src="https://rstankov-blog.s3.amazonaws.com/producthunt_request_flow_chart.png" alt="Product Hunt Architecture" style="width: 100%">
<p>Overall, I'm very happy with our current system. It allows us to handle our traffic and ship fast and reliable.</p>
<h3 id="whatscomesnext">Whats comes next?</h3>
<p><strong>Software is never done</strong> and this is true for us. We already had started improving on our foundations like:</p>
<ul>
<li>better core React components</li>
<li>kinda, moving towards <a href="https://speakerdeck.com/rstankov/domain-driven-design-kinda">Domain Driven Design</a>.</li>
<li>improving on backend infrastructure for better performance and monitoring</li>
</ul>
<p>For any questions or comments, you can ping me on <a href="https://twitter.com/rstankov">Twitter</a>.</p>
</div>]]></content:encoded></item><item><title><![CDATA[How Not Implement Html Editor]]></title><description><![CDATA[The story of technical failure at Product Hunt.  We used Slate for an HTML editor and tightly coupled its internal structure to our database.]]></description><link>https://blog.rstankov.com/how-not-implement-html-editor/</link><guid isPermaLink="false">5fbc1d4301a4620004e6cf6a</guid><category><![CDATA[product hunt]]></category><category><![CDATA[React]]></category><category><![CDATA[ruby]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Fri, 27 Nov 2020 07:33:55 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1605514449459-5a9cfa0b9955?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjEyNzY3fQ" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1605514449459-5a9cfa0b9955?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjEyNzY3fQ" alt="How Not Implement Html Editor"><p>If someone says, &quot;Tell me about a technical failure you made&quot;, this is the story I'm going to share. ?</p>
<p>For the <a href="https://www.producthunt.com/collections">Product Hunt Collection</a> project, I needed to implement a <a href="https://en.wikipedia.org/wiki/WYSIWYG">WYSIWYG HTML editor</a>. The two choices at that time (March 2017) were <a href="https://draftjs.org/">Draft.js</a> and <a href="https://www.slatejs.org/">Slate</a>. I decided to go with <a href="https://www.slatejs.org/">Slate</a>. It was easier to understand and had great examples that fit our needs.</p>
<p>Slate is a sound library and still serves us well to this day. The mistake I made was in the way I applied it to our project.</p>
<p>In the frontend, I wrapped and exposed Slate via:</p>
<ul>
<li><code>HTMLInput</code> - React component that encapsulates the Slate HTML editor. Used in forms.</li>
<li><code>slateToHTML</code> - function which converts the <a href="https://docs.slatejs.org/concepts/06-editor">Slate Editor state</a> to a React component displaying its output HTML.</li>
</ul>
<p>This encapsulation worked quite well.</p>
<pre><code class="language-jsx">&lt;!-- HTMLInput in a form --&gt;
&lt;Form.Field name=&quot;description&quot; control={HTMLInput} /&gt;

&lt;!-- show HTML content --&gt;
&lt;p&gt;{slateToHTML(collection.description)}&lt;/p&gt;
</code></pre>
<p>Slate represents its <a href="https://docs.slatejs.org/concepts/06-editor">state</a> with a big nested JS object called the <a href="https://docs.slatejs.org/concepts/06-editor">Editor</a>. For the backend, I decided to store the <a href="https://docs.slatejs.org/concepts/06-editor">Slate Editor state</a> directly in a <a href="https://www.postgresql.org/docs/9.4/datatype-json.html">&quot;JSONB&quot;</a> column.</p>
<p>My reasoning was the following:</p>
<ul>
<li>It will make the code simpler.
<ul>
<li>I only need to get the editor's state and pass it to the backend. Then, from the backend, put it back into Slate via <code>slateToHTML</code>.</li>
</ul>
</li>
<li>It will be more secure because we don't deal with raw HTML, and I can validate the JSON structure in the backend via <a href="https://graphql.org/learn/schema/#input-types">GraphQL input types</a>.</li>
<li>We will have only 1~2 <code>slateToHTML</code> conversions per page, and there won't be a significant performance hit.</li>
</ul>
<p>It took me a couple of days to build and ship a working HTML editor powered by Slate, but then...</p>
<h2 id="whatwentwrong">What went wrong</h2>
<p>The first bad sign was when we needed to send an email containing HTML generated by Slate. We needed to write a converter in Ruby that turned the Slate Editor state into an HTML string. Now, we had two places where we defined what our HTML Editor capabilities were. ?</p>
<p>Then Slate <a href="https://github.com/ianstormtaylor/slate/blob/main/Changelog.md#0500--november-27-2019">changed the structure of its Editor state</a> and records in our database couldn't be directly passed to Slate. We used our Ruby converter and extended it to be a translation layer between the old and the new Slate Editor formats. We were doubling our work. ?‍♂️</p>
<p>There were many smaller annoyances like searching in HTML fields or validating based on HTML length. ?</p>
<p>We also needed different places to allow/disallow certain HTML and custom elements. We only validated this in the frontend, because validating Slate structure per field was tricky. ?</p>
<p>This was combined with an increased number of places that used Slate like Comments, Ship Messages, Goals, and others. We started hitting performance issues. ?‍♂️</p>
<p>Those issues started arising one by one in the span of a year. We were in <a href="https://en.wikipedia.org/wiki/Boiling_frog">boiling frog</a> mode. Every fix was a workaround and made our system slower and more complex. ?</p>
<h2 id="howwefixedthis">How we fixed this</h2>
<p>We got stuck on an outdated Slate version - <code>0.19.30</code>. It had a lot of bugs. The latest version at the time was <code>0.44.9</code> ?</p>
<p>During this time, we were working on <a href="https://www.producthunt.com/stories">Product Hunt Stories</a> and needed to tune Slate even more. <a href="https://twitter.com/zyqxd">David</a> was the person who took the challenge to fix our Slate issues.</p>
<p>The first step was to eliminate <code>slateToHTML</code> and just use sanitized HTML as string. The GraphQL API used the Ruby converter to return strings instead of Slate Editor objects.</p>
<p><a href="https://twitter.com/zyqxd">David</a> made <code>HTMLInput</code> to accept HTML as a string and convert it to Slate Editor state. With this, our frontend was thinking we deal only with strings, not JS objects.</p>
<p>It was a challenge to handle <a href="https://www.slatejs.org/examples/embeds">custom elements</a>. There were a lot of those like the YouTube player, Tweets, gallery, and others. Those are now just simple HTML elements with data attributes:</p>
<pre><code class="language-jsx">&lt;div data-component=&quot;tweet&quot; data-tweet-id=&quot;1321095097883205632&quot; /&gt;
</code></pre>
<p>Slate can parse those and convert them to React components like <code>SlateTweetEmbed</code> or <code>SlateYouTubePlayer</code>.</p>
<p>A special model concern was added to deal with HTML sanitization and validation:</p>
<pre><code class="language-ruby">class SomeModel &lt; ApplicationRecord
  include SlateFieldOverride

  # &quot;mode&quot; defines which HTML tags are allowed
  slate_field :body, html_field: :body_html, mode: :everything
  
  # ...
end
</code></pre>
<p>The hardest thing was to migrate the database data. Fortunately, this went without any issues.</p>
<p>Our Ruby converter was battle-tested because it was already used to convert data for the frontend.</p>
<p><a href="https://twitter.com/zyqxd">David</a> migrated table by table, starting with the less relevant ones and eventually converting all of them.</p>
<p>The code for the migration looked something like:</p>
<pre><code class="language-ruby">Comment.not_migrated.find_each do |comment|
  comment.update!(
    body_html: Slate.to_html(comment.body),
  )
end
</code></pre>
<p>Having HTML to be stored just as string solved all issues we used to have:</p>
<ul>
<li>rendering HTML in React was fast</li>
<li>display in emails</li>
<li>allow/disallow certain tags per field</li>
<li>easier search in HTML fields</li>
<li>simpler data model</li>
</ul>
<p>?</p>
<h2 id="lessonslearned">Lessons learned</h2>
<ul>
<li>Don't depend on structures coming from places you don't control (like external libraries) - build a translation layer for those.</li>
<li>Isolate external dependencies, so they only have one input and one output - if we didn't have the centralized HTMLInput and backend renderer, we would have had a lot of more issues.</li>
<li>When adding a core component like a WYSIWYG Editor, which you expect to be widely used in your system - spend extra time to think through how its use is going to be expanded.</li>
<li>Not being able to update a dependency sign that you haven't isolated this dependency enough.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>I think our whole team and I learned a lot from this blunder. I hope it won't take us so much time to realize and course correct when we make such mistakes in the future.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Testing GraphQL Backend in Product Hunt]]></title><description><![CDATA[Custom tooling and conventions about testing resolver and mutation GraphQL ruby classes.
]]></description><link>https://blog.rstankov.com/testing-graphql-backend-in-product-hunt/</link><guid isPermaLink="false">5f205247434aa90004c22262</guid><category><![CDATA[GraphQL]]></category><category><![CDATA[ruby]]></category><category><![CDATA[product hunt]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Tue, 04 Aug 2020 13:01:03 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1515879218367-8466d910aaa4?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjEyNzY3fQ" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1515879218367-8466d910aaa4?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjEyNzY3fQ" alt="Testing GraphQL Backend in Product Hunt"><p>People don't write tests because it takes too much time to write them. They lose a lot of mental energy on answering trivial questions like &quot;Do we test this in our application?&quot;, &quot;Should I test this?&quot;, &quot;How do I test this?&quot;, &quot;How do I set up my test&quot;, and so on.</p>
<p>For your team to write tests, your team members need two things: good conventions on how and what to test, and good tooling.</p>
<p>I spend a lot of time making the common use-cases easy to test. In <a href="http://producthunt.com/">Product Hunt</a>, one such example is our Ruby <a href="https://graphql.org/">GraphQL</a> testing helpers.</p>
<p>We test three categories of GraphQL API code.</p>
<ol>
<li>Helpers</li>
<li>Mutation classes</li>
<li>Resolver classes</li>
</ol>
<h3 id="helpers">Helpers</h3>
<p>Those are helper module and classes like <code>RecordLoader</code> / <code>RequestInfo</code> / <code>Context</code>  / <code>RateLimiter</code> and others. We don't have many of those. They are regular Ruby objects and are tested as such.</p>
<h3 id="mutations">Mutations</h3>
<p><a href="https://graphql.org/learn/queries/#mutations">Mutations in GraphQL</a> are the operations that change your data. It is <strong>very, very</strong> important for those to be fully tested.</p>
<p>For each mutation, we test:</p>
<ul>
<li>Authentication and authorization rules</li>
<li>Failure cases - validation, errors</li>
<li>Happy paths</li>
</ul>
<p>Mutations inherit from <a href="https://blog.rstankov.com/how-product-hunt-structures-graphql-mutations/#backendtooling">Mutations::BaseMutation</a> class. <em>I have written a blog post on the topic - <a href="https://blog.rstankov.com/how-product-hunt-structures-graphql-mutations/#backendtooling">here</a></em></p>
<p>Here is an example mutation:</p>
<pre><code class="language-ruby">module Mutations
  class QuestionCreate &lt; BaseMutation
    argument :title, String, required: false

    authorize :create, Question

    returns Types::QuestionType

    def perform(attributes)
      question = Question.new(user: current_user)
      question.update(attributes)
      question
    end
  end
end
</code></pre>
<p>The way to test this, without extra tooling is:</p>
<pre><code class="language-ruby">describe Mutations::QuestionCreate do
  let(:user) { create :user }

  it 'creates a question' do
    context = Graph::Context.new(
      query: OpenStruct.new(schema: StacksSchema),
      values: context.merge(current_user: user),
      object: nil,
    )

    result = described_class.new(object: nil, context: context, field: nil).resolve(
      title: 'What email client do you use?',
    )

    expect(result[:errors]).to eq []
    expect(result[:node]).to have_attributes(
      id: be_present,
      user: user,
      title: 'What email client do you use?',
    )
  end
end
</code></pre>
<p>Yikes! ? Having to write something like this for every mutation will put off even the most enthusiastic developers.</p>
<p>The <a href="https://graphql-ruby.org/">graphql-ruby</a> classes aren't designed to be instantiated and run by you. The gem itself does this. Thus running them requires a lot of orchestration. That's why we have custom helpers to test mutations.</p>
<p>We have a <a href="https://gist.github.com/RStankov/9de68e4138758fe2042f705764e3c58c">custom helpers</a> to test mutations.</p>
<p>Here is the updated example:</p>
<pre><code class="language-ruby">describe Mutations::QuestionCreate do
  let(:user) { create :user }

  it 'requires an user' do
    # executes the mutation with one-liner
    result = execute_mutation(
      title: 'test',
    )

    # verify that mutation returned this error
    expect_access_denied_error(result)
  end

  it 'creates a question' do
    # the one-liner, allow us to specify current user
    result = execute_mutation(
      current_user: user,
      title: 'What email client do you use?',
    )

    # &quot;expect_node&quot; verify that:
    # 1. there are no errors
    # 2. &quot;node&quot; is not null
    expect_node(result) do |node|
      expect(node).to have_attributes(
        id: be_present,
        user: user,
        title: 'What email client do you use?',
      )
    end
  end

  it 'requires a title' do
    result = execute_mutation(
      current_user: user,
      title: '',
    )

    # &quot;expect_error&quot; verify that:
    # 1. &quot;node&quot; is nil
    # 2. there is an error for given attribute and message
    expect_error result, :title, :blank
  end
end
</code></pre>
<p>This is a lot better. It clearly communicates what the business logic of this mutation is.</p>
<p><a href="https://gist.github.com/RStankov/9de68e4138758fe2042f705764e3c58c">Here</a>, you can find the <a href="https://gist.github.com/RStankov/9de68e4138758fe2042f705764e3c58c">gist</a> of those helper methods.</p>
<h3 id="resolvers">Resolvers</h3>
<p>Resolvers inherit from <a href="https://graphql-ruby.org/fields/resolvers.html">GraphQL::Schema::Resolver</a>. They help us structure the code and make it easier to test. The alternative is to write the logic in the <a href="https://graphql-ruby.org/type_definitions/objects.html#object-classes">type classes</a>. We tend to avoid this because classes get bloated and are harder to test.</p>
<p>The following is an example of a resolver that tells us if the logged-in user has liked a given object:</p>
<pre><code class="language-ruby">class Resolvers::IsLiked &lt; Resolvers::Base
  type Boolean, null: false

  def resolve
    current_user = context.current_user
    return false if current_user.blank?
    
    Graph::IsLoaderPolymorphic.for(current_user.likes).load(object)
  end
end
</code></pre>
<p><em>Note: <code>Graph::IsLoaderPolymorphic</code> is batching helper. I have written about batching in GraphQL - <a href="https://blog.rstankov.com/dealing-with-n-1-with-graphql-part-1/">here</a> and <a href="https://blog.rstankov.com/dealing-with-n-1-in-graphql-part-2/">here</a></em>.</p>
<p>How will we test this?</p>
<p>We have two helpers for testing resolvers: <code>execute_resolver</code> and <code>execute_batch_resolver</code>.</p>
<pre><code class="language-ruby">describe Resolvers::IsLiked do
  let(:user) { create :user }
  let(:answer) { create :answer }

  def expect_call(object:, user:)
    expect(execute_batch_resolver(current_user: user, object: object))
  end

  it 'returns false when there is no user' do
    expect_call(object: answer, user: nil).to eq false
  end

  it 'returns false when the user has not liked the answer' do
    expect_call(object: answer, user: user).to eq false
  end

  it 'returns true when the user has liked the answer' do
    create: like, subject: answer, profile: user.profile

    expect_call(object: answer, user: user).to eq true
  end
end
</code></pre>
<p><a href="https://gist.github.com/RStankov/9de68e4138758fe2042f705764e3c58c">Here</a>, you can find the <a href="https://gist.github.com/RStankov/9de68e4138758fe2042f705764e3c58c">gist</a> of those helper methods.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Test helpers can make testing more accessible. Our GraphQL helpers are just one example of this. People should think about the domain they are testing, not about test boilerplate.</p>
<p>If you have any questions or comments, you can ping me on <a href="https://twitter.com/rstankov">Twitter</a>.</p>
</div>]]></content:encoded></item><item><title><![CDATA[How Product Hunt Structures GraphQL Mutations]]></title><description><![CDATA[<div class="kg-card-markdown"><p><a href="https://graphql.org/learn/queries/#mutations">Mutations</a> in <a href="https://graphql.org">GraphQL</a> ... mutate data. ?</p>
<p>At the time of this writing, <a href="https://www.producthunt.com/">Product Hunt</a> codebase and related projects have 222 mutations. All of our mutations have the same structure. This enables us not only to have a consistent system but to build a lot of tooling around it to make our</p></div>]]></description><link>https://blog.rstankov.com/how-product-hunt-structures-graphql-mutations/</link><guid isPermaLink="false">5d18ce9e566565000403d575</guid><category><![CDATA[GraphQL]]></category><category><![CDATA[ruby]]></category><category><![CDATA[Rails]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Tue, 02 Jul 2019 12:58:40 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1509966756634-9c23dd6e6815?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjEyNzY3fQ" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1509966756634-9c23dd6e6815?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjEyNzY3fQ" alt="How Product Hunt Structures GraphQL Mutations"><p><a href="https://graphql.org/learn/queries/#mutations">Mutations</a> in <a href="https://graphql.org">GraphQL</a> ... mutate data. ?</p>
<p>At the time of this writing, <a href="https://www.producthunt.com/">Product Hunt</a> codebase and related projects have 222 mutations. All of our mutations have the same structure. This enables us not only to have a consistent system but to build a lot of tooling around it to make our developer's life easier.</p>
<p>Here the <a href="https://www.producthunt.com/">Product Hunt</a> guide on structuring <a href="https://graphql.org/learn/queries/#mutations">GraphQL mutations</a>:</p>
<h2 id="naming">Naming</h2>
<blockquote>
<p>There are only two hard things in Computer Science: cache invalidation and <em>naming things</em><br>
-- Phil Karlton</p>
</blockquote>
<p>We have decided  to name our mutations in the following pattern:</p>
<pre><code>[module][object][action]
</code></pre>
<p>Here are some examples:</p>
<pre><code>CommentCreate
CollectionPostAdd
PostVoteCreate
PostVoteDestroy
PostSubmissionCreate
ShipContactCreate
ShipContactDestroy
</code></pre>
<p>The reason to follow this approach is consistency and easier grouping.  Autocompleting mutations names is even easier because you filter from module to object to the action.</p>
<p><img src="https://s3.amazonaws.com/rstankov-blog/uploads/2019/06/Screenshot-2019-06-30-at-18.07.19.png" alt="How Product Hunt Structures GraphQL Mutations"></p>
<h2 id="mutationshape">Mutation shape</h2>
<p>This is how a typical mutation looks like:</p>
<pre><code class="language-graphql">mutation CollectionPostAdd($input: CollectionPostAddInput!) {  
  response: collectionPostAdd(input: $input) {      
    node {
       id
       ...SomeFragment
    }    
    errors {    
      name  
      messages
    }
  }
}
</code></pre>
<p>It has the following elements:</p>
<ul>
<li>it is <a href="https://relay.dev/docs/en/next/graphql-server-specification">Relay</a> compatible</li>
<li>the result object is <strong>always</strong> named <code>node</code></li>
<li>it always has <code>error</code> array for user input validations</li>
<li>in frontend we <a href="https://graphql.org/learn/queries/#aliases">alias</a> the mutation to “response.”</li>
</ul>
<p>Let look at each one of those elementals:</p>
<h3 id="relaycompatibility">Relay compatibility</h3>
<p>We are using <a href="https://apollographql.com">Apollo</a> in our frontend. However, we try to have <a href="https://relay.dev/docs/en/next/graphql-server-specification">Relay comparable scheme</a>, just in case this situation changes.</p>
<p>In order for a <a href="https://relay.dev/docs/en/next/graphql-server-specification#mutations">mutation</a> to be <a href="https://relay.dev/docs/en/next/graphql-server-specification">Relay compatible</a>, it is required to have a field named <code>clientMutationId</code> and accepted all of its arguments as  <code>inputs</code> variable. <code>inputs</code> contains all arguments.</p>
<p>We tend to use <code>clientMutationId</code> when we don’t care about the mutation result. For example - <code>TrackEventCreate</code> .</p>
<p><code>inputs</code> is more interesting. It is an <a href="https://graphql.org/learn/schema/#input-types">input type</a>, containing all values needed for the mutation.</p>
<p>It makes it very easy to work with Forms. Because we have a single argument object to hold the form state, we can just pass this object to the mutation. When we add/remove arguments from a given mutation, we don't have to change the mutation call at all.</p>
<p>I really like this concept.</p>
<h3 id="nodefield">Node field</h3>
<p>We noticed that most mutations operate on a single object. Because <a href="https://relay.dev/docs/en/next/graphql-server-specification">Relay</a>  uses the term <code>node</code> a lot <em>(example - <a href="https://relay.dev/docs/en/next/graphql-server-specification#connections">connections</a>)</em>. We decide to name the object returned from the mutation - <code>node</code>. This makes it easier for the frontend to handle it since it knows what to expect and where to look for it. Plus backend mutation can just return an object, making defining mutations in the backend easier.</p>
<p>In some rare occasions, we might need a second object to be returned from mutation. We support this.</p>
<h3 id="errorsfield">Errors field</h3>
<p>There are a lot of debates on how to handle mutation errors. Should the <a href="https://graphql.github.io/graphql-spec/June2018/#sec-Errors">GraphQL error system</a> be used, or should we return an array of error objects?</p>
<p>I prefer returning error objects. I split the errors into two buckets based on their cause:</p>
<ol>
<li>those caused by system error</li>
<li>those caused by a user input error</li>
</ol>
<p>In <a href="https://www.producthunt.com/">Product Hunt</a>, we handle system errors with <a href="https://graphql.github.io/graphql-spec/June2018/#sec-Errors">GraphQL system error</a>, and we handle user input errors with error objects. Because of this, all our mutations have <code>error</code> fields, which return an array of <code>Error</code> objects:</p>
<pre><code class="language-graphql">type Error {
  name: String!
  messages: [String!]!
}
</code></pre>
<p>Our <a href="https://blog.rstankov.com/how-product-hunt-structures-graphql-mutations/#basemutation"><code>BaseMutation</code></a> knows how to return it and our <a href="https://blog.rstankov.com/how-product-hunt-structures-graphql-mutations/#formmutation"><code>Form.Mutation</code></a> knows how to map the errors to its input fields.</p>
<p>We use the name <code>base</code> for errors which can’t be attached to a particular field. Example: &quot;you posted too many posts for today&quot;.</p>
<p>I have written about this before ? <a href="http://blog.rstankov.com/graphql-mutations-and-form-errors/">here</a>.</p>
<h3 id="responsealias">Response alias</h3>
<p>We <a href="https://graphql.org/learn/queries/#aliases">alias</a> the mutation field name to <code>response</code>, so we can simplify our frontend code:</p>
<pre><code class="language-js">const response = await mutation({ variables: $input });


responseNode(response)
responseErrors(resoonse)
</code></pre>
<p>Notice that this code applies to every mutation we have.</p>
<h2 id="backendtooling">Backend tooling</h2>
<h3 id="basemutation">BaseMutation</h3>
<p>If you are working by schema first, enforcing those rules will be quite painful.</p>
<p>In <a href="https://www.producthunt.com/">Product Hunt</a>, we use the revolver first approach to GraphQL development. We have a lot of built-in tools on top of <a href="https://graphql-ruby.org/">GraphQL ruby gem</a>.</p>
<p>One of those tools is <code>BaseMutation</code>. Every mutation in our system uses it.</p>
<p>Here is an example:</p>
<pre><code class="language-ruby">module Graph::Mutations
  class CollectionPostAdd &lt; BaseMutation
    argument_record :collection, Collection, authorize: :edit
    argument_record :post, Post
    argument :description, String, required: false

    returns Graph::Types::CollectionPostType

    def perform(collection:, post:, description: nil)
      Collections.collect(
        current_user: current_user,
        collection: collection, 
        post: post, 
        description: description
      )
    end
  end
end
</code></pre>
<ul>
<li>it generates consistent mutations.</li>
<li>it can fetch records</li>
<li>it handles authorization</li>
<li>it knows how to map returns to <code>node</code> field</li>
<li>it knows how to handle raised errors from <a href="https://rubyonrails.org/">Ruby on Rails</a>, validation, authorization and similar</li>
</ul>
<p>The developer just has to think about:</p>
<ul>
<li>what is your input</li>
<li>what are you returning</li>
<li>what are the authorization rules</li>
<li>implement</li>
</ul>
<p>All the mechanics are handled by this class.</p>
<p>This is how we define mutations:</p>
<pre><code class="language-ruby">module Graph::Types
  class MutationType &lt; Types::BaseObject
    # we have this small helper to reduce the noise when defining mutations
    def self.mutation_field(mutation)
      field mutation.name.demodulize.underscore, mutation: mutation
    end

    mutation_field Graph::Mutations::CommentCreate
    mutation_field Graph::Mutations::CollectionPostAdd
    mutation_field Graph::Mutations::PostVoteCreate
    mutation_field Graph::Mutations::PostVoteDestroy
    mutation_field Graph::Mutations::PostSubmissionCreate
    mutation_field Graph::Mutations::ShipContactCreate
    mutation_field Graph::Mutations::ShipContactDestroy

    # ...
  end
end
</code></pre>
<h2 id="frontendtooling">Frontend tooling</h2>
<p>Having all wiring in the backend makes it very easy to build tooling for frontend. I already mentioned <code>responseNode</code> and <code>responseErrors</code>. We have them, but in reality, we use <code>Form.Mutation</code> and <code>MuttationButton</code> more.</p>
<h3 id="formmutation">Form.Mutation</h3>
<p>I have written and spoken about this before. <a href="http://blog.rstankov.com/graphql-mutations-and-form-errors/">Here</a> is a link to a post about it, and <a href="https://speakerdeck.com/rstankov/how-not-to-hate-your-life-when-dealing-with-forms">here</a> is a link to a presentation about forms in general  (here is a <a href="https://www.youtube.com/watch?v=kIi9OV338c4&amp;list=PLOEJ0eNOcZeosJfuT0dRcRWvNz4pru7SD&amp;index=7&amp;t=0s">recording</a>)</p>
<pre><code class="language-jsx">&lt;Form.Mutation onSubmit={onComplete}&gt;
  &lt;Form.Field name=&quot;title&quot; /&gt;
  &lt;Form.Field name=&quot;email&quot; control=&quot;email&quot; /&gt;
  &lt;Form.Field name=&quot;description&quot; control=&quot;textarea&quot; /&gt;
  &lt;Form.Field name=&quot;length&quot; control=&quot;select&quot; options={LENGTH_OPTIONS} /&gt;
  &lt;Form.Field name=&quot;level&quot; control=&quot;radioGroup&quot; options={LEVEL_OPTIONS} /&gt;
  &lt;Form.Field name=&quot;speakers&quot; control={SpeakersInput} /&gt;
  &lt;Form.Submit /&gt;
&lt;/Form.Mutation&gt;
</code></pre>
<p>This form handles:</p>
<ul>
<li>provides consistent UI for forms</li>
<li>map fields and arguments</li>
<li>knows how to pass inputs to mutation and interpret results</li>
<li>protects against double submit</li>
<li>handles validation errors and map them to fields</li>
<li>handles the successful submission and passes <code>node</code> to <code>onSubmit</code> handler</li>
</ul>
<h3 id="mutationbutton">MutationButton</h3>
<p>The second main way mutations are triggered by buttons. We have a React component name <code>MutationButton</code>:</p>
<pre><code class="language-js">function LikeButton({ post, onLike }) {
  const mutation = post.isLiked ? DESTROY_MUTATION : CREATE_MUTATION;
  const optimistic = post.isLiked ? optimisticDestroy : optimisticCreate;

  return (
    &lt;MutationButton
      requireLogin={true}
      mutation={mutation}
      input={{ postId: post.id }}
      optimisticResponse={optimistic(post)}
      onMutate={onLike}
      active={post.isLiked}
      icon={&lt;Like Icon /&gt;
      label={post.likesCount}
    /&gt;
  );
}
</code></pre>
<p>This  button handles:</p>
<ul>
<li>triggering the mutation with right arguments</li>
<li>protects against double click</li>
<li>handle optimistic updates</li>
<li>handles clicks from not logged in users</li>
<li>knows how to interpret the result of the mutations</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Having all those structure and tooling around mutations helps the developers to focus on business logic and not worry about mechanics. This is a big win in my book.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Dealing with N+1 in GraphQL  (Part 2)]]></title><description><![CDATA[How to deal with N+1 queries in GraphQL within Ruby on Rails project when Active Record's build in preload is not enough.  Part 2 of the series.]]></description><link>https://blog.rstankov.com/dealing-with-n-1-in-graphql-part-2/</link><guid isPermaLink="false">5ba781a4c5e5f80004aaa753</guid><category><![CDATA[ruby]]></category><category><![CDATA[Rails]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[product hunt]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Wed, 26 Sep 2018 11:23:07 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1534972195531-d756b9bfa9f2?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjEyNzY3fQ&amp;s=1498b73397f58f328ab3e68086ee9cfb" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1534972195531-d756b9bfa9f2?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjEyNzY3fQ&s=1498b73397f58f328ab3e68086ee9cfb" alt="Dealing with N+1 in GraphQL  (Part 2)"><p>In <a href="https://blog.rstankov.com/dealing-with-n-1-with-graphql-part-1">Part 1</a>, I was showing how at <a href="https://www.producthunt.com">Product Hunt</a> we use <code>GraphQL::Batch</code> to solve N+1 when loading associations.</p>
<p>Those are the obvious first candidates for optimization in the GraphQL world.<br>
But the N+1 queries is hidden in a lot of places.</p>
<p>For example, let's say we have the following query:</p>
<pre><code class="language-graphql">query {
  posts(date: 'today') {
    id
    name
    isVoted
  }
}
</code></pre>
<p>This <code>isVoted</code>  returns whether the current user (viewer) have voted for this post.</p>
<p>The naive implementation would be something like:</p>
<pre><code class="language-ruby">class Graph::Types::PostType &lt; GraphQL::Schema::Object
  field :id, ID, null: false
  # ..
  field :is_voted, function: Graph::Resolvers::IsVotedResolver.new
end

class Graph::Resolvers::IsVotedResolver &lt; GraphQL::Function
  type !types.Boolean

  def call(post, _args, ctx)
    # handle not-logged in users
    return false unless ctx.current_user?

    # check if user have voted for a post
    ctx.current_user.votes.where(post_id: post.id).exists?
  end
end
</code></pre>
<p>This generates the following queries:</p>
<pre><code class="language-sql">SELECT * FROM posts WHERE DATE(featured_at) = {date}
SELECT * FROM votes WHERE post_id={post.id} and user_id={user_id}
SELECT * FROM votes WHERE post_id={post.id} and user_id={user_id}
SELECT * FROM votes WHERE post_id={post.id} and user_id={user_id}
SELECT * FROM votes WHERE post_id={post.id} and user_id={user_id}
SELECT * FROM votes WHERE post_id={post.id} and user_id={user_id}
SELECT * FROM votes WHERE post_id={post.id} and user_id={user_id}
SELECT * FROM votes WHERE post_id={post.id} and user_id={user_id}
-- ...
</code></pre>
<p>Those are N+1. But there isn't a build in solution in <a href="https://rubyonrails.org/">Rails</a> for those. Because of this, they are often unaddressed.</p>
<p>The solution we have in <a href="http://www.producthunt.com">Product Hunt</a> for this problem as of most of N+1 is to uses <a href="https://github.com/Shopify/graphql-batch"><code>GraphQL::Batch</code></a>:</p>
<pre><code class="language-ruby">class Graph::Resolvers::IsVotedResolver &lt; GraphQL::Function
  type !types.Boolean

  def call(post, _args, ctx)
    # handle not-logged in users
    return false unless ctx.current_user?

    # `.for` ensures we get the same loader instance
    loader = VotesLoader.for(ctx.current_user)
    # `.load` adds the post id to the list of things to load
    loader.load(post.id)
    # return a future promise for this particular post load
    loader
  end

  class VotesLoader &lt; GraphQL::Batch::Loader
    def initialize(user)
      @user = user
    end
    
    # this gets called after all `isVoted` were collected
    def perform(post_ids)
      # THE TRICK: load with one query only the voted post id from votes, not all posts
      voted_post_ids = @user.votes.where(post_id: post_ids).pluck(:post_id)

      # fulfill the future promises
      post_ids.each do |post_id|
        fulfill post_id, voted_post_ids.include?(post_id)
      end
    end
  end
end
</code></pre>
<p>This now generates only two queries.</p>
<pre><code class="language-sql">SELECT * FROM posts WHERE DATE(featured_at) = {date}
SELECT post_id FROM votes WHERE post_id IN ({post_ids})
</code></pre>
<p>This problem exists with normal <a href="https://rubyonrails.org/">Rails</a> applications. It isn't GraphQL specific. But I have rarely seen this being addressed. With  <code>GraphQL::Batch</code> we can elegantly solve this problem.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Dealing with N+1 in GraphQL  (Part 1)]]></title><description><![CDATA[How to deal with N+1 queries in GraphQL within Ruby on Rails project. Part 1 of the series.]]></description><link>https://blog.rstankov.com/dealing-with-n-1-with-graphql-part-1/</link><guid isPermaLink="false">5b5606ebdd3b93000404cd80</guid><category><![CDATA[ruby]]></category><category><![CDATA[Rails]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[product hunt]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Tue, 07 Aug 2018 12:19:41 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1484417894907-623942c8ee29?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjEyNzY3fQ&amp;s=676bfb6a4b21f736d765c193ea250e09" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1484417894907-623942c8ee29?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjEyNzY3fQ&s=676bfb6a4b21f736d765c193ea250e09" alt="Dealing with N+1 in GraphQL  (Part 1)"><p>When people see GraphQL their first question - what about N+1 problems.</p>
<p>This is a legit question.  In <a href="https://www.producthunt.com/">Product Hunt</a> as in every GraphQL powered project, we have the same issues.</p>
<p>For example, let's say we have the following query:</p>
<pre><code class="language-graphql">query {
  posts(date: 'today') {
    id
    name
    slug
    topics {
      id
      name
      slug
    }
  }
}
</code></pre>
<p>And the following type definition for a post:</p>
<pre><code class="language-ruby">class Graph::Types::PostType &lt; GraphQL::Schema::Object
  field :id, ID, null: false
  field :name, String, null: false
  field :slug, String, null: false
  field :topics, [Graph::Types::TopicType], null: false
end
</code></pre>
<p>This would result the following queries:</p>
<pre><code class="language-sql">SELECT * FROM posts WHERE DATE(featured_at) = {date}
SELECT * FROM topics JOIN post_topics WHERE post_id = {post_id}
SELECT * FROM topics JOIN post_topics WHERE post_id = {post_id}
SELECT * FROM topics JOIN post_topics WHERE post_id = {post_id}
SELECT * FROM topics JOIN post_topics WHERE post_id = {post_id}
SELECT * FROM topics JOIN post_topics WHERE post_id = {post_id}
SELECT * FROM topics JOIN post_topics WHERE post_id = {post_id}
-- ...
</code></pre>
<p>All those N+1s SQL queries ?</p>
<p>We don't only have additional queries but when a couple of posts have the same topics we load those multiple times.</p>
<p>The best solution, I have found to this problem so far was to use <a href="https://github.com/Shopify/graphql-batch"><code>GraphQL::Batch</code></a> gem from Shopify.</p>
<p>Adding the gem to your project is simple.</p>
<pre><code class="language-ruby">class Graph::Schema &lt;&lt; GraphQL::Schema
  # ...

  use GraphQL::Batch

  # ...
end
</code></pre>
<p>The way batching works: Instead of executing the query immediately, it returns an instance of <code>GraphQL::Batch::Loader</code>. The same instance of a loader object is returned for a given field leaf. After all fields for a given GraphQL query leaf are collected, then the <code>perform</code> method of the loader is called with all requested records. <code>preform</code> then must load and return for each record the requested data.</p>
<p>Here is how are going to solve the N+1 for topics with <a href="https://github.com/Shopify/graphql-batch"><code>GraphQL::Batch</code></a>.</p>
<p>First, we change the post type to use a new resolver.</p>
<pre><code class="language-ruby">class Graph::Types::PostType &lt; GraphQL::Schema::Object
  # ...

  field :topics, [Graph::Types::TopicType], null: false, function: Graph::Resolvers::Posts::TopicsResolver.new
end
</code></pre>
<p>Define the resolver itself:</p>
<pre><code class="language-ruby">class Graph::Resolvers::Posts::TopicsResolver &lt; GraphQL::Function
  def call(post, _args, _ctx)
    # `.for` makes sure we return the same loader instance
    # so all leaves, so we can group data
    loader = TopicsLoader.for
    # adds a post to the list of posts to be loaded
    loader.load(post)
    # returns the loader, not the actual topics
    # this gets transformed into topics afterward
    loader
  end

  # Loaders represent promises and mechanism to 
  # postpone loading until we have all posts in the list
  class TopicsLoader &lt; GraphQL::Batch::Loader
    # perform called with all the posts
    def perform(posts)
      # this is the built-in active record mechanism to 
      # preload associations into a group of records
      # association are loaded with the minimum amount of queries
      # if a couple of posts have same topics they would be loaded once
      ::ActiveRecord::Associations::Preloader.new.preload(posts, :topics)
      
      posts.each do |post|
        # returns topics for every post in the list
        fulfill post, post.topics
      end
    end
  end
end
</code></pre>
<p>Now we have a lot less queries ?</p>
<pre><code class="language-sql">SELECT * FROM posts WHERE DATE(featured_at) = {date}
SELECT * FROM topics JOIN post_topics WHERE post_id IN ({post_ids})
</code></pre>
<p>Also, each topic is loaded only once ?</p>
<p>Since loading associations is a very typical problem, I have created a generic <a href="https://gist.github.com/RStankov/48070003a31d71a66f57a237e27d5865"><code>AssociationLoader</code></a>. It can be found in this <a href="https://gist.github.com/RStankov/48070003a31d71a66f57a237e27d5865">gist</a>.</p>
<pre><code class="language-ruby">class Graph::Types::PostType &lt; GraphQL::Schema::Object
  # ...

  field :topics, [Graph::Types::TopicType], null: false, function: Graph::Resolvers::AssociationResolver.new(:topics)
end
</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[Learnings From Graphql Europe]]></title><description><![CDATA[This weekend, I had the pleasure to attend GraphQL-Europe. Here is a recap of most notable things I learned there.]]></description><link>https://blog.rstankov.com/learnings-from-graphql-europe/</link><guid isPermaLink="false">598468bfaf8dad5ca316eb5d</guid><category><![CDATA[GraphQL]]></category><category><![CDATA[ruby]]></category><category><![CDATA[conference]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Wed, 24 May 2017 07:22:29 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1498463805989-f61ca7700381?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=8e826815d1c87cf94c87e0a584ca49b0" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1498463805989-f61ca7700381?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=8e826815d1c87cf94c87e0a584ca49b0" alt="Learnings From Graphql Europe"><p>This weekend, I had the pleasure to attend <a href="https://graphql-europe.org">GraphQL-Europe</a> in Berlin, the first <a href="https://blog.rstankov.com/learnings-from-graphql-europe/graphql.org">GraphQL</a> conference in Europe.</p>
<p>Here is a recap of most notable things I learned there:</p>
<h2 id="howgraphqlgotfromzerotowideadoptioninoneyear">How GraphQL got from zero to wide adoption in one year</h2>
<p><a href="http://facebook.github.io/graphql/">GraphQL spec</a> was released less than an <a href="http://graphql.org/blog/production-ready/">year ago</a>. But it feels a lot more mature. This is because it was in production at Facebook for about 5 years before open-sourcing it.</p>
<p>The <a href="https://graphql-europe.org">conference</a> started with &quot;<a href="https://graphql-europe.org/schedule/graphql-evolution-or-revolution">GraphQL: Evolution or Revolution?</a>&quot; a great history lesson by <a href="https://twitter.com/helferjs">Jonas Helfer</a>.</p>
<p>In <a href="https://graphql-europe.org/schedule/five-years-of-client-graphql-infrastructure">Five Years of Client GraphQL Infrastructure</a>, <a href="https://twitter.com/dlschafer">Dan Schafer</a>  told us a couple of stories about how features like mutations were invented. There wasn’t much upfront design, they were just solving issue after issue. First in the frontend, then moving to the backend.</p>
<p>This connected very well with the <a href="https://graphql-europe.org/schedule/closing-keynote">Closing Keynote</a> by <a href="https://twitter.com/leeb">Lee Byron</a>. Where he talked about the future of GraphQL.</p>
<p>Those three talks convinced me that, GraphQL is not an accident.</p>
<h2 id="subscriptions">Subscriptions</h2>
<p>I haven't paid much attention of GraphQL Subscriptions in the past. they are now <a href="https://github.com/facebook/graphql/pull/305">officially merged</a> into the GraphQL spec.</p>
<p>In <a href="https://graphql-europe.org/schedule/realtime-graphql-from-the-trenches">Realtime GraphQL from the Trenches</a>, <a href="https://twitter.com/tazsingh">Taz Singh</a> gave an interesting look into them.</p>
<p>I don't have many reasons to use them, mostly because <a href="https://www.producthunt.com/">Product Hunt</a> doesn't need them at this time.</p>
<h2 id="persistedqueries">Persisted queries</h2>
<p>Another feature I haven’t paid much attention to are <a href="https://dev-blog.apollodata.com/persisted-graphql-queries-with-apollo-client-119fd7e6bba5">persisted queries</a>. Which is a miss on my part.</p>
<p>I had a couple conversations about persisted queries in between talks. Except for their obvious benefits - small requests, analytics and caching. I haven't realized they are also good from a security standpoint. Since if your GraphQL accepts only persisted queries in production, this makes impossible for somebody to open Chrome console and start firing expensive custom queries.</p>
<h2 id="handlingerrors">Handling errors</h2>
<p>The <a href="https://graphql-europe.org/schedule/panel-discussion">Panel Discussion</a> was very interesting. One of my major learnings from there - is that there isn't a standard way in GraphQL to handle form errors.</p>
<p>The format, I personally prefer looks something like this:</p>
<pre><code class="language-graphql">updateProfile(input: UpdateProfileInput) {  
  node: Profile
  errors: [Error]!
}
</code></pre>
<p><em>(I plan to write a blog post about form errors, someday)</em></p>
<p>I didn't know in GraphQL, you can return both a response and an error. It turns out that is a good strategy to handle failures in resolvers, which don't break the whole request. You can just return <code>null</code> for a given node and return errors explaining the failure.</p>
<pre><code class="language-json">{
  &quot;errors&quot;: [
    {
      &quot;message&quot;: &quot;Service for fetching widget 1 failed&quot;,
      &quot;locations&quot;: [ { &quot;line&quot;: 4, &quot;column&quot;: 5 } ],
      &quot;path&quot;: [ &quot;widget1&quot; ]
    }
  ],
  &quot;data&quot;: {
    &quot;widget1&quot;: null,
    &quot;widget2&quot;: {
      key: &quot;value&quot;,
    }
  }
}
</code></pre>
<h2 id="graphqlinrubyworld">GraphQL in Ruby world</h2>
<p><a href="http://graphql-ruby.org/">Ruby GraphQL gem</a>, used by <a href="https://github.com/">github</a> and <a href="https://www.shopify.com/">Shopify</a>, is quite powerful and battle tested.</p>
<p><a href="https://twitter.com/nettofarah">Netto Farah</a> show a great solution for battling N+1 queries in Ruby project - <a href="https://github.com/nettofarah/graphql-query-resolver">GraphQL::QueryResolver</a>.<br>
Also in the same <a href="https://graphql-europe.org/schedule/fighting-legacy-codebases-with-graphql-and-rails">talk</a>, he gave great tips about better tracking of GraphQL. If you are using GraphQL with Ruby definitely check his <a href="https://speakerdeck.com/nettofarah/rescuing-legacy-codebases-with-graphql-1">slides</a>.</p>
<p><a href="https://graphql-europe.org/schedule/launching-githubs-public-graphql-api">Launching GitHub's Public GraphQL API</a> gave great tips about:</p>
<ul>
<li>handling authorization</li>
<li>using GraphQL for backfilling legacy REST endpoints</li>
<li>tips about schema design</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Overall <a href="https://graphql-europe.org/">GraphQL-Europe</a> was great. Kudos to <a href="https://www.honeypot.io/">Honeypot</a> and <a href="https://www.graph.cool/">Graphcool</a> for organizing such great event.</p>
<p>p.s. I almost forgot! I finally found out what <a href="http://www.odata.org/">OData</a> is :P.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Introducing SearchObject GraphQL Plugin]]></title><description><![CDATA[Reasoning behind SearchObject plugin for GraphQL Ruby gem.]]></description><link>https://blog.rstankov.com/introducing-searchobject-graphql-plugin/</link><guid isPermaLink="false">598468bfaf8dad5ca316eb5c</guid><category><![CDATA[GraphQL]]></category><category><![CDATA[ruby]]></category><category><![CDATA[search_object]]></category><category><![CDATA[open_source]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Mon, 15 May 2017 07:16:38 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1461749280684-dccba630e2f6?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=6d5f72185efb15cfd4e816ecd1a6c966" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1461749280684-dccba630e2f6?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=6d5f72185efb15cfd4e816ecd1a6c966" alt="Introducing SearchObject GraphQL Plugin"><p>When I started using <a href="http://graphql.org/">GraphQL</a>, I immediately saw, that <a href="https://github.com/RStankov/SearchObject">SearchObject</a> would be a perfect fit for search resolvers.</p>
<p>Having a <a href="http://graphql.org/">GraphQL</a> query to fetch the first 10 most recent published news posts would look something like this:</p>
<pre><code class="language-graphql">query {
  posts(first: 10, categoryName: &quot;News&quot;, order: &quot;RECENT&quot;, published: true) {    
    id  
    title  
    body  
    author {      
       id
       name
    }
  }
}
</code></pre>
<p>And it would have a corresponding <a href="https://github.com/RStankov/SearchObject">SearchObject</a>:</p>
<pre><code class="language-ruby">class Resolvers::PostSearch  
  include SearchObject.module  
  
  scope { Post.all }    
  
  option :categoryName, with: :apply_category_name_filter
  option :published, with: :apply_published_filter
  option :order, enum: %i(RECENT VIEWS LIKES)  
  
  # ... definitions of the option methods
end
</code></pre>
<p>So clean. ☀️</p>
<p>But then, <code>PostSearch</code> have to be connected with <a href="https://rmosolgo.github.io/graphql-ruby/">GraphQL Ruby gem</a>:</p>
<pre><code class="language-ruby">PostOrderEnum = GraphQL::EnumType.define do
  name 'PostOrder'

  value 'RECENT'
  value 'VIEWS'
  value 'LIKES'
end  

Types::QueryType = GraphQL::ObjectType.define do
  name 'Query'

  field :posts, types[Types::PostType] do
    argument :categoryName, types.String  
    argument :published, types.Boolean  
    argument :order, PostOrderEnum
    resolve -&gt;(_obj, args, _ctx) { Resolvers::PostSearch.results(filters: args.to_h) } 
  end
end
</code></pre>
<p>That isn't so bad. ?</p>
<p>But then, thinking about how can this code can change in the future:</p>
<ul>
<li>adding/removing options would involve going to both files</li>
<li>adding a new order option would mean searching for the <code>PostOrderEnum</code> and manually sync it with <code>PostSearch</code> enum</li>
<li>reusing <code>PostSearch</code> in other types, for queries like:  <code>query { user(id: 1) { posts(published: true) } }</code>
<ul>
<li>requires <a href="https://en.wikipedia.org/wiki/Copy_and_paste_programming">copy and paste</a> argument/type definitions</li>
<li>which makes updating the resolver even harder</li>
</ul>
</li>
</ul>
<p>Yikes! ? ?</p>
<p>This is where <a href="https://github.com/RStankov/SearchObjectGraphQL">SearchObject::Plugin::GraphQL</a> comes in. It puts type definitions and the resolver itself:</p>
<pre><code class="language-ruby">class Resolvers::PostSearch  
  # include the the plugin
  include SearchObject.module(:graphql)
    
  # documentation and type about this resolver
  # can be provided into the resolver itself
  type types[Types::PostType]
  description 'Lists posts'

  # enums or other types can also be nested
  OrderEnum = GraphQL::EnumType.define do
    name 'PostOrder'

    value 'RECENT'
    value 'VIEWS'
    value 'LIKES'
  end
    
  scope { Post.all }    
  
  # options just need to have a their type specifed.
  option :categoryName, type: types.String, with: :apply_category_name_filter
  option :published, type: types.Boolean, with: :apply_published_filter  
  # enums are automatically handled
  option :order, type: OrderEnum
  
  # ... definitions of the option methods
end
</code></pre>
<p>Then <code>PostSearch</code> can be used just as <a href="https://rmosolgo.github.io/graphql-ruby/schema/code_reuse#functions">GraphQL::Function</a>:</p>
<pre><code class="language-ruby">Types::QueryType = GraphQL::ObjectType.define do
  name 'Query'

  field :posts, function: Resolvers::PostSearch
end
</code></pre>
<p>Now, changing filter options requires changing only a single file. <code>PostSearch</code> can be reused in other types, by just adding <code>function: Resolvers::PostSearch</code>.</p>
<p>For more information check <a href="https://github.com/RStankov/SearchObjectGraphQL/tree/master/example">SearchObject::Plugin::GraphQL example</a>.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Introducing KittyEvents]]></title><description><![CDATA[Implements the publish/subscribe pattern using ActiveJob. You setup your events and list the subscribers for them. When an event is triggered, KittyEvents will fanout the event to each of your subscribers.]]></description><link>https://blog.rstankov.com/introducing-kittyevents/</link><guid isPermaLink="false">598468bfaf8dad5ca316eb6a</guid><category><![CDATA[ruby]]></category><category><![CDATA[open_source]]></category><category><![CDATA[Rails]]></category><category><![CDATA[product hunt]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Mon, 20 Feb 2017 19:08:45 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1489389944381-3471b5b30f04?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=64e35eedb964d117543506bea852008f" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1489389944381-3471b5b30f04?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=64e35eedb964d117543506bea852008f" alt="Introducing KittyEvents"><p>During the Christmas break me and <a href="https://twitter.com/mscccc">Mike</a> were discussing a new feature at <a href="https://www.producthunt.com">Product Hunt</a>. The feature required scheduling an <a href="http://guides.rubyonrails.org/active_job_basics.html">ActiveJobs</a> when a user signs up, votes or submits a comment.</p>
<p>There is <code>SignUp</code> object which handles user registration. So scheduling a new background job there is quite simple:</p>
<pre><code class="language-ruby">module SignUp
  # ... handle user sign up

  def after_sign_up(user)
    WelcomeEmailWorker.perform_later(user)
    WelcomeTweetWorker.perform_later(user)
    SyncProfileImageWorker.perform_later(user)
    NewFancyFeatureWorker.perform_later(user) # &lt;- new worker
  end
end
</code></pre>
<p>Unfortunately <code>after_sign_up</code> method was becoming quite large ☹️<br>
Now imagine having to add <code>NewFancyFeatureWorker</code> to 10 other places ?</p>
<p>Those issues pushed us to create a simple wrapper around <a href="http://guides.rubyonrails.org/active_job_basics.html">ActiveJobs</a>, which we call <a href="https://github.com/producthunt/KittyEvents">KittyEvents</a>.</p>
<p>Now in <code>SignUp</code> there is just one trigger for an &quot;event&quot;:</p>
<pre><code class="language-ruby">module SignUp
  # ... handle user sign up

  def after_sign_up(user)
    ApplicationEvents.trigger(:user_signup, user)
  end
end
</code></pre>
<p>And there is a central place, where events are mapped to  <a href="http://guides.rubyonrails.org/active_job_basics.html">ActiveJobs Workers</a>:</p>
<pre><code class="language-ruby"># config/initializers/application_events.rb
module ApplicationEvents
  extend KittyEvents

  event :user_signup, [
    WelcomeEmailWorker,
    WelcomeTweetWorker,
    SyncProfileImageWorker,
    NewFancyFeatureWorker, # &lt;- new worker
  ]

  # ... other events
end
</code></pre>
<p>When an event is triggered, all <a href="http://guides.rubyonrails.org/active_job_basics.html">ActiveJobs Workers</a> for this events are scheduled and executed.</p>
<p>Another bonus, is when using <a href="https://github.com/producthunt/KittyEvents">KittyEvents</a>, you only make a single Redis call to trigger any number of events. This shaves off precious milliseconds when using <a href="https://github.com/producthunt/KittyEvents">KittyEvents</a> in request.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Introducing MiniForm]]></title><description><![CDATA[Introducing my newest gem - MiniForm. It is group of helpers for dealing with form objects and nested forms.]]></description><link>https://blog.rstankov.com/introducing-miniform/</link><guid isPermaLink="false">598468bfaf8dad5ca316eb69</guid><category><![CDATA[open_source]]></category><category><![CDATA[ruby]]></category><category><![CDATA[Rails]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Mon, 02 Mar 2015 03:23:26 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1461632830798-3adb3034e4c8?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=2a2103abf46489c584928a10cb189f84" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1461632830798-3adb3034e4c8?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=2a2103abf46489c584928a10cb189f84" alt="Introducing MiniForm"><p>My last blog post - <a href="https://blog.rstankov.com/update_as_design/">Dealing with form objects</a>, got me thinking about form objects. I also had a 12 hours flight to San Francisco, where I was meeting my teammates at <a href="http://www.producthunt.com/">Product Hunt</a>.</p>
<p>During that flight, I went back and checked all of my custom form implementations. I gathered them together in a folder and started combining and extracting the common parts. By the end of the flight I almost had what now is <a href="https://github.com/RStankov/MiniForm">MiniForm</a>. (I had to change its name several times since I'm not very good with names and most of the decent names are not available).</p>
<p><a href="https://github.com/RStankov/MiniForm">MiniForm</a> allows the following code:</p>
<pre><code class="language-ruby"> class RegistrationForm  
  include ActiveModel::Model

  attr_reader :user, :account

  attr_accessor :first_name, :last_name, :email, :name, :plan, :terms_of_service

  # user validation
  validates :first_name, presence: true
  validates :last_name, presence: true
  validates :email, presence: true, email: true

  # account validation
  validates :account_name, presence: true

  # form custom validation
  validates :plan, inclusion: {in AccountPlan::VALUES}
  validates :terms_of_service, acceptance: true

  # ensure uniqueness
  validate :ensure_unique_user_email
  validate :ensure_unique_account_name

  def initialize
    @user    = User.new
    @account = Account.new owner: @user
  end

  def update(attributes)
    attributes.each do |name, value|
      public_send &quot;#{name}=&quot;, value
    end

    if valid? &amp; user.update(user_attributes) &amp;&amp; account.update(account_attributes)
      account.users &lt;&lt; user
      AccountWasCreated.perform_later(account)
    else
      false
    end
  end

  private

  def ensure_unique_user_email
    errors.add :email, 'already taken' if User.where(email: email).any?
  end

  def ensure_unique_account_name
    errors.add :name, 'already taken' if Account.where(name: name).any?
  end

  def user_attributes
    {first_name: first_name, last_name: last_name, email: email}
  end

  def account_attributes
    {plan: plan, name: name}
  end
end 
</code></pre>
<p>To become:</p>
<pre><code class="language-ruby">class RegistrationForm  
  include MiniForm::Model

  # `model` delegates attributes to models 
  # and copies the validations from them
  model :user, attributes: %i(first_name last_name email), save: true
  model :account, attributes: %i(name plan), save: true

  attributes :terms_of_service

  validates :plan, inclusion: {in AccountPlan::VALUES}
  validates :terms_of_service, acceptance: true

  def initialize
    @user    = User.new
    @account = Account.new owner: @user
  end

  # `update` calls `perform` if model is valid
  # and models are saved
  def perform
    account.users &lt;&lt; user
    AccountWasCreated.perform_later(account)
  end
end
</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[SearchObject 1.1]]></title><description><![CDATA[New features in the newest version of Search Object.]]></description><link>https://blog.rstankov.com/searchobject-1-1/</link><guid isPermaLink="false">598468bfaf8dad5ca316eb64</guid><category><![CDATA[ruby]]></category><category><![CDATA[open_source]]></category><category><![CDATA[search_object]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Fri, 30 Jan 2015 15:14:58 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1493119508027-2b584f234d6c?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=6698dc83ba2c4e48638c8aa13810999f" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1493119508027-2b584f234d6c?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=6698dc83ba2c4e48638c8aa13810999f" alt="SearchObject 1.1"><p>Today I released version 1.1 of my <a href="https://rubygems.org/gems/search_object">Search Object</a> gem. It has two major new features.</p>
<h3 id="usinginstancemethodforstraightdispatch">Using instance method for straight dispatch</h3>
<p>Suggested and developed by <a href="https://twitter.com/gsamokovarov">Genadi Samokovarov</a>:</p>
<pre><code class="language-ruby">class ProductSearch
  include SearchObject.module

  scope { Product.all }

  option :date, with: :parse_dates

  private

  def parse_dates(scope, value)
    # some &quot;magic&quot; method to parse dates
  end
end
</code></pre>
<h3 id="classesmixedwithsearchobjectcanbeinherited">Classes mixed with SearchObject can be inherited</h3>
<p>I had this setting on a branch for a long time. In one of the projects I worked on it would have been usefull to have a search base object:</p>
<pre><code class="language-ruby">class BaseSearch
  include SearchObject.module

  # ... options and configuration
end
 
# and used as 
class ProductSearch &lt; BaseSearch
  scope { Product }
end
</code></pre>
<p>Its implementation actually might push me into an interesting refactoring.</p>
<h3 id="otherminorfeatures">Other minor features</h3>
<ul>
<li>I added Rubocop for verifying the project</li>
<li>I started testing against Ruby 2.2</li>
<li>I stopped testing against Ruby 1.9</li>
</ul>
</div>]]></content:encoded></item><item><title><![CDATA[Preserving Named Arguments On Inheritance]]></title><description><![CDATA[Ruby doesn't care very much carrying when you overwrite a method. The method is just replaced by the new method. But what happens with named arguments?]]></description><link>https://blog.rstankov.com/rubys-named-argument-and-super/</link><guid isPermaLink="false">598468bfaf8dad5ca316eb65</guid><category><![CDATA[ruby]]></category><dc:creator><![CDATA[Radoslav Stankov]]></dc:creator><pubDate>Mon, 05 Jan 2015 21:30:27 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1457305237443-44c3d5a30b89?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;s=4063ad6beb4b1939f2ed65ef3207c5d4" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://images.unsplash.com/photo-1457305237443-44c3d5a30b89?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=4063ad6beb4b1939f2ed65ef3207c5d4" alt="Preserving Named Arguments On Inheritance"><p>I don't use inheritance very often, but sometimes it is the best solution. Ruby doesn't care very much carrying when you overwrite a method. The method is just replaced by the new method. It doesn't care about arguments at all.</p>
<p>Since 2.0, Ruby support <a href="http://robots.thoughtbot.com/ruby-2-keyword-arguments">named arguments</a>. I'm starting to use them more and more. But they are a bit tricky when overwriting.</p>
<p>Let's say we have a class which creates user objects named <code>UserBuilder</code>:</p>
<pre><code class="language-ruby">class UserBuilder
  def create(name: )
    puts &quot;name: #{name}&quot;
  end
end
</code></pre>
<p>When I have to create a <code>CustomerBuilder</code>, which creates customers (user with address), I can write it like:</p>
<pre><code class="language-ruby">class CustomerBuilder &lt; UserBuilder
  def create(address:, name:)
    super name: name
    puts &quot;address: #{address}&quot;
  end
end
</code></pre>
<p>This works but creates a coupling between <code>UserBuilder</code> and <code>CustomerBuilder</code>. This coupling is a bit tricky to catch. Imagine the following scenarios:</p>
<ul>
<li><code>name</code> become optional in <code>UserBuilder</code></li>
<li><code>name</code> is renamed to <code>full_name</code> in <code>UserBuilder</code></li>
<li><code>name</code> is split into <code>first_name</code> and <code>last_name</code> in <code>UserBuilder</code></li>
</ul>
<p>In all of those cases change in <code>UserBuilder</code> triggers changes in <code>CustomerBuilder</code>.</p>
<p>Such dependencies are toxic. Here is a better solution:</p>
<pre><code class="language-ruby">class CustomerBuilder &lt; UserBuilder
  def create(address:, **args)
    super **args
    puts &quot;address: #{address}&quot;
  end
end
</code></pre>
<p>Now we can add, change, remove named arguments to <code>UserBuilder#create</code> and <code>CustomerBuilder#create</code> is not affected. Named arguments are just delegated down the change.</p>
</div>]]></content:encoded></item></channel></rss>