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