Skip to content

Commit

Permalink
Added factories for all validations. Configuration factories can now …
Browse files Browse the repository at this point in the history
…only be set once.
  • Loading branch information
AlphaHydrae committed Jan 27, 2015
1 parent 9fc654a commit b160f27
Show file tree
Hide file tree
Showing 18 changed files with 244 additions and 131 deletions.
1 change: 1 addition & 0 deletions TODO.md
@@ -0,0 +1 @@
* test validation factories
47 changes: 26 additions & 21 deletions lib/errapi.rb
Expand Up @@ -6,16 +6,23 @@ module Errapi

module Errapi

def self.configure name = nil, &block
def self.configure *args, &block

init_configs
name ||= :default
options = args.last.kind_of?(Hash) ? args.pop : {}
name = args.shift || :default

init_configs
if @configs[name]
@configs[name].configure &block
raise ArgumentError, %/Configuration "#{name}" has already been configured./
else
@configs[name] = Configuration.new &block
@configs[name] = options[:config] || Configuration.new
end

if options.fetch :defaults, true
default_config! @configs[name]
end

@configs[name].configure &block
end

def self.config name = nil
Expand All @@ -25,23 +32,21 @@ def self.config name = nil
private

def self.init_configs
@configs ? @configs : @configs = { default: default_config }
@configs ? @configs : @configs = {}
end

def self.default_config
Configuration.new.tap do |config|
config.plugin Errapi::Plugins::I18nMessages
config.plugin Errapi::Plugins::Reason
config.plugin Errapi::Plugins::Location
config.validation_factory Errapi::Validations::Exclusion
config.validation_factory Errapi::Validations::Format
config.validation_factory Errapi::Validations::Inclusion
config.validation_factory Errapi::Validations::Length
config.validation_factory Errapi::Validations::Presence.new
config.validation_factory Errapi::Validations::Trim
config.validation_factory Errapi::Validations::Type
config.register_condition Errapi::Condition::SimpleCheck
config.register_condition Errapi::Condition::ErrorCheck
end
def self.default_config! config
config.plugin Errapi::Plugins::I18nMessages.new
config.plugin Errapi::Plugins::Reason.new
config.plugin Errapi::Plugins::Location.new
config.validation_factory Errapi::Validations::Exclusion::Factory.new
config.validation_factory Errapi::Validations::Format::Factory.new
config.validation_factory Errapi::Validations::Inclusion::Factory.new
config.validation_factory Errapi::Validations::Length::Factory.new
config.validation_factory Errapi::Validations::Presence::Factory.new
config.validation_factory Errapi::Validations::Trim::Factory.new
config.validation_factory Errapi::Validations::Type::Factory.new
config.condition_factory Errapi::Condition::SimpleCheck
config.condition_factory Errapi::Condition::ErrorCheck
end
end
53 changes: 49 additions & 4 deletions lib/errapi/configuration.rb
Expand Up @@ -12,10 +12,29 @@ def initialize
@validation_factories = {}
@condition_factories = {}
@location_factories = {}
@configured = false
@configured_blocks = []
end

def configured?
@configured
end

def configure
yield self
raise "Configuration can only be done once." if @configured
yield self if block_given?
@configured_blocks.each{ |block| block.call }
@configured_blocks.clear
@configured = true
self
end

def on_configured &block
if @configured
block.call
else
@configured_blocks << block
end
end

def new_error options = {}
Expand All @@ -35,30 +54,46 @@ def new_context
end

def plugin impl, options = {}
name = options[:name] || Utils.underscore(impl.to_s.sub(/.*::/, '')).to_sym
name = implementation_name impl, options
impl.config = self if impl.respond_to? :config=
@plugins[name] = impl
end

def remove_plugin name
raise ArgumentError, "No plugin registered for name #{name.inspect}" unless @plugins.key? name
@plugins.delete name
end

def validation_factory factory, options = {}
name = options[:name] || Utils.underscore(factory.to_s.sub(/.*::/, '')).to_sym
name = implementation_name factory, options
factory.config = self if factory.respond_to? :config=
@validation_factories[name] = factory
end

def remove_validation_factory name
raise ArgumentError, "No validation factory registered for name #{name.inspect}" unless @validation_factories.key? name
@validation_factories.delete name
end

def validation name, options = {}
raise ArgumentError, "No validation factory registered for name #{name.inspect}" unless @validation_factories.key? name
factory = @validation_factories[name]
factory.respond_to?(:validation) ? factory.validation(options) : factory.new(options)
end

def register_condition factory
def condition_factory factory
factory.conditionals.each do |conditional|
raise ArgumentError, "Conditional #{conditional} should start with 'if' or 'unless'." unless conditional.to_s.match /^(if|unless)/
@condition_factories[conditional] = factory
end
end

def remove_condition_factory factory
factory.conditionals.each do |conditional|
@condition_factories.delete conditional
end
end

def extract_conditions! source, options = {}
[].tap do |conditions|
@condition_factories.each_pair do |conditional,factory|
Expand All @@ -70,6 +105,16 @@ def extract_conditions! source, options = {}

private

def implementation_name impl, options = {}
if options[:name]
options[:name].to_sym
elsif impl.respond_to? :name
impl.name.to_sym
else
raise ArgumentError, "Plugins and factories added to a configuration must respond to #name or be supplied with the :name option."
end
end

def apply_plugins operation, *args
@plugins.each_pair do |name,plugin|
plugin.send operation, *args if plugin.respond_to? operation
Expand Down
13 changes: 11 additions & 2 deletions lib/errapi/object_validator.rb
Expand Up @@ -5,14 +5,18 @@ module Errapi
class ObjectValidator
include LocationBuilders

# TODO: remove "options" or if used, pass them to new validators instantiated in #register_validations
def initialize config, options = {}, &block
# TODO: remove these options or if used, pass them to new validators instantiated in #register_validations
@config = config
@validations = []
instance_eval &block if block

@config.on_configured do
instance_eval &block if block
end
end

def validates *args, &block
check_config!
register_validations *args, &block
end

Expand All @@ -27,6 +31,7 @@ def validates_each *args, &block
end

def validate value, context, options = {}
check_config!
# TODO: skip validation by default if previous errors at current location
# TODO: add support for previous value and skip validation by default if value is unchanged

Expand Down Expand Up @@ -186,6 +191,10 @@ def register_validations *args, &block
@validations << validations_definition
end

def check_config!
raise "Configuration has not been done. You must call Errapi.config to complete the configuration process." unless @config.configured?
end

class ContextProxy
instance_methods.each{ |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
attr_accessor :current_location
Expand Down
10 changes: 10 additions & 0 deletions lib/errapi/plugins.rb
@@ -1,4 +1,14 @@
module Errapi::Plugins
class Base

def self.plugin_name name = nil
name ? @name = name : @name
end

def name
self.class.name
end
end
end

Dir[File.join File.dirname(__FILE__), File.basename(__FILE__, '.*'), '*.rb'].each{ |lib| require lib }
5 changes: 3 additions & 2 deletions lib/errapi/plugins/i18n_messages.rb
@@ -1,8 +1,9 @@
require 'i18n'

# TODO: support interpolating source and target name (e.g. "Project name cannot be null.")
class Errapi::Plugins::I18nMessages
class << self
module Errapi::Plugins
class I18nMessages < Base
plugin_name :message

def serialize_error error, serialized
return if serialized.key? :message
Expand Down
38 changes: 19 additions & 19 deletions lib/errapi/plugins/location.rb
@@ -1,29 +1,29 @@
module Errapi
class Plugins::Location
class << self
attr_writer :config
attr_accessor :camelize
module Errapi::Plugins
class Location < Base
plugin_name :location

def serialize_error error, serialized
if error.location && error.location.respond_to?(:serialize)
attr_writer :config
attr_accessor :camelize

serialized_location = error.location.serialize
unless serialized_location.nil?
serialized[:location] = serialized_location
serialized[location_type_key] = error.location.location_type if error.location.respond_to? :location_type
end
def serialize_error error, serialized
if error.location && error.location.respond_to?(:serialize)

serialized_location = error.location.serialize
unless serialized_location.nil?
serialized[:location] = serialized_location
serialized[location_type_key] = error.location.location_type if error.location.respond_to? :location_type
end
end
end

private
private

def location_type_key
camelize? ? :locationType : :location_type
end
def location_type_key
camelize? ? :locationType : :location_type
end

def camelize?
@camelize.nil? ? @config.options.camelize : @camelize
end
def camelize?
@camelize.nil? ? @config.options.camelize : @camelize
end
end
end
30 changes: 15 additions & 15 deletions lib/errapi/plugins/reason.rb
@@ -1,22 +1,22 @@
module Errapi
class Plugins::Reason
class << self
attr_writer :config
attr_accessor :camelize
module Errapi::Plugins
class Reason < Base
plugin_name :reason

def serialize_error error, serialized
serialized[:reason] = serialized_reason error
end
attr_writer :config
attr_accessor :camelize

private
def serialize_error error, serialized
serialized[:reason] = serialized_reason error
end

private

def serialized_reason error
camelize? ? Utils.camelize(error.reason.to_s).to_sym : error.reason
end
def serialized_reason error
camelize? ? Utils.camelize(error.reason.to_s).to_sym : error.reason
end

def camelize?
@camelize.nil? ? @config.options.camelize : @camelize
end
def camelize?
@camelize.nil? ? @config.options.camelize : @camelize
end
end
end
31 changes: 25 additions & 6 deletions lib/errapi/validations.rb
@@ -1,3 +1,5 @@
require File.join(File.dirname(__FILE__), 'utils.rb')

module Errapi::Validations

class Base
Expand Down Expand Up @@ -37,19 +39,36 @@ def callable_option_value_error key_desc, type_desc, supplied_value
end
end

class Factory
class ValidationFactory

def self.build impl, options = {}
@validation_class = impl
@name = options[:name] || Errapi::Utils.underscore(impl.to_s.sub(/.*::/, '')).to_sym
end

def self.name
@name
end

def name
self.class.name
end

def self.validation_class
@validation_class
end

def validation_class
self.class.validation_class
end

def config= config
raise "A configuration has already been set for this factory." if @config
@config = config
end

def validation options = {}
self.class.const_get('Implementation').new options
end

def to_s
Errapi::Utils.underscore self.class.name.sub(/.*::/, '')
validation_class.new options
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/errapi/validations/exclusion.rb
Expand Up @@ -2,6 +2,10 @@

module Errapi::Validations
class Exclusion < Base
class Factory < ValidationFactory
build Exclusion
end

include Clusivity

def initialize options = {}
Expand Down
3 changes: 3 additions & 0 deletions lib/errapi/validations/format.rb
@@ -1,5 +1,8 @@
module Errapi::Validations
class Format < Base
class Factory < ValidationFactory
build Format
end

def initialize options = {}
unless key = exactly_one_option?(OPTIONS, options)
Expand Down

0 comments on commit b160f27

Please sign in to comment.