required_query_attributes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []