1
0
Fork 0
mirror of https://github.com/rubyjs/therubyrhino synced 2023-03-27 23:21:34 -04:00

RubyObject/RubyFunction review + specs for better integration when some ruby needs to enter Rhino's JS world ...

This commit is contained in:
kares 2011-12-08 17:28:23 +01:00
parent 0941f932ed
commit 11f4df4be9
9 changed files with 275 additions and 69 deletions

View file

@ -7,8 +7,22 @@ module Rhino
@callable = callable
end
def call(cxt, scope, this, args)
To.javascript @callable.call(*Array(args).map {|a| To.ruby(a)})
def unwrap
@callable
end
# override Object BaseFunction#call(Context context, Scriptable scope,
# Scriptable thisObj, Object[] args)
def call(context, scope, this, args)
rb_args = To.args_to_ruby(args.to_a)
begin
result = @callable.call(*rb_args)
rescue => e
# ... correct wrapping thus it's try { } catch (e) works in JS :
raise JS::WrappedException.new(org.jruby.exceptions.RaiseException.new(e))
end
To.to_javascript(result, scope)
end
end
end

View file

@ -8,64 +8,89 @@ module Rhino
@ruby = object
end
# abstract Object Wrapper#unwrap();
def unwrap
@ruby
end
def getClassName()
# abstract String Scriptable#getClassName();
def getClassName
@ruby.class.name
end
def getPrototype()
Prototype::Generic
def toString
"[ruby #{getClassName}]" # [object User]
end
def put(key, start, value)
if @ruby.respond_to?("#{key}=")
@ruby.send("#{key}=", To.ruby(value))
value
else
super
end
end
def getIds()
@ruby.public_methods(false).map {|m| m.gsub(/(.)_(.)/) {java.lang.String.new("#{$1}#{$2.upcase}")}}.to_java
end
def to_s
"[Native #{@ruby.class.name}]"
end
alias_method :prototype, :getPrototype
class Prototype < JS::ScriptableObject
def get(name, start)
robject = To.ruby(start)
if name == "toString"
return RubyFunction.new(lambda { "[Ruby #{robject.class.name}]"})
end
rb_name = name.gsub(/([a-z])([A-Z])/) {"#{$1}_#{$2.downcase}"}.to_sym
if (robject.public_methods(false).collect(&:to_sym).include?(rb_name))
method = robject.method(rb_name)
if method.arity == 0
To.javascript(method.call)
else
RubyFunction.new(method)
end
else
super(name, start)
# override Object Scriptable#get(String name, Scriptable start);
# override Object Scriptable#get(int index, Scriptable start);
def get(name, start)
if name.is_a?(String)
# NOTE: preferrably when using a ruby object in JS methods should
# be used but instance variables will work as well but if there's
# a attr reader it is given a preference e.g. :
#
# class Foo
# attr_reader :bar2
# def initialize
# @bar1 = 'bar1'
# @bar2 = 'bar2'
# end
# end
#
# fooObj.bar1; // 'bar1'
# fooObj.bar2; // function
# fooObj.bar2(); // 'bar2'
#
if @ruby.respond_to?(name)
return RubyFunction.new(@ruby.method(name))
elsif @ruby.instance_variables.include?(var_name = "@#{name}")
var_value = @ruby.instance_variable_get(var_name)
return Rhino::To.to_javascript(var_value, self)
end
end
def has(name, start)
rb_name = name.gsub(/([a-z])([A-Z])/) {"#{$1}_#{$2.downcase}"}.to_sym
To.ruby(start).public_methods(false).collect(&:to_sym).include?(rb_name) ? true : super(name,start)
end
Generic = new
super
end
# override boolean Scriptable#has(String name, Scriptable start);
# override boolean Scriptable#has(int index, Scriptable start);
def has(name, start)
if name.is_a?(String)
if @ruby.respond_to?(name) ||
@ruby.instance_variables.include?("@#{name}")
return true
end
end
super
end
# override void Scriptable#put(String name, Scriptable start, Object value);
# override void Scriptable#put(int index, Scriptable start, Object value);
def put(name, start, value)
if name.is_a?(String)
if @ruby.respond_to?(set_name = "#{name}=")
return @ruby.send(set_name, Rhino::To.to_ruby(value))
end
end
super
end
# override boolean Scriptable#hasInstance(Scriptable instance);
def hasInstance(instance)
super
end
# override Object[] Scriptable#getIds();
def getIds
ids = @ruby.instance_variables.map { |ivar| ivar[1..-1].to_java }
@ruby.public_methods(false).each do |name|
name = name[0...-1] if name[-1, 1] == '=' # 'foo=' ... 'foo'
name = name.to_java
ids << name unless ids.include?(name)
end
super.each { |id| ids.unshift(id) }
ids.to_java
end
end
end

