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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 18d15295e2b2d2cea9b6cdb58f2de2e5c2ae2931
4
- data.tar.gz: d616df1b06c2c711d6f82e4eb2548a536eaff1f7
3
+ metadata.gz: 2f5d900885e38728ea16270cdb724f90d3b95b9e
4
+ data.tar.gz: e2a245aa7c577b066b2081cf73607cab961b8ada
5
5
  SHA512:
6
- metadata.gz: cf93f98b7b7bfddaee4689e7dae70d267c8e4b202e480c22221ce2d9c8b4d8e3dc9cc6258581eb589228ef79c60d442a3e085781bd43c855718345e176720335
7
- data.tar.gz: d88df8c695daf167df7b46f621f9cc4ce5b74a754cde2d523c97234203a0c84449a96830b933b6e450e9b369daef47df598f7004f4e7abe600ea0df9edb32d12
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
@@ -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/cop/migration/add_index_non_concurrently"
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
@@ -1,5 +1,5 @@
1
1
  module RuboCop
2
2
  module Migration
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -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.1.1
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-07 00:00:00.000000000 Z
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/add_index_non_concurrently.rb
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