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