Go to content
The Codest
  • About Us
  • Services
  • Our Team
  • Case studies
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
  • About Us
  • Services
  • Our Team
  • Case studies
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
2018-01-03
Software Development

HI, I'M PORO

Katarzyna Jaruga

HI, I'M PORO - Image

Many people are learning Ruby by starting with Rails framework and, unfortunately, this is the worst possible way of learning this language. Don’t get me wrong: Rails is great, it helps you to build web applications quickly and efficiently without having to get into many technical details.

It’s nice to meet you!

Many people are learning Ruby by starting with Rails framework and, unfortunately, this is the worst possible way of learning this language. Don’t get me wrong: Rails is great, it helps you to build web applications quickly and efficiently without having to get into many technical details. They provide a lot of “Rails magic” that makes things simply work. And for a newbie programmer this is really great, because the most pleasant moment of the process is when you can say “it’s alive!”, and see that all parts fit together and people use your app. We like to be “creators” :) But there is one thing that distinguishes good programmers from the average: the good ones understand how the tools they use work. And by “understanding your tools” I don’t mean knowing all the methods and modules provided by a framework, but understanding how it works, understanding how the “Rails magic” happens. Only then you can feel comfortable with using objects and programming with Rails. The foundation of the object-oriented programming, and the secret weapon which makes the complicated Rails application easier, is the already mentioned in the title PORO, that is Plain Old Ruby Object

What really lies beneath this name? What is this great secret weapon? It is a simple Ruby class that does not inherit from anything. Yes, just that, and so much.

class AwesomePoro
end

How can I help you?

You are continuously developing your application and adding new functionalities as the number of users and their expectations are growing. You get to the point where you encounter more and more dark places of extremely twisted logic, the places which are avoided like the plague by even the bravest developers. The more such places, the more difficult to manage and develop the application. A standard example is the action of registering a new user, which triggers a whole group of other actions associated with this event:

  • checking the IP address in a spam database,
  • sending an email to the new user,
  • adding a bonus to an account of a recommending user,
  • creating accounts in related services,
  • and many more…

A sample code responsible for user registration might look like this:

class RegistrationController < ApplicationController
  def create
    user = User.new(registration_params)
    if user.valid? && ip_valid?(registration_ip)
      user.save!
      user.add_bonuses
      user.synchronize_related_accounts
      user.send_email
    end
  end
end

Okay, you’ve got it coded, everything works, but… is all of this code really okay? Maybe we could write it better? First of all, it breaks the basic principle of programming - Single Responsibility, so surely we could write it better. But how? This is where the already mentioned PORO comes to help you. It’s enough to separate a class RegistrationService, which will be responsible for only one thing: notifying all the related services. By services we will consider the individual actions which we have already singled out above. In the same controller all you need to do is create an object RegistrationService and call on it the “fire!” method. The code has become much clearer, our controller is taking up less space, and each of the newly created classes is now responsible for only one action, so we can easily replace them should a need arise.

class RegistrationService
  def fire!(params)
    user = User.new(params)
    if user.valid? && ip_validator.valid?(registration_ip)
      user.save!
      after_registered_events(user)
    end
    user
  end
  private
  def after_registered_events(user)
    BonusesCreator.new.fire!(user)
    AccountsSynchronizator.fire!(user)
    EmailSender.fire!(user)
  end
  def ip_validator
    @ip_validator ||= IpValidator.new
  end
end
class RegistrationController < ApplicationController
  def create
    user = RegistrationService.new.fire!(registration_params)
  end
end

However Plain Old Ruby Object may prove useful not only for controllers. Imagine that the application you are creating uses a monthly billing system. The exact day of creating such a billing is not important to us, we only need to know that it concerns a specific month and year. Of course you can set the day for the first day of each month and store this information in the object of the “Date” class, but neither is it a true information, nor do you need it in your application. By using PORO you can create a class “MonthOfYear”, the objects of which will store the exact information you need. Moreover, when applying in it the module “Comparable”, it will be possible to iterate and to compare its objects, just like when you are using the Date class.

class MonthOfYear
  include Comparable
  attr_reader :year, :month
  def initialize(month, year)
    raise ArgumentError unless month.between?(1, 12)
    @year, @month = year, month
  end
  def <=>(other)
    [year, month] <=> [other.year, other.month]
  end
end

Introduce me to Rails.

In the Rails world, we are used to the fact that each class is a model, a view or a controller. They also have their precise location in the directory structure, so where can you put our little PORO army? Consider a few options. The first thought that comes to mind is: if the created classes are neither models, nor views nor controllers, we should put them all in the “/lib” directory. Theoretically, it is a good idea, however if all of your PORO files will land in one directory, and the application will be large, this directory will quickly become a dark place which you fear to open. Therefore, undoubtedly, it is not a good idea.

AwesomeProject
├──app
│  ├─controllers
│  ├─models
│  └─views
│
└─lib
  └─services
      #all poro here

You can also name some of your classes non-ActiveRecord Models and put them in the “app/models” directory, and name the ones which are responsible for handling other classes the services and put them in the “app/services” directory. This is a pretty good solution, but it has one drawback: when creating a new PORO, each time you will have to decide every whether it is more of a model or a service. This way, you may reach a situation where you have two dark places in your application, only smaller ones. There is yet a third approach, namely: using namespaced classes and modules. All you need to do is create a directory which has the same name as a controller or a model, and put all PORO files used by the given controller or model inside.

AwesomeProject
├──app
│  ├─controllers
│  │ ├─registration_controller
│  │ │ └─registration_service.rb
│  │ └─registration_controller.rb
│  ├─models
│  │ ├─settlement
│  │ │ └─month_of_year.rb
│  │ └─settlement.rb
│  └─views
│
└─lib

Thanks to this arrangement, when using it, you don’t have to precede the name of a class with a namespace. You have gained shorter code and more logically organized directory structure.

Check me out!

It is a pleasant surprise that when using PORO, the unit tests of your application are faster and easier to write and later on more likely to be understood by others. As each class is now responsible for only one thing, you can recognize the boundary conditions sooner, and easily add appropriate test scenarios to them.

describe MonthOfYear do
  subject { MonthOfYear.new(11, 2015) }
  it { should be_kind_of Comparable }
  describe "creating new instance" do
    it "initializes with correct year and month" do
      expect { described_class.new(10, 2015) }.to_not raise_error
    end
    it "raises error when given month is incorrect" do
      expect { described_class.new(0, 2015)  }.to raise_error(ArgumentError)
      expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
    end
  end
end

I hope we’ll meet again!

The examples we presented clearly show that using PORO improves readability of applications and makes them more modular, and, in consequence, easier to manage and expand. Embracing the principle of the Single Responsibility facilitates the exchange of particular classes if necessary, and doing so without interfering with other elements. It also makes testing them a simpler and faster procedure. Moreover, this way keeping Rails models and controllers short is much easier, and we all know that they tend to get unnecessarily big in the process of development.

Related articles

Software Development

3 Useful HTML Tags You Might Not Know Even Existed

Nowadays, accessibility (A11y) is crucial on all stages of building custom software products. Starting from the UX/UI design part, it trespasses into advanced levels of building features in code. It provides tons of benefits for...

Jacek Ludzik
Software Development

5 examples of Ruby’s best usage

Have you ever wondered what we can do with Ruby? Well, the sky is probably the limit, but we are happy to talk about some more or less known cases where we can use this powerful language. Let me give you some examples.

Pawel Muszynski
Software Development

Maintaining a Project in PHP: 5 Mistakes to Avoid

More than one article has been written about the mistakes made during the process of running a project, but rarely does one look at the project requirements and manage the risks given the technology chosen.

Sebastian Luczak
Software Development

5 reasons why you will find qualified Ruby developers in Poland

Real Ruby professionals are rare birds on the market. Ruby is not the most popular technology, so companies often struggle with the problem of finding developers who have both high-level skills and deep experience; oh, and by the...

Jakub
Software Development

9 Mistakes to Avoid While Programming in Java

What mistakes should be avoided while programming in Java? In the following piece we answers this question.

Rafal Sawicki
Software Development

A quick dive into Ruby 2.6. What is new?

Released quite recently, Ruby 2.6 brings a bunch of conveniences that may be worth taking a glimpse of.  What is new? Let’s give it a shot!

Patrycja Slabosz

Subscribe to our knowledge base and stay up to date on the expertise from industry.

About us

We are an agile software development company dedicated to empowering our clients' digital transformation projects and ensuring successful IT project delivery.

    United Kingdom - Headquarters

  • Office 303B, 182-184 High Street North E6 2JA London, England

    Poland - Local Tech Hubs

  • Business Link High5ive, Pawia 9, 31-154 Kraków, Poland
  • Brain Embassy, Konstruktorska 11, 02-673 Warsaw, Poland
  • Aleja Grunwaldzka 472B, 80-309 Gdańsk, Poland

    The Codest

  • Home
  • About us
  • Services
  • Case studies
  • Know how
  • Careers

    Services

  • PHP development
  • Java development
  • Python development
  • Ruby on Rails development
  • React Developers
  • Vue Developers
  • TypeScript Developers
  • DevOps
  • QA Engineers

    Resources

  • What are top CTOs and CIOs Challenges? [2022 updated]
  • Facts and Myths about Cooperating with External Software Development Partner
  • From the USA to Europe: Why do American startups decide to relocate to Europe
  • Privacy policy
  • Website terms of use

Copyright © 2022 by The Codest. All rights reserved.

We use cookies on the site for marketing, analytical and statistical purposes. By continuing to use, without changing your privacy settings, our site, you consent to the storage of cookies in your browser. You can always change the cookie settings in your browser. You can find more information in our Privacy Policy.