Recently I was trying to find out a way of sharing some business logic between the client and the server. We have a reactive UI where results get calculated as you change values. These are complex formulas we rather not write in both Ruby and JavaScript so we don’t have to maintain them in two places.
The easiest solution would have been to make ajax calls to the server with the parameters get the results. But we wanted a UI that reacts immediately so needed to have the code loaded in the browser.
So this a solution I come up with, in summary:
- Formulas are stored as plain JavaScript
- Ruby Models load and use those formulas using V8
- The formulas are served to the front end as partials
Here is a rundown of what I did step by step. I will use a simple interest formula in this example.
Storing the formulas
I needed the formulas to be accessible as partials so I added them in the view folder:
In app/views/formulas/_interest.js
1 2 3 |
|
Using the formulas in my Ruby models
In order to run the JavaScript on the server you will need therubyracer gem, in Gemfile:
gem 'therubyracer'
In app/models/contract.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The JavaScript code is loaded inside the interest method and ran with the provided parameters. In this way the code is accessible as a plain ruby method and only needs to be tested in one place. In my test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Loading the formulas in the front-end
I couldn’t find a way of injecting the JS code into the assets pipeline, so instead I just loaded in a partial that gets loaded in the application layout.
In app/views/layouts/application.html.erb:
<%= render 'formulas/index' %>
In app/views/formulas/_index.html.erb
1 2 3 4 5 6 7 8 9 10 |
|
This piece of JavaScript creates a global object APP if not found, then an object formulas inside APP and finally adds my interest formula to APP.formulas.interest.
Using the formulas in the front end
Once the formulas are loaded as shown above they can be used as:
1
|
|
So this is a way of sharing code between the back and the front end. If you how how could be improved or of a cleaner way altogether I would love to hear about it, maybe some DSL that can be used in both places.