circuit 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|