Slide 1

Slide 1 text

24 tricks with Ruby (on Rails) that will blow your mind ! 1 / 27

Slide 2

Slide 2 text

Trick 1 Object#presence_in ActiveSupport gives us .presence_in on Object, WTF? Ever saw some code like this? def order ORDER_WHITELIST.include?(params[:order]) ? params[:order] : 'title_asc' end How about using .presence_in: def order params[:order].presence_in(ORDER_WHITELIST) || 'title_asc' end 2 / 27

Slide 3

Slide 3 text

Trick 2 Use Symbol#to_proc Passing trivial blocks can be made much more concise by using Ruby's &-operator. # not so good %w(apples oranges peaches).map { |x| x.upcase } # => ["APPLES", "ORANGES", "PEACHES"] # better %w(apples oranges peaches).map(&:upcase) # => ["APPLES", "ORANGES", "PEACHES"] 3 / 27

Slide 4

Slide 4 text

Trick 3 Parallel assignments coordinates = [5, 7, 2] # rather long x = coordinates[0] y = coordinates[1] z = coordinates[2] # better x, y, z = coordinates 4 / 27

Slide 5

Slide 5 text

Trick 4 The splat * operator Say you want to allow passing an array around or a single value. However, you always want to make sure you're dealing with an array. You can use the splat operator. foo = 1 bar = [2, 3] foo_array = [*foo] # => [1] bar_array = [*bar] # => [2, 3] Hint: Array(foo) and Array(bar) also works! 5 / 27

Slide 6

Slide 6 text

Trick 5 Array#Cycle ring = %w[one two three].cycle p ring.take(5) # => ["one", "two", "three", "one", "two"] 6 / 27

Slide 7

Slide 7 text

Trick 6 Enumerable#flat_map Using the map method in conjuction with flatten should ring a bell. Make sure to check if you could use flat_map to save some CPU cycles. # not so good [1, 2, 3].map { |x| [x, x+1] }.flatten # better [1, 2, 3].flat_map { |x| [x, x+1] } 7 / 27

Slide 8

Slide 8 text

Trick 7 Enumerable#max_by Don't reinvent the wheel and be overly clever. For example, trying to find the element of a collection that has the max length. # not so good longest = %w(cat sheep bear).inject do |memo, word| memo.length > word.length ? memo : word end # => "sheep" # better longest = %w(cat sheep bear).max_by(&:length) # => "sheep" 8 / 27

Slide 9

Slide 9 text

Trick 8 Enumerable#count While it is possible so combine a lot of the methods of Enumerable, make sure the standard library does not provide shortcut already. # not so good [1, 2, 3].select { |x| x > 2 }.count # better [1, 2, 3].count { |x| x > 2 } 9 / 27

Slide 10

Slide 10 text

Trick 9 Array#sample to get a random array element # ugly [1, 2, 3][rand(3)] # still bad [1, 2, 3].shuffle.first # better [1, 2, 3].sample .sample also provides a performance benefit over the other variants! 10 / 27

Slide 11

Slide 11 text

Trick 10 String#unicode_normalize [1] a1 = "ä" => "ä" [2] a2 = "ä" => "ä" [3] a1 == a2 => false WTF? [1] a1.bytes => [97, 204, 136] # NFD: Canonical Decomposition [2] a2.bytes => [195, 164] # NFC: Canonical Decomposition, followed by Canonical Compositio Ah, now what? .unicode_normalize to the recue a1 = a1.unicode_normalize(:nfc) a2 = a2.unicode_normalize(:nfc) a1 == a2 => true 11 / 27

Slide 12

Slide 12 text

Trick 10 String#unicode_normalize (cont) Split the family? >> " " ".split('') => [" # ", "​", " $ ", "​", " % "] ! 12 / 27

Slide 13

Slide 13 text

Trick 11 Rails 5.1 has introduced Date#all_day helper # quite long User.where(created_at: Date.today.beginning_of_day..Date.today.end_of_day) # better User.where(created_at: Date.today.all_day) 13 / 27

Slide 14

Slide 14 text

Trick 12 Make use of reverse_each In case you need to traverse a collection in reverse, there is no need to reverse it first and then iterate over it. Just use reverse_each directly. # bad [1, 2, 3].reverse.each { |x| puts x } # good [1, 2, 3].reverse_each { |x| puts x } 14 / 27

Slide 15

Slide 15 text

Trick 13 Make use of each_key # slower { a: 'Hello', b: 'World'}.keys.each { |x| puts x } # faster { a: 'Hello', b: 'World'}.each_key { |x| puts x } # => a b 15 / 27

Slide 16

Slide 16 text

