required_query_attributes 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9983089c7aaeb16d4d63ecf5054937faaeefd2c9
4
+ data.tar.gz: 0b58629ae1b2a85a0f8343fc0ad1657e5ac9624c
5
+ SHA512:
6
+ metadata.gz: c17fd158de571d0cf5e2ede9cc015a6adb58ff28eef54b1a7584b782a43ec398d1b9aee8508fe0b2a0588224647cd6d3a55e9acace2ebf49e77f837bab00d298
7
+ data.tar.gz: 392a7d28d5a63c04b19cea264625357b961f296ef1d90345dc1761e810489f5d7ff933d7552771fa763e19ee424a114fc55cc9911cbb4241163a3f48b5feecc9
@@ -0,0 +1,14 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
3
+ version: 2
4
+ jobs:
5
+ build:
6
+ docker:
7
+ - image: circleci/ruby:2.4.3
8
+ working_directory: ~/repo
9
+ steps:
10
+ - checkout
11
+ - run: bundle install --jobs=4 --retry=3 --path vendor/bundle
12
+ - run: bundle exec bundle-audit update && bundle exec bundle-audit check
13
+ - run: bundle exec rubocop
14
+ - run: bundle exec rspec
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ inherit_gem:
2
+ ws-style:
3
+ - default.yml
4
+
5
+ AllCops:
6
+ # Specify your target Ruby version here (only major/minor versions):
7
+ TargetRubyVersion: 2.4
@@ -0,0 +1 @@
1
+ 2.4.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in required_query_attributes.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Wealthsimple
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,68 @@
1
+ # RequiredQueryAttributes [![CircleCI](https://circleci.com/gh/wealthsimple/required_query_attributes.svg?style=svg)](https://circleci.com/gh/wealthsimple/required_query_attributes)
2
+
3
+ Enforce presence of specific columns in ActiveRecord queries.
4
+
5
+ ## Description
6
+
7
+ If you have a `records` table like this:
8
+
9
+ ```ruby
10
+ create_table 'records' do |t|
11
+ t.date 'date'
12
+ t.string 'text'
13
+ end
14
+ ```
15
+
16
+ You might want to make sure that `SELECT` queries always provide a predicate condition on `date`.
17
+
18
+ It could be because you have a very large table, and your only good index is on `date`.
19
+
20
+ Or maybe your data is partitioned by `date`, and thus not providing it will trigger full table scan.
21
+
22
+ `RequiredQueryAttributes` ensures all `SELECT` queries provide at least one predicate condition on the desired columns, or they will fail with an exception:
23
+
24
+ ```ruby
25
+ RequiredQueryAttributes::RequiredAttributeMissing:
26
+ Required attributes ["date"] missing from query:
27
+ SELECT "records".* FROM "records" WHERE "records"."text" = 'string_to_find'
28
+ ```
29
+
30
+ ## Installation
31
+
32
+ Add this line to your application's `Gemfile` and execute `bundle`:
33
+
34
+ ```ruby
35
+ gem 'required_query_attributes'
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ Using the same `records` example:
41
+
42
+ ```ruby
43
+ create_table 'records' do |t|
44
+ t.date 'date'
45
+ t.string 'text'
46
+ end
47
+ ```
48
+
49
+ Configure your class:
50
+
51
+ ```ruby
52
+ class Record < ActiveRecord::Base
53
+ include RequiredQueryAttributes
54
+
55
+ require_query_attribute :date
56
+
57
+ # Add additional attributes if desired
58
+ # require_query_attribute :text
59
+ end
60
+ ```
61
+
62
+ Profit.
63
+
64
+ ## FYI
65
+
66
+ This gem tries to do its job as cleanly as possible, only changing `ActiveRecord` behaviour when necessary.
67
+
68
+ There are no global changes to `ActiveRecord`: only models which you choose to install `RequiredQueryAttributes` are affected.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+
5
+ require 'required_query_attributes/model'
6
+ require 'required_query_attributes/relation'
7
+ require 'required_query_attributes/version'
8
+
9
+ module RequiredQueryAttributes
10
+ def self.included(base)
11
+ base.send :extend, Model::ClassMethods
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module RequiredQueryAttributes
2
+ # Extensions to `ActiveRecord::Base`.
3
+ # We want to have the smallest possible footprint here.
4
+ module Model
5
+ module ClassMethods
6
+ # Declare this in your model to enforce that the provided attribute
7
+ # must be used in at least one condition on SELECT statements.
8
+ #
9
+ # Parameters:
10
+ #
11
+ # - name - Attribute to enforce presence in queries.
12
+ #
13
+ # @api public
14
+ def require_query_attribute(attribute)
15
+ @required_query_attributes ||= []
16
+ @required_query_attributes << attribute.to_s
17
+ end
18
+
19
+ attr_reader :required_query_attributes
20
+
21
+ def self.extended(base)
22
+ # Attach our own RequiredQueryAttributes::Relation
23
+ # to existing delegate factories
24
+ base.instance_eval do
25
+ @relation_delegate_cache.values.each do |klass|
26
+ klass.include Relation
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ module RequiredQueryAttributes
2
+ class RequiredAttributeMissing < ActiveRecord::ActiveRecordError; end
3
+
4
+ module Relation
5
+ extend ActiveSupport::Concern
6
+
7
+ # Intercepts calls to 'load', validating for required attributes before execution
8
+ def load(*)
9
+ required_query_attributes = @klass.required_query_attributes
10
+ check_required_query_attributes(required_query_attributes) if required_query_attributes
11
+ super
12
+ end
13
+
14
+ private
15
+
16
+ def check_required_query_attributes(required_query_attributes)
17
+ predicate_attributes = where_clause.send(:predicates).
18
+ select { |node| node.left.relation.name == table_name }.
19
+ map { |node| node.left.name }
20
+
21
+ missing_names = required_query_attributes - predicate_attributes
22
+
23
+ if missing_names.present?
24
+ raise RequiredAttributeMissing,
25
+ "Required attributes #{missing_names} missing from query: #{to_sql.truncate(200)}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequiredQueryAttributes
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'required_query_attributes/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'required_query_attributes'
9
+ spec.version = RequiredQueryAttributes::VERSION
10
+ spec.authors = ['Marco Costa']
11
+ spec.email = ['marco@marcotc.com']
12
+ spec.license = "MIT"
13
+
14
+ spec.summary = 'Enforce presence of specific columns in ActiveRecord queries.'
15
+ spec.description = <<~DESC
16
+ Enforces the presence of specific columns in SELECT queries. Useful when you have
17
+ performance restrictions when specific columns are not included in your WHERE clauses.
18
+ Indexed columns and partition keys are common examples of such columns.
19
+ DESC
20
+ spec.homepage = 'https://github.com/wealthsimple/required_query_attributes'
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.required_ruby_version = '>= 2.4'
30
+
31
+ spec.add_dependency 'activerecord', '~> 5'
32
+
33
+ spec.add_development_dependency 'bundler', '~> 1.15'
34
+ spec.add_development_dependency 'bundler-audit', '~> 0'
35
+ spec.add_development_dependency 'rake', '~> 10.0'
36
+ spec.add_development_dependency 'rspec', '~> 3.0'
37
+ spec.add_development_dependency 'rubocop', '0.49.1' # Temporarily necessary to avoid breaking ws-style
38
+ spec.add_development_dependency 'sqlite3', '~> 1'
39
+ spec.add_development_dependency 'ws-style', '~> 0'
40
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: required_query_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Marco Costa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler-audit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.49.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.49.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ws-style
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: |
126
+ Enforces the presence of specific columns in SELECT queries. Useful when you have
127
+ performance restrictions when specific columns are not included in your WHERE clauses.
128
+ Indexed columns and partition keys are common examples of such columns.
129
+ email:
130
+ - marco@marcotc.com
131
+ executables: []
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - ".circleci/config.yml"
136
+ - ".gitignore"
137
+ - ".rspec"
138
+ - ".rubocop.yml"
139
+ - ".ruby-version"
140
+ - Gemfile
141
+ - LICENSE.md
142
+ - README.md
143
+ - Rakefile
144
+ - lib/required_query_attributes.rb
145
+ - lib/required_query_attributes/model.rb
146
+ - lib/required_query_attributes/relation.rb
147
+ - lib/required_query_attributes/version.rb
148
+ - required_query_attributes.gemspec
149
+ homepage: https://github.com/wealthsimple/required_query_attributes
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '2.4'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.6.14
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Enforce presence of specific columns in ActiveRecord queries.
173
+ test_files: []