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.
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.
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
class Query
def initialize(query_string) puts "Initializing new query object" 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 Memoizer
def query(query_string)
@queries ||= {}
@queries[query_string] ||= begin
puts 'Cache miss'
yield query_string
end
end
alias_method :execute, :query
end
class Stats
def initialize(onion) @config = onion.config end
def respond_to?(method) @config[:stats] and super end
def query(query_string)
retval = yield query_string
puts "Measured select (#{query_string}) at 1 second"
retval
end
alias_method :execute, :query
end
class TimesOut
def query(query_string)
retval = yield query_string
puts 'Did not timeout! Yay fast database!'
retval
end
alias_method :execute, :query
end
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment