valhammer 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ea291bcc9bb1f0c3931b9f5f83b8837f75be21e
4
- data.tar.gz: 508cdc21ee92a31b986f5dbc9c6d449f5948355a
3
+ metadata.gz: a17bafd0cee09ccf70430c604c381aa2cf9bcd1d
4
+ data.tar.gz: 1104fae993e967539bf5f96eb2f2b917cfe9fc9f
5
5
  SHA512:
6
- metadata.gz: 15ccdb15b5890eecf69712db5dd761562389294bbb18eee58aaf76f5a15a7b6ffb73fe50c21fe89974c13ed4e5493f8ceb736c03fc351e34806b6a2f525df9d3
7
- data.tar.gz: e73bcfc221876e783226d08f9ce828ccb15783b4147df604f89c3259ed9d8f279d54f4b0716f7076c0cce20274d4436f9434f08d3eaf47ec1305728ce2fb1545
6
+ metadata.gz: e48594d0e505482ab9a8915c7336890351e245dd97ba4849ddcf604ae836ef3421b07985a93338d984ba21475064332b3c0008a739b3b515ed360fdf8344fb55
7
+ data.tar.gz: 75fc238355ba8e2338af112a085f340f427c32c8a0e0b9bfea5fcdc0d89369694d426d15b45055620e2600a7efc9ca5a38e08a683a74b0b94cc09ba948002910
data/.simplecov CHANGED
@@ -1,9 +1,5 @@
1
- require 'coveralls'
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]][Coverage Status]
7
+ [![Coverage Status][CS img]][Code Climate]
8
8
 
9
9
  [Gem Version]: https://rubygems.org/gems/valhammer
10
- [Build Status]: https://travis-ci.org/ausaccessfed/valhammer
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/travis/ausaccessfed/valhammer/develop.svg
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/coveralls/ausaccessfed/valhammer.svg
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 = @valhammer_indexes.select do |i|
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
- opts = validations[:uniqueness] = { allow_nil: true }
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)
@@ -1,3 +1,3 @@
1
1
  module Valhammer
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.0'
3
3
  end
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', __FILE__)
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)) && (opts.nil? || v.options == opts) &&
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 composite unique index' do
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 'coveralls'
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.1.3
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-04-30 00:00:00.000000000 Z
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: coveralls
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
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.1.1
4
- branches:
5
- only:
6
- - master
7
- - develop