Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Cleanroom Pattern

Seth Vargo
August 12, 2014

The Cleanroom Pattern

Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code!

The cleanroom pattern is a unique way for more safely evaluating Ruby DSLs without adding additional overhead.

Seth Vargo

August 12, 2014
Tweet

More Decks by Seth Vargo

Other Decks in Technology

Transcript

  1. class Project def name(val = NULL) set_or_return(:name, val) end def

    description(val = NULL) set_or_return(:description, val) end end
  2. class # @overload name(val) # Sets the name of this

    project # @param [String] val # @overload name # Returns this project's name # @return [String] end # @overload name(val) # Sets the name of this project # @param [String] val # @overload name # Returns this project's name # @return [String] def name(val = NULL)
  3. [1] pry(main)> project = Project.new => #<Project:0x007f943ba2f158> [2] pry(main)> project.name

    => nil [3] pry(main)> project.name("hamlet") => "hamlet" [4] pry(main)> project.name
  4. [1] pry(main)> project = Project.new => #<Project:0x007f943ba2f158> [2] pry(main)> project.name

    => nil [3] pry(main)> project.name("hamlet") => "hamlet" [4] pry(main)> project.name => "hamlet"
  5. [1] pry(main)> project = Project.new [2] pry(main)> project.name [3] pry(main)>

    project.name("hamlet") [4] pry(main)> project.name
  6. BasicObject#instance_eval ... the variable self is set to obj while

    the code is executing, giving the code access to obj’s instance variables
  7. BasicObject#instance_eval ... the variable while the code is executing, the

    code access to variables giving the code access to obj’s instance variables
  8. class Project def self.load(path) contents = IO.read(path) filename = File.basename(path)

    new.tap do |i| i.instance_eval(contents, filename, 1) end end end
  9. class Project def name(val = NULL) set_or_return(:name, val) end private

    def sanitize(val) return val if val.equal?(NULL) end end
  10. class Project def name(val = NULL) set_or_return(:name, val) end private

    def sanitize(val) return val if val.equal?(NULL) val.downcase.gsub(/\s+/, "-") end end
  11. class Project def name(val = NULL) set_or_return(:name, sanitize(val)) end private

    def sanitize(val) return val if val.equal?(NULL) val.downcase.gsub(/\s+/, "-") end end
  12. BasicObject#instance_eval ... the variable while the code is executing, the

    code access to variables giving the code access to obj’s instance variables
  13. class Project def set_or_return(key, val) if val.equal?(NULL) instance_variable_get(:"@#{key}") else raise

    Error unless val.is_a?(String) instance_variable_set(:"@#{key}", val) end end end
  14. CLEANROOM class Project def self.load(path) contents = IO.read(path) filename =

    File.basename(path) new.tap do |i| i.instance_eval(contents, filename, 1) end end end