In Rubyroid Labs we are very passionate about application architecture. Most projects we work here are long-term projects, so if you are not being careful about your application design at some point you will find yourself in a position where in order to add a new feature it’s just easier to rebuild the whole project from the scratch. And it’s definitely not something you want to face up with.

One of the bad signs that your project is getting sick is that new team members spend a significant amount of time just reading through the source code in order to understand the logic. Today we want share a list of different gems, which in our opinion can help you to organize your code and make your team members smile.

1. interactor

This library is absolutely fabulous and has always been in our shortlist when talking about writing some complex business logic. What is an interactor? As gem readme says – “an interactor is a simple, single-purpose object, used to encapsulate your application’s business logic”. You can think of that as a service-objects we all love, but it’s a way more than that. Let’s take a look on the example:

# app/interactors/create_order.rb
class CreateOrder
  include Interactor
  
  def call
    order = Order.create(order_params)

    if order.persisted?
      context.order = order
    else
      context.fail!
    end
  end
  
  def rollback
    context.order.destroy
  end
end

# app/interactors/place_order.rb
class PlaceOrder
  include Interactor::Organizer

  organize CreateOrder, ChargeCard, SendThankYou
end

In this example you probably noticed couple absolutely great features of this gem.
The first thing is that you are able to organize your simple interactors to an executable chain, which will be executed in proper order. Special variable context is being used to share states between different interactors.
The second thing is that if one of the interactors fails for some reason, all previous will be rolled-back. You could see that rollback on CreateOrder, which will drop an order if ChargeCard or SendThankYou fails.
It is so cool, isn’t it?

2. draper

If you have ever used custom Rails-helpers, you know how messy they become overtime. And in most cases we use them to display some data in a more fancy way. That’s where decorator design pattern can help us. Let’s take a look at a draper syntax, which is pretty self-explanatory:

# app/controllers/articles_controller.rb
def show
  @article = Article.find(params[:id]).decorate
end

# app/decorators/article_decorator.rb
class ArticleDecorator < Draper::Decorator
  delegate_all

  def publication_status
    if published?
      "Published at #{published_at}"
    else
      "Unpublished"
    end
  end

  def published_at
    object.published_at.strftime("%A, %B %e")
  end
end

# app/views/articles/show.html.erb
<%= @article.publication_status %>

In the code above you can see that our goal was to show published_at attribute in specific format. In classical rails-way we have 2 options of how to do that.
First one is just to write a special helper. The problem with this is that all helpers live under the namespace and as project longs you can face up with some weird name collision, which is extremely hard to debug.
The second one is to create a new method inside the model and use that method instead. This solution also feels wrong since it breaks model class responsibility. By default models are responsible for interaction with data, other than representation of that data.
That’s why using draper in this scenario is a more elegant way to achieve our goal.

Be sure that you check another stunning article:

19 Ruby on Rails Gems which Can Amaze

3. virtus

Sometimes using a simple ruby object is not enough for you. Imagine you have some complex form on a page, where different pieces form should be saved as different models in the database. That’s where virtus can help you. Let’s take a look at the example:

class User
  include Virtus.model

  attribute :name, String
  attribute :age, Integer
  attribute :birthday, DateTime
end

user = User.new(:name => 'Piotr', :age => 31)
user.attributes # => { :name => "Piotr", :age => 31, :birthday => nil }

user.name # => "Piotr"

user.age = '31' # => 31
user.age.class # => Fixnum

user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>

# mass-assignment
user.attributes = { :name => 'Jane', :age => 21 }
user.name # => "Jane"
user.age  # => 21

As you can see, virtus looks similar to standard {OpenStruct} class, but gives you many more features. You should definitely play around to explore them all.

This gem could be a good start for you, but if you are looking for more advanced techniques – definitely go and check dry-types, dry-struct and dry-validation.

4. cells

If you are not familiar with Nick Sutterer Ruby on Rails advanced architecture, you should definitely check that out. Not all of us ready to apply this whole concept to their existing applications. But sometimes your views become really complicated, with different conditions applied for different types of user and etc. That’s where cells gem could help. It allows moving part of you views to isolated components, which are just regular ruby-classes. Let’s take a look at code sample:

# app/cells/comment_cell.rb
class CommentCell < Cell::ViewModel
  property :body
  property :author

  def show
    render
  end

  private
  
  def author_link
    link_to "#{author.email}", author
  end
end

# app/cells/comment/show.html.erb
<h3>New Comment</h3>
  <%= body %>

By <%= author_link %>

# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
  def index
    @comments = Comment.recent
  end
end

# app/controllers/dashboard/index.html.erb
<% @comments.each do |comment| %>
  <%= cell(:comment, comment) %>
<% end %>

In this example we want to display recent comments on our dashboard. Imagine all comments should be displayed identical on our application. Rails will use some shared partial for rendering. But instead of doing that we use CommentCell object. You can think of that object as a combination of draper we talked about before with ability to render views. But of course it has  many more features. Check their README to learn more about all options.

How to Clear Out Your Controllers and Models With Waterfall Gem

5. retryable

All modern web application do have different types of integrations. Sometimes it’s made through solid API calls, sometimes you have to upload a file to FTP or even use some binary protocol. Problem with all integrations is that sometimes their calls just fail. In some cases – it fails without any reasons. And the best thing you can do is just to try again. Thumbs up if you have ever had to do something like this:

begin
  result = YetAnotherApi::Client.get_info(params)
rescue  YetAnotherApi::Exception => e
  retries ||= 0
  retries += 1
  raise e if retries > 5
  retry
end

Here is where retryable can help you. Let’s take a look at how we can rewrite the example above using that gem:

Retryable.retryable(tries: 5, on: => YetAnotherApi::Exception) do
  result = YetAnotherApi::Client.get_info(params)
end

It looks much nicer, doesn’t it? Check that gem out for other scenarios it supports.

6. decent_exposure

If you are not a big fan of using magic – this library is not for you. But in some applications we definitely have a lot of duplications for very simple and standard CRUD actions. That’s where decent_exposure could help you. Let’s imagine that we are creating new controllers to manage things. That’s how scaffold will look for us:

class ThingsController < ApplicationController
  before_action :set_thing, only: [:show, :edit, :update, :destroy]

  def index
    @things = Thing.all
  end

  def show
  end

  def new
    @thing = Thing.new
  end

  def edit
  end

  def create
    @thing = Thing.new(thing_params)

    respond_to do |format|
      if @thing.save
        format.html { redirect_to @thing, notice: 'Thing was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  def update
    respond_to do |format|
      if @thing.update(thing_params)
        format.html { redirect_to @thing, notice: 'Thing was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

  def destroy
    @thing.destroy
    respond_to do |format|
      format.html { redirect_to things_url, notice: 'Thing was successfully destroyed.' }
    end
  end

  private

  def set_thing
    @thing = Thing.find(params[:id])
  end

  def thing_params
    params.require(:thing).permit(:for, :bar)
  end
end

We can’t say that 60 lines of code is not too much. But as rubyists we always want it to be as minimalistic as possible. Let’s take a look at how it could be transformed using decent_exposure:

class ThingsController < ApplicationController
  expose :things, ->{ Thing.all }
  expose :thing

  def create
    if thing.save
      redirect_to thing_path(thing)
    else
      render :new
    end
  end

  def update
    if thing.update(thing_params)
      redirect_to thing_path(thing)
    else
      render :edit
    end
  end

  def destroy
    thing.destroy
    redirect_to things_path
  end

  private

  def thing_params
    params.require(:thing).permit(:foo, :bar)
  end
end

Yakes! Now it’s a little more than 30 lines of code without losing any functionality. As you could notice – all magic is brought by expose method. Check this gem documentation for a better understanding of how things work under the hood.

7. groupdate

Every developer knows that dealing with different time zones is a hard thing. Especially when you are trying to write some aggregation on your database. It always gave me hard time when someone asked: “Could I get how many users we are getting every day this month excluding free users” or similar. Now you can stop worrying about such requests and just use gem. Here is an example:

User.paid.group_by_week(:created_at, time_zone: "Pacific Time (US & Canada)").count
# {
#   Sun, 06 Mar 2016 => 70,
#   Sun, 13 Mar 2016 => 54,
#   Sun, 20 Mar 2016 => 80
# }

Hope you enjoyed some of these libraries. Let us know if you have any other tools in your mind which can help to write more expressive and awesome code.

How useful was this post?

Click on a star to rate it!

Average rating 4.8 / 5. Vote count: 14

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?


Author

Daria Stolyar is a Marketing Manager at Rubyroid Labs. You can follow her at Linkedin.

2 Comments

Write A Comment