Permalink
Browse files

Separated sandbox and temporary using instance_eval because shikashi …

…is a son of a bitch.
  • Loading branch information...
1 parent a9ce280 commit 57d4ee37f1885cab6f809b7dbda824ea7485edce @razielgn committed Feb 22, 2013
Showing with 216 additions and 61 deletions.
  1. +1 −0 .gitignore
  2. +2 −1 formulas.gemspec
  3. +16 −13 lib/formulas.rb
  4. +143 −0 lib/formulas/eval_sandbox.rb
  5. +54 −47 spec/formulas_spec.rb
View
@@ -15,3 +15,4 @@ spec/reports
test/tmp
test/version_tmp
tmp
+.rbx
View
@@ -15,5 +15,6 @@ Gem::Specification.new do |gem|
gem.files = Dir.glob('lib/**/*')
gem.require_paths = ["lib"]
- gem.add_dependency 'shikashi', '~> 0.5.0'
+ gem.add_dependency 'shikashi', '~> 0.5.0'
+ gem.add_dependency 'activesupport', '>= 3.0.0'
end
View
@@ -1,9 +1,7 @@
require 'formulas/version'
-require 'shikashi'
+require 'formulas/eval_sandbox'
module Formulas
- include Shikashi
-
def self.included(base)
base.extend ClassMethods
end
@@ -23,6 +21,15 @@ def calculated_field(field)
def calculated_fields(*fields)
fields.each{ |f| calculated_field(f) }
end
+
+ def calculated_fields_source(receiver, params)
+ method = params.fetch(:method, :to_a)
+ @@_calculated_fields_source = ->{ receiver.send(method) }
+ end
+
+ def get_calculated_fields_source
+ @@_calculated_fields_source
+ end
end
def calculated_fields_errors
@@ -34,18 +41,14 @@ def calculated_fields_errors
def sandbox_run(field, code)
begin
- Sandbox.new.run(privileges, code)
- rescue Exception => ex
+ source = self.class.get_calculated_fields_source.call
+
+ EvalSandbox.new(data_source: source,
+ object_instance: self,
+ code: code).eval!
+ rescue SyntaxError, SecurityError => ex
calculated_fields_errors[field] = ex.message
nil
end
end
-
- def privileges
- @privileges ||= Privileges.new.tap do |p|
- p.instances_of(Fixnum).allow :+, :-, :*, :/, :**
- p.instances_of(Float).allow :+, :-, :*, :/, :**
- p.instances_of(Bignum).allow :+, :-, :*, :/, :**
- end
- end
end
@@ -0,0 +1,143 @@
+require 'shikashi'
+require 'active_support/inflector'
+
+module Formulas
+ class EvalException < StandardError; end
+
+ class EvalSandbox
+ include Shikashi
+
+ attr_reader :data_source, :code, :obj_instance
+
+ def initialize(params = {})
+ @data_source = params.fetch(:data_source)
+ @code = params.fetch(:code)
+ @obj_instance = params.fetch(:object_instance)
+ end
+
+ def eval!
+ sandbox!(code)
+ end
+
+ def method_missing(method, *args)
+ if param = data_source.find{ |p| p.label == method.to_s }
+ evaluate_param(param)
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(method, include_private)
+ !!data_source.find{ |p| p.label == method.to_s }
+ end
+
+ private
+
+ def sandbox!(code)
+ instance_eval(code)
+ #Sandbox.new.run(privileges: privileges,
+ #code: code,
+ #binding: binding,
+ #no_base_namespace: true)
+ end
+
+ def evaluate_param(param)
+ class_name = param.class_name
+ message = param.message
+
+ relation = class_name.tableize.singularize
+
+ if of_class?(obj_instance, class_name) && obj_instance.respond_to?(message)
+ #if not obj_instance.respond_to? message
+ #raise EvalException, "#{obj_instance} doesn't respond to #{message}"
+ #end
+
+ result = obj_instance.send(message)
+ elsif obj_instance.respond_to?(relation)
+ #obj_instance = obj_instance.send(relation)
+
+ #if not obj_instance.respond_to? message
+ #raise EvalException, "#{obj_instance} doesn't respond to #{message}"
+ #end
+
+ result = obj_instance.send(relation).send(message)
+ else
+ raise EvalException, "#{obj_instance} doesn't respond to #{relation}"
+ end
+
+ #if result.nil?
+ #raise EvalException, "#{relation}.#{message} is nil!"
+ #end
+
+ sandbox!(result.to_s)
+ end
+
+ #def create_context!
+ #singleton = class << self; self; end
+
+ #data_source.each do |param|
+ #singleton.send(:define_method, param.label) do
+ #end
+ #end
+ #end
+
+ def of_class?(obj, class_name_str)
+ obj.class.to_s == class_name_str
+ end
+
+ def privileges
+ Privileges.new.tap do |p|
+ p.instances_of(Fixnum).allow :+, :-, :*, :/, :**
+ p.instances_of(Float).allow :+, :-, :*, :/, :**
+ p.instances_of(Bignum).allow :+, :-, :*, :/, :**
+
+ data_source.each do |param|
+ p.instances_of(self.class).allow param.label.to_sym
+ end
+ end
+ end
+ end
+
+ #calcolatore = Object.new do
+ #source.each do |param|
+ #define_method(param.label) do
+ #class_name = params.class_name
+ #message = param.message
+
+ #relation = class_name.tableize.singularize
+
+ #if instance.class.to_s == class_name
+ #if not instance.respond_to? message
+ #raise "#{instance} doesn't respond to #{message}"
+ #end
+
+ #result = instance.send(message)
+ #else
+ #if not instance.respond_to? relation
+ #raise "#{instance} doesn't respond to #{relation}"
+ #end
+
+ #instance = instance.send(relation)
+ #if not instance.respond_to? message
+ #raise "#{instance} doesn't respond to #{message}"
+ #end
+
+ #result = instance.send(message)
+ #end
+
+ #if result.nil?
+ #raise "#{relation}.#{message} is nil!"
+ #end
+
+ #result
+ #end
+ #end
+
+ #def initialize(privileges, code)
+
+ #end
+ #end
+
+
+ #Sandbox.new.run(privileges, code)
+end
View
@@ -1,9 +1,17 @@
require 'spec_helper'
+require 'ostruct'
+
+Source = [
+ OpenStruct.new(label: 'aFFA', class_name: 'SimpleAttributes', message: 'attr1_calc'),
+ OpenStruct.new(label: 'aFFB', class_name: 'SimpleAttributes', message: 'attr2_calc'),
+ OpenStruct.new(label: 'iTFP', class_name: 'ComplexAttributes', message: 'attr1_calc')
+]
class SimpleAttributes
include Formulas
calculated_fields :attr1, :attr2
+ calculated_fields_source Source, method: :to_a
attr_reader :attr1, :attr2
@@ -13,60 +21,50 @@ def initialize(params = {})
end
end
-describe 'Formulas' do
- context 'simple attributes' do
- context 'integers' do
- context '+ should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2 + 2') }
- its(:attr1_calc){ should == 4 }
- end
+class ComplexAttributes
+ include Formulas
- context '- should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2 - 2') }
- its(:attr1_calc){ should == 0 }
- end
+ calculated_fields :attr1, :attr2
+ calculated_fields_source Source, method: :to_a
- context '* should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2 * 2') }
- its(:attr1_calc){ should == 4 }
- end
+ attr_reader :attr1, :attr2
- context '/ should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2 / 2') }
- its(:attr1_calc){ should == 1 }
- end
+ def initialize(params = {})
+ @attr1 = params[:attr1]
+ @attr2 = params[:attr2]
+ end
- context '** should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2 ** 2') }
- its(:attr1_calc){ should == 4 }
- end
+ def simple_attribute
+ SimpleAttributes.new(attr1: '2 * 10', attr2: 'aFFA * 25')
+ end
+end
+
+describe 'Formulas' do
+ context 'simple attributes' do
+ context 'integers' do
+ [['+', '2 + 2', 4],
+ ['-', '2 - 2', 0],
+ ['*', '2 * 2', 4],
+ ['/', '2 / 2', 1],
+ ['**', '2 ** 2', 4]].each do |op, code, res|
+ context "#{op} should be allowed" do
+ subject{ SimpleAttributes.new(attr1: code) }
+ its(:attr1_calc){ should == res }
+ end
+ end
end
context 'floats' do
- context '+ should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2.0 + 2') }
- its(:attr1_calc){ should == 4.0 }
- end
-
- context '- should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2.0 - 2') }
- its(:attr1_calc){ should == 0.0 }
- end
-
- context '* should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2.0 * 2') }
- its(:attr1_calc){ should == 4.0 }
- end
-
- context '/ should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2.0 / 2') }
- its(:attr1_calc){ should == 1.0 }
- end
-
- context '** should be allowed' do
- subject{ SimpleAttributes.new(attr1: '2.0 ** 2') }
- its(:attr1_calc){ should == 4.0 }
- end
+ [['+', '2.0 + 2', 4.0],
+ ['-', '2.0 - 2', 0.0],
+ ['*', '2.0 * 2', 4.0],
+ ['/', '2.0 / 2', 1.0],
+ ['**', '2.0 ** 2', 4.0]].each do |op, code, res|
+ context "#{op} should be allowed" do
+ subject{ SimpleAttributes.new(attr1: code) }
+ its(:attr1_calc){ should == res }
+ end
+ end
end
context 'unallowed stuff' do
@@ -96,4 +94,13 @@ def initialize(params = {})
end
end
end
+
+ context 'complex attributes' do
+ context 'referencing existing attributes within the object' do
+ subject{ ComplexAttributes.new(attr1: 'aFFB * aFFA', attr2: '4 * iTFP') }
+
+ its(:attr1_calc){ should == 10000 }
+ its(:attr2_calc){ should == 40000 }
+ end
+ end
end

0 comments on commit 57d4ee3

Please sign in to comment.