<?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: Nikola Buhinicek</title>
    <description>The latest articles on DEV Community by Nikola Buhinicek (@buha).</description>
    <link>https://dev.to/buha</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%2F131109%2Fdcadb67d-103e-4b02-8db9-f25722785ad6.png</url>
      <title>DEV Community: Nikola Buhinicek</title>
      <link>https://dev.to/buha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/buha"/>
    <language>en</language>
    <item>
      <title>Engineering Ladder - Being in The Middle Sucks?</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Tue, 23 Jan 2024 07:59:12 +0000</pubDate>
      <link>https://dev.to/productive/engineering-ladder-being-in-the-middle-sucks-2ei9</link>
      <guid>https://dev.to/productive/engineering-ladder-being-in-the-middle-sucks-2ei9</guid>
      <description>&lt;p&gt;You might be wondering to what "middle" am I referring to? &lt;/p&gt;

&lt;p&gt;It's the middle I'm currently in, the middle of the &lt;a href="http://www.engineeringladders.com/"&gt;Engineering Ladder&lt;/a&gt;, focusing on the &lt;strong&gt;Tech Lead&lt;/strong&gt; role. This role sits between the &lt;strong&gt;Developer&lt;/strong&gt;/&lt;strong&gt;Individual Contributor&lt;/strong&gt; and "&lt;em&gt;higher&lt;/em&gt;" roles like &lt;strong&gt;Staff Engineer (SE)&lt;/strong&gt;  or &lt;strong&gt;Engineering Manager (EM)&lt;/strong&gt;, combining aspects of both. &lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Tech Lead&lt;/strong&gt; is still contributing to the code base and has its own fair share of tasks/milestones/deliverables, but he is not anymore working "&lt;em&gt;on its own&lt;/em&gt;". He has a team of people to lead, to watch over the codebase/architecture, to approve technical specifications, to watch over the product and more. &lt;/p&gt;

&lt;p&gt;As I'm diving into this role, I wanted to give you my cent or two on what I think is the most interesting part of the role "&lt;em&gt;upgrade&lt;/em&gt;". It's about the &lt;strong&gt;Tech Leads relationships&lt;/strong&gt; to both the &lt;strong&gt;Developers on one&lt;/strong&gt; and the &lt;strong&gt;SEs and EMs on the other side&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Just to be clear, &lt;strong&gt;this won't be 1:1&lt;/strong&gt; to all Tech Lead roles, their responsibilities and their descriptions. This is just my point of view on being a &lt;strong&gt;Tech Lead&lt;/strong&gt; at &lt;strong&gt;Productive&lt;/strong&gt; and the way we see this role.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Lead - baby EM?
&lt;/h2&gt;

&lt;p&gt;If you read books or posts about management, you probably read something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Being a Tech Lead is an exercise in leading without authority.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;strong&gt;without authority&lt;/strong&gt; part is pretty true imo. Your work starts to depend on other people but at the same time you don't really have some authority over them. You can and must have the situation in control but if the situation gets out of hand, you can't resolve it on your own - you need your EM. Again, this depends on the level that your company expects you to work on as a TL.&lt;/p&gt;

&lt;p&gt;For me and the way I see it, the most interesting and challenging part of being a Tech Lead is exactly that, the subtle intro to a management role.&lt;/p&gt;

&lt;p&gt;It's a role where you should really start thinking a lot about the people you work with.&lt;/p&gt;

&lt;p&gt;Several relations come into play and thats mostly bcz you have to watch both up and down from where you're standing. &lt;/p&gt;

&lt;h3&gt;
  
  
  Looking up the 🪜 - Staff Engineers and Engineering Managers
&lt;/h3&gt;

&lt;p&gt;Your relationship with your EM must be build on solid ground. Your EM has to be a person you enjoy working with, you have a lot to learn from and a person you respect for the things he has done and still is doing. If you are not on the same page, it's less likely that you will benefit from this relationship as much as you could in a "good setup".&lt;br&gt;
In my case, both of my EMs over the time were people I worked with from the first day in Productive. Over the years while working here, I learned a lot from them. The technical aspect is not as important here as the whole overview on how they get things done is - how do they approach problems and how do they solve them.&lt;br&gt;
Other than having a good relationship with you SE/EMs, the main point here is that &lt;strong&gt;you have to trust their calls and support them publicly&lt;/strong&gt;, to help them set those ideas in action. If you don't believe that what they are doing is going to help either you, your team or your organization, you will do a "bad job" here. That's because you in the first place won't accept those new ideas and implement them into your workflows and processes. How could we then expect the rest of our team to do so? &lt;strong&gt;One should lead by example&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking down the 🪜 - Software Engineers
&lt;/h3&gt;

&lt;p&gt;On the other side, you also have to have really good relations with the people you "manage", the people in your team. In my case, being a TL for the Core Team, there are 3 Backend (BE) and 4 Frontend (FE) engineers (including me and the FE TL).&lt;br&gt;
It is essential for us to know the strengths and weaknesses of our peers so that we could assign them the tasks in which they will prosper and in which they could learn and gain knowledge.&lt;br&gt;
You also need to know them in order to know how to best approach them and to set your EMs ideas in motion. How to present those ideas to them in a way that they also see a benefit in them and not just "something new" they have to do.&lt;br&gt;
These relationships and the work you make on this front will tell you if you are fit to be in management or not. Working with people is not for everyone and those who do it should do it for the right reasons - and that's basically to do it for the people you are managing and seeing them grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking at yourself 🪞
&lt;/h3&gt;

&lt;p&gt;This is really important. Don't forget to look on yourself - on your motivation, on your moral and your overall "standards". &lt;br&gt;
You could be great with both your SE/EM and your team members, but &lt;strong&gt;you shouldn't just be a proxy between them&lt;/strong&gt;. &lt;br&gt;
Have your opinions, question your EMs ideas, question your team members and look at things without bias. Not everything proposed by any side should be accepted blindly - as I said before, you should know all the details of the changes you are implementing so that you could expect people to follow.&lt;br&gt;
For each idea that you are working on you should know what is the thing/problem your are solving, how are you solving it, what are the benefits of doing this and what could you come by down the road.&lt;br&gt;
Those are the topics that you will most likely be going over with your EM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take home from this 🙃
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ask for Feedback
&lt;/h3&gt;

&lt;p&gt;What I think that people in this role, actually any new position, would benefit the most is &lt;strong&gt;feedback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For me, feedback is always welcome. Often times I even ask for it before the scheduled 1-on-1s, 360s or other kinds of performance reviews.&lt;/p&gt;

&lt;p&gt;It should be pretty obvious when a newly promoted TL starts doing some of his new chores and that can go in both directions. Anyway it goes, it's great when your EM has your back. When he shares positive feedback about the good things you are doing and how to make the "bad" things better, how to handle them in a more appropriate way. &lt;/p&gt;

&lt;p&gt;So my dear EMs, watch on your TLs, look out what they are doing, tailor your 1-on-1's to check the progress and their thoughts. Try remembering when you went thru this phase and what helped you to adjust to this new role. &lt;/p&gt;

&lt;h4&gt;
  
  
  Tell the people around you what's up
&lt;/h4&gt;

&lt;p&gt;I noticed that my communication to some of my peers lately changed. In our chat there was a lot more work related messages - asking for status updates, asking for estimates, proposing them changes in their workflows, asking for explanations on decision, ... But I never actually told them why did I start making all those questions, asking for answers and started giving advice on workflows/processes. &lt;/p&gt;

&lt;p&gt;So I wrote them a message explaining that now, as a TL and owner of some milestones, I have to have a better overview of their work and that we will have to communicate more often. &lt;br&gt;
That I will probably be annoying them with status checks on tasks - trying not to micromanage tho. &lt;br&gt;
That they can involve me in their Github Pull Requests (PRs) where they usually wouldn't - not as a reviewer, just as a subscriber.&lt;br&gt;
I told them that I'm here to help them get unstuck with their task, at least try to help and to further delegate the issue. &lt;br&gt;
That they can always ping me if they needed advice. &lt;br&gt;
And I ended it with - "&lt;strong&gt;give me feedback&lt;/strong&gt; at any point, correct me if doing something wrong or if you don't agree with me on something".&lt;/p&gt;

&lt;p&gt;And when I start a conversation with someone new, this is now one of the first messages I start the conversation with.&lt;/p&gt;

&lt;p&gt;That seems to be working well so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  All in all
&lt;/h2&gt;

&lt;p&gt;Ain't gonna lie, this really is a journey with a lot of downs and occasional ups. It's a change that sets you far out of your comfort zone. Just when you think you figured something out, you spot a flaw and find yourself two steps back.&lt;/p&gt;

&lt;p&gt;Thoughts of going back to being an individual contributor sound great most the times you hit a low 😅 But if you remember how you learned any new thing, that probably looked a lot like this: you get out of your comfort zone, fail a few or a lot of times but eventually you figure it out. We must get out of our comfort zone to learn and to grow.&lt;/p&gt;

&lt;p&gt;Ultimately, this trip to management is not irreversible, you can always take a step back and give it another shot later. Reflect to this experience and try to get the max out of it so that you know better next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, does it suck to be in the middle?
&lt;/h2&gt;

&lt;p&gt;If you ask me, depends on the day 😅 I would say &lt;strong&gt;no it doesn't&lt;/strong&gt;, but damn, it sure isn't easy.&lt;/p&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
      <category>development</category>
    </item>
    <item>
      <title>Watch Out When Overriding Memoized Methods</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Mon, 08 Jan 2024 09:34:33 +0000</pubDate>
      <link>https://dev.to/productive/watch-out-when-overriding-memoized-methods-5dj7</link>
      <guid>https://dev.to/productive/watch-out-when-overriding-memoized-methods-5dj7</guid>
      <description>&lt;p&gt;This post was inspired by a 🐛 that was caused by not thinking enough when overriding a memoized method. Once we noticed the buggy behaviour, it took me some &lt;em&gt;binding.pry&lt;/em&gt; time to figure out what was going on.&lt;/p&gt;

&lt;p&gt;Lets check out what happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code snippets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Actions&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FormAction&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="o"&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;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
      &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;

      &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolate_properties&lt;/span&gt;
      &lt;span class="n"&gt;interpolatable_attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpolate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolatable_attributes&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;NotImplementedError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_attributes&lt;/span&gt;
      &lt;span class="vi"&gt;@form_attributes&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:attributes&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;stringify_keys!&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;A simple &lt;strong&gt;Ruby&lt;/strong&gt; class which is the base class for the specific actions we need to implement for our &lt;strong&gt;Rails app&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The action for which we figured out the bug was the action of creating comments on various objects. Its code looks like:&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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Actions&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateComment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;FormAction&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolatable_attributes&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_attributes&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;commentable_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much code is actually shown but just enough to spot the bug 🐛. Found it?&lt;/p&gt;

&lt;h3&gt;
  
  
  What was going on
&lt;/h3&gt;

&lt;p&gt;Basically what was happening when running &lt;code&gt;CreateComment#execute_action&lt;/code&gt; was the following:&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;execute_action&lt;/code&gt; we firstly need to interpolate some attributes - defined by the specific action class. In this case, for the &lt;code&gt;CreateComment&lt;/code&gt; action that was only the &lt;code&gt;'body'&lt;/code&gt; attribute.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
  &lt;span class="c1"&gt;# form_attributes = { 'body' =&amp;gt; 'non interpolated' } &lt;/span&gt;
  &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;

  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's fine - we do some logic, the &lt;code&gt;'body'&lt;/code&gt; attribute gets interpolated and that value is set as the new value for the &lt;code&gt;'body'&lt;/code&gt; key in the form_attributes hash.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
  &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;
  &lt;span class="c1"&gt;# form_attributes = { 'body' =&amp;gt; 'interpolated' }&lt;/span&gt;
  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, as the implementation of the &lt;code&gt;CreateComment&lt;/code&gt; class uses &lt;code&gt;super.merge(...)&lt;/code&gt; what we get is actually a new hash each time the method is called.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
  &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;

  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
  &lt;span class="c1"&gt;# process method called with { 'body' =&amp;gt; 'non interpolated' }&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next time, after the interpolation is over, the method &lt;code&gt;form_attributes&lt;/code&gt; is called when passing that hash to our form object and in that moment, the form object gets a newly generated hash that doesn't have the interpolated &lt;code&gt;'body'&lt;/code&gt; value. So the interpolation was actually pointless as would any other modification of the &lt;code&gt;form_attributes&lt;/code&gt; hash prior to the &lt;code&gt;form.process&lt;/code&gt; call be.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution 🐛🔨
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Actions&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateComment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;FormAction&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_attributes&lt;/span&gt;
      &lt;span class="vi"&gt;@form_attributes&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;commentable_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Added memoization to the &lt;code&gt;CreateComment#form_attributes&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Simple as that and when you think about it, also pretty obvious that it needed to look like this from the start.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;p.s. These snippets maybe look a bit too simple and memoization probably looks like not needed but it's a stripped version of the real classes. The &lt;code&gt;form_attributes&lt;/code&gt; method is called and modified quite a few times 🙃&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>development</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Close Call with Real-Time: How Rethinking Pub-Sub Saved the Day</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Fri, 15 Dec 2023 08:43:10 +0000</pubDate>
      <link>https://dev.to/productive/a-close-call-with-real-time-how-rethinking-pub-sub-saved-the-day-52kp</link>
      <guid>https://dev.to/productive/a-close-call-with-real-time-how-rethinking-pub-sub-saved-the-day-52kp</guid>
      <description>&lt;p&gt;All of this started while I was working on our new feature -  &lt;strong&gt;Automations&lt;/strong&gt; 🤖. In a nutshell, Automations allow customers to set up actions triggered under specific conditions. Some of the currently implemented actions are sending slack messages, creating and updating tasks, or posting comments on objects. &lt;/p&gt;

&lt;p&gt;I wanted that actions that modify tasks and comments would send a message over our real-time system so that our frontend clients (browser, mobile, desktop app) could pick that up and show those changes as they occur.&lt;/p&gt;

&lt;p&gt;Currently, our app’s real-time updates are tied to POST/PATCH/DELETE requests. We have a controller extension &lt;code&gt;Extensions::Broadcastable&lt;/code&gt; that hooks on &lt;code&gt;save_form&lt;/code&gt; and &lt;code&gt;destroy_resource&lt;/code&gt; methods and sends a real-time event if the action was successful. However, this approach wasn't suitable for my automation actions, as they don't go thru controllers.&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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Api::V2::Extensions::Broadcastable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_form&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;broadcast_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'create'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'created'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'updated'&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;def&lt;/span&gt; &lt;span class="nf"&gt;destroy_resource&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;broadcast_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deleted'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;As I was digging into this topic, I somehow changed the scope of my task from making the automations actions “live” to revamping our whole broadcasting architecture. I wanted to move that logic out of controllers to a place where I could catch all the changes - which would also catch the automations actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  📖 Sidestory: A Recent Development in Pub-Sub
&lt;/h4&gt;

&lt;p&gt;Just recently, &lt;a href="https://dev.to/d4be4st"&gt;Stef&lt;/a&gt; implemented a Publish-Subscribe architecture in our Rails app. He made it while revamping our search feature. That was a pretty cool moment for the team and it sounded really useful. After the presentation about it, we immediately started thinking what of our current code could've been done with Pub-Sub. No one really revamped anything by the time I was working on this real-time topic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🧐 Exploring Different Approaches
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Callbacks
&lt;/h4&gt;

&lt;p&gt;Yeah... altho we all know that callbacks are evil, I couldn’t at least think about them, just for a moment... &lt;/p&gt;

&lt;h4&gt;
  
  
  2. Forms instead of Controllers
&lt;/h4&gt;

&lt;p&gt;Both the controller actions and my automation actions use the same forms to handle our data. So, why wouldn't I hook on forms and send my real-time messages from there.&lt;br&gt;
This approach was bugging me a bit as we don't use Form objects in all the places we are actually changing our data. So this wouldn't make the whole app feel "live" but I would cover more places than what we have currently. That sounded promising to me.&lt;/p&gt;

&lt;p&gt;Wanted to pitch my thoughts to the rest of the Core team. Thru that discussion, a lightbulb moment happened 💡 &lt;/p&gt;
&lt;h4&gt;
  
  
  3. Embracing Pub/Sub
&lt;/h4&gt;

&lt;p&gt;I was mind-blown 🤯 That is exactly what I wanted!!&lt;br&gt;&lt;br&gt;
Publishing events happens for every change. Ofc, except the places we are using methods that skip callbacks (update_column, update_all, ...) as Pub/Sub and the aspect of publishing changes essentially is hooked to callbacks - but that’s a topic for itself 🫠&lt;/p&gt;
&lt;h3&gt;
  
  
  Making a PoC
&lt;/h3&gt;

&lt;p&gt;As with all big changes in our codebase, and generally in our product, I was putting this code behind a &lt;a href="https://dev.to/productive/decoupling-deployment-from-release-with-feature-toggles-7lo"&gt;Feature Flag (FF)&lt;/a&gt;.&lt;br&gt;
Simply, when one would have the &lt;code&gt;pubSubBroadcasting&lt;/code&gt; FF enabled I would skip sending the real-time events from the controller actions and I would handle the published events accordingly. If you didn't have that FF, nothing changed.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::V2::Tenanted::TasksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApiController&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Extenstensions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcastable&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_broadcast?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;FeatureFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pub-sub-broadcasting'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Made a few Subscribers that would listen for all the &lt;strong&gt;task and comment related changes&lt;/strong&gt; and simply handle them as sending a real-time event.&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Core::Tasks::Broadcaster&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Realtime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcaster&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task.upsert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_upsert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task.delete'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_delete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comment.upsert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_comment_upsert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comment.delete'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_comment_upsert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And ofc, added RSpec tests and set up some widgets on New Relic to cover the difference in number of events that we are sending now - as we knew that we are going to send more events now.&lt;/p&gt;

&lt;p&gt;Basically that was it. The next step was to slowly propagate this FF over all organizations, check our New Relic metrics and see if nothing breaks. Once we would release that change to all of our user base, we should cover the remaining objects and make subscribers for their Pub/Sub events too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Showing off with it
&lt;/h3&gt;

&lt;p&gt;As I was pretty proud of this solution, I kinda talked a lot about it and so naturally it popped up in a 1on1 meeting with my EM &lt;a href="https://dev.to/ilucin"&gt;Lucin&lt;/a&gt;. He wanted to discuss that a bit more so I told him the same thing I wrote here. My vibe was like &lt;em&gt;"Isn't that great? Our APP will be LIVE, all the data would be in sync."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;His response was "But do we really want that?". &lt;/p&gt;

&lt;p&gt;That wasn't the response that I was looking for 🙃&lt;/p&gt;

&lt;p&gt;What I didn't know was that our frontend client, once it gets socket messages, depending on the screen the user is, has to make additional API calls so that all the required data could be fetched again. So, a great deal of my real-time events actually ends up generating additional requests to our server and in a way, we are just generating a lot more traffic (self DDoSing?). As I didn't know about this, I wasn't even paying attention to those metrics along the way. &lt;/p&gt;

&lt;h3&gt;
  
  
  One step forward, two steps back
&lt;/h3&gt;

&lt;p&gt;Let's get better data so that we can make a better call on this.&lt;/p&gt;

&lt;p&gt;I took a period of one week from our logs and checked the number of the POST/PATCH/DELETE requests versus the number of dispatched publish events. This in the end would roughly be the same number as the events we are sending over the socket in the current and in the new way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tasks endpoint
controller POST/PATCH/DELETE actions - 211k
task.upsert + task.delete publishes  - 263k
&amp;gt; thats ~25% more real-time events

