rubocop-migration 0.1.1 → 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 +4 -4
- data/README.md +0 -5
- data/lib/rubocop-migration.rb +6 -1
- data/lib/rubocop/cop/migration/unsafe_migration.rb +69 -0
- data/lib/rubocop/migration/strong_migrations_checker.rb +47 -0
- data/lib/rubocop/migration/version.rb +1 -1
- data/rubocop-migration.gemspec +2 -0
- metadata +32 -3
- data/lib/rubocop/cop/migration/add_index_non_concurrently.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f5d900885e38728ea16270cdb724f90d3b95b9e
|
4
|
+
data.tar.gz: e2a245aa7c577b066b2081cf73607cab961b8ada
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf48675fc5ac8d96584cacbe6b30eae4f0f8a289679fd9ac1cbbd1fcebe9b87cd01fee9f166727085161f40ba1d79029ee69c6801bb465725dfbcca9c7de7159
|
7
|
+
data.tar.gz: b30e87bf96055a935fa57f07c24d9aa6e3a47e205baf9fe4225a5ce36f1547d6fb5e1ef560ee3ae6e0e151a370d54da786e59b2a0209ffda143173da7594ae47
|
data/README.md
CHANGED
@@ -31,8 +31,3 @@ git clone --depth 1 git://github.com/bbatsov/rubocop.git vendor/rubocop
|
|
31
31
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
32
32
|
|
33
33
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
34
|
-
|
35
|
-
## TODO
|
36
|
-
|
37
|
-
https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/
|
38
|
-
http://leopard.in.ua/2016/09/20/safe-and-unsafe-operations-postgresql
|
data/lib/rubocop-migration.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
require "active_support"
|
2
2
|
require "active_support/core_ext"
|
3
|
+
require "active_record"
|
4
|
+
# Only load a portion of strong_migrations
|
5
|
+
require "strong_migrations/migration"
|
6
|
+
require "strong_migrations/unsafe_migration"
|
3
7
|
require "rubocop"
|
4
8
|
|
5
9
|
require "rubocop/migration/version"
|
6
|
-
require "rubocop/
|
10
|
+
require "rubocop/migration/strong_migrations_checker"
|
11
|
+
require "rubocop/cop/migration/unsafe_migration"
|
7
12
|
|
8
13
|
module RuboCop
|
9
14
|
module Migration
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Migration
|
4
|
+
class UnsafeMigration < Cop
|
5
|
+
# List of all public methods that can be used within a migration method,
|
6
|
+
# like `add_index`, `rename_table`, etc.
|
7
|
+
SCHEMA_STATEMENTS = ActiveRecord::ConnectionAdapters::SchemaStatements
|
8
|
+
.public_instance_methods
|
9
|
+
.freeze
|
10
|
+
SCHEMA_STATEMENTS_PATTERN = SCHEMA_STATEMENTS.map { |s| ":#{s}" }.join(" ")
|
11
|
+
|
12
|
+
ERROR_NOTICE = "Ignore this warning by inserting `# rubocop:disable UnsafeMigration`\nabove your code and `# rubocop:enable UnsafeMigration` below code.".freeze
|
13
|
+
|
14
|
+
# Match `ActiveRecord::Migration` and `ActiveRecord::Migration[5.0]`
|
15
|
+
def_node_matcher :migration_class?, <<-PATTERN
|
16
|
+
{
|
17
|
+
(class
|
18
|
+
(const nil _)
|
19
|
+
(const
|
20
|
+
(const nil :ActiveRecord) :Migration) ...)
|
21
|
+
|
22
|
+
(class
|
23
|
+
(const nil _)
|
24
|
+
(send
|
25
|
+
(const
|
26
|
+
(const nil :ActiveRecord) :Migration) :[] _) ...)
|
27
|
+
}
|
28
|
+
PATTERN
|
29
|
+
|
30
|
+
# `down` migrations can be ignored as they should not be run in
|
31
|
+
# production environments per:
|
32
|
+
# https://github.com/ankane/strong_migrations/commit/fcfbcb4b1bef6a1ed9ec4808268ed0e684ec4389
|
33
|
+
def_node_matcher :migration_method_match, <<-PATTERN
|
34
|
+
(def {:change :up} args $...)
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def_node_search :schema_statement_match, <<-PATTERN
|
38
|
+
$(send nil ${#{SCHEMA_STATEMENTS_PATTERN}} $...)
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def investigate(processed_source)
|
42
|
+
ast = processed_source.ast
|
43
|
+
return if !ast || !migration_class?(ast)
|
44
|
+
ast.each_child_node do |child_node|
|
45
|
+
migration_methods = migration_method_match(child_node)
|
46
|
+
migration_methods&.each { |method_node| investigate_migration_method(method_node) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def investigate_migration_method(method_node)
|
53
|
+
schema_statement_match(method_node) do |statement_node, method_name, args_nodes|
|
54
|
+
# TODO: Better/safer way to do this?
|
55
|
+
args_source = "[#{args_nodes.map(&:source).join(', ')}]"
|
56
|
+
args = eval(args_source)
|
57
|
+
|
58
|
+
checker = RuboCop::Migration::StrongMigrationsChecker.new
|
59
|
+
error = checker.check_operation(method_name, *args)
|
60
|
+
if error
|
61
|
+
error += "\n#{ERROR_NOTICE}"
|
62
|
+
add_offense(statement_node, :expression, error)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Migration
|
3
|
+
class StrongMigrationsChecker
|
4
|
+
include StrongMigrations::Migration
|
5
|
+
|
6
|
+
# StrongMigrations raises errors for potentially unsafe code, and relies
|
7
|
+
# on the user to add a `safety_assured { }` block to supress this warning.
|
8
|
+
# These warnings are unsupported by rubocop-migration for now.
|
9
|
+
SAFETY_ASSURED_WARNINGS = [
|
10
|
+
:add_column_json,
|
11
|
+
:add_index_columns,
|
12
|
+
:change_table,
|
13
|
+
:execute,
|
14
|
+
:remove_column,
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
def version_safe?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def postgresql?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def check_operation(method_name, *args)
|
26
|
+
send(method_name, *args)
|
27
|
+
rescue StrongMigrations::UnsafeMigration => e
|
28
|
+
strip_wait_message(e.message)
|
29
|
+
rescue NoMethodError
|
30
|
+
# Do nothing, this method is unrecognized by StrongMigrations unsafe
|
31
|
+
# operations and is likely safe.
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def raise_error(message_key)
|
37
|
+
return if SAFETY_ASSURED_WARNINGS.include?(message_key)
|
38
|
+
super(message_key)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Strip large "WAIT!" ASCII art from the StrongMigrations error message.
|
42
|
+
def strip_wait_message(error_message)
|
43
|
+
error_message.split("\n\n", 2).last
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/rubocop-migration.gemspec
CHANGED
@@ -24,6 +24,8 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.required_ruby_version = ">= 2.3.0"
|
25
25
|
|
26
26
|
spec.add_dependency "activesupport", ">= 4"
|
27
|
+
spec.add_dependency "activerecord", ">= 4"
|
28
|
+
spec.add_dependency "strong_migrations", "~> 0.1"
|
27
29
|
spec.add_dependency "rubocop", ">= 0.48.0"
|
28
30
|
|
29
31
|
spec.add_development_dependency "bundler", "~> 1.14"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-migration
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Graham
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-07-
|
11
|
+
date: 2017-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: strong_migrations
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.1'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rubocop
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -152,7 +180,8 @@ files:
|
|
152
180
|
- bin/setup
|
153
181
|
- circle.yml
|
154
182
|
- lib/rubocop-migration.rb
|
155
|
-
- lib/rubocop/cop/migration/
|
183
|
+
- lib/rubocop/cop/migration/unsafe_migration.rb
|
184
|
+
- lib/rubocop/migration/strong_migrations_checker.rb
|
156
185
|
- lib/rubocop/migration/version.rb
|
157
186
|
- rubocop-migration.gemspec
|
158
187
|
- vendor/.gitkeep
|
@@ -1,66 +0,0 @@
|
|
1
|
-
module RuboCop
|
2
|
-
module Cop
|
3
|
-
module Migration
|
4
|
-
class AddIndexNonConcurrently < Cop
|
5
|
-
MSG = 'Use `algorithm: :concurrently` to avoid locking table.'.freeze
|
6
|
-
|
7
|
-
def_node_matcher :add_index_match, <<-PATTERN
|
8
|
-
(send nil :add_index _ _ (hash $...))
|
9
|
-
PATTERN
|
10
|
-
|
11
|
-
def_node_matcher :add_reference_match, <<-PATTERN
|
12
|
-
(send nil {:add_reference :add_belongs_to} _ _ (hash $...))
|
13
|
-
PATTERN
|
14
|
-
|
15
|
-
def_node_matcher :add_reference_index_options?, <<-PATTERN
|
16
|
-
(pair (sym :index) !({:nil false}))
|
17
|
-
PATTERN
|
18
|
-
|
19
|
-
def on_send(node)
|
20
|
-
if node.method_name == :add_index
|
21
|
-
check_add_index(node)
|
22
|
-
elsif [:add_reference, :add_belongs_to].include?(node.method_name)
|
23
|
-
check_add_reference(node)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def check_add_index(node)
|
30
|
-
pairs = add_index_match(node)
|
31
|
-
if !pairs_contains_algorithm_concurrently?(pairs)
|
32
|
-
add_offense(node, :expression, MSG)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def check_add_reference(node)
|
37
|
-
pairs = add_reference_match(node)
|
38
|
-
index_options = pairs&.find { |pair| add_reference_index_options?(pair) }&.value
|
39
|
-
return unless index_options
|
40
|
-
|
41
|
-
valid = true
|
42
|
-
if index_options.true_type?
|
43
|
-
# `index: true` is always invalid
|
44
|
-
valid = false
|
45
|
-
elsif index_options.hash_type?
|
46
|
-
# check for `algorithm: :concurrently`
|
47
|
-
valid = pairs_contains_algorithm_concurrently?(index_options.pairs)
|
48
|
-
end
|
49
|
-
add_offense(node, :expression, MSG) if !valid
|
50
|
-
end
|
51
|
-
|
52
|
-
def pairs_contains_algorithm_concurrently?(pairs)
|
53
|
-
return false unless pairs.is_a?(Array)
|
54
|
-
pairs.each do |pair|
|
55
|
-
next unless pair.pair_type?
|
56
|
-
key = pair.key
|
57
|
-
value = pair.value
|
58
|
-
next unless (key.sym_type? || key.str_type?) && key.children.first.to_s == "algorithm"
|
59
|
-
return true if value.children.first.to_s == "concurrently"
|
60
|
-
end
|
61
|
-
false
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|