Daily Ruby Tips

Every day a ruby tip on twitter at @daily_ruby_tips

No spam, I hate that too

Get last output in console

# Get last output in console

irb(main):001:0> a = 1
=> 1
irb(main):002:0> b = _
=> 1

Private attr_reader

# Private attr_reader
require 'httparty'

class ExampleUserConnector
  URL = "https://jsonplaceholder.typicode.com/users"

  def initialize(adapter: HTTParty)
    @adapter = adapter
  end

  def call
    adapter.get(URL).body
  end

  private

  attr_reader :adapter
end

Writing large Integers

# Writing large Integers

123_456_789
# => 123456789

Here documents

# Here documents

string = <<-FIN
a long string with
a lot of text
FIN
# => "a long string with\na lot of text\n"

strings = [<<END, "short", "strings"]
another long string
with a lot of text 
END
# => ["another long string\nwith a lot of text \n", "short", "strings"]

Ruby doc

Enumerable#find ifnone

# Enumerable#find ifnone

enum = [{a: 1, b:2}, {a:2, b:3}]

enum.find{ |e| e[:a] == 1 } # => {:a=>2, :b=>3}
enum.find{ |e| e[:a] == 3 } # => nil

# ifnone is called if no object matches:
# find(ifnone = nil) { |obj| block } → obj or nil
enum.find( proc{'default'} ) { |e| e[:a] == 1 } # => {:a=>2, :b=>3}
enum.find( proc{'default'} ) { |e| e[:a] == 3 } # => "default"

# and you can pass a proc or a lambda
enum.find( -> (){'default'} ) { |e| e[:a] == 3 } # => "default"

Enumerable#partition

# Enumerable#partition

(1..6).partition {|v| v.even? }  
#=> [[2, 4, 6], [1, 3, 5]]

Unescaping text

# Unescaping text

"\"WTF is this \\n \\u2202 text \\u{1F9D0}\\u{1F914}\"".undump
# => "WTF is this \n ∂ text 🧐🤔"

String spliting

# String spliting

"This is amazing".split 
# => ["This", "is", "amazing"]
"This is amazing".split(' ', 2)
# => ["This", "is amazing"]

# wrap the matcher in parentheses to include it in the output
"This is amazing".split(/(\s)/) 
# => ["This", " ", "is", " ", "amazing"]

"This is amazing".partition(' ')
# => ["This", " ", "is amazing"]
"This is amazing".rpartition(' ')
# => ["This is", " ", "amazing"]

String#strip

# String#strip

user_input = "  John    "

user_input.strip  # => "John"
user_input.lstrip # => "John    "
user_input.rstrip # => "  John"

user_input.strip! # => "John"

user_input = "John"
user_input.strip! # => nil

String#center

# String#center

'Foo'.center(20)
# => "        Foo         "

def puts_title(title, size:20, decoration:"#")
  puts "".center(size, decoration)
  puts " #{title} ".center(size, decoration)
  puts "".center(size, decoration)
end

puts_title("Hello!")
####################
###### Hello! ######
####################

String#casecmp?

# String#casecmp?

"aBcDeF".casecmp?("abcde")     #=> false
"aBcDeF".casecmp?("abcdef")    #=> true
"aBcDeF".casecmp?("abcdefg")   #=> false
"abcdef".casecmp?("ABCDEF")    #=> true
"\u{e4 f6 fc}".casecmp?("\u{c4 d6 dc}")   #=> true

Example from Ruby Doc

Reduce

# Reduce

# Sum some numbers
(5..10).reduce(:+)                             #=> 45
# Same using a block and inject
(5..10).inject { |sum, n| sum + n }            #=> 45
# Multiply some numbers
(5..10).reduce(1, :*)                          #=> 151200
# Same using a block
(5..10).inject(1) { |product, n| product * n } #=> 151200
# find the longest word
longest = %w{ cat sheep bear }.inject do |memo, word|
   memo.length > word.length ? memo : word
end
longest                                        #=> "sheep"

The inject and reduce methods are aliases.

Example from ruby doc

OpenStruct

# OpenStruct
require 'ostruct'

address = OpenStruct.new(country: "France")
# => #<OpenStruct country="France">
address.city = "Paris"
# => #<OpenStruct country="France", city="Paris">

# OpenStruct VS Struct
Address = Struct.new(:country)
address2 = Address.new(country = "France")
# => #<struct Address country="France">
address2.city = "Paris"
# NoMethodError: undefined method `city=' for #<struct Address country="France">

Struct

# Struct
class User
  Address = Struct.new(:city, :contry)
  
  attr_accessor :name, :address

  def initialize(name, address)
    @name = name
    @address = Address.new(address[:city], address[:country])
  end
end

u = User.new("Rob Halford", {city: "Phoenix", country:"USA"})
u.address
# => #<struct User::Address city="Phoenix", contry="USA">
u.address.city = 'San Diego'
u.address
# => #<struct User::Address city="San Diego", contry="USA">

Time tips

# Time tips
t = Time.new(2018, 8, 28, 22, 40, 01, "+02:00")
# => 2018-08-27 22:40:01 +0200
t.monday?
# => true
t.wday # monday = 1, tuesday = 2,...
# => 1
t.yday # Returns the day of the year, 1..366.
# => 239

respond_to?

# respond_to?
class User
  def name; end

  private
  def registered?; end
end

user = User.new
user.respond_to? :name               # => true
user.respond_to? :registered?        # => false
user.respond_to? :registered?, true  # => true

itself and occurence count

# itself and occurence count
array = [1,2,3,4,5,1,2,2,3]

array.itself
# => [1, 2, 3, 4, 5, 1, 2, 2, 3]

# Real worl exemple: occurence count 
array.each_with_object(Hash.new(0)) { |item, count| count[item] = count[item] + 1 }
# => {1=>2, 2=>3, 3=>2, 4=>1, 5=>1}

array.group_by{|e| e}
# => {1=>[1, 1], 2=>[2, 2, 2], 3=>[3, 3], 4=>[4], 5=>[5]}
array.group_by(&:itself)
# => {1=>[1, 1], 2=>[2, 2, 2], 3=>[3, 3], 4=>[4], 5=>[5]}
array.group_by(&:itself).transform_values(&:count)
# => {1=>2, 2=>3, 3=>2, 4=>1, 5=>1}

source

You should also look at this long thread about this feature

gsub: retrieve the matched string

# gsub: retrieve the matched string 
"The Force will be with you. Always.".gsub(/[ae]/){"*#{$&}*"}
# => "Th*e* Forc*e* will b*e* with you. Alw*a*ys."

"The Force will be with you. Always.".gsub(/[ae]/, '*\0*')
# => "Th*e* Forc*e* will b*e* with you. Alw*a*ys."

Imagine we’re in the context of a rails app with the following model:

# Imagine we're in the context of a rails app with the following model:
class Restaurant < ApplicationRecord
  scope :best, -> { where(stars: 3) }
end

# You suddenly feel the urge to understand how a `scope` gets defined by ActiveRecord.

# You know that `scope`, used as it is in the context of the `Restaurant` class,
# must be a class-level method.

Restaurant.method(:scope)
# => #<Method: Restaurant.scope>

Restaurant.method(:scope).source_location
# => ["/Users/nicolasfilzi/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/activerecord-5.2.1/lib/active_record/scoping/named.rb", 163]

 # Now you know where to start your code spelunking session! Enjoy!

by @n_filzi

Map with index

# Map with index

%w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
# => ["0:foo", "1:bar", "2:baz"]

Enumerator#next and peek

