├── VERSION ├── .gitignore ├── pkg ├── format_for_extensions-0.1.0.gem └── format_for_extensions-0.1.1.gem ├── test ├── models │ ├── person.rb │ └── abstract_model.rb ├── helper.rb ├── lib │ └── format_for_extensions │ │ └── test_config.rb └── test_format_for_extensions.rb ├── Gemfile ├── config └── format_for_extensions.yml ├── Gemfile.lock ├── lib ├── format_for_extensions │ └── config.rb └── format_for_extensions.rb ├── LICENSE.txt ├── Rakefile ├── format_for_extensions.gemspec └── README.rdoc /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.5 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .rvmrc 3 | pkg 4 | -------------------------------------------------------------------------------- /pkg/format_for_extensions-0.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/format_for_extensions/HEAD/pkg/format_for_extensions-0.1.0.gem -------------------------------------------------------------------------------- /pkg/format_for_extensions-0.1.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/format_for_extensions/HEAD/pkg/format_for_extensions-0.1.1.gem -------------------------------------------------------------------------------- /test/models/person.rb: -------------------------------------------------------------------------------- 1 | class Person < AbstractModel 2 | 3 | #insert the names of the form fields here 4 | column :email, :string 5 | column :postal_code, :string 6 | 7 | validates_postal_code_for :postal_code, :allow_blank => true 8 | validates_email_for :email, :allow_blank => true 9 | end -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | # Add dependencies required to use your gem here. 3 | # Example: 4 | gem "rails", ">= 2.3.5" 5 | 6 | # Add dependencies to develop your gem here. 7 | # Include everything needed to run rake, tests, features, etc. 8 | group :development do 9 | gem "bundler", ">= 1.0.0" 10 | gem "jeweler", "~> 1.6.2" 11 | gem "mocha", "0.9.5" 12 | gem "rcov", ">= 0" 13 | gem "shoulda", ">= 0" 14 | end 15 | -------------------------------------------------------------------------------- /test/models/abstract_model.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | # require 'active_record/base' 3 | 4 | class AbstractModel < ActiveRecord::Base 5 | self.abstract_class = true 6 | 7 | def self.columns() @columns ||= []; end 8 | 9 | def self.column(name, sql_type = nil, default = nil, null = true) 10 | columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/format_for_extensions.yml: -------------------------------------------------------------------------------- 1 | # Default validation configurations... 2 | en: 3 | postal_code: 4 | regexp: '^(\d{5})(?:-(\d{4}))?$|^[ABCEGHJKLMNPRSTVXY]\d[A-Z] *\d[A-Z]\d$' 5 | message: "is invalid. Valid formats: 94105-0011 or 94105 or T2X 1V4 or T2X1V4" 6 | 7 | email: 8 | regexp: '^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$' 9 | message: "is invalid. The address should be in a format similar to 'user@example.com'." 10 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | begin 4 | Bundler.setup(:default, :development) 5 | rescue Bundler::BundlerError => e 6 | $stderr.puts e.message 7 | $stderr.puts "Run `bundle install` to install missing gems" 8 | exit e.status_code 9 | end 10 | require 'test/unit' 11 | require 'shoulda' 12 | require 'active_record' 13 | require 'active_record/test_case' 14 | require 'mocha' 15 | 16 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 17 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 18 | require 'format_for_extensions' 19 | 20 | 21 | 22 | # These have to load AFTER our gem... 23 | require 'models/abstract_model' 24 | require 'models/person' 25 | 26 | class Test::Unit::TestCase 27 | end 28 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | actionmailer (2.3.11) 5 | actionpack (= 2.3.11) 6 | actionpack (2.3.11) 7 | activesupport (= 2.3.11) 8 | rack (~> 1.1.0) 9 | activerecord (2.3.11) 10 | activesupport (= 2.3.11) 11 | activeresource (2.3.11) 12 | activesupport (= 2.3.11) 13 | activesupport (2.3.11) 14 | git (1.2.5) 15 | jeweler (1.6.2) 16 | bundler (~> 1.0) 17 | git (>= 1.2.5) 18 | rake 19 | mocha (0.9.5) 20 | rake 21 | rack (1.1.1) 22 | rails (2.3.11) 23 | actionmailer (= 2.3.11) 24 | actionpack (= 2.3.11) 25 | activerecord (= 2.3.11) 26 | activeresource (= 2.3.11) 27 | activesupport (= 2.3.11) 28 | rake (>= 0.8.3) 29 | rake (0.8.7) 30 | rcov (0.9.9) 31 | shoulda (2.11.3) 32 | 33 | PLATFORMS 34 | ruby 35 | 36 | DEPENDENCIES 37 | bundler (>= 1.0.0) 38 | jeweler (~> 1.6.2) 39 | mocha (= 0.9.5) 40 | rails (>= 2.3.5) 41 | rcov 42 | shoulda 43 | -------------------------------------------------------------------------------- /lib/format_for_extensions/config.rb: -------------------------------------------------------------------------------- 1 | module FormatForExtensions 2 | class Config 3 | require 'yaml' 4 | 5 | # TODO: Bake in support for multiple locales... 6 | DEFAULT_LOCALE = 'en' 7 | 8 | def self.values(locale = DEFAULT_LOCALE) 9 | @@config ||= nil 10 | 11 | # First, we want to see if the consumer of this gem has defined a config file 12 | return @@config[locale] unless @@config.nil? 13 | 14 | begin 15 | @@config = YAML.load_file(File.join(Rails.root, 'config', 'format_for_extensions.yml')) 16 | rescue 17 | # fall back to our smart defaults 18 | @@config = YAML.load_file(File.join(File.dirname(__FILE__), '../../config', 'format_for_extensions.yml')) 19 | end 20 | 21 | @@config[locale] 22 | end 23 | 24 | # Returns the configured fields 25 | def self.fields 26 | values.keys 27 | end 28 | 29 | # returns the configured properties (of the first field) 30 | def self.properties 31 | values[fields.first].keys 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Karle Durante 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 | -------------------------------------------------------------------------------- /test/lib/format_for_extensions/test_config.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | require 'format_for_extensions/config' 3 | 4 | class TestConfig < Test::Unit::TestCase 5 | context "When loading the default yml file" do 6 | context "and inspecting field values" do 7 | setup {@config = FormatForExtensions::Config.values} 8 | should "return the postal code message" do 9 | assert_equal "is invalid. Valid formats: 94105-0011 or 94105 or T2X 1V4 or T2X1V4", @config['postal_code']['message'] 10 | end 11 | 12 | should "return the email message" do 13 | assert_equal "is invalid. The address should be in a format similar to 'user@example.com'.", @config['email']['message'] 14 | end 15 | end 16 | 17 | should "return the configured fields" do 18 | fields = FormatForExtensions::Config.fields 19 | ['postal_code', 'email'].each do |field| 20 | assert fields.include?(field) 21 | end 22 | end 23 | 24 | should "return the configured properties" do 25 | properties = FormatForExtensions::Config.properties 26 | ['regexp', 'message'].each do |property| 27 | assert properties.include?(property) 28 | end 29 | end 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 17 | gem.name = "format_for_extensions" 18 | gem.homepage = "http://github.com/customink/format_for_extensions" 19 | gem.license = "MIT" 20 | gem.summary = %Q{Extends ActiveRecord validates_format_of validations with reusable and customizable validation methods.} 21 | gem.description = %Q{Tired of repeating 'validates_format_of' with the same regex expression across your models only to validate the same email address and postal code fields? So am I. Use format_for_extensions to dynamically define reusable formats for any ActiveRecord model attribute you want.} 22 | gem.email = "kdurante@customink.com" 23 | gem.authors = ["Karle Durante", "Justin Molineaux"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rake/testtask' 29 | Rake::TestTask.new(:test) do |test| 30 | test.libs << 'lib' << 'test' 31 | test.pattern = 'test/**/test_*.rb' 32 | test.verbose = true 33 | end 34 | 35 | require 'rcov/rcovtask' 36 | Rcov::RcovTask.new do |test| 37 | test.libs << 'test' 38 | test.pattern = 'test/**/test_*.rb' 39 | test.verbose = true 40 | test.rcov_opts << '--exclude "gems/*"' 41 | end 42 | 43 | task :default => :test 44 | 45 | require 'rake/rdoctask' 46 | Rake::RDocTask.new do |rdoc| 47 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 48 | 49 | rdoc.rdoc_dir = 'rdoc' 50 | rdoc.title = "ar_validation_extensions #{version}" 51 | rdoc.rdoc_files.include('README*') 52 | rdoc.rdoc_files.include('lib/**/*.rb') 53 | end 54 | -------------------------------------------------------------------------------- /test/test_format_for_extensions.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestFormatForExtensions < Test::Unit::TestCase 4 | 5 | context "When loading FormatFor" do 6 | context "with valid configurations" do 7 | setup { @fields = FormatForExtensions::Config.values.keys } 8 | 9 | should "define extended validation methods for each configured field" do 10 | @fields.each do |field| 11 | assert ActiveRecord::Base.respond_to?("validates_#{field}_for") 12 | end 13 | end 14 | 15 | should "define data accessor methods for each configured field" do 16 | @fields.each do |field| 17 | assert ActiveRecord::Base.respond_to?("#{field}_regexp") 18 | assert ActiveRecord::Base.respond_to?("#{field}_message") 19 | end 20 | end 21 | 22 | should "consider the postal code to be valid" do 23 | person = Person.new 24 | ['94105-0011', '94105', 'T2X 1V4', 'T2X1V4'].each do |postal_code| 25 | person.postal_code = postal_code 26 | assert person.valid? 27 | end 28 | end 29 | 30 | should "consider the postal code to be invalid" do 31 | person = Person.new 32 | ['9410', 'abc', '123-123', '123456', '1234567', '12345678', '12345-678', 'blahN8P1Z4', 'N8P1Z'].each do |postal_code| 33 | person.postal_code = postal_code 34 | assert !person.valid? 35 | end 36 | end 37 | 38 | should "consider the email to be valid" do 39 | person = Person.new 40 | ['user@example.org', 'some.other.guy@yahoo.us.ca', 'a@b.com'].each do |email| 41 | person.email = email 42 | assert person.valid? 43 | end 44 | end 45 | 46 | should "consider the email to be invalid" do 47 | person = Person.new 48 | ['user@example', 'yahoo.us.ca', 'a.com'].each do |email| 49 | person.email = email 50 | assert !person.valid? 51 | end 52 | end 53 | end 54 | 55 | context "with invalid configurations " do 56 | # TODO: How to test this?? Once active record and the gem are loaded, how do we unload/reload them with bad params? 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/format_for_extensions.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | 3 | module FormatForExtensions 4 | module ActiveRecord 5 | module Validations 6 | def self.included(base) 7 | base.extend ClassMethods 8 | end 9 | 10 | module ClassMethods 11 | require 'format_for_extensions/config' 12 | 13 | begin 14 | FIELDS = FormatForExtensions::Config.fields 15 | PROPERTIES = FormatForExtensions::Config.properties 16 | rescue Exception => e 17 | puts "Exception loading config: #{e.inspect}" 18 | raise(RuntimeError, "Your format_for_extensions.yml file seems to be invalid, perhaps you've mis-keyed something?") 19 | end 20 | 21 | FIELDS.each do |field| 22 | define_method("validates_#{field}_for") do |*attr_names| 23 | configuration = {:with => self.send("#{field}_regexp"), 24 | :message => self.send("#{field}_message") 25 | } 26 | 27 | # Add in user supplied options (yes, users can still override the regexp and 28 | # message with custom options) 29 | configuration.update(attr_names.extract_options!) 30 | 31 | # Leverage existing ActiveRecord::Validations... 32 | validates_format_of(attr_names, configuration) 33 | end 34 | end 35 | 36 | # Compose accessor methods for each of the specified field types 37 | # e.g. postal_code_regexp, postal_code_message, etc... 38 | FIELDS.each do |field| 39 | PROPERTIES.each do |property| 40 | define_method("#{field}_#{property}".to_sym) do 41 | value = FormatForExtensions::Config.values[field][property] 42 | 43 | raise(ArgumentError, "Your format_for_extensions.yml is missing the mapping '#{property}' for '#{field}'") if value.blank? 44 | 45 | return property == 'regexp' ? Regexp.new(value) : value 46 | end 47 | end 48 | end 49 | 50 | end 51 | end 52 | end 53 | end 54 | 55 | ActiveRecord::Base.class_eval do 56 | include FormatForExtensions::ActiveRecord::Validations 57 | end -------------------------------------------------------------------------------- /format_for_extensions.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "format_for_extensions" 8 | s.version = "0.1.5" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Karle Durante", "Justin Molineaux"] 12 | s.date = "2013-10-25" 13 | s.description = "Tired of repeating 'validates_format_of' with the same regex expression across your models only to validate the same email address and postal code fields? So am I. Use format_for_extensions to dynamically define reusable formats for any ActiveRecord model attribute you want." 14 | s.email = "kdurante@customink.com" 15 | s.extra_rdoc_files = [ 16 | "LICENSE.txt", 17 | "README.rdoc" 18 | ] 19 | s.files = [ 20 | "Gemfile", 21 | "Gemfile.lock", 22 | "LICENSE.txt", 23 | "README.rdoc", 24 | "Rakefile", 25 | "VERSION", 26 | "config/format_for_extensions.yml", 27 | "format_for_extensions.gemspec", 28 | "lib/format_for_extensions.rb", 29 | "lib/format_for_extensions/config.rb", 30 | "pkg/format_for_extensions-0.1.0.gem", 31 | "pkg/format_for_extensions-0.1.1.gem", 32 | "test/helper.rb", 33 | "test/lib/format_for_extensions/test_config.rb", 34 | "test/models/abstract_model.rb", 35 | "test/models/person.rb", 36 | "test/test_format_for_extensions.rb" 37 | ] 38 | s.homepage = "http://github.com/customink/format_for_extensions" 39 | s.licenses = ["MIT"] 40 | s.require_paths = ["lib"] 41 | s.rubygems_version = "1.8.23" 42 | s.summary = "Extends ActiveRecord validates_format_of validations with reusable and customizable validation methods." 43 | 44 | if s.respond_to? :specification_version then 45 | s.specification_version = 3 46 | 47 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 48 | s.add_runtime_dependency(%q, [">= 2.3.5"]) 49 | s.add_development_dependency(%q, [">= 1.0.0"]) 50 | s.add_development_dependency(%q, ["~> 1.6.2"]) 51 | s.add_development_dependency(%q, ["= 0.9.5"]) 52 | s.add_development_dependency(%q, [">= 0"]) 53 | s.add_development_dependency(%q, [">= 0"]) 54 | else 55 | s.add_dependency(%q, [">= 2.3.5"]) 56 | s.add_dependency(%q, [">= 1.0.0"]) 57 | s.add_dependency(%q, ["~> 1.6.2"]) 58 | s.add_dependency(%q, ["= 0.9.5"]) 59 | s.add_dependency(%q, [">= 0"]) 60 | s.add_dependency(%q, [">= 0"]) 61 | end 62 | else 63 | s.add_dependency(%q, [">= 2.3.5"]) 64 | s.add_dependency(%q, [">= 1.0.0"]) 65 | s.add_dependency(%q, ["~> 1.6.2"]) 66 | s.add_dependency(%q, ["= 0.9.5"]) 67 | s.add_dependency(%q, [">= 0"]) 68 | s.add_dependency(%q, [">= 0"]) 69 | end 70 | end 71 | 72 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = format_for_extensions 2 | 3 | Tired of repeating 'validates_format_of' with the same regex expression across your models only to validate the same email address and postal code fields? So am I. Use format_for_extensions to dynamically define reusable formats for any field you want. 4 | 5 | By default, format_for_extensions comes with smart formats for email addresses and postal codes. Even better, no code repetition! Use constructs that make sense such as "validates_email_for :account_owner" and "validates_postal_code_for :address_postal_code". 6 | 7 | 8 | == System Requirements 9 | format_for_extensions should work with Rails 2.3.5 and above. However, I have not yet tested this with Rails 3.1. 10 | 11 | 12 | == Installation 13 | Modify your Gemfile to include format_for_extensions: 14 | 15 | gem 'format_for_extensions' 16 | 17 | Run `bundle install`. You thought it would be harder? 18 | 19 | 20 | == Usage 21 | By default, format_for_extensions supports email and postal code formats. These can be leveraged by including code such as: 22 | 23 | class Person < ActiveRecord::Base 24 | # Notice how the normal ActiveRecord Validation options can still be used... 25 | validates_email_for :email_address 26 | validates_email_for :home_email_address, :if => :full_validation_required 27 | validates_postal_code_for :postal_code, :allow_blank => true 28 | end 29 | 30 | 31 | === Configuration 32 | 33 | Default configurations are specified in the gem's config/format_for_extensions.yml file. If you wish to modify the default regular expressions or messages reported when validation fails, simply copy this file to the config directory in your Rails root folder. You can also manually create [rails_root]/config/format_for_extensions.yml if you'd like to. 34 | 35 | The format of this file is as follows: 36 | 37 | # format_for_extensions.yml 38 | en: 39 | email: 40 | regex: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/ 41 | message: "is invalid. The address should be in a format similar to 'user@example.com'." 42 | 43 | PLEASE NOTE that localization is not really supported at this time. By default we only support english mappings, the 'en' locale exists to allow us to more easily support localization in the future. 44 | 45 | If you modify this file, you will need to bounce your application server (or restart your rails console) as this gem caches configurations at load time. 46 | 47 | 48 | === The Hotness 49 | You can dynamically create your own format validators! For instance, add this to your [rails_root]/config/format_for_extensions.yml file: 50 | 51 | # format_for_extensions.yml 52 | en: 53 | hotness: 54 | regex: /^hot/ 55 | message: "is not hot!" 56 | 57 | And you now have a completely useless method to validate that fields begin with 'hot': 58 | 59 | class Person < ActiveRecord::Base 60 | # Validation will fail if your first_name does not begin with 'hot' 61 | validates_hotness_for :first_name 62 | end 63 | 64 | As you might have guessed, the gem is driven off of the configuration file. The same way you can add validators, you can remove them by simply deleting lines from the configuration file. 65 | 66 | 67 | == TODO 68 | - We intend to support more locales other then english. The idea being that we'd check the current local, and load the regex/message accordingly. 69 | - Support some other highly repetitious fields, such as phone number. 70 | 71 | 72 | == Contributing to format_for_extensions 73 | 74 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet 75 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it 76 | * Fork the project 77 | * Start a feature/bugfix branch 78 | * Commit and push until you are happy with your contribution 79 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 80 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 81 | 82 | == Copyright 83 | 84 | Copyright (c) 2011 CustomInk. See LICENSE.txt for 85 | further details. 86 | 87 | --------------------------------------------------------------------------------