Skip to content

SkylerRogers/ruby-tricks

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

75 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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:
# https://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

About

Ruby Tricks

Resources

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 100.0%