Companies endpoint
controller POST/PATCH/DELETE actions      -  5.2k
company.upsert + company.delete publishes - 20.1k
&amp;gt; thats ~4 times more real-time events

Deals endpoint
controller POST/PATCH/DELETE actions -    31k
deal.upsert + deal.delete publishes  - 1_728k
&amp;gt; thats ~55 times more real-time events!!!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wasn't really aware of how badly this could end up. I was making a PoC out of this for &lt;strong&gt;tasks&lt;/strong&gt; and &lt;strong&gt;comments&lt;/strong&gt; and you can see here that there wasn't such a big difference. 25% more events was okay, I knew it would be more.&lt;/p&gt;

&lt;p&gt;But look at our &lt;strong&gt;deals&lt;/strong&gt; endpoint for example - 55 times more events would be sent. That would add up to the traffic we sent over sockets, to our infrastructure bill for that services, and I don't want to imagine the number of API calls generated as a result of this - by ourselves...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deals have that much traffic because a lot of other objects update financial data on deals so that was understandable too, it immediately came to us...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Back to the drawing board
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Pub/Sub shouldn't be a bad call for this
&lt;/h4&gt;

&lt;p&gt;As this is the part of our code that gets all the changes in our data, when wanting to make a frontend client to be as live as it gets, this should be a good call. The solution would be in not sending all the changes over sockets but filter them by relevant and not relevant. This way we would surely see a drop of those mad &lt;strong&gt;company&lt;/strong&gt; and &lt;strong&gt;deal&lt;/strong&gt; numbers.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Send all the needed data to front?
&lt;/h4&gt;

&lt;p&gt;As mentioned before, each real-time message already contains the object that was changed. The issue here is that there are a lot of screens in our client and we can't know all the contexts our users are in and what additional data should be sent - which is exactly why our client makes API calls when receiving some socket messages.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Why didn't I just resolve the problem I had
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;To make those actions "live" in our frontend clients (browser, mobile, desktop APP), I wanted to plug them into our real-time system.&lt;br&gt;
So, why didn't I just put a bit of explicit calls to the code of my automations actions where I would just call the class that sends the real-time message.&lt;br&gt;
But no, I wanted to play smart and to fix a problem that wasn't really there in the first place - to make everything more live &lt;em&gt;while no one was asking for it&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Aftermath
&lt;/h3&gt;

&lt;p&gt;So yeah, the Pub/Sub usage in our real-time part of the app is put on pause until we leverage things up.&lt;/p&gt;

&lt;p&gt;I went on with the &lt;strong&gt;3rd solution&lt;/strong&gt; and added 5 lines of code - a call to the Broadcaster class is one line and I have 5 events over 4 classes to send...&lt;/p&gt;

&lt;p&gt;This was a nice learning opportunity for me and I would say that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When having concrete problems, stick to handling them and resolve them first&lt;/li&gt;
&lt;li&gt;I had the data the whole time in New Relic, I should've prepare better &lt;/li&gt;
&lt;li&gt;It's not bad to be explicit in code, not everything should be an abstraction, generalization, metaprogramming, ... &lt;em&gt;Hope to write on this point a bit more soon&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good thing in this story is that we didn't actually do any damage with this and we didn't lose a lot of time. &lt;/p&gt;

&lt;p&gt;Anyone faced similar problems? If yes, how did you deal with them?&lt;/p&gt;

</description>
      <category>api</category>
      <category>rails</category>
      <category>development</category>
      <category>programming</category>
    </item>
    <item>
      <title>Custom Fields: Give Your Customers the Fields They Need</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Wed, 06 Dec 2023 10:20:00 +0000</pubDate>
      <link>https://dev.to/productive/custom-fields-give-your-customers-the-fields-they-need-41pi</link>
      <guid>https://dev.to/productive/custom-fields-give-your-customers-the-fields-they-need-41pi</guid>
      <description>&lt;p&gt;Here at Productive, &lt;strong&gt;we’re building an operating system for digital agencies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But, because each agency is different (think type, size, services they offer, the way they’re set up as an organization…), they need customization options for their workflows. So it’s pretty hard to model all those needs and use cases through a unified data model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If only there were a way to let them shape those models to their own needs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvm0ccd18zdv5n5yq5fm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvm0ccd18zdv5n5yq5fm.png" alt="Customer feature requests" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s say that one of our customers, &lt;em&gt;ACME digital agency&lt;/em&gt; wants to keep track of their employees’ nicknames and to be able to search them by that field. Other than that, they would also like to keep track of their birthdays and be able to sort them and group them by that date.&lt;/p&gt;

&lt;p&gt;To me, as a developer, this sounds as simple as it gets—add two new columns to the &lt;code&gt;people&lt;/code&gt; table, open those attributes to be editable over the API and send them back in the response.&lt;br&gt;
But should we do that? Should we add all kinds of fields to our models even if those fields are going to be used only by a handful of our customers?&lt;/p&gt;

&lt;p&gt;Let me show you how we tackled this type of feature request and made a pretty generic system around it.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Did Our Customers Want?
&lt;/h3&gt;

&lt;p&gt;It was pretty clear to us what our customers wanted, and that was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to be able to &lt;strong&gt;add additional fields&lt;/strong&gt; to some of our models (People, Projects, Tasks, …)&lt;/li&gt;
&lt;li&gt;to have &lt;strong&gt;various data types on those fields&lt;/strong&gt; (text, number, or date)&lt;/li&gt;
&lt;li&gt;to be able to &lt;strong&gt;search, sort, or even group&lt;/strong&gt; by those fields&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Our Approach
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Custom Field Model
&lt;/h3&gt;

&lt;p&gt;As we’re building a RESTful &lt;a href="https://developer.productive.io/"&gt;API&lt;/a&gt; that’s formatted by the &lt;a href="https://jsonapi.org/"&gt;JSON:API specification&lt;/a&gt; and store our data in a &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/"&gt;MySQL8&lt;/a&gt; relational database, a few things were pretty straightforward – we need a new model and we’ll name it &lt;strong&gt;Custom Field&lt;/strong&gt; (naming wasn’t an issue here 🥲).&lt;/p&gt;

&lt;p&gt;The main attributes of that model should be:&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="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="n"&gt;data_type_id&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;integer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;date&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;enumeration&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;described&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
&lt;span class="n"&gt;customizable_type&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; 
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tells&lt;/span&gt; &lt;span class="n"&gt;us&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;custom&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;could&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;ve&lt;/span&gt; &lt;span class="n"&gt;also&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;enumeration&lt;/span&gt; &lt;span class="err"&gt;😅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How To Store the Field Values?
&lt;/h3&gt;

&lt;p&gt;OK, so now that we know how to define custom fields, how can we know which value someone assigned to a custom field for some object? And where to store that information?&lt;/p&gt;

&lt;p&gt;Three possible solutions came to mind:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Add a limited number of &lt;em&gt;custom_field&lt;/em&gt; columns to our models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can add a few &lt;code&gt;custom_field&lt;/code&gt; columns to our models and that will work for some of our customers but there will always be others that need few extra fields. Adding numerous columns to our models surely isn’t the best solution, we can do better than this 😅&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Add a join table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As mentioned before, while relying on a relational database, a join table sounds like the go-to approach. That table would be a simple join table between the custom field and a polymorphic target (yay, Rails 🥳). Other than those foreign keys, we would have a column to store the value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add a single JSON column to our models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This sounded as flexible as it gets. It would be a simple map where the key would be the custom field ID and the value would be the assigned value for that custom field.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8izj6admv3lmmufdpt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8izj6admv3lmmufdpt1.png" alt="Solutions" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why We Ended Up Choosing JSON
&lt;/h3&gt;

&lt;p&gt;The first solution was just too limited so we discarded that one immediately and focused on the remaining two solutions.&lt;/p&gt;

&lt;p&gt;On one hand, a better design would be to have the custom field values represented by a model but on the other hand, we won’t actually do much with that data. That would just be data that our users set on our objects, data that isn’t important for our business logic. So a simple JSON column didn’t sound bad either.&lt;/p&gt;

&lt;p&gt;The searching and sorting aspect of this feature request was probably the most important one for us. That was supposed to work as fast as it gets, without being a burden to our performance.&lt;/p&gt;

&lt;p&gt;That’s why we implemented both solutions, tested a lot of searching/sorting/grouping scenarios (we’ll go through that in more detail soon), and then checked the metrics.&lt;/p&gt;

&lt;p&gt;The faster solution was the second one, the one with the JSON column, and that made sense to us. That solution doesn’t use &lt;code&gt;JOIN&lt;/code&gt; clauses in SQL since the values are written directly in the searched table and can be queried in the &lt;code&gt;WHERE&lt;/code&gt; clause. Luckily for us, MySQL8 supports a bunch of &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/json-functions.html"&gt;great functions to work with JSON columns&lt;/a&gt; (&lt;code&gt;JSON_EXTRACT&lt;/code&gt;, &lt;code&gt;JSON_UNQUOTE&lt;/code&gt;, &lt;code&gt;JSON_CONTAINS&lt;/code&gt; and others).&lt;/p&gt;
&lt;h3&gt;
  
  
  Great! Now that we know how to store the custom field values too, let’s dig into the coding.
&lt;/h3&gt;

&lt;p&gt;From a development point of view, we did the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added a new model, &lt;strong&gt;Custom Field&lt;/strong&gt;, and implemented CRUD operations that can be called over the API&lt;/li&gt;
&lt;li&gt;Wrote schema migrations that added a JSON column – &lt;code&gt;custom_fields&lt;/code&gt; – to some of our models (people, projects, tasks, …)&lt;/li&gt;
&lt;li&gt;Opened the &lt;code&gt;custom_fields&lt;/code&gt; attribute so it can be edited over the API&lt;/li&gt;
&lt;li&gt;Wrote a generic validation that checks if all the values in the &lt;code&gt;custom_fields&lt;/code&gt; hash have the appropriate data type&lt;/li&gt;
&lt;li&gt;Added the &lt;code&gt;custom_fields&lt;/code&gt; attribute to the API response of the appropriate models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was most of the work we needed to do to be able to manage custom fields in our models.&lt;/p&gt;

&lt;p&gt;But…what about the searching and sorting aspect of custom fields?&lt;/p&gt;
&lt;h3&gt;
  
  
  Searching Through Custom Field Values
&lt;/h3&gt;

&lt;p&gt;We already had a generic solution written for &lt;a href="https://developer.productive.io/index.html#header-filtering"&gt;searching over the API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We have a format of sending query params for searching, like &lt;code&gt;filter[attribute][operation]=value&lt;/code&gt;. For searching through custom fields, we wanted to keep the same format so we ended with a quite similar one – &lt;code&gt;filter[custom_fields][custom_field_id][operation]=value&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We had to add an if-else statement that would handle the custom fields filtering in a different way than filtering through other attributes as the format contained one additional argument — &lt;code&gt;custom_field_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What was different in the filtering logic was that we have to load the custom field that’s being filtered by and check what data type its values are. That’s needed to cast the values into numbers or dates—text values don’t make a difference.&lt;/p&gt;

&lt;p&gt;So the query params and its SQL query counterparts, based on custom field type, would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* for a text custom field and query filter[custom_fields][1][eq]=abc */&lt;/span&gt;
&lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."1"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="cm"&gt;/* for a number custom field and query filter[custom_fields][2][eq]=42 */&lt;/span&gt;
&lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."2"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="cm"&gt;/* for a date custom field and query filter[custom_fields][3][eq]=2022-11-01 */&lt;/span&gt;
&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."3"'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-11-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* The -&amp;gt;&amp;gt; operator is an alias for JSON_UNQUOTE(JSON_EXTRACT(...)) */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sorting by Custom Field Values
&lt;/h3&gt;

&lt;p&gt;The concept of &lt;a href="https://developer.productive.io/index.html#header-sorting"&gt;sorting&lt;/a&gt; by attributes is something we also already tackled by abstracting logic.&lt;/p&gt;

&lt;p&gt;The only thing that changes when sorting by custom fields is that we first need to cast the values and then sort by them.&lt;/p&gt;

&lt;p&gt;Once again, there’s a small change in the format for custom fields sorters (&lt;code&gt;sort=custom_fields[custom_field_id]&lt;/code&gt;) compared to when sorting by a standard attribute (&lt;code&gt;sort=attribute&lt;/code&gt;). We need to handle the custom_fields sorters separately because we have to load the desired custom_field and check its type.&lt;/p&gt;

&lt;p&gt;Then the &lt;code&gt;ORDER BY&lt;/code&gt; statement, based on custom field types, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* for a text custom field and query sort=custom_fields[1] */&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."1"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="cm"&gt;/* for a number custom field and query sort=-custom_fields[2]
Noticed the - on the begining? That defines the order of sorting - DESC or ASC */&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."2"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;unsigned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* for a date custom field and query sort=custom_fields[3] */&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."3"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grouping by Custom Field Values
&lt;/h3&gt;

&lt;p&gt;This was a fun one. The main point here was that you should include the custom fields as some kind of columns stated in the &lt;code&gt;SELECT&lt;/code&gt; statement so that you could later use those columns in the &lt;code&gt;GROUP BY&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;To get the custom field in the &lt;code&gt;SELECT&lt;/code&gt; statement, you have to create a virtual column for it. All we needed to do was to extract the values of the grouped custom field and give that virtual column an alias so that we could reference it in the &lt;code&gt;GROUP BY&lt;/code&gt; statement. For the column alias we went with the format &lt;code&gt;custom_fields_{custom_field_id}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a custom field with &lt;code&gt;id=x&lt;/code&gt;, this is done as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."x"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;custom_fields_x&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have the virtual column defined, the grouping part gets done simply, by adding the &lt;code&gt;GROUP BY&lt;/code&gt; statement with the earlier mentioned alias.&lt;/p&gt;

&lt;p&gt;So in the end, you get a &lt;code&gt;SQL&lt;/code&gt; query like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* query group=custom_fields[1] */&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."1"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;custom_fields_1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;custom_fields_1&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Our Customers Got
&lt;/h3&gt;

&lt;p&gt;A simple way to define Custom Fields:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguvx6aizi5m9xhrs3qwo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguvx6aizi5m9xhrs3qwo.png" alt="Custom fields UI" width="800" height="378"&gt;&lt;/a&gt;&lt;br&gt;
And a place to assign values to their fields:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oyesva4yxmac6r9w50l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oyesva4yxmac6r9w50l.png" alt="Assigning custom fields" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summa Summarum
&lt;/h2&gt;

&lt;p&gt;We made it possible for our customers to define custom fields in our data models. Also, we made it possible to search, sort and group by those fields.&lt;br&gt;
It wasn’t long before we had even more requests that built upon our custom fields architecture. The fields we supported at first were okay, but now our customers wanted more field types. They wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to have dropdown custom fields&lt;/li&gt;
&lt;li&gt;to have relational custom fields&lt;/li&gt;
&lt;li&gt;a field where the values would be objects from one of our existing data models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But before we dig into that, let’s give some time for this basics to sink in. I’ll be back soon with another blog post in which I cover how we solved that new set of feature requests&lt;/p&gt;

</description>
      <category>rails</category>
      <category>development</category>
      <category>programming</category>
      <category>buildingproductive</category>
    </item>
    <item>
      <title>Here to stay - Analyzing RubyGems stats for 2018</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Tue, 22 Jan 2019 19:10:05 +0000</pubDate>
      <link>https://dev.to/buha/here-to-stay---analyzing-rubygems-stats-for-2018-4md0</link>
      <guid>https://dev.to/buha/here-to-stay---analyzing-rubygems-stats-for-2018-4md0</guid>
      <description>&lt;p&gt;As years go by, there is always a question of whether Ruby is dead or alive, and as usual, we have done our research and compiled some statistics about Ruby’s 2018 lifeline that might help you find an answer.&lt;/p&gt;

&lt;p&gt;2018 was an interesting year in terms of gems. Some increased, and some decreased their download numbers. Some were well maintained, while others could have used a helping hand. We have also seen a few newcomers that almost made it to the top of their categories. As for the language itself, at the end of 2018, &lt;a href="https://www.ruby-lang.org/en/news/2018/12/25/ruby-2-6-0-released/"&gt;Ruby 2.6&lt;/a&gt; was released, getting us a few steps closer to achieving the &lt;a href="https://blog.heroku.com/ruby-3-by-3"&gt;Ruby 3×3 goals&lt;/a&gt;. The most notable improvement in this release was the brand new (experimental) &lt;a href="https://medium.com/@k0kubun/ruby-2-6-jit-progress-and-future-84e0a830ecbf"&gt;JIT compiler&lt;/a&gt; that should boost the performance of Ruby programs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;The data used for generating the statistics below were obtained from two sources – the latest RubyGems &lt;a href="https://rubygems.org/pages/data"&gt;PG-dump&lt;/a&gt; and &lt;a href="http://bestgems.org/"&gt;BestGems&lt;/a&gt;. The latter keeps track of all daily gem downloads and exposes this data through their API. Having that and &lt;a href="https://github.com/vr4b4c"&gt;Vedran’s&lt;/a&gt; awesome &lt;a href="https://gist.github.com/NBuhinicek/c2373ed296d0033a56849a0a2024148b"&gt;script&lt;/a&gt;, which made it pretty easy to browse through the numbers, we have compared some of the most used gems.&lt;/p&gt;

&lt;p&gt;Bear in mind that some of these downloads are a direct result of gem dependencies, non-cached CI builds, and gem mirroring services. There is still a strong correlation between the number of gem downloads and the number of its active users, but we feel it’s important to make it clear that these two numbers are not exactly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gem creation &amp;amp; releases
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzinsg95vvcdwotbg05u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzinsg95vvcdwotbg05u.png" alt="rubygems stats 2018 – gem creation" width="800" height="394"&gt;&lt;/a&gt;&lt;br&gt;
Unfortunately, this was another year in a row that the creation of new gems decreased even further. That’s not so surprising after all because there already is a huge number of gems that are well-known, well-maintained, and cover a lot of common use cases. To make a new gem in a field that’s already well covered is a real quest, but not impossible. Further down, we will introduce a gem that managed to make a huge dent in the JSON:API field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0uhptpfhgmse62xcsjvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0uhptpfhgmse62xcsjvs.png" alt="rubygems stats 2018 – gem releases" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
As for the creation of gems, a downhill trend is also evident in the number of gem releases. But you can’t get one without the other. Older and well-maintained gems usually get a few new minor releases and a major release in a calendar year. Newer gems that are still in infancy usually have more code churn, breaking changes, and consequently releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails downloads
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dss8w4kpxn9wgf385h8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9dss8w4kpxn9wgf385h8.png" alt="rubygems stats 2018 – rails downloads" width="800" height="422"&gt;&lt;/a&gt;&lt;br&gt;
In 2018, the number of &lt;a href="https://github.com/rails/rails"&gt;Rails&lt;/a&gt; downloads had increased again. It had its ups and downs, but it held around the 3 million download mark. As the end of the year came, we heard a lot about the new major release, &lt;a href="https://edgeguides.rubyonrails.org/6_0_release_notes.html"&gt;Rails 6&lt;/a&gt;. People jumped on the hype train raising the number of monthly downloads to almost 4 million.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rails/rails/pull/34786"&gt;Action Mailbox&lt;/a&gt;, &lt;a href="https://github.com/rails/rails/pull/34873"&gt;Action Text&lt;/a&gt;, and parallel testing are the most anticipated additions, but there will be more improvements included in Rails 6. We will need to be patient though, as the release is scheduled for the end of April 2019.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other web frameworks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sinatra – still falling behind
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0cvkudpdgbfqy4dv7of8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0cvkudpdgbfqy4dv7of8.png" alt="rubygems stats 2018 – sinatra downloads" width="800" height="389"&gt;&lt;/a&gt;&lt;br&gt;
When &lt;a href="https://github.com/sinatra/sinatra"&gt;Sinatra&lt;/a&gt; got a new maintainer in 2017, and they pushed a new &lt;a href="http://sinatrarb.com/2017/05/15/sinatra-two-point-oh.html"&gt;2.0 release&lt;/a&gt;, things started to look better and the upward trend continued into 2018 as well. Having just five patches throughout the year, they reached 1,5 million downloads as their monthly standard, pushing it to the all-time high of just above 2 million downloads at the end of 2018.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8mberf8dfejwz16j6cx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8mberf8dfejwz16j6cx.png" alt="rubygems stats 2018 – padrino and hanami downloads" width="800" height="413"&gt;&lt;/a&gt;&lt;br&gt;
On the other side of the download spectre, &lt;a href="https://github.com/padrino/padrino-framework"&gt;Padrino&lt;/a&gt; yearly downloads fell by 10%, while &lt;a href="https://github.com/hanami/hanami"&gt;Hanami&lt;/a&gt; downloads increased by about 33%. Hanami’s core team is currently working on version 2.0, which could be something to look out for in 2019. Their download numbers are much lower compared to Rails and Sinatra but that doesn’t detract from the fact that there are people that find them more suitable for their use cases and see potential in their further development. Having options to choose from is always a positive thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database adapters
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybae5r07u1wbaidm9upx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybae5r07u1wbaidm9upx.png" alt="rubygems stats 2018 – ruby database adapters" width="800" height="423"&gt;&lt;/a&gt;&lt;br&gt;
For PG and &lt;a href="https://github.com/brianmario/mysql2"&gt;MySQL2&lt;/a&gt; 2018 was a great year. PG managed to increase their numbers for 4, and MySQL2 for 5 million downloads. &lt;a href="https://github.com/sparklemotion/sqlite3-ruby"&gt;SQLite3&lt;/a&gt;, on the other hand, experienced a drastic drop to only one-third of the downloads made in 2017 (from 21 to 8 million yearly downloads).&lt;/p&gt;

&lt;h2&gt;
  
  
  Application servers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fos58n38jvvq1t3pf55kj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fos58n38jvvq1t3pf55kj.png" alt="rubygems stats 2018 – puma and thin downloads" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
The situation here is pretty much the same as the year before. &lt;a href="https://github.com/puma/puma"&gt;Puma&lt;/a&gt;, the default server for Rails, is still in the number one spot. And &lt;a href="https://github.com/macournoyer/thin"&gt;Thin&lt;/a&gt;, with no new releases in the past year, actually made a 250 thousand drop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52emznphi022hsi2m6my.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52emznphi022hsi2m6my.png" alt="rubygems stats 2018 – unicorn and passenger downloads" width="800" height="418"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://bogomips.org/unicorn.git"&gt;Unicorn&lt;/a&gt;, although better maintained than Thin, didn’t manage to increase the number of downloads compared to 2017. Developers behind &lt;a href="https://github.com/phusion/passenger"&gt;Passenger&lt;/a&gt;, on the other side, are putting a lot of work into new releases, having a &lt;a href="https://github.com/phusion/passenger/releases/tag/release-6.0.0"&gt;6.0 version&lt;/a&gt; released at the end of November and managing to triple their download numbers since 2017. They had a huge spike in the middle of the year, recording more downloads in one month than in the last two years combined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other ORMs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falmyd7bcv8z12oaklkyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falmyd7bcv8z12oaklkyr.png" alt="rubygems stats 2018 – sequel downloads" width="800" height="320"&gt;&lt;/a&gt;&lt;br&gt;
2018 maybe wasn’t the best year for &lt;a href="https://github.com/jeremyevans/sequel"&gt;Sequel&lt;/a&gt;, as its downloads dropped by 14%. Sequel is well maintained and has a status that just a few gems have – 0 outstanding issues. Other than that, we could mention rom-rb, which is maybe not a strict ORM, but has the same uses. It had a small growth during the year, recording just over 90 thousand downloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feso5dlpf2hx724z73jms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feso5dlpf2hx724z73jms.png" alt="rubygems stats 2018 – ruby testing" width="800" height="411"&gt;&lt;/a&gt;&lt;br&gt;
For &lt;a href="https://github.com/rspec/rspec"&gt;RSpec&lt;/a&gt; it seems that only the sky’s the limit. Its downloads more than doubled hitting almost 200 million. Even though it’s defined as a development dependency in a lot of gems, it’s not used as a runtime dependency that often. The vast majority of the downloads thus stem from actual project uses. &lt;a href="https://github.com/seattlerb/minitest"&gt;Minitest&lt;/a&gt;, on the other hand, gained a bit more popularity, although there are some open issues and the last release was just under 12 months ago. All in all, it’s great to see these numbers skyrocket because that just means that people are testing their programs more and that the testing culture is evolving even further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2lcjwydu19nc78vgnav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2lcjwydu19nc78vgnav.png" alt="rubygems stats 2018 – ruby debugging" width="800" height="440"&gt;&lt;/a&gt;&lt;br&gt;
It is inevitable that bugs will occur during software development, but we can examine them easily using the following gems. The downloads of &lt;a href="https://github.com/pry/pry"&gt;pry&lt;/a&gt;, &lt;a href="https://github.com/ruby/irb"&gt;irb’s&lt;/a&gt; number one enemy, jumped by 50%, but this number is eclipsed by the number of open issues – 177 as of writing this article. &lt;a href="https://github.com/deivid-rodriguez/byebug"&gt;Byebug&lt;/a&gt;, which is just below pry, has a download number that is growing even faster, presumably because they started 2018 by releasing &lt;a href="https://github.com/deivid-rodriguez/byebug/releases/tag/v10.0.0"&gt;version 10.0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another interesting debugging tool – &lt;a href="https://github.com/BetterErrors/better_errors"&gt;Better Errors&lt;/a&gt;. They replaced the standard Rails error page with their own, where except the usual stacktrace, you can also debug your code as you were in a debugger. It had a consistent growth rate that continued into 2018 as well. Well maintained and released, it is a very helpful gem to have up your sleeve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background workers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskm3oc5xwnpyn0b5hwxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskm3oc5xwnpyn0b5hwxy.png" alt="rubygems stats 2018 – ruby background workers" width="800" height="425"&gt;&lt;/a&gt;&lt;br&gt;
The situation here is the same as the year before. &lt;a href="https://github.com/mperham/sidekiq/"&gt;Sidekiq&lt;/a&gt; is way ahead of &lt;a href="https://github.com/collectiveidea/delayed_job"&gt;Delayed::Job&lt;/a&gt; and &lt;a href="https://github.com/resque/resque"&gt;Resque&lt;/a&gt;, by 10 million downloads.. By removing even more dependencies and constantly improving and releasing, Sidekiq has become much faster and leaner than its competitors. Resque dropped a &lt;a href="https://github.com/resque/resque/releases/tag/v2.0.0"&gt;2.0 release&lt;/a&gt; in November, but there are no spikes visible on our graph. Comparing it to Delayed::Job, their numbers matched each other throughout the year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6whcxftv5h1iwjcth14j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6whcxftv5h1iwjcth14j.png" alt="rubygems stats 2018 – cancancan and pundit downloads" width="800" height="417"&gt;&lt;/a&gt;&lt;br&gt;
The authorization has been pretty stable too. CanCanCan is still in the lead, while Pundit came in second with 7 million downloads less. While CanCanCan’s downloads were falling through the year, Pundit’s, on the other hand, were stable until July and then started to increase. The reason for that is the 2.0 release, which Pundit fans had been waiting for more than two years. Pundit could take the cake and dethrone CanCanCan if the download trend continues into 2019.&lt;/p&gt;

&lt;h2&gt;
  
  
  Administration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0x6j1iy3fq41mxodjhj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0x6j1iy3fq41mxodjhj3.png" alt="rubygems stats 2018 – ruby administration" width="800" height="403"&gt;&lt;/a&gt;&lt;br&gt;
It was a big year for &lt;a href="https://github.com/activeadmin/activeadmin"&gt;Active Admin&lt;/a&gt;, its downloads doubled and it left &lt;a href="https://github.com/sferik/rails_admin"&gt;Rails Admin&lt;/a&gt; far behind. Both are still well maintained and follow similar release cycles.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON:API Specifications
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1cvc2p9rkwr3lxfkgui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1cvc2p9rkwr3lxfkgui.png" alt="rubygems stats 2018 – json api specification" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
This is a big one. The most popular and talked-about gem in the &lt;a href="https://jsonapi.org/"&gt;JSON:API&lt;/a&gt; world, &lt;a href="https://github.com/Netflix/fast_jsonapi"&gt;Fast JSON API&lt;/a&gt; by Netflix, which was first released in February 2018, surpassed &lt;a href="https://github.com/jsonapi-rb/jsonapi-rb"&gt;JSONAPI-rb&lt;/a&gt; on a yearly download base, and it could soon become THE gem to use for JSON:API serialization. JSONAPI-rb missed a whole year in terms of releases, and at &lt;a href="https://github.com/cerebris/jsonapi-resources"&gt;JSONAPI::Resources&lt;/a&gt;, the issues slowly piling up. Having that in mind, it’s no wonder that there are ever more migrations to the newcomer, that is well maintained and cared for.&lt;/p&gt;

&lt;h2&gt;
  
  
  File uploaders
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn3qet1r4j88kfpqo6rdm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn3qet1r4j88kfpqo6rdm.png" alt="rubygems stats 2018 – carrierwave and paperclip downloads" width="800" height="401"&gt;&lt;/a&gt;&lt;br&gt;
2018 wasn’t the best year for &lt;a href="https://github.com/carrierwaveuploader/carrierwave/"&gt;CarrierWave&lt;/a&gt; and it was even worse for &lt;a href="https://github.com/thoughtbot/paperclip"&gt;Paperclip&lt;/a&gt;, which was &lt;a href="https://robots.thoughtbot.com/closing-the-trombone"&gt;deprecated&lt;/a&gt;. Both were well maintained, and released often, but they managed to get just 20-30% more downloads than in 2017. Finishing the year as equals and not actually growing that much are signs that something’s off.&lt;/p&gt;

&lt;p&gt;The reason for that is just what we predicted last year as a possible game-changer – &lt;a href="https://github.com/rails/rails/tree/v5.2.2/activestorage"&gt;Active Storage&lt;/a&gt;. Available from Rails 5.2 onwards, it gained a lot of popularity and finished the year with 5 million downloads.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtk28x0cs6fv9kg7rz12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtk28x0cs6fv9kg7rz12.png" alt="rubygems stats 2018 – shrine downloads" width="800" height="327"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/shrinerb/shrine"&gt;Shrine&lt;/a&gt; is another great solution for file upload management. While the numbers seem low, having been downloaded just over 300 thousand times, it offers a lot of advantages over the more popular choices, with generality, simplicity, and modularity being at the forefront.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aggregated stats for 2018
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gc13m0vcl87o2w2hwox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gc13m0vcl87o2w2hwox.png" alt="rubygems stats 2018 – aggregated stats" width="800" height="289"&gt;&lt;/a&gt;&lt;br&gt;
The number of Rails downloads continued to grow and reached 37,5 million. 29 new gems were published daily on average. 260 new gem releases were published daily on average. Taking into consideration just the gems mentioned in this post, total downloads jumped from 389,794,028 in 2017 to 499,035,801 in 2018. That’s a 28% increase in just one year!&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;As in the previous years, 2018 was another successful year for Ruby, and if after all of these numbers, the state of Ruby is not clear to you, let us assure you that it is here to stay. We are not the only &lt;a href="https://naturaily.com/blog/who-gives-f-about-rails"&gt;ones&lt;/a&gt; that came to this conclusion.&lt;/p&gt;

&lt;p&gt;With more than 3,700 contributors and new ones coming in on a daily basis, Rails is way ahead of, for example, Django, which has just over 1,600 contributors. That number of Rails backers just proves that Ruby has a strong community that is involved in Rails development and makes a lot of gems that in the end, help other developers build awesome applications better and faster.&lt;/p&gt;

&lt;p&gt;Maybe Ruby isn’t the number one language to pick in terms of performance, as there are many more performant choices. But it is clear that performance is of great importance with the latest Ruby releases just by looking at the GC and JIT changes done by the Ruby Core team. Achieving the 3×3 promise (Ruby 3.0 being three times faster 2.0) thus seems closer by the day.&lt;/p&gt;

&lt;p&gt;So, buckle up, as there are a lot of things about to change. And in the meanwhile, if you want to dig into the data feel free to download it &lt;a href="https://photos.infinum.com/store/cfb5dfae248ef1dc1a4a6695e956c742.pdf"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>gems</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