Trick 14 Enumerable#each_with_object vs. inject If you need to return the object inside reduce or inject, make use of each_with_object. # not so good (1..10).reduce(Hash.new(0)) { |hash, i| hash[i] = i*2; hash } # => {1=>2, 2=>4, 3=>6, 4=>8, 5=>10, 6=>12, 7=>14, 8=>16, 9=>18, 10=>20} # better (1..10).each_with_object(Hash.new(0)) { |i, hash| hash[i] = i*2 } # => {1=>2, 2=>4, 3=>6, 4=>8, 5=>10, 6=>12, 7=>14, 8=>16, 9=>18, 10=>20} 16 / 27

Slide 17

Slide 17 text

Trick 15 Integer#digits You want an array of the digits of a number in reverse order, so you could do: awesome_number = 12345 p awesome_number.to_s.chars.map(&:to_i).reverse # Result: [5, 4, 3, 2, 1] But, there is .digits: p awesome_number.digits # => [5, 4, 3, 2, 1] 17 / 27

Slide 18

Slide 18 text

Trick 16 You can use grep(pattern) Instead of using select and matching a regular expression inside the block, just use grep. # not so good %w(xml yaml json txt html).select { |w| w =~ /ml$/ } # => ["xml", "yaml", "html"] # better %w(xml yaml json txt html).grep(/ml$/) # => ["xml", "yaml", "html"] There is also .grep_v, which greps all the elements NOT in pattern: (1..10).grep_v 2..5 #=> [1, 6, 7, 8, 9, 10] 18 / 27

Slide 19

Slide 19 text

Trick 17 Use find when appropriate Save CPU cycles if you only need to find the first element. # not so good (1..10).select { |num| num % 3 == 0 }.first # => 3 # better (1..10).find { |num| num % 3 == 0 } # => 3 19 / 27

Slide 20

Slide 20 text

Trick 18 Squiggly heredoc # ugly code indentation puts <<-EOS This line is not indented. This one, however, is indented with 2 spaces. EOS # => This line is not indented. # => This one, however, is indented with 2 spaces. # better code identation, preserving the desired indentation of the string puts <<~EOS This line is not indented. This one, however, is indented with 2 spaces. EOS # => This line is not indented. # => This one, however, is indented with 2 spaces. 20 / 27

Slide 21

Slide 21 text

Trick 19 Hash#dig (since Ruby 2.3) x = { foo: { bar: [ 'a', { baz: 'x' } ] } } x.dig(:foo, :bar) # => [ 'a', { baz: 'x' } ] x.dig(:foo, :bar, 1, :baz) # => "x" x.dig(:foo, :wronk, 1, :baz) # => nil 21 / 27

Slide 22

Slide 22 text

Trick 20 More concise check for nil: try # without try unless @number.nil? @number.next end # with try @number.try(:next) Ruby 2.3 introduced the safe navigation operator &., which is quite similar, but NOT the same. >> false.try(:foobar) => nil # vs. >> false&.foobar => NoMethodError: undefined method `foobar' for false:FalseClass 22 / 27

Slide 23

Slide 23 text

Trick 21 Don't repeat options all the time: with_options # always repeating "dependent: :destroy" class Account < ActiveRecord::Base has_many :customers, dependent: :destroy has_many :products, dependent: :destroy has_many :invoices, dependent: :destroy has_many :expenses, dependent: :destroy end # using with_options to dry it up class Account < ActiveRecord::Base with_options dependent: :destroy do |assoc| assoc.has_many :customers assoc.has_many :products assoc.has_many :invoices assoc.has_many :expenses end end 23 / 27

Slide 24

Slide 24 text

Trick 22 Enumerable#many? - Rails shorthand # common way to check if there is more than one element <% if user.comment.size > 1 %> ... <% end %> # convenience through ActiveSupport <% if user.comments.many? %> ... <% end %> Can be called with a block too, so to return if more than one person is over 26: people.many? { |p| p.age > 26 } 24 / 27

Slide 25

Slide 25 text

Trick 23 Ruby 2.3 added fetch_values method to hash capitals = { usa: "Washington DC", china: "Beijing", india: "New Delhi", australia: "Canberra" } capitals.fetch_values(:usa, :india) #=> ["Washington DC", "New Delhi"] capitals.fetch_values(:usa, :spain) { |country| "N/A" } #=> ["Washington DC", "N/A"] 25 / 27

Slide 26

Slide 26 text

Trick 24 Hash#transform_keys This is available in Ruby 2.5 (and also in Rails) hash = { a: 1, b: 2 } hash.transform_keys { |k| k.to_s } #=> { 'a' => 1, 'b' => 2 } 26 / 27

Slide 27

Slide 27 text

Thank you for your attention 27 / 27