schema_validations 0.1.0.pre4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +25 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +25 -0
- data/README.rdoc +125 -0
- data/Rakefile +19 -0
- data/init.rb +1 -0
- data/lib/schema_validations.rb +104 -0
- data/lib/schema_validations/active_record/validations.rb +133 -0
- data/lib/schema_validations/railtie.rb +11 -0
- data/lib/schema_validations/version.rb +3 -0
- data/runspecs +36 -0
- data/schema_validations.gemspec +42 -0
- data/spec/connection.rb +13 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/active_model.rb +13 -0
- data/spec/validations_spec.rb +296 -0
- metadata +168 -0
data/.gitignore
ADDED
@@ -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
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/runspecs
ADDED
@@ -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
|
+
|
data/spec/connection.rb
ADDED
@@ -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'
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|