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. 24 tricks with Ruby (on Rails) that will
    blow your mind
    !
    1 / 27

    View full-size slide

  2. 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

    View full-size slide

  3. 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

    View full-size slide

  4. 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

    View full-size slide

  5. 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

    View full-size slide

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

    View full-size slide

  7. 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

    View full-size slide

  8. 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

    View full-size slide

  9. 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

    View full-size slide

  10. 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

    View full-size slide

  11. 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

    View full-size slide

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

    View full-size slide

  13. 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

    View full-size slide

  14. 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

    View full-size slide

  15. 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

    View full-size slide

  16. 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

    View full-size slide

  17. 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

    View full-size slide

  18. 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

    View full-size slide

  19. 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

    View full-size slide

  20. 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

    View full-size slide

  21. 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

    View full-size slide

  22. 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

    View full-size slide

  23. 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

    View full-size slide

  24. 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

    View full-size slide

  25. 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

    View full-size slide

  26. 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

    View full-size slide

  27. Thank you for your attention
    27 / 27

    View full-size slide