rubocop-rails 2.4.1 → 2.6.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/LICENSE.txt +1 -1
- data/README.md +6 -2
- data/config/default.yml +71 -6
- data/lib/rubocop-rails.rb +3 -0
- data/lib/rubocop/cop/mixin/active_record_helper.rb +77 -0
- data/lib/rubocop/cop/mixin/index_method.rb +161 -0
- data/lib/rubocop/cop/rails/content_tag.rb +82 -0
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
- data/lib/rubocop/cop/rails/delegate.rb +1 -3
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
- data/lib/rubocop/cop/rails/environment_comparison.rb +60 -14
- data/lib/rubocop/cop/rails/exit.rb +2 -2
- data/lib/rubocop/cop/rails/file_path.rb +1 -0
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +2 -2
- data/lib/rubocop/cop/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rails/index_by.rb +56 -0
- data/lib/rubocop/cop/rails/index_with.rb +59 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
- data/lib/rubocop/cop/rails/link_to_blank.rb +1 -3
- data/lib/rubocop/cop/rails/pick.rb +51 -0
- data/lib/rubocop/cop/rails/presence.rb +2 -6
- data/lib/rubocop/cop/rails/rake_environment.rb +24 -6
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
- data/lib/rubocop/cop/rails/refute_methods.rb +52 -26
- data/lib/rubocop/cop/rails/reversible_migration.rb +6 -1
- data/lib/rubocop/cop/rails/save_bang.rb +16 -9
- data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -1
- data/lib/rubocop/cop/rails/time_zone.rb +1 -3
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +16 -16
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +155 -0
- data/lib/rubocop/cop/rails/unknown_env.rb +7 -6
- data/lib/rubocop/cop/rails_cops.rb +8 -0
- data/lib/rubocop/rails/inject.rb +1 -1
- data/lib/rubocop/rails/schema_loader.rb +61 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +190 -0
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +32 -8
@@ -85,9 +85,7 @@ module RuboCop
|
|
85
85
|
end
|
86
86
|
|
87
87
|
# prefer `Time` over `DateTime` class
|
88
|
-
if strict?
|
89
|
-
corrector.replace(node.children.first.source_range, 'Time')
|
90
|
-
end
|
88
|
+
corrector.replace(node.children.first.source_range, 'Time') if strict?
|
91
89
|
remove_redundant_in_time_zone(corrector, node)
|
92
90
|
end
|
93
91
|
end
|
@@ -3,50 +3,51 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# Prefer the use of
|
6
|
+
# Prefer the use of distinct, before pluck instead of after.
|
7
7
|
#
|
8
|
-
# The use of
|
8
|
+
# The use of distinct before pluck is preferred because it executes within
|
9
9
|
# the database.
|
10
10
|
#
|
11
11
|
# This cop has two different enforcement modes. When the EnforcedStyle
|
12
12
|
# is conservative (the default) then only calls to pluck on a constant
|
13
|
-
# (i.e. a model class) before
|
13
|
+
# (i.e. a model class) before distinct are added as offenses.
|
14
14
|
#
|
15
15
|
# When the EnforcedStyle is aggressive then all calls to pluck before
|
16
|
-
#
|
17
|
-
# cannot distinguish between calls to pluck on an
|
18
|
-
# vs a call to pluck on an
|
16
|
+
# distinct are added as offenses. This may lead to false positives
|
17
|
+
# as the cop cannot distinguish between calls to pluck on an
|
18
|
+
# ActiveRecord::Relation vs a call to pluck on an
|
19
|
+
# ActiveRecord::Associations::CollectionProxy.
|
19
20
|
#
|
20
21
|
# Autocorrect is disabled by default for this cop since it may generate
|
21
22
|
# false positives.
|
22
23
|
#
|
23
24
|
# @example EnforcedStyle: conservative (default)
|
24
25
|
# # bad
|
25
|
-
# Model.pluck(:id).
|
26
|
+
# Model.pluck(:id).distinct
|
26
27
|
#
|
27
28
|
# # good
|
28
|
-
# Model.
|
29
|
+
# Model.distinct.pluck(:id)
|
29
30
|
#
|
30
31
|
# @example EnforcedStyle: aggressive
|
31
32
|
# # bad
|
32
33
|
# # this will return a Relation that pluck is called on
|
33
|
-
# Model.where(cond: true).pluck(:id).
|
34
|
+
# Model.where(cond: true).pluck(:id).distinct
|
34
35
|
#
|
35
36
|
# # bad
|
36
37
|
# # an association on an instance will return a CollectionProxy
|
37
|
-
# instance.assoc.pluck(:id).
|
38
|
+
# instance.assoc.pluck(:id).distinct
|
38
39
|
#
|
39
40
|
# # bad
|
40
|
-
# Model.pluck(:id).
|
41
|
+
# Model.pluck(:id).distinct
|
41
42
|
#
|
42
43
|
# # good
|
43
|
-
# Model.
|
44
|
+
# Model.distinct.pluck(:id)
|
44
45
|
#
|
45
46
|
class UniqBeforePluck < RuboCop::Cop::Cop
|
46
47
|
include ConfigurableEnforcedStyle
|
47
48
|
include RangeHelp
|
48
49
|
|
49
|
-
MSG = 'Use
|
50
|
+
MSG = 'Use `distinct` before `pluck`.'
|
50
51
|
NEWLINE = "\n"
|
51
52
|
PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
|
52
53
|
'${:uniq :distinct} ...)]'
|
@@ -66,8 +67,7 @@ module RuboCop
|
|
66
67
|
|
67
68
|
return unless method
|
68
69
|
|
69
|
-
add_offense(node, location: :selector
|
70
|
-
message: format(MSG, method: method))
|
70
|
+
add_offense(node, location: :selector)
|
71
71
|
end
|
72
72
|
|
73
73
|
def autocorrect(node)
|
@@ -75,7 +75,7 @@ module RuboCop
|
|
75
75
|
method = node.method_name
|
76
76
|
|
77
77
|
corrector.remove(dot_method_with_whitespace(method, node))
|
78
|
-
corrector.insert_before(node.receiver.loc.dot.begin,
|
78
|
+
corrector.insert_before(node.receiver.loc.dot.begin, '.distinct')
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# When you define a uniqueness validation in Active Record model,
|
7
|
+
# you also should add a unique index for the column. There are two reasons
|
8
|
+
# First, duplicated records may occur even if Active Record's validation
|
9
|
+
# is defined.
|
10
|
+
# Second, it will cause slow queries. The validation executes a `SELECT`
|
11
|
+
# statement with the target column when inserting/updating a record.
|
12
|
+
# If the column does not have an index and the table is large,
|
13
|
+
# the query will be heavy.
|
14
|
+
#
|
15
|
+
# Note that the cop does nothing if db/schema.rb does not exist.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad - if the schema does not have a unique index
|
19
|
+
# validates :account, uniqueness: true
|
20
|
+
#
|
21
|
+
# # good - if the schema has a unique index
|
22
|
+
# validates :account, uniqueness: true
|
23
|
+
#
|
24
|
+
# # good - even if the schema does not have a unique index
|
25
|
+
# validates :account, length: { minimum: MIN_LENGTH }
|
26
|
+
#
|
27
|
+
class UniqueValidationWithoutIndex < Cop
|
28
|
+
include ActiveRecordHelper
|
29
|
+
|
30
|
+
MSG = 'Uniqueness validation should be with a unique index.'
|
31
|
+
|
32
|
+
def on_send(node)
|
33
|
+
return unless node.method?(:validates)
|
34
|
+
return unless uniqueness_part(node)
|
35
|
+
return if condition_part?(node)
|
36
|
+
return unless schema
|
37
|
+
return if with_index?(node)
|
38
|
+
|
39
|
+
add_offense(node)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def with_index?(node)
|
45
|
+
klass = class_node(node)
|
46
|
+
return true unless klass # Skip analysis
|
47
|
+
|
48
|
+
table = schema.table_by(name: table_name(klass))
|
49
|
+
return true unless table # Skip analysis if it can't find the table
|
50
|
+
|
51
|
+
names = column_names(node)
|
52
|
+
return true unless names
|
53
|
+
|
54
|
+
# Compatibility for Rails 4.2.
|
55
|
+
add_indicies = schema.add_indicies_by(table_name: table_name(klass))
|
56
|
+
|
57
|
+
(table.indices + add_indicies).any? do |index|
|
58
|
+
index.unique &&
|
59
|
+
(index.columns.to_set == names ||
|
60
|
+
include_column_names_in_expression_index?(index, names))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def include_column_names_in_expression_index?(index, column_names)
|
65
|
+
return false unless (expression_index = index.expression)
|
66
|
+
|
67
|
+
column_names.all? do |column_name|
|
68
|
+
expression_index.include?(column_name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def column_names(node)
|
73
|
+
arg = node.first_argument
|
74
|
+
return unless arg.str_type? || arg.sym_type?
|
75
|
+
|
76
|
+
ret = [arg.value]
|
77
|
+
names_from_scope = column_names_from_scope(node)
|
78
|
+
ret.concat(names_from_scope) if names_from_scope
|
79
|
+
|
80
|
+
ret.map! do |name|
|
81
|
+
klass = class_node(node)
|
82
|
+
resolve_relation_into_column(
|
83
|
+
name: name.to_s,
|
84
|
+
class_node: klass,
|
85
|
+
table: schema.table_by(name: table_name(klass))
|
86
|
+
)
|
87
|
+
end
|
88
|
+
ret.include?(nil) ? nil : ret.to_set
|
89
|
+
end
|
90
|
+
|
91
|
+
def column_names_from_scope(node)
|
92
|
+
uniq = uniqueness_part(node)
|
93
|
+
return unless uniq.hash_type?
|
94
|
+
|
95
|
+
scope = find_scope(uniq)
|
96
|
+
return unless scope
|
97
|
+
|
98
|
+
case scope.type
|
99
|
+
when :sym, :str
|
100
|
+
[scope.value]
|
101
|
+
when :array
|
102
|
+
array_node_to_array(scope)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_scope(pairs)
|
107
|
+
pairs.each_pair.find do |pair|
|
108
|
+
key = pair.key
|
109
|
+
next unless key.sym_type? && key.value == :scope
|
110
|
+
|
111
|
+
break pair.value
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def class_node(node)
|
116
|
+
node.each_ancestor.find(&:class_type?)
|
117
|
+
end
|
118
|
+
|
119
|
+
def uniqueness_part(node)
|
120
|
+
pairs = node.arguments.last
|
121
|
+
return unless pairs.hash_type?
|
122
|
+
|
123
|
+
pairs.each_pair.find do |pair|
|
124
|
+
next unless pair.key.sym_type? && pair.key.value == :uniqueness
|
125
|
+
|
126
|
+
break pair.value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def condition_part?(node)
|
131
|
+
pairs = node.arguments.last
|
132
|
+
return unless pairs.hash_type?
|
133
|
+
|
134
|
+
pairs.each_pair.any? do |pair|
|
135
|
+
key = pair.key
|
136
|
+
next unless key.sym_type?
|
137
|
+
|
138
|
+
key.value == :if || key.value == :unless
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def array_node_to_array(node)
|
143
|
+
node.values.map do |elm|
|
144
|
+
case elm.type
|
145
|
+
when :str, :sym
|
146
|
+
elm.value
|
147
|
+
else
|
148
|
+
return nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -15,8 +15,6 @@ module RuboCop
|
|
15
15
|
# Rails.env.production?
|
16
16
|
# Rails.env == 'production'
|
17
17
|
class UnknownEnv < Cop
|
18
|
-
include NameSimilarity
|
19
|
-
|
20
18
|
MSG = 'Unknown environment `%<name>s`.'
|
21
19
|
MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
|
22
20
|
'Did you mean `%<similar>s`?'
|
@@ -57,11 +55,14 @@ module RuboCop
|
|
57
55
|
|
58
56
|
def message(name)
|
59
57
|
name = name.to_s.chomp('?')
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
|
59
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: environments)
|
60
|
+
similar_names = spell_checker.correct(name)
|
61
|
+
|
62
|
+
if similar_names.empty?
|
64
63
|
format(MSG, name: name)
|
64
|
+
else
|
65
|
+
format(MSG_SIMILAR, name: name, similar: similar_names.join(', '))
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'mixin/active_record_helper'
|
4
|
+
require_relative 'mixin/index_method'
|
3
5
|
require_relative 'mixin/target_rails_version'
|
4
6
|
|
5
7
|
require_relative 'rails/action_filter'
|
@@ -14,6 +16,7 @@ require_relative 'rails/assert_not'
|
|
14
16
|
require_relative 'rails/belongs_to'
|
15
17
|
require_relative 'rails/blank'
|
16
18
|
require_relative 'rails/bulk_change_table'
|
19
|
+
require_relative 'rails/content_tag'
|
17
20
|
require_relative 'rails/create_table_with_timestamps'
|
18
21
|
require_relative 'rails/date'
|
19
22
|
require_relative 'rails/delegate'
|
@@ -32,18 +35,22 @@ require_relative 'rails/helper_instance_variable'
|
|
32
35
|
require_relative 'rails/http_positional_arguments'
|
33
36
|
require_relative 'rails/http_status'
|
34
37
|
require_relative 'rails/ignored_skip_action_filter_option'
|
38
|
+
require_relative 'rails/index_by'
|
39
|
+
require_relative 'rails/index_with'
|
35
40
|
require_relative 'rails/inverse_of'
|
36
41
|
require_relative 'rails/lexically_scoped_action_filter'
|
37
42
|
require_relative 'rails/link_to_blank'
|
38
43
|
require_relative 'rails/not_null_column'
|
39
44
|
require_relative 'rails/output'
|
40
45
|
require_relative 'rails/output_safety'
|
46
|
+
require_relative 'rails/pick'
|
41
47
|
require_relative 'rails/pluralization_grammar'
|
42
48
|
require_relative 'rails/presence'
|
43
49
|
require_relative 'rails/present'
|
44
50
|
require_relative 'rails/rake_environment'
|
45
51
|
require_relative 'rails/read_write_attribute'
|
46
52
|
require_relative 'rails/redundant_allow_nil'
|
53
|
+
require_relative 'rails/redundant_foreign_key'
|
47
54
|
require_relative 'rails/redundant_receiver_in_with_options'
|
48
55
|
require_relative 'rails/reflection_class_name'
|
49
56
|
require_relative 'rails/refute_methods'
|
@@ -57,5 +64,6 @@ require_relative 'rails/scope_args'
|
|
57
64
|
require_relative 'rails/skips_model_validations'
|
58
65
|
require_relative 'rails/time_zone'
|
59
66
|
require_relative 'rails/uniq_before_pluck'
|
67
|
+
require_relative 'rails/unique_validation_without_index'
|
60
68
|
require_relative 'rails/unknown_env'
|
61
69
|
require_relative 'rails/validation'
|
data/lib/rubocop/rails/inject.rb
CHANGED
@@ -8,7 +8,7 @@ module RuboCop
|
|
8
8
|
def self.defaults!
|
9
9
|
path = CONFIG_DEFAULT.to_s
|
10
10
|
hash = ConfigLoader.send(:load_yaml_configuration, path)
|
11
|
-
config = Config.new(hash, path)
|
11
|
+
config = Config.new(hash, path).tap(&:make_excludes_absolute)
|
12
12
|
puts "configuration from #{path}" if ConfigLoader.debug?
|
13
13
|
config = ConfigLoader.merge_with_default(config, path, unset_nil: false)
|
14
14
|
ConfigLoader.instance_variable_set(:@default_configuration, config)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Rails
|
5
|
+
# It loads db/schema.rb and return Schema object.
|
6
|
+
# Cops refers database schema information with this module.
|
7
|
+
module SchemaLoader
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# It parses `db/schema.rb` and return it.
|
11
|
+
# It returns `nil` if it can't find `db/schema.rb`.
|
12
|
+
# So a cop that uses the loader should handle `nil` properly.
|
13
|
+
#
|
14
|
+
# @return [Schema, nil]
|
15
|
+
def load(target_ruby_version)
|
16
|
+
return @schema if defined?(@schema)
|
17
|
+
|
18
|
+
@schema = load!(target_ruby_version)
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset!
|
22
|
+
return unless instance_variable_defined?(:@schema)
|
23
|
+
|
24
|
+
remove_instance_variable(:@schema)
|
25
|
+
end
|
26
|
+
|
27
|
+
def db_schema_path
|
28
|
+
path = Pathname.pwd
|
29
|
+
until path.root?
|
30
|
+
schema_path = path.join('db/schema.rb')
|
31
|
+
return schema_path if schema_path.exist?
|
32
|
+
|
33
|
+
path = path.join('../').cleanpath
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def load!(target_ruby_version)
|
42
|
+
path = db_schema_path
|
43
|
+
return unless path
|
44
|
+
|
45
|
+
ast = parse(path, target_ruby_version)
|
46
|
+
Schema.new(ast)
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(path, target_ruby_version)
|
50
|
+
klass_name = :"Ruby#{target_ruby_version.to_s.sub('.', '')}"
|
51
|
+
klass = ::Parser.const_get(klass_name)
|
52
|
+
parser = klass.new(RuboCop::AST::Builder.new)
|
53
|
+
|
54
|
+
buffer = Parser::Source::Buffer.new(path, 1)
|
55
|
+
buffer.source = path.read
|
56
|
+
|
57
|
+
parser.parse(buffer)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Rails
|
5
|
+
module SchemaLoader
|
6
|
+
# Represent db/schema.rb
|
7
|
+
class Schema
|
8
|
+
attr_reader :tables, :add_indicies
|
9
|
+
|
10
|
+
def initialize(ast)
|
11
|
+
@tables = []
|
12
|
+
@add_indicies = []
|
13
|
+
|
14
|
+
build!(ast)
|
15
|
+
end
|
16
|
+
|
17
|
+
def table_by(name:)
|
18
|
+
tables.find do |table|
|
19
|
+
table.name == name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_indicies_by(table_name:)
|
24
|
+
add_indicies.select do |add_index|
|
25
|
+
add_index.table_name == table_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def build!(ast)
|
32
|
+
raise "Unexpected type: #{ast.type}" unless ast.block_type?
|
33
|
+
|
34
|
+
each_table(ast) do |table_def|
|
35
|
+
@tables << Table.new(table_def)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Compatibility for Rails 4.2.
|
39
|
+
each_add_index(ast) do |add_index_def|
|
40
|
+
@add_indicies << AddIndex.new(add_index_def)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def each_table(ast)
|
45
|
+
case ast.body.type
|
46
|
+
when :begin
|
47
|
+
ast.body.children.each do |node|
|
48
|
+
next unless node.block_type? && node.method?(:create_table)
|
49
|
+
|
50
|
+
yield(node)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
yield ast.body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_add_index(ast)
|
58
|
+
ast.body.children.each do |node|
|
59
|
+
next if !node&.send_type? || !node.method?(:add_index)
|
60
|
+
|
61
|
+
yield(node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Represent a table
|
67
|
+
class Table
|
68
|
+
attr_reader :name, :columns, :indices
|
69
|
+
|
70
|
+
def initialize(node)
|
71
|
+
@name = node.send_node.first_argument.value
|
72
|
+
@columns = build_columns(node)
|
73
|
+
@indices = build_indices(node)
|
74
|
+
end
|
75
|
+
|
76
|
+
def with_column?(name:)
|
77
|
+
@columns.any? { |c| c.name == name }
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def build_columns(node)
|
83
|
+
each_content(node).map do |child|
|
84
|
+
next unless child&.send_type?
|
85
|
+
next if child.method?(:index)
|
86
|
+
|
87
|
+
Column.new(child)
|
88
|
+
end.compact
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_indices(node)
|
92
|
+
each_content(node).map do |child|
|
93
|
+
next unless child&.send_type?
|
94
|
+
next unless child.method?(:index)
|
95
|
+
|
96
|
+
Index.new(child)
|
97
|
+
end.compact
|
98
|
+
end
|
99
|
+
|
100
|
+
def each_content(node)
|
101
|
+
return enum_for(__method__, node) unless block_given?
|
102
|
+
|
103
|
+
case node.body&.type
|
104
|
+
when :begin
|
105
|
+
node.body.children.each do |child|
|
106
|
+
yield(child)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
yield(node.body)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Represent a column
|
115
|
+
class Column
|
116
|
+
attr_reader :name, :type, :not_null
|
117
|
+
|
118
|
+
def initialize(node)
|
119
|
+
@name = node.first_argument.value
|
120
|
+
@type = node.method_name
|
121
|
+
@not_null = nil
|
122
|
+
|
123
|
+
analyze_keywords!(node)
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def analyze_keywords!(node)
|
129
|
+
pairs = node.arguments.last
|
130
|
+
return unless pairs.hash_type?
|
131
|
+
|
132
|
+
pairs.each_pair do |k, v|
|
133
|
+
if k.value == :null
|
134
|
+
@not_null = v.true_type? ? false : true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Represent an index
|
141
|
+
class Index
|
142
|
+
attr_reader :name, :columns, :expression, :unique
|
143
|
+
|
144
|
+
def initialize(node)
|
145
|
+
@columns, @expression = build_columns_or_expr(node.first_argument)
|
146
|
+
@unique = nil
|
147
|
+
|
148
|
+
analyze_keywords!(node)
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def build_columns_or_expr(columns)
|
154
|
+
if columns.array_type?
|
155
|
+
[columns.values.map(&:value), nil]
|
156
|
+
else
|
157
|
+
[[], columns.value]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def analyze_keywords!(node)
|
162
|
+
pairs = node.arguments.last
|
163
|
+
return unless pairs.hash_type?
|
164
|
+
|
165
|
+
pairs.each_pair do |k, v|
|
166
|
+
case k.value
|
167
|
+
when :name
|
168
|
+
@name = v.value
|
169
|
+
when :unique
|
170
|
+
@unique = true
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Represent an `add_index`
|
177
|
+
class AddIndex < Index
|
178
|
+
attr_reader :table_name
|
179
|
+
|
180
|
+
def initialize(node)
|
181
|
+
@table_name = node.first_argument.value
|
182
|
+
@columns, @expression = build_columns_or_expr(node.arguments[1])
|
183
|
+
@unique = nil
|
184
|
+
|
185
|
+
analyze_keywords!(node)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|