Skip to content

Commit 7c08214

Browse files
authored
[#14] engine: return Function object instead of lambda (#15)
When the result of the JavaScript call is a function (e.g. `compile`), create a new `Function` class instance instead of using a lambda. This fixes an issue where the object finalizer was causing the Ruby VM to hold on to the engine's instance. Fixes #14
1 parent e970c39 commit 7c08214

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ RSpec/MultipleMemoizedHelpers:
154154
RSpec/NestedGroups:
155155
Max: 8
156156

157+
Style/DocumentDynamicEvalDefinition:
158+
Enabled: false
159+
157160
Style/BlockDelimiters:
158161
EnforcedStyle: semantic
159162
AllowBracesOnProceduralOneLiners: true

lib/handlebars/engine.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require "json"
55
require "mini_racer"
66
require "securerandom"
7+
require_relative "engine/function"
78
require_relative "engine/version"
89

910
module Handlebars
@@ -199,9 +200,7 @@ def call_via_eval(name, args, assign: false)
199200
result = evaluate(code)
200201

201202
if var && result.is_a?(MiniRacer::JavaScriptFunction)
202-
result = ->(*a) { @context.call(var, *a) }
203-
finalizer = ->(*) { evaluate("delete #{var}") }
204-
ObjectSpace.define_finalizer(result, finalizer)
203+
result = Function.new(@context, var)
205204
end
206205

207206
result

lib/handlebars/engine/function.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
module Handlebars
4+
class Engine
5+
# A proxy for a JavaScript function defined in the context.
6+
class Function
7+
def initialize(context, name)
8+
@context = context
9+
@name = name
10+
ObjectSpace.define_finalizer(self, self.class.finalizer(context, name))
11+
end
12+
13+
def call(*args)
14+
@context.call(@name, *args)
15+
end
16+
17+
def self.finalizer(context, name)
18+
proc {
19+
context.eval("delete #{name}")
20+
}
21+
end
22+
end
23+
end
24+
end

0 commit comments

Comments
 (0)