valhammer 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.simplecov +2 -6
- data/README.md +55 -5
- data/lib/valhammer/validations.rb +16 -5
- data/lib/valhammer/version.rb +1 -1
- data/spec/db/schema.rb +3 -0
- data/spec/dummy/config.ru +1 -1
- data/spec/lib/valhammer/validations_spec.rb +46 -10
- data/valhammer.gemspec +2 -1
- metadata +17 -4
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a17bafd0cee09ccf70430c604c381aa2cf9bcd1d
|
4
|
+
data.tar.gz: 1104fae993e967539bf5f96eb2f2b917cfe9fc9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e48594d0e505482ab9a8915c7336890351e245dd97ba4849ddcf604ae836ef3421b07985a93338d984ba21475064332b3c0008a739b3b515ed360fdf8344fb55
|
7
|
+
data.tar.gz: 75fc238355ba8e2338af112a085f340f427c32c8a0e0b9bfea5fcdc0d89369694d426d15b45055620e2600a7efc9ca5a38e08a683a74b0b94cc09ba948002910
|
data/.simplecov
CHANGED
@@ -1,9 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
4
|
-
SimpleCov::Formatter::HTMLFormatter,
|
5
|
-
Coveralls::SimpleCov::Formatter
|
6
|
-
]
|
1
|
+
require 'codeclimate-test-reporter'
|
2
|
+
CodeClimate::TestReporter.start
|
7
3
|
|
8
4
|
SimpleCov.start do
|
9
5
|
add_filter('spec')
|
data/README.md
CHANGED
@@ -4,19 +4,18 @@
|
|
4
4
|
[![Build Status][BS img]][Build Status]
|
5
5
|
[![Dependency Status][DS img]][Dependency Status]
|
6
6
|
[![Code Climate][CC img]][Code Climate]
|
7
|
-
[![Coverage Status][CS img]][
|
7
|
+
[![Coverage Status][CS img]][Code Climate]
|
8
8
|
|
9
9
|
[Gem Version]: https://rubygems.org/gems/valhammer
|
10
|
-
[Build Status]: https://
|
10
|
+
[Build Status]: https://codeship.com/projects/91215
|
11
11
|
[Dependency Status]: https://gemnasium.com/ausaccessfed/valhammer
|
12
12
|
[Code Climate]: https://codeclimate.com/github/ausaccessfed/valhammer
|
13
|
-
[Coverage Status]: https://coveralls.io/r/ausaccessfed/valhammer
|
14
13
|
|
15
14
|
[GV img]: https://img.shields.io/gem/v/valhammer.svg
|
16
|
-
[BS img]: https://img.shields.io/
|
15
|
+
[BS img]: https://img.shields.io/codeship/eb0d3cd0-0cd1-0133-3c85-7aae0ba3591b/develop.svg
|
17
16
|
[DS img]: https://img.shields.io/gemnasium/ausaccessfed/valhammer.svg
|
18
17
|
[CC img]: https://img.shields.io/codeclimate/github/ausaccessfed/valhammer.svg
|
19
|
-
[CS img]: https://img.shields.io/
|
18
|
+
[CS img]: https://img.shields.io/codeclimate/coverage/github/ausaccessfed/valhammer.svg
|
20
19
|
|
21
20
|
Automatically validate ActiveRecord models based on the database schema.
|
22
21
|
|
@@ -119,6 +118,39 @@ end
|
|
119
118
|
That is, the last column in the key is the field which gets validated, and the
|
120
119
|
other columns form the `scope` argument.
|
121
120
|
|
121
|
+
If any of the scope columns are nullable, the validation will be conditional on
|
122
|
+
the presence of the scope values. This avoids the situation where your
|
123
|
+
underlying database would accept a row (because it considers `NULL` values not
|
124
|
+
equal to each other, which is true of [SQLite][sqlite-null-index],
|
125
|
+
[PostgreSQL][postgres-null-index] and [MySQL/MariaDB][mysql-null-index] at the
|
126
|
+
least.)
|
127
|
+
|
128
|
+
If the above example table had nullable columns, for example:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
create_table(:widgets) do |t|
|
132
|
+
t.string :supplier_code, null: true, default: nil
|
133
|
+
t.string :item_code, null: true, default: nil
|
134
|
+
|
135
|
+
t.index [:supplier_code, :item_code], unique: true
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
This amended table structure causes Valhammer to create validations as though
|
140
|
+
you had written:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
class Widget < ActiveRecord::Base
|
144
|
+
validates :item_code, uniqueness: { scope: :supplier_code,
|
145
|
+
if: -> { supplier_code },
|
146
|
+
allow_nil: true }
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
[sqlite-null-index]: https://www.sqlite.org/lang_createindex.html
|
151
|
+
[postgres-null-index]: http://www.postgresql.org/docs/9.0/static/indexes-unique.html
|
152
|
+
[mysql-null-index]: https://dev.mysql.com/doc/refman/5.0/en/create-index.html
|
153
|
+
|
122
154
|
## Duplicate Unique Keys
|
123
155
|
|
124
156
|
Valhammer is able to handle the simple case when multiple unique keys reference
|
@@ -184,6 +216,24 @@ anyway, if it means your association queries benefit from the index).
|
|
184
216
|
|
185
217
|
Alternatively, apply the validation yourself using ActiveRecord.
|
186
218
|
|
219
|
+
## Partial Unique Keys
|
220
|
+
|
221
|
+
When a unique key is partially applied to a relation, that key will not be given
|
222
|
+
a uniqueness validation.
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
create_table(:widgets) do |t|
|
226
|
+
t.string :supplier_code, null: true, default: nil
|
227
|
+
t.string :item_code, null: true, default: nil
|
228
|
+
|
229
|
+
t.index [:supplier_code, :item_code], unique: true,
|
230
|
+
where: 'item_code LIKE "a%"'
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
In this case, it is not possible for valhammer to determine the behaviour of the
|
235
|
+
`where` clause, so the validation must be manually created.
|
236
|
+
|
187
237
|
## Contributing
|
188
238
|
|
189
239
|
Refer to [GitHub Flow](https://guides.github.com/introduction/flow/) for
|
@@ -62,16 +62,21 @@ module Valhammer
|
|
62
62
|
def valhammer_unique(validations, column, opts)
|
63
63
|
return unless opts[:uniqueness]
|
64
64
|
|
65
|
-
unique_keys =
|
66
|
-
i.unique && i.columns.last == column.name
|
67
|
-
end
|
68
|
-
|
65
|
+
unique_keys = valhammer_unique_keys(column)
|
69
66
|
return unless unique_keys.one?
|
70
67
|
|
71
68
|
scope = unique_keys.first.columns[0..-2]
|
72
69
|
|
73
|
-
|
70
|
+
validations[:uniqueness] = valhammer_unique_opts(scope)
|
71
|
+
end
|
72
|
+
|
73
|
+
def valhammer_unique_opts(scope)
|
74
|
+
nullable = scope.select { |c| columns_hash[c].null }
|
75
|
+
|
76
|
+
opts = { allow_nil: true }
|
74
77
|
opts[:scope] = scope if scope.any?
|
78
|
+
opts[:if] = -> { nullable.all? { |c| send(c) } } if nullable.any?
|
79
|
+
opts
|
75
80
|
end
|
76
81
|
|
77
82
|
def valhammer_numeric(validations, column, opts)
|
@@ -108,6 +113,12 @@ module Valhammer
|
|
108
113
|
field == primary_key || VALHAMMER_EXCLUDED_FIELDS.include?(field)
|
109
114
|
end
|
110
115
|
|
116
|
+
def valhammer_unique_keys(column)
|
117
|
+
@valhammer_indexes.select do |i|
|
118
|
+
i.unique && !i.where && i.columns.last == column.name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
111
122
|
def valhammer_assoc_name(field)
|
112
123
|
reflect_on_all_associations(:belongs_to)
|
113
124
|
.find { |a| a.foreign_key == field }.try(:name)
|
data/lib/valhammer/version.rb
CHANGED
data/spec/db/schema.rb
CHANGED
@@ -15,17 +15,20 @@ ActiveRecord::Schema.define(version: 0) do
|
|
15
15
|
t.timestamps null: false
|
16
16
|
|
17
17
|
t.index :identifier, unique: true
|
18
|
+
t.index [:organisation_id, :name], unique: true
|
18
19
|
end
|
19
20
|
|
20
21
|
create_table :capabilities, force: true do |t|
|
21
22
|
t.belongs_to :organisation, null: true, default: nil
|
22
23
|
|
23
24
|
t.string :name, null: false, default: nil
|
25
|
+
t.string :identifier, null: false, default: nil
|
24
26
|
t.boolean :core, null: true, default: nil
|
25
27
|
|
26
28
|
t.timestamps null: false
|
27
29
|
|
28
30
|
t.index [:organisation_id, :name], unique: true
|
31
|
+
t.index :identifier, unique: true, where: 'organisation_id IS NULL'
|
29
32
|
end
|
30
33
|
|
31
34
|
create_table :organisations, force: true do |t|
|
data/spec/dummy/config.ru
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require ::File.expand_path('../config/environment',
|
1
|
+
require ::File.expand_path('../config/environment', __FILE__)
|
2
2
|
run Rails.application
|
@@ -1,4 +1,11 @@
|
|
1
1
|
RSpec.describe Valhammer::Validations do
|
2
|
+
around do |example|
|
3
|
+
ActiveRecord::Base.transaction do
|
4
|
+
example.run
|
5
|
+
fail(ActiveRecord::Rollback)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
2
9
|
def validation_impl(kind)
|
3
10
|
name = "#{kind.to_s.camelize}Validator"
|
4
11
|
|
@@ -10,8 +17,11 @@ RSpec.describe Valhammer::Validations do
|
|
10
17
|
end
|
11
18
|
|
12
19
|
RSpec::Matchers.define :a_validator_for do |field, kind, opts = nil|
|
20
|
+
include RSpec::Matchers::Composable
|
21
|
+
|
13
22
|
match do |v|
|
14
|
-
v.is_a?(validation_impl(kind)) &&
|
23
|
+
v.is_a?(validation_impl(kind)) &&
|
24
|
+
(opts.nil? || values_match?(opts, v.options)) &&
|
15
25
|
v.attributes.map(&:to_s) == [field.to_s]
|
16
26
|
end
|
17
27
|
|
@@ -85,13 +95,46 @@ RSpec.describe Valhammer::Validations do
|
|
85
95
|
it { is_expected.not_to include(a_validator_for(:mail, :uniqueness)) }
|
86
96
|
end
|
87
97
|
|
88
|
-
context 'with a
|
98
|
+
context 'with a partial unique index' do
|
89
99
|
subject { Capability.validators }
|
90
100
|
|
101
|
+
it { is_expected.not_to include(a_validator_for(:identifier, :uniqueness)) }
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'with a composite unique index' do
|
91
105
|
let(:opts) do
|
92
106
|
{ scope: ['organisation_id'], case_sensitive: true, allow_nil: true }
|
93
107
|
end
|
108
|
+
|
109
|
+
it { is_expected.to include(a_validator_for(:name, :uniqueness, opts)) }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with a composite unique index with nullable scope column' do
|
113
|
+
subject { Capability.validators }
|
114
|
+
|
115
|
+
let(:opts) do
|
116
|
+
{ scope: ['organisation_id'], case_sensitive: true, allow_nil: true,
|
117
|
+
if: an_instance_of(Proc) }
|
118
|
+
end
|
119
|
+
|
94
120
|
it { is_expected.to include(a_validator_for(:name, :uniqueness, opts)) }
|
121
|
+
|
122
|
+
it 'skips validation when the nullable scope column is null' do
|
123
|
+
o = Organisation.create!(country: 'Australia', city: 'Brisbane',
|
124
|
+
name: 'Test Organisation')
|
125
|
+
|
126
|
+
attrs = { organisation_id: o.id, name: 'Software Development',
|
127
|
+
identifier: SecureRandom.urlsafe_base64 }
|
128
|
+
|
129
|
+
Capability.create!(attrs)
|
130
|
+
|
131
|
+
attrs[:identifier] = SecureRandom.urlsafe_base64
|
132
|
+
Capability.create!(attrs.merge(organisation_id: nil))
|
133
|
+
|
134
|
+
attrs[:identifier] = SecureRandom.urlsafe_base64
|
135
|
+
expect(Capability.new(attrs.merge(organisation_id: nil))).to be_valid
|
136
|
+
expect(Capability.new(attrs)).not_to be_valid
|
137
|
+
end
|
95
138
|
end
|
96
139
|
|
97
140
|
context 'with duplicate unique indexes' do
|
@@ -205,13 +248,6 @@ RSpec.describe Valhammer::Validations do
|
|
205
248
|
end
|
206
249
|
|
207
250
|
context 'sanity check' do
|
208
|
-
around do |example|
|
209
|
-
ActiveRecord::Base.transaction do
|
210
|
-
example.run
|
211
|
-
fail(ActiveRecord::Rollback)
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
251
|
let(:organisation) do
|
216
252
|
Organisation.create!(name: 'Enhanced Collaborative Methodologies Pty Ltd',
|
217
253
|
country: 'Australia', city: 'Brisbane')
|
@@ -231,7 +267,7 @@ RSpec.describe Valhammer::Validations do
|
|
231
267
|
context Capability do
|
232
268
|
subject do
|
233
269
|
Capability.create!(organisation: organisation, core: true,
|
234
|
-
name: 'Project Management')
|
270
|
+
name: 'Project Management', identifier: 'pm')
|
235
271
|
end
|
236
272
|
|
237
273
|
it { is_expected.to be_valid }
|
data/valhammer.gemspec
CHANGED
@@ -23,11 +23,12 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency 'rails', '~> 4.1'
|
24
24
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
25
25
|
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'rubocop', '~> 0.34'
|
26
27
|
spec.add_development_dependency 'guard'
|
27
28
|
spec.add_development_dependency 'guard-rubocop'
|
28
29
|
spec.add_development_dependency 'guard-rspec'
|
29
30
|
spec.add_development_dependency 'guard-bundler'
|
30
31
|
spec.add_development_dependency 'simplecov'
|
31
|
-
spec.add_development_dependency '
|
32
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
32
33
|
spec.add_development_dependency 'sqlite3'
|
33
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: valhammer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shaun Mangelsdorf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.34'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.34'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: guard
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,7 +151,7 @@ dependencies:
|
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '0'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
154
|
+
name: codeclimate-test-reporter
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
142
156
|
requirements:
|
143
157
|
- - ">="
|
@@ -175,7 +189,6 @@ files:
|
|
175
189
|
- ".rspec"
|
176
190
|
- ".rubocop.yml"
|
177
191
|
- ".simplecov"
|
178
|
-
- ".travis.yml"
|
179
192
|
- Gemfile
|
180
193
|
- Guardfile
|
181
194
|
- LICENSE
|