circuit 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +34 -0
- data/LICENSE +20 -0
- data/README.md +161 -0
- data/Rakefile +27 -0
- data/config.ru +7 -0
- data/description.md +5 -0
- data/docs/COMPATIBILITY.md +14 -0
- data/docs/ROADMAP.md +29 -0
- data/lib/circuit.rb +125 -0
- data/lib/circuit/behavior.rb +99 -0
- data/lib/circuit/compatibility.rb +73 -0
- data/lib/circuit/middleware.rb +6 -0
- data/lib/circuit/middleware/rewriter.rb +43 -0
- data/lib/circuit/rack.rb +14 -0
- data/lib/circuit/rack/behavioral.rb +45 -0
- data/lib/circuit/rack/builder.rb +50 -0
- data/lib/circuit/rack/multi_site.rb +22 -0
- data/lib/circuit/rack/request.rb +81 -0
- data/lib/circuit/railtie.rb +24 -0
- data/lib/circuit/storage.rb +74 -0
- data/lib/circuit/storage/memory_model.rb +70 -0
- data/lib/circuit/storage/nodes.rb +56 -0
- data/lib/circuit/storage/nodes/memory_store.rb +63 -0
- data/lib/circuit/storage/nodes/model.rb +67 -0
- data/lib/circuit/storage/nodes/mongoid_store.rb +56 -0
- data/lib/circuit/storage/sites.rb +38 -0
- data/lib/circuit/storage/sites/memory_store.rb +53 -0
- data/lib/circuit/storage/sites/model.rb +29 -0
- data/lib/circuit/storage/sites/mongoid_store.rb +43 -0
- data/lib/circuit/validators.rb +74 -0
- data/lib/circuit/version.rb +3 -0
- data/spec/internal/app/behaviors/change_path.rb +7 -0
- data/spec/internal/app/controllers/application_controller.rb +5 -0
- data/spec/internal/app/helpers/application_helper.rb +2 -0
- data/spec/internal/config/initializers/circuit.rb +7 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +1 -0
- data/spec/lib/circuit/behavior_spec.rb +113 -0
- data/spec/lib/circuit/middleware/rewriter_spec.rb +79 -0
- data/spec/lib/circuit/rack/behavioral_spec.rb +60 -0
- data/spec/lib/circuit/rack/builder_spec.rb +125 -0
- data/spec/lib/circuit/rack/multi_site_spec.rb +34 -0
- data/spec/lib/circuit/rack/request_spec.rb +80 -0
- data/spec/lib/circuit/railtie_spec.rb +34 -0
- data/spec/lib/circuit/storage/nodes_spec.rb +62 -0
- data/spec/lib/circuit/storage/sites_spec.rb +60 -0
- data/spec/lib/circuit/storage_spec.rb +20 -0
- data/spec/lib/circuit/validators_spec.rb +69 -0
- data/spec/lib/circuit_spec.rb +139 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/support/blueprints.rb +24 -0
- data/spec/support/matchers/be_current_time_matcher.rb +14 -0
- data/spec/support/matchers/extended_have_key.rb +31 -0
- data/spec/support/matchers/have_accessor_matcher.rb +13 -0
- data/spec/support/matchers/have_attribute_matcher.rb +22 -0
- data/spec/support/matchers/have_block_matcher.rb +14 -0
- data/spec/support/matchers/have_errors_on_matcher.rb +30 -0
- data/spec/support/matchers/have_module_matcher.rb +13 -0
- data/spec/support/matchers/have_reader_matcher.rb +18 -0
- data/spec/support/matchers/have_writer_matcher.rb +18 -0
- data/spec/support/matchers/set_instance_variable.rb +32 -0
- data/spec/support/spec_helpers/base_behaviors.rb +20 -0
- data/spec/support/spec_helpers/base_models.rb +64 -0
- data/spec/support/spec_helpers/logger_helpers.rb +58 -0
- data/spec/support/spec_helpers/multi_site_helper.rb +48 -0
- data/spec/support/spec_helpers/rack_helpers.rb +8 -0
- data/spec/support/spec_helpers/shared_examples/node_store.rb +87 -0
- data/spec/support/spec_helpers/shared_examples/site_store.rb +76 -0
- data/spec/support/spec_helpers/simple_machinable.rb +29 -0
- data/spec/support/spec_helpers/stores_cleaner.rb +46 -0
- data/vendor/active_support-3.2/core_ext/string/inflections.rb +53 -0
- data/vendor/active_support-3.2/inflector/methods.rb +65 -0
- data/vendor/rack-1.4/builder.rb +167 -0
- data/vendor/rack-1.4/urlmap.rb +96 -0
- metadata +238 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'active_support/core_ext/kernel/reporting'
|
5
|
+
require 'dionysus/string/version_match'
|
6
|
+
|
7
|
+
module Circuit
|
8
|
+
# Compatibility extensions for Rack 1.3 and Rails 3.1
|
9
|
+
module Compatibility
|
10
|
+
# Make Rack 1.3 and ActiveSupport 3.1 compatible with circuit.
|
11
|
+
# * Overrides Rack::Builder and Rack::URLMap with the classes from Rack 1.4
|
12
|
+
# * Adds #demodulize and #deconstantize inflections to ActiveSupport
|
13
|
+
def self.make_compatible
|
14
|
+
rack13 if ::Rack.release.version_match?("~> 1.3.0")
|
15
|
+
active_support31 if ActiveSupport::VERSION::STRING.version_match?("~> 3.1.0")
|
16
|
+
end
|
17
|
+
|
18
|
+
# Include in a model to modify it for compatibility.
|
19
|
+
module ActiveModel31
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
# @!method define_attribute_methods(*args)
|
23
|
+
# Modified to call `attribute_method_suffix ''` first to create the
|
24
|
+
# accessors.
|
25
|
+
# @see http://rubydoc.info/gems/activemodel/ActiveModel/AttributeMethods/ClassMethods#define_attribute_methods-instance_method
|
26
|
+
# @see http://rubydoc.info/gems/activemodel/ActiveModel/AttributeMethods/ClassMethods#attribute_method_suffix-instance_method
|
27
|
+
|
28
|
+
included do
|
29
|
+
if ActiveModel::VERSION::STRING.version_match?("~> 3.1.0")
|
30
|
+
if has_active_model_module?("AttributeMethods")
|
31
|
+
class << self
|
32
|
+
alias_method_chain :define_attribute_methods, :default_accessor
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
def has_active_model_module?(mod_name)
|
42
|
+
included_modules.detect {|mod| mod.to_s == "ActiveModel::#{mod_name.to_s.camelize}"}
|
43
|
+
end
|
44
|
+
|
45
|
+
def define_attribute_methods_with_default_accessor(*args)
|
46
|
+
attribute_method_suffix ''
|
47
|
+
define_attribute_methods_without_default_accessor(*args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def self.vendor_path
|
55
|
+
Circuit.vendor_path
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.rack13
|
59
|
+
require "rack/urlmap"
|
60
|
+
require "rack/builder"
|
61
|
+
|
62
|
+
require vendor_path.join("rack-1.4", "builder").to_s
|
63
|
+
silence_warnings { require vendor_path.join("rack-1.4", "urlmap").to_s }
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.active_support31
|
67
|
+
require "active_support/inflector"
|
68
|
+
|
69
|
+
require vendor_path.join("active_support-3.2", "inflector", "methods").to_s
|
70
|
+
require vendor_path.join("active_support-3.2", "core_ext", "string", "inflections").to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Circuit
|
2
|
+
module Middleware
|
3
|
+
# Raise if rewriting fails.
|
4
|
+
class RewriteError < CircuitError ; end
|
5
|
+
|
6
|
+
# Rewriter middleware
|
7
|
+
# @example Use the middleware (in rackup)
|
8
|
+
# use Rewriter do |script_name, path_info|
|
9
|
+
# ["/pages", script_name+path_info]
|
10
|
+
# end
|
11
|
+
# @example Use the middleware with Rack::Request object (in rackup)
|
12
|
+
# use Rewriter do |request|
|
13
|
+
# ["/site/#{request.site.id}"+request.script_name, request.path_info]
|
14
|
+
# end
|
15
|
+
# @see http://rubydoc.info/gems/rack/Rack/Request Rack::Request documentation
|
16
|
+
class Rewriter
|
17
|
+
# @param [#call] app Rack app
|
18
|
+
# @yield [script_name, path_info] `SCRIPT_NAME` and `PATH_INF`O values
|
19
|
+
# @yield [Request] `Rack::Request` object
|
20
|
+
# @yieldreturn [Array<String>] new `script_name` and `path_info`
|
21
|
+
def initialize(app, &block)
|
22
|
+
@app = app
|
23
|
+
@block = block
|
24
|
+
end
|
25
|
+
|
26
|
+
# Executes the rewrite
|
27
|
+
def call(env)
|
28
|
+
begin
|
29
|
+
request = ::Rack::Request.new(env)
|
30
|
+
script_name, path_info, path = request.script_name, request.path_info, request.path
|
31
|
+
env["SCRIPT_NAME"], env["PATH_INFO"] = @block.call(script_name, path_info)
|
32
|
+
if script_name != env["SCRIPT_NAME"] or path_info != env["PATH_INFO"]
|
33
|
+
::Circuit.logger.info("[CIRCUIT] Rewriting: '#{path}'->'#{request.path}'")
|
34
|
+
end
|
35
|
+
rescue RewriteError => ex
|
36
|
+
headline = "[CIRCUIT] Rewrite Error"
|
37
|
+
::Circuit.logger.error("%s: %s\n%s %s"%[headline, ex.message, " "*headline.length, ex.backtrace.first])
|
38
|
+
end
|
39
|
+
@app.call(env)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/circuit/rack.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
|
3
|
+
module Circuit
|
4
|
+
module Rack
|
5
|
+
require 'circuit/rack/request'
|
6
|
+
::Rack::Request.send(:include, Request)
|
7
|
+
|
8
|
+
require 'circuit/rack/builder'
|
9
|
+
::Rack::Builder.send(:include, BuilderExt)
|
10
|
+
|
11
|
+
autoload :Behavioral, 'circuit/rack/behavioral'
|
12
|
+
autoload :MultiSite, 'circuit/rack/multi_site'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Circuit
|
2
|
+
module Rack
|
3
|
+
# Raised if the `rack.circuit.site` rack variable is not defined (or `nil`)
|
4
|
+
class MissingSiteError < CircuitError; end
|
5
|
+
|
6
|
+
# Finds the route (Array of Nodes) for the request, and executes the
|
7
|
+
# route's behavior.
|
8
|
+
class Behavioral
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
request = ::Rack::Request.new(env)
|
15
|
+
|
16
|
+
unless request.site
|
17
|
+
raise MissingSiteError, "Rack variable %s is missing"%[::Rack::Request::ENV_SITE]
|
18
|
+
end
|
19
|
+
|
20
|
+
remap! request
|
21
|
+
request.route.last.behavior.builder.tap do |builder|
|
22
|
+
builder.run(@app) unless builder.app?
|
23
|
+
end.call(env)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def remap(request)
|
29
|
+
route = ::Circuit.node_store.get(request.site, request.path)
|
30
|
+
return nil if route.blank?
|
31
|
+
|
32
|
+
request.route = route
|
33
|
+
return request
|
34
|
+
end
|
35
|
+
|
36
|
+
def remap!(request)
|
37
|
+
if result = remap(request)
|
38
|
+
result
|
39
|
+
else
|
40
|
+
raise ::Circuit::Storage::Nodes::NotFoundError, "Path not found"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rack/server'
|
2
|
+
require 'rack/builder'
|
3
|
+
|
4
|
+
module Circuit
|
5
|
+
module Rack
|
6
|
+
# Extensions to Rack::Builder
|
7
|
+
module BuilderExt
|
8
|
+
# @return [Boolean] true if a default app is set
|
9
|
+
def app?
|
10
|
+
!!@run
|
11
|
+
end
|
12
|
+
|
13
|
+
# Duplicates the `@use` Array and `@map` Hash instance variables
|
14
|
+
def initialize_copy(other)
|
15
|
+
@use = other.instance_variable_get(:@use).dup
|
16
|
+
unless other.instance_variable_get(:@map).nil?
|
17
|
+
@map = other.instance_variable_get(:@map).dup
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# A Rack::Builder variation that does not require a fully-compliant rackup
|
23
|
+
# file; specifically that a default app (`run` directive) is not required.
|
24
|
+
class Builder < ::Rack::Builder
|
25
|
+
include BuilderExt
|
26
|
+
|
27
|
+
# Parses the rackup (or circuit-rackup .cru) file.
|
28
|
+
# @return [Circuit::Rack::Builder] the builder
|
29
|
+
def self.parse_file(config, opts = ::Rack::Server::Options.new)
|
30
|
+
# allow for objects that are String-like but don't respond to =~
|
31
|
+
# (e.g. Pathname)
|
32
|
+
config = config.to_s
|
33
|
+
|
34
|
+
if config.to_s =~ /\.cru$/
|
35
|
+
options = {}
|
36
|
+
cfgfile = ::File.read(config)
|
37
|
+
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
38
|
+
builder = eval "%s.new {\n%s\n}"%[self, cfgfile], TOPLEVEL_BINDING, config
|
39
|
+
return builder, options
|
40
|
+
else
|
41
|
+
# this should be a fully-compliant rackup file (or a constant name),
|
42
|
+
# so use the real Rack::Builder, but return a Builder object instead
|
43
|
+
# of the app
|
44
|
+
app, options = ::Rack::Builder.parse_file(config, opts)
|
45
|
+
return self.new(app), options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Circuit
|
2
|
+
module Rack
|
3
|
+
# Finds the Circuit::Site for the request. Returns a 404 if the site is not found.
|
4
|
+
class MultiSite
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
request = ::Rack::Request.new(env)
|
11
|
+
|
12
|
+
request.site = Circuit.site_store.get(request.host)
|
13
|
+
unless request.site
|
14
|
+
# TODO custom 404 page for site not found
|
15
|
+
return [404, {}, ["Not Found"]]
|
16
|
+
end
|
17
|
+
|
18
|
+
@app.call(request.env)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Circuit
|
4
|
+
module Rack
|
5
|
+
# Extensions made to the ::Rack::Request class
|
6
|
+
# @see http://rubydoc.info/gems/rack/Rack/Request ::Rack::Request API documentation
|
7
|
+
# @see ClassMethods
|
8
|
+
module Request
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
# Key for storing the Circuit::Site instance
|
12
|
+
ENV_SITE = 'rack.circuit.site'
|
13
|
+
|
14
|
+
# Key for storing the array of Circuit::Node instances
|
15
|
+
ENV_ROUTE = 'rack.circuit.route'
|
16
|
+
|
17
|
+
# Key for storing original parameters that Circuit modifies.
|
18
|
+
ENV_ORIGINALS = 'rack.circuit.originals'
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Parses a URI path String into its segments
|
22
|
+
# @param [String] path to parse
|
23
|
+
# @return [Array<String>] segment parts; first segment will be `nil` if
|
24
|
+
# it is the root (i.e. the path is absolute)
|
25
|
+
# @see http://tools.ietf.org/html/rfc2396#section-3.3 RFC 2396: Uniform
|
26
|
+
# Resource Identifiers (URI): Generic Syntax - Path Component
|
27
|
+
def path_segments(path)
|
28
|
+
path.gsub(/^\/+/,'').split(/\/+/).select { |seg| !seg.blank? }.tap do |result|
|
29
|
+
result.unshift nil if path =~ /^\//
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Circuit::Site] site from the environment
|
35
|
+
def site() @env[ENV_SITE]; end
|
36
|
+
|
37
|
+
# @param [Circuit::Site] site to set into the environment
|
38
|
+
def site=(site) @env[ENV_SITE] = site; end
|
39
|
+
|
40
|
+
# @return [Array<Circuit::Node>] the array of nodes that make up the
|
41
|
+
# route
|
42
|
+
def route() @env[ENV_ROUTE]; end
|
43
|
+
|
44
|
+
# Sets the route and modifies the `PATH_INFO` and `SCRIPT_NAME` variables
|
45
|
+
# to conform to the route. This means that that route's path will be set
|
46
|
+
# as the `SCRIPT_NAME` and the remainder of the path will be set as the
|
47
|
+
# `PATH_INFO`. Also, the original `PATH_INFO` and `SCRIPT_NAME`
|
48
|
+
# values are copied into the originals key.
|
49
|
+
# @param [Array<Circuit::Node>] route the array of nodes that make up the route
|
50
|
+
def route=(route)
|
51
|
+
raise(Circuit::Error, "Route has already been set") if self.route
|
52
|
+
save_original_path_envs
|
53
|
+
@env["PATH_INFO"] = "/"+path_segments[route.length..-1].join("/")
|
54
|
+
@env["PATH_INFO"] = "" if @env["PATH_INFO"] == "/"
|
55
|
+
@env["SCRIPT_NAME"] = route.last.path
|
56
|
+
@env[ENV_ROUTE] = route;
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String] the route's path (aka. the `SCRIPT_NAME`)
|
60
|
+
def route_path() @env["SCRIPT_NAME"]; end
|
61
|
+
|
62
|
+
# @return [Hash] the originals of any keys that Circuit modifies are
|
63
|
+
# first copied into this Hash
|
64
|
+
def circuit_originals() @env[ENV_ORIGINALS] ||= {}; end
|
65
|
+
|
66
|
+
# @return [Array<String>] segment parts; first segment will be `nil` if
|
67
|
+
# it is the root (i.e. the path is absolute)
|
68
|
+
# @see ClassMethods#path_segments
|
69
|
+
def path_segments
|
70
|
+
self.class.path_segments self.path
|
71
|
+
end
|
72
|
+
|
73
|
+
# Saves the original path keys into #circuit_originals (i.e. `PATH_INFO`
|
74
|
+
# and `SCRIPT_NAME`)
|
75
|
+
def save_original_path_envs
|
76
|
+
circuit_originals["PATH_INFO"] = @env["PATH_INFO"]
|
77
|
+
circuit_originals["SCRIPT_NAME"] = @env["SCRIPT_NAME"]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Circuit
|
2
|
+
# Initializer `circuit_railtie.configure_rails_initialization`
|
3
|
+
# ------------------------------------------------------------
|
4
|
+
# * adds Rack::MultiSite and Rack::Behavioral to the Rails middleware stack
|
5
|
+
# * Sets the Circuit logger to the Rails logger
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer "circuit_railtie.configure_rails_initialization" do |app|
|
8
|
+
app.middleware.insert 0, Rack::MultiSite
|
9
|
+
app.middleware.insert 1, Rack::Behavioral
|
10
|
+
|
11
|
+
unless Circuit.cru_path
|
12
|
+
Circuit.cru_path = app.root.join("app", "behaviors")
|
13
|
+
end
|
14
|
+
|
15
|
+
root = app.root.expand_path.to_s
|
16
|
+
rel = Circuit.cru_path.expand_path.to_s.gsub(/^#{Regexp.escape(root)}\//, "")
|
17
|
+
app.config.paths.add rel, :eager_load => true, :glob => "**/*.rb"
|
18
|
+
|
19
|
+
app.config.after_initialize do
|
20
|
+
Circuit.logger = Rails.logger
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module Circuit
|
4
|
+
module Storage
|
5
|
+
autoload :MemoryModel, 'circuit/storage/memory_model'
|
6
|
+
autoload :Sites, 'circuit/storage/sites'
|
7
|
+
autoload :Nodes, 'circuit/storage/nodes'
|
8
|
+
|
9
|
+
# Raised if a storage instance is undefined.
|
10
|
+
class InstanceUndefinedError < CircuitError
|
11
|
+
def initialize(msg="Storage instance is undefined.")
|
12
|
+
super(msg)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @raise InstanceUndefinedError if the instance isn't defined
|
17
|
+
# @return [Circuit::Storage::Nodes::BaseStore,Circuit::Storage::Sites::BaseStore]
|
18
|
+
# the storage instance
|
19
|
+
def instance
|
20
|
+
@instance || raise(InstanceUndefinedError)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the storage instance and alias the `Node` or `Site` model under
|
24
|
+
# `Circuit` as `Circuit::Node` or `Circuit::Site`.
|
25
|
+
#
|
26
|
+
# @raise ArgumentError if the storage instance or the `Site` or `Node`
|
27
|
+
# model cannot be determined
|
28
|
+
# @overload set_instance(instance)
|
29
|
+
# @param [Circuit::Storage::Nodes::BaseStore,Circuit::Storage::Sites::BaseStore]
|
30
|
+
# instance storage instance
|
31
|
+
# @return [Circuit::Storage::Nodes::BaseStore,Circuit::Storage::Sites::BaseStore]
|
32
|
+
# storage instance
|
33
|
+
# @overload set_instance(klass_or_name, *args)
|
34
|
+
# @param [Class,String,Symbol] klass_or_name class or name of class
|
35
|
+
# (under Circuit::Storage::Nodes or
|
36
|
+
# Circuit::Storage::Sites) for storage
|
37
|
+
# instance
|
38
|
+
# @param [Array] args any arguments to instantiate the storage instance
|
39
|
+
# @return [Circuit::Storage::Nodes::BaseStore,Circuit::Storage::Sites::BaseStore]
|
40
|
+
# storage instance
|
41
|
+
def set_instance(*args)
|
42
|
+
klass = nil
|
43
|
+
|
44
|
+
case args.first
|
45
|
+
when Nodes::BaseStore, Sites::BaseStore
|
46
|
+
@instance = args.first
|
47
|
+
when Class
|
48
|
+
klass = args.first
|
49
|
+
when String, Symbol
|
50
|
+
# TODO do we need to fall up the module hiearchy to find the constant?
|
51
|
+
# (e.g. a store defined in the Kernel namespace?)
|
52
|
+
klass = const_get(args.first.to_s.camelize)
|
53
|
+
else
|
54
|
+
raise ArgumentError, "Unexpected type for storage instance: %s"%[args.first.class]
|
55
|
+
end
|
56
|
+
|
57
|
+
if klass
|
58
|
+
@instance = klass.new(*args[1..-1])
|
59
|
+
end
|
60
|
+
|
61
|
+
case @instance
|
62
|
+
when Nodes::BaseStore
|
63
|
+
::Circuit.const_set(:Node, @instance.class.const_get(:Node))
|
64
|
+
when Sites::BaseStore
|
65
|
+
::Circuit.const_set(:Site, @instance.class.const_get(:Site))
|
66
|
+
else
|
67
|
+
bad_instance = @instance; @instance = nil
|
68
|
+
raise ArgumentError, "Cannot determine a Site or Node class for storage type: %s"%[bad_instance.class]
|
69
|
+
end
|
70
|
+
|
71
|
+
@instance
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_model/naming'
|
3
|
+
require 'active_model/attribute_methods'
|
4
|
+
|
5
|
+
module Circuit
|
6
|
+
module Storage
|
7
|
+
module MemoryModel
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
include ActiveModel::AttributeMethods
|
11
|
+
include Compatibility::ActiveModel31
|
12
|
+
attr_reader :attributes, :errors
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
included do
|
16
|
+
extend ActiveModel::Naming
|
17
|
+
class_attribute :all
|
18
|
+
self.all = Array.new
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def setup_attributes(*attrs)
|
23
|
+
attribute_method_suffix '?'
|
24
|
+
attribute_method_suffix '='
|
25
|
+
define_attribute_methods attrs.collect(&:to_sym)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def attributes=(hash)
|
30
|
+
@attributes = hash.with_indifferent_access
|
31
|
+
end
|
32
|
+
|
33
|
+
def save!
|
34
|
+
self.save ? true : raise("Invalid %s: %p"%[self.class, self.errors])
|
35
|
+
end
|
36
|
+
|
37
|
+
def persisted?
|
38
|
+
@persisted
|
39
|
+
end
|
40
|
+
|
41
|
+
def persisted!(val=true)
|
42
|
+
@persisted = val
|
43
|
+
end
|
44
|
+
|
45
|
+
def eql?(obj)
|
46
|
+
obj.instance_of?(self.class) && obj.attributes == self.attributes
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def attribute(key)
|
52
|
+
attributes[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
def attribute=(key, val)
|
56
|
+
attributes[key] = val
|
57
|
+
end
|
58
|
+
|
59
|
+
def attribute?(attr)
|
60
|
+
!attributes[attr.to_sym].blank?
|
61
|
+
end
|
62
|
+
|
63
|
+
def memory_model_setup
|
64
|
+
@persisted = false
|
65
|
+
@attributes = HashWithIndifferentAccess.new
|
66
|
+
@errors = ActiveModel::Errors.new(self)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|