Ruby in Shopify Functions
Introduction
Do you have plenty of Shopify Scripts and want to migrate to Shopify Functions? You don’t want to rewrite them in JavaScript? Look no further!
We will use JavaScript eval
and Ruby2JS
so that we can write a Ruby script to handle discounts directly from the app!
At the end, we also answer the question: is it possible to compile Ruby to WASM to use it in a Function?
Ruby as a First-Class… Configuration
ⓘ Note
This is just something I thought would be funny to create. It has 0 real use cases, it breaks all the possible programming best-practices, but the real treasure is the
friendsknowledge we made along the way.
Rationale
I don’t like many things about Shopify Scripts, but one thing I love is that it allows an infinite level of customization inside a very simple app.
Shopify Functions are great, but the deployment, even with simplified deployment, is cumbersome and requires lots of steps.
What if we could create a function that allows us to write a Shopify Script and run it?
Translating Ruby into JavaScript
With Ruby2JS, it’s super simple. We will write an AWS Lambda that just does that.
require 'ruby2js'
def handler(event:, context:)
Ruby2JS.convert(event["body"], preset: true)
end
This is fetched when the function configuration is saved.
Testing
$ curl -'https://lambdaid.lambda-url.eu-west-3.on.aws/' -H 'content-type: text/plain' -d 'puts "hi"'
console.log("hi")
Fantastic.
Creating the extension
For the function we’ll just follow this tutorial.
$ npm init @shopify/app@latest -- --template https://github.com/Shopify/function-examples/sample-apps/discounts
$ cd ruby-in-functions
$ yarn deploy
$ yarn dev
Configuration Page
We change the configuration page to allow us to write Ruby code, and then we’ll translate it to JS when it is submitted.
With the help of react-simple-code-editor and prismjs, we have added a simple code editor with Ruby syntax highlighting.
<Editor
value={script.value}
onValueChange={script.onChange}
highlight={code => highlight(code, languages.ruby, 'ruby')}
padding={10}
style=
/>
Now we just need to ensure that we translate the code before saving it in the function configuration.
Saving the Configuration
We are saving the script in the configuration, and we are translating it to JS before saving it.
This is the Ruby code we will use:
# Use this script to offer a percentage discount that increases according to the total value of the items in their cart.
# For example, offer customers 10% off if they spend $30 or more, 15% off if they spend $50 or more.
SPENDING_THRESHOLDS = [
{
threshold: 30,
discount_type: :percent,
discount_amount: 10,
discount_message: 'Spend $30 and get 10% off!',
},
{
threshold: 50,
discount_type: :percent,
discount_amount: 15,
discount_message: 'Spend $50 and get 15% off!',
}
]
applicable_tier = SPENDING_THRESHOLDS.sort!{|a, b| a.discount_amount < b.discount_amount}.find { |tier| cart.subtotal_price >= (Money.new(cents: 100) * tier[:threshold]) }
Input.cart.line_items.each do |line_item|
next if line_item.variant.product.gift_card?
line_item.change_line_price(line_item.line_price * applicable_tier[:discount_amount], message: applicable_tier[:discount_message])
end
It’s a simple tiered discount based on the Shopify examples
This is the call to save the configuration:
mutation CreateAutomaticDiscount($discount: DiscountAutomaticAppInput!) {
discountCreate: discountAutomaticAppCreate(automaticAppDiscount: $discount) {
userErrors {
code
message
field
}
}
}
Tweaking Javascript
The function is automatically translated into this javascript code
const SPENDING_THRESHOLDS = [
{
threshold: 30,
discount_type: "percent",
discount_amount: 10,
discount_message: "Spend $30 and get 10% off!"
},
{
threshold: 50,
discount_type: "percent",
discount_amount: 15,
discount_message: "Spend $50 and get 15% off!"
}
];
let applicable_tier = SPENDING_THRESHOLDS.sort((a, b) => (
a.discount_amount < b.discount_amount
)).find(tier => (
Input.cart.subtotal_price >= (new Money({cents: 100}) * tier.threshold)
));
for (let line_item of Input.cart.line_items) {
if (line_item.variant.product.gift_card) continue;
line_item.change_line_price(
line_item.line_price * applicable_tier.discount_amount,
{message: applicable_tier.discount_message}
)
}
We apply some tweaks to make it work and run it inside an eval
. I won’t go into details, but you can check the
code here.
eval(configuration.code); // isn't this beautiful?
And this is the result!
Conclusion
Obviously, not all the Shopify Script API is translatable into Functions API , but we just wanted to have som fun, and we did.
Can We Write a Shopify Function in Ruby?
Considering that it is possible to compile Ruby to WebAssembly (Wasm), and that we can run Wasm inside a function, the answer is… no.
When ruby.wasm
compiles the Ruby code, it includes the entire Ruby interpreter.
This results in creating a file that is on the order of megabytes.
Considering that the size limit for functions is 256KB, it is not possible to use Ruby in a function.