View file

@ -29,6 +29,10 @@ module Rhino
end
def javascript(object, scope = nil); to_javascript(object, scope); end # alias
def args_to_ruby(args)
args.map { |arg| to_ruby(arg) }
end
def array_to_ruby(js_array)
js_array.length.times.map { |i| to_ruby( js_array.get(i, js_array) ) }
end

View file

@ -25,7 +25,7 @@ describe Rhino::Context do
it "allows you to scope the context to an object" do
class MyScope
def foo; proc { 'bar' }; end
def foo; 'bar'; end
end
Rhino::Context.open(:with => MyScope.new) do |ctx|
ctx.eval("foo()").should == 'bar'

View file

@ -0,0 +1,40 @@
require File.expand_path('../spec_helper', File.dirname(__FILE__))
describe Rhino::RubyFunction do
it "create and unwrap ruby function" do
rb_function = Rhino::RubyFunction.new method = Object.new.method(:to_s)
rb_function.unwrap.should be(method)
end
it "call a ruby function" do
rb_function = Rhino::RubyFunction.new method = 'foo'.method(:to_s)
context = nil; scope = nil; this = nil; args = nil
rb_function.call(context, scope, this, args).should == 'foo'
end
it "args get converted before delegating a ruby function call" do
klass = Class.new(Object) do
def foo(array)
array.all? { |elem| elem.is_a?(String) }
end
end
rb_function = Rhino::RubyFunction.new method = klass.new.method(:foo)
context = nil; scope = nil; this = nil
args = [ '1'.to_java, java.lang.String.new('2') ].to_java
args = [ Rhino::JS::NativeArray.new(args) ].to_java
rb_function.call(context, scope, this, args).should be(true)
end
it "returned value gets converted to javascript" do
klass = Class.new(Object) do
def foo
[ 42 ]
end
end
rb_function = Rhino::RubyFunction.new method = klass.new.method(:foo)
context = nil; scope = nil; this = nil; args = [].to_java
rb_function.call(context, scope, this, args).should be_a(Rhino::JS::NativeArray)
end
end

View file

