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!
Top comments (8)
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!
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.
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!
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 =)
great article. so i can replace the devise with knock