Skip to content

Commit

Permalink
Merge 6449aa8 into 2245727
Browse files Browse the repository at this point in the history
  • Loading branch information
notEthan committed Oct 9, 2019
2 parents 2245727 + 6449aa8 commit 831d1bd
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 97 deletions.
2 changes: 1 addition & 1 deletion lib/jsi.rb
Expand Up @@ -18,7 +18,7 @@ class Bug < NotImplementedError
autoload :Base, 'jsi/base'
autoload :BaseArray, 'jsi/base'
autoload :BaseHash, 'jsi/base'
autoload :SchemaClasses, 'jsi/base'
autoload :SchemaClasses, 'jsi/schema_classes'
autoload :JSICoder, 'jsi/jsi_coder'

autoload :SimpleWrap, 'jsi/simple_wrap'
Expand Down
93 changes: 0 additions & 93 deletions lib/jsi/base.rb
Expand Up @@ -250,99 +250,6 @@ def class_for_schema(schema)
end
end

# this module is just a namespace for schema classes.
module SchemaClasses
extend Memoize

# JSI::SchemaClasses[schema_id] returns a class for the schema with the
# given id, the same class as returned from JSI.class_for_schema.
#
# @param schema_id [String] absolute schema id as returned by {JSI::Schema#schema_id}
# @return [Class subclassing JSI::Base] the class for that schema
def self.[](schema_id)
@classes_by_id[schema_id]
end
@classes_by_id = {}
end

# see {JSI.class_for_schema}
def SchemaClasses.class_for_schema(schema_object)
memoize(:class_for_schema, JSI::Schema.from_object(schema_object)) do |schema_|
begin
begin
Class.new(Base).instance_exec(schema_) do |schema|
begin
include(JSI::SchemaClasses.module_for_schema(schema))

jsi_class = self
define_method(:jsi_class) { jsi_class }

SchemaClasses.instance_exec(self) { |klass| @classes_by_id[klass.schema_id] = klass }

self
end
end
end
end
end
end

# a module for the given schema, with accessor methods for any object
# property names the schema identifies. also has class and instance
# methods called #schema to access the {JSI::Schema} this module
# represents.
#
# accessor methods are defined on these modules so that methods can be
# defined on {JSI.class_for_schema} classes without method redefinition
# warnings. additionally, these overriding instance methods can call
# `super` to invoke the normal accessor behavior.
#
# no property names that are the same as existing method names on the JSI
# class will be defined. users should use #[] and #[]= to access properties
# whose names conflict with existing methods.
def SchemaClasses.module_for_schema(schema_object)
memoize(:module_for_schema, JSI::Schema.from_object(schema_object)) do |schema_|
Module.new.tap do |m|
m.instance_exec(schema_) do |schema|
define_method(:schema) { schema }
define_singleton_method(:schema) { schema }
define_singleton_method(:included) do |includer|
includer.send(:define_singleton_method, :schema) { schema }
end

define_singleton_method(:schema_id) do
schema.schema_id
end
define_singleton_method(:inspect) do
%Q(#<Module for Schema: #{schema_id}>)
end

instance_method_modules = [m, Base, BaseArray, BaseHash]
instance_methods = instance_method_modules.map do |mod|
mod.instance_methods + mod.private_instance_methods
end.inject(Set.new, &:|)
accessors_to_define = schema.described_object_property_names.map(&:to_s) - instance_methods.map(&:to_s)
accessors_to_define.each do |property_name|
define_method(property_name) do
if respond_to?(:[])
self[property_name]
else
raise(NoMethodError, "instance does not respond to []; cannot call reader `#{property_name}' for: #{pretty_inspect.chomp}")
end
end
define_method("#{property_name}=") do |value|
if respond_to?(:[]=)
self[property_name] = value
else
raise(NoMethodError, "instance does not respond to []=; cannot call writer `#{property_name}=' for: #{pretty_inspect.chomp}")
end
end
end
end
end
end
end

# module extending a {JSI::Base} object when its instance is Hash-like (responds to #to_hash)
module BaseHash
# yields each key and value of this JSI.
Expand Down
86 changes: 86 additions & 0 deletions lib/jsi/schema_classes.rb
@@ -0,0 +1,86 @@
module JSI
# this module is just a namespace for schema classes.
module SchemaClasses
# JSI::SchemaClasses[schema_id] returns a class for the schema with the
# given id, the same class as returned from JSI.class_for_schema.
#
# @param schema_id [String] absolute schema id as returned by {JSI::Schema#schema_id}
# @return [Class subclassing JSI::Base] the class for that schema
def self.[](schema_id)
@classes_by_id[schema_id]
end
@classes_by_id = {}

class << self
include Memoize

# see {JSI.class_for_schema}
def class_for_schema(schema_object)
memoize(:class_for_schema, JSI::Schema.from_object(schema_object)) do |schema_|
Class.new(Base).instance_exec(schema_) do |schema|
define_singleton_method(:schema) { schema }
define_method(:schema) { schema }
include(JSI::SchemaClasses.module_for_schema(schema, conflicting_modules: [Base, BaseArray, BaseHash]))

jsi_class = self
define_method(:jsi_class) { jsi_class }

SchemaClasses.instance_exec(self) { |klass| @classes_by_id[klass.schema_id] = klass }

self
end
end
end

# a module for the given schema, with accessor methods for any object
# property names the schema identifies. also has a singleton method
# called #schema to access the {JSI::Schema} this module represents.
#
# accessor methods are defined on these modules so that methods can be
# defined on {JSI.class_for_schema} classes without method redefinition
# warnings. additionally, these overriding instance methods can call
# `super` to invoke the normal accessor behavior.
#
# no property names that are the same as existing method names on the JSI
# class will be defined. users should use #[] and #[]= to access properties
# whose names conflict with existing methods.
def SchemaClasses.module_for_schema(schema_object, conflicting_modules: [])
schema__ = JSI::Schema.from_object(schema_object)
memoize(:module_for_schema, schema__, conflicting_modules) do |schema_, conflicting_modules_|
Module.new.tap do |m|
m.instance_exec(schema_) do |schema|
define_singleton_method(:schema) { schema }
define_singleton_method(:schema_id) do
schema.schema_id
end
define_singleton_method(:inspect) do
%Q(#<Module for Schema: #{schema_id}>)
end

conflicting_instance_methods = (conflicting_modules_ + [m]).map do |mod|
mod.instance_methods + mod.private_instance_methods
end.inject(Set.new, &:|)
accessors_to_define = schema.described_object_property_names.map(&:to_s) - conflicting_instance_methods.map(&:to_s)
accessors_to_define.each do |property_name|
define_method(property_name) do
if respond_to?(:[])
self[property_name]
else
raise(NoMethodError, "schema instance of class #{self.class} does not respond to []; cannot call reader '#{property_name}'. instance is #{instance.pretty_inspect.chomp}")
end
end
define_method("#{property_name}=") do |value|
if respond_to?(:[]=)
self[property_name] = value
else
raise(NoMethodError, "schema instance of class #{self.class} does not respond to []=; cannot call writer '#{property_name}='. instance is #{instance.pretty_inspect.chomp}")
end
end
end
end
end
end
end
end
end
end
6 changes: 3 additions & 3 deletions test/base_test.rb
Expand Up @@ -283,7 +283,7 @@
let(:instance) { nil }
it 'errors' do
err = assert_raises(NoMethodError) { subject.foo }
assert_match(%r(\Ainstance does not respond to \[\]; cannot call reader `foo' for: #<JSI::SchemaClasses\["[^"]+#"\].*nil.*>\z)m, err.message)
assert_match(%r(\Aschema instance of class .* does not respond to \[\]; cannot call reader 'foo'. instance is )m, err.message)
end
end
describe 'properties with the same names as instance methods' do
Expand Down Expand Up @@ -318,7 +318,7 @@
end
it 'does not define readers' do
assert_equal('bar', subject.foo)
assert_equal(JSI::SchemaClasses.module_for_schema(subject.schema), subject.method(:foo).owner)
assert_equal(JSI::SchemaClasses.module_for_schema(subject.schema, conflicting_modules: [JSI::Base, JSI::BaseArray, JSI::BaseHash]), subject.method(:foo).owner)

assert_equal(JSI::Base, subject.method(:initialize).owner)
assert_equal('hi', subject['initialize'])
Expand Down Expand Up @@ -357,7 +357,7 @@
let(:instance) { nil }
it 'errors' do
err = assert_raises(NoMethodError) { subject.foo = 0 }
assert_match(%r(\Ainstance does not respond to \[\]=; cannot call writer `foo=' for: #<JSI::SchemaClasses\["[^"]+#"\].*nil.*>\z)m, err.message)
assert_match(%r(\Aschema instance of class .* does not respond to \[\]=; cannot call writer 'foo='. instance is )m, err.message)
end
end
end
Expand Down

0 comments on commit 831d1bd

Please sign in to comment.