Ruby Readable Booleans

There’s a great little trick you can do to improve the readability of your code. A common problem is dealing with methods that have a boolean flag arguments. Here’s an example I ran into just today in a Rails application:

1
2
3
def rating_stars(..., clickable = false)
  # ...
end

The problem with this is that you typically see calls like this scattered around the application:

1
<%= rating_stars(..., true) %>

Would you know what true did there if I hadn’t shown you the name of the variable first? I didn’t. I had to go hunting for that method definition.

Ironically the opposite problem, a magical dangling false, is much more rare in my experience. That’s typically the default for these kind of arguments and it just makes more sense and reads better to leave it out.

Anyway, the point is that we can typically improve the ease of understanding the common case. Remember that one of the new features of Ruby 2.0 is keyword arguments. keyword arguments make it easier create readable method. For example, after looking up the method and understanding what was needed:

1
2
3
def rating_stars(..., clickable: false)
  # ...
end
1
<%= rating_stars(..., clickable: true) %>

So far so good, my hope is that might save a future maintainer a trip to the method definition to understand the call.

Workshop - Learn Ruby on Rails in a Weekend

Let’s Start

Have you always wanted to learn Ruby on Rails but never found the time to do so?
Have you been reading articles and watching videos on Ruby on Rails but wish that someone could be by your side to answer your burning questions?

This weekend workshop is designed to make you feel confident about what Ruby on Rails can do for you, and you’ll graduate from the class with knowledge on building web applications for fun or profit.

In the 2 days, you’ll be doing lots of coding, In the process, you’ll learn about web design, application design process, database usage, and most importantly, the web application framework that Groupon, and many others were built with - Ruby on Rails.

You Will Learn

1. Development Environment Setup
Learn to set up a ‘development environment’ on your machine, and be introduced to the various open source tools and online services.

2. Application and Database Design
Learn to decompose everyday problems into conceptual models that can be coded into a web application.

3. Web Design
Learn basic HTML, CSS using the Bootstrap library to create beautiful and functional web applications.

4. Ruby on Rails
Learn fundamentals of Ruby on Rails and be wowed by its magic and simplicity.

5. Your Own App
Learn to build your own social networking app using Ruby on Rails.

6. Cloud Deployment
Learn to deploy your web application to the cloud so that your friends can use it too.

Who can join?

- Beginners, with great attitude and a strong will for learning Ruby on Rails.
- Experienced programmers, who are new to web development or Ruby on Rails.

What to prepare?

- Completed exercises on tryruby.org.
- Your laptop to be installed with Ruby 2.1 and Rails 4 (we’ll get this done on Saturday).

Schedule

First day
We’ll get you setup with Ruby on Rails on your laptop in the first hour, and then we’ll go through open source tools and online services like Git, Bundler, GitHub, Heroku… etc.

Second day
We’ll start the day with creating a brand new Ruby on Rails app and setting it up with a home page. Unlike most courses, we’ll not be using Rails scaffold, and you’ll learn how to build an app from scratch, with your bare hands.
We’ll learn:
- Simple Web Design with Bootstrap.
- Application Design Process.
- Setting up of Databases.
- User Authentication.
- Rails Models and Forms.
- Model Validations.
- Ajax Updates.
- Deploy to Cloud.

What to bring?

- Your laptop (and charger).
- Your ‘never say die’ spirit.

Terms and Conditions

Please bring your own laptop and charger. Internet is provided.
The instructor reserves the rights to cancel the workshop if it does not have a minimum of 6 participants.

See you!!!

Guide to Become an Expert Ruby/Rails Developer

This guide is meant to help you acquire the fundamental skills you need to become a Ruby Developer. Using books, video courses, and projects, you’ll learn how to develop web applications using Ruby, HTML & CSS, jQuery, and Rails. Additionally, you will learn about best practices, design patterns and principles, and the fundamentals of visual design. The goal is to build a solid foundation that you can expand on to become an expert Ruby Developer.

Required Reading and Courses
1. The Passionate Programmer by Chad Fowler.
2. Online Ruby Course by Pragmatic Studios.
3. HTML & CSS by Jon Duckett.
4. Smashing CoffeeScript by Alex Hudson.
5. Don’t Make Me Think by Steve Krug.
6. Everyday Rails Testing with RSpec by Aaron Sumner.
7. Online Ruby on Rails Course by Pragmatic Studios.
8. Bootstrapping Design by Jarrod Drysdale.
9. Practical Object-Oriented Design in Ruby by Sandi Metz.

How To Use This Guide
The best way to use this guide is to start from the Ruby section and proceed towards the CoffeeScript and jQuery section. Each section has a Core Activity and an Assessment. The core activities are meant to teach you and the assessments are meant for you to practice solving problems on your own. There are additional readings for each section for the purposes of professional development. These readings can be done in-between study sessions or during your commute to work, but don’t skip them. I encourage you to read the entire guide before you start doing anything. It’s a good idea to know where you will end up before you invest any time.

Setup Development Environment
Follow these instructions to setup your development environment for either Mac OS X or Linux. Download & install Sublime Text 3

Create a GitHub Account
Make sure you create a GitHub account if you don’t have one already. You’ll need a GitHub account in order to store and share your code with the world.

So far so good, Continuous Learning to stay marketable.
Take your time, see you in next article. :)

What’s New in Rails4 View?

Well, previouse article I had talked about What’s New in Rails4 ActiveModel?. Today We are looking at view:

Assume we have an owner class which has many items and each items are usualy belongs to an owner:

1
2
3
class Owner < ActiveRecord::Base
  has_many :items
end
1
2
3
class Item < ActiveRecord::Base
  belongs_to :owner
end

Select box
- Rails3 & 4
In Rails3 & 4 if we want to build a select box with owner we could do it with a single method called:

1
collection_select(:item, :owner_id, Owner.all, :id, :name)

Radio button & checkbox
- Rails3
In Rails3 we need do with the loops and builds each of the elements:

1
2
3
4
<% @owners.each do |owner| %>
  <%= radio_button_tag :owner_id, owner.id %>
  <%= owner.name %>
<% end %>

HTML output:

1
2
<input id="owner_id" name="owner_id" type="radio" value="1" /> Slow-draw
<input id="owner_id" name="owner_id" type="radio" value="2" /> Sheriff

- Rails4
Now in Rails4 we have collection_radio_buttons & collection_check_boxes method which builds all elements from a collection:

1
2
collection_radio_buttons(:item, :owner_id, Owner.all, :id, :name)
collection_check_boxes(:item, :owner_id, Owner.all, :id, :name)

Date field
- Rails3
At some points we must use date_select form helper:

1
<%= f.date_select :return_date %>

HTML output:

1
2
3
4
5
6
7
8
9
10
11
<select id="item_return_date_1i" name="item[return_date(1i)]">
  <option value="2008">2008</option>
  ...
</select>
<select id="item_return_date_2li" name ="item[return_date(2i)]">
  <option selected="selected" value="1">January</option>
  ...
</select>
<select id="item_return_date_3i" name="item[return_date(3i)]">
  ...
</select>

- Rails4
Rails4 now there is a date_field:

1
<%= f.date_field :return_date %>

HTML output:

1
<input id="item_return_date" name="item[return_date]" type="date">

Use Memoization to Speed Up Your Code in Ruby/Rails

Have you ever heard Memoization? In this article you will get an introduction of Memoization. You will learn what it is, how you can use it to speed up your code.

What is Memoization?
Memoization is the process of storing a computed value to avoid duplicated work by future calls.

What are Memoization used for?
- Perform some work
- Store the work result
- Use stored results in future calls

Using
In Ruby the most common pattern for using Memoization is the conditional assignment operator: ||=.

Example
In Rails if you’ve ever worked with a user login system, you really family with the pattern of loading the current_user in the application_controller.rb:

application_controller.rb
1
2
3
def current_user
  User.find(session[:user_id]) if session[:user_id]
end

Within each request in a Rails application you will usually see multiple calls to current_user that means User.find method is run multiple times.

In this case, we know the problem is because there are multiple calls to current_user occurring. Let’s fix this code by introducing Memoization into the current_user method and storing the result of User.find method by using conditional assignment to an instance variable:

application_controller.rb
1
2
3
def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id]
end

Well, there are no more calls to rebuild the User object each time current_user method is called.

When should you memoize?
- When you’ve got duplicated database
- When you’ve got expensive calculations
- When you’ve got repeated calculations that don’t change

When shouldn’t you memoize
- Memoization shouldn’t be used with methods that take parameters:

1
2
3
4
5
6
7
8
#incorrect
def full_name
  @full_name ||= "#{first_name} #{last_name}"
end

puts full_name('Bunlong', 'Van') #=> "Bunlong Van"

puts full_name('Ryan', 'Van') #=> "Bunlong Van"

- Memoization shouldn’t be used with methods that use instance variables:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#incorrect
def full_name
  @full_name ||= "#{first_name} #{last_name}"
end

@first_name = 'Bunlong'
@last_name = 'Van'

puts full_name #=> "Bunlong Van"

@first_name = 'Ryan'
@last_name = 'Van'

puts full_name #=> "Bunlong Van"

Memoization conditional assignment have problem when return nil or false in Ruby
If you are not clear in using conditional assignment it can bite you, let try code below:

1
2
3
4
5
6
7
8
9
10
11
def print
  @print || = begin
                puts "printing"
                sleep 2
                false
              end
end

print #=> "printing"

print #=> "printing"

Suprised that “printing” was printed twice? Conditional assignment is always going to run if the instance variable @print is false or nil.

Well, we can solve the problem by using defined?:

1
2
3
4
5
6
7
8
9
10
def print
  return @print if defined?(@print)
  puts "printing"
  sleep 2
  @print = false
end

print #=> "printing"

print

Memoization conditional assignment have problem when return nil or false in Rails

1
2
3
def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id]
end

What’s happen if User.find return nil? Conditional assignment is always going to run and current_user method is always going to call if the instance variable @print is false or nil.

Well, we can fix the problem by using the Memoizable API in Rails:

1
2
3
4
5
6
7
# somewhere inside the class
extend ActiveSupport::Memoizable

def current_user
  @current_user ||= User.find(session[:user_id]) if session[:user_id]
end
memoize :current_user

Or we can use Memoizable to fix this problem

So far so good, I hope this article comes in handy for some of you who haven’t heard of memoization yet or who just didn’t really understand what’s going on there. :)

What’s New in Rails4 ActiveModel?

Well, previouse article I had talked about What’s New in Rails4 ActiveRecord Finder?. Today please keep going to take a look “What’s New in Rails4 ActiveModel?”:

SCOPES

eager-evaluated scopes are deprecated
- Rails3:

Rails3
1
2
scope :sold, where(state: 'sold')
default_scope where(state: 'available')

Warning:
- Useing #scope without passing a callable object is deprecated.
- Calling #default_scope without a block is deprecated.

- Rails4:
Scopes should take a proc object:

Rails4
1
scope :sold, ->{ where(state: 'sold') }

Defaults scopes should take proc object or a block:

Rails4
1
2
default_scope { where(state: 'available') }
default_schop ->{ where(state: 'available') }

RELATION#NONE

- Rails3:

Rails3
1
2
3
4
5
6
7
8
9
10
11
12
class User < ActiveRecord::Base
  def visible_posts
    case role
    when 'Country Manager'
      Post.where(country: country)
    when 'Reviewer'
      Post.published
    when 'Bad User'
      [] #represents empty collection
    end
  end
end
1
2
  @posts = current_user.visible_posts
  @posts.recent

@posts.recent error when ‘Bad User’ because NoMethodError: undefined method ‘recent’ for []:Array.
One way we can fix this:

1
2
3
4
5
6
@posts = current_user.visible_posts
if @posts.any? # must check for presence
  @posts.recent
else
  [] # must return empty collection to caller
end

Rails4:

Rails4
1
2
3
4
5
6
7
8
9
10
11
12
class User < ActiveRecord::Base
  def visible_posts
    case role
    when 'Country Manager'
      Post.where(country: country)
    when 'Reviewer'
      Post.published
    when 'Bad User'
      Post.none # returns ActiveRecord:Relation and never hits the database
    end
  end
end
1
2
@posts = current_user.visible_posts
@posts.recent # no need to check for presence

Post.none returns ActiveRecord:Relation and never hits the database and @posts.recent no need to check for presence.

RELATION#NOT

Rails3:

Rails3
1
Post.where('author != ?', author)

When author is nil it’s going to generate incorrect SQL syntax: SELECT "posts".* FROM "posts" WHERE (author != NULL)
One way we can fix this:

Rails3
1
2
3
4
5
if author
  Post.where('author != ?', author)
else
  Post.where('author IS NOT NULL')
end

Rails4:

Rails4
1
Post.where.not(author: author)

When author is nill it’s goint to generate correct SQL syntax: SELECT "posts".* FROM "posts" WHERE (author IS NOT NULL)

RELATION#ORDER

case1

Rails3:

Rails3
1
2
3
4
5
class User < ActiveRecord:Base
  default_scope { order(:name) }
end

User.order("created_at DESC")

It’s going to generate SQL: SELECT * FROM users ORDER BY name asc, created_at desc, new calls to order are appended.

Rails4:

Rails4
1
2
3
4
5
class User < ActiveRecord:Base
  default_scope ->{ order(:name) }
end

User.order(created_at: :desc)

It’s going to generate SQL: SELECT * FROM users ORDER BY created_at desc, name asc New calls to order are prepend.

case2

Rails3:

Rails3
1
User.order(:name, 'created_at DESC')

Rails4:

Rails4
1
User.order(:name, created_at: :desc)

It’s going to generate SQL: SELECT * FROM users ORDER BY name asc, created_at desc

Rails3:

Rails3
1
User.order('created_at DESC')

Rails4:

Rails4
1
User.order(created_at: :desc)

It’s going to generate SQL: SELECT * FROM users ORDER BY created_at desc

So far so good, ActiveModel in Rails4 is better. :)

Test Models With RSpec in Ruby on Rails

Testing is a good pratice. You should be doing it. It will make you a better programmer and save you a great deal of headache as your web app grows up. It is especially important when working alongside other programmers. Testing is not perfect though so don’t try to be perfect. Just get started, and you will improve as time goes on.

How should I be testing?
- Using RSpec & factorygirl.
- Testing the Model.

Installation
Add rspec-rails and factorygirl to both the :development and :test groups in the Gemfile:

Gemfile
1
2
3
4
group :development, :test do
  gem 'factory_girl_rails', '4.2.1'
  gem 'rspec-rails', '~> 3.0.0'
end

Download and install by running command:

1
bundle install

Initialize the spec/ directory (where specs will reside) with:

1
rails generate rspec:install

This adds the following files which are used for configuration:
- .rspec
- spec/spec_helper.rb
- spec/rails_helper.rb

Generators
Once installed, RSpec and factorygirl will generate spec files instead of Test::Unit test files when run commands like: rails generate model and rails generate controller are used.

Example:

1
rails generate model Post

After you run the command above this adds the following directory and file:
- spec/models/posts.rb
- spec/factories/posts.rb

Let’s get started the Model testing

Assume we have three Models such as post.rb, category.rb, categorization.rb:

post.rb
1
2
3
4
5
6
7
8
9
10
11
class Post < ActiveRecord::Base
  validates :title, length: { minimum: 10, maximum: 100 }, presence: true, uniqueness: true
  validates :body, length: { minimum: 20, maximum: 200 }
  validates :status, length: { minimum: 2, maximum: 20 }, presence: true
  validates :category_id, presence: true

  has_many :categorizations
  has_many :categories, through: :categorizations

  scope :search_by_title, -> (title) { where("(title like ?) OR title in (?)", "%#{title}%", title.split) }
end
category.rb
1
2
3
4
5
6
7
8
class Category < ActiveRecord::Base
  validates :name, length: { minimum: 10, maximum: 50 }, presence: true, uniqueness: true
  validates :short_name, length: { minimum: 10, maximum: 50 }, presence: true, uniqueness: true
  validates :description, length: { maximum: 200 }

  has_many :categorizations
  has_many :posts, through: :categorizations
end
categorization.rb
1
2
3
4
5
6
7
class Categorization < ActiveRecord::Base
  validates :category_id, presence: true
  validates :post_id, presence: true

  belongs_to :category
  belongs_to :post
end

Next, we define default factorygirl object for each Models in spec/factories/:

posts.rb
1
2
3
4
5
6
7
8
FactoryGirl.define do
  factory :post do
    title 'Ruby on Rails'
    body 'Ruby on Rails is good'
    status 'open'
    category_id 1
  end
end
categories.rb
1
2
3
4
5
6
7
8
FactoryGirl.define do
  factory :category do
    id 1
    name 'programming'
    short_name 'programming'
    description 'computer programming'
  end
end
categorizations.rb
1
2
3
4
5
6
FactoryGirl.define do
  factory :categorization do
    category_id '1'
    post_id '1'
  end
end

Here, how we test each Models in spec/models/:

post.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
require 'spec_helper'

describe Post, 'validation' do
  it { should ensure_length_of(:title).is_at_least(10) }
  it { should ensure_length_of(:title).is_at_most(100) }
  it { should validate_presence_of(:title) }
  it { should validate_uniqueness_of(:title) }

  it { should ensure_length_of(:body).is_at_least(20) }
  it { should ensure_length_of(:body).is_at_most(200) }

  it { should ensure_length_of(:status).is_at_least(2) }
  it { should ensure_length_of(:status).is_at_most(20) }
  it { should validate_presence_of(:status) }

  it { should validate_presence_of(:category_id) }
end

describe Post, 'association' do
  it { should have_many(:categorizations) }
  it { should have_many(:categories).through(:categorizations) }
end

describe Post, 'column_specification' do
  it { should have_db_column(:title).of_type(:string).with_options(length: { minimum: 10, maximum: 100 }, presence: true, uniqueness: true) }
  it { should have_db_column(:body).of_type(:text).with_options(length: { minimum: 20, maximum: 200 }) }
  it { should have_db_column(:status).of_type(:string).with_options(length: { minimum: 2, maximum: 20, presence: true }) }
  it { should have_db_column(:category_id).of_type(:integer) }

  it { should have_db_index(:title).unique(true) }
end

describe Post, '.search_by_name' do
  before(:each) do
    FactoryGirl.create(:post, title: 'Ruby on Rails')
  end

  it 'returns post that match with title' do
    Post.search_by_title('Ruby on Rails').count.should eql 1
  end

  it 'returns post that like title' do
    Post.search_by_title('ruby on rails').count.should eql 1
  end

  it 'returns post when title is blank' do
    Post.search_by_title('').count.should eql 1
  end

  it 'returns empty when title is not match' do
    Post.search_by_title('not match').count.should eql 0
  end
end
category.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'spec_helper'

describe Category, 'validation' do
  it { should ensure_length_of(:name).is_at_least(10) }
  it { should ensure_length_of(:name).is_at_most(50) }
  it { should validate_presence_of(:name) }
  it { should validate_uniqueness_of(:name) }

  it { should ensure_length_of(:short_name).is_at_least(10) }
  it { should ensure_length_of(:short_name).is_at_most(50) }
  it { should validate_presence_of(:short_name) }
  it { should validate_uniqueness_of(:short_name) }
end

describe Category, 'association' do
  it { should have_many(:categorizations) }
  it { should have_many(:posts).through(:categorizations) }
end

describe Category, 'column_specification' do
  it { should have_db_column(:name).of_type(:string).with_options(length: { minimum: 10, maximum: 50 }, presence: true, uniqueness: true) }
  it { should have_db_column(:short_name).of_type(:string).with_options(length: { minimum: 10, maximum: 50 }, presence: true, uniqueness: true) }
  it { should have_db_column(:description).of_type(:text).with_options(length: { maximum: 200 }) }
end
categorization.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'spec_helper'

describe Categorization, 'validation' do
  it { should validate_presence_of(:category_id) }
  it { should validate_presence_of(:post_id) }
end

describe Categorization, 'association' do
  it { should belong_to(:category) }
  it { should belong_to(:post) }
end

describe Categorization, 'column_specification' do
  it { should have_db_column(:category_id).of_type(:integer).with_options(presence: true) }
  it { should have_db_column(:post_id).of_type(:integer).with_options(presence: true) }
end

To run Models specs use the following command:

1
rspec spec/models

You should get back the response something like:

For more detail about RSpec, FactoryGirl.
So far so good, We’ve already created 40 models specs to test model. :)

Good Rails3 ActiveRecord Finder vs Very Good Rails4 ActiveRecord Finder

Well, previouse article I had talked about Keyword Arguments Feature in Ruby 2.0. now we can apply keyword arguments in Rails4.

FINDERS

old-style finders are deprecated
- Rails3:

Rails3
1
Post.find(:all, conditions: { author: 'admin'})

Warning: Calling #find(:all) is deprecated. Please call #all directly instead. You have also used finder options. Please build s scope instead of using finder options.

- Rails4:

Rails4
1
Post.where(author: 'admin')

Dynamic finder that return collections are deprecated
- Rails3:

Rails3
1
2
Post.find_all_by_title('Rails 4')
Post.find_last_by_author('admin')

Warning: This dynamic method is deprecated. Please use e.g Post.where(…).all instead.

- Rails4:

Rails4
1
2
Post.where(title: 'Rails 4')
Post.where(author: 'admin').last

FIND_BY

- Rails3:

Rails3
1
2
Post.find_by_title('Rails 4') # Dynamic find_by finders that take a single argument are not deprecated.
Post.find_by_title('Rails 4', conditions: { author: 'admin' }) # Dynamic find_by finders with conditions are deprecated.

- Rails4:

Rails4
1
2
Post.find_by(title: 'Rails 4')
Post.find_by(title: 'Rails 4', author: 'admin')

FIND_BY WITH HASH

allows dynamic input more easily
- Rails4:

Rails4
1
2
3
4
post_params = { title: 'Rails 4', author: 'admin' }
Post.find_by(post_params)

Post.find_by("published_on < ?", 2.weeks.ago)

FIND_OR_*

dynamic finders that create new objects are deprecated
- Rails3:

Rails3
1
2
Post.find_or_initialize_by_title('Rails 4')
Post.find_or_create_by_title('Rails 4')

Warning: This dinamic method is deprecated. Please use e.g Post.find_or_initialize_by(name: ‘foo’) instead.
Warning: This dinamic method is deprecated. Please use e.g Post.find_or_create_by(name: ‘foo’) instead.

- Rails4:

Rails4
1
2
Post.find_or_initialize_by(title: 'Rails 4')
Post.find_or_create_by(title: 'Rails 4')

So far so good, let upgrade to Rails4 then refactor ActiveRecord Finder together. :)

What We Should Test With RSpec in Rails

Well, what is takes to begin testing Rails applications. The hardest part of being a beginner is that you often don’t know what you should test with RSpec in Rails.

Here is the most important thing is that you are testing: Feature specs, Model specs, Controller specs, View specs, Route specs.

Feature specs
Feature specs is a kind of acceptance test, the tests that walk through your entire application ensuring that each of the components work together.
They are written from the perspective of a user clicking around the application and filling in forms on the page.
While Feature specs are great for testing high level functionality, keep in mind that feature specs is slow to run.

Model specs
Model specs are similar to unit tests in that they are used to test smaller parts of the system, such as classes or methods, and they interact with the database too.

Controller specs
When testing multiple paths through a controller is necessary, we favor using controller specs over feature specs, as they are faster to run and often easier to write.

View specs
View specs is great for testing the conditional display of information in the templates. Most developers forget about these tests and use feature specs instead.
While you can cover each view conditional with a feature specs, I prefer to user view specs.

Route specs
Most Ruby on Rails developers don’t test their routes, If you ever need to test an abstract base controller independently from any subclass, you will like need to add route specs for your testing.

So far so good, this was just an overview of what we should get started testing Rails. :)

Keyword Arguments Feature in Ruby 2.0

One of the new features of Ruby 2.0 is keyword arguments. keyword arguments make it easier create method that take optional named arguments.

keyword arguments in the method definition must be symbols given in the new-style hash syntax.
Assume we have a method:

1
2
3
4
5
6
7
8
9
def print(message: 'Hello')
  puts message
end

print #=> Hello

print(message: 'Hi') #=> Hi

print({ message: 'Hi' }) #=> Hi

Ruby 2.0 blocks can also be defined with keyword arguments:

1
2
3
4
5
6
7
8
9
define_method(:print) do |message: 'Hello'|
  puts message
end

print #=> Hello

print(message: 'Hi') #=> Hi

print({ message: 'Hi' }) #=> Hi

Keyword arguments vs Positional arguments
Assume we have a method with positional arguments:

1
2
3
4
5
def total(subtotal, tax, discount)
  subtotal + tax - discount
end

total(100, 10, 5) # => 105

This method does its job, but as a reader of the code using the total method, I have no idea what those arguments mean without looking up the implementation of the method.

By using keyword arguments, we know what the arguments mean without looking up the implementation of the called method:

1
2
3
4
5
6
7
def obvious_total(subtotal:, tax:, discount:)
  subtotal + tax - discount
end

obvious_total(subtotal: 100, tax: 10, discount: 5) # => 105

obvious_total({ subtotal: 100, tax: 10, discount: 5 }) # => 105

Keyword arguments allow us to switch the order of the arguments, without affecting the behavior of the method:

1
2
3
obvious_total(subtotal: 100, discount: 5, tax: 10) # => 105

obvious_total({ subtotal: 100, discount: 5, tax: 10 }) # => 105