The three types of methods in Ruby

Augusts Bautra - Feb 16 - - Dev Community

In this short post I will look at some patterns in Ruby methods and their naming conventions.

The most prominent pattern is delineation of methods that produce a value and no side-effects from methods with side-effects. I achieve this by only using verbs in side-effect-producing method names, whereas methods with only return value get adjectives and nouns.

Return-value-only

Let's have a look:

def get_name
def name

def produce_red_cube
def red_cube

def normalize_params(params)
def normal_params(params) # or normalized_params

def make_request
def response # sidestep the process by which the response is obtained altogether

def render_view
def content # here as well, focus on the result
Enter fullscreen mode Exit fullscreen mode

As you can see, it's irrelevent how the value is obtained, no need to mention it in the method name. Otherwise we'd end up with def obtain_<thing>_by_deep_coding_magic methods all over the place.

Naming things this way has the functional side-effect of aligning initialization with getter, making memoization straightforward. Everything becomes self-ensuring and safer.

# before
attr_reader :company

def load_company
  @company = Company.find(params[:company_id])
  authorize @company, :show
end

def do_something_important
  load_company # important that loading occurs before first call to #company, brittle
  company.something
end

# after
def company
  @company ||= Company.find(params[:company_id]).tap { |c| authorize c, :show } 
end

def do_something_important  
  company.something
end
Enter fullscreen mode Exit fullscreen mode

You may have noticed that this also makes before_action filters unnecessary. 😼

Methods with side-effects

Naming is simple - start with a descriptive verb, followed by the signifying noun.

def enqueue_job
def process_upload
def reorder_toys! # throw in a bang to really show things will change
Enter fullscreen mode Exit fullscreen mode

These come in two flavours - where the return value is wholly unimportant, indeed, having one would be confusing, and where there is a return value, usually an "outcome" object.

For methods without interesting return values I recommend explicitly returning nil to signify this. It's also useful to prevent leakage of unintended return values, consider:

# bad
def close_old_cases
  cases.each do |case|
    case.close! if case.old?
  end
end #=> cases

# good
def close_old_cases(cases)
  cases.each do |case|
    case.close! if case.old?
  end

  nil
end #=> nil
Enter fullscreen mode Exit fullscreen mode

If a method has both significant side-effects (writing to DB, filesystem, etc.) and has an interesting return value, I recommend being explicit about this with a YARD-style comment at least:

# @return [ConversionReport] # convert's an accounts currencies and returns a report object with details
def convert_currencies!(from, to)
  ...
  ConversionReport.new(at: Time.current, from : from, to: to)
end

convert_currencies!.success? #=> true
Enter fullscreen mode Exit fullscreen mode

Naming things can be hard, especially for non-native speakers. I usually try to use expressive and precise words - all those process and calculate get a bit repetitive :)

Another pattern I like is using Ruby's special call method to push the naming into the class. There are many approaches to naming when doing this since there can be several considerations (for example, leading with context/concept to get alphabetical grouping etc.), but I prefer noun-ification with an emphasis on professions, for example:

def process_upload
UploadReviewer.call

def manage_record_lock
Receptionist.call

def calculate_pay
PayrollClerk.call
Enter fullscreen mode Exit fullscreen mode

Photo by Ainur Iman on Unsplash

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .