Skip to content

Instantly share code, notes, and snippets.

@JamesYang76
Last active May 21, 2021 03:40
Show Gist options
  • Save JamesYang76/8b57eeb2ebb9f92442e1dd071a7f09b1 to your computer and use it in GitHub Desktop.
Save JamesYang76/8b57eeb2ebb9f92442e1dd071a7f09b1 to your computer and use it in GitHub Desktop.
Ruby basic

Block

def print_once
  yield
end

print_once { puts "Block is being run" }

def my_block
  yield 2
  yield 3
end
my_block {|parameter| puts "parameter is: #{parameter}" }

def do_something_with_block
  return "No block given" unless block_given?
  yield
end

Proc

my_proc = Proc.new { |x| puts "arg: #{x}" }
my_proc.call "Abc" #=> arg: Abc

def explicit_block(&block)
  return "No block given" unless block
  block.call # same as yield
end
explicit_block { puts "Explicit block called" }

factor = Proc.new {|n| print n*2 }
# or 
factor = proc {|n| print n*2}

# using the proc value
[3,2,1].each(&factor)
=> 642
# & ruby knows that this is a proc and not a variable.


def my_each(&block )
    self.length.times do |i|
      # and now we can call our new Proc like normal
      block.call( self[i] )
    end
end
[1,2,3].my_each { |i| puts i*2 }
# &converts the block into a proc so we treat the block as a proc inside our method.

Lamda

 lamb = lambda {|arg| puts "I am a lambda #{arg}" }
 lamb = -> (arg) { puts "I am a stuby lambda#{arg}" }
 
# Procs don’t care about the correct number of arguments, while lambdas will raise an exception.
# Return and break behaves differently in procs and lambdas

References

refer : https://www.rubyguides.com/2016/02/ruby-procs-and-lambdas/

https://blog.appsignal.com/2018/09/04/ruby-magic-closures-in-ruby-blocks-procs-and-lambdas.html

https://medium.com/podiihq/ruby-blocks-procs-and-lambdas-bb6233f68843

include vs extend

module Greetings
  def say_hello
    puts "Hello!"
  end
end

class Human
  include Greetings
end

Human.new.say_hello # => "Hello!"
Human.say_hello     # NoMethodError

class Robot
  extend Greetings
end

Robot.new.say_hello # NoMethodError
Robot.say_hello     # => "Hello!"

include vs prepend

module FooBar
  def say
    puts "2 - Module"
  end
end

class Foo
  include FooBar

  def say
    puts "1 - Implementing Class"
    super
  end
end

Foo.new.say # =>
            # 1 - Implementing Class
            # 2 - Module
Foo.ancestors
 => [Foo, FooBar,...]
module FooBar
 def say
   puts "2 - Module"
   super
 end
end

class Foo
 prepend FooBar

 def say
   puts "1 - Implementing Class"
 end
end

Foo.new.say # =>
           # 2 - Module
           # 1 - Implementing Class

Foo.ancestors
=> [FooBar, Foo, ...]

References

https://medium.com/@connorstack/understanding-ruby-load-require-gems-bundler-and-rails-autoloading-from-the-bottom-up-3b422902ca0

https://medium.com/@leo_hetsch/ruby-modules-include-vs-prepend-vs-extend-f09837a5b073

style guide

https://github.com/rubocop-hq/ruby-style-guide

and/ or

 a = true && false
 => false 
 a
 => false 
 a = true and false
 => false 
 a
 => true 
 
def show
  @book = Book.find(params[:id])
  if @book.special?
    render action: "special_show" and return
  end
  render action: "regular_show"
end
##################################################################
 a = false || true
 => true 
 a
 => true 
 a = false or true
 => true 
 a
 => false

Splat Operator

# Array Coercion
up_to_ten = *1..10
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
hello = *"Hello"
=> ["Hello"]

# Flatten Arrays
some_numbers = [1, 2, 3]
up_to_five = [*some_numbers, 4, 5]
=> [1, 2, 3, 4, 5]

# Multiple Variable Assignment
letters = ["a", "b", "c", "d", "e"]
first, *second = letters
puts "#{first}, #{second}"
=>  a, ["b", "c", "d", "e"]
*first, second = 10,20,30
puts "#{first}, #{second}"
=> [10, 20], 30

