Skip to content

Latest commit

 

History

History
1017 lines (719 loc) · 17.1 KB

README.md

File metadata and controls

1017 lines (719 loc) · 17.1 KB

Ruby Tricks

Join the chat at https://gitter.im/franzejr/ruby-tricks

The majority of these Ruby Tricks were extracted from James Edward Gray II talk. If you know some other tricks, please contribute!

Table of Contents

Tricks

Alphanumeric incrementing

"1".next
#=> "2"

"a".next
#=> "b"

"1a".next
#=> "1b"

"1z".next
#=> "2a"

"1aa".next
#=> "1ab"

"1az".next
#=> "1ba"

"1aaz".next
#=> "1aba"

View Source

Associative arrays

aa = [ %w[Someone 1],
      %w[Bla 2]]

p aa.assoc("Someone")
p aa.assoc("Bla")

# Result:
# ["Someone", "1"]
# ["Bla", "2"]

p aa.rassoc("1")
p aa.rassoc("2")

# Result:
# ["Someone", "1"]
# ["Bla", "2"]

View Source

Autovivification

deep = Hash.new { |hash,key| hash[key] = Hash.new(&hash.default_proc) }


deep[:a][:b][:c][:d] = 42
p deep

# Result:
# {:a=>{:b=>{:c=>{:d=>42}}}}

View Source

Blocks can take blocks

var = :var
object = Object.new

object.define_singleton_method(:show_var_and_block) do |&block|
  p [var, block]
end

object.show_var_and_block { :block }

# Result:
# [:var, #<Proc:0x007ffd6c038128@./blocks_can_take_blocks.rb:8>]

View Source

Bubbling up thread errors

Thread.abort_on_exception = true

Thread.new do
  fail 'Ops, we cannot continue'
end

loop do
  sleep
end

# Result:
# ./bubbling_up_thread_errors.rb:4:in `block in <main>': Ops, we cannot continue (RuntimeError)

View Source

Case on ranges

age = rand(1..100)
p age

case age
  when -Float::INFINITY..20
    p 'You are too young'
  when 21..64
    p 'You are at the right age'
  when 65..Float::INFINITY
    p 'You are too old'
end

# Result:
# 55
# "You are at the right age"

View Source

Count all objects

require 'pp'

pp ObjectSpace.count_objects

# Result:
# {:TOTAL=>30163,
#  :FREE=>1007,
#  :T_OBJECT=>39,
#  :T_CLASS=>534,
#  :T_MODULE=>24,
#  :T_FLOAT=>4,
#  :T_STRING=>9290,
#  :T_REGEXP=>70,
#  :T_ARRAY=>2231,
#  :T_HASH=>53,
#  :T_STRUCT=>1,
#  :T_BIGNUM=>2,
#  :T_FILE=>14,
#  :T_DATA=>966,
#  :T_MATCH=>1,
#  :T_COMPLEX=>1,
#  :T_NODE=>15896,
#  :T_ICLASS=>30}

View Source

Cycle

ring = %w[one two three].cycle

p ring.take(5)

# Result:
# ["one", "two", "three", "one", "two"]

View Source

Data

puts DATA.read

__END__
Hey oh!
Hey oh!

View Source

Easiest database pstore

require 'pstore'

db = PStore.new('mydatabase.pstore')

db.transaction do
  db['people1'] = 'Someone'
  db['money1'] = 400
end

db.transaction do
  db['people2'] = 'Someone2'
  db['money2'] = 300
end


db.transaction(true) do
  p 'People %p' % db['people1']
  p 'Money %p' % db['money1']
  p "SECOND PERSON"
  p 'People %p' % db['people2']
  p 'Money %p' % db['money2']
end

# Result:
# "People \"Someone\""
# "Money 400"
# "SECOND PERSON"
# "People \"Someone2\""
# "Money 300"

View Source

Easiest database pstore yaml

require 'yaml/store'

db = YAML::Store.new('people.yml')

db.transaction do
  db['people1'] = 'Someone'
  db['money1'] = 400
end

db.transaction do
  db['people2'] = 'Someone2'
  db['money2'] = 300
end


db.transaction(true) do
  p 'People %p' % db['people1']
  p 'Money %p' % db['money1']
  p "SECOND PERSON"
  p 'People %p' % db['people2']
  p 'Money %p' % db['money2']
end

# Result:
# "People \"Someone\""
# "Money 400"
# "SECOND PERSON"
# "People \"Someone2\""
# "Money 300"

View Source

Enable garbage collector profiler

GC::Profiler.enable

10.times do
  array = Array.new(1_000_000) { |i| i.to_s }
end

puts GC::Profiler.result

View Source

Enable ruby warnings

$VERBOSE = true

class WarnMe
  def var
    @var || 42
  end
end


p WarnMe.new.var


# Result:
# ./enable_ruby_warnings.rb:5: warning: instance variable @var not initialized
# 42

View Source

Fast memoization fibonacci

fibonacci = Hash.new{ |numbers,index|
  numbers[index] = fibonacci[index - 2] + fibonacci[index - 1]
}.update(0 => 0, 1 => 1)


p fibonacci[300]

# Result:
# 222232244629420445529739893461909967206666939096499764990979600

View Source

Fetch data

params = {var: 42}

p params.fetch(:var)
p params.fetch(:missing, 42)
p params.fetch(:missing) { 40 + 2 }

params.fetch(:missing)


# Result:
# 42
# 42
# 42
# ./fetch_data.rb:7:in `fetch': key not found: :missing (KeyError)
# 	from ./fetch_data.rb:7:in `<main>'

View Source

Get random data

require 'securerandom'

p SecureRandom.random_number
p SecureRandom.random_number(100)
p
p SecureRandom.hex(20)
p SecureRandom.base64(20)

# Result:
# 0.7851536586163714
# 46
# "3efb674fbc2ba390856c15489652e75e8afff6d1"
# "yFv0WzugzFC6/D71teVe1Y5r1kU="

View Source

Head tail

def my_reduce(array)
    head, *tail = array
    return (tail.empty? ? head : (head + my_reduce(tail)))
end

# triangular number example
n = 100
my_reduce((1..n).to_a) == (n*(n+1))/2 #=> True

View Source

Inject

p (1..10).inject{ |r,e| p [r,e]; r*2}


# Result:
# [1, 2]
# [2, 3]
# [4, 4]
# [8, 5]
# [16, 6]
# [32, 7]
# [64, 8]
# [128, 9]
# [256, 10]
# 512

View Source

Inspecting the source with script lines

SCRIPT_LINES__ = { }

#require_relative = 'better_be_well_formed_code'
require_relative = 'better_be_well_formed_code_with_a_line_size_greather_than_80_it_is_not_good'

if SCRIPT_LINES__.values.flatten.any? { |line| line.size > 80}
  abort 'Clean up your code first!'
end

View Source

Iterating over specific types

ObjectSpace.each_object(String) do |object|
  p object
end

# Result:
# "block in dependent_specs"
# "block in dependent_specs"
# "block (3 levels) in dependent_gems"
# "block (3 levels) in dependent_gems"
# ... (huge output suppressed)
# "This rdoc is bundled with Ruby"

View Source

Lambda your own syntax

# encoding UTF-8

module Kernel
  alias_method , :lambda
end

l = λ { p :called }
l.call

# Result:
# :called

View Source

Memoization

# based on Justin Weiss' article:
# http://www.justinweiss.com/articles/4-simple-memoization-patterns-in-ruby-and-one-gem/

class Memoize
  # one liner
  def my_simple_method
    @my_simple_method ||= do_some_calculation
  end

  # multiple lines
  def my_more_complex_method
    @my_more_complex_method ||= begin
      a = do_some_calculation
      b = do_some_more_calculation
      a + b
    end
  end

  # what if our calculations return nil?...

  # one liner
  def my_simple_method
    return @my_simple_method if defined? @my_simple_method
    @my_simple_method = do_some_calculation
  end

  # multiple lines
  def my_more_complex_method
    return @my_more_complex_method if defined? @my_more_complex_method
    @my_more_complex_method = begin
      a = do_some_calculation
      b = do_some_more_calculation
      a + b
    end
  end

  # what about differing arguments?...

  def my_really_complex_method(*args)
    @my_really_complex_method ||= Hash.new do |h, key|
      h[key] = do_some_calculation(*key)
    end
    @my_really_complex_method[args]
  end
end

View Source

Print formatted with debug

def debug(name, content)
  p "%s:  %p" % [name, content]
end

debug "Num", 42

# Result:
# "Num:  42"

View Source

Ruby debug flag

def var
  @var || 40
end

if $DEBUG
  p "var is %p" % var
end

p var + 2

# Result:
# ruby_debug_flag.rb:2: warning: instance variable @var not initialized
# "var is 40"
# ruby_debug_flag.rb:2: warning: instance variable @var not initialized
# 42

View Source

Shortcut variable interpolation

@instance = :instance
@@class = :class
$global = :global

p "#@instance, #@@class, and #$global variables don't need braces"

# Result:
# "instance, class, and global variables don't need braces"

View Source

Single instance running

DATA.flock(File::LOCK_EX | File::LOCK_NB) or abort 'Already running'

trap('INT', 'EXIT')
puts 'Running...'
loop do
  sleep
end

__END__
DO NOT DELETE: used for locking

View Source

Smalltalk conditionals

def  true.-(a, &b); a[] end
def false.-(a, &b); b[] end

puts (1 == 1).--> { :ok } { :different }
puts (4 == 2).--> { :ok } { :different }

# Result:
# # ok
# # different

View Source

Splat operator

# Splat Operator (*) 

# When calling methods

arguments = [1, 2, 3, 4]
my_method(*arguments) # any number of arguments

# or:

arguments = [2, 3, 4]
my_method(1, *arguments) # any number of trailing arguments

# or:

arguments = [1, 2]
my_method(*arguments, 3, 4) # any number of preceding arguments

# or:

arguments = [2, 3]
my_method(1, *arguments, 4) # any number of "in between" arguments

# All are equivalent to:

my_method(1, 2, 3, 4)

# Two splats (**) convert a hash into an arbitary number of keyword arguments
# This operator doesn't technically have a name

arguments = { first: 1, second: 2, third: 3 }
my_method(**arguments)

# or:

arguments = { first: 1, second: 2 }
my_method(third: 3, **arguments)

# Are equivalent to:

my_method(first:1, second:2, three:3)

View Source

Stab operator

# Stab Operator - Lambdas in Ruby 1.9 or later.
# Y Combinator
# Ruby supports a syntax for lambdas known as the 'stab' operator.
# Rather than something like lambda { a < 5 },
# you can type -> { a < 5 }.
#
# Below is a version of the fibonacci sequence that can
# perform recursive calls without named functions.
#
# Improver function for fibonacci sequence
# Assumes that the 0th element of the sequence is 0,
# and the 1st element of the sequence is 1.
fib_improver = ->(partial) {
  ->(n) { n < 2 ? n : partial.(n-1) + partial.(n-2) }
}

# The y combinator
y = ->(f) {
  ->(x) { x.(x) }.(
    ->(x) { f.(->(v) { x.(x).(v)}) }
  )
}

# Using the stab operator and y combinator, we can
# write a fibonacci function with anonymous functions
# This solution is not memoized and so will be very slow.
fib = fib_improver.(y.(fib_improver))

p fib.(1)

p fib.(10)
# Notice that after loading, fib isn't defined anymore.

View Source

Struct without assignment

Struct.new("Name", :first, :last) do
  def full
    "#{first} #{last}"
  end
end

franzejr = Struct::Name.new("Franze", "Jr")
p franzejr.full

# Result:
# "Franze Jr"

View Source

Super magic keyword

class Parent
  def show_args(*args)
    p args
  end
end

class Child < Parent
  def show_args(a,b,c)
    super(a,b,c)
  end
end

Child.new.show_args(:a, :b, :c)

# Result:
# [:a, :b, :c]

View Source

Super magic keyword2

class Parent
  def show_args(*args, &block)
    p [*args, block]
  end
end

class Child < Parent
  def show_args(a,b,c)
    super
  end
end

#Everything goes up, including the block
Child.new.show_args(:a, :b, :c) { :block }

# Result:
# [:a, :b, :c, #<Proc:0x007fbf7a0486e8@super_magic_keyword2.rb:14>]

View Source

Super magic keyword3

class Parent
  def show_args(*args, &block)
    p [*args, block]
  end
end

class Child < Parent
  def show_args(a,b,c)
    # Call super without any params
    # making args an empty array []
    super()
  end
end

#Nothing goes up
Child.new.show_args(:a, :b, :c)

# Result:
# [nil]

View Source

Super magic keyword4

class Parent
  def show_args(*args, &block)
    p [*args, block]
  end
end

class Child < Parent
  def show_args(a,b,c)
    # modify super by passing nothing
    # calling super with a nil proc,
    # which is basically calling super()
    super(&nil)
  end
end

#Nothing goes up, neither the block
Child.new.show_args(:a, :b, :c) { :block }

# Result:
# [nil]

View Source

Super magic keyword5

class DontDelegateToMe; end
class DelegateToMe; def delegate; "DelegateToMe" end end

module DelegateIfCan
  def delegate
    if defined? super
      "Modified:  #{super}"
    else
      "DelegateIfCan"
    end
  end
end

p DelegateToMe.new.extend(DelegateIfCan).delegate
p DontDelegateToMe.new.extend(DelegateIfCan).delegate

# Result:
# "Modified:  DelegateToMe"
# "DelegateIfCan"

View Source

Tail call

RubyVM::InstructionSequence.compile_option = { tailcall_optimization: true,
                                               trace_instruction: false }

eval <<end
  def factorial(n, result=1)
    if n==1
      result
    else
      factorial(n-1, n*result)
    end
  end
end

p factorial(100000)

# Result:

View Source

Trigger irb as needed

require 'irb'

def my_program_context
  @my_program_context ||= Struct.new(:value).new(40)
end

trap(:INT) do
  IRB.start
  trap(:INT, 'EXIT')
end

loop do
  p "Current value: #{my_program_context.value}"
  sleep 1
end

# Result:
# "Current value: 40"
# "Current value: 40"

View Source

Unused variable format

  [
    ['Someone', 41, 'another field'],
    ['Someone2', 42, 'another field2'],
    ['Someone3', 43, 'another field3']
  ].each do |name,_,_|
    p name
  end

# Result:
# "Someone"
# "Someone2"
# "Someone3"

View Source

Variables from a regex

if  /\A(?<first>\w+),\s*(?<last>\w+)\z/ =~ "Franze, Jr"
  puts "#{first} #{last}"
end

# Result:
# Franze Jr

View Source

Zip

letters = "a".."d"
numbers = 1..3

letters.zip(numbers) do |letter, number|
  p(letter: letter, number: number)
end

# Result:
# {:letter=>"a", :number=>1}
# {:letter=>"b", :number=>2}
# {:letter=>"c", :number=>3}
# {:letter=>"d", :number=>nil}

View Source

Contributors

Contributing

  1. Fork it
  2. Create your trick branch: git checkout -b my-ruby-trick
  3. Add your trick to the collection of .rb files
  4. Regenerate README.md: rake build (install Rake with bundle)
  5. Commit your changes: git commit -am 'Add trick'
  6. Push to the branch: git push origin my-new-trick
  7. Create new Pull Request and explain why your code is trick