schema_validations 0.1.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ .*.sw?
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .rvmrc
23
+ *.log
24
+ *.sqlite3
25
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2006 RedHill Consulting, Pty. Ltd.
2
+ Copyright (c) 2011 Ronen Barzel & Michał Łomnicki
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ Except as contained in this notice, the name(s) of the above copyright
16
+ holders shall not be used in advertising or otherwise to promote the sale,
17
+ use or other dealings in this Software without prior written authorization.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,125 @@
1
+ = SchemaValidations
2
+
3
+ == Overview
4
+
5
+ One of the great things about Rails (ActiveRecord, in particular) is that it
6
+ inspects the database and automatically defines accessors for all your
7
+ columns, keeping your model class definitions simple and DRY. That's great
8
+ for simple data columns, but where it falls down is when your table
9
+ contains constraints.
10
+
11
+ create_table :users do |t|
12
+ t.string :email, :null => false, :limit => 30
13
+ t.boolean :confirmed, :null => false
14
+ end
15
+
16
+ In that case :null => false, :limit => 30 and :boolean must be covered on the model level.
17
+
18
+ class User < ActiveRecord::Base
19
+ validates :email, :presence => true, :length => 30
20
+ validates :confirmed, :presence => true, :inclusion => { :in => [true, false] }
21
+ end
22
+
23
+ ...which isn't the most DRY approach.
24
+
25
+ SchemaValidations aims to cover that and does boring work for you. It inspect
26
+ the database and automatically creates validations basing on the schema. After
27
+ installing it your model is as simple as it can be.
28
+
29
+ class User < ActiveRecord::Base
30
+ end
31
+
32
+ Validations are there but they are created by schema_validations under the hood.
33
+
34
+ == Installation
35
+
36
+ Simply add schema_validations to your Gemfile.
37
+
38
+ gem "schema_validations"
39
+
40
+ == What if I want something special?
41
+
42
+ SchemaValidations is highly customizable. You can configure behavior
43
+ globally via SchemaValidations.setup or per-model via
44
+ SchemaValidations::ActiveRecord::schema_validations, such as:
45
+
46
+ class User < ActiveRecord::Base
47
+ schema_validations :except => :email
48
+ validates :email, :presence => true, :length => { :in => 5..30 }
49
+ end
50
+
51
+ See SchemaValidations::Config for the available options.
52
+
53
+ == Which validations are covered?
54
+
55
+ Constraints:
56
+
57
+ | Constraint | Validation |
58
+ |---------------------|----------------------------------------------------------|
59
+ | :null => false | validates ... :presence => true |
60
+ | :limit => 100 | validates ... :length => { :maximum => 100 } |
61
+ | :unique => true | validates ... :uniqueness => true |
62
+
63
+ Data types:
64
+
65
+ | Type | Validation |
66
+ |--------------------|-----------------------------------------------------------|
67
+ | :boolean | :validates ... :inclusion => { :in => [true, false] } |
68
+ | :float | :validates ... :numericality => true |
69
+ | :integer | :validates ... :numericality => { :only_integer => true } |
70
+
71
+ == Dependency
72
+
73
+ SchemaValidations uses the {+schema_plus+}[http://rubygems.org/gems/schema_plus] gem for its schema
74
+ queries. That gem will by default auto-create foreign key constraints that
75
+ you probably want -- but if you don't want them, you can disable them using
76
+ {+schema_plus+}[http://rubygems.org/gems/schema_plus]'s config.
77
+
78
+ == Compatibility
79
+
80
+ SchemaValidations supports all combinations of:
81
+ * rails 3.0 or 3.1
82
+ * MRI ruby 1.8.7 or 1.9.2
83
+
84
+ == How do I know what it did?
85
+ If you're curious (or dubious) about what validations SchemaValidations defines, you can check the log file. For every assocation that SchemaValidations defines, it generates an info entry such as
86
+
87
+ [schema_validations] Article.validates_length_of :title, :allow_nil=>true, :maximum=>50
88
+
89
+ which shows the exact validation definition call.
90
+
91
+ SchemaValidations defines the validations lazily, only creating them when a
92
+ record of the class is first accessed. So you may need to search through
93
+ the log file for "schema_validations" to find them all (and some may not be
94
+ defined at all if they were never needed for the logged use case).
95
+
96
+ == I don't use constraints
97
+ TODO and blame you
98
+
99
+ == History
100
+
101
+ * SchemaValidations is derived from the "Red Hill On Rails" plugin schema_validations originally created by harukizaemon (https://github.com/harukizaemon)
102
+
103
+ * SchemaValidations was created in 2011 by Michał Łomnicki and Ronen Barzel
104
+
105
+ == Testing
106
+
107
+ SchemaValidations is tested using rspec, sqlite3, and rvm, with some
108
+ hackery to test against multiple versions of rails and ruby. To run the
109
+ full combo of tests, after you've forked & cloned:
110
+
111
+ $ cd schema_validations
112
+ $ ./runspecs --install # do this once to install gem dependencies for all versions (slow)
113
+ $ ./runspecs # as many times as you like
114
+
115
+ You can also pick a specific version of rails and ruby to use, such as:
116
+ $ rvm use 1.9.2
117
+ $ export SCHEMA_VALIDATIONS_RAILS_VERSION=3.1
118
+ $ bundle update --local rails
119
+ $ rake spec
120
+
121
+ If you're running ruby 1.9.2, code coverage results will be in coverage/index.html -- it should be at 100% coverage.
122
+
123
+ == License
124
+
125
+ This gem is released under the MIT license.
@@ -0,0 +1,19 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.rspec_opts = '-Ispec'
7
+ end
8
+
9
+ task :default => :spec
10
+
11
+ require 'rake/rdoctask'
12
+ Rake::RDocTask.new do |rdoc|
13
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
14
+
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = "schema_validations #{version}"
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'schema_validations' unless defined?(SchemaValidations)
@@ -0,0 +1,104 @@
1
+ require 'valuable'
2
+
3
+ require 'schema_plus'
4
+ require 'schema_validations/version'
5
+ require 'schema_validations/active_record/validations'
6
+ require 'schema_validations/railtie' if defined?(Rails)
7
+
8
+ module SchemaValidations
9
+
10
+ # The configuation options for SchemaValidations. Set them globally in
11
+ # <tt>config/initializers/schema_validations.rb</tt>, e.g.:
12
+ #
13
+ # SchemaValidations.setup do |config|
14
+ # config.auto_create = false
15
+ # end
16
+ #
17
+ # or override them per-model, e.g.:
18
+ #
19
+ # class MyModel < ActiveRecord::Base
20
+ # schema_validations :only => [:name, :active]
21
+ # end
22
+ #
23
+ class Config < Valuable
24
+ ##
25
+ # :attr_accessor: auto_create
26
+ #
27
+ # Whether to automatically create validations based on database constraints.
28
+ # Boolean, default is +true+.
29
+ has_value :auto_create, :klass => :boolean, :default => true
30
+
31
+ ##
32
+ # :attr_accessor: only
33
+ #
34
+ # List of field names to include in automatic validation.
35
+ # Value is a single name, and array of names, or +nil+. Default is +nil+.
36
+ has_value :only, :default => nil
37
+
38
+ ##
39
+ # :attr_accessor: except
40
+ #
41
+ # List of field names to exclude from automatic validation.
42
+ # Value is a single name, an array of names, or +nil+. Default is <tt>[:created_at, :updated_at, :created_on, :updated_on]</tt>.
43
+ has_value :except, :default => [:created_at, :updated_at, :created_on, :updated_on]
44
+
45
+ ##
46
+ # :attr_accessor: only_type
47
+ #
48
+ # List of validation types to exclude from automatic validation.
49
+ # Value is a single type, and array of types, or +nil+. Default is +nil+.
50
+ # A type is specified as, e.g., +:validates_presence_of+ or simply +:presence+.
51
+ has_value :except_type, :default => nil
52
+
53
+ ##
54
+ # :attr_accessor: only_type
55
+ #
56
+ # List of validation types to include in automatic validation.
57
+ # Value is a single type, and array of types, or +nil+. Default is +nil+.
58
+ # A type is specified as, e.g., +:validates_presence_of+ or simply +:presence+.
59
+ has_value :only_type, :default => nil
60
+
61
+ def dup #:nodoc:
62
+ self.class.new(Hash[attributes.collect{ |key, val| [key, Valuable === val ? val.class.new(val.attributes) : val] }])
63
+ end
64
+
65
+ def update_attributes(opts)#:nodoc:
66
+ opts = opts.dup
67
+ opts.keys.each { |key| self.send(key).update_attributes(opts.delete(key)) if self.class.attributes.include? key and Hash === opts[key] }
68
+ super(opts)
69
+ self
70
+ end
71
+
72
+ def merge(opts)#:nodoc:
73
+ dup.update_attributes(opts)
74
+ end
75
+
76
+ end
77
+
78
+ # Returns the global configuration, i.e., the singleton instance of Config
79
+ def self.config
80
+ @config ||= Config.new
81
+ end
82
+
83
+ # Initialization block is passed a global Config instance that can be
84
+ # used to configure SchemaValidations behavior. E.g., if you want to
85
+ # disable automation creation validations put the following in
86
+ # config/initializers/schema_validations.rb :
87
+ #
88
+ # SchemaValidations.setup do |config|
89
+ # config.auto_create = false
90
+ # end
91
+ #
92
+ def self.setup # :yields: config
93
+ yield config
94
+ end
95
+
96
+ def self.insert #:nodoc:
97
+ return if @inserted
98
+ @inserted = true
99
+ ::ActiveRecord::Base.extend SchemaValidations::ActiveRecord::Validations
100
+ end
101
+
102
+ end
103
+
104
+ SchemaValidations.insert unless defined? Rails::Railtie
@@ -0,0 +1,133 @@
1
+ module SchemaValidations
2
+ module ActiveRecord
3
+ module Validations
4
+
5
+ # Per-model override of Config options. Use via, e.g.
6
+ # class MyModel < ActiveRecord::Base
7
+ # schema_associations :auto_create => false
8
+ # end
9
+ def schema_validations(opts)
10
+ @schema_validations_config = SchemaValidations.config.merge(opts)
11
+ end
12
+
13
+ def schema_validations_config # :nodoc:
14
+ @schema_validations_config ||= SchemaValidations.config.dup
15
+ end
16
+
17
+ def define_attribute_methods(*) #:nodoc:
18
+ super
19
+ load_schema_validations
20
+ end
21
+
22
+ private
23
+
24
+ # Adds schema-based validations to model.
25
+ # Attributes as well as associations are validated.
26
+ # For instance if there is column
27
+ #
28
+ # <code>email NOT NULL</code>
29
+ #
30
+ # defined at database-level it will be translated to
31
+ #
32
+ # <code>validates_presence_of :email</code>
33
+ #
34
+ # If there is an association named <tt>user</tt>
35
+ # based on <tt>user_id NOT NULL</tt> it will be translated to
36
+ #
37
+ # <code>validates_presence_of :user</code>
38
+ #
39
+ # Note it uses the name of association (user) not the column name (user_id).
40
+ # Only <tt>belongs_to</tt> associations are validated.
41
+ #
42
+ # This accepts following options:
43
+ # * :only - auto-validate only given attributes
44
+ # * :except - auto-validate all but given attributes
45
+ #
46
+ def load_schema_validations #:nodoc:
47
+ # Don't bother if: it's already been loaded; the class is abstract; not a base class; or the table doesn't exist
48
+ return unless create_schema_validations?
49
+
50
+ load_column_validations
51
+ load_association_validations
52
+ @schema_validations_loaded = true
53
+ end
54
+
55
+ def load_column_validations #:nodoc:
56
+ content_columns.each do |column|
57
+ name = column.name.to_sym
58
+
59
+ # Data-type validation
60
+ if column.type == :integer
61
+ validate_logged :validates_numericality_of, name, :allow_nil => true, :only_integer => true
62
+ elsif column.number?
63
+ validate_logged :validates_numericality_of, name, :allow_nil => true
64
+ elsif column.text? && column.limit
65
+ validate_logged :validates_length_of, name, :allow_nil => true, :maximum => column.limit
66
+ end
67
+
68
+ # NOT NULL constraints
69
+ if column.required_on
70
+ if column.type == :boolean
71
+ validate_logged :validates_inclusion_of, name, :in => [true, false], :message => :blank
72
+ else
73
+ validate_logged :validates_presence_of, name
74
+ end
75
+ end
76
+
77
+ # UNIQUE constraints
78
+ add_uniqueness_validation(column) if column.unique?
79
+ end
80
+ end
81
+
82
+ def load_association_validations #:nodoc:
83
+ reflect_on_all_associations(:belongs_to).each do |association|
84
+ # :primary_key_name was deprecated (noisily) in rails 3.1
85
+ foreign_key_method = (association.respond_to? :foreign_key) ? :foreign_key : :primary_key_name
86
+ column = columns_hash[association.send(foreign_key_method).to_s]
87
+ next unless column
88
+
89
+ # NOT NULL constraints
90
+ validate_logged :validates_presence_of, association.name if column.required_on
91
+
92
+ # UNIQUE constraints
93
+ add_uniqueness_validation(column) if column.unique?
94
+ end
95
+ end
96
+
97
+ def add_uniqueness_validation(column) #:nodoc:
98
+ scope = column.unique_scope.map(&:to_sym)
99
+ condition = :"#{column.name}_changed?"
100
+ name = column.name.to_sym
101
+ validate_logged :validates_uniqueness_of, name, :scope => scope, :allow_nil => true, :if => condition
102
+ end
103
+
104
+ def create_schema_validations? #:nodoc:
105
+ schema_validations_config.auto_create? && !(@schema_validations_loaded || abstract_class? || name.blank? || !table_exists?)
106
+ end
107
+
108
+ def validate_logged(method, arg, opts={}) #:nodoc:
109
+ if _filter_validation(method, arg)
110
+ msg = "[schema_validations] #{self.name}.#{method} #{arg.inspect}"
111
+ msg += ", #{opts.inspect[1...-1]}" if opts.any?
112
+ logger.info msg
113
+ send method, arg, opts
114
+ end
115
+ end
116
+
117
+ def _filter_validation(macro, name) #:nodoc:
118
+ config = schema_validations_config
119
+ types = [macro]
120
+ if match = macro.to_s.match(/^validates_(.*)_of$/)
121
+ types << match[1].to_sym
122
+ end
123
+ return false if config.only and not Array.wrap(config.only).include?(name)
124
+ return false if config.except and Array.wrap(config.except).include?(name)
125
+ return false if config.only_type and not (Array.wrap(config.only_type) & types).any?
126
+ return false if config.except_type and (Array.wrap(config.except_type) & types).any?
127
+ return true
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,11 @@
1
+ module SchemaValidations
2
+ class Railtie < Rails::Railtie #:nodoc:
3
+
4
+ initializer 'schema_validations.insert', :after => "schema_plus.insert" do
5
+ ActiveSupport.on_load(:active_record) do
6
+ SchemaValidations.insert
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module SchemaValidations
2
+ VERSION = "0.1.0.pre4"
3
+ end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ RUBY_VERSIONS = %W[1.8.7 1.9.2]
6
+ RAILS_VERSIONS = %W[3.0 3.1]
7
+
8
+ options = {}
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: #{$0} [options]"
11
+
12
+ opts.on("--install", "Install gem dependencies") do |v|
13
+ options[:install] = v
14
+ end
15
+ end.parse!
16
+
17
+ cmds = if options [:install]
18
+ ['bundle update']
19
+ else
20
+ ['bundle update --local rails | grep rails', 'rake spec']
21
+ end
22
+
23
+ n = 1
24
+ total = RUBY_VERSIONS.size * RAILS_VERSIONS.size
25
+ RUBY_VERSIONS.each do |ruby|
26
+ RAILS_VERSIONS.each do |rails|
27
+ puts "\n\n*** ruby version #{ruby} - rails version #{rails} [#{n} of #{total}]\n\n"
28
+ n += 1
29
+ allcmds = []
30
+ allcmds << "rvm use #{ruby}"
31
+ allcmds << "export SCHEMA_VALIDATIONS_RAILS_VERSION=#{rails}"
32
+ allcmds += cmds
33
+ allcmds << 'exit'
34
+ system %Q{echo '#{allcmds.join(' \n ')}' | bash -i} or abort "aborting #{$0}"
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "schema_validations/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "schema_validations"
7
+ s.version = SchemaValidations::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ronen Barzel", "Michał Łomnicki"]
10
+ s.email = ["ronen@barzel.org", "michal.lomnicki@gmail.com"]
11
+ s.homepage = "https://github.com/lomba/schema_validations"
12
+ s.summary = "Automatically creates validations basing on the database schema."
13
+ s.description = "SchemaValidations extends ActiveRecord to automatically create validations by inspecting the database schema. This makes your models more DRY as you no longer need to duplicate NOT NULL, unique, numeric and varchar constraints on the model level."
14
+
15
+ s.rubyforge_project = "schema_validations"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("schema_plus")
23
+
24
+ case ENV['SCHEMA_VALIDATIONS_RAILS_VERSION']
25
+ when '3.0'
26
+ s.add_development_dependency("rails", "~> 3.0")
27
+ s.add_development_dependency("mysql2", "~> 0.2.6")
28
+ when '3.1'
29
+ s.add_development_dependency("rails", ">= 3.1.0.rc4")
30
+ s.add_development_dependency("mysql2")
31
+ else
32
+ s.add_development_dependency("mysql2")
33
+ end
34
+
35
+ s.add_development_dependency("rake", "~> 0.8.7")
36
+ s.add_development_dependency("rspec")
37
+ s.add_development_dependency("sqlite3")
38
+ s.add_development_dependency("simplecov")
39
+ s.add_development_dependency("simplecov-gem-adapter")
40
+ s.add_development_dependency("ruby-debug19") if RUBY_VERSION >= "1.9.2"
41
+ end
42
+
@@ -0,0 +1,13 @@
1
+ require 'logger'
2
+
3
+ ActiveRecord::Base.logger = Logger.new(File.open("sqlite3.log", "w"))
4
+
5
+ ActiveRecord::Base.configurations = {
6
+ 'schema_plus' => {
7
+ :adapter => 'sqlite3',
8
+ :database => File.expand_path('schema_plus.sqlite3', File.dirname(__FILE__)),
9
+ }
10
+
11
+ }
12
+
13
+ ActiveRecord::Base.establish_connection 'schema_plus'
@@ -0,0 +1,27 @@
1
+ if RUBY_VERSION > "1.9"
2
+ require 'simplecov'
3
+ require 'simplecov-gem-adapter'
4
+ SimpleCov.start "gem"
5
+ end
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+
10
+ require 'rspec'
11
+ require 'active_record'
12
+ require 'schema_validations'
13
+ require 'logger'
14
+ require 'connection'
15
+
16
+ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
17
+
18
+ def remove_all_models
19
+ ObjectSpace.each_object(Class) do |c|
20
+ next unless c.ancestors.include? ActiveRecord::Base
21
+ next if c == ActiveRecord::Base
22
+ next if c.name.blank?
23
+ ActiveSupport::Dependencies.remove_constant c.name
24
+ end
25
+ end
26
+
27
+ SimpleCov.command_name ActiveRecord::Base.connection.adapter_name if defined? SimpleCov
@@ -0,0 +1,13 @@
1
+ # ported from rspec-rails
2
+ # There is no reason to install whole gem as we
3
+ # need only that tiny helper
4
+ module ::ActiveModel::Validations
5
+
6
+ def error_on(attribute)
7
+ self.valid?
8
+ [self.errors[attribute]].flatten.compact
9
+ end
10
+
11
+ alias :errors_on :error_on
12
+
13
+ end
@@ -0,0 +1,296 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Validations" do
4
+
5
+ before(:all) do
6
+ define_schema
7
+ end
8
+
9
+ after(:each) do
10
+ remove_all_models
11
+ end
12
+
13
+ context "auto-created" do
14
+ before(:each) do
15
+ with_auto_validations do
16
+ class Article < ActiveRecord::Base ; end
17
+
18
+ class Review < ActiveRecord::Base
19
+ belongs_to :article
20
+ belongs_to :news_article, :class_name => 'Article', :foreign_key => :article_id
21
+ schema_validations :except => :content
22
+ end
23
+ end
24
+ end
25
+
26
+ it "should be valid with valid attributes" do
27
+ Article.new(valid_attributes).should be_valid
28
+ end
29
+
30
+ it "should validate content presence" do
31
+ post = Article.new.should have(1).error_on(:content)
32
+ end
33
+
34
+ it "should check title length" do
35
+ Article.new(:title => 'a' * 100).should have(1).error_on(:title)
36
+ end
37
+
38
+ it "should validate state numericality" do
39
+ Article.new(:state => 'unknown').should have(1).error_on(:state)
40
+ end
41
+
42
+ it "should validate if state is integer" do
43
+ Article.new(:state => 1.23).should have(1).error_on(:state)
44
+ end
45
+
46
+ it "should validate average_mark numericality" do
47
+ Article.new(:average_mark => "high").should have(1).error_on(:average_mark)
48
+ end
49
+
50
+ it "should validate boolean fields" do
51
+ Article.new(:active => nil).should have(1).error_on(:active)
52
+ end
53
+
54
+ it "should validate title uniqueness" do
55
+ article1 = Article.create(valid_attributes)
56
+ article2 = Article.new(:title => valid_attributes[:title])
57
+ article2.should have(1).error_on(:title)
58
+ article1.destroy
59
+ end
60
+
61
+ it "should validate state uniqueness in scope of 'active' value" do
62
+ article1 = Article.create(valid_attributes)
63
+ article2 = Article.new(valid_attributes.merge(:title => 'SchemaPlus 2.0 released'))
64
+ article2.should_not be_valid
65
+ article2.toggle(:active)
66
+ article2.should be_valid
67
+ article1.destroy
68
+ end
69
+
70
+ it "should validate presence of belongs_to association" do
71
+ review = Review.new
72
+ review.should have(1).error_on(:article)
73
+ end
74
+
75
+ it "should validate uniqueness of belongs_to association" do
76
+ article = Article.create(valid_attributes)
77
+ article.should be_valid
78
+ review1 = Review.create(:article => article, :author => 'michal')
79
+ review1.should be_valid
80
+ review2 = Review.new(:article => article, :author => 'michal')
81
+ review2.should have_at_least(1).error_on(:article_id)
82
+ end
83
+
84
+ it "should validate associations with unmatched column and name" do
85
+ Review.new.should have(1).error_on(:news_article)
86
+ end
87
+
88
+ def valid_attributes
89
+ {
90
+ :title => 'SchemaPlus released!',
91
+ :content => "Database matters. Get full use of it but don't write unecessary code. Get SchemaPlus!",
92
+ :state => 3,
93
+ :average_mark => 9.78,
94
+ :active => true
95
+ }
96
+ end
97
+
98
+ end
99
+
100
+ context "auto-created but changed" do
101
+ before(:each) do
102
+ with_auto_validations do
103
+ class Article < ActiveRecord::Base ; end
104
+ class Review < ActiveRecord::Base
105
+ belongs_to :article
106
+ belongs_to :news_article, :class_name => 'Article', :foreign_key => :article_id
107
+ end
108
+ end
109
+ @too_big_content = 'a' * 1000
110
+ end
111
+
112
+ it "would normally have an error" do
113
+ @review = Review.new(:content => @too_big_content)
114
+ @review.should have(1).error_on(:content)
115
+ @review.should have(1).error_on(:author)
116
+ end
117
+
118
+ it "shouldn't validate fields passed to :except option" do
119
+ Review.schema_validations :except => :content
120
+ @review = Review.new(:content => @too_big_content)
121
+ @review.should have(:no).errors_on(:content)
122
+ @review.should have(1).error_on(:author)
123
+ end
124
+
125
+ it "shouldn't validate types passed to :except_type option using full validation" do
126
+ Review.schema_validations :except_type => :validates_length_of
127
+ @review = Review.new(:content => @too_big_content)
128
+ @review.should have(:no).errors_on(:content)
129
+ @review.should have(1).error_on(:author)
130
+ end
131
+
132
+ it "shouldn't validate types passed to :except_type option using shorthand" do
133
+ Review.schema_validations :except_type => :length
134
+ @review = Review.new(:content => @too_big_content)
135
+ @review.should have(:no).errors_on(:content)
136
+ @review.should have(1).error_on(:author)
137
+ end
138
+
139
+ it "should only validate type passed to :only_type option" do
140
+ Review.schema_validations :only_type => :length
141
+ @review = Review.new(:content => @too_big_content)
142
+ @review.should have(1).error_on(:content)
143
+ @review.should have(:no).errors_on(:author)
144
+ end
145
+
146
+
147
+ it "shouldn't create validations if locally disabled" do
148
+ Review.schema_validations :auto_create => false
149
+ @review = Review.new(:content => @too_big_content)
150
+ @review.should have(:no).errors_on(:content)
151
+ @review.should have(:no).error_on(:author)
152
+ end
153
+ end
154
+
155
+ context "auto-created disabled" do
156
+ around(:each) do |example|
157
+ with_auto_validations(false, &example)
158
+ end
159
+
160
+ before(:each) do
161
+ class Review < ActiveRecord::Base
162
+ belongs_to :article
163
+ belongs_to :news_article, :class_name => 'Article', :foreign_key => :article_id
164
+ end
165
+ @too_big_content = 'a' * 1000
166
+ end
167
+
168
+ it "should not create validation" do
169
+ Review.new(:content => @too_big_title).should have(:no).errors_on(:content)
170
+ end
171
+
172
+ it "should create validation if locally enabled" do
173
+ Review.schema_validations :auto_create => true
174
+ Review.new(:content => @too_big_content).should have(1).error_on(:content)
175
+ end
176
+
177
+ end
178
+
179
+ context "manually invoked" do
180
+ before(:each) do
181
+ class Article < ActiveRecord::Base ; end
182
+ Article.schema_validations :only => [:title, :state]
183
+
184
+ class Review < ActiveRecord::Base
185
+ belongs_to :dummy_association
186
+ schema_validations :except => :content
187
+ end
188
+ end
189
+
190
+ it "should validate fields passed to :only option" do
191
+ too_big_title = 'a' * 100
192
+ wrong_state = 'unknown'
193
+ article = Article.new(:title => too_big_title, :state => wrong_state)
194
+ article.should have(1).error_on(:title)
195
+ article.should have(1).error_on(:state)
196
+ end
197
+
198
+ it "shouldn't validate skipped fields" do
199
+ article = Article.new
200
+ article.should have(:no).errors_on(:content)
201
+ article.should have(:no).errors_on(:average_mark)
202
+ end
203
+
204
+ it "shouldn't validate association on unexisting column" do
205
+ Review.new.should have(:no).errors_on(:dummy_association)
206
+ end
207
+
208
+ it "shouldn't validate fields passed to :except option" do
209
+ Review.new.should have(:no).errors_on(:content)
210
+ end
211
+
212
+ it "should validate all fields but passed to :except option" do
213
+ Review.new.should have(1).error_on(:author)
214
+ end
215
+
216
+ end
217
+
218
+ context "manually invoked" do
219
+ before(:each) do
220
+ class Review < ActiveRecord::Base
221
+ belongs_to :article
222
+ end
223
+ @columns = Review.content_columns.dup
224
+ Review.schema_validations :only => [:title]
225
+ end
226
+
227
+ it "shouldn't validate associations not included in :only option" do
228
+ Review.new.should have(:no).errors_on(:article)
229
+ end
230
+
231
+ it "shouldn't change content columns of the model" do
232
+ @columns.should == Review.content_columns
233
+ end
234
+
235
+ end
236
+
237
+ context "when used with STI" do
238
+ around(:each) { |example| with_auto_validations(&example) }
239
+
240
+ it "should set validations on base class" do
241
+ class Review < ActiveRecord::Base ; end
242
+ class PremiumReview < Review ; end
243
+ PremiumReview.new
244
+ Review.new.should have(1).error_on(:author)
245
+ end
246
+
247
+ it "shouldn't create doubled validations" do
248
+ class Review < ActiveRecord::Base ; end
249
+ Review.new
250
+ class PremiumReview < Review ; end
251
+ PremiumReview.new.should have(1).error_on(:author)
252
+ end
253
+
254
+ end
255
+
256
+ protected
257
+ def with_auto_validations(value = true)
258
+ old_value = SchemaValidations.config.auto_create
259
+ begin
260
+ SchemaValidations.setup do |config|
261
+ config.auto_create = value
262
+ end
263
+ yield
264
+ ensure
265
+ SchemaValidations.config.auto_create = old_value
266
+ end
267
+ end
268
+
269
+ def define_schema
270
+ ActiveRecord::Migration.suppress_messages do
271
+ ActiveRecord::Schema.define do
272
+ connection.tables.each do |table| drop_table table end
273
+
274
+ create_table :articles, :force => true do |t|
275
+ t.string :title, :limit => 50
276
+ t.text :content, :null => false
277
+ t.integer :state
278
+ t.float :average_mark, :null => false
279
+ t.boolean :active, :null => false
280
+ end
281
+ add_index :articles, :title, :unique => true
282
+ add_index :articles, [:state, :active], :unique => true
283
+
284
+ create_table :reviews, :force => true do |t|
285
+ t.integer :article_id, :null => false
286
+ t.string :author, :null => false
287
+ t.string :content, :limit => 200
288
+ t.string :type
289
+ end
290
+ add_index :reviews, :article_id, :unique => true
291
+
292
+ end
293
+ end
294
+ end
295
+
296
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schema_validations
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ - pre4
10
+ version: 0.1.0.pre4
11
+ platform: ruby
12
+ authors:
13
+ - Ronen Barzel
14
+ - "Micha\xC5\x82 \xC5\x81omnicki"
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-07-25 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: schema_plus
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: mysql2
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ - 8
56
+ - 7
57
+ version: 0.8.7
58
+ type: :development
59
+ version_requirements: *id003
60
+ - !ruby/object:Gem::Dependency
61
+ name: rspec
62
+ prerelease: false
63
+ requirement: &id004 !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :development
71
+ version_requirements: *id004
72
+ - !ruby/object:Gem::Dependency
73
+ name: sqlite3
74
+ prerelease: false
75
+ requirement: &id005 !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ type: :development
83
+ version_requirements: *id005
84
+ - !ruby/object:Gem::Dependency
85
+ name: simplecov
86
+ prerelease: false
87
+ requirement: &id006 !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ type: :development
95
+ version_requirements: *id006
96
+ - !ruby/object:Gem::Dependency
97
+ name: simplecov-gem-adapter
98
+ prerelease: false
99
+ requirement: &id007 !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ type: :development
107
+ version_requirements: *id007
108
+ description: SchemaValidations extends ActiveRecord to automatically create validations by inspecting the database schema. This makes your models more DRY as you no longer need to duplicate NOT NULL, unique, numeric and varchar constraints on the model level.
109
+ email:
110
+ - ronen@barzel.org
111
+ - michal.lomnicki@gmail.com
112
+ executables: []
113
+
114
+ extensions: []
115
+
116
+ extra_rdoc_files: []
117
+
118
+ files:
119
+ - .gitignore
120
+ - Gemfile
121
+ - MIT-LICENSE
122
+ - README.rdoc
123
+ - Rakefile
124
+ - init.rb
125
+ - lib/schema_validations.rb
126
+ - lib/schema_validations/active_record/validations.rb
127
+ - lib/schema_validations/railtie.rb
128
+ - lib/schema_validations/version.rb
129
+ - runspecs
130
+ - schema_validations.gemspec
131
+ - spec/connection.rb
132
+ - spec/spec_helper.rb
133
+ - spec/support/active_model.rb
134
+ - spec/validations_spec.rb
135
+ has_rdoc: true
136
+ homepage: https://github.com/lomba/schema_validations
137
+ licenses: []
138
+
139
+ post_install_message:
140
+ rdoc_options: []
141
+
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">"
154
+ - !ruby/object:Gem::Version
155
+ segments:
156
+ - 1
157
+ - 3
158
+ - 1
159
+ version: 1.3.1
160
+ requirements: []
161
+
162
+ rubyforge_project: schema_validations
163
+ rubygems_version: 1.3.6
164
+ signing_key:
165
+ specification_version: 3
166
+ summary: Automatically creates validations basing on the database schema.
167
+ test_files: []
168
+