Wait, really? Yes, really.
I recently needed to solve an odd problem that required this kind of weird solution. Unfortunately, the solution fell short of the exact need for my client; however, the results of my findings are definitely worth sharing — especially given that my PopularSearchEngine™ deep dive yielded very little to help me figure all of this out.
If you follow me on Twitter, you may have seen my cryptic tweet after spending hours trying to get this to work:
How's your day?
The framework the rest of this guide follows stems directly from the requirements that I was presented with by my client. In order for everyone to have the same frame of reference, this is a brief background on the codebase and the ask.
The Codebase Setup
The backend built with Ruby on Rails and the frontend is an Angular app. The Frontend lives in its own folder inside the root of the Rails codebase. When deployed, The frontend is compiled with Webpack into static assets and loaded into Rails via
<script> tags in the Rails application layout view file.
There exists a set of data that was in one format and the goal was create a script that migrated the data from that format into the new format and save it. The catch? The data was stored in the Rails database and the code to transform it and generate the format lived in the Angular code. The question of data flow was uncertain: The frontend didn’t know how to save the data and the backend had no idea how to do the transformation correctly.
They wanted a modular and reusable way to run data migration scripts that required Frontend code, and they wanted to keep it all in a rake task, if possible.
Everything can be done with Ruby + a Frontend app; that said, since the code I was working in was expected to work inside the context of Rails, this guide will also be done within Rails.
This how-to assumes the following:
- You have Rails app Backend with at least on Model.
- You have a modern Frontend app.
- You use Webpack to compile your Frontend assets.
Creating and Running your Script
Rails has two primary ways to run snippets of code server side, in-context of Rails: A rake task or a Ruby script with Rails
runner. I’ve found that I prefer to use the latter option for most things, unless I want to be able to reuse the code inside other Rake tasks, and for a migration of data, having a separate script felt appropriate. That said, either option will work. For the purposes of this guide, we will use the script + Rails runner pattern.
Lets create the shell of our script:
And then run it to make sure everything is wired correctly:
Verify your JS environment
If you see the expected results from these examples, you’re good to go!
Linking Ruby functions into your JS environment
Define the Ruby function
Lets create a function that fetches a random user from the database when called.
Attach it to the JS Runtime
Now that we have our proc function, we can attach it to the JS runtime:
This attaches our
fetch_random_user ruby proc to our JS runtime at the definition
Ruby is optional and can be named anything you would like (or omitted entirely). I prefer to prefix my Ruby functions in the JS environment with it, or something similar, so I know that it is defined in Ruby at usage, rather than somewhere in my JS.
And execute on it
Using your Frontend app assets
An Example App
Lets say we have a small application that does the following:
- When given a User, the program says hello to the user, like we did in the previous example, and transforms the user’s data into a special json structure for a new user Profile.
- When a user is not provided, the program should exit with a generic ‘Hello world!’ and a message stating that it cannot generate a profile.
- The format for the Profile json should look like the following:
Lets create two modules, one for saying hello:
and one for holding the transform logic:
Create main application
We can import those modules directly into our primary application file:
And then create a few functions to handle our main logic:
Bundle Your Frontend
For us to use our new and lovely frontend application, we need to create a Webpack bundle that we can import into our runtime. Here is a barebones example of a custom webpack configuration file:
 Targetting node is important so the bundle is self-contained and can be included in our Ruby script!
umd stands for Universal Module Definition, and is necessary to load into our CommonJS environment.
Once you have your configuration setup, you can create your target library with:
This will create a
main.js file available in the rails
public/assets folder. It will include all of the imported bundles
Load your bundle into the JS Environment
Using the JS environment you created in your Ruby script, you can now load your newly compiled bundle library into your runtime:
Use your bundled modules
Now you can use your bundled modules using CommonJS && the Node
Putting it all together
Next Steps: Accepting args & saving data
At this point, we’ve shown how you can call Ruby functions from a JS runtime context and how you can load frontend module assets into a CommonJS environment. You can take it a step further have your Ruby functions accept arguments and save data.
An example of how that might look:
Notes && Caveats
Like all solutions, your mileage may vary. A few known caveats based on my testing:
- This method does not work if you need access to the window or dom. This ultimately was why this solution did not work for my client.
- Why not ExecJS or TheRubyRacer? I ruled out both relatively early on. You can use ExecJS for JS runtime without the need for bundled JS assets. TheRubyRacer’s V8 runtime is old and misses a lot of the niceties we’ve come to know and love and rely on (Like promises!). You can use it if you don’t need anything beyond V8 version 3. If you are able to use TRR, you can also use the default CommonJS gem (and not the one specifically ported for MiniRacer!
Good luck and have fun!