Upgrade to Pro — share decks privately, control downloads, hide ads and more …

24 Ruby (on Rails) tricks

Avatar for Jan Jan
January 10, 2018

24 Ruby (on Rails) tricks

Avatar for Jan

Jan

January 10, 2018
Tweet

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. Trick 5 Array#Cycle ring = %w[one two three].cycle p ring.take(5)

    # => ["one", "two", "three", "one", "two"] 6 / 27
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. Trick 10 String#unicode_normalize (cont) Split the family? >> " "

    ".split('') => [" # ", "​", " $ ", "​", " % "] ! 12 / 27
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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