Converting a rails routes output into a Postman Collection
In an effort to learn a little bit about Ruby and Rails, I decided a fun project would be to attempt generating a YAML file that aligns to the OpenAPI v3.0 specification that I could import into Postman and test routes without manually having to do things via the Rails console and/or the consuming frontend application.
As this relates to what could be important information, I am going to redact as much as possible.
For context - I am normally writing in JS, Python, Swift, Rust (if I don't need C) or Golang these days. I knew jack shit about Ruby coming into this (other than general basics).
tl;dr to run
To see the full project, head to the GitHub repo.
Aims + Outcomes
What I wanted to achieve:
- Understanding more about Rails in general.
- Understanding a number of Ruby Gems: namely
factory-bot(the latter two which I ran out of time for).
- Learning about file systems for the Ruby ecosystem - the most important aspect of any language.
- Learning how to write CLIs in Ruby and sussing out the gems.
- Just learning Ruby in general. I don't know huge amounts other than the usual suspects and reading docs when I need answers.
Initially I wanted to take the output of
rails routes and manipulate it from there, but for the sake of time I cut it back.
At first, I also wanted to resolve all the controller functions and generate a placeholder for the final important request body to Postman, but I soon realised this wouldn't be so feasible with the file structure and changing approach to code.
To get my head around Rails, I did the following:
- A one-over of the Rails API my team uses to see what does/doesn't make sense.
- Reading the Ruby and Rails docs on Dash (surprisingly a good idea).
- Checking out the Rails source code.
- Bought and read "Active Rails" (skimmed over after the first few chapters on migrations).
Converting it down to a more consumable format
I made a decision on what I considered the important parts of the application. I took the initial output that I would get from
rails routes and did some manual cleansing.
The initial output looked like so (what a mess with the whitespace, I know):
While I could have written the code to cleanse this back, I decided to do some manual culling.
First, I echo the routes into a file to work with ie
bin/rails routes > routes.txt.
Next, I identified that
URI Pattern and
Controller#Action as the pieces of information I wanted, so I ended up using some regex cleaning in VSCode (use whatever) using
.+?(?=GET|DELETE|POST|PUT|PATCH) to replace with
'' to trim the start and manually cut out the few lines left that were not aligned to the format I wanted (first line, three others related to GraphQL that I could manually add once to Postman anyways).
Writing the base CLI tool
I did what I always do here and went to
awesome GitHub repo for the language and looked at the suggested CLI libraries. I just went with
Slop as it seemed super basic. There were a few good options.
I generated the
main.rb file as the app entrypoint and legit only use
Slop to parse for
--file for a path the
The workflow with the Result monads
After that, the application itself is basically to call a file helper to parse the file and get it into an array of
RouteInfo structs and then a second phase to take that array and mold is to the
The file helper part is quite straight forward. I rescued any errors and propagated the error to identify where the methods would fail.
The OpenAPI helper was a little more complex because a) I stopped being so bothered and b) I was splitting up structs into smaller pieces. There are some helper methods there that I would prefer to be abstracted elsewhere but I wanted them as lambdas and it wasn't so straight forward for this Ruby n00b. I used a
deep_merge Gem and I didn't really think through a nicer way to start immutable. This might not be a good decision.
Where possible, I used the
dry-struct libraries to learn how the worked and help with valiation of the classes. Unsure if I used these effectively, and I basically converted using the
attributes method straight away but it was nice for catching type errors (which I really appreciated).
There are some comments prefixed with
# ! to indicate things that I was a bit unhappy with the decision, but are likely just the result of lack of real-world Ruby experience.
I had an example OpenAPI v3 specification YAML file that I tried before writing all of this see how that worked when importing to Postman:
This set as my basis for the structs and design of hashes and how I would write. The
success lambda when folding at the end of the
main.rb OpenAPI class monad did the conversion of symbols to strings and writing of the YAML file. The resulting file looked like so:
The above is omitting about 7000 lines. All 900+ route were successfully mapped. The initial home route did end up an empty string, so there is always work to do.
Because there was no easy way to find the models and copy their properties over, any REST verb other than
GET just received a default JSON body of
id. This means any call needs to be manually updated when using, but this is actually just so much better than nothing anyway.
Importing to Postman
This part was the delightful part.
Once imported, you can
Generate Collection on Postman and it would bring in all 900 requests.
After they were in, there was still two smaller pieces of work to be done. I had to update the default variables in Postman and set
Setting the baseUrl value for all requests
As for the requests made, I ended up jumping into the UI and watching the request information sent to the
API when creating a team. I copied that info, found the correct request (Postman laid out the folders so nice!) and then BAM successfully managed to create a team from Postman.
Updating the create team with the request body from Chrome DevTools
This is a great way to do some validation from outside of the Rails ecosystem while waiting on the UI to catch up.
While I didn't get to explore RSpec and the others, I considered this as mission success!
- Can you go point-free for the
dry-rbgems? Is using the
do notationthe preferred way? Can I compose without having to grab the return values from genetors and forwarding on the returned monad?
- I didn't look too deep to see if Ruby supported reducers until right at the end, but I should have. That may have answered my question about if I used the identity monad. Still stands whether the
do notationis the preferred way?
- In general, can you define lambda funcs as Ruby class methods?
- Still not fully around the diff between procs and lambdas other than procs returning from the parent scope on the return keyword (even that might be incorrect).
- Are there issues when changing the database engines behind the DBMS you nominate for Rails? Thinking about the
ActiveModelsupport for defining schemas.
- Some of the
Resultmonads I saw being used in the work controllers don't use an
eithermethod to fold the monad - wondering if there is a reason for raising HTTP errors instead of propagating a
Failuremonad and folding?
- How does
hash.transform_keys(&:to_s)work? I saw it as an option for converting hash keys but had no success, so I borrowed a dumb hack to extend the
Hashclass from Stack Overflow that I hate.
- What's the best way to handle validation for large structs? Just flatten them? I had some deep nested holes growing.
- I don't see many
rescueblocks - is this an anti-pattern? A lot of
unlessetc so maybe all errors are handled inline?
- What's the deal with splats? Tried doing one for a Struct constructor which didn't work but the following did in
irb(maybe my misunderstanding between hashes and constructors?):
The post-mortem of this is that I at least got my feet wet with a lot of the different parts of Ruby and the gems we use. I am definitely up for any feedback anyone has on what is written as I am sure there were some anti-patterns here.
To see the full project, head to the GitHub repo.
1,200+ PEOPLE ALREADY JOINED ❤️️
Get fresh posts + news direct to your inbox.
No spam. We only send you relevant content.