Skip to content

Commit

Permalink
Merge emmanuel's refactorings:
Browse files Browse the repository at this point in the history
- datamapper#25 emmanuel/feature/clean_and_refactor
- datamapper#26 emmanuel/feature/remove_chainable
- datamapper#28 emmanuel/feature/externalize_responsibilities
- datamapper#29 emmanuel/feature/refactor_context

Conflicts:
	lib/dm-validations.rb
	lib/dm-validations/support/context.rb
  • Loading branch information
xaviershay committed Jun 2, 2011
5 parents a3a6397 + 4aaef7d + a524b36 + 0c2e54b + 6d8ff54 commit 1cb55b8
Show file tree
Hide file tree
Showing 19 changed files with 363 additions and 301 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Expand Up @@ -6,7 +6,7 @@ SOURCE = ENV.fetch('SOURCE', :git).to_sym
REPO_POSTFIX = SOURCE == :path ? '' : '.git'
DATAMAPPER = SOURCE == :path ? Pathname(__FILE__).dirname.parent : 'http://github.com/datamapper'
DM_VERSION = '~> 1.1.0'
DO_VERSION = '~> 0.10.2'
DO_VERSION = '~> 0.10.4'
DM_DO_ADAPTERS = %w[ sqlite postgres mysql oracle sqlserver ]

gem 'dm-core', DM_VERSION, SOURCE => "#{DATAMAPPER}/dm-core#{REPO_POSTFIX}"
Expand Down
136 changes: 53 additions & 83 deletions lib/dm-validations.rb
Expand Up @@ -17,16 +17,22 @@ def try_call(*args)
end

module DataMapper
class Property
def self.new(model, name, options = {})
property = super
property.model.auto_generate_validations(property)
module Validations
module PropertyExtensions
# @api private
def new(*)
property = super

# FIXME: explicit return needed for YARD to parse this properly
return property
end
end
end
property.model.auto_generate_validations(property)

# FIXME: explicit return needed for YARD to parse this properly
return property
end
end # module PropertyExtensions
end # module Validations

Property.extend Validations::PropertyExtensions
end # module DataMapper

require 'dm-validations/exceptions'
require 'dm-validations/validation_errors'
Expand Down Expand Up @@ -55,32 +61,26 @@ module Validations

Model.append_inclusions self

extend Chainable

def self.included(model)
model.extend ClassMethods
end

# Ensures the object is valid for the context provided, and otherwise
# throws :halt and returns false.
#
chainable do
def save(context = default_validation_context)
validation_context(context) { super() }
end
def save(context = default_validation_context)
model.validators.assert_valid(context)
Validations::Context.in_context(context) { super() }
end

chainable do
def update(attributes = {}, context = default_validation_context)
validation_context(context) { super(attributes) }
end
def update(attributes = {}, context = default_validation_context)
model.validators.assert_valid(context)
Validations::Context.in_context(context) { super(attributes) }
end

chainable do
def save_self(*)
return false unless !dirty_self? || validation_context_stack.empty? || valid?(current_validation_context)
super
end
def save_self(*)
return false unless !dirty_self? || Validations::Context.stack.empty? || valid?(model.validators.current_context)
super
end

# Return the ValidationErrors
Expand All @@ -91,39 +91,32 @@ def errors

# Mark this resource as validatable. When we validate associations of a
# resource we can check if they respond to validatable? before trying to
# recursivly validate them
# recursively validate them
#
def validatable?
true
end

# Alias for valid?(:default)
#
# TODO: deprecate
def valid_for_default?
valid?(:default)
end

# Check if a resource is valid in a given context
#
# @api public
def valid?(context = :default)
klass = respond_to?(:model) ? model : self.class
klass.validators.execute(context, self)
model = respond_to?(:model) ? self.model : self.class
model.validators.execute(context, self)
end

# @api semipublic
def validation_property_value(name)
__send__(name) if respond_to?(name, true)
end

# Get the corresponding Resource property, if it exists.
#
# Note: DataMapper validations can be used on non-DataMapper resources.
# In such cases, the return value will be nil.
def validation_property(field_name)
if respond_to?(:model) && (properties = model.properties(repository.name)) && properties.named?(field_name)
properties[field_name]
end
end

module ClassMethods
include DataMapper::Validations::ValidatesPresence
include DataMapper::Validations::ValidatesAbsence
Expand All @@ -142,13 +135,16 @@ module ClassMethods
# Return the set of contextual validators or create a new one
#
def validators
@validators ||= ContextualValidators.new
@validators ||= ContextualValidators.new(self)
end

def inherited(base)
super
validators.contexts.each do |context, validators|
base.validators.context(context).concat(validators)
self.validators.contexts.each do |context, validators|
validators.each do |v|
options = v.options.merge(:context => context)
base.validators.add(v.class, v.field_name, options)
end
end
end

Expand All @@ -160,57 +156,31 @@ def create(attributes = {}, *args)

private

# Clean up the argument list and return a opts hash, including the
# merging of any default opts. Set the context to default if none is
# provided. Also allow :context to be aliased to :on, :when & group
#
def opts_from_validator_args(args, defaults = nil)
opts = args.last.kind_of?(Hash) ? args.pop.dup : {}
context = opts.delete(:group) || opts.delete(:on) || opts.delete(:when) || opts.delete(:context) || :default
opts[:context] = Array(context)
opts.update(defaults) unless defaults.nil?
opts
end

# Given a new context create an instance method of
# valid_for_<context>? which simply calls valid?(context)
# if it does not already exist
#
def create_context_instance_methods(context)
name = "valid_for_#{context.to_s}?"
unless respond_to?(:resource_method_defined) ? resource_method_defined?(name) : instance_methods.include?(name)
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name} # def valid_for_signup?
valid?(#{context.to_sym.inspect}) # valid?(:signup)
end # end
def self.create_context_instance_methods(model, context)
# TODO: deprecate `valid_for_#{context}?`
# what's wrong with requiring the caller to pass the context as an arg?
# eg, `valid?(:context)`
# these methods are handy for symbol-based callbacks,
# eg. `:if => :valid_for_context?`
# but these methods are so trivial to add where needed, making it
# overkill to do this for all contexts on all validated objects.
context = context.to_sym

name = "valid_for_#{context}?"
present = model.respond_to?(:resource_method_defined) ? model.resource_method_defined?(name) : model.instance_methods.include?(name)
unless present
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name} # def valid_for_signup?
valid?(#{context.inspect}) # valid?(:signup)
end # end
RUBY
end
end

# Create a new validator of the given klazz and push it onto the
# requested context for each of the attributes in the fields list
# @param [Hash] opts
# Options supplied to validation macro, example:
# {:context=>:default, :maximum=>50, :allow_nil=>true, :message=>nil}
#
# @param [Array<Symbol>] fields
# Fields given to validation macro, example:
# [:first_name, :last_name] in validates_presence_of :first_name, :last_name
#
# @param [Class] klazz
# Validator class, example: DataMapper::Validations::LengthValidator
def add_validator_to_context(opts, fields, validator_class)
fields.each do |field|
validator = validator_class.new(field, opts.dup)

opts[:context].each do |context|
validator_contexts = validators.context(context)
next if validator_contexts.include?(validator)
validator_contexts << validator
create_context_instance_methods(context)
end
end
end
end # module ClassMethods
end # module Validations

Expand Down
20 changes: 16 additions & 4 deletions lib/dm-validations/auto_validate.rb
@@ -1,14 +1,14 @@
module DataMapper
class Property
# for options_with_message
accept_options :message, :messages, :set, :validates, :auto_validation, :format
end
# for options_with_message
Property.accept_options :message, :messages, :set, :validates, :auto_validation, :format

module Validations
module AutoValidations
@disable_auto_validations = false

# adds message for validator
#
# @api private
def options_with_message(base_options, property, validator_name)
options = base_options.clone
opts = property.options
Expand All @@ -26,6 +26,8 @@ def options_with_message(base_options, property, validator_name)

# disables generation of validations for
# duration of given block
#
# @api public
def without_auto_validations
@disable_auto_validations = true
yield
Expand Down Expand Up @@ -81,6 +83,7 @@ def without_auto_validations
# :message => "Some message"
# It is just shortcut if only one validation option is set
#
# @api private
def auto_generate_validations(property)
return if (disabled_auto_validations? ||
skip_auto_validation_for?(property))
Expand Down Expand Up @@ -108,6 +111,7 @@ def auto_generate_validations(property)
# @return [TrueClass, FalseClass]
# true if auto validation is currently disabled
#
# @api semipublic
def disabled_auto_validations?
@disable_auto_validations || false
end
Expand All @@ -122,11 +126,13 @@ def disabled_auto_validations?
# true for properties with :auto_validation option that has
# positive value
#
# @api private
def skip_auto_validation_for?(property)
(property.options.key?(:auto_validation) &&
!property.options[:auto_validation])
end

# @api private
def infer_presence_validation_for(property, options)
return if skip_presence_validation?(property)

Expand All @@ -137,6 +143,7 @@ def infer_presence_validation_for(property, options)
)
end

# @api private
def infer_length_validation_for(property, options)
return unless (property.kind_of?(DataMapper::Property::String) ||
property.kind_of?(DataMapper::Property::Text))
Expand All @@ -158,6 +165,7 @@ def infer_length_validation_for(property, options)
)
end

# @api private
def infer_format_validation_for(property, options)
return unless property.options.key?(:format)

Expand All @@ -170,6 +178,7 @@ def infer_format_validation_for(property, options)
)
end

# @api private
def infer_uniqueness_validation_for(property, options)
return unless property.options.key?(:unique)

Expand All @@ -191,6 +200,7 @@ def infer_uniqueness_validation_for(property, options)
end
end

# @api private
def infer_within_validation_for(property, options)
return unless property.options.key?(:set)

Expand All @@ -203,6 +213,7 @@ def infer_within_validation_for(property, options)
)
end

# @api private
def infer_type_validation_for(property, options)
return if property.respond_to?(:custom?) && property.custom?

Expand Down Expand Up @@ -242,6 +253,7 @@ def infer_type_validation_for(property, options)

private

# @api private
def skip_presence_validation?(property)
property.allow_blank? || property.serial?
end
Expand Down

0 comments on commit 1cb55b8

Please sign in to comment.