# Enumerator#next and peek
e = [1,2,3].each   # returns an enumerator object.
e.next   # => 1
e.next   # => 2
e.peek   # => 3
e.next   # => 3
e.next   # raises StopIteration
e.rewind
e.peek   # => 1

Infinitely nested Hash

# Infinitely nested Hash
h = Hash.new
h[:this][:is][:really] = 'Amazing!'
# => NoMethodError: undefined method `[]' for nil:NilClass

h = Hash.new {|h, k| h[k] = Hash.new(&h.default_proc) }
h[:this][:is][:really] = 'Amazing!'
h
# => {:this=>{:is=>{:really=>"Amazing!"}}}

Source

gsub with hash

# gsub with hash
"The Force will be with you. Always.".gsub(/[eo]/, 'e' => 3, 'o' => '0')
# => "Th3 F0rc3 will b3 with y0u. Always."

gsub with blocks

# gsub with blocks
"The Force will be with you. Always.".gsub(/[aeiou]/) {|s| s.upcase}
# => "ThE FOrcE wIll bE wIth yOU. AlwAys."

"The Force will be with you. Always.".gsub(/[aeiou]/) {$&.upcase}
# => "ThE FOrcE wIll bE wIth yOU. AlwAys."

Custom setter

# Custom setter
class User
  attr_reader :name

  def name=(name)
    @name = name.capitalize
  end
end

user = User.new
user.name = 'john'
user # => #<User:0x0000000111638a00 @name="John">

Struct subclasses aren’t triple-equal to themselves

# Struct subclasses aren't triple-equal to themselves

klass = Struct.new(:foo)
klass === klass            #=> false
Class === klass            #=> true
Class === Struct           #=> true
Class === Struct.new(:baz) #=> true

each_slice

# each_slice

(1..10).each_slice(3) {|a| p a}
# => [1, 2, 3]
# => [4, 5, 6]
# => [7, 8, 9]
# => [10]

assoc and rassoc

# assoc and rassoc

a  = [ 
       [ "colors", "red", "blue", "green" ],
       [ "letters", "a", "b", "c" ],
       "foo"
     ]
a.assoc("letters")  #=> [ "letters", "a", "b", "c" ]
a.assoc("foo")      #=> nil

#-----------------------------------------------

a = [ [ 1, "one"], [2, "two"], [3, "three"], ["ii", "two"] ]
a.rassoc("two")    #=> [2, "two"]
a.rassoc("four")   #=> nil

Assoc doc@

Negative Array indices

# Negative Array indices
array = [:a, :b, :c, :d, :e]

array[-1]    # => :e
array[0..-3] # => [:a, :b, :c]
array[1..-1] # => [:b, :c, :d, :e]

Benchmark-ips

# Benchmark-ips
# gem install benchmark-ips
require 'benchmark/ips'

array = Array(1..100)

Benchmark.ips do |x|
  x.report("sample:")  { array.sample }
  x.report("shuffle.first:")  { array.shuffle.first }
end

# Warming up --------------------------------------
#              sample:   258.402k i/100ms
#       shuffle.first:    41.862k i/100ms
# Calculating -------------------------------------
#              sample:      8.141M (± 2.7%) i/s -     40.828M in   5.018541s
#       shuffle.first:    480.061k (± 2.8%) i/s -      2.428M in   5.061688s

benchmark-ips on Github

More about Benchmark on Fast-Ruby

Pathname

# Pathname
require 'pathname'

pn = Pathname.new("/usr/bin/ruby")
pn.size        # => 52016
pn.directory?  # => false
pn.dirname     # => Pathname:/usr/bin
pn.basename    # => Pathname:ruby
pn.to_s        # => "/usr/bin/ruby"

Pathname.new('foo.rb').extname # => '.rb'

Pathname.new("/usr/").children 
# => [#<Pathname:/usr/bin>,
 #<Pathname:/usr/standalone>,
 #... ]

See pathname doc

Super

# Super
class Base
  def bar
    puts "Base#bar"
  end
end

class Foo < Base
  def bar(baz)
    # By default super sends all arguments from Foo#bar to Base#bar
    # Call super() to send no arguments to Base#bar and to avoid 
    # "ArgumentError: wrong number of arguments (given 1, expected 0)""
    super()
    puts "#{baz} Foo#bar"
  end
end

Foo.new.bar('Hello')
# => Base#bar
# => Hello Foo#bar

Tap method

# Tap method
class User
  def get_band_from_api
    @band = "Metallica" #example code
  end
end

user = User.new
user.get_band_from_api # => "Metallica"
user
# => #<User:0x000000010c366773 @band="Metallica">

user = User.new.tap(&:get_band_from_api)
# => #<User:0x000000010c366778 @band="Metallica">

Round to the nearest ten

# Round to the nearest ten
# instead of
15.7.round * 10 # => 160
# you can use
157.round(-1)   # => 160

# In Ruby 2.5 and above
155.round(-1, half: :up)     # => 160
155.round(-1, half: :down)   # => 150

The Spaceship Operator

# The Spaceship Operator
1 <=> 5 # => -1
5 <=> 5 # => 0
6 <=> 5 # => 1

numbers = Array(1..10)
numbers.group_by { |n| n <=> 5 }
# => {-1=>[1, 2, 3, 4], 0=>[5], 1=>[6, 7, 8, 9, 10]}

Comparable in ruby doc

How to Use The Spaceship Operator in Ruby

Symbol vs String

# Symbol vs String
'foo'.__id__ == 'foo'.__id__ # => false
:foo.__id__  == :foo.__id__  # => true

Two strings with the same contents are two different objects, but for any given name there is only one Symbol object More

clone vs dup

# clone vs dup
class Book
  attr_accessor :title, :read
  def initialize(title:)
    self.title = title
    self.read = false
  end
end

book = Book.new(title: 'The Hobbit')

def book.read!; read = true; end

book_clone = book.clone
book_dup = book.dup

book_clone.read! # => true
book_dup.read! # => NoMethodError: undefined method `read!'

book.title << ' by Tolkien'
book_clone.title # => "The Hobbit by Tolkien"
book_dup.title   # => "The Hobbit by Tolkien"

book.freeze.clone.frozen? # => true
book.freeze.dup.frozen? # => false

Frozen object and object id

# Frozen object and object id
class Book
  attr_accessor :title

  def initialize(title:)
    self.title = title
  end
end

book = Book.new(title: 'The Hobbit')

book.title.__id__ # => 70172975062580
book.title << ' by Tolkien'
book.title.__id__ # => 70172975062580 => ⚠️ the same
book.title = 'The Return of the King'
book.title.__id__ # => 70172979274460 => ⚠️ different

book.freeze
book.frozen?       # => true
book.title.frozen? # => false
book.title << ' by Tolkien'     # => "The Return of the King by Tolkien"
book.title = 'The Silmarillion' # => RuntimeError: can't modify frozen Book

Object equality test

# Object equality test
class Book
  attr_reader :author, :title

  def initialize(author:, title:)
    @author = author
    @title = title
  end

  def ==(other)
    self.class === other &&
      other.author == @author &&
      other.title == @title
  end
end

book1 = Book.new(title: 'The Hobbit', author: 'Tolkien')
book2 = Book.new(title: 'The Hobbit', author: 'Tolkien')

book1 == book2 # => true

Dig

# Dig
john = {
  name: "John Doe",
  address: {
    city: "Paris"
  }
}
jeanne = { name: "Jeanne"}

john.dig(:address, :city)   # => "Paris"

jeanne[:address][:city]  # => NoMethodError: undefined method `[]' for nil:NilClass
jeanne.dig(:address, :city) #=> nil

Hash#dig in ruby doc

Metaprogramming basics #1

# Metaprogramming basics #1

class Foo
  ["hello", "bye"].each do |method|
    define_method "say_#{method}" do
      "I say " + method.to_s
    end
  end
end

Foo.new.say_hello # => I say hello"
Foo.new.say_bye   # => I say bye"

# Real world exemple:
class Post
  STATUS = %w(to_moderate published planned done hidden)

  def initialize(status:)
    @status = status
  end

  STATUS.each do |status|
    define_method("#{status}?") do
      @status == status
    end
  end
end

post = Post.new(status: "published")
post.published? # => true
post.done?      # => false

Return the name of the current called method

# Return the name of the current called method
class Foo
  def bar
    [__method__, __callee__]
  end

  alias_method :hello, :bar 
end

Foo.new.bar # => [:bar, :bar]
Foo.new.hello # => [:bar, :hello]

Private class methods

# Private class methods
class Example

  def self.foo; end

  private
  # private_foo will not be private as it will be a singleton method
  def self.private_foo; end

  class << self
    def bar; end

    private
    def private_bar; end
  end
end

Example.methods(false)          # => [:foo, :private_foo, :bar]
Example.private_methods(false)  # => [:private_bar, :initialize, :inherited]

More: Class Methods In Ruby: a Thorough Review & Why I Define Them Using class « self

Singleton Methods

# Singleton Methods

class Foo
  def bar; "bar!"; end
end

foo = Foo.new
foo.bar # => "bar!"

def foo.bar
  "This is the new bar"
end
foo.define_singleton_method(:hi) {'hi!'}

foo.singleton_methods # => [:bar, :hi]
foo.bar               # => "This is the new bar"
foo.hi                # => "hi!"

Safe navigation operator (&.)

# Safe navigation operator (&.)
class Foo
  attr_accessor :bar
  def initialize(bar: Bar.new)
    @bar = bar
  end
end

class Bar
  def hello
    "hello"
  end
end

Foo.new.bar.hello # "hello"
Foo.new(bar: nil).bar # nil
Foo.new(bar: nil).bar.hello # NoMethodError: undefined method `hello' for nil:NilClass
Foo.new(bar: nil).bar&.hello # nil
Foo.new(bar: false).bar&.hello # NoMethodError: undefined method `hello' for false:FalseClass
Foo.new(bar: false).bar && Foo.new(bar: false).bar.hello # false

Convert value to boolean

# Convert value to boolean
!!(true) # true
!!(1)    # true
!!(nil)  # false
# Usage:
def foo?
  !!foo
end

# Bonus: Cast to boolean with ActiveModel in Rails
ActiveModel::Type::Boolean.new.cast(0)        # false
ActiveModel::Type::Boolean.new.cast('false')  # false
ActiveModel::Type::Boolean.new.cast('true')   # true
ActiveModel::Type::Boolean.new.cast(1)        # true

General Delimited Input, %w and %W

# General Delimited Input, %w and %W

%w(hello world)
# ["hello", "world"]
%w-hello world-
# ["hello", "world"]
%w$hello world$
# ["hello", "world"]

hello = 'hi'
%w(#{hello} world)
# ["\#{hello}", "world"]
%W(#{hello} world)
# ["hi", "world"]

more info about Q, q, W, w, x, r, s

more info about w, x

The “method” method

# The "method" method
# Passing a method taking a block to another method or object
%w[Hello World].each &method(:puts)
# Hello
# World

Splat operator

# Splat operator
first, *others, last = ['a', 'b', 'c', 'd']
{first: first, others: others, last: last}
#=> {:first=>"a", :others=>["b", "c"], :last=>"d"}

def foo(*args, **opts)
  puts 'args: ' + args.to_s
  puts 'opts: ' + opts.to_s
  puts '"a" opts: ' + opts[:a].to_s
end

foo(1, 2, 3, a: 4, b: 5)
# args: [1, 2, 3]
# opts: {:a=>4, :b=>5}
# "a" opts: 4