@ -0,0 +1,123 @@
require File.expand_path('../spec_helper', File.dirname(__FILE__))
describe Rhino::RubyObject do
it "unwraps a ruby object" do
rb_object = Rhino::RubyObject.new object = Object.new
rb_object.unwrap.should be(object)
end
class UII < Object
end
it "returns the ruby class name" do
rb_object = Rhino::RubyObject.new UII.new
rb_object.getClassName.should == UII.name
end
it "reports being a ruby object on toString" do
rb_object = Rhino::RubyObject.new UII.new
rb_object.toString.should == '[ruby UII]'
end
class UII
attr_reader :anAttr0
attr_accessor :the_attr_1
def initialize
@anAttr0 = nil
@the_attr_1 = 'attr_1'
@an_attr_2 = 'an_attr_2'
end
def theMethod0; nil; end
def a_method1; 1; end
def the_method_2; '2'; end
end
it "gets methods and instance variables" do
rb_object = Rhino::RubyObject.new UII.new
rb_object.get('anAttr0', nil).should be_a(Rhino::RubyFunction)
rb_object.get('the_attr_1', nil).should be_a(Rhino::RubyFunction)
rb_object.get('an_attr_2', nil).should == 'an_attr_2'
[ 'theMethod0', 'a_method1', 'the_method_2' ].each do |name|
rb_object.get(name, nil).should be_a(Rhino::RubyFunction)
end
rb_object.get('non-existent-method', nil).should be(Rhino::JS::Scriptable::NOT_FOUND)
end
it "has methods and instance variables" do
rb_object = Rhino::RubyObject.new UII.new
rb_object.has('anAttr0', nil).should be_true
rb_object.has('the_attr_1', nil).should be_true
rb_object.has('an_attr_2', nil).should be_true
[ 'theMethod0', 'a_method1', 'the_method_2' ].each do |name|
rb_object.has(name, nil).should be_true
end
rb_object.has('non-existent-method', nil).should be_false
end
it "puts using attr writer" do
start = mock('start')
start.expects(:put).never
rb_object = Rhino::RubyObject.new UII.new
rb_object.put('the_attr_1', start, 42)
rb_object.the_attr_1.should == 42
end
it "puts a non-existent attr (delegates to start)" do
start = mock('start')
start.expects(:put).once
rb_object = Rhino::RubyObject.new UII.new
rb_object.put('nonExistingAttr', start, 42)
end
it "getIds include ruby class methods" do
rb_object = Rhino::RubyObject.new UII.new
[ 'anAttr0', 'the_attr_1', 'an_attr_2' ].each do |attr|
rb_object.getIds.to_a.should include(attr)
end
[ 'theMethod0', 'a_method1', 'the_method_2' ].each do |method|
rb_object.getIds.to_a.should include(method)
end
end
it "getIds include ruby instance methods" do
rb_object = Rhino::RubyObject.new object = UII.new
object.instance_eval do
def foo; 'foo'; end
end
rb_object.getIds.to_a.should include('foo')
end
it "getIds include writers as attr names" do
rb_object = Rhino::RubyObject.new object = UII.new
rb_object.getIds.to_a.should include('the_attr_1')
rb_object.getIds.to_a.should_not include('the_attr_1=')
object.instance_eval do
def foo=(foo)
'foo'
end
end
rb_object.getIds.to_a.should include('foo')
rb_object.getIds.to_a.should_not include('foo=')
end
end

View file

@ -141,24 +141,22 @@ describe Rhino::To do
end
end
it "creates a prototype for the object based on its class" do
Class.new.tap do |klass|
klass.class_eval do
def foo(one, two)
"1: #{one}, 2: #{two}"
end
end
Rhino::To.javascript(klass.new).tap do |o|
o.should be_kind_of(Rhino::RubyObject)
o.prototype.tap do |p|
p.should_not be_nil
p.get("foo", p).should_not be_nil
p.get("toString", p).should_not be_nil
end
end
end
end
# it "creates a prototype for the object based on its class" do
# klass = Class.new do
# def foo(one, two)
# "1: #{one}, 2: #{two}"
# end
# end
#
# Rhino::To.javascript(klass.new).tap do |o|
# o.should be_kind_of(Rhino::RubyObject)
# o.prototype.tap do |p|
# p.should_not be_nil
# p.get("foo", p).should_not be_nil
# p.get("toString", p).should_not be_nil
# end
# end
# end
end

View file

@ -1,2 +1,3 @@
require 'mocha'
require 'rhino'

View file

@ -17,5 +17,6 @@ Gem::Specification.new do |s|
s.add_development_dependency "rake"
s.add_development_dependency "rspec"
s.add_development_dependency "mocha"
s.add_development_dependency "jruby-openssl"
end