Skip to content

Instantly share code, notes, and snippets.

@mberlanda
Last active June 12, 2019 09:32
Show Gist options
  • Save mberlanda/194e5f3034479d06fccf32135adfa5a8 to your computer and use it in GitHub Desktop.
Save mberlanda/194e5f3034479d06fccf32135adfa5a8 to your computer and use it in GitHub Desktop.
Provide some insights about the recent ruby changes and features

Recent Ruby changes and features

Ruby 2.5

https://www.ruby-lang.org/en/news/2017/12/25/ruby-2-5-0-released/

  • rescue/else/ensure in do..end blocks :
# https://bugs.ruby-lang.org/issues/12906
my_func = lamdbda do
  begin
    # do something
  rescue SomeException
    # do something else
  end
end

# New syntax
my_func = lamdbda do
  # do something
rescue SomeException
  # do something else
end
  • yield_self vs tap
# .tap
foo = 123
bar = {}
foo.tap { |n| bar[:a] = n } # => 123
bar # => {:a=>123}

# .yield_self
123.yield_self { |n| {a: n} } # => {:a=>123}
  • hash slice and transform_keys
# feature backported from ActiveSupport
foo = {a: 1, b: 2, c: 3} # => {:a=>1, :b=>2, :c=>3}
foo.slice(:a, :c, :d) # => {:a=>1, :c=>3}
foo.transform_keys { |k| k.to_s.upcase } # => {"A"=>1, "B"=>2, "C"=>3}
  • Enumerable accept blocks on several methods
(1...5).any? { |x| x < 4 }
(1...5).none? { |x| x > 4 }
(1...5).all? { |x| x > 0 }
(1...5).one? { |x| x == 1 }

Ruby 2.6

https://www.ruby-lang.org/en/news/2018/12/25/ruby-2-6-0-released/

https://www.rubyguides.com/2018/11/ruby-2-6-new-features/

https://www.ghostcassette.com/function-composition-in-ruby/

  • alias yield_self with then
123.then { |n| {a: n} } # => {:a=>123}
  • endless range support
# ruby 2.5
(1.. Float::INFINITY).last # => Infinity

# New implementation
(1..).last # RangeError (cannot get the last element of endless range)

# Sample usage
["a", "b", "c"].zip(1..)
[1,2,3,4,5][1..]
(1..).step(5).take(100)
  • Enumerable#chain
(1..3).chain(2..5) # => #<Enumerator::Chain: [1..3, 2..5]>
(1..3).chain(2..5).to_a # => [1, 2, 3, 2, 3, 4, 5]
[1,2,3].chain([2, 3]) # => #<Enumerator::Chain: [[1, 2, 3], [2, 3]]>
[1,2,3].chain([2, 3]).to_a # => [1, 2, 3, 2, 3]
  • some conversion methods
Integer("a") # ArgumentError (invalid value for Integer(): "a") 
Float('1.03') # => 1.03
Rational('1') # => (1/1)
Complex(1.02) # => (1.02+0i)
  • function composition
a = ->(x) { x ** 2 }
b = ->(x) { x + 2 }
c = ->(x) { x - 2 }

(a >> b >> c).call(4)
(a << b << c).call(4)

Examples:

  • Pricing rules
# https://drivy.engineering/ruby-lambda-composition/
# List of our individual pricing rules
TAX           = ->(val) { val + val*0.05 }
FEE           = ->(val) { val + 1 }
PREMIUM       = ->(val) { val + 10 }
DISCOUNT      = ->(val) { val * 0.90 }
ROUND_TO_CENT = ->(val) { val.round(2) }
# One presenter
PRESENT       = ->(val) { val.to_f }

# Pre-define some rule sets for some pricing scenarios
REGULAR_SET    = [FEE, TAX, ROUND_TO_CENT, PRESENT]
PREMIUM_SET    = [FEE, PREMIUM, TAX, ROUND_TO_CENT, PRESENT]
DISCOUNTED_SET = [FEE, DISCOUNT, TAX, ROUND_TO_CENT, PRESENT]

def apply_rules(rules:, base_price:)
  rules.inject(:>>).call(base_price)
end

amount = BigDecimal(100)

puts "regular:    #{apply_rules(rules: REGULAR_SET, base_price: amount)}"    # => 106.05
puts "premium:    #{apply_rules(rules: PREMIUM_SET, base_price: amount)}"    # => 116.55
puts "discounted: #{apply_rules(rules: DISCOUNTED_SET, base_price: amount)}" # => 95.45
  • API Client
# https://blog.stanko.io/function-composition-ruby-8f91aea21e5f

require 'net/http'
require 'uri'
require 'json'

fetch =
  self.method(:URI) \
  >> Net::HTTP.method(:get) \
  >> JSON.method(:parse) \
  >> -> response { response.dig('bpi', 'EUR', 'rate') || '0' } \
  >> -> value { value.gsub(',', '') } \
  >> self.method(:Float)

fetch.call('https://api.coindesk.com/v1/bpi/currentprice.json') # => 7028.6713

What it is not:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment