Skip to content

Commit

Permalink
column.rb: removed dead code
Browse files Browse the repository at this point in the history
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 e78f9d0
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 45 deletions.
40 changes: 40 additions & 0 deletions README.md
Expand Up @@ -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
-----------------

Expand Down
10 changes: 0 additions & 10 deletions lib/motion_model/model/column.rb
Expand Up @@ -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
Expand Down
10 changes: 3 additions & 7 deletions lib/motion_model/model/finder_query.rb
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -204,6 +201,7 @@ def new(options = {})
new_obj
end

# Returns number of objects (rows) in collection
def length
@collection.length
end
Expand All @@ -223,7 +221,5 @@ def push(object)
result
end
alias_method :<<, :push


end
end
end
5 changes: 0 additions & 5 deletions lib/motion_model/model/model.rb
Expand Up @@ -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:
#
Expand Down Expand Up @@ -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
Expand Down
133 changes: 111 additions & 22 deletions lib/motion_model/validatable.rb
@@ -1,5 +1,7 @@
module MotionModel
module Validatable
class ValidationSpecificationError < RuntimeError; end

def self.included(base)
base.extend(ClassMethods)
base.instance_variable_set('@validations', [])
Expand All @@ -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
2 changes: 1 addition & 1 deletion lib/motion_model/version.rb
@@ -1,3 +1,3 @@
module MotionModel
VERSION = "0.3.0"
VERSION = "0.3.1"
end

0 comments on commit e78f9d0

Please sign in to comment.