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
connection_pool :whatever
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:
Instantiating Query Object
Selecting SELECT ... FROM ... FOR UPDATE ... on #<Query:0x18ae890>
Did not timeout! Yay fast database!
Measured select at 1.00018
Instantiating Query Object
Executing INSERT ... on #<Query:0x18ae37c>
Did not timeout! Yay fast database!
Measured execute at 1.000167
Executing INSERT ... on #<Query:0x18ae37c>
Did not timeout! Yay fast database!
Measured execute at 1.000085
Backward:
Instantiating Query Object
Selecting (reverse) SELECT ... FROM ... FOR UPDATE ... on #<Query:0x18ad968>
Measured select at 1.000178
Did not timeout! Yay fast database!
Instantiating Query Object
Executing (reverse) INSERT ... on #<Query:0x18ad454>
Measured execute at 1.000127
Did not timeout! Yay fast database!
Executing (reverse) INSERT ... on #<Query:0x18ad454>
Measured execute at 1.000081
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
#target (see WrappableMethods::ClassMethods, something is wrong with bindings)
# wrappable_method(:construct_query) do |query_string|
# Query.new(query_string, connection_pool)
# end
def construct_query(query_string)
build_proc(:construct_query) do
Query.new(query_string, connection_pool)
end.call query_string
end
def query(query_string)
build_proc(:query) do
construct_query(query_string).query
end.call query_string
end
def execute(query_string)
build_proc(:execute) do
construct_query(query_string).execute
end.call query_string
end
def transaction() yield end
def connection_pool(pool = nil)
@pool = pool || @pool
end
def config(hash = nil)
hash ? @config.merge!(hash) : @config
end
end
class Query
def initialize(query_string, connection_pool)
@query_string, @connection_pool = query_string, connection_pool
puts "Instantiating Query Object"
end
def execute
sleep 1
puts "Executing #{@query_string} on #{self}"
[1,2,3]
end
def query
sleep 1
puts "Selecting #{@query_string} on #{self}"
1
end
end
class Memoizer
def initialize
@memos = {}
end
def construct_query(query_string)
@memos[query_string] ||= yield query_string
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)
yield(query_string)
puts 'Did not timeout! Yay fast database!'
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