Ruby-on-rails – How to break up long lines of Ruby

coding-stylerubyruby-on-rails

I always get great big lines of code at the top of my Rails models. I am looking for suggestions for the best way to break them up with standard Ruby style. For example, one line I am looking at now is this:

delegate :occupation, :location, :picture_url, :homepage_url, :headline, :full_name, :to => :profile, :prefix => true, :allow_nil => true

What is the conventional style for breaking up these long method call lines?

Best Answer

The short answer is it depends.

Basics

For a start you can save few chars using the "new" Ruby hash syntax:

result = very_long_method_name(something: 1, user: user, flange_factor: 1.34)

vs.

result = very_long_method_name(:something => 1, :user => user, :flange_factor => 1.34)

Hash/Arrays

Sometimes you need to initialize an array or hash, especially for hashes it's nice to write them this way:

args = {
  first_name: "Aldo",
  email: "nospam@mail.example.com",
  age: Float::INFINITY
}

The same hash on the same line would be (not as nice):

args = {first_name: "Aldo", email: "nospam@mail.example.com", age: Float::INFINITY}

Various method calls

Some methods require many params or these params have long names:

%table
  %thead
    %th
      %td= t("first_name", scope: "activemodel.lazy_model.not_so_active_model", some_interpolation_argument: "Mr.", suffix: "(Jr.)")

In this case I would probably write it this way:

%table
  %thead
    %th
      %td= t("first_name",
             scope: "activemodel.lazy_model.not_so_active_model",
             some_interpolation_argument: "Mr.",
             suffix: "(Jr.)")

It's still not very beautiful but I guess is less ugly.

class person < ActiveRecord::Base
  validates :n_cars, numericality: {
                       only_integer: true,
                       greater_than: 2,
                       odd: true,
                       message: t("greater_than_2_and_odd",
                                  scope: "activerecord.errors.messages")
                     }
end

Again, not the most beautiful code on earth but It has some kind of structure.

Also, sometimes you could use variables to split lines. This is just an example, but basicly you name blocks of things (and sometimes after this you realise you can actually move that block in a method)

class person < ActiveRecord::Base
  NUMERICALITY_OPTS = {
    only_integer: true,
    greater_than: 2,
    odd: true,
    message: t("greater_than_2_and_odd", scope: "activerecord.errors.messages")
  }
  validates :n_cars, numericality: NUMERICALITY_OPTS
end

Blocks

Speaking of blocks (closures):

User.all.map { |user| user.method_name }

can be written like this:

User.all.map(&:method_name)

If you have proper blocks try to use do-end instead of curly braces:

nicotine_level = User.all.map do |user|
  user.smoker? ? (user.age * 12.34) : 0.1234
end

Conditional

Don't use the ternary if operator for complex things:

nicotine_level = user.smoker? ? (user.age * 1.234 + user.other_method) : ((user.age - 123 + user.flange_factor) * 0)

if user.smoker?
  nicotine_level = user.age * 1.234 + user.other_method
else
  nicotine_level = (user.age - 123 + user.flange_factor) * 0
end

If you have complex if statements like this:

if user.vegetarian? && !user.smoker? && (user.age < 25) && (user.n_girlfriends == 0) && (user.first_name =~ /(A|Z)[0-1]+/)
end

It's probably just better to move things in methods and make things not only shorter but also readable:

if user.healthy? && user.has_a_weird_name?
  # Do something
end

# in User
def healthy?
  vegetarian? && !smoker? && (age < 25) && (n_girlfriends == 0)
end

def user.has_a_weird_name?
  user.first_name =~ /(A|Z)[0-1]+/
end

Long strings

Heredoc is your friend...I always need to google in order to get the syntax right but once you get it right is makes certain things nicer to read:

execute <<-SQL
  UPDATE people
  SET smoker = 0
  OK, this is a very bad example.
SQL

Queries

I tend to do this way for simple cases:

# Totally random example, it's just to give you an idea
def cars_older_than_n_days(days)
  Car.select("cars.*, DATEDIFF(NOW(), release_date) AS age")
     .joins(:brand)
     .where(brand: {country: "German"})
     .having("age > ?", days)
end

Sometimes queries are even worst. If I use squeel and the query is very big I tend to use parenthesis like this:

# Again, non-sense query
Person.where {
  first_name = "Aldo" |
  last_name = "McFlange" |
  (
    age = "18" &
    first_name = "Mike" &
    email =~ "%@hotmail.co.uk"
  ) |
  (
    person.n_girlfriends > 1 &
    (
      country = "Italy" |
      salary > 1_234_567 |
      very_beautiful = true |
      (
        whatever > 123 &
        you_get_the_idea = true 
      )
    )
  )
}

I'd say, if possible try to avoid complex queries and split them in smaller scopes or whatever:

scope :healthy_users, lambda {
  younger_than(25).
  without_car.
  non_smoking.
  no_girlfriend
}

scope :younger_than, lambda { |age|
  where("users.age < ?", age)
}

scope :without_car, lambda {
  where(car_id: nil)
}

scope :non_smoking, lambda {
  where(smoker: false)
}

scope :no_girlfriend, lambda {
  where(n_girlfriends: 0)
}

This would be probably the best way.

The reality

Unfortunately people tend to write long lines and it's bad:

  • Long lines are difficult to read (There is a reason if printed books don't have super-large pages)
  • It's true we mostly use 2 screens but when using things like git diff from the console having long lines is painful
  • Sometimes you work on your 13" laptop with less screen estate
  • Even if I love to work with 2 screens I like to split my editor to edit 2 files at the same time - long lines force me to use the horizontal scrollbar (most hated thing on earth)
  • Yes, you can enable word wrapping in your editor but it's still not as nice (IMHO)

I have a ruler in my editor so that I know when I'm about to cross the 80th char on the line. But rarely cross the line by few chars it's actually nicer than split it.

Conclusion

There are several ways of keep lines under the 80s and often depends on the situation. The problem with long lines is not just bad style, long lines are often a symptom of too much complexity.