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

Just when you thought you couldn’t refactor any...

Just when you thought you couldn’t refactor any more…

Deck for an upcoming talk…

Avatar for rubypitch

rubypitch

July 31, 2017

Other Decks in Programming

Transcript

  1. The problem parse 'youtube.com/watch?v=Cx6aGMC6MjU' => {type: :video, id: 'Cx6aGMC6MjU'} parse

    'not-a-youtube-url' => {type: :unknown} parse 'youtube.com/abc' => {type: :channel, name: 'abc'} Correct Complete Clear Compact
  2. Step 1: parsing the type parse 'youtube.com/watch?v=Cx6aGMC6MjU' => {type: :video,

    id: 'Cx6aGMC6MjU'} parse 'not-a-youtube-url' => {type: :unknown} parse 'youtube.com/abc' => {type: :channel, name: 'abc'}
  3. String#start_with?([prefixes]+) → true or false # Introduced in Ruby 1.8.7

    # Returns true if str starts with one of the prefixes given. 'hello'.start_with? 'hell' # => true # Returns true if one of the prefixes matches. 'hello'.start_with? 'heaven', 'hell' # => true 'hello'.start_with? 'heaven', 'paradise' # => false
  4. Using String#start_with? if text.start_with? 'youtube.com/watch?v=' {type: :video} elsif text.start_with? 'youtube.com/'

    {type: :channel} else {type: :unknown} end Correct Complete Clear Compact ✖ ✖ ✔ ✖
  5. The problem parse 'youtube.com/watch?v=(11 letters, digits, _ or -)' =>

    {type: :video, id: …} parse '(anything else)' => {type: :unknown} parse 'youtube.com/(at least 1 letter, digit, _ or -)' => {type: :channel, name: …}
  6. String#match?(pattern) → true or false # Introduced in Ruby 2.4

    # Converts pattern to a Regexp (if it isn’t already one), then returns a true or false indicating whether the regexp is matched without updating $~ and other related variables. 'Ruby'.match? %r{P...} # => false 'Ruby'.match? %r{R...} # => true $~ # => nil
  7. Using String#match? if text.match? %r{youtube\.com/watch\?v=[\w-]{11}} {type: :video} elsif text.match? %r{youtube\.com/[\w-]+}

    {type: :channel} else {type: :unknown} end Correct Complete Clear Compact ✔ ✖ ✔ ✖
  8. rxp === str → true or false # Following a

    regular expression literal with the === operator allows you to compare against a String. # Case Equality — Used in case statements. %r{^[a-z]*$} === 'HELLO' # => false %r{^[A-Z]*$} === 'HELLO' # => true case 'HELLO' when %r{^[a-z]*$} then 'Lower case' when %r{^[A-Z]*$} then 'Upper case' end # =>'Upper case'
  9. Using String#match? if text.match? %r{youtube\.com/watch\?v=[\w-]{11}} {type: :video} elsif text.match? %r{youtube\.com/[\w-]+}

    {type: :channel} else {type: :unknown} end Correct Complete Clear Compact ✔ ✖ ✔ ✖
  10. Using Regexp#=== and case statement case text when %r{youtube\.com/watch\?v=[\w-]{11}} {type:

    :video} when %r{youtube\.com/[\w-]+} {type: :channel} else {type: :unknown} end Correct Complete Clear Compact ✔ ✖ ✔ ✔
  11. Step 2: parsing everything else parse 'youtube.com/watch?v=Cx6aGMC6MjU' => {type: :video,

    id: 'Cx6aGMC6MjU'} parse 'not-a-youtube-url' => {type: :unknown} parse 'youtube.com/abc' => {type: :channel, name: 'abc'}
  12. Using Regexp#=== and case statement case text when %r{youtube\.com/watch\?v=[\w-]{11}} {type:

    :video} when %r{youtube\.com/[\w-]+} {type: :channel} else {type: :unknown} end How can we capture and name these matches?
  13. Regexp captures # Introduced in Ruby 1.9 # Parentheses can

    be used for capturing. Capture groups can be referred to by name when defined with the (?<name>) or (?'name') constructs. %r{\$(?<dollars>\d+)\.(?<cents>\d+)}.match '$3.67' # => #<MatchData "$3.67" dollars:"3" cents:"67"> $~['dollars'] # => "3" $~['cents'] # => "67"
  14. Using Regexp captures case text when %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} {type: :video, id:

    $~['id']} when %r{youtube\.com/(?<name>[\w-]+)} {type: :channel, name: $~['name']} else {type: :unknown} end Correct Complete Clear Compact ✔ ✔ ≈ ✖
  15. MatchData#named_captures → hash # Introduced in 2.4 # Returns a

    Hash using named capture. A key of the hash is a name of the named captures. A value of the hash is a string of last successful capture of corresponding group. %r{\$(?<dollars>\d+)\.(?<cents>\d+)}.match '$3.67' # => #<MatchData "$3.67" dollars:"3" cents:"67"> $~.named_captures # => {"dollars" => "3", "cents"=> "67"}
  16. Using MatchData#named_captures case text when %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} $~.named_captures.merge type: :video when

    %r{youtube\.com/(?<name>[\w-]+)} $~.named_captures.merge type: :channel else {type: :unknown} end Correct Complete Clear Compact ✔ ✔ ≈ ≈
  17. Using MatchData#named_captures case text when %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} $~.named_captures.merge type: :video when

    %r{youtu\.be/(?<id>[\w-]{11})} $~.named_captures.merge type: :video when %r{youtube\.com/(?<name>[\w-]+)} $~.named_captures.merge type: :channel else {type: :unknown} end Correct Complete Clear Compact ✔ ✔ ≈ ≈
  18. Using MatchData#named_captures case text when %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} $~.named_captures.merge type: :video when

    %r{youtu\.be/v/(?<id>[\w-]{11})} $~.named_captures.merge type: :video when %r{youtube\.com/channel/UC(?<id>[\w-]{22})} $~.named_captures.merge type: :channel when %r{youtube\.com/(?<name>[\w-]+)} $~.named_captures.merge type: :channel else {type: :unknown} end Correct Complete Clear Compact ✔ ✔ ≈ ✖
  19. Enumerable#find(ifnone = nil) {|obj| block} → obj or nil #

    Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or nil otherwise. (1..90).find {|i| i % 5 == 0 and i % 7 == 0} # => 35 (1..10).find {|i| i % 5 == 0 and i % 7 == 0} # => nil (1..10).find(-> {0}) do |i| i % 5 == 0 and i % 7 == 0 end # => 0
  20. Using Enumerable#find PATTERNS = { %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} => :video, %r{youtube\.com/(?<name>[\w-]+)} =>

    :channel, } PATTERNS.find do |regex, type| if text.match(regex) …return the Regexp captures and the matched type… end end
  21. break statement # Introduced before Ruby 1.8.7 # Use break

    to leave a block early. break accepts a value that supplies the result of the expression it is "breaking" out of: (1..10).each{|value| break if value.even?} # => nil (1..10).each do |value| break(value * 2) if value.even? end # => 4
  22. Using Enumerable#find and break PATTERNS = { %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} => :video,

    %r{youtube\.com/(?<name>[\w-]+)} => :channel, } PATTERNS.find do |regex, type| if text.match(regex) …return the Regexp captures and the matched type… end end
  23. Using Enumerable#find and break PATTERNS = { %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} => :video,

    %r{youtube\.com/(?<name>[\w-]+)} => :channel, } PATTERNS.find do |regex, type| if text.match(regex) break $~.named_captures.merge(type: type) end end
  24. Using Enumerable#find and break PATTERNS = { %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} => :video,

    %r{youtube\.com/(?<name>[\w-]+)} => :channel, } PATTERNS.find(-> { {type: :unknown} }) do |regex, type| if text.match(regex) break $~.named_captures.merge(type: type) end end
  25. Using Enumerable#find and break PATTERNS = { %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} => :video,

    %r{youtube\.com/(?<name>[\w-]+)} => :channel, } PATTERNS.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match(regex) end Correct Complete Clear Compact ✔ ✔ ✔ ≈
  26. Using Enumerable#find and break PATTERNS = { %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} => :video,

    %r{youtu\.be/(?<id>[\w-]{11})} => :video, %r{youtube\.com/(?<name>[\w-]+)} => :channel, } PATTERNS.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match(regex) end Correct Complete Clear Compact ✔ ✔ ✔ ≈
  27. Using Enumerable#find and break PATTERNS = { %r{youtube\.com/watch\?v=(?<id>[\w-]{11})} => :video,

    %r{youtu\.be/(?<id>[\w-]{11})} => :video, %r{youtube\.com/channel/(?<id>UC[\w-]{22})} => :channel, %r{youtube\.com/(?<name>[\w-]+)} => :channel, } PATTERNS.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match(regex) end Correct Complete Clear Compact ✔ ✔ ≈ ≈
  28. Using Enumerable#find and break VIDEO_PATTERNS = [ %r{youtube\.com/watch\?v=(?<id>[\w-]{11})}, %r{youtu\.be/(?<id>[\w-]{11})}] CHANNEL_PATTERNS

    = [ %r{youtube\.com/channel/(?<id>UC[\w-]{22})}, %r{youtube\.com/(?<name>[\w-]+)}] patterns = …first iterate through video patterns, then channel patterns… patterns.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match(regex) end Correct Complete Clear Compact ✔ ✔ ≈ ≈
  29. Enumerator.new # Creates a new Enumerator object, which can be

    used as an Enumerable. Iteration is defined by the given block, in which a "yielder" object, given as block parameter, can be used to yield a value by calling the yield method (aliased as <<): fib = Enumerator.new do |y| a = b = 1 loop{ y << a; a, b = b, a + b } end fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
  30. Using Enumerable#find and break VIDEO_PATTERNS = [ %r{youtube\.com/watch\?v=(?<id>[\w-]{11})}, %r{youtu\.be/(?<id>[\w-]{11})}] CHANNEL_PATTERNS

    = [ %r{youtube\.com/channel/(?<id>UC[\w-]{22})}, %r{youtube\.com/(?<name>[\w-]+)}] patterns = Enumerator.new do |patterns| VIDEO_PATTERNS.each {|re| patterns << [re, :video]} CHANNEL_PATTERNS.each {|re| patterns << [re, :channel]} end patterns.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match(regex) end
  31. Using Enumerable#find and break VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?<id>[\w-]{11})}, %r{youtu\.be/(?<id>[\w-]{11})}] CHANNEL_PATTERNS =

    [%r{youtube\.com/channel/(?<id>UC[\w-]{22})}, %r{youtube\.com/(?<name>[\w-]+)}] patterns = Enumerator.new do |patterns| VIDEO_PATTERNS.each {|re| patterns << [re, :video]} CHANNEL_PATTERNS.each {|re| patterns << [re, :channel]} end patterns.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match regex end Correct Complete Clear Compact ✔ ✔ ✔ ✔
  32. Using Enumerable#find and break VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?<id>[\w-]{11})}, %r{youtu\.be/(?<id>[\w-]{11})}] CHANNEL_PATTERNS =

    [%r{youtube\.com/channel/(?<id>UC[\w-]{22})}, %r{youtube\.com/(?<name>[\w-]+)}] patterns = Enumerator.new do |patterns| VIDEO_PATTERNS.each {|re| patterns << [re, :video]} CHANNEL_PATTERNS.each {|re| patterns << [re, :channel]} end patterns.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match %r{\A(?:https?://)?(?:www\.)?#{regex}\Z} end Correct Complete Clear Compact ✔ ≈ ✔ ✔
  33. Using Enumerable#find and break VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?<id>[\w-]{11})}, %r{youtu\.be/(?<id>[\w-]{11})}] PLAYLIST_PATTERNS =

    [%r{youtube\.com/playlist/?\?list=(?<id>UC[\w-]+)}] CHANNEL_PATTERNS = [%r{youtube\.com/channel/(?<id>UC[\w-]{22})}, %r{youtube\.com/(?<name>[\w-]+)}] patterns = Enumerator.new do |patterns| VIDEO_PATTERNS.each {|re| patterns << [re, :video]} PLAYLIST_PATTERNS.each {|re| patterns << [re, :playlist]} CHANNEL_PATTERNS.each {|re| patterns << [re, :channel]} end patterns.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match %r{\A(?:https?://)?(?:www\.)?#{regex}\Z} end Correct Complete Clear Compact ✔ ✔ ✔ ✔
  34. The final result VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?<id>[\w-]{11})}, %r{youtu\.be/(?<id>[\w-]{11})}] PLAYLIST_PATTERNS = [%r{youtube\.com/playlist/?\?list=(?<id>UC[\w-]+)}]

    CHANNEL_PATTERNS = [%r{youtube\.com/channel/(?<id>UC[\w-]{22})}, %r{youtube\.com/(?<name>[\w-]+)}] patterns = Enumerator.new do |patterns| VIDEO_PATTERNS.each {|re| patterns << [re, :video]} PLAYLIST_PATTERNS.each {|re| patterns << [re, :playlist]} CHANNEL_PATTERNS.each {|re| patterns << [re, :channel]} end patterns.find(-> { {type: :unknown} }) do |regex, type| break $~.named_captures.merge(type: type) if text.match %r{\A(?:https?://)?(?:www\.)?#{regex}\Z} end $~ MatchData#named_captures break Enumerable#find String#match Optional captures \A … \Z << Enumerator.new %r Captures \w Quantifiers