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\nBackward\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:
Query cache miss
Selecting: SELECT ... FROM ... FOR UPDATE ...
Initializing new query object
Querying: SELECT ... FROM ... FOR UPDATE ...
Did not timeout! Yay fast database!
Measured select (SELECT ... FROM ... FOR UPDATE ...) at 1.000472
Execute cache miss
Initializing new query object
Executing: INSERT ...
Did not timeout! Yay fast database!
Measured execute (INSERT ...) at 1.000449
Execute cache miss
Initializing new query object
Executing: INSERT ...
Did not timeout! Yay fast database!
Measured execute (INSERT ...) at 1.000682
Backward
Query cache miss
Selecting: (reverse) SELECT ... FROM ... FOR UPDATE ...
Initializing new query object
Querying: (reverse) SELECT ... FROM ... FOR UPDATE ...
Measured select ((reverse) SELECT ... FROM ... FOR UPDATE ...) at 1.000162
Did not timeout! Yay fast database!
Execute cache miss
Initializing new query object
Executing: (reverse) INSERT ...
Measured execute ((reverse) INSERT ...) at 1.000119
Did not timeout! Yay fast database!
Execute cache miss
Initializing new query object
Executing: (reverse) INSERT ...
Measured execute ((reverse) INSERT ...) at 1.000149
Did not timeout! Yay fast database!
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'
require 'util'
class Onion < Array #so simple it makes me cry
include WrappableMethods
def initialize(*classes_or_objects, &blk)
@config = {}
super classes_or_objects.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).query
end
wrappable_method :execute do |query_string|
Query.new(query_string).execute
end
def transaction
yield
end
def config(hash = nil)
if hash
@config.merge! hash
else
@config
end
end
end
class Query
def initialize(query_string)
@query_string = query_string
puts "Initializing new query object"
end
def execute
sleep 1
puts "Executing: #{@query_string}"
end
def query
sleep 1
puts "Querying: #{@query_string}"
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)
time = Time.now
returning yield(query_string) do
puts "Measured select (#{query_string}) at #{Time.now - time}"
end
end
def execute(query_string)
time = Time.now
returning yield(query_string) do
puts "Measured execute (#{query_string}) at #{Time.now - time}"
end
end
end
class TimesOut
def initialize(onion) @config = onion.config end
def query(query_string)
value = Timeout.timeout(@config[:timeout]) { yield(query_string) }
puts 'Did not timeout! Yay fast database!'
value
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