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 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