loading...
Cover image for You probably don't need these gems

You probably don't need these gems

paveltkachenko profile image Pavel Tkachenko ・4 min read

Gems are fun! They simplify your development process a lot. But sometimes programmers bundle their project with gems they even don't need. I often see projects where complexity is increasing dramatically by trying to solve simple problem using heavy gems. This article is devoted to beginner Rails developers, who copy-paste gems to their Gemfile without any doubt.

You probably don't need config gem

https://github.com/rubyconfig/config

Popular gem for handling configuration of your app and global data. This gem in very cool and is maintained regularly. It's included in many project bundles. But maybe you can replace whole gem with 3 lines of code?

# Ensure file has lodash before filename to be loaded before other initializers
# which can use settings data.
# config/initializers/_settings.rb
settings_file = File.join(Rails.root, "config", "settings", "#{Rails.env}.yml")
settings_string = File.read(settings_file)
SETTINGS = YAML.safe_load(settings_string).deep_symbolize_keys

And now just create folder with different environments

/config
  /settings
    development.yml
    production.yml
    test.yml
# config/settings/development.yml
site:
  title: "My awesome site"
  contact_mail: "tpepost@gmail.com"

Now you can use SETTINGS in any part of your project.

SETTINGS[:site][:title] #=> My awesome site

Of course it's not a full functionality that config gem provides, but in many cases people use it in this way, nothing more.

You probably don't need local_time gem

https://github.com/basecamp/local_time

Despite the official support of Basecamp, it is not supported anymore as I'm aware. Gem has been updated last time 2 years ago. Also the included strftime JavaScript implementation is not 100% complete. And finally it requires sprockets. And you remember that Webpack is now default for Rails.

I think it is better to use popular JS library for this. You can use date-fns (I try to avoid moment.js due to it's large size (329KB)). The whole replacement of locale_time gem are two files: rails helper and in app/helpers, which creates necessary tag, which is then used by a small JS file.

First of all, install date-fns via yarn/npm or whichever you prefer. Rails expects you to use yarn (so am I).

yarn add date-fns

Then create helpers for your view. When you call the helper, it will create span tag with all required data for date-fns to convert its text to proper format. If JS is disabled, default Rails date string will appear.

# app/helpers/time_helper.rb
module TimeHelper
  def format_time(time, format = "yyyy-MM-dd'T'HH:mm:ss.SSSxxx")
    content_tag(
      :span,
      time,
      data: { "time-format": format, "time-value": time.to_json }
    )
  end

  def relative_time(time)
    format_time(time, :relative)
  end

  def year(time)
    format_time(time, "yyyy")
  end
end

Finaly add appropriate JS file to find all timehelpers in your views and set time to requested format.

// app/javascript/your/custom/path/timeHelper.js
// Don't forget to import into your pack

import formatDistance from "date-fns/formatDistance";
import format from "date-fns/format";

// Ensure it works with turbolinks
document.addEventListener("turbolinks:load", function() {
  const timeHelpers = document.querySelectorAll("[data-time-format]");

  Array.from(timeHelpers).forEach(function(timeHelper) {


    const timeFormat = timeHelper.getAttribute("data-time-format");
    const value = timeHelper.getAttribute("data-time-value");
    const datetime = Date.parse(JSON.parse(value));

    if (timeFormat === "relative") {
      timeHelper.textContent = formatDistance(datetime, new Date());
    }
    else {
      timeHelper.textContent =  format(datetime, timeFormat);
    }
  });
});

Now you can use it in your views

  <p>Posted <%= relative_time(@post.created_at) %><p>
  <p>Created at: <%= format_time(@post) %><p>
  <p>Year: <%= year(@post) %><p>
  <p>Custom format: <%= year(@format_time, "MMMM") %><p>

  <!-- Custom formating: https://date-fns.org/v2.14.0/docs/format -->

As you can see, the workaround is very simple. Now you can expand this functionality further and forget about Sprockets.

You probably don't need devise + devise-jwt gems

https://github.com/heartcombo/devise
https://github.com/waiting-for-dev/devise-jwt

Rails is a popular solution as the API server only. And when you need authentication, developers bundle monstrous devise to their project. Often in API workflow people use JWT authentication process. This causes them to add devise-jwt to empower devise functionality, which is huge even without any plugins. In API mode developers use only small part of devise, they need only encrypting, JWT issuing and sign-in/sign-up functionality. For this purpose it's better to use a simple gem - knock.

It provides everything you need for JWT authentication process. It's very thin and simple. It doesn't have view generators, included ready-to-use controllers, recovering letters and many other staff. Of course such functionality is very important for many apps, but when you act only as API server, you probably will write your custom logic anyway, that will have conflict with devise.

You need to add has_secured_password to your model

class User < ActiveRecord::Base
  has_secure_password
end

Include knock logic to controller

class ApplicationController < ActionController::API
  include Knock::Authenticable
end

And before_action everywhere you need.

class SecuredController < ApplicationController
  before_action :authenticate_user

  def index
    # etc...
  end
end

That is all. Now you have current_user - same as in Devise. Simple and fast.

Your probably need these gems!

This article is not about proving that gems we discussed are bad or not suitable for your project. They solve specific problems in a convenient way. Moreover, I insist on using them to reduce onboarding pain for new developers, because they are most likely familiar with these gems and not familiar with your custom codebase. You don't need to write your own code everywhere, you just need to ask yourselves a question before bundle install command: "Do I really need this gem? Maybe I can solve it in a simple way without bloating my dependency graph?".

Be wise, use gems in a smart way!

Posted on Jun 29 by:

paveltkachenko profile

Pavel Tkachenko

@paveltkachenko

Ruby Fullstack Developer. In love with Ruby and Rails. Sometimes happy with JS.

Discussion

markdown guide
 

Great post. Adding dependencies to a project, in general, should always be a well thought process. It's very easy to add them for a quick benefit, but once it's all over the code base it might become pretty hard to remove.

A perspective that I find valuable for thinking about this is to consider that it's always a trade-off. How much time can I save now vs how coupled do I want to be in the long run to this new dependency. Size of the dependency vs size of the functionality I actually need.

Cheers!

 

great article. so i can replace the devise with knock

 

Great post. Definitely important to note the evolutions in Rails that helps make old tools obsolete.

 

Both Rails and vanilla Ruby take big steps everyday. I'm very happy with it.
I look forward to new magic we can use from hey.com =)

 

It's actually funny because we don't use any of these. I was hoping for some revelation but I'm glad to know we are doing something right haha

 

Try dry-rb gems. They are awesome!

 

Thanks for writing this!

I was able to replace local_time and momentjs with your proposed solution 👍

 

Glad it was helpful! Be free if you have topics to investigate, I'll try my best to conduct new article.