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
puts "Backward:"
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
Querying: SELECT ... FROM ... FOR UPDATE ...
Did not timeout! Yay fast database!
Measured select at 1.006871
Execute cache miss
Executing: INSERT ...
Did not timeout! Yay fast database!
Measured execute at 1.00025
Measured execute at 6.0e-06
Backward:
Query cache miss
Querying: (reverse) SELECT ... FROM ... FOR UPDATE ...
Measured select at 1.000129
Did not timeout! Yay fast database!
Execute cache miss
Executing: (reverse) INSERT ...
Measured execute at 1.000101
Did not timeout! Yay fast database!
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|
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}"
[1,2,3]
end
def query
sleep 1
puts "Querying: #{@query_string}"
1
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)
@executions[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 at #{Time.now - time}"
end
end
def execute(query_string)
time = Time.now
returning yield(query_string) do
puts "Measured execute 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
class Object
def returning(value)
yield value
value
end
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