lib/stockpile.rb in stockpile-1.0 vs lib/stockpile.rb in stockpile-1.1

- old
+ new

@@ -1,22 +1,22 @@ # coding: utf-8 require 'forwardable' -# Stockpile is a thin wrapper around connections to a fast key-value store used -# for caching (currently only supporting Redis). +# Stockpile is a thin wrapper around connections to a fast key-value store +# used for caching (currently only supporting Redis). # # This provides a couple of layers of functionality: # # * Connection management. Some third-party providers of Redis limit # simultaneous connections; Stockpile can manage a single connection that # gets shared by all clients using a Stockpile instance. # * Providing an application-level cache adapter mechanism. class Stockpile extend Forwardable - VERSION = "1.0" # :nodoc: + VERSION = "1.1" # :nodoc: @default_manager = nil class << self # Determines if the default connection width is narrow or wide based on @@ -77,36 +77,50 @@ unless mod.kind_of?(Module) raise ArgumentError, "#{mod} is not a class or module" end name = options.fetch(:method, :cache).to_sym - msc = mod.singleton_class - + mklass = mod.singleton_class default = options.fetch(:default_manager, nil) - msc.send(:define_method, name) do |init_options = {}| + mklass.send(:define_method, name) do |init_options = {}| init_options = init_options.merge(default_manager: default) @__stockpile__ ||= ::Stockpile.new(init_options) + @__stockpile_triggers__ ||= [] + + triggers, @__stockpile_triggers__ = @__stockpile_triggers__, [] + triggers.each(&:call) + + @__stockpile__ end if options.fetch(:adaptable, true) adapter = :"#{name}_adapter" - msc.send(:define_method, adapter) do |m, k = nil| + mklass.send(:define_method, adapter) do |m, k = nil| o = self - send(name).singleton_class.send(:include, m) + @__stockpile_triggers__ ||= [] + + trigger = -> { send(name).singleton_class.send(:include, m) } + + if defined?(@__stockpile__) && @__stockpile__ + trigger.call + else + @__stockpile_triggers__ << trigger + end + if k mk = k.singleton_class m.public_instance_methods.each do |pim| mk.send(:define_method, pim) do |*args, &block| o.send(name).send(pim, *args, &block) end end end end - msc.send(:define_method, :"#{adapter}!") do |m| + mklass.send(:define_method, :"#{adapter}!") do |m| send(adapter, m, m) end end end @@ -129,41 +143,50 @@ # +default_manager+ or ::Stockpile.default_manager will be used. # An error will be raised if no connection provider is available # through any means. # +clients+:: Connections will be created for the provided list of clients. # These connections must be assigned to their appropriate clients - # after initialization. This may also be called +client+. + # after initialization. This may also be called +client+. These + # values may be provided as names (e.g., +:cache+), or as hashes + # of client name to client options (e.g., <tt>{ cache: { + # namespace: 'x' } }</tt>). See Stockpile#connect for more + # details on this latter format. # # All other options will be passed to the connection provider. # # === Synopsis # - # # Create and assign a connection to Redis.current, Resque, and Rollout. - # # Under a narrow connection management width, all three will be the - # # same client connection. - # Stockpile.new(manager: Stockpile::Redis, clients: [ :redis, :resque ]) do |stockpile| + # # Create and assign a connection to Redis.current, Resque, and Rollout. + # # Under a narrow connection management width, all three will be the + # # same client connection. + # options = { + # manager: Stockpile::Redis, + # clients: [ :redis, :resque ] + # } + # Stockpile.new(options) do |stockpile| # Redis.current = stockpile.connection_for(:redis) # Resque.redis = stockpile.connection_for(:resque) # # Clients will be created by name if necessary. # $rollout = Rollout.new(stockpile.connection_for(:rollout)) # end def initialize(options = {}) - manager = options.fetch(:manager) { - options[:default_manager] || self.class.default_manager - } + options = options.dup + manager = options.delete(:manager) + default = options.delete(:default_manager) || self.class.default_manager - unless manager + unless manager || default raise ArgumentError, "No connection manager provided or set as default." end - clients = (Array(options[:clients]) + Array(options[:client])).flatten.uniq + manager ||= default - options = { narrow: Stockpile.narrow? }.merge(options.reject { |k, _| - k == :manager || k == :clients || k == :client - }) + clients = [ + Array(options.delete(:clients)), Array(options.delete(:client)) + ].flatten.uniq - @manager = manager.new(options) + @manager = manager.new({ narrow: Stockpile.narrow? }.merge(options)) + connect(*clients) yield self if block_given? end ## @@ -181,22 +204,60 @@ # This will connect the Stockpile instance to the cache provider, optionally # including for a set of named clients. # # If the connection is using a narrow connection width, the same connection # will be shared. + # + # === Clients + # + # +clients+ may be provided in one of several ways: + # + # * A Hash object, mapping client names to client options. + # + # connect(redis: nil, rollout: { namespace: 'rollout' }) + # # Transforms into: + # # connect(redis: {}, rollout: { namespace: 'rollout' }) + # + # * An (implicit) array of client names, for connections with no options + # provided. + # + # connect(:redis, :resque, :rollout) + # # Transforms into: + # # connect(redis: {}, resque: {}, rollout: {}) + # + # * An array of Hash objects, mapping client names to client options. + # + # connect({ redis: nil }, + # { rollout: { namespace: 'rollout' } }) + # # Transforms into: + # # connect(redis: {}, rollout: { namespace: 'rollout' }) + # + # * A mix of client names and Hash objects: + # + # connect(:redis, { rollout: { namespace: 'rollout' } }) + # # Transforms into: + # # connect(redis: {}, rollout: { namespace: 'rollout' }) + # + # ==== Client Options + # + # Stockpile cache providers will handle the parsing of options to ensure that + # only suitable options are passed along (most providers will ignore any + # options that change the target system). def_delegator :@manager, :connect ## # :method: connection_for # :call-seq: # connection_for(client_name) + # connection_for(client_name, options) # # Returns a connection for a particular client. If the connection manager is # using a narrow connection width, this returns the same as #connection. # # The +client_name+ of +:all+ will always return +nil+. # - # If the requested client does not yet exist, the connection will be created. + # If the requested client does not yet exist, the connection will be created + # with the provided options. def_delegator :@manager, :connection_for ## # :method: reconnect # :call-seq: