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.
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