├── .gitignore ├── CHANGELOG ├── MIT-LICENSE ├── README ├── Rakefile ├── VERSION.yml ├── lib └── validation_reflection.rb ├── rails └── init.rb ├── test ├── test_helper.rb └── validation_reflection_test.rb └── validation_reflection.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | *~ 3 | pkg* 4 | validation_reflection*.gem -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | == 1.0.0 2010-10-08 2 | * 1.0 release for Rails 3 3 | 4 | == 1.0.0-rc.1 2010-07-30 5 | * Fixed reload bug 6 | 7 | == 1.0.0-beta4 2010-06-12 8 | * Changed initialization to reflect new initialization in Rails 3. 9 | 10 | == 0.3.8 2010-07-30 11 | * Enhancement from Sutto: 12 | ** Fix reload bug 13 | 14 | == 0.3.7 2010-06-11 15 | * Enhancement from Sutto: 16 | ** Use Rails.root if available over RAILS_ROOT 17 | 18 | == 0.3.6 2010-02-14 19 | * Enhancement from skoppensboer: 20 | ** Changes Jeweler spec to reflect only Gemcutter 21 | * Enhancement from duritong: 22 | ** fix remembering of attributes defined as an array 23 | 24 | == 0.3.5, 2009-10-09 25 | * version bump 26 | 27 | == 0.3.4, 2009-10-09 28 | * Enhancements from Jonas Grimfelt 29 | ** Don't include instead of explicit namespaces to make the code much DRY:er and readable 30 | ** Avoid mutable strings 31 | ** Be clear about the namespaces for external classes (to avoid Ruby 1.9.x issues) 32 | ** Fixing gem loading issues on Ruby 1.9.x 33 | ** Removed the freezing of validations 34 | 35 | == 0.3.3, 2009-09-12 36 | * version bump 37 | 38 | == 0.3.2, 2009-09-12 39 | * gemified by Christopher Redinger 40 | 41 | == 0.3.1, 2008-01-03 42 | * require 'ostruct'; thanks to Georg Friedrich. 43 | 44 | == 0.3, 2008-01-01 45 | * Added configurability in config/plugins/validation_reflection.rb 46 | 47 | == 0.2.1, 2006-12-28 48 | * Moved lib files into subfolder boiler_plate. 49 | 50 | == 0.2, 2006-08-06 51 | ? 52 | 53 | = Deprecation Notice 54 | 55 | Version 0.1 had supplied three methods 56 | 57 | - validates_presence_of_mandatory_content_columns 58 | - validates_lengths_of_string_attributes 59 | - validates_all_associated 60 | 61 | These have been removed. Please use the Enforce Schema Rules plugin instead 62 | 63 | http://enforce-schema-rules.googlecode.com/svn/trunk/enforce_schema_rules/ 64 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Christopher Redinger 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Validation Reflection 2 | ===================== 3 | 4 | Version 1.0.0, 2010-10-08 5 | 6 | This plugin adds reflective access to validations 7 | 8 | - ModelClass.reflect_on_all_validations 9 | - ModelClass.reflect_on_validations_for(:property) 10 | 11 | Both of these methods return arrays containing instances of 12 | ActiveRecord::Reflection::MacroReflection. For example 13 | 14 | class Person < ActiveRecord::Base 15 | validates_presence_of :name 16 | validates_numericality_of :size, :only_integer => true 17 | end 18 | 19 | refl = Person.reflect_on_validations_for(:name) 20 | refl[0].macro 21 | # => :validates_presence_of 22 | 23 | refl = Person.reflect_on_validations_for(:size) 24 | refl[0].macro 25 | # => :validates_numericality_of 26 | refl[0].options 27 | # => { :only_integer => true } 28 | 29 | 30 | == Customization 31 | 32 | Usually, all the standard Rails validations are reflected. 33 | You can change this -- add or remove validations -- in an 34 | application-specific initializer, 35 | 36 | config/initializers/validation_reflection.rb 37 | 38 | In that file change config.reflected_validations to suit your 39 | needs. Say, you have a custom validation for email addresses, 40 | validates_as_email, then you could add it like this 41 | 42 | config.reflected_validations << :validates_as_email 43 | 44 | If validates_as_email is implemented in terms of other validation 45 | methods, these validations are added to the reflection metadata, 46 | too. As that may not be what you want, you can disable reflection 47 | for these subordinate validations 48 | 49 | config.reflected_validations << { 50 | :method => :validates_as_email, 51 | :ignore_subvalidations => true 52 | } 53 | 54 | You have to make sure that all reflected validations are defined 55 | before this plugin is loaded. To this end, you may have to 56 | explicitly set the load order of plugins somewhere in the environment 57 | configuration using 58 | 59 | config.plugins = [...] 60 | 61 | 62 | == Special Thanks 63 | 64 | To Michael Schuerig, michael@schuerig.de for his initial concept and implementation of this plugin. 65 | 66 | == License 67 | 68 | ValidationReflection uses the MIT license. Please check the LICENSE file for more details. 69 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'rake' 3 | require 'rake/testtask' 4 | require 'rake/rdoctask' 5 | 6 | desc 'Default: run unit tests.' 7 | task :default => :test 8 | 9 | desc 'Test the validation_reflection plugin.' 10 | Rake::TestTask.new(:test) do |t| 11 | t.libs << 'lib' 12 | t.pattern = 'test/**/*_test.rb' 13 | t.verbose = true 14 | end 15 | 16 | desc 'Generate documentation for the validation_reflection plugin.' 17 | Rake::RDocTask.new(:rdoc) do |rdoc| 18 | rdoc.rdoc_dir = 'rdoc' 19 | rdoc.title = 'ValidationReflection' 20 | rdoc.options << '--line-numbers' << '--inline-source' 21 | rdoc.rdoc_files.include('README') 22 | rdoc.rdoc_files.include('lib/**/*.rb') 23 | end 24 | 25 | begin 26 | require 'jeweler' 27 | Jeweler::Tasks.new do |gemspec| 28 | gemspec.name = "validation_reflection" 29 | gemspec.summary = "Adds reflective access to validations" 30 | gemspec.description = "Adds reflective access to validations" 31 | gemspec.email = "redinger@gmail.com" 32 | gemspec.homepage = "http://github.com/redinger/validation_reflection" 33 | gemspec.authors = ["Christopher Redinger"] 34 | end 35 | Jeweler::RubyforgeTasks.new 36 | rescue LoadError 37 | puts "Jeweler not available. Install it with: sudo gem install jeweler" 38 | end -------------------------------------------------------------------------------- /VERSION.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :major: 1 3 | :minor: 0 4 | :patch: 0 5 | -------------------------------------------------------------------------------- /lib/validation_reflection.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | module ValidationReflection # :nodoc: 4 | 5 | extend self 6 | 7 | CORE_VALIDATONS = [ 8 | :validates_acceptance_of, 9 | :validates_associated, 10 | :validates_confirmation_of, 11 | :validates_exclusion_of, 12 | :validates_format_of, 13 | :validates_inclusion_of, 14 | :validates_length_of, 15 | :validates_numericality_of, 16 | :validates_presence_of, 17 | :validates_uniqueness_of, 18 | ].freeze 19 | 20 | @@reflected_validations = CORE_VALIDATONS.dup 21 | 22 | @@in_ignored_subvalidation = false 23 | 24 | mattr_accessor :reflected_validations, 25 | :in_ignored_subvalidation 26 | 27 | def included(base) # :nodoc: 28 | return if base.kind_of?(::ValidationReflection::ClassMethods) 29 | base.extend(ClassMethods) 30 | end 31 | 32 | # Iterate through all validations and store/cache the info 33 | # for later easy access. 34 | # 35 | def install(base) 36 | base.send :class_attribute, :validations 37 | 38 | @@reflected_validations.each do |validation_type| 39 | next if base.respond_to?(:"#{validation_type}_with_reflection") 40 | ignore_subvalidations = false 41 | 42 | if validation_type.kind_of?(::Hash) 43 | ignore_subvalidations = validation_type[:ignore_subvalidations] 44 | validation_type = validation_type[:method] 45 | end 46 | 47 | base.class_eval %{ 48 | class << self 49 | def #{validation_type}_with_reflection(*attr_names) 50 | ignoring_subvalidations(#{ignore_subvalidations}) do 51 | #{validation_type}_without_reflection(*attr_names) 52 | remember_validation_metadata(:#{validation_type}, *attr_names) 53 | end 54 | end 55 | alias_method_chain :#{validation_type}, :reflection 56 | end 57 | }, __FILE__, __LINE__ 58 | end 59 | end 60 | 61 | module ClassMethods 62 | 63 | include ::ValidationReflection 64 | 65 | # Returns an array of MacroReflection objects for all validations in the class 66 | def reflect_on_all_validations 67 | validations || [] 68 | end 69 | 70 | # Returns an array of MacroReflection objects for all validations defined for the field +attr_name+. 71 | def reflect_on_validations_for(attr_name) 72 | self.reflect_on_all_validations.select do |reflection| 73 | reflection.name == attr_name.to_sym 74 | end 75 | end 76 | 77 | private 78 | 79 | # Store validation info for easy and fast access. 80 | # 81 | def remember_validation_metadata(validation_type, *attr_names) 82 | configuration = attr_names.last.is_a?(::Hash) ? attr_names.pop : {} 83 | self.validations ||= [] 84 | attr_names.flatten.each do |attr_name| 85 | self.validations << ::ActiveRecord::Reflection::MacroReflection.new(validation_type, attr_name.to_sym, configuration, self) 86 | end 87 | end 88 | 89 | def ignoring_subvalidations(ignore) 90 | save_ignore = self.in_ignored_subvalidation 91 | unless self.in_ignored_subvalidation 92 | self.in_ignored_subvalidation = ignore 93 | yield 94 | end 95 | ensure 96 | self.in_ignored_subvalidation = save_ignore 97 | end 98 | end 99 | end 100 | 101 | ActiveSupport.on_load(:active_record) do 102 | include ValidationReflection 103 | ::ValidationReflection.install(self) 104 | end 105 | -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'validation_reflection' -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | ENV['RAILS_ENV'] = 'test' 3 | RAILS_ROOT = File.join(File.dirname(__FILE__)) 4 | 5 | require 'rubygems' 6 | 7 | begin 8 | require 'active_record' 9 | rescue LoadError 10 | gem 'activerecord', '>= 1.2.3' 11 | require 'active_record' 12 | end 13 | 14 | begin 15 | require 'test/unit' 16 | rescue LoadError 17 | gem 'test-unit', '>= 1.2.3' 18 | require 'test/unit' 19 | end -------------------------------------------------------------------------------- /test/validation_reflection_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.join(File.dirname(__FILE__), 'test_helper') 3 | 4 | ActiveRecord::Base.class_eval do 5 | def self.validates_something_weird(*cols) 6 | cols.each do |col| 7 | validates_format_of col, :with => /weird/ 8 | end 9 | end 10 | def self.validates_something_selfcontained(*cols) 11 | cols.each do |col| 12 | validates_format_of col, :with => /blablabla/ 13 | end 14 | end 15 | end 16 | 17 | require 'validation_reflection' 18 | 19 | ActiveRecord::Base.class_eval do 20 | include ValidationReflection 21 | ::ValidationReflection.reflected_validations << :validates_something_weird 22 | ::ValidationReflection.reflected_validations << { 23 | :method => :validates_something_selfcontained, 24 | :ignore_subvalidations => true 25 | } 26 | ::ValidationReflection.install(self) 27 | end 28 | 29 | 30 | class ValidationReflectionTest < Test::Unit::TestCase 31 | 32 | class Dummy < ActiveRecord::Base 33 | class << self 34 | 35 | def create_fake_column(name, null = true, limit = nil) 36 | sql_type = limit ? "varchar (#{limit})" : nil 37 | col = ActiveRecord::ConnectionAdapters::Column.new(name, nil, sql_type, null) 38 | col 39 | end 40 | 41 | def columns 42 | [ 43 | create_fake_column('col0'), 44 | create_fake_column('col1'), 45 | create_fake_column('col1a'), 46 | create_fake_column('col1b'), 47 | create_fake_column('col2', false, 100), 48 | create_fake_column('col3'), 49 | create_fake_column('col4'), 50 | create_fake_column('col5'), 51 | create_fake_column('col6'), 52 | create_fake_column('col7') 53 | ] 54 | end 55 | end 56 | 57 | has_one :nothing 58 | 59 | validates_presence_of :col1 60 | validates_presence_of [ :col1a, :col1b ] 61 | validates_length_of :col2, :maximum => 100 62 | validates_format_of :col3, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i 63 | validates_numericality_of :col4, :only_integer => true 64 | validates_numericality_of :col5, :less_than => 5 65 | validates_something_weird :col6 66 | validates_something_selfcontained :col7 67 | end 68 | 69 | def test_sanity 70 | assert_equal [], Dummy.reflect_on_validations_for(:col0) 71 | end 72 | 73 | def test_validates_presence_of_is_reflected 74 | reflections = Dummy.reflect_on_validations_for(:col1) 75 | assert reflections.all? { |r| r.name.to_s == 'col1' } 76 | assert reflections.find { |r| r.macro == :validates_presence_of } 77 | end 78 | 79 | def test_string_limit_is_reflected 80 | reflections = Dummy.reflect_on_validations_for(:col2) 81 | assert reflections.any? { |r| r.macro == :validates_length_of && r.options[:maximum] == 100 } 82 | end 83 | 84 | def test_format_is_reflected 85 | reflections = Dummy.reflect_on_validations_for(:col3) 86 | assert reflections.any? { |r| r.macro == :validates_format_of && r.options[:with] == /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i } 87 | end 88 | 89 | def test_numeric_integer_is_reflected 90 | reflections = Dummy.reflect_on_validations_for(:col4) 91 | assert reflections.any? { |r| r.macro == :validates_numericality_of && r.options[:only_integer] } 92 | end 93 | 94 | def test_numeric_is_reflected 95 | reflections = Dummy.reflect_on_validations_for(:col5) 96 | assert reflections.any? { |r| r.macro == :validates_numericality_of } 97 | end 98 | 99 | def test_validation_options_are_reflected 100 | reflections = Dummy.reflect_on_validations_for(:col5) 101 | refl = reflections[0] 102 | assert_equal 5, refl.options[:less_than] 103 | end 104 | 105 | def test_custom_validations_are_reflected 106 | reflections = Dummy.reflect_on_validations_for(:col6) 107 | assert reflections.any? { |r| r.macro == :validates_something_weird } 108 | assert reflections.any? { |r| r.macro == :validates_format_of } 109 | end 110 | 111 | def test_custom_validations_with_options_are_reflected 112 | reflections = Dummy.reflect_on_validations_for(:col7) 113 | assert reflections.any? { |r| r.macro == :validates_something_selfcontained } 114 | end 115 | 116 | def test_subvalidations_are_reflected 117 | reflections = Dummy.reflect_on_validations_for(:col6) 118 | assert_equal 2, reflections.size 119 | end 120 | 121 | def test_ignored_subvalidations_are_not_reflected 122 | reflections = Dummy.reflect_on_validations_for(:col7) 123 | assert_equal 1, reflections.size 124 | end 125 | 126 | end 127 | -------------------------------------------------------------------------------- /validation_reflection.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{validation_reflection} 8 | s.version = "1.0.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Christopher Redinger"] 12 | s.date = %q{2010-10-08} 13 | s.description = %q{Adds reflective access to validations} 14 | s.email = %q{redinger@gmail.com} 15 | s.extra_rdoc_files = [ 16 | "README" 17 | ] 18 | s.files = [ 19 | ".gitignore", 20 | "CHANGELOG", 21 | "MIT-LICENSE", 22 | "README", 23 | "Rakefile", 24 | "VERSION.yml", 25 | "lib/validation_reflection.rb", 26 | "rails/init.rb", 27 | "test/test_helper.rb", 28 | "test/validation_reflection_test.rb", 29 | "validation_reflection.gemspec" 30 | ] 31 | s.homepage = %q{http://github.com/redinger/validation_reflection} 32 | s.rdoc_options = ["--charset=UTF-8"] 33 | s.require_paths = ["lib"] 34 | s.rubygems_version = %q{1.3.6} 35 | s.summary = %q{Adds reflective access to validations} 36 | s.test_files = [ 37 | "test/test_helper.rb", 38 | "test/validation_reflection_test.rb" 39 | ] 40 | 41 | if s.respond_to? :specification_version then 42 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 43 | s.specification_version = 3 44 | 45 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 46 | else 47 | end 48 | else 49 | end 50 | end 51 | 52 | --------------------------------------------------------------------------------