Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit e78f9d06087bf72d086bf86aa27192772d8d62ee 1 parent c485ea8
@sxross authored
View
40 README.md
@@ -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
-----------------
View
10 lib/motion_model/model/column.rb
@@ -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
View
10 lib/motion_model/model/finder_query.rb
@@ -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
View
5 lib/motion_model/model/model.rb
@@ -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
View
133 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', [])
@@ -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
View
2  lib/motion_model/version.rb
@@ -1,3 +1,3 @@
module MotionModel
- VERSION = "0.3.0"
+ VERSION = "0.3.1"
end
View
110 spec/validation_spec.rb
@@ -0,0 +1,110 @@
+class Hash
+ def except(keys)
+ self.dup.reject{|k, v| keys.include?(k)}
+ end
+end
+
+class ValidatableTask
+ include MotionModel::Model
+ include MotionModel::Validatable
+ columns :name => :string,
+ :email => :string,
+ :some_day => :string
+
+ validate :name, :presence => true
+ validate :name, :length => 2..10
+ validate :email, :email => true
+ validate :some_day, :format => /\A\d?\d-\d?\d-\d\d\Z/
+ validate :some_day, :length => 8..10
+end
+
+describe "validations" do
+ before do
+ @valid_tasks = {
+ :name => 'bob',
+ :email => 'bob@domain.com',
+ :some_day => '12-12-12'
+ }
+ end
+
+ describe "presence" do
+ it "is initially false if name is blank" do
+ task = ValidatableTask.new(@valid_tasks.except(:name))
+ task.valid?.should === false
+ end
+
+ it "contains correct error message if name is blank" do
+ task = ValidatableTask.new(@valid_tasks.except(:name))
+ task.valid?
+ task.error_messages_for(:name).first.should ==
+ "incorrect value supplied for name -- should be non-empty."
+ end
+
+ it "is true if name is filled in" do
+ task = ValidatableTask.create(@valid_tasks.except(:name))
+ task.name = 'bob'
+ task.valid?.should === true
+ end
+ end
+
+ describe "length" do
+ it "succeeds when in range of 2-10 characters" do
+ task = ValidatableTask.create(@valid_tasks.except(:name))
+ task.name = '123456'
+ task.valid?.should === true
+ end
+
+ it "fails when length less than two characters" do
+ task = ValidatableTask.create(@valid_tasks.except(:name))
+ task.name = '1'
+ task.valid?.should === false
+ task.error_messages_for(:name).first.should ==
+ "incorrect value supplied for name -- should be between 2 and 10 characters long."
+ end
+
+ it "fails when length greater than 10 characters" do
+ task = ValidatableTask.create(@valid_tasks.except(:name))
+ task.name = '123456709AB'
+ task.valid?.should === false
+ task.error_messages_for(:name).first.should ==
+ "incorrect value supplied for name -- should be between 2 and 10 characters long."
+ end
+ end
+
+ describe "email" do
+ it "succeeds when a valid email address is supplied" do
+ ValidatableTask.new(@valid_tasks).should.be.valid?
+ end
+
+ it "fails when an empty email address is supplied" do
+ ValidatableTask.new(@valid_tasks.except(:email)).should.not.be.valid?
+ end
+
+ it "fails when a bogus email address is supplied" do
+ ValidatableTask.new(@valid_tasks.except(:email).merge({:email => 'bogus'})).should.not.be.valid?
+ end
+ end
+
+ describe "format" do
+ it "succeeds when date is in the correct format" do
+ ValidatableTask.new(@valid_tasks).should.be.valid?
+ end
+
+ it "fails when date is in incorrect format" do
+ ValidatableTask.new(@valid_tasks.except(:some_day).merge({:some_day => 'a-12-12'})).should.not.be.valid?
+ end
+ end
+
+ describe "validating one element" do
+ it "validates any properly formatted arbitrary string and succeeds" do
+ task = ValidatableTask.new
+ task.validate_for(:some_day, '12-12-12').should == true
+ end
+
+ it "validates any improperly formatted arbitrary string and fails" do
+ task = ValidatableTask.new
+ task.validate_for(:some_day, 'a-12-12').should == false
+ end
+
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.