Slide 1

Slide 1 text

Just when you thought you couldn’t refactor any more… speakerdeck.com/claudiob Slides available!

Slide 2

Slide 2 text

String #start_with? MatchData #named_captures String #match? Enumerable #find Regexp #=== break statement Regexp captures Enumerator .new

Slide 3

Slide 3 text

What’s in a YouTube URL?

Slide 4

Slide 4 text

The problem parse 'youtube.com/watch?v=Cx6aGMC6MjU' => {type: :video, id: 'Cx6aGMC6MjU'} parse 'not-a-youtube-url' => {type: :unknown} parse 'youtube.com/confreaks' => {type: :channel, name: 'confreaks'} Correct Complete Clear Compact Correct Complete Clear Compact & & &

Slide 5

Slide 5 text

String#start_with?

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

parse 'youtube.com/watch?v=Cx6aGMC6MjU' => {type: :video, id: 'Cx6aGMC6MjU'} parse 'not-a-youtube-url' => {type: :unknown} parse 'youtube.com/confreaks' => {type: :channel, name: 'confreaks'} Step 1: parsing the type

Slide 8

Slide 8 text

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 Correct Complete Clear Compact & & &

Slide 9

Slide 9 text

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: …}

Slide 10

Slide 10 text

String#match?

Slide 11

Slide 11 text

String#match?(pattern) → true or false # Introduced in Ruby 2.4 # Converts pattern to a Regexp (if it isn’t already one), then returns true or false indicating whether the regexp is matched without updating $~ and other related variables. 'Ruby'.match? %r{R...} # => true 'Ruby'.match? %r{R..} # => false 'Ruby'.match? %r{P...} # => false

Slide 12

Slide 12 text

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 Correct Complete Clear Compact & & &

Slide 13

Slide 13 text

Regexp#===

Slide 14

Slide 14 text

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{R...} === 'Ruby' # => true case 'Ruby' when %r{P...} then 'Starts with P' when %r{R...} then 'Starts with R' end # => 'Starts with R'

Slide 15

Slide 15 text

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 Correct Complete Clear Compact & & &

Slide 16

Slide 16 text

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 Correct Complete Clear Compact & & &

Slide 17

Slide 17 text

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/confreaks' => {type: :channel, name: 'confreaks'}

Slide 18

Slide 18 text

case text when %r{youtube\.com/watch\?v=[\w-]{11}} {type: :video} when %r{youtube\.com/[\w-]+} {type: :channel} else {type: :unknown} end Using Regexp#=== and case statement How can we capture and name these matches?

Slide 19

Slide 19 text

Regexp captures

Slide 20

Slide 20 text

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 (?) or (?'name') constructs. %r{\$(?\d+)\.(?\d+)}.match '$3.67' # => # $~['dollars'] # => "3" $~['cents'] # => "67"

Slide 21

Slide 21 text

case text when %r{youtube\.com/watch\?v=(?[\w-]{11})} {type: :video, id: $~['id']} when %r{youtube\.com/(?[\w-]+)} {type: :channel, name: $~['name']} else {type: :unknown} end Using Regexp captures Correct Complete Clear Compact Correct Complete Clear Compact & & &

Slide 22

Slide 22 text

MatchData#named_captures

Slide 23

Slide 23 text

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{\$(?\d+)\.(?\d+)}.match '$3.67' # => # $~.named_captures # => {"dollars" => "3", "cents"=> "67"}

Slide 24

Slide 24 text

case text # e.g. "youtube.com/confreaks" when %r{youtube\.com/(?[\w-]+)} $~.named_captures # => {name: 'confreaks'} Using MatchData#named_captures

Slide 25

Slide 25 text

case text # e.g. "youtube.com/confreaks" when %r{youtube\.com/(?[\w-]+)} $~.named_captures # => {name: 'confreaks'} $~.named_captures.merge type: :channel # => {id: 'confreaks', type: :channel} Using MatchData#named_captures

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Enumerable#find

Slide 28

Slide 28 text

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. (18..99).find {|i| i % 17 == 0} # => 34 (18..29).find {|i| i % 17 == 0} # => nil (18..29).find(-> {0}) {|i| i % 17 == 0} # => 0

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

break statement

Slide 31

Slide 31 text

# 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: (18..99).find {|i| i % 17 == 0} # => 34 (18..99).find {|i| break("Found #{i}!") if i % 17 == 0} # => "Found 34!" break statement

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

What’s in a YouTube URL?

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?[\w-]{11})}, %r{youtu\.be/(?[\w-]{11})}] CHANNEL_PATTERNS = [%r{youtube\.com/channel/(?UC[\w-]{22})}, %r{youtube\.com/(?[\w-]+)}] PLAYLIST_PATTERNS = [%r{youtube\.com/playlist/\?list=(?UC[\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 Using Enumerable#find and break

Slide 40

Slide 40 text

Enumerator.new

Slide 41

Slide 41 text

VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?[\w-]{11})}, %r{youtu\.be/(?[\w-]{11})}] CHANNEL_PATTERNS = [%r{youtube\.com/channel/(?UC[\w-]{22})}, %r{youtube\.com/(?[\w-]+)}] PLAYLIST_PATTERNS = [%r{youtube\.com/playlist/?\?list=(?UC[\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 Using Enumerable#find and break

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

The final result VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?[\w-]{11})}, %r{youtu\.be/(?[\w-]{11})}] CHANNEL_PATTERNS = [%r{youtube\.com/channel/(?UC[\w-]{22})}, %r{youtube\.com/(?[\w-]+)}] PLAYLIST_PATTERNS = [%r{youtube\.com/playlist/\?list=(?UC[\w-]+)}] patterns = Enumerator.new do |patterns| VIDEO_PATTERNS.each {|regex| patterns << [regex, :video]} PLAYLIST_PATTERNS.each {|regex| patterns << [regex, :playlist]} CHANNEL_PATTERNS.each {|regex| patterns << [regex, :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 Correct Complete Clear Compact & & &

Slide 45

Slide 45 text

VIDEO_PATTERNS = [%r{youtube\.com/watch\?v=(?[\w-]{11})}, %r{youtu\.be/(?[\w-]{11})}] CHANNEL_PATTERNS = [%r{youtube\.com/channel/(?UC[\w-]{22})}, %r{youtube\.com/(?[\w-]+)}] PLAYLIST_PATTERNS = [%r{youtube\.com/playlist/\?list=(?UC[\w-]+)}] patterns = Enumerator.new do |patterns| VIDEO_PATTERNS.each {|regex| patterns << [regex, :video]} PLAYLIST_PATTERNS.each {|regex| patterns << [regex, :playlist]} CHANNEL_PATTERNS.each {|regex| patterns << [regex, :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 %r Regexp captures \w quantifiers Enumerator.new break Enumerable#find \A…\Z Optional captures $~ named_captures String#match << The final result

Slide 46

Slide 46 text

Thanks! speakerdeck.com/claudiob