Skip to content

Instantly share code, notes, and snippets.

@sancarn
Last active February 17, 2021 11:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sancarn/0b3e30dfffa8e2e7fcba218f26f6d089 to your computer and use it in GitHub Desktop.
Save sancarn/0b3e30dfffa8e2e7fcba218f26f6d089 to your computer and use it in GitHub Desktop.

Ruby Proxy

Provides the ability to wrap an object model without having specific access to that model.

Wraps each returned object in a new Proxy object.

Example

Object model

class Application
  def self.open_current
      return Current.new()
  end
end
class Current
  def get_row(row)
      Row.new(row)
  end
end
class Row
  def initialize(row)
    @row = row
  end
  def data()
      return {:index=>@row,:data=>[1,2,3]}
  end
end

Proxy override

#Extend and replace `Application` model with `ProxyApplication` model
require './Proxy.rb'
class ProxyApplication < ProxyOverride
  def self.test()
      return true
  end
end
class ProxyCurrent < ProxyOverride
  def test()
      return true
  end
  def get_row(index)
    if UNAUTHORISED
      throw "Authorisation Failed. You are unauthorised to execute this method."
    else
      return @obj.get_row(index)
    end
  end
end
class ProxyRow < ProxyOverride
  def test()
    return true
  end
end
Proxy.enwrap(Application)

Usage

UNAUTHORISED=false

current = Application.open_current()
row = current.get_row(1)
p [Application.class, current.class, row.class, Application.test, current.test, row.test, row.data]

Results

Results now depend on Unauthroised constant. if Unauthorised == true then an error will be thrown. Otherwise if Unauthorised == false we get the following results.

[Application, Current, Row, true, true, true, {:index=>1, :data=>[1, 2, 3]}]

Note: Generally speaking the reference to Application is lost after enwrapping, so if this reference is required at a later date either:

  1. Modify the library to optionally overwrite the global reference
  2. Cache the global reference prior to overwriting:
localCache = {:app=>Application}
Proxy.enwrap(Application)
=begin
=================================
PROXY VERSION 3
=================================
DEFINITION:
I want a simple wrapper which will wrap the Application class and down.
Criterium:
* Each class must be extendable directly via the proxy class:
class ProxyApplication
def method()
#...
end
end
class ProxyCurrent
def method()
#...
end
end
#...
* All methods of delegated class should be accessible from Proxy<<Class>>
* Proxy object will first call on Proxy class to try to handle routines, before reverting to original object.
* All original object methods will return wrapped `Proxy` objects.
if Application.open_current #=> Current instance then
ProxyApplication.open_current #=> ProxyCurrent instance
if #<Current>.get_row(1) #=> Row instance
#<ProxyCurrent>.get_row(1) #=> ProxyRow instance
#...
* Ideally would work even if within console
* If is class, override original class name with Proxy object
=end
class Proxy
#=============================
# PUBLIC CLASS METHODS
#=============================
def self.enwrap(objToWrap)
#Get klass
klass = objToWrap.is_a?(Class) ? objToWrap : objToWrap.class
#Refuse to enwrap these values
if ::Proxy.isStandardLibraryClass(klass)
return objToWrap
end
#Extension class name
newClassName = "Proxy" + klass.to_s
#If class already defined then receive it, else define it
if !(::Object.constants.include?(newClassName.to_sym))
#Define a class dynamically
classDef = ::Object.const_set(newClassName.to_sym, Class.new(ProxyOverride))
else
classDef = ::Object.const_get(newClassName.to_sym)
end
#Wrap object
override = classDef.new(objToWrap)
retVal = Proxy.new(override, objToWrap)
#If object is a class then overwrite the class name
if objToWrap.is_a?(Class)
::Object.const_set(klass.name.to_sym, retVal)
end
#Return new instance of wrapped(objToWrap)
return retVal
end
#Test whether an object should be wrapped or not
def self.isStandardLibraryClass(klass)
@@classes ||= nil
if !@@classes
#It is anticipated that all classes up till RubyVM are default ruby classes
classes = ::Object.constants.select {|n| ::Object.const_get(n).class.to_s == "Class"}
@@classes = classes.first(classes.find_index(:RubyVM)+1)
end
#
return @@classes.include?(klass.name.to_sym)
end
#=============================
# PUBLIC INSTANCE METHODS
#=============================
def initialize(override,delegated)
@override = override
@delegated = delegated
end
def method_missing(m,*args,&block)
#Wrap block arguments
if block_given?
newBlock = Proc.new do |*args|
args = args.map {|arg| ::Proxy.enwrap(arg)}
block.call(*args)
end
else
newBlock = Proc.new {}
end
#If delegate is a class and override class responds to method then...
if @delegated.is_a?(Class) && @override.class.respond_to?(m)
#Get value to return
retVal = @override.class.__send__(m,*args,&newBlock)
#If override instance responds to method then...
elsif @override.respond_to?(m)
#Get value to return
retVal = @override.__send__(m,*args,&newBlock)
#Assume delegate instance responds to method.
else
#Get value to return
retVal = @delegated.__send__(m,*args,&newBlock)
end
#Ensure retVal is wrapped in a Proxy
return ::Proxy.enwrap(retVal)
end
#Use delegated class for `class()` as this is more commonly used
def class
@delegated.is_a?(Class) ? @delegated : @delegated.class
end
end
class ProxyOverride
def initialize(obj)
#Work around for
@obj = obj
end
end
#----------------------------------------------------------------------
# EXISTING OBJECT MODEL
#----------------------------------------------------------------------
class Application
def self.open_current
return Current.new()
end
end
class Current
def get_row(row)
Row.new(row)
end
end
class Row
def initialize(row)
@row = row
end
def data()
return {:index=>@row,:data=>[1,2,3]}
end
end
#----------------------------------------------------------------------
# PROXY OBJECT MODEL
#----------------------------------------------------------------------
#Extend and replace `Application` model with `ProxyApplication` model
require './Proxy.rb'
class ProxyApplication < ProxyOverride
def self.test()
return true
end
end
class ProxyCurrent < ProxyOverride
def test()
return true
end
def get_row(index)
if UNAUTHORISED
throw "Authorisation Failed. You are unauthorised to execute this method."
else
return @obj.get_row(index)
end
end
end
class ProxyRow < ProxyOverride
def test()
return true
end
end
Proxy.enwrap(Application)
#----------------------------------------------------------------------
# PROXY OBJECT MODEL TESTING
#----------------------------------------------------------------------
UNAUTHORISED=false
current = Application.open_current()
row = current.get_row(1)
p [Application.class, current.class, row.class, Application.test, current.test, row.test, row.data]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment