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

24 Ruby (on Rails) tricks

Jan
January 10, 2018

24 Ruby (on Rails) tricks

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