Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 87c8afcc3f7fa921b5d889fecc9bf2aa4092284d @jeremyevans committed Oct 27, 2009
Showing with 259 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +18 −0 LICENSE
  3. +28 −0 Rakefile
  4. +89 −0 lib/sequel_validation_helpers_block.rb
  5. +12 −0 sequel_validation_helpers_block.gemspec
  6. +110 −0 spec/sequel_validation_helpers_block_spec.rb
@@ -0,0 +1,2 @@
+rdoc
+*.gem
18 LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) 2009 Jeremy Evans
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
+require "rake"
+require "rake/clean"
+require "spec/rake/spectask"
+begin
+ require "hanna/rdoctask"
+rescue LoadError
+ require "rake/rdoctask"
+end
+
+CLEAN.include ["*.gem", "rdoc"]
+RDOC_OPTS = ['--inline-source', '--line-numbers', '--title', 'Sequel validation_helpers_block: Allows easy determination of which validation rules apply to a given column, at the expense of increased verbosity', '--main', 'Sequel::Plugins::ValidationHelpersBlock']
+
+Rake::RDocTask.new do |rdoc|
+ rdoc.rdoc_dir = "rdoc"
+ rdoc.options += RDOC_OPTS
+ rdoc.rdoc_files.add %w"lib/sequel_validation_helpers_block.rb LICENSE"
+end
+
+desc "Run specs"
+Spec::Rake::SpecTask.new("spec") do |t|
+ t.spec_files = ["spec/sequel_validation_helpers_block_spec.rb"]
+end
+task :default=>[:spec]
+
+desc "Package sequel_validation_helpers_block"
+task :package do
+ sh %{gem build sequel_validation_helpers_block.gemspec}
+end
@@ -0,0 +1,89 @@
+module Sequel
+ module Plugins
+ # The ValidationHelpersBlock plugin allows easy determination of which
+ # validation rules apply to a given column, by grouping validations
+ # by column instead of by validation type. It is significantly more
+ # verbose than the default validation_helpers plugin, but provides
+ # a nicer DSL, for example:
+ #
+ # class Item < Sequel::Model
+ # plugin :validation_helpers_block
+ #
+ # def validate
+ # validates do
+ # name do
+ # presence
+ # max_length 10
+ # end
+ # date do
+ # format %r{\d\d/\d\d/\d\d\d\d}
+ # end
+ # number do
+ # presence
+ # integer
+ # end
+ # end
+ # end
+ # end
+ module ValidationHelpersBlock
+ # Require the validation_helpers plugin
+ def self.apply(model)
+ model.plugin(:validation_helpers)
+ end
+
+ # DSL class used directly inside the validates block.
+ # Methods without an explicit receiver that are called
+ # inside the block are assumed to be attributes of the
+ # object that need to be validated.
+ class ValidationHelpersAttributesBlock < BasicObject
+ # Set the object being validated and instance_eval the block.
+ def initialize(obj, &block)
+ @obj = obj
+ instance_eval(&block)
+ end
+
+ # Create a new ValidationHelpersValidationsBlock object
+ # using the stored object and given attribute name.
+ def method_missing(m, &block)
+ ValidationHelpersValidationsBlock.new(@obj, m, &block)
+ end
+ end
+
+ # DSL class used inside attribute blocks.
+ # The only methods allowed inside the block are those supported
+ # by validation_helpers, and they specify the validations to
+ # run for the related attribute on the related object.
+ class ValidationHelpersValidationsBlock < BasicObject
+ # validation_helpers methods that do not require a leading argument.
+ VALIDATION_HELPERS_NO_ARG_METHODS = [:integer, :not_string, :numeric, :presence, :unique].freeze
+
+ # validation_helpers methods that require a leading argument
+ VALIDATION_HELPERS_1_ARG_METHODS = [:exact_length, :format, :includes, :length_range, :max_length, :min_length].freeze
+
+ # Store the object and attribute and instance_eval the block.
+ def initialize(obj, attr, &block)
+ @obj = obj
+ @attr = attr
+ instance_eval(&block)
+ end
+
+ VALIDATION_HELPERS_NO_ARG_METHODS.each do |m|
+ class_eval("def #{m}(opts={}); @obj.validates_#{m}(@attr, opts); end", __FILE__, __LINE__)
+ end
+
+ VALIDATION_HELPERS_1_ARG_METHODS.each do |m|
+ class_eval("def #{m}(arg, opts={}); @obj.validates_#{m}(arg, @attr, opts); end", __FILE__, __LINE__)
+ end
+ end
+
+ module InstanceMethods
+ private
+
+ # Start a new validation_helpers_block DSL for the given object
+ def validates(&block)
+ ValidationHelpersAttributesBlock.new(self, &block)
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,12 @@
+spec = Gem::Specification.new do |s|
+ s.name = "sequel_validation_helpers_block"
+ s.version = "1.0.0"
+ s.author = "Jeremy Evans"
+ s.email = "code@jeremyevans.net"
+ s.platform = Gem::Platform::RUBY
+ s.summary = "Allows easy determination of which validation rules apply to a given column, at the expense of increased verbosity"
+ s.files = %w'LICENSE lib/sequel_validation_helpers_block.rb spec/sequel_validation_helpers_block_spec.rb'
+ s.require_paths = ["lib"]
+ s.has_rdoc = true
+ s.rdoc_options = ['--inline-source', '--line-numbers', '--title', 'Sequel validation_helpers_block: Allows easy determination of which validation rules apply to a given column, at the expense of increased verbosity', '--main', 'Sequel::Plugins::ValidationHelpersBlock', 'lib/sequel_validation_helpers_block.rb', 'LICENSE']
+end
@@ -0,0 +1,110 @@
+require 'rubygems'
+require 'sequel'
+$: << File.join(File.dirname(__FILE__), '..', 'lib')
+Sequel.extension :blank
+
+describe "Sequel::Plugins::ValidationHelpersBlock" do
+ before do
+ @db = Sequel::Database.new
+ @c = Class.new(Sequel::Model(@db)) do
+ def self.set_validations(&block)
+ define_method(:validate, &block)
+ end
+ set_columns([:name, :date, :number])
+ end
+ @c.plugin :validation_helpers_block
+ @m = @c.new
+ end
+
+ specify "should allow specifying validations in a block format" do
+ @c.set_validations do
+ validates do
+ name do
+ presence
+ max_length 10
+ end
+ date do
+ format %r{\d\d/\d\d/\d\d\d\d}
+ end
+ number do
+ presence
+ integer
+ end
+ end
+ end
+ @m.should_not be_valid
+ @m.errors.should == {:name=>["is not present", "is longer than 10 characters"], :date=>["is invalid"], :number=>["is not present", "is not a number"]}
+ @m.set(:name=>'1234567890-', :number=>'a', :date=>'Tuesday')
+ @m.should_not be_valid
+ @m.errors.should == {:name=>["is longer than 10 characters"], :date=>["is invalid"], :number=>["is not a number"]}
+ @m.set(:name=>'1234', :number=>'10', :date=>'10/11/2009')
+ @m.should be_valid
+ end
+
+ specify "should accept options for validation methods" do
+ @c.set_validations do
+ validates do
+ name do
+ max_length 10, :message=>'cannot be more than 10 characters', :allow_blank=>true
+ end
+ date do
+ format %r{\d\d/\d\d/\d\d\d\d}, :allow_missing=>true
+ end
+ number do
+ integer :allow_nil=>true
+ end
+ end
+ end
+ @m.should be_valid
+ @m.errors.should == {}
+ @m.set(:name=>' ', :number=>nil)
+ @m.should be_valid
+ @m.errors.should == {}
+ @m.set(:name=>' 12 ', :number=>'', :date=>nil)
+ @m.should_not be_valid
+ @m.errors.should == {:name=>["cannot be more than 10 characters"], :date=>["is invalid"], :number=>["is not a number"]}
+ end
+
+ specify "should support all validation_helpers methods" do
+ @c.set_validations do
+ validates do
+ name do
+ max_length 12
+ exact_length 10
+ min_length 8
+ length_range 9..11
+ unique
+ end
+ date do
+ format %r{\d\d/\d\d/\d\d\d\d}
+ includes ['10/11/2009']
+ end
+ number do
+ presence
+ integer
+ numeric
+ not_string
+ end
+ end
+ end
+ ds = @db.dataset
+ ds.extend(Module.new {
+ def columns(sql)
+ [:name, :date, :number]
+ end
+
+ def fetch_rows(sql)
+ yield({:v => /COUNT.*"?name"? = '1234567890'/i.match(sql) ? 0 : 1})
+ end
+ })
+ @c.dataset = ds
+ @m.should_not be_valid
+ @m.errors.should == {:name=>["is longer than 12 characters", "is not 10 characters", "is shorter than 8 characters", "is too short or too long", "is already taken"], :date=>["is invalid", "is not in range or set: [\"10/11/2009\"]"], :number=>["is not present", "is not a number", "is not a number"]}
+ @m.set(:name=>'123456789', :date=>'10/12/2009', :number=>'12')
+ @m.should_not be_valid
+ @m.errors.should == {:name=>["is not 10 characters", "is already taken"], :date=>["is not in range or set: [\"10/11/2009\"]"], :number=>["is a string"]}
+ @m.set(:name=>'1234567890', :date=>'10/11/2009', :number=>12)
+ @m.should be_valid
+ @m.errors.should == {}
+ end
+end

0 comments on commit 87c8afc

Please sign in to comment.