arguments

# For hash
def my_method(options = {}) 
 options.inspect
end
my_method( width: 400, height: 50, show_border: false )
my_method( { width: 400, height: 50, show_border: false } )

#For keyword
def my_keyword(width: 100, height: 100, show_border: false) 
 " #{width} #{height} #{show_border}"
end
my_keyword( width: 400, height: 50, show_border: false )
options = { show_border: true}
my_keyword(options)

my_keyword(width: 400, height: 500, options) #cause errors

# hash and keword arguments are identical

#For *
def dual_argument_capturing_method(*args, **keyword_args)
 {args: args, keyword_args: keyword_args}
end
dual_argument_capturing_method(1, 2, 3, key: "value",key2:"value2")
 => {:args=>[1, 2, 3], :keyword_args=>{:key=>"value", :key2=>"value2"}}

def generate_thumbnail(name, width, height)
  # ...
end
dimensions = [240, 320]
generate_thumbnail("headshot.jpg", *dimensions)


def hello_message(greeting, time_of_day, first_name:, last_name:)
  "#{greeting} #{time_of_day}, #{first_name} #{last_name}!"
end

args = ["Morning"]
keyword_args = {last_name: "Weiss"}

hello_message("Good", *args, first_name: "Justin", **keyword_args)
=> "Good Morning, Justin Weiss!"

fetch

Returns a value from the hash for the given key.

h = { "a" => 100, "b" => 200 }
h.fetch("a")                            #=> 100
h.fetch("z", "go fish")                 #=> "go fish"
h.fetch("z") { |el| "go fish, #{el}"}   #=> "go fish, z"

enumerable

refer : https://ruby-doc.org/core-2.2.3/Enumerable.html

# map
# The original array is not modified. Returns the modified array.
[1,2,3,4,5,6,7,8,9,10].map{|e| e*3 }
# [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]

# collect is alias for map

# select
# Returns a new array containing all elements of ary for which the given block returns a true value.
[1,2,3,4,5].select { |num|  num.even?  }   #=> [2, 4]

# reject
# Returns an array for all elements of enum for which the given block returns false.
(1..10).reject { |i|  i % 3 == 0 }   #=> [1, 2, 4, 5, 7, 8, 10]

# inject
# Combines all elements of enum by applying a binary operation
(5..10).inject(:+) #=>45
(5..10).inject { |product, n| product * n } #=> 151200
# product=5, n=6
# product=30, n=7
# product=210, n=8
# product=1680, n=9
# product=15120, n=10
# You can also specify an initial value as parameter before the block
["bar","baz","quux"].inject("foo") {|acc,elem| acc + "!!" + elem } #=>"foo!!bar!!baz!!quux" 

# reduce is alias for inject

# find
# Passes each entry in enum to block. Returns the first for which block is not false.
[1,2,3,4,5,6,7,8,9,10].find{ |el| el / 2 == 2 } #=>4

# detect is alias for find

# find_all
# Returns an array containing all elements of enum for which the given block returns a true value.
[1,2,3,4,5].find_all { |num|  num.even?  }   #=> [2, 4]

# find_all vs select
# On a hash, find_all returns an array, but select returns a hash instead of an array
hash = {a: 1, b: 2, c: 3, d: 4}
hash.find_all { |key, value| value.odd? } #=> [[:a, 1], [:c, 3]]
hash.select { |key, value| value.odd? }   #=> {a:1, c:3}

#pluck
# It is from activesupport
[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) #=> [[1, "David"], [2, "Rafael"]]

group_by

group_by returns a hash where the keys are defined by our grouping rule, and the values are the corresponding objects from our original collection.

enu = (1..10) 
enu.group_by { |obj| obj % 4 == 1 }  # => {true=>[1, 5, 9], false=>[2, 3, 4, 6, 7, 8, 10]}
(1..6).group_by { |i| i%3 }   #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}

Product.all.group_by(&:name)

slice

[ "a", "b", "c", "d", "e" ].slice(1,2) #index, length
[ "a", "b", "c", "d", "e" ][1, 2]
[ "a", "b", "c", "d", "e" ][1..2]
=> ["b", "c"]

"hello there".slice(1,2)
"hello there"[1,2]
=>"el" 

{ a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
=> {:a=>1, :b=>2}

Product.first.slice(:id, :title)
=> {"id"=>121, "title"=>"house"} 

to_h

[[:foo, 1], [:bar, 2]].to_h
 => {:foo=>1, :bar=>2}
 
[ [:foo,[ [:bar,1]].to_h ] ].to_h
 => {:foo=>{:bar=>1}

try and The Safe Navigation Operator (&.)

# if the receiver does not respond to it the call returns nil rather than raising an exception.

@person.try(:name) # @person.name if @person && @person.name
@person.try(:spouse).try(:name) # @person.spouse.name if @person && @person.spouse && @person.spouse.name

# You can pass arguments and blocks to try():
@manufacturer.products.first.try(:enough_in_stock?, 32)  # => "Yes"
try! 
#Same as try, but raises a NoMethodError exception if the receiver is not nil and does not implement the tried method.
123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer

&
#Same as try!
account&.owner&.address

account = Account.new(owner: false)
account.owner.address
# => NoMethodError: undefined method `address' for false:FalseClass `

account && account.owner && account.owner.address
# => false

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => undefined method `address' for false:FalseClass`

dig

address = params.dig(:account, :owner, :address) # address = params[:account].try(:[], :owner).try(:[], :address)

h = { foo: {bar: {baz: 1}}}

h.dig(:foo, :bar, :baz)     #=> 1
h.dig(:foo, :zot, :xyz)     #=> nil

g = { foo: [10, 11, 12] }
g.dig(:foo, 1)              #=> 11
g.dig(:foo, 1, 0)           #=> TypeError: Integer does not have #dig method
g.dig(:foo, :bar)           #=> TypeError: no implicit conversion of Symbol into Integer

symbol#to_proc

result = names.map {|name| name.upcase}
result = names.map(&:upcase)


def to_proc
  proc { |obj, *args| obj.send(self, *args) }
end

range

(1..10).to_a   #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(1...10).to_a  #=> [1, 2, 3, 4, 5, 6, 7, 8, 9]

Random.rand

  • Without arguments, rand gives you a floating point number between 0 & 1 (like 0.4836732493)
  • With an integer argument (rand(10)) you get a new integer between 0 & that number
  • With a range argument (rand(1..20)) you get an integer between the start of the range & the end of the range
# Generate an integer from 0 to 10
 (rand() * 10).to_i
 rand(10)
 # Generate an integer from 1 to 20
 rand(1..20)

Percent Strings

stringA = "World!"
%w[Hello\n #{stringA}]
=> ["Hello\\n", "\#{stringA}"] 
%W[Hello\n #{stringA}] 
=> ["Hello\n", "World!"] 

a = "three"
%i[one two #{a}]
=> [:one, :two, :"\#{a}"]
%I[one two #{a}]
 => [:one, :two, :three]
 
date = `date`
 "Tue Mar  3 13:50:57 NZDT 2020\n"
echo = %x(echo `date`) #shell command
=> "Tue Mar 3 13:51:13 NZDT 2020\n" 

# %() is shorthand for %Q
%(<tr><td class="name">#{name}</td>)

%Q() gives a double-quoted string

Heredoc

Nicely indented multi-line strings.

page = <<-HTML
  Heredocs are cool & useful
HTML
# "  Heredocs are cool & useful\n"

#Note that this was introduced with Ruby 2.3. On older Rubies you may use String#strip_heredoc from ActiveSupport.
page = <<~HTML
  Heredocs are cool & useful
HTML
# "Heredocs are cool & useful\n"


page = <<~HTML.strip
  Heredocs are cool & useful
HTML
# "Heredocs are cool & useful"

#<< vs <<- (or <<~)
# terminating sequence must be at the very beginning of the line
def foo
  puts(<<TEXT)
      Hello!
TEXT
  end
end


#interpolation
type  = "healthy"
table = "food"
query = <<-SQL
  SELECT * FROM #{table}
  WHERE #{type} = true
SQL

# You can disable the interpolation by surrounding the heredoc name with single quotes.
doc = <<-'TIME'
Current time is #{Time.now}
TIME

string = [<<ONE, <<TWO, <<THREE]
  the first thing
ONE
  the second thing
TWO
  and the third thing
THREE
=> ["the first thing\n", "the second thing\n", "and the third thing\n"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment