From cd1218be18d8918c0262c6a8f368502fc4516f44 Mon Sep 17 00:00:00 2001 From: Zach Gianos Date: Mon, 11 Aug 2025 21:09:05 -0700 Subject: [PATCH] [#25] engine: fix serialization of helper options functions to ruby This fixes an issue where `mini_racer` does not support serializing javascript functions nested in objects, such as in the options hash of helper function. To avoid this issue, redefine any functions with a generic `"function"` string. TODO: In the future, we can build a method to call these functions. --- lib/handlebars/engine.rb | 4 ++-- lib/handlebars/engine/init.js | 17 +++++++++++++---- spec/handlebars/engine_spec.rb | 12 ++++++++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/handlebars/engine.rb b/lib/handlebars/engine.rb index 62aad78..209e291 100644 --- a/lib/handlebars/engine.rb +++ b/lib/handlebars/engine.rb @@ -84,9 +84,9 @@ def register_helper(name = nil, function = nil, **helpers, &block) case f when Proc attach(n, &f) - evaluate("registerHelper('#{n}', #{n})") + evaluate("registerRbHelper('#{n}', #{n})") when String, Symbol - evaluate("Handlebars.registerHelper('#{n}', #{f})") + evaluate("registerJsHelper('#{n}', #{f})") end end end diff --git a/lib/handlebars/engine/init.js b/lib/handlebars/engine/init.js index 22d1d99..0572c69 100644 --- a/lib/handlebars/engine/init.js +++ b/lib/handlebars/engine/init.js @@ -16,14 +16,23 @@ var template = (spec) => { var registerPartial = Handlebars.registerPartial.bind(Handlebars); var unregisterPartial = Handlebars.unregisterPartial.bind(Handlebars); -var registerHelper = (...args) => { - const fn = args[args.length - 1]; +var registerJsHelper = Handlebars.registerHelper.bind(Handlebars); + +var registerRbHelper = (name, fn) => { function wrapper(...args) { + // Ruby cannot access the `this` context, so pass it as the first argument. args.unshift(this); + const { ...options } = args[args.length-1]; + Object.entries(options).forEach(([key, value]) => { + if (typeof value === "function") { + // functions are cannot be passed back to Ruby + options[key] = "function"; + } + }); + args[args.length-1] = options return fn(...args); } - args[args.length - 1] = wrapper; - return Handlebars.registerHelper(...args); + return registerJsHelper(name, wrapper); }; var unregisterHelper = Handlebars.unregisterHelper.bind(Handlebars); diff --git a/spec/handlebars/engine_spec.rb b/spec/handlebars/engine_spec.rb index c91fb5d..c0c7370 100644 --- a/spec/handlebars/engine_spec.rb +++ b/spec/handlebars/engine_spec.rb @@ -255,7 +255,7 @@ describe "the options" do it "includes the main block function" do opts = include( - "fn" => kind_of(MiniRacer::JavaScriptFunction), + "fn" => "function", # kind_of(MiniRacer::JavaScriptFunction), ) args = [anything, any_args, opts] render @@ -264,7 +264,7 @@ it "includes the else block function" do opts = include( - "inverse" => kind_of(MiniRacer::JavaScriptFunction), + "inverse" => "function", # kind_of(MiniRacer::JavaScriptFunction), ) args = [anything, any_args, opts] render @@ -279,6 +279,14 @@ <<~JS function (...args) { args.unshift(this); + const { ...options } = args[args.length-1]; + Object.entries(options).forEach(([key, value]) => { + if (typeof value === "function") { + // functions are cannot be passed back to Ruby + options[key] = "function"; + } + }); + args[args.length-1] = options return tester(...args); } JS