Rails - ActiveRecord - Includes vs Joins

Joining tables is a common scenario in a project. We have to join tables for few reasons and there are corresponding joins in the database level. Inner join, full outer join, left outer join, right outer join and cross join are them. How would you do it in Rails / ActiveRecord?

While you can leverage raw SQL, rails provides 2 neat ways to join tables. includes and joins.

joins()

  • lazy loading
  • to access attributes of relation, additional sql queries will be made
  • uses SQL INNER JOIN to join tables
  • all the benefits and pitfalls of INNER JOIN

includes()

  • eager loading
  • minimal number of queries to fetch all data
  • can access relation data without additional sql queries
  • uses SQL LEFT OUTER JOIN
  • all the benefits and pitfalls of LEFT OUTER JOIN
  • chain it with joins() and you have an INNER JOIN

Practical use

You have 3 models in a SaaS application. User, Role and Organization. User has many Roles and Role belongs to `Organization.

  1. Get all roles with or without users/organizations (left outer join)

     Role.includes(:users, :organizations)
    

    Keep in mind that not always will the included table data exist. You may want to use &. or .try() to skip nil situations.

  2. Get all roles with users/organizations

     Role.includes(:users, :organizations).joins(roles: :organizations)
    

    includes() uses LEFT OUTER JOIN but when you pair it with joins() it becomes an INNER JOIN which is the trick to get the desired result set.

  3. Query through relation ships, you only need Role data

     Role
       .joins(:users, :organizations)
       .where(
         users: { active: true },
         organizations: { active: true }
       )
    

    Keep in mind that if you now do a role.organization.name you will have to pull the additional data using a new query. This is true if lets say in a view you iterate through every role and print users name, organization name etc. In that case just through an includes() which will use eager loading and fetch all data at once.

I think this 3 use cases cover the average uses of joins() and includes().

Custom Ubuntu Vagrant Base Image

I live in Linux, but not every one in my team GivingFire.com does. Windows has its uses, but when it comes to Rails development, its not a good idea. Windows 10 WSL (Windows Subsystem for Linux) is a great step, but its still not there. But one can always use a virtual machine and vagrant makes integration super easy. This is how you would make your own Vagrant image.

You need to first use an existing image. I used the one provided by Canonical them self. It doesn’t follow the proper specs of vagrant, but I don’t care.

#~ cd /path/to/your/project
#~ vagrant init ubuntu/xenial64

Make necessary changes to the vagrant file…

#~ vagrant up
#~ vagrant ssh

Now as I said, the box by Canonical is having some issues. You can either create a new vagrant user with password or change the current password. If you don’t do this you will get stuck with “Authentication failure” and unable to login after creating an VM using the box.

#~ sudo passed ubuntu

Now do your installations and configuration within the virtual machine. Make sure you test your application before we continue to the next step.

Now it is time to minimize it and make it in to a box that you can distribute. Lets clean up apt, zero out and clear the bash history.

#~ sudo apt-get clean
#~ sudo dd if=/dev/zero of=/EMPTY bs=1M
#~ sudo rm -f /EMPTY
#~ cat /dev/null > ~/.bash_history && history -c && exit

Now we can make the box and add it to the list of boxes.

#~ vagrant package --output project.box
#~ vagrant box add project project.box

Now lets try it out.

#~ vagrant destroy
#~ vagrant init project

If you didn’t create a vagrant user with password vagrant which is the default you will have to explicitly mention it in the vagrant file.

Vagrant.configure("2") do |config|
  config.vm.box = "gf"

  config.vm.network "forwarded_port", guest: 3000, host: 80

  config.ssh.username = "ubuntu"
  config.ssh.password = "password"

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "1024"
  end
end

Then you can do vagrant up and then ssh to the box using vagrant ssh. It will prompt the password.

Once you know its working, distribute it how ever you see it fit.

Blacklist - Tom Keen - Goodbye friend!

I have been a fan of The Blacklist. It is a great show including a great cast and wonderful story telling. I have always admired Tom Keen (Ryan Eggold) but today is went down putting a great show.

I would like to thank Ryan for the great entertainment provided throughout the 5 seasons of the show. It is sad that you had to go for the evolution of the story line.

The death of Tom would be a new beginning for Elizabeth Keen (Megan Boone). She was already beginning to be Reddingtons child and I believe she will turn out to be like her father now on. It is interesting to see how the story will play out.

Rails Service Objects - Why and How

There seems to be some confusion about the role of the ‘controller’ and ‘model’ among many Rails developers. Today I was inspecting the code of a project I am planning to take over by January 2018. One controller almost had 500 lines of code and a model had 1300 lines of code, a ton of fat methods with really ugly naming.

I believe controllers and models should be skinny. Any business logic should go to the service objects and any validation logic should go to form objects. To show how serious I am on the topic, lets work on a simple website with a contact form.

class ContactsController < ApplicationController
  def show
    # Ok, I am thinking of a form object here. It can be a modal.
    # Doesn't really matter.
    @contact_form = ContactForm.new
  end

  def create
    # What to do here?
  end

  private

  def contact_params
    # For starters, that * down there is the splat operator.
    params.require(:contact_form).permit(*ContactForm::PERMITTED_ATTRIBUTES)
  end
end

While the job at hand is really simple, we can have a create action like this

def create
  @contact_form = ContactForm.new(contact_params)
  if @contact_form.valid?
    ContactMailer.notify_admin(
      @contact_form.email,
      @contact_form.first_name,
      @contact_form.last_name,
      @contact_form.message
    ).deliver_later

    ContactMailer.notify_user(
      @contact_form.email,
      @contact_form.first_name
    ).deliver_later

    flash[:success] = 'Message sent!'
    redirect_to root_path
  else
    flash[:warning] = 'Message not sent, check form for errors!'
    render :show
  end
end

Its simple enough, but why is business logic in the controller? Lets clean it up!

def create
  @contact_form = ContactForm.new(contact_params)
  if @contact_form.valid?
    ContactService.process(@contact_form)
    flash[:success] = 'Message sent!'
    redirect_to root_path
  else
    flash[:warning] = 'Message not sent, check form for errors!'
    render :show
  end
end

Really? You want me to spoon feed you too? Ok! Alright!

class ContactService
  attr_reader :contact_form

  def initialize(contact_form)
    @contact_form = contact_form
  end

  def process
    # blah!
  end

  def self.process(contact_form)
    new(contact_form).process
  end
end

I have been said “Who uses process? Use run!”. Now let me point out why I use #process instead of #run.

ContactService.process(@contact_form)

The above sounds “Contact service process contact form”. If it was run, “Contact service run contact form”. I don’t know about you but it doesn’t sound right to me.

Thats it, happy hacking!

Sri Lanka - E Waste Recycle

E waste is a danger for the natural eco system and needs to be disposed responsibly, some thing Sri Lankans know very little about as I hear. Its time to change. There are locations where you can drop off your e-waste for them to be disposed of safely.

I was cleaning my store room today and remains of 4 computers, 1 laptop, 1 tablet, 4 phones, 31 CFL bulbs, nearly 200 dead batteries, 3 UPS units, 1 organ and more. Mum was like “Garbage!” but once I said the dangers of the e-waste they got sorted, bagged and I got one month to dispose of them.

She called my uncle, T M R Bangsajayah, the former Managing Director of Sri Lanka Insurance, for information. He told there is a company in Rajagiriya that collects waste and that he will find their contact and give. I believe he was talking about Green Link (Pvt) Ltd.

I am going to call them up tomorrow and schedule a pickup if possible or deliver it to their collection center since all is bagged and tagged.

Contacts:

  • +94 11 294 2022
  • +94 11 566 0300
  • +94 71 406 6455
  • +94 71 630 5184
  • +94 71 420 0555
  • +94 11 294 2022 (Fax)