diff --git a/.document b/.document new file mode 100644 index 0000000..ecf3673 --- /dev/null +++ b/.document @@ -0,0 +1,5 @@ +README.rdoc +lib/**/*.rb +bin/* +features/**/*.feature +LICENSE diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1e0daf --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +## MAC OS +.DS_Store + +## TEXTMATE +*.tmproj +tmtags + +## EMACS +*~ +\#* +.\#* + +## VIM +*.swp + +## PROJECT::GENERAL +coverage +rdoc +pkg + +## PROJECT::SPECIFIC diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4c47868 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2010 Corin Langosch + +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. + diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..786e0e1 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,51 @@ +=About + +This gem provides sexy validations/ validators functionality for models. +It was heavily inspired by the new validators in rails 3. Basically +it's a replacement for "inlcude ActiveModel::Validations", because those +are strangely complex and don't work together properly with sequel for +example. + +==Install + +Simply install it as any other gem: + + gem install sexy_validations + +Or when using bundler, add it got your Gemfile: + + gem sexy_validations + +This should also install the geokit gem. + +==Quick Start + +In your model: + + class User + plugin :sexy_validations + + validates :name, :presence => true, :length => 2..20 + validates :desfription, :length => 0..2000 + validates :photo, :image => "250x250" # see the sequel_paperclip gem + end + +==Todo + +* Use I18n for messages +* Source documentation (rdoc) +* Tests + +==Contributing + +If you'd like to contribute a feature or bugfix: Thanks! To make sure your +fix/feature has a high chance of being included, please read the following +guidelines: + +1. Fork the project. +2. Make your feature addition or bug fix. +3. Add tests for it. This is important so we don’t break anything in a future version unintentionally. +4. Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) +5. Send me a pull request. Bonus points for topic branches. + + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..717bf2f --- /dev/null +++ b/Rakefile @@ -0,0 +1,46 @@ +require 'rubygems' +require 'rake' + +begin + require 'jeweler' + Jeweler::Tasks.new do |gem| + gem.name = 'sexy_validations' + gem.authors = ['Corin Langosch'] + gem.date = Date.today.to_s + gem.email = 'info@netskin.com' + gem.homepage = 'http://github.com/gucki/sexy_validations' + gem.summary = 'Sexy validations for Models' + gem.description = 'Module which provides sexy validations for models.' + gem.add_development_dependency "rspec", ">= 1.2.9" + # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings + end + Jeweler::GemcutterTasks.new +rescue LoadError + puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler" +end + +require 'spec/rake/spectask' +Spec::Rake::SpecTask.new(:spec) do |spec| + spec.libs << 'lib' << 'spec' + spec.spec_files = FileList['spec/**/*_spec.rb'] +end + +Spec::Rake::SpecTask.new(:rcov) do |spec| + spec.libs << 'lib' << 'spec' + spec.pattern = 'spec/**/*_spec.rb' + spec.rcov = true +end + +task :spec => :check_dependencies + +task :default => :spec + +require 'rake/rdoctask' +Rake::RDocTask.new do |rdoc| + version = File.exist?('VERSION') ? File.read('VERSION') : "" + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "sexy_validations #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/lib/sexy_validations.rb b/lib/sexy_validations.rb new file mode 100644 index 0000000..b51158a --- /dev/null +++ b/lib/sexy_validations.rb @@ -0,0 +1,64 @@ +module SexyValidations + def self.included(klass) + klass.instance_eval do + klass.class_inheritable_array :validations + klass.validations = [] + + extend ClassMethods + include InstanceMethods + end + end + + module ClassMethods + attr_accessor :errors + + def load_validator(name) + require "sexy_validations/validators/#{name}" + "SexyValidations::Validators::#{name.to_s.capitalize}".constantize + end + + def validates(attribute = nil, validations = nil, &block) + if validations + validations.each_pair do |validator, options| + klass = load_validator(validator) + self.validations << { + :attribute => attribute, + :validator => klass, + :options => options, + } + end + else + if attribute + self.validations << { + :method => "#{attribute}_validation", + } + else + self.validations << { + :block => block, + } + end + end + end + end + + module InstanceMethods + def validate! + errors.clear + validations.each do |validation| + valid = case + when validation[:validator] + if errors[validation[:attribute]].empty? + validation[:validator].validate(self, validation[:attribute], send(validation[:attribute]), validation[:options]) + end + when validation[:method] + send(validation[:method]) + when validation[:block] + validation[:block].call + else + raise ArgumentError, "invalid validation (internal error)" + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/acceptance.rb b/lib/sexy_validations/validators/acceptance.rb new file mode 100644 index 0000000..f5ad4ff --- /dev/null +++ b/lib/sexy_validations/validators/acceptance.rb @@ -0,0 +1,13 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Acceptance + def self.validate(model, attribute, value, options) + unless value.to_i > 0 + model.errors.add(attribute, "muss akzeptiert werden") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/age.rb b/lib/sexy_validations/validators/age.rb new file mode 100644 index 0000000..dee0e67 --- /dev/null +++ b/lib/sexy_validations/validators/age.rb @@ -0,0 +1,27 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Age + def self.validate(record, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = { + :within => options, + } + end + + min = options[:within].min + if value.calc_age < min + record.errors.add(attribute, "ungültig (mindestes #{min} Jahre)") + end + + max = options[:within].max + if value.calc_age > max + record.errors.add(attribute, "ungültig (maximal #{max} Jahre)") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/confirmation.rb b/lib/sexy_validations/validators/confirmation.rb new file mode 100644 index 0000000..8f5fef0 --- /dev/null +++ b/lib/sexy_validations/validators/confirmation.rb @@ -0,0 +1,15 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Confirmation + def self.validate(model, attribute, value, options) + return unless value + + if value != model.send("#{attribute}_confirmation") + model.errors.add(attribute, "stimmt nicht mit der Bestätigung überein") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/count.rb b/lib/sexy_validations/validators/count.rb new file mode 100644 index 0000000..9a8bf69 --- /dev/null +++ b/lib/sexy_validations/validators/count.rb @@ -0,0 +1,27 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Count + def self.validate(record, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = { + :within => options, + } + end + + min = options[:within].min + if value.count < min + record.errors.add(attribute, "zu wenig Selektionen (mindestes #{min})") + end + + max = options[:within].max + if value.count > max + record.errors.add(attribute, "zu viele Selektionen (maximal #{max})") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/email.rb b/lib/sexy_validations/validators/email.rb new file mode 100644 index 0000000..aa42bcf --- /dev/null +++ b/lib/sexy_validations/validators/email.rb @@ -0,0 +1,17 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Email + REGEXP = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + + def self.validate(record, attribute, value, options) + return unless value + + unless value =~ REGEXP + record.errors.add(attribute, "ungültiges Format") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/file.rb b/lib/sexy_validations/validators/file.rb new file mode 100644 index 0000000..cfb7c7b --- /dev/null +++ b/lib/sexy_validations/validators/file.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class File + def self.humanized_size(num) + for x in ['Byte','KB','MB','GB','TB'] + return "%d %s"%[num, x] if num < 1024.0 + num /= 1024.0 + end + end + + def self.validate(model, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = { + :size => options, + } + end + + unless value.is_a?(File) || value.is_a?(Tempfile) + raise ArgumentError, "#{value} is not a File" + end + + if options[:size] + min = options[:size].min + if value.size < min + model.errors.add(attribute, "zu klein (mindestes #{humanized_size(min)})") + end + + max = options[:size].max + if value.size > max + model.errors.add(attribute, "zu groß (maximal #{humanized_size(max)})") + end + end + + end + end + end +end + diff --git a/lib/sexy_validations/validators/format.rb b/lib/sexy_validations/validators/format.rb new file mode 100644 index 0000000..faec3ba --- /dev/null +++ b/lib/sexy_validations/validators/format.rb @@ -0,0 +1,21 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Format + def self.validate(model, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = { + :with => options, + } + end + + unless value =~ options[:with] + model.errors.add(attribute, options[:message] || "ungültiges Format") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/image.rb b/lib/sexy_validations/validators/image.rb new file mode 100644 index 0000000..88add8c --- /dev/null +++ b/lib/sexy_validations/validators/image.rb @@ -0,0 +1,36 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Image + def self.validate(model, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = { + :geometry => options, + } + end + + unless value.is_a?(File) || value.is_a?(Tempfile) + raise ArgumentError, "#{value} is not a File" + end + + if options[:geometry] + geo1 = Sequel::Plugins::Paperclip::Processors::Image::Geometry.from_s(options[:geometry]) + geo2 = Sequel::Plugins::Paperclip::Processors::Image::Geometry.from_file(value) + if geo2 + if geo2.width < geo1.width + model.errors.add(attribute, "zu klein (weniger als %d Pixel breit)"%[geo1.width]) + end + if geo2.height < geo1.height + model.errors.add(attribute, "zu klein (weniger als %d Pixel hoch)"%[geo1.height]) + end + else + model.errors.add(attribute, "Bildformat unbekannt oder Datei beschädigt") + end + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/inclusion.rb b/lib/sexy_validations/validators/inclusion.rb new file mode 100644 index 0000000..8b3001e --- /dev/null +++ b/lib/sexy_validations/validators/inclusion.rb @@ -0,0 +1,21 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Inclusion + def self.validate(model, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = { + :within => options, + } + end + + unless options[:within].include?(value) + model.errors.add(attribute, options[:message] || "ungültige Auswahl") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/length.rb b/lib/sexy_validations/validators/length.rb new file mode 100644 index 0000000..5e3e347 --- /dev/null +++ b/lib/sexy_validations/validators/length.rb @@ -0,0 +1,27 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Length + def self.validate(model, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = { + :within => options, + } + end + + min = options[:within].min + if value.length < min + model.errors.add(attribute, "zu kurz (mindestens #{min} Zeichen)") + end + + max = options[:within].max + if value.length > max + model.errors.add(attribute, "zu lang (maximal #{max} Zeichen)") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/password.rb b/lib/sexy_validations/validators/password.rb new file mode 100644 index 0000000..6c2f491 --- /dev/null +++ b/lib/sexy_validations/validators/password.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Password + def self.validate(record, attribute, value, options) + return unless value + + unless options.is_a?(Hash) + options = {} + end + + options[:length] ||= 6..20 + options[:format] ||= /^[\x20-\x7e]+$/i + + min = options[:length].min + if value.length < min + record.errors.add(attribute, "zu kurz (mindestes #{min} Zeichen)") + end + + max = options[:length].max + if value.length > max + record.errors.add(attribute, "zu lang (maximal #{max} Zeichen)") + end + + unless value =~ options[:format] + record.errors.add(attribute, "enthält ungültige Zeichen") + end + end + end + end +end + diff --git a/lib/sexy_validations/validators/presence.rb b/lib/sexy_validations/validators/presence.rb new file mode 100644 index 0000000..692caeb --- /dev/null +++ b/lib/sexy_validations/validators/presence.rb @@ -0,0 +1,13 @@ +# encoding: utf-8 +module SexyValidations + module Validators + class Presence + def self.validate(model, attribute, value, options) + return unless value.blank? + + model.errors.add(attribute, "muss ausgefüllt werden") + end + end + end +end + diff --git a/sexy_validations.gemspec b/sexy_validations.gemspec new file mode 100644 index 0000000..ec586fa --- /dev/null +++ b/sexy_validations.gemspec @@ -0,0 +1,38 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{sexy_validations} + s.version = "0.0.1" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Corin Langosch"] + s.date = %q{2010-09-09} + s.description = %q{Module which provides sexy validations for models.} + s.email = %q{info@netskin.com} + s.extra_rdoc_files = [ + "LICENSE", + "README.rdoc" + ] + s.homepage = %q{http://github.com/gucki/sexy_validations} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.7} + s.summary = %q{Sexy validations for Models} + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q, [">= 1.2.9"]) + else + s.add_dependency(%q, [">= 1.2.9"]) + end + else + s.add_dependency(%q, [">= 1.2.9"]) + end +end + diff --git a/spec/sequel_sexy_validations_spec.rb b/spec/sequel_sexy_validations_spec.rb new file mode 100644 index 0000000..2213377 --- /dev/null +++ b/spec/sequel_sexy_validations_spec.rb @@ -0,0 +1,7 @@ +require File.expand_path(File.dirname(__FILE__) + '/spec_helper') + +describe "SequelSexyValidations" do + it "fails" do + fail "hey buddy, you should probably rename this file and start specing for real" + end +end diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 0000000..4e1e0d2 --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1 @@ +--color diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..1cd62a3 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,9 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__)) +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +require 'sequel_sexy_validations' +require 'spec' +require 'spec/autorun' + +Spec::Runner.configure do |config| + +end