This is a stupid project to allow you to write Ruby code in Shopify Functions, it's just for fun.
ⓘ Note
This is just a thing I thought it would be funny to make, it has 0 real use cases, but the real treasure is the
friendsknowledge we made along the way.
I don't like many thing of Shopify Scripts, but one thing I love is that it allows an infinite level of customisation 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 make a function that allow us to write a Shopify Script, and run it?
With Ruby2JS is 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 called (with a fetch call) when the function configuration is saved.
$ curl -'https://lambdaid.lambda-url.eu-west-3.on.aws/' -H 'content-type: text/plain' -d 'puts "hi"'
console.log("hi")
Fantastic.
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
Now we just need to change the configuration page to allow us to write Ruby code, and then we'll translate it to JS when it is submitted.
We removed all the fields from the example and thanks to react-simple-code-editor and prismjs we 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={{
fontFamily: '"Fira code", "Fira Mono", monospace',
fontSize: 12,
}}
/>
Now we just make sure we translate the code before saving it in a metaobject.
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 and input to save the configuration:
mutation CreateAutomaticDiscount($discount: DiscountAutomaticAppInput!) {
discountCreate: discountAutomaticAppCreate(automaticAppDiscount: $discount) {
userErrors {
code
message
field
}
}
}
{
variables: {
discount: {
...
baseDiscount,
combinesWith
:
{
}
,
endsAt: new Date("2024-12-31T23:59:59Z"),
functionId
:
functionId,
startsAt
:
new Date("2022-12-31T23:59:59Z"),
title
:
"my-ruby-discount",
metafields
:
[
{
namespace: "$app:ruby-in-functions",
key: "function-configuration",
type: "json",
value: JSON.stringify({code: js_code}),
},
],
}
,
}
,
}
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 will apply some tweaks to make it work and run it inside an eval
.