# Copyright (C) 2011 AMEE UK Ltd. - http://www.amee.com
# Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
# :title: Class: AMEE::DataAbstraction::Input
module AMEE
module DataAbstraction
# Subclass of Term providing methods and attributes appropriate for
# representing calculation inputs specifically
#
class Input < Term
attr_accessor :dirty
# Returns the valid choices for this input
# (Abstract, implemented only for subclasses of input.)
def choices
raise NotImplementedError
end
# Returns an ppropriate data structure for a rails select list form helper.
def options_for_select
[[nil,nil]]+choices.map{|x|[x.underscore.humanize,x] unless x.nil? }.compact
end
# Initialization of Input objects follows that of the parent
# Term class.
#
def initialize(options={},&block)
@validation = nil
validation_message {"#{name} is invalid."}
super
@dirty = false
end
# Configures the value of self to be fixed to val, i.e.
# the value is read-only.
#
def fixed val
value(val)
@fixed=true
@optional=false
end
# Block to define custom complaint message for an invalid value.
def validation_message(&block)
@validation_block=block
end
# Set a default validation message appropriate for input terms which have
# a list of choices.
def choice_validation_message
validation_message {"#{name} is invalid because #{value} is not one of #{choices.join(', ')}."}
end
# Represents the value of self. Set a value by passing an argument.
# Retrieve a value by calling without an argument, e.g.,
#
# my_term.value 12345
#
# my_term.value #=> 12345
#
# If self is configured to have a fixed value and a value is passed
# which does not correspond to the fixed value, a FixedValueInterference
# exception is raised.
#
def value(*args)
unless args.empty?
if args.first.to_s != @value.to_s
raise Exceptions::FixedValueInterference if fixed?
mark_as_dirty
end
end
super
end
# Represents a custom object, symbol or pattern (to be called via ===) to
# determine the whether value set for self should be considered
# acceptable. The following symbols are acceptable :numeric, :date or
# :datetime If validation is specified using a Proc object, the term
# value should be initialized as the block variable. E.g.,
#
# my_input.validation 20
#
# my_input.valid? #=> true
#
# my_input.value 'some string'
# my_input.valid? #=> false
#
# my_input.value 21
# my_input.valid? #=> false
#
# my_input.value 20
# my_input.valid? #=> true
#
# ---
#
# my_input.validation lambda{ |value| value.is_a? Numeric }
#
# my_input.valid? #=> true
#
# my_input.value 'some string'
# my_input.valid? #=> false
#
# my_input.value 12345
# my_input.valid? #=> true
#
# ---
#
# my_input.validation :numeric
#
# my_input.valid? #=> false
#
# my_input.value 21
# my_input.valid? #=> true
#
# my_input.value "20"
# my_input.valid? #=> true
#
# my_input.value "e"
# my_input.valid? #=> false
def validation(*args)
unless args.empty?
if args.first.is_a?(Symbol)
@validation = case args.first
when :numeric then lambda{|v| v.is_a?(Fixnum) || v.is_a?(Integer) || v.is_a?(Float) || v.is_a?(BigDecimal) || (v.is_a?(String) && v.match(/^[\d\.]+$/))}
when :date then lambda{|v| v.is_a?(Date) || v.is_a?(DateTime) || Date.parse(v) rescue nil}
when :datetime then lambda{|v| v.is_a?(Time) || v.is_a?(DateTime) || DateTime.parse(v) rescue nil}
end
else
@validation = args.first
end
end
@validation
end
# Returns true if self is configured to contain a fixed (read-only)
# value
#
def fixed?
@fixed
end
# Returns true if the value of self does not need to be
# specified for the parent calculation to calculate a result. Otherwise,
# returns false
#
def optional?(usage=nil)
@optional
end
# Returns true if the value of self is required in order
# for the parent calculation to calculate a result. Otherwise, returns
# false
#
def compulsory?(usage=nil)
!optional?(usage)
end
# Manually set the term as optional
def optional!
@optional=true
end
# Manually set the term as compuslory
def compulsory!
@optional=false
end
# Check that the value of self is valid. If invalid, and is defined
# as part of a calculation, add the invalidity message to the parent
# calculation's error list. Otherwise, raise a ChoiceValidation
# exception.
#
def validate!
# Typically, you just wipe yourself if supplied value not valid, but
# deriving classes might want to raise an exception
#
invalid unless fixed? || valid?
end
# Declare the calculation invalid, reporting to the parent calculation or
# raising an exception, as appropriate.
#
def invalid
if parent
parent.invalid(label,instance_eval(&@validation_block))
else
raise AMEE::DataAbstraction::Exceptions::ChoiceValidation.new(instance_eval(&@validation_block))
end
end
# Returns true if the UI element of self is disabled.
# Otherwise, returns false.
#
def disabled?
super || fixed?
end
def dirty?
@dirty
end
protected
# Returns true if the value set for self is either blank
# or passes custom validation criteria. Otherwise, returns false.
#
def valid?
validation.blank? || validation === @value_before_cast
end
def mark_as_dirty
@dirty = true
parent.dirty! if parent and parent.is_a? OngoingCalculation
end
end
end
end