circuit 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/Gemfile +34 -0
  2. data/LICENSE +20 -0
  3. data/README.md +161 -0
  4. data/Rakefile +27 -0
  5. data/config.ru +7 -0
  6. data/description.md +5 -0
  7. data/docs/COMPATIBILITY.md +14 -0
  8. data/docs/ROADMAP.md +29 -0
  9. data/lib/circuit.rb +125 -0
  10. data/lib/circuit/behavior.rb +99 -0
  11. data/lib/circuit/compatibility.rb +73 -0
  12. data/lib/circuit/middleware.rb +6 -0
  13. data/lib/circuit/middleware/rewriter.rb +43 -0
  14. data/lib/circuit/rack.rb +14 -0
  15. data/lib/circuit/rack/behavioral.rb +45 -0
  16. data/lib/circuit/rack/builder.rb +50 -0
  17. data/lib/circuit/rack/multi_site.rb +22 -0
  18. data/lib/circuit/rack/request.rb +81 -0
  19. data/lib/circuit/railtie.rb +24 -0
  20. data/lib/circuit/storage.rb +74 -0
  21. data/lib/circuit/storage/memory_model.rb +70 -0
  22. data/lib/circuit/storage/nodes.rb +56 -0
  23. data/lib/circuit/storage/nodes/memory_store.rb +63 -0
  24. data/lib/circuit/storage/nodes/model.rb +67 -0
  25. data/lib/circuit/storage/nodes/mongoid_store.rb +56 -0
  26. data/lib/circuit/storage/sites.rb +38 -0
  27. data/lib/circuit/storage/sites/memory_store.rb +53 -0
  28. data/lib/circuit/storage/sites/model.rb +29 -0
  29. data/lib/circuit/storage/sites/mongoid_store.rb +43 -0
  30. data/lib/circuit/validators.rb +74 -0
  31. data/lib/circuit/version.rb +3 -0
  32. data/spec/internal/app/behaviors/change_path.rb +7 -0
  33. data/spec/internal/app/controllers/application_controller.rb +5 -0
  34. data/spec/internal/app/helpers/application_helper.rb +2 -0
  35. data/spec/internal/config/initializers/circuit.rb +7 -0
  36. data/spec/internal/config/routes.rb +3 -0
  37. data/spec/internal/db/schema.rb +1 -0
  38. data/spec/lib/circuit/behavior_spec.rb +113 -0
  39. data/spec/lib/circuit/middleware/rewriter_spec.rb +79 -0
  40. data/spec/lib/circuit/rack/behavioral_spec.rb +60 -0
  41. data/spec/lib/circuit/rack/builder_spec.rb +125 -0
  42. data/spec/lib/circuit/rack/multi_site_spec.rb +34 -0
  43. data/spec/lib/circuit/rack/request_spec.rb +80 -0
  44. data/spec/lib/circuit/railtie_spec.rb +34 -0
  45. data/spec/lib/circuit/storage/nodes_spec.rb +62 -0
  46. data/spec/lib/circuit/storage/sites_spec.rb +60 -0
  47. data/spec/lib/circuit/storage_spec.rb +20 -0
  48. data/spec/lib/circuit/validators_spec.rb +69 -0
  49. data/spec/lib/circuit_spec.rb +139 -0
  50. data/spec/spec_helper.rb +79 -0
  51. data/spec/support/blueprints.rb +24 -0
  52. data/spec/support/matchers/be_current_time_matcher.rb +14 -0
  53. data/spec/support/matchers/extended_have_key.rb +31 -0
  54. data/spec/support/matchers/have_accessor_matcher.rb +13 -0
  55. data/spec/support/matchers/have_attribute_matcher.rb +22 -0
  56. data/spec/support/matchers/have_block_matcher.rb +14 -0
  57. data/spec/support/matchers/have_errors_on_matcher.rb +30 -0
  58. data/spec/support/matchers/have_module_matcher.rb +13 -0
  59. data/spec/support/matchers/have_reader_matcher.rb +18 -0
  60. data/spec/support/matchers/have_writer_matcher.rb +18 -0
  61. data/spec/support/matchers/set_instance_variable.rb +32 -0
  62. data/spec/support/spec_helpers/base_behaviors.rb +20 -0
  63. data/spec/support/spec_helpers/base_models.rb +64 -0
  64. data/spec/support/spec_helpers/logger_helpers.rb +58 -0
  65. data/spec/support/spec_helpers/multi_site_helper.rb +48 -0
  66. data/spec/support/spec_helpers/rack_helpers.rb +8 -0
  67. data/spec/support/spec_helpers/shared_examples/node_store.rb +87 -0
  68. data/spec/support/spec_helpers/shared_examples/site_store.rb +76 -0
  69. data/spec/support/spec_helpers/simple_machinable.rb +29 -0
  70. data/spec/support/spec_helpers/stores_cleaner.rb +46 -0
  71. data/vendor/active_support-3.2/core_ext/string/inflections.rb +53 -0
  72. data/vendor/active_support-3.2/inflector/methods.rb +65 -0
  73. data/vendor/rack-1.4/builder.rb +167 -0
  74. data/vendor/rack-1.4/urlmap.rb +96 -0
  75. metadata +238 -0
@@ -0,0 +1,56 @@
1
+ module Circuit
2
+ module Storage
3
+ module Nodes
4
+ autoload :Model, 'circuit/storage/nodes/model'
5
+ autoload :MemoryStore, 'circuit/storage/nodes/memory_store'
6
+ autoload :MongoidStore, 'circuit/storage/nodes/mongoid_store'
7
+
8
+ extend Circuit::Storage
9
+
10
+ # Raised when #get isn't overriden/implemented
11
+ class UnimplementedError < CircuitError; end
12
+
13
+ # Raised when a path is not found
14
+ class NotFoundError < CircuitError; end
15
+
16
+ # @abstract Subclass and override {#get}
17
+ class BaseStore
18
+ # @param [Sites::Model] site to find path under
19
+ # @param [String] path to find
20
+ # @return [Array<Model>] array of nodes for each path segment
21
+ def get(site, path)
22
+ raise UnimplementedError, "#{self.class.to_s}#get not implemented."
23
+ end
24
+
25
+ # @raise NotFoundError if the path cannot be found
26
+ # @param (see #get)
27
+ # @return [Model] Node Model
28
+ # @see #get
29
+ def get!(site, path)
30
+ get(site, path) or raise NotFoundError, "Path not found"
31
+ end
32
+
33
+ protected
34
+
35
+ # Iterates over the path segments to find the nodes
36
+ # @param [Model] root node
37
+ # @param [String] path to find
38
+ # @return [Array<Model>] array of node Models
39
+ # @see Rack::Request::ClassMethods#path_segments
40
+ def find_nodes_for_path(root, path)
41
+ raise(NotFoundError, "Root path not found") if root.nil?
42
+ [root].tap do |result|
43
+ ::Rack::Request.path_segments(path).each do |segment|
44
+ next if segment.blank?
45
+ if node = result.last.find_child_by_segment(segment)
46
+ result << node
47
+ else
48
+ break
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ module Circuit
2
+ module Storage
3
+ module Nodes
4
+ # Concrete node store for memory
5
+ class MemoryStore < BaseStore
6
+ # @param [Sites::Model] site to find path under
7
+ # @param [String] path to find
8
+ # @return [Array<Model>] array of nodes for each path segment
9
+ def get(site, path)
10
+ find_nodes_for_path(site.route, path)
11
+ rescue NotFoundError
12
+ return nil
13
+ end
14
+
15
+ # Concrete memory Node class
16
+ class Node
17
+ include Circuit::Storage::MemoryModel
18
+ setup_attributes :slug, :behavior_klass, :site, :parent, :children
19
+
20
+ # @!attribute slug
21
+ # @return [String] path segment slug
22
+
23
+ # @!attribute behavior_klass
24
+ # @return [String] name of Behavior class or module
25
+
26
+ # @!attribute site
27
+ # @return [Sites::Model] site
28
+
29
+ # @!attribute parent
30
+ # @return [Sites::Node] parent node
31
+
32
+ # @!attribute children
33
+ # @return [Array<Sites::Node>] array of child nodes
34
+
35
+ include Circuit::Storage::Nodes::Model
36
+ include Circuit::Storage::Nodes::Model::Validations
37
+
38
+ def initialize(opts={})
39
+ memory_model_setup
40
+ behavior = opts.delete(:behavior) || opts.delete("behavior")
41
+ self.attributes = opts
42
+ self.slug = opts[:slug]
43
+ self.behavior = behavior if behavior
44
+ self.children ||= Array.new
45
+ end
46
+
47
+ # Save the Node to memory
48
+ # @return [Boolean] `true` if the Node was saved
49
+ def save
50
+ return false if invalid?
51
+ unless persisted?
52
+ self.site.route = self if self.site
53
+ self.parent.children << self if self.parent
54
+ self.children.each {|c| c.parent = self}
55
+ self.class.all << self
56
+ end
57
+ persisted!
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,67 @@
1
+ require 'active_support/concern'
2
+ require 'active_model/validations'
3
+ require 'circuit/validators'
4
+
5
+ module Circuit
6
+ module Storage
7
+ module Nodes
8
+ # @abstract include into a Class or Module to setup the necessary methods
9
+ # for a Node model
10
+ module Model
11
+ extend ActiveSupport::Concern
12
+
13
+ # Validations for Node models
14
+ # * validates the slug's format and presence when not the root
15
+ # * validates the presence of the behavior_klass
16
+ # * validates the presence of the parent if the slug is defined
17
+ # @see Circuit::Validators::SlugValidator
18
+ # @abstract include into a Node class or Module to add the Validations
19
+ module Validations
20
+ extend ActiveSupport::Concern
21
+ include ActiveModel::Validations
22
+
23
+ included do
24
+ validates :slug, :slug => {:allow_blank => true}, :presence => {:unless => :root?}
25
+ validates :behavior_klass, :presence => true
26
+ validates :parent, :presence => true, :if => :slug?
27
+ end
28
+ end
29
+
30
+ # @return [Boolean] `false` if the parent is `nil`
31
+ def root?
32
+ parent.nil?
33
+ end
34
+
35
+ # @return [Class,Module] Behavior class or module for the behavior_klass
36
+ # @see Behavior#get
37
+ def behavior
38
+ return nil if self.behavior_klass.blank?
39
+ Behavior.get(behavior_klass)
40
+ end
41
+
42
+ # @param [Class,Module] klass Module or Class to set the behavior_klass
43
+ def behavior=(klass)
44
+ self.behavior_klass = klass.to_s
45
+ end
46
+
47
+ # @param [String] segment path segment
48
+ # @return [Model] child for the segment or `nil` if not found
49
+ def find_child_by_segment(segment)
50
+ self.children.detect {|c| c.slug == segment}
51
+ end
52
+
53
+ # Walks the tree and forms the full path to the Node
54
+ # @return [String] full path down to this node
55
+ def path
56
+ result = []
57
+ node = self
58
+ while node.parent
59
+ result.unshift node.slug
60
+ node = node.parent
61
+ end
62
+ "/"+result.join("/")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,56 @@
1
+ require 'mongoid/tree'
2
+
3
+ module Circuit
4
+ module Storage
5
+ module Nodes
6
+ # Concrete nodes store for Mongoid
7
+ class MongoidStore < BaseStore
8
+ # @param [Sites::Model] site to find path under
9
+ # @param [String] path to find
10
+ # @return [Array<Model>] array of nodes for each path segment
11
+ def get(site, path)
12
+ find_nodes_for_path(site.route, path)
13
+ rescue NotFoundError
14
+ return nil
15
+ end
16
+
17
+ # Concrete Mongoid Node class
18
+ class Node
19
+ include Model
20
+ include Model::Validations
21
+ include Mongoid::Document
22
+ include Mongoid::Tree
23
+ include Mongoid::Tree::Ordering
24
+ include Mongoid::Tree::Traversal
25
+
26
+ store_in :collection => "circuit_nodes"
27
+
28
+ field :slug, :type => String
29
+ field :behavior_klass, :type => String
30
+
31
+ # @!attribute slug
32
+ # @return [String] path segment slug
33
+
34
+ # @!attribute behavior_klass
35
+ # @return [String] name of Behavior class or module
36
+
37
+ # @!attribute site
38
+ # @return [Sites::Model] site
39
+
40
+ # @!attribute parent
41
+ # @return [Sites::Node] parent node
42
+
43
+ # @!attribute children
44
+ # @return [Array<Sites::Node>] array of child nodes
45
+
46
+ belongs_to :site, :class_name => "Circuit::Site",
47
+ :inverse_of => :route
48
+
49
+ def find_child_by_segment(segment)
50
+ self.children.where(:slug => segment).first
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,38 @@
1
+ module Circuit
2
+ module Storage
3
+ module Sites
4
+ autoload :Model, 'circuit/storage/sites/model'
5
+ autoload :MemoryStore, 'circuit/storage/sites/memory_store'
6
+ autoload :MongoidStore, 'circuit/storage/sites/mongoid_store'
7
+
8
+ extend Circuit::Storage
9
+
10
+ # Raised when #get isn't overriden/implemented
11
+ class UnimplementedError < CircuitError; end
12
+
13
+ # Raised when a site is not found
14
+ class NotFoundError < CircuitError; end
15
+
16
+ # Raised when multiple sites are found for the same host
17
+ class MultipleFoundError < CircuitError; end
18
+
19
+ # @abstract Subclass and override {#get}
20
+ class BaseStore
21
+ # @raise MultipleFoundError if multiple sites are found for the given `host`
22
+ # @param [String] host to find
23
+ # @return [Model] site
24
+ def get(host)
25
+ raise UnimplementedError, "#{self.class.to_s}#get not implemented."
26
+ end
27
+
28
+ # @raise NotFoundError if the site is not found
29
+ # @param (see #get)
30
+ # @return [Model] site model
31
+ # @see #get
32
+ def get!(host)
33
+ get(host) or raise NotFoundError, "Host not found"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,53 @@
1
+ module Circuit
2
+ module Storage
3
+ module Sites
4
+ # Concrete site store for memory
5
+ class MemoryStore < BaseStore
6
+ # @raise MultipleFoundError if multiple sites are found for the given `host`
7
+ # @param [String] host to find
8
+ # @return [Model] site
9
+ def get(host)
10
+ sites = Site.all.select {|s| s.host == host or s.aliases.include?(host)}
11
+ if sites.length > 1
12
+ raise MultipleFoundError, "Multiple sites found"
13
+ end
14
+ sites.first
15
+ end
16
+
17
+ # Concrete memory Site class
18
+ class Site
19
+ include Circuit::Storage::MemoryModel
20
+ setup_attributes :host, :aliases, :route
21
+
22
+ # @!attribute host
23
+ # @return [String] domain name
24
+
25
+ # @!attribute aliases
26
+ # @return [Array<String>] array of domain name aliases
27
+
28
+ # @!attribute route
29
+ # @return [Nodes::Model] root node
30
+
31
+ include Circuit::Storage::Sites::Model
32
+ include Circuit::Storage::Sites::Model::Validations
33
+
34
+ def initialize(opts={})
35
+ memory_model_setup
36
+ self.attributes = opts
37
+ self.aliases ||= Array.new
38
+ end
39
+
40
+ # Save the Site to memory
41
+ # @return [Boolean] `true` if the Site was saved
42
+ def save
43
+ return false if invalid?
44
+ unless persisted?
45
+ self.class.all << self
46
+ end
47
+ persisted!
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ require 'active_support/concern'
2
+ require 'active_model/validations'
3
+ require 'circuit/validators'
4
+
5
+ module Circuit
6
+ module Storage
7
+ module Sites
8
+ # @abstract include into a Class or Module to setup the necessary methods
9
+ # for a Site model
10
+ module Model
11
+ # Validations for Site models
12
+ # * validates the host's format and presence
13
+ # * validates the aliases' formats
14
+ # @see Circuit::Validators::DomainValidator
15
+ # @see Circuit::Validators::DomainArrayValidator
16
+ # @abstract include into a Site class or Module to add the Validations
17
+ module Validations
18
+ extend ActiveSupport::Concern
19
+ include ActiveModel::Validations
20
+
21
+ included do
22
+ validates :host, :presence => true, :domain => true
23
+ validates :aliases, :domain_array => true
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,43 @@
1
+ module Circuit
2
+ module Storage
3
+ module Sites
4
+ # Concrete site store for Mongoid
5
+ class MongoidStore < BaseStore
6
+ # @raise MultipleFoundError if multiple sites are found for the given `host`
7
+ # @param [String] host to find
8
+ # @return [Model] site
9
+ def get(host)
10
+ sites = Circuit::Site.any_of({:host => host}, {:aliases => host}).all
11
+ if sites.length > 1
12
+ raise MultipleFoundError, "Multiple sites found"
13
+ end
14
+ sites.first
15
+ end
16
+
17
+ # Concrete Mongoid Site class
18
+ class Site
19
+ include Model
20
+ include Model::Validations
21
+ include Mongoid::Document
22
+
23
+ store_in :collection => "circuit_sites"
24
+
25
+ field :host, :type => String
26
+ field :aliases, :type => Array
27
+
28
+ # @!attribute host
29
+ # @return [String] domain name
30
+
31
+ # @!attribute aliases
32
+ # @return [Array<String>] array of domain name aliases
33
+
34
+ # @!attribute route
35
+ # @return [Nodes::Model] root node
36
+
37
+ has_one :route, :class_name => "Circuit::Node",
38
+ :inverse_of => :site
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,74 @@
1
+ require 'active_model/validator'
2
+
3
+ module Circuit
4
+ module Validators
5
+ DOMAIN_REGEXP = /\A(([a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?)\.)*([a-z]{2,}|([a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?))\Z/
6
+ SLUG_REGEXP = /\A(?:[-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*\Z/
7
+
8
+ # @!method validates_domain(*attr_names, options)
9
+ # Validates that the attributes are domain names
10
+ # @see ActiveModel::EachValidator
11
+ # @param [Array<Symbol>] attr_names to validate
12
+ # @param [Hash] options for validator
13
+ # @option options [String] :message ("is not a valid domain.") invalid value message
14
+ # @option options [String] :allow_nil (false) true to allow nil values
15
+ # @option options [String] :allow_blank (false) true to allow blank values
16
+
17
+ class DomainValidator < ActiveModel::EachValidator
18
+ def initialize(options)
19
+ options[:message] ||= "is not a valid domain."
20
+ super(options)
21
+ end
22
+
23
+ def validate_each(record, attribute, value)
24
+ record.errors.add attribute, options[:message] unless value =~ DOMAIN_REGEXP
25
+ end
26
+ end
27
+
28
+ # @!method validates_domain_array(*attr_names, options)
29
+ # Validates that an Array is an Array of domain names
30
+ # @see ActiveModel::EachValidator
31
+ # @param [Array<Symbol>] attr_names to validate
32
+ # @param [Hash] options for validator
33
+ # @option options [String] :message ("has an valid domain.") invalid value message
34
+ # @option options [String] :allow_nil (false) true to allow nil values
35
+ # @option options [String] :allow_blank (false) true to allow blank values
36
+
37
+ class DomainArrayValidator < ActiveModel::EachValidator
38
+ def initialize(options)
39
+ options[:message] ||= "has an invalid domain."
40
+ super(options)
41
+ end
42
+
43
+ def validate_each(record, attribute, value)
44
+ if Array.wrap(value).detect { |val| !(val =~ DOMAIN_REGEXP) }
45
+ record.errors.add attribute, options[:message]
46
+ end
47
+ end
48
+ end
49
+
50
+ # @!method validates_slug(*attr_names, options)
51
+ # Validates that the attributes are slugs (i.e. URI path segments)
52
+ # @see ActiveModel::EachValidator
53
+ # @param [Array<Symbol>] attr_names to validate
54
+ # @param [Hash] options for validator
55
+ # @option options [String] :message ("is not a valid path segment.") invalid value message
56
+ # @option options [String] :allow_nil (false) true to allow nil values
57
+ # @option options [String] :allow_blank (false) true to allow blank values
58
+
59
+ class SlugValidator < ActiveModel::EachValidator
60
+ def initialize(options)
61
+ options[:message] ||= "is not a valid path segment."
62
+ super(options)
63
+ end
64
+
65
+ def validate_each(record, attribute, value)
66
+ record.errors.add attribute, options[:message] unless value =~ SLUG_REGEXP
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ DomainValidator = Circuit::Validators::DomainValidator
73
+ DomainArrayValidator = Circuit::Validators::DomainArrayValidator
74
+ SlugValidator = Circuit::Validators::SlugValidator