Permalink
Browse files

column.rb: removed dead code

column.rb: removed dead code
readme.md: updated documentation on types and validations
finder_query.rb: updated some nodoc methods
validatable.rb: added lots of framework goodness
                added validations for length, presence, format, email
validation_spec.rb: added
version.rb: bumped to 0.3.1
  • Loading branch information...
sxross committed Dec 15, 2012
1 parent c485ea8 commit e78f9d06087bf72d086bf86aa27192772d8d62ee
View
@@ -128,6 +128,46 @@ a_task = Task.create(:name => 'joe-bob', :due_date => '2012-09-15') # due_da
a_task.due_date = '2012-09-19' # due_date is cast to NSDate
```
+Currently supported types are:
+
+* `:string`
+* `:boolean`, `:bool`
+* `:int`, `:integer`
+* `:float`, `:double`
+* `:date`
+* `:array`
+
+You are really not encouraged to stuff big things in your models, which is why a blob type
+is not implemented. The smaller your data, the less overhead involved in saving/loading.
+
+What Validation Methods Exist
+-----------------
+
+ validate :field_name, :presence => true
+ validate :field_name, :length => 5..8 # specify a range
+ validate :field_name, :email
+ validate :field_name, :format
+
+The framework is sufficiently flexible that you can add in custom validators like so:
+
+```ruby
+module MotionModel
+ module Validatable
+ def validate_foo(field, value, setting)
+ # do whatever you need to make sure that the value
+ # denoted by *value* for the field corresponds to
+ # whatever is passed in setting.
+ end
+ end
+end
+
+validate :my_field, :foo => 42
+```
+
+In the above example, your new `validate_foo` method will get the arguments
+pretty much as you expect. The value of the
+last hash is passed intact via the `settings` argument.
+
Model Instances and Unique IDs
-----------------
@@ -13,16 +13,6 @@ def initialize(name = nil, type = nil, options = {})
@destroy = options[:dependent]
end
- # REVIEW: Dead code?
- def add_attr(name, type, options)
- @name = name
- @type = type
- @default = options[:default]
- @destroy = options[:dependent]
- end
-
- alias_method :add_attribute, :add_attr
-
def classify
case @type
when :belongs_to
@@ -7,7 +7,7 @@ def initialize(*args)#nodoc
@collection = args.last
end
- def belongs_to(obj, klass = nil)
+ def belongs_to(obj, klass = nil) #nodoc
@related_object = obj
@klass = klass
self
@@ -18,8 +18,6 @@ def belongs_to(obj, klass = nil)
# Task.find(:name => 'bob').and(:gender).eq('M')
# Task.asignees.where(:assignee_name).eq('bob')
def and(field_name)
- # TODO: Allow for Task.assignees.where(:assignee_name => 'bob')
-
@field_name = field_name
self
end
@@ -40,7 +38,6 @@ def order(field = nil, &block)
self
end
- ######## relational operators ########
def translate_case(item, case_sensitive)#nodoc
item = item.underscore if case_sensitive === false && item.respond_to?(:underscore)
item
@@ -204,6 +201,7 @@ def new(options = {})
new_obj
end
+ # Returns number of objects (rows) in collection
def length
@collection.length
end
@@ -223,7 +221,5 @@ def push(object)
result
end
alias_method :<<, :push
-
-
end
-end
+end
@@ -16,9 +16,6 @@
#
# Now, you can write code like:
#
-# Task.create :task_name => 'Walk the dog',
-# :details => 'Pick up after yourself',
-# :due_date => '2012-09-17'
#
# Recognized types are:
#
@@ -80,8 +77,6 @@ def bulk_update(&block)
def columns(*fields)
return @_columns.map{|c| c.name} if fields.empty?
- # col = Column.new # REVIEW: Dead code?
-
case fields.first
when Hash
column_from_hash fields
@@ -1,5 +1,7 @@
module MotionModel
module Validatable
+ class ValidationSpecificationError < RuntimeError; end
+
def self.included(base)
base.extend(ClassMethods)
base.instance_variable_set('@validations', [])
@@ -12,53 +14,140 @@ def validate(field = nil, validation_type = {})
raise ex
end
- if validation_type == {} # || !(validation_type is_a?(Hash))
+ if validation_type == {}
ex = ValidationSpecificationError.new('validation type not present or not a hash')
raise ex
end
@validations << {field => validation_type}
- end
+ end
+ alias :validate, :validates
+
+ def validations
+ @validations
+ end
end
+ # This has two functions:
+ #
+ # * First, it triggers validations.
+ #
+ # * Second, it returns the result of performing the validations.
def valid?
@messages = []
@valid = true
- self.class.instance_variable_get(@validations).each do |validations|
+ self.class.validations.each do |validations|
validate_each(validations)
end
@valid
end
- def validate_each(validations)
+ # Raw array of hashes of error messages.
+ def error_messages
+ @messages
+ end
+
+ # Array of messages for a given field. Results are always an array
+ # because a field can fail multiple validations.
+ def error_messages_for(field)
+ key = field.to_sym
+ error_messages.select{|message| message.has_key?(key)}.map{|message| message[key]}
+ end
+
+ def validate_each(validations) #nodoc
validations.each_pair do |field, validation|
- validate_one field, validation
+ @valid &&= validate_one field, validation
+ end
+ end
+
+ def validation_method(validation_type) #nodoc
+ validation_method = "validate_#{validation_type}".to_sym
+ end
+
+ def each_validation_for(field) #nodoc
+ self.class.validations.select{|validation| validation.has_key?(field)}.each do |validation|
+ validation.each_pair do |field, validation_hash|
+ yield validation_hash
+ end
+ end
+ end
+
+ # Validates an arbitrary string against a specific field's validators.
+ # Useful before setting the value of a model's field. I.e., you get data
+ # from a form, do a <tt>validate_for(:my_field, that_data)</tt> and
+ # if it succeeds, you do <tt>obj.my_field = that_data</tt>.
+ def validate_for(field, value)
+ @messages = []
+ key = field.to_sym
+ result = true
+ each_validation_for(key) do |validation|
+ validation.each_pair do |validation_type, setting|
+ method = validation_method(validation_type)
+ if self.respond_to? method
+ value.strip! if value.is_a?(String)
+ result &&= self.send(method, field, value, setting)
+ end
+ end
end
+ result
end
- def validate_one(field, validation)
+ def validate_one(field, validation) #nodoc
+ result = true
validation.each_pair do |validation_type, setting|
- case validation_type
- when :presence
- @valid &&= validate_presence(field)
- if setting
- additional_message = "non-empty"
- else
- additional_message = "empty"
- end
- @valid = !@valid if setting == false
- @messages << {field => "incorrect value supplied for #{field.to_s} -- should be #{additional_message}"}
+ if self.respond_to? validation_method(validation_type)
+ value = self.send(field)
+ value.strip! if value.is_a?(String)
+ result &&= self.send(validation_method(validation_type), field, value, setting)
else
- @valid = false
ex = ValidationSpecificationError.new("unknown validation type :#{validation_type.to_s}")
end
end
+ result
end
-
- def validate_presence(field)
- value = self.send(field.to_s)
- return false if value.nil?
- return value.strip.length > 0
+
+ # Validates that something has been entered in a field
+ def validate_presence(field, value, setting)
+ if value.is_a?(String) || value.nil?
+ result = value.nil? || ((value.length == 0) == setting)
+ additional_message = setting ? "non-empty" : "non-empty"
+ add_message(field, "incorrect value supplied for #{field.to_s} -- should be #{additional_message}.") if result
+ return !result
+ end
+ return false
+ end
+
+ # Validates that the length is in a given range of characters. E.g.,
+ #
+ # validate :name, :length => 5..8
+ def validate_length(field, value, setting)
+ if value.is_a?(String) || value.nil?
+ result = value.nil? || (value.length < setting.first || value.length > setting.last)
+ add_message(field, "incorrect value supplied for #{field.to_s} -- should be between #{setting.first} and #{setting.last} characters long.") if result
+ return !result
+ end
+ return false
+ end
+
+ def validate_email(field, value, setting)
+ if value.is_a?(String) || value.nil?
+ result = value.nil? || value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i).nil?
+ add_message(field, "#{field.to_s} does not appear to be an email address.") if result
+ end
+ return !result
+ end
+
+ # Validates contents of field against a given Regexp. This can be tricky because you need
+ # to anchor both sides in most cases using \A and \Z to get a reliable match.
+ def validate_format(field, value, setting)
+ result = value.nil? || setting.match(value).nil?
+ add_message(field, "#{field.to_s} does not appear to be in the proper format.") if result
+ return !result
+ end
+
+ # Add a message for <tt>field</tt> to the messages collection.
+ def add_message(field, message)
+ @messages.push({field.to_sym => message})
end
end
end
@@ -1,3 +1,3 @@
module MotionModel
- VERSION = "0.3.0"
+ VERSION = "0.3.1"
end
Oops, something went wrong.

0 comments on commit e78f9d0

Please sign in to comment.