Skip to content

Commit 253e5d6

Browse files
committed
JS::Object inherits BasicObject
To enable calling the JavaScript send method.
1 parent d00ee2d commit 253e5d6

File tree

3 files changed

+56
-16
lines changed

3 files changed

+56
-16
lines changed

packages/gems/js/ext/js/js-core.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ void Init_js() {
580580
rb_define_module_function(rb_mJS, "global", _rb_js_global_this, 0);
581581

582582
i_to_js = rb_intern("to_js");
583-
rb_cJS_Object = rb_define_class_under(rb_mJS, "Object", rb_cObject);
583+
rb_cJS_Object = rb_define_class_under(rb_mJS, "Object", rb_cBasicObject);
584584
VALUE rb_cJS_singleton = rb_singleton_class(rb_cJS_Object);
585585
rb_define_alloc_func(rb_cJS_Object, jsvalue_s_allocate);
586586
rb_define_method(rb_cJS_Object, "[]", _rb_js_obj_aref, 1);

packages/gems/js/lib/js.rb

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,23 @@ def self.__async(future, &block)
125125
end
126126
end
127127

128-
class JS::Object
128+
# Inherit BasicObject to prevent define coventional menthods. #Override the `Object#send` to give priority to `send` method of JavaScript.
129+
#
130+
# This is to make it easier to use JavaScript Objects with `send` method such as `WebSocket` and `XMLHttpRequest`.
131+
# The JavaScript method call short-hand in `JS::Object` is implemented using `method_missing`.
132+
# If JS::Object inherits from Object, the `send` method defined in Ruby will take precedence over the JavaScript `send` method.
133+
# If you want to call the JavaScript `send` method, you must use the `call` method as follows:
134+
#
135+
# ws = JS.global[:WebSocket].new("ws://example.com")
136+
# ws.call(:send, ["Hello, world! from Ruby"])
137+
#
138+
# This inheritation allows you to call the JavaScript `send` method with the following syntax:
139+
#
140+
# ws.send("Hello, world! from Ruby")
141+
142+
143+
144+
class JS::Object < BasicObject
129145
# Create a JavaScript object with the new method
130146
#
131147
# The below examples show typical usage in Ruby
@@ -141,16 +157,16 @@ class JS::Object
141157
#
142158
def new(*args, &block)
143159
args = args + [block] if block
144-
JS.global[:Reflect].construct(self, args.to_js)
160+
::JS.global[:Reflect].construct(self, args.to_js)
145161
end
146162

147163
# Converts +self+ to an Array:
148164
#
149165
# JS.eval("return [1, 2, 3]").to_a.map(&:to_i) # => [1, 2, 3]
150166
# JS.global[:document].querySelectorAll("p").to_a # => [[object HTMLParagraphElement], ...
151167
def to_a
152-
as_array = JS.global[:Array].from(self)
153-
Array.new(as_array[:length].to_i) { as_array[_1] }
168+
as_array = ::JS.global[:Array].from(self)
169+
::Array.new(as_array[:length].to_i) { as_array[_1] }
154170
end
155171

156172
# Provide a shorthand form for JS::Object#call
@@ -176,7 +192,7 @@ def method_missing(sym, *args, &block)
176192
result = invoke_js_method(sym_str[0..-2].to_sym, *args, &block)
177193
# Type coerce the result to boolean type
178194
# to match the true/false determination in JavaScript's if statement.
179-
return JS.global.Boolean(result) == JS::True
195+
return ::JS.global.Boolean(result) == ::JS::True
180196
end
181197

182198
invoke_js_method(sym, *args, &block)
@@ -186,7 +202,6 @@ def method_missing(sym, *args, &block)
186202
#
187203
# See JS::Object#method_missing for details.
188204
def respond_to_missing?(sym, include_private)
189-
return true if super
190205
sym_str = sym.to_s
191206
sym = sym_str[0..-2].to_sym if sym_str.end_with?("?")
192207
self[sym].typeof == "function"
@@ -203,7 +218,7 @@ def respond_to_missing?(sym, include_private)
203218
# end.await # => 42
204219
def apply(*args, &block)
205220
args = args + [block] if block
206-
JS.global[:Reflect].call(:apply, self, JS::Undefined, args.to_js)
221+
::JS.global[:Reflect].call(:apply, self, ::JS::Undefined, args.to_js)
207222
end
208223

209224
# Await a JavaScript Promise like `await` in JavaScript.
@@ -233,8 +248,13 @@ def apply(*args, &block)
233248
# JS.eval("return new Promise((ok, err) => err(new Error())").await # => raises JS::Error
234249
def await
235250
# Promise.resolve wrap a value or flattens promise-like object and its thenable chain
236-
promise = JS.global[:Promise].resolve(self)
237-
JS.promise_scheduler.await(promise)
251+
promise = ::JS.global[:Promise].resolve(self)
252+
::JS.promise_scheduler.await(promise)
253+
end
254+
255+
# https://github.com/rails/rails/blob/5c0b7496ab32c25c80f6d1bdc8b32ec6f75ce1e4/activerecord/lib/active_record/promise.rb#L40-L42
256+
[:nil?, :is_a?].each do |method|
257+
define_method(method, ::Object.instance_method(method))
238258
end
239259

240260
private
@@ -246,12 +266,12 @@ def invoke_js_method(sym, *args, &block)
246266
return self.call(sym, *args, &block) if self[sym].typeof == "function"
247267

248268
# Check to see if a non-functional property exists.
249-
if JS.global[:Reflect].call(:has, self, sym.to_s) == JS::True
250-
raise TypeError,
269+
if ::JS.global[:Reflect].call(:has, self, sym.to_s) == ::JS::True
270+
raise ::TypeError,
251271
"`#{sym}` is not a function. To reference a property, use `[:#{sym}]` syntax instead."
252272
end
253273

254-
raise NoMethodError,
274+
raise ::NoMethodError,
255275
"undefined method `#{sym}' for an instance of JS::Object"
256276
end
257277
end

packages/npm-packages/ruby-wasm-wasi/test/unit/test_object.rb

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,29 @@ def test_respond_to_missing?
368368
object = JS.eval(<<~JS)
369369
return { foo() { return true; } };
370370
JS
371-
assert_true object.respond_to?(:foo)
372-
assert_true object.respond_to?(:new)
373-
assert_false object.respond_to?(:bar)
371+
assert_true object.__send__(:respond_to_missing?, :foo, false)
372+
assert_false object.__send__(:respond_to_missing?, :bar, false)
373+
374+
# new is method of JS::Object
375+
assert_false object.__send__(:respond_to_missing?, :new, false)
376+
377+
# send is not implemented in JS::Object,
378+
# because JS::Object is a subclass of JS::BaseObject
379+
assert_false object.__send__(:respond_to_missing?, :send, false)
380+
end
381+
382+
def test_send_method_for_javascript_object_with_send_method
383+
object = JS.eval(<<~JS)
384+
return { send(message) { return message; } };
385+
JS
386+
assert_equal "hello", object.send('hello').to_s
387+
end
388+
389+
def test_send_method_for_javascript_object_without_send_method
390+
object = JS.eval(<<~JS)
391+
return { write(message) { return message; } };
392+
JS
393+
assert_raise(NoMethodError) { object.send('hello') }
374394
end
375395

376396
def test_member_get

0 commit comments

Comments
 (0)