At one point a while back, I had a config file outside a rails app and what I wanted was something like this:
Given this mappping definition
/order/:meal/:cheese
How can I turn these strings into parsed hashes?/order/hotdog/cheddar -> {meal:'hotdog', cheese:'cheddar'}
I knew that something in Rails was doing this. I just didn’t know what. I also didn’t know what assumptions or abstraction level it was working at.
Journey into Journey
The gem that handles parsing the routes file and creating a tree is journey.
Journey used to be (years ago) a separate gem but is not integrated into
action_dispatch
which itself is a part of actionpack
. So to install it you
need to gem install actionpack
(or use bundler) but to include it in your
program you need to require 'action_dispatch/journey'
. If you have
any rails 4+ gem installed on your system, you don’t need to install
anything. Action pack comes with rails.
require 'action_dispatch/journey'
# reorganize pattern matches into hashes
def hashify_match matches
h = {}
matches.names.each_with_index do |key, i|
h[key.to_sym] = matches.captures[i]
end
h
end
pattern = ActionDispatch::Journey::Path::Pattern.from_string '/order/(:meal(/:cheese))'
matches = pattern.match '/order/hamburger/american'
puts hashify_match matches
matches = pattern.match '/order/hotdog/cheddar'
puts hashify_match matches
# {:meal=>"hamburger", :cheese=>"american"}
# {:meal=>"hotdog", :cheese=>"cheddar"}
We have to have hashify_match
reorganize our objects because this is what
pattern.match
returns:
irb(main):001:0> matches = pattern.match '/order/hamburger/american'
=> #<ActionDispatch::Journey::Path::Pattern::MatchData:0x007f9d4d527aa0
@match=#<MatchData "/order/hamburger/american" 1:"hamburger" 2:"american">,
@names=["meal", "cheese"],
@offsets=[0, 0, 0]>
So we have to turn these ordered matches into a hash.
irb(main):001:0> matches.names
=> ["meal", "cheese"]
irb(main):002:0> matches.captures
=> ["hamburger", "american"]
We could also zip the results together but we wouldn’t have symbolized keys.
irb(main):001:0> Hash[matches.names.zip(matches.captures)]
=> {"meal"=>"hamburger", "cheese"=>"american"}
You could symbolize them easily within a rails app or by including active support.
require 'active_support'
require 'active_support/core_ext'
Hash[matches.names.zip(matches.captures)].symbolize_keys