ruby / ruby.wasm

ruby.wasm is a collection of WebAssembly ports of the CRuby.
https://ruby.github.io/ruby.wasm/
MIT License
668 stars 52 forks source link

Undefine Object#send method to call JavaScript send method #509

Closed ledsun closed 3 weeks ago

ledsun commented 1 month ago

Background

The following ruby.wasm code will cause an error.

ws = JS.global[:WebSocket].new("ws://localhost:9292")
ws[:onopen] = -> (event) {
  ws.send("Hello")
}
Error: /bundle/gems/js-2.6.2/lib/js.rb:184:in `method_missing': undefined method `Hello' for an instance of JS::Object (NoMethodError)

This error is caused by the Object#send method being called instead of the send method of the JavaScript WebSocket object.

Currently, we can use the send method by using the call method as follows.

ws.call(:send, ["Hello"])

Goal

Enable to call the JavaScript send method with the following syntax:

ws.send("Hello, world! from Ruby")

Solution

1st approch

Undef Object#send method. But, this is too much change to enable short-hand methods.

2nd approch

Overirde Object#send method. Then, it checks whether the JavaScript object has a send method and changes its behavior.

kateinoigakukun commented 1 month ago

Given that method_missing API is just a shorthand of JS::Object#call, it might be too much to undefine such conventional methods. Could you collect some case studies on how other similar method_missing users (e.g. ActiveRecord, PyCall.rb, etc...) address this kind of name conflict?

ledsun commented 1 month ago

I have collected case studies.

In ActiveRecord, the send column is defined as a method using define_attribute_methods. In other words, the Object#send method is overridden.

In PyCall, if there is a name conflict with the send method, the Object#send method is given priority. To call the send method of a Python object, use PyColl.getattr as follows.

require 'pycall'
client = PyCall.import_module 'http.client'
conn = client.HTTPConnection.new('www.example.com')
PyCall.getattr(conn, 'send').("GET / HTTP/1.0\r\n\r\n".b)

I like the way Rails does it. So I defined the JS::Object#send method.

kateinoigakukun commented 1 month ago

Sorry for my late response. I have been thinking about this, how about inheriting BasicObject instead of Object? If we do that, we don't need to care about most of conventional methods (not only for send) except for a few like __send__. It slightly breaks API compatibility but shouldn't be a big deal considering its use cases.

ledsun commented 1 month ago

how about inheriting BasicObject instead of Object?

I think it's an interesting idea. I'll try to see how it's implemented.

ledsun commented 3 weeks ago

It looks like it can be successfully implemented by inheriting the BasicObject. I will create a new pull request with a new implementation.

I close this pull request.