performance_promise 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0fa56d47d2c2db583f926cd79f5094485853dff9
4
+ data.tar.gz: 3508721f4c377fe470c1a615d3c84a8e0eeaaaa9
5
+ SHA512:
6
+ metadata.gz: df5209a3ee5fd6236ed95e65d8b11030376d91ce905c08c081bb4b4bbd4afe405d35b979d69b99279d5f778fb425245d20e1f41c00221f95564b08da89e906f0
7
+ data.tar.gz: 7865ac4b28c63efc00c279420d6a988eb34b2da69204def8fc36b9ce8cfbd77728449efbe7723d23e3fd8f151f526a34c8d673751362dd92dcb984b1743e49c1
data/.gitignore ADDED
@@ -0,0 +1,41 @@
1
+ *.swp
2
+ *.rbc
3
+ capybara-*.html
4
+ .rspec
5
+ /log
6
+ /tmp
7
+ /db/*.sqlite3
8
+ /db/*.sqlite3-journal
9
+ /public/system
10
+ /coverage/
11
+ /spec/tmp
12
+ **.orig
13
+ rerun.txt
14
+ pickle-email-*.html
15
+
16
+ # TODO Comment out these rules if you are OK with secrets being uploaded to the repo
17
+ config/initializers/secret_token.rb
18
+ config/secrets.yml
19
+
20
+ ## Environment normalisation:
21
+ /.bundle
22
+ /vendor/bundle
23
+
24
+ Gemfile.lock
25
+
26
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
27
+ .rvmrc
28
+
29
+ # if using bower-rails ignore default bower_components path bower.json files
30
+ /vendor/assets/bower_components
31
+ *.bowerrc
32
+ bower.json
33
+
34
+ # Ignore pow environment settings
35
+ .powenv
36
+
37
+ # Ignore rbenv
38
+ .ruby-version
39
+
40
+ # Ignore any created gem files
41
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :test do
5
+ gem 'rspec'
6
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Bipin Suresh
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.
22
+
data/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # Performance Promise
2
+ The `performance_promise` gem enables you to annotate and validate the performance of your Rails actions.
3
+
4
+ You can declare the performance characteristics of your Rails actions in code (right next to the action definition itself), and `performance_promise` will monitor and validate the promise. If the action breaks the promise, the `performance_promise` gem will alert you and provide a helpful suggestion with a passing performance annotation.
5
+
6
+ Example syntax:
7
+ ```ruby
8
+ class ArticlesController < ApplicationController
9
+
10
+ Performance :makes => 1.query,
11
+ :full_table_scans => [Article]
12
+ def index
13
+ # ...
14
+ end
15
+
16
+ Performance :makes => 1.query + Article.N.queries,
17
+ :full_table_scans => [Article, Comment]
18
+ def expensive_action
19
+ # ...
20
+ end
21
+ end
22
+ ```
23
+
24
+ You may also choose to enable a default/minimum performance promise for _all_ actions by turning on the `untagged_methods_are_speedy` config parameter ([see here](https://github.com/bipsandbytes/performance_promise#untagged_methods_are_speedy-bool)). **You can reap all the benefits of performance validation without having to make any changes to code except tagging expensive actions**.
25
+
26
+
27
+ ## Installation
28
+ You can install it as a gem:
29
+ ```sh
30
+ gem install performance_promise
31
+ ```
32
+
33
+ or add it to a Gemfile (Bundler):
34
+ ```ruby
35
+ group :development, :test do
36
+ gem 'performance_promise'
37
+ end
38
+ ```
39
+
40
+ ## Building
41
+ You can build the gem yourself:
42
+ ```sh
43
+ gem build performance_promise.gemspec
44
+ ```
45
+
46
+ ## Configuration
47
+ For safety, `performance_promise` is disabled by default. To enable it, create a new file `config/initializers/performance_promise.rb` with the following code:
48
+ ```ruby
49
+ require 'performance_promise/performance.rb'
50
+
51
+ PerformancePromise.configure do |config|
52
+ config.enable = true
53
+ # config.validations = [
54
+ # :makes, # validate the number of DB queries made
55
+ # ]
56
+ # config.untagged_methods_are_speedy = true
57
+ # config.speedy_promise = {
58
+ # :makes => 2,
59
+ # }
60
+ # config.allowed_environments = [
61
+ # 'development',
62
+ # 'test',
63
+ # ]
64
+ # config.logger = Rails.logger
65
+ # config.throw_exception = true
66
+ end
67
+ PerformancePromise.start
68
+ ```
69
+
70
+ ## Usage
71
+ To understand how to use `performance_promise`, let's use a simple [Blog App][rails-getting-started]. A `Blog` has `Article`s, each of which may have one or more `Comment`s.
72
+
73
+ Here is a simple controller:
74
+ ```ruby
75
+ class ArticlesController < ApplicationController
76
+ def index
77
+ @articles = Article.all
78
+ end
79
+ end
80
+ ```
81
+ Assuming your routes and views are setup, you should be able to succesfully visit `/articles`.
82
+
83
+ You can annotate this action with a promise of how many database queries the action will make so:
84
+ ```ruby
85
+ class ArticlesController < ApplicationController
86
+
87
+ Performance :makes => 1.query
88
+ def index
89
+ @articles = Article.all
90
+ end
91
+
92
+ end
93
+ ```
94
+ Visit `/articles` to confirm that the view is rendered successfully again.
95
+
96
+ Now suppose, you make the view more complex, causing it to execute more database queries
97
+ ```ruby
98
+ Performance :makes => 1.query
99
+ def index
100
+ @articles = Article.all
101
+ @total_comments = 0
102
+ @articles.each do |article|
103
+ @total_comments += article.comments.length
104
+ end
105
+ puts @total_comments
106
+ end
107
+ ```
108
+ Since the performance annotation has not been updated, visiting `/articles` now will throw an exception. The exception tells you that the performance of your view does not respect the annotation promise.
109
+
110
+ ![alt tag](http://i.imgur.com/S5unAoJ.png)
111
+
112
+ Let's update the annotation:
113
+ ```ruby
114
+ Performance :makes => 1.query + Article.N.queries
115
+ def index
116
+ @articles = Article.all
117
+ @total_comments = 0
118
+ @articles.each do |article|
119
+ @total_comments += article.comments.length
120
+ end
121
+ puts @total_comments
122
+ end
123
+ ```
124
+ Now that you have annotated the action correctly, visiting `/articles` renders successfully.
125
+
126
+ The experienced code-reviewer however might ask the author to get rid of the `N + 1` query here, and use `.includes` instead:
127
+ ```ruby
128
+ Performance :makes => 2.queries
129
+ def index
130
+ @articles = Article.all.includes(:comments)
131
+ @total_comments = 0
132
+ @articles.each do |article|
133
+ @total_comments += article.comments.length
134
+ end
135
+ puts @total_comments
136
+ end
137
+ ```
138
+ And now, we've successfully caught and averted a bad code commit!
139
+
140
+ ## Advanced configuration
141
+ `performance_promise` opens up more functionality through configuration variables:
142
+
143
+ #### `allowed_environments: array`
144
+ By default, `performance_promise` runs only in `development` and `testing`. This ensures that you can identify issues when developing or running your test-suite. Be very careful about enabling this in `production` – you almost certainly don't want to.
145
+
146
+ #### `throw_exception: bool`
147
+ Tells `performance_promise` whether to throw an exception. Set to `true` by default, but can be overriden if you simply want to ignore failing cases (they will still be written to the log).
148
+
149
+ #### `speedy_promise: hash`
150
+ If you do not care to determine the _exact_ performance of your action, you can still simply mark it as `Speedy`:
151
+ ```ruby
152
+ Speedy()
153
+ def index
154
+ ...
155
+ end
156
+ ```
157
+ A `Speedy` action is supposed to be well behaved, making lesser than `x` database queries, and taking less than `y` to complete. You can set these defaults using this configuration parameter.
158
+
159
+ #### `untagged_methods_are_speedy: bool`
160
+ By default, actions that are not annotated aren't validated by `performance_promise`. If you'd like to force all actions to be validated, one option is to simply default them all to be `Speedy`. This allows developers to make _no_ change to their code, while still reaping the benefits of performance validation. Iff a view fails to be `Speedy`, then the developer is forced to acknowledge it in code.
161
+
162
+
163
+ ## FAQ
164
+ > **What is the strange syntax? Is it a function call? Is it a method?**
165
+
166
+ We borrow the coding style from Python's `decorators`. This style allows for a function to be wrapped by another. This is a great use case for that style since it allows for us to express the annotation right above the function definition.
167
+
168
+ Credit goes to [Yehuda Katz][yehuda-katz] for the [port of decortators][ruby-decorators] into Ruby.
169
+
170
+ > **Will this affect my production service?**
171
+
172
+ By default, `performace_promise` is applied only in `development` and `test` environments. You can choose to override this, but is strongly discouraged.
173
+
174
+
175
+ > **What are some other kinds of performance guarantees that I can make with `performance_promise`?**
176
+
177
+ In addition to promises about the number of database queries, you can also make promises on how long the entire view will take to render, and whether it performs any table scans.
178
+ ```ruby
179
+ Performance :makes => 1.query + Article.N.queries,
180
+ :takes => 1.second,
181
+ :full_tables_scans => [Article]
182
+ def index
183
+ ...
184
+ end
185
+ ```
186
+
187
+ If you come up with other validations that you think will be useful, please consider sharing it with the community by [writing your own plugin here](https://github.com/bipsandbytes/performance_promise/tree/master/lib/performance_promise/validations), and raising a Pull Request.
188
+
189
+ > **Is this the same as [Bullet][bullet] gem?**
190
+
191
+ [Bullet][bullet] is a great piece of software that allows developers to help identify N + 1 queries and unused eager loading. It does this by watching your application in development mode, and alerting you when it does either of those things.
192
+
193
+ `performance_promise` can be tuned to not only identify N + 1 queries, but can also alert whenever there's _any_ change in performance. It allows you to identify expensive actions irrespective of their database query profile.
194
+
195
+ `performance_promise` also has access to the entire database query object. In the future, `performance_promise` can be tuned to perform additonal checks like how long the most expensive query took, whether the action performed any table scans (available through an `EXPLAIN`) etc.
196
+
197
+ Finally, the difference between `bullet` and `performance_promise` is akin to testing by refreshing your browser and testing by writing specs. `performance_promise` encourages you to specify your action's performance by declaring it in code itself. This allows both code-reviewers as well as automated tests to verify your code's performance.
198
+
199
+ [rails-getting-started]: <http://guides.rubyonrails.org/getting_started.html>
200
+ [bullet]: <https://github.com/flyerhzm/bullet>
201
+ [yehuda-katz]: <http://yehudakatz.com/>
202
+ [ruby-decorators]: <http://yehudakatz.com/2009/07/11/python-decorators-in-ruby/>
@@ -0,0 +1,86 @@
1
+ # An implementation of pythonish decorators in Ruby
2
+ # Credits: Yehuda Katz (http://yehudakatz.com/)
3
+ # https://github.com/wycats/ruby_decorators
4
+
5
+
6
+ module MethodDecorators
7
+ def self.extended(klass)
8
+ class << klass
9
+ attr_accessor :decorated_methods
10
+ end
11
+ end
12
+
13
+ def method_missing(name, *args, &blk)
14
+ if Object.const_defined?(name)
15
+ const = Object.const_get(name)
16
+ elsif Decorator.decorators.key?(name)
17
+ const = Decorator.decorators[name]
18
+ else
19
+ return super
20
+ end
21
+
22
+ instance_eval <<-ruby_eval, __FILE__, __LINE__ + 1
23
+ def #{name}(*args, &blk)
24
+ decorate(#{const.name}, *args, &blk)
25
+ end
26
+ ruby_eval
27
+
28
+ send(name, *args, &blk)
29
+ end
30
+
31
+ def method_added(name)
32
+ return unless @decorators
33
+
34
+ decorators = @decorators.dup
35
+ @decorators = nil
36
+ @decorated_methods ||= Hash.new {|h,k| h[k] = []}
37
+
38
+ class << self; attr_accessor :decorated_methods; end
39
+
40
+ decorators.each do |klass, args|
41
+ decorator = klass.respond_to?(:new) ? klass.new(self, instance_method(name), *args) : klass
42
+ @decorated_methods[name] << decorator
43
+ end
44
+
45
+ class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
46
+ def #{name}(*args, &blk)
47
+ ret = nil
48
+ self.class.decorated_methods[#{name.inspect}].each do |decorator|
49
+ ret = decorator.call(self, *args, &blk)
50
+ end
51
+ ret
52
+ end
53
+ ruby_eval
54
+ end
55
+
56
+ def decorate(klass, *args)
57
+ @decorators ||= []
58
+ @decorators << [klass, args]
59
+ end
60
+ end
61
+
62
+ class Decorator
63
+ class << self
64
+ attr_accessor :decorators
65
+ def decorator_name(name)
66
+ Decorator.decorators ||= {}
67
+ Decorator.decorators[name] = self
68
+ end
69
+ end
70
+
71
+ def self.inherited(klass)
72
+ name = klass.name.gsub(/^./) {|m| m.downcase}
73
+
74
+ return if name =~ /^[^A-Za-z_]/ || name =~ /[^0-9A-Za-z_]/
75
+
76
+ MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1
77
+ def #{klass}(*args, &blk)
78
+ decorate(#{klass}, *args, &blk)
79
+ end
80
+ ruby_eval
81
+ end
82
+
83
+ def initialize(klass, method)
84
+ @method = method
85
+ end
86
+ end
@@ -0,0 +1,78 @@
1
+ require 'performance_promise.rb'
2
+
3
+
4
+ class LazilyEvaluated
5
+ def initialize(c)
6
+ if c.is_a?(Fixnum)
7
+ @operand1 = c
8
+ @operator = 'constant'
9
+ elsif c.is_a?(Class)
10
+ @operand1 = c
11
+ @operator = 'model'
12
+ elsif c.is_a?(Array)
13
+ @operand1, @operand2, @operator = c
14
+ else
15
+ raise
16
+ end
17
+ end
18
+
19
+ def +(other)
20
+ return LazilyEvaluated.new([self, other, '+'])
21
+ end
22
+
23
+ def -(other)
24
+ return LazilyEvaluated.new([self, other, '-'])
25
+ end
26
+
27
+ def *(other)
28
+ return LazilyEvaluated.new([self, other, '*'])
29
+ end
30
+
31
+ def /(other)
32
+ return LazilyEvaluated.new([self, other, '/'])
33
+ end
34
+
35
+ def evaluate
36
+ # don't do anything in prod-like environments
37
+ return 0 unless PerformancePromise.configuration.allowed_environments.include?(Rails.env)
38
+
39
+ case @operator
40
+ when 'constant'
41
+ return @operand1
42
+ when 'model'
43
+ return @operand1.count
44
+ when '+'
45
+ return @operand1.evaluate + @operand2.evaluate
46
+ when '-'
47
+ return @operand1.evaluate - @operand2.evaluate
48
+ when '*'
49
+ return @operand1.evaluate * @operand2.evaluate
50
+ when '/'
51
+ return @operand1.evaluate / @operand2.evaluate
52
+ else
53
+ raise
54
+ end
55
+ end
56
+
57
+ def queries
58
+ # syntactic sugar
59
+ self
60
+ end
61
+ end
62
+
63
+
64
+ class Fixnum
65
+ def queries
66
+ LazilyEvaluated.new(self)
67
+ end
68
+ alias query queries
69
+ end
70
+
71
+
72
+ module ActiveRecord
73
+ class Base
74
+ def self.N
75
+ LazilyEvaluated.new(self)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,13 @@
1
+ require 'performance_promise.rb'
2
+
3
+
4
+ class Performance < Decorator
5
+ def initialize(klass, method, options)
6
+ @klass, @method = klass, method
7
+ PerformancePromise.promises["#{klass}\##{method.name.to_s}"] = options
8
+ end
9
+
10
+ def call(this, *args)
11
+ @method.bind(this).call(*args)
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require 'performance_promise/validations/number_of_db_queries.rb'
2
+ require 'performance_promise/validations/time_taken_for_render.rb'
3
+ require 'performance_promise/validations/full_table_scans.rb'
4
+
5
+
6
+ module PerformanceValidations
7
+ extend ValidateNumberOfQueries
8
+ extend ValidateTimeTakenForRender
9
+ extend ValidateFullTableScans
10
+
11
+ def self.report_promise_passed(method, db_queries, options)
12
+ PerformancePromise.configuration.logger.warn '-' * 80
13
+ PerformancePromise.configuration.logger.warn Utils.colored(:green, "Passed promise on #{method}")
14
+ PerformancePromise.configuration.logger.warn '-' * 80
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require 'performance_promise.rb'
2
+
3
+
4
+ class Speedy < Performance
5
+ def initialize(klass, method)
6
+ super(klass, method, PerformancePromise.configuration.speedy_promise)
7
+ end
8
+ end
@@ -0,0 +1,41 @@
1
+ require 'singleton'
2
+
3
+
4
+ class SQLRecorder
5
+ include Singleton
6
+
7
+ @db_queries = []
8
+ def flush
9
+ captured_queries = @db_queries
10
+ @db_queries = []
11
+ return captured_queries
12
+ end
13
+
14
+ def record(payload, duration)
15
+ return if invalid_payload?(payload)
16
+ sql = payload[:sql]
17
+ cleaned_trace = clean_trace(caller)
18
+ explained = ActiveRecord::Base.connection.execute("EXPLAIN QUERY PLAN #{sql}", 'SQLR-EXPLAIN')
19
+ @db_queries << {
20
+ :sql => sql,
21
+ :duration => duration,
22
+ :trace => cleaned_trace,
23
+ :explained => explained,
24
+ }
25
+ end
26
+
27
+ def invalid_payload?(payload)
28
+ ignore_query_names = [
29
+ 'SCHEMA',
30
+ 'SQLR-EXPLAIN',
31
+ ]
32
+ payload[:name] && ignore_query_names.any? { |name| payload[:name].in?(name) }
33
+ end
34
+
35
+ def clean_trace(trace)
36
+ Rails.backtrace_cleaner.remove_silencers!
37
+ Rails.backtrace_cleaner.add_silencer { |line| not line =~ /^(app)\// }
38
+ Rails.backtrace_cleaner.clean(trace)
39
+ end
40
+ end
41
+ SQLRecorder.instance.flush
@@ -0,0 +1,42 @@
1
+ module Utils
2
+ def self.summarize_queries(db_queries)
3
+ summary = Hash.new(0)
4
+ db_queries.each do |query|
5
+ summary[query.except(:duration)] += 1
6
+ end
7
+ summary
8
+ end
9
+
10
+ def self.guess_order(db_queries)
11
+ order = []
12
+ queries_with_count = summarize_queries(db_queries)
13
+ queries_with_count.each do |query, count|
14
+ if count == 1
15
+ order << "1.query"
16
+ else
17
+ if (lookup_field = /WHERE .*"(.*?_id)" = \?/.match(query[:sql]))
18
+ klass = lookup_field[1].humanize
19
+ order << "#{klass}.N.queries"
20
+ else
21
+ order << "n(???)"
22
+ end
23
+ end
24
+ end
25
+
26
+ order.join(" + ")
27
+ end
28
+
29
+ def self.colored(color, string)
30
+ color =
31
+ case color
32
+ when :red
33
+ "\e[31m"
34
+ when :green
35
+ "\e[32m"
36
+ when :cyan
37
+ "\e[36m"
38
+ end
39
+ end_color = "\e[0m"
40
+ "#{color}#{string}#{end_color}"
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ # Creating your own validations
2
+
3
+ You can easily extend `performance_promise` by writing your own validations. You do it in 3 steps:
4
+
5
+ 1. Write your validation
6
+ 2. Register your validation
7
+ 3. Enable your validation in configuration
8
+
9
+ ## Write your validation
10
+ Create a new file in this directory, with a single function:
11
+
12
+ ```ruby
13
+ module MODULE_NAME
14
+ def validate_NAME(db_queries, render_time, promised)
15
+ ...
16
+ end
17
+ end
18
+ ```
19
+
20
+ * `MODULE_NAME`: A name for the module.
21
+ * `NAME`: The symbol that will be used in the `Performance` promise. For example, if the `NAME` is `makes`, then the function function name will be `validates_makes`, and the `Promise` will take an option `:makes`.
22
+ * `validates_NAME`: A function that is called if the a function makes a promise with that `NAME`.
23
+
24
+ The function takes 3 parameters:
25
+ * `db_queries`: An `array` of database queries which can be inspected.
26
+ * `render_time`: Time it took to render the view.
27
+ * `promised`: The performance guarantee made by the author.
28
+
29
+ And returns 3 parameters:
30
+ * `passes`: Whether the promised made by the author in `promised` is upheld.
31
+ * `error_message`: An error message to show to the user explaining why the promise failed, and a best guess of how to fix it, if possible.
32
+ * `backtrace`: If possible, an `array` showing the codepath that caused the promise to be broken.
33
+
34
+ See [time_taken_for_render](https://github.com/bipsandbytes/performance_promise/blob/master/lib/performance_promise/validations/time_taken_for_render.rb) for a simple example.
35
+
36
+ ## Register your validation
37
+ You are now ready to register this plugin. Simply add this plugin to the list of validations in [performace_validations](https://github.com/bipsandbytes/performance_promise/blob/master/lib/performance_promise/performance_validations.rb):
38
+ ```ruby
39
+ module PerformanceValidations
40
+ ...
41
+ extend MODULE_NAME
42
+ ...
43
+ end
44
+ ```
45
+
46
+ ## Enable your validation in configuration
47
+ You are now ready to include this validation in your configuration file:
48
+ ```ruby
49
+ PerformancePromise.configure do |config|
50
+ config.enable = true
51
+ config.validations = [
52
+ ...
53
+ :NAME,
54
+ ]
55
+ end
56
+ ```
@@ -0,0 +1,33 @@
1
+ module ValidateFullTableScans
2
+ def validate_full_table_scans(db_queries, render_time, promised)
3
+ full_table_scans = []
4
+
5
+ # check the explained queries to see if there were any
6
+ # SCAN TABLEs
7
+ db_queries.each do |db_query|
8
+ detail = db_query[:explained][0]['detail']
9
+ makes_full_table_scan = detail.match(/SCAN TABLE (.*)/)
10
+ if makes_full_table_scan
11
+ table_name = makes_full_table_scan[1]
12
+ full_table_scans << table_name
13
+ end
14
+ end
15
+
16
+ # we do not care about duplicates
17
+ full_table_scans = full_table_scans.uniq
18
+
19
+ # map the models in the promise to their corresponding table names
20
+ promised_full_table_scans = promised.map { |model| model.table_name }
21
+
22
+ # check that the performed FTSs are a subset of the promised FTSs
23
+ passes = (full_table_scans & promised_full_table_scans == full_table_scans)
24
+ error_message = ''
25
+ backtrace = []
26
+
27
+ unless passes
28
+ error_message = "Promised table scans on #{promised_full_table_scans}, made: #{full_table_scans}"
29
+ end
30
+
31
+ return passes, error_message, backtrace
32
+ end
33
+ end