Skip to content

Instantly share code, notes, and snippets.

@jbr
Forked from nkallen/MODULARITY_OLYMPICS.markdown
Created February 16, 2010 06:55
Show Gist options
  • Save jbr/305362 to your computer and use it in GitHub Desktop.
Save jbr/305362 to your computer and use it in GitHub Desktop.
require 'onion'
Onion.new Stats, Memoizer, TimesOut do
config :stats => true, :timeout => 1_000
puts "FORWARD:"
transaction do
query "SELECT ... FROM ... FOR UPDATE ..."
execute "INSERT ..."
execute "INSERT ..."
end
reverse!
puts "\n\n\nREVERSED ORDER\n\n\n"
transaction do
query "(reverse) SELECT ... FROM ... FOR UPDATE ..."
execute "(reverse) INSERT ..."
execute "(reverse) INSERT ..."
end
end
I want to turn this into a programming contest. Everyone should do this using their favorite technique and language. But, I want to add three more challenges (that admittedly stack the deck in my favor).
1. create a stats collecting query decorate that measures the time it takes to execute the query
(assume you have a stats object that responds to #measure and takes a block: stats.measure { sleep 1.second }
2. read "configuration" from a hash like:
{ :timeout => 1.second, :stats => true }
that adds the timeout and stats decorators to the factory if present in the hash, otherwise not.
3. at the end write a program that REVERSES the order of the decorators. So if you did Timeout(Stats(Query)) make it Stats(Timeout(Query)). Note that you must do this programmatically so write a generic decorator reversing function. You may expand the proxy interface to have a method called "underlying", "delegate" or some such.
The contest is: do this in your language in as "elegant" a way as you like. The goal is clarity and modularity.
Forward:
Instantiating Query Object
Selecting SELECT ... FROM ... FOR UPDATE ... on #<Object:0x11f6750>
Did not timeout! Yay fast database!
Measured select at 1.00 seconds
Instantiating Query Object
Executing INSERT ... on #<Object:0x11f6750>
Did not timeout! Yay fast database!
Measured select at 1.00 seconds
Executing INSERT ... on #<Object:0x11f6750>
Did not timeout! Yay fast database!
Measured select at 1.00 seconds
Backward:
Instantiating Query Object
Selecting SELECT ... FROM ... FOR UPDATE ... on #<Object:0x11f4ea0>
Measured select at 1.00 seconds
Did not timeout! Yay fast database!
Instantiating Query Object
Executing INSERT ... on #<Object:0x11f4ea0>
Measured select at 1.00 seconds
Did not timeout! Yay fast database!
Executing INSERT ... on #<Object:0x11f4ea0>
Measured select at 1.00 seconds
Did not timeout! Yay fast database!
require 'wrappable_methods'
class Object
def returning(value)
yield value
value
end
end
class Onion < Array #so simple it makes me cry
include WrappableMethods
def initialize(*quack, &blk)
@config = {}
super quack.map {|q| q.new self rescue q.new rescue q}
instance_eval &blk
end
wrappable_method :query do |query_string|
puts "Selecting: #{query_string}"
Query.new(query_string)
end
wrappable_method :execute do |query_string|
puts "Executing: #{query_string}"
Query.new(query_string)
end
def transaction
puts "BEGIN"
yield
puts "COMMIT"
end
def config(hash = nil)
if hash
@config.merge! hash
else
@config
end
end
end
class Query
def initialize(query_string) puts "Initializing new query object" end
end
class Memoizer
def initialize
@queries = {}
@executions = {}
end
def query(query_string)
@queries[query_string] ||= begin
puts 'Query cache miss'
yield query_string
end
end
def execute(query_string)
@queries[query_string] ||= begin
puts 'Execute cache miss'
yield query_string
end
end
end
class Stats
def initialize(onion) @config = onion.config end
def respond_to?(method) @config[:stats] and super end
def query(query_string)
returning yield(query_string) do
puts "Measured select (#{query_string}) at 1 second"
end
end
alias_method :execute, :query
end
class TimesOut
def query(query_string)
returning yield(query_string) do
puts 'Did not timeout! Yay fast database!'
end
end
alias_method :execute, :query
end
module WrappableMethods
def self.included(klass) klass.extend ClassMethods end
def build_proc(method, &innermost)
reverse.inject(innermost) do |inner, item|
if item.respond_to?(method)
lambda {|*args| item.send method, *args, &inner}
else
inner
end
end
end
module ClassMethods
def wrappable_method(method_name, &blk)
define_method method_name do |*args|
build_proc(method_name, &blk).call *args
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment