rails_best_practices 1.19.3 → 1.21.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 +5 -5
- data/.gitignore +0 -1
- data/.travis.yml +2 -3
- data/CHANGELOG.md +10 -11
- data/Gemfile +3 -5
- data/Gemfile.lock +125 -0
- data/Guardfile +2 -0
- data/README.md +5 -1
- data/Rakefile +2 -17
- data/assets/result.html.erb +2 -0
- data/lib/rails_best_practices.rb +4 -2
- data/lib/rails_best_practices/analyzer.rb +63 -51
- data/lib/rails_best_practices/cli.rb +22 -0
- data/lib/rails_best_practices/command.rb +1 -131
- data/lib/rails_best_practices/core/check.rb +63 -55
- data/lib/rails_best_practices/core/checks_loader.rb +24 -23
- data/lib/rails_best_practices/core/configs.rb +1 -2
- data/lib/rails_best_practices/core/controllers.rb +1 -2
- data/lib/rails_best_practices/core/error.rb +1 -1
- data/lib/rails_best_practices/core/helpers.rb +1 -2
- data/lib/rails_best_practices/core/mailers.rb +1 -2
- data/lib/rails_best_practices/core/methods.rb +27 -21
- data/lib/rails_best_practices/core/model_associations.rb +10 -5
- data/lib/rails_best_practices/core/models.rb +1 -2
- data/lib/rails_best_practices/core/modules.rb +1 -1
- data/lib/rails_best_practices/core/routes.rb +2 -2
- data/lib/rails_best_practices/core/runner.rb +87 -72
- data/lib/rails_best_practices/inline_disables.rb +3 -0
- data/lib/rails_best_practices/inline_disables/comment_ripper.rb +19 -0
- data/lib/rails_best_practices/inline_disables/inline_disable.rb +50 -0
- data/lib/rails_best_practices/lexicals/long_line_check.rb +7 -3
- data/lib/rails_best_practices/option_parser.rb +156 -0
- data/lib/rails_best_practices/prepares.rb +1 -1
- data/lib/rails_best_practices/prepares/controller_prepare.rb +23 -17
- data/lib/rails_best_practices/prepares/gemfile_prepare.rb +2 -2
- data/lib/rails_best_practices/prepares/helper_prepare.rb +6 -1
- data/lib/rails_best_practices/prepares/initializer_prepare.rb +3 -3
- data/lib/rails_best_practices/prepares/mailer_prepare.rb +2 -1
- data/lib/rails_best_practices/prepares/model_prepare.rb +63 -23
- data/lib/rails_best_practices/prepares/route_prepare.rb +28 -21
- data/lib/rails_best_practices/prepares/schema_prepare.rb +1 -1
- data/lib/rails_best_practices/reviews/add_model_virtual_attribute_review.rb +38 -34
- data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +94 -88
- data/lib/rails_best_practices/reviews/check_destroy_return_value_review.rb +15 -5
- data/lib/rails_best_practices/reviews/check_save_return_value_review.rb +20 -8
- data/lib/rails_best_practices/reviews/default_scope_is_evil_review.rb +1 -1
- data/lib/rails_best_practices/reviews/dry_bundler_in_capistrano_review.rb +1 -1
- data/lib/rails_best_practices/reviews/hash_syntax_review.rb +16 -16
- data/lib/rails_best_practices/reviews/isolate_seed_data_review.rb +12 -12
- data/lib/rails_best_practices/reviews/keep_finders_on_their_own_model_review.rb +10 -11
- data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +25 -24
- data/lib/rails_best_practices/reviews/move_code_into_controller_review.rb +4 -4
- data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +9 -10
- data/lib/rails_best_practices/reviews/move_finder_to_named_scope_review.rb +10 -11
- data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +24 -22
- data/lib/rails_best_practices/reviews/not_rescue_exception_review.rb +1 -1
- data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +1 -2
- data/lib/rails_best_practices/reviews/not_use_time_ago_in_words_review.rb +1 -1
- data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +8 -8
- data/lib/rails_best_practices/reviews/protect_mass_assignment_review.rb +35 -32
- data/lib/rails_best_practices/reviews/remove_empty_helpers_review.rb +4 -4
- data/lib/rails_best_practices/reviews/remove_unused_methods_in_controllers_review.rb +20 -17
- data/lib/rails_best_practices/reviews/remove_unused_methods_in_helpers_review.rb +12 -10
- data/lib/rails_best_practices/reviews/remove_unused_methods_in_models_review.rb +38 -18
- data/lib/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review.rb +11 -11
- data/lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb +77 -74
- data/lib/rails_best_practices/reviews/review.rb +2 -1
- data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +2 -3
- data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +12 -12
- data/lib/rails_best_practices/reviews/use_before_filter_review.rb +14 -10
- data/lib/rails_best_practices/reviews/use_model_association_review.rb +15 -15
- data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +24 -22
- data/lib/rails_best_practices/reviews/use_observer_review.rb +28 -28
- data/lib/rails_best_practices/reviews/use_parentheses_in_method_def_review.rb +6 -6
- data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +63 -60
- data/lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb +9 -8
- data/lib/rails_best_practices/reviews/use_scope_access_review.rb +16 -14
- data/lib/rails_best_practices/reviews/use_turbo_sprockets_rails3_review.rb +2 -1
- data/lib/rails_best_practices/version.rb +1 -1
- data/rails_best_practices.gemspec +48 -49
- data/spec/fixtures/lib/rails_best_practices/plugins/reviews/not_use_rails_root_review.rb +1 -2
- data/spec/rails_best_practices/analyzer_spec.rb +73 -42
- data/spec/rails_best_practices/core/check_spec.rb +5 -5
- data/spec/rails_best_practices/core/checks_loader_spec.rb +3 -3
- data/spec/rails_best_practices/core/configs_spec.rb +1 -1
- data/spec/rails_best_practices/core/controllers_spec.rb +1 -1
- data/spec/rails_best_practices/core/error_spec.rb +21 -18
- data/spec/rails_best_practices/core/except_methods_spec.rb +7 -7
- data/spec/rails_best_practices/core/gems_spec.rb +4 -4
- data/spec/rails_best_practices/core/helpers_spec.rb +1 -1
- data/spec/rails_best_practices/core/klasses_spec.rb +3 -3
- data/spec/rails_best_practices/core/mailers_spec.rb +1 -1
- data/spec/rails_best_practices/core/methods_spec.rb +6 -6
- data/spec/rails_best_practices/core/model_associations_spec.rb +10 -6
- data/spec/rails_best_practices/core/model_attributes_spec.rb +4 -4
- data/spec/rails_best_practices/core/models_spec.rb +1 -1
- data/spec/rails_best_practices/core/modules_spec.rb +5 -5
- data/spec/rails_best_practices/core/routes_spec.rb +5 -5
- data/spec/rails_best_practices/core/runner_spec.rb +9 -7
- data/spec/rails_best_practices/core_ext/erubis_spec.rb +10 -10
- data/spec/rails_best_practices/inline_disables/inline_disable_spec.rb +62 -0
- data/spec/rails_best_practices/lexicals/long_line_check_spec.rb +32 -31
- data/spec/rails_best_practices/lexicals/remove_tab_check_spec.rb +6 -6
- data/spec/rails_best_practices/lexicals/remove_trailing_whitespace_check_spec.rb +6 -6
- data/spec/rails_best_practices/prepares/config_prepare_spec.rb +2 -2
- data/spec/rails_best_practices/prepares/controller_prepare_spec.rb +18 -10
- data/spec/rails_best_practices/prepares/gemfile_prepare_spec.rb +17 -17
- data/spec/rails_best_practices/prepares/helper_prepare_spec.rb +3 -3
- data/spec/rails_best_practices/prepares/initializer_prepare_spec.rb +3 -3
- data/spec/rails_best_practices/prepares/mailer_prepare_spec.rb +2 -2
- data/spec/rails_best_practices/prepares/model_prepare_spec.rb +79 -43
- data/spec/rails_best_practices/prepares/route_prepare_spec.rb +141 -76
- data/spec/rails_best_practices/prepares/schema_prepare_spec.rb +2 -2
- data/spec/rails_best_practices/reviews/add_model_virtual_attribute_review_spec.rb +18 -12
- data/spec/rails_best_practices/reviews/always_add_db_index_review_spec.rb +28 -22
- data/spec/rails_best_practices/reviews/check_destroy_return_value_review_spec.rb +15 -13
- data/spec/rails_best_practices/reviews/check_save_return_value_review_spec.rb +31 -21
- data/spec/rails_best_practices/reviews/default_scope_is_evil_review_spec.rb +6 -6
- data/spec/rails_best_practices/reviews/dry_bundler_in_capistrano_review_spec.rb +5 -5
- data/spec/rails_best_practices/reviews/hash_syntax_review_spec.rb +13 -13
- data/spec/rails_best_practices/reviews/isolate_seed_data_review_spec.rb +7 -7
- data/spec/rails_best_practices/reviews/keep_finders_on_their_own_model_review_spec.rb +9 -9
- data/spec/rails_best_practices/reviews/law_of_demeter_review_spec.rb +29 -22
- data/spec/rails_best_practices/reviews/move_code_into_controller_review_spec.rb +6 -6
- data/spec/rails_best_practices/reviews/move_code_into_helper_review_spec.rb +11 -6
- data/spec/rails_best_practices/reviews/move_code_into_model_review_spec.rb +32 -22
- data/spec/rails_best_practices/reviews/move_finder_to_named_scope_review_spec.rb +7 -7
- data/spec/rails_best_practices/reviews/move_model_logic_into_model_review_spec.rb +9 -7
- data/spec/rails_best_practices/reviews/needless_deep_nesting_review_spec.rb +9 -9
- data/spec/rails_best_practices/reviews/not_rescue_exception_review_spec.rb +9 -9
- data/spec/rails_best_practices/reviews/not_use_default_route_review_spec.rb +5 -5
- data/spec/rails_best_practices/reviews/not_use_time_ago_in_words_review_spec.rb +7 -7
- data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +7 -7
- data/spec/rails_best_practices/reviews/protect_mass_assignment_review_spec.rb +24 -17
- data/spec/rails_best_practices/reviews/remove_empty_helpers_review_spec.rb +6 -6
- data/spec/rails_best_practices/reviews/remove_unused_methods_in_controllers_review_spec.rb +47 -32
- data/spec/rails_best_practices/reviews/remove_unused_methods_in_helpers_review_spec.rb +21 -14
- data/spec/rails_best_practices/reviews/remove_unused_methods_in_models_review_spec.rb +57 -53
- data/spec/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review_spec.rb +10 -8
- data/spec/rails_best_practices/reviews/replace_instance_variable_with_local_variable_review_spec.rb +20 -14
- data/spec/rails_best_practices/reviews/restrict_auto_generated_routes_review_spec.rb +54 -31
- data/spec/rails_best_practices/reviews/simplify_render_in_controllers_review_spec.rb +9 -9
- data/spec/rails_best_practices/reviews/simplify_render_in_views_review_spec.rb +13 -13
- data/spec/rails_best_practices/reviews/use_before_filter_review_spec.rb +11 -9
- data/spec/rails_best_practices/reviews/use_model_association_review_spec.rb +7 -7
- data/spec/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review_spec.rb +35 -31
- data/spec/rails_best_practices/reviews/use_observer_review_spec.rb +6 -6
- data/spec/rails_best_practices/reviews/use_parentheses_in_method_def_review_spec.rb +10 -8
- data/spec/rails_best_practices/reviews/use_query_attribute_review_spec.rb +31 -24
- data/spec/rails_best_practices/reviews/use_say_with_time_in_migrations_review_spec.rb +15 -11
- data/spec/rails_best_practices/reviews/use_scope_access_review_spec.rb +14 -14
- data/spec/rails_best_practices/reviews/use_turbo_sprockets_rails3_review_spec.rb +61 -59
- metadata +21 -14
@@ -27,21 +27,20 @@ module RailsBestPractices
|
|
27
27
|
first_argument = node.arguments.all.first
|
28
28
|
second_argument = node.arguments.all[1]
|
29
29
|
if @controller_names.last
|
30
|
-
if
|
30
|
+
if first_argument.sexp_type == :bare_assoc_hash
|
31
31
|
action_names = [first_argument.hash_values.first.to_s]
|
32
|
-
elsif
|
32
|
+
elsif first_argument.sexp_type == :array
|
33
33
|
action_names = first_argument.array_values.map(&:to_s)
|
34
|
-
elsif
|
35
|
-
if
|
34
|
+
elsif second_argument.try(:sexp_type) == :bare_assoc_hash && second_argument.hash_value('to').present?
|
35
|
+
if second_argument.hash_value('to').sexp_type == :string_literal
|
36
36
|
controller_name, action_name = second_argument.hash_value('to').to_s.split('#')
|
37
37
|
action_names = [action_name]
|
38
38
|
else
|
39
39
|
action_names = [second_argument.hash_value('to').to_s]
|
40
40
|
end
|
41
|
-
elsif
|
42
|
-
|
43
|
-
action_names = node.arguments.all.select
|
44
|
-
{ |arg| :symbol_literal == arg.sexp_type }.map(&:to_s)
|
41
|
+
elsif first_argument.sexp_type == :symbol_literal && second_argument.try(:sexp_type) &&
|
42
|
+
second_argument.sexp_type == :symbol_literal
|
43
|
+
action_names = node.arguments.all.select { |arg| arg.sexp_type == :symbol_literal }.map(&:to_s)
|
45
44
|
else
|
46
45
|
action_names = [first_argument.to_s]
|
47
46
|
end
|
@@ -49,18 +48,18 @@ module RailsBestPractices
|
|
49
48
|
@routes.add_route(current_namespaces, current_controller_name, action_name)
|
50
49
|
end
|
51
50
|
else
|
52
|
-
if
|
51
|
+
if first_argument.sexp_type == :bare_assoc_hash
|
53
52
|
route_node = first_argument.hash_values.first
|
54
53
|
# do not parse redirect block
|
55
|
-
if
|
54
|
+
if route_node.sexp_type != :method_add_arg
|
56
55
|
controller_name, action_name = route_node.to_s.split('#')
|
57
56
|
@routes.add_route(current_namespaces, controller_name.underscore, action_name)
|
58
57
|
end
|
59
|
-
elsif
|
58
|
+
elsif first_argument.sexp_type == :array
|
60
59
|
first_argument.array_values.map(&:to_s).each do |action_node|
|
61
60
|
@routes.add_route(current_namespaces, controller_name, action_node.to_s)
|
62
61
|
end
|
63
|
-
elsif
|
62
|
+
elsif second_argument.try(:sexp_type) == :bare_assoc_hash
|
64
63
|
if second_argument.hash_value('to').present?
|
65
64
|
controller_name, action_name = second_argument.hash_value('to').to_s.split('#')
|
66
65
|
else
|
@@ -78,12 +77,16 @@ module RailsBestPractices
|
|
78
77
|
case options.sexp_type
|
79
78
|
when :bare_assoc_hash
|
80
79
|
if options.hash_value('controller').present?
|
81
|
-
return if
|
80
|
+
return if options.hash_value('controller').sexp_type == :regexp_literal
|
81
|
+
|
82
82
|
controller_name = options.hash_value('controller').to_s
|
83
83
|
action_name = options.hash_value('action').present? ? options.hash_value('action').to_s : '*'
|
84
84
|
@routes.add_route(current_namespaces, controller_name, action_name)
|
85
85
|
else
|
86
|
-
route_node =
|
86
|
+
route_node =
|
87
|
+
options.hash_values.find do |value_node|
|
88
|
+
value_node.sexp_type == :string_literal && value_node.to_s.include?('#')
|
89
|
+
end
|
87
90
|
if route_node.present?
|
88
91
|
controller_name, action_name = route_node.to_s.split('#')
|
89
92
|
@routes.add_route(current_namespaces, controller_name.underscore, action_name)
|
@@ -100,7 +103,10 @@ module RailsBestPractices
|
|
100
103
|
options = node.arguments.all.last
|
101
104
|
case options.sexp_type
|
102
105
|
when :bare_assoc_hash
|
103
|
-
route_node =
|
106
|
+
route_node =
|
107
|
+
options.hash_values.find do |value_node|
|
108
|
+
value_node.sexp_type == :string_literal && value_node.to_s.include?('#')
|
109
|
+
end
|
104
110
|
if route_node.present?
|
105
111
|
controller_name, action_name = route_node.to_s.split('#')
|
106
112
|
@routes.add_route(current_namespaces, controller_name.underscore, action_name)
|
@@ -144,14 +150,15 @@ module RailsBestPractices
|
|
144
150
|
if node.arguments.all.last.hash_value('module').present?
|
145
151
|
@namespaces << node.arguments.all.last.hash_value('module').to_s
|
146
152
|
end
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
153
|
+
@controller_name =
|
154
|
+
if node.arguments.all.last.hash_value('controller').present?
|
155
|
+
[:scope, node.arguments.all.last.hash_value('controller').to_s]
|
156
|
+
else
|
157
|
+
@controller_name.try(:first) == :scope ? @controller_name : nil
|
158
|
+
end
|
152
159
|
when 'with_options'
|
153
160
|
argument = node.arguments.all.last
|
154
|
-
if
|
161
|
+
if argument.sexp_type == :bare_assoc_hash && argument.hash_value('controller').present?
|
155
162
|
@controller_name = [:with_option, argument.hash_value('controller').to_s]
|
156
163
|
end
|
157
164
|
else
|
@@ -46,46 +46,50 @@ module RailsBestPractices
|
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
aref_node = right_value.grep_node(sexp_type: :aref)
|
56
|
-
if aref_node
|
57
|
-
assignments(left_value.receiver.to_s) << { message: left_value.message.to_s, arguments: aref_node.to_s }
|
58
|
-
end
|
59
|
-
end
|
49
|
+
# check an attribute assignment node, if there is a array reference node in the right value of assignment node,
|
50
|
+
# then remember this attribute assignment.
|
51
|
+
def assign(node)
|
52
|
+
left_value = node.left_value
|
53
|
+
right_value = node.right_value
|
54
|
+
return unless left_value.sexp_type == :field && right_value.sexp_type == :call
|
60
55
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# then this node needs to add a virtual attribute.
|
65
|
-
def call_assignment(node)
|
66
|
-
if ['save', 'save!'].include? node.message.to_s
|
67
|
-
receiver = node.receiver.to_s
|
68
|
-
add_error "add model virtual attribute (for #{receiver})" if params_dup?(assignments(receiver).collect { |h| h[:arguments] })
|
69
|
-
end
|
56
|
+
aref_node = right_value.grep_node(sexp_type: :aref)
|
57
|
+
if aref_node
|
58
|
+
assignments(left_value.receiver.to_s) << { message: left_value.message.to_s, arguments: aref_node.to_s }
|
70
59
|
end
|
60
|
+
end
|
71
61
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
62
|
+
# check a call node with message "save" or "save!",
|
63
|
+
# if there exists an attribute assignment for the receiver of this call node,
|
64
|
+
# and if the arguments of this attribute assignments has duplicated entries (different message and same arguments),
|
65
|
+
# then this node needs to add a virtual attribute.
|
66
|
+
def call_assignment(node)
|
67
|
+
if ['save', 'save!'].include? node.message.to_s
|
68
|
+
receiver = node.receiver.to_s
|
69
|
+
add_error "add model virtual attribute (for #{receiver})" if params_dup?(
|
70
|
+
assignments(receiver).collect { |h| h[:arguments] }
|
71
|
+
)
|
76
72
|
end
|
73
|
+
end
|
77
74
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
75
|
+
# if the nodes are duplicated.
|
76
|
+
def params_dup?(nodes)
|
77
|
+
return false if nodes.nil?
|
82
78
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
79
|
+
!dups(nodes).empty?
|
80
|
+
end
|
81
|
+
|
82
|
+
# get the assignments of receiver.
|
83
|
+
def assignments(receiver)
|
84
|
+
@assignments[receiver] ||= []
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get the duplicate entries from an Enumerable.
|
88
|
+
#
|
89
|
+
# @return [Enumerable] the duplicate entries.
|
90
|
+
def dups(nodes)
|
91
|
+
nodes.each_with_object({}) { |v, h| h[v] = h[v].to_i + 1 }.reject { |_k, v| v == 1 }.keys
|
92
|
+
end
|
89
93
|
end
|
90
94
|
end
|
91
95
|
end
|
@@ -11,7 +11,7 @@ module RailsBestPractices
|
|
11
11
|
# Review process:
|
12
12
|
# only check the command and command_calls nodes and at the end of review process,
|
13
13
|
# if the receiver of command node is "create_table", then remember the table names
|
14
|
-
# if the receiver of command_call node is "integer" or "string" and suffix with _id, then remember it as foreign key
|
14
|
+
# if the receiver of command_call node is "integer" or "string" or "bigint" and suffix with _id, then remember it as foreign key
|
15
15
|
# if the receiver of command_call node is "string", the name of it is _type suffixed and there is an integer or string column _id suffixed, then remember it as polymorphic foreign key
|
16
16
|
# if the receiver of command_call node is remembered as foreign key and it have argument non-false "index", then remember the index columns
|
17
17
|
# if the receiver of command node is "add_index", then remember the index columns
|
@@ -40,9 +40,9 @@ module RailsBestPractices
|
|
40
40
|
# if the message of command_call node is "create_table", then remember the table name.
|
41
41
|
# if the message of command_call node is "add_index", then remember it as index columns.
|
42
42
|
add_callback :start_command_call do |node|
|
43
|
-
if %w[integer string].include? node.message.to_s
|
43
|
+
if %w[integer string bigint].include? node.message.to_s
|
44
44
|
remember_foreign_key_columns(node)
|
45
|
-
elsif
|
45
|
+
elsif node.message.to_s == 'index'
|
46
46
|
remember_index_columns_inside_table(node)
|
47
47
|
end
|
48
48
|
end
|
@@ -75,119 +75,125 @@ module RailsBestPractices
|
|
75
75
|
@foreign_keys.each do |table, foreign_key|
|
76
76
|
table_node = @table_nodes[table]
|
77
77
|
foreign_key.each do |column|
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
next unless not_indexed?(table, column)
|
79
|
+
|
80
|
+
add_error "always add db index (#{table} => [#{Array(column).join(', ')}])",
|
81
|
+
table_node.file,
|
82
|
+
table_node.line_number
|
81
83
|
end
|
82
84
|
end
|
83
85
|
end
|
84
86
|
|
85
87
|
private
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
89
|
+
# remember the node as index columns, when used outside a table
|
90
|
+
# block, i.e.
|
91
|
+
# add_index :table_name, :column_name
|
92
|
+
def remember_index_columns_outside_table(node)
|
93
|
+
table_name = node.arguments.all.first.to_s
|
94
|
+
index_column = node.arguments.all[1].to_object
|
93
95
|
|
94
|
-
|
95
|
-
|
96
|
-
|
96
|
+
@index_columns[table_name] ||= []
|
97
|
+
@index_columns[table_name] << index_column
|
98
|
+
end
|
97
99
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
100
|
+
# remember the node as index columns, when used inside a table
|
101
|
+
# block, i.e.
|
102
|
+
# t.index [:column_name, ...]
|
103
|
+
def remember_index_columns_inside_table(node)
|
104
|
+
table_name = @table_name
|
105
|
+
index_column = node.arguments.all.first.to_object
|
104
106
|
|
105
|
-
|
106
|
-
|
107
|
-
|
107
|
+
@index_columns[table_name] ||= []
|
108
|
+
@index_columns[table_name] << index_column
|
109
|
+
end
|
108
110
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
# remember table nodes
|
112
|
+
def remember_table_nodes(node)
|
113
|
+
@table_name = node.arguments.all.first.to_s
|
114
|
+
@table_nodes[@table_name] = node
|
115
|
+
end
|
114
116
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
117
|
+
# remember foreign key columns
|
118
|
+
def remember_foreign_key_columns(node)
|
119
|
+
table_name = @table_name
|
120
|
+
foreign_key_column = node.arguments.all.first.to_s
|
121
|
+
@foreign_keys[table_name] ||= []
|
122
|
+
if foreign_key_column =~ /(.*?)_id$/
|
123
|
+
@foreign_keys[table_name] <<
|
124
|
+
if @foreign_keys[table_name].delete("#{Regexp.last_match(1)}_type")
|
125
|
+
["#{Regexp.last_match(1)}_id", "#{Regexp.last_match(1)}_type"]
|
123
126
|
else
|
124
|
-
|
127
|
+
foreign_key_column
|
125
128
|
end
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
129
|
+
foreign_id_column = foreign_key_column
|
130
|
+
elsif foreign_key_column =~ /(.*?)_type$/
|
131
|
+
@foreign_keys[table_name] <<
|
132
|
+
if @foreign_keys[table_name].delete("#{Regexp.last_match(1)}_id")
|
133
|
+
["#{Regexp.last_match(1)}_id", "#{Regexp.last_match(1)}_type"]
|
130
134
|
else
|
131
|
-
|
135
|
+
foreign_key_column
|
132
136
|
end
|
133
|
-
|
134
|
-
|
137
|
+
foreign_id_column = "#{Regexp.last_match(1)}_id"
|
138
|
+
end
|
135
139
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
140
|
+
if foreign_id_column
|
141
|
+
index_node = node.arguments.all.last.hash_value('index')
|
142
|
+
if index_node.present? && (index_node.to_s != 'false')
|
143
|
+
@index_columns[table_name] ||= []
|
144
|
+
@index_columns[table_name] << foreign_id_column
|
142
145
|
end
|
143
146
|
end
|
147
|
+
end
|
144
148
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
end
|
149
|
+
# remove the non foreign keys without corresponding tables.
|
150
|
+
def remove_table_not_exist_foreign_keys
|
151
|
+
@foreign_keys.each do |table, foreign_keys|
|
152
|
+
foreign_keys.delete_if do |key|
|
153
|
+
if key.is_a?(String) && key =~ /_id$/
|
154
|
+
class_name = Prepares.model_associations.get_association_class_name(table, key[0..-4])
|
155
|
+
class_name ? !@table_nodes[class_name.gsub('::', '').tableize] : !@table_nodes[key[0..-4].pluralize]
|
153
156
|
end
|
154
157
|
end
|
155
158
|
end
|
159
|
+
end
|
156
160
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
}
|
161
|
+
# remove the non foreign keys with only _type column.
|
162
|
+
def remove_only_type_foreign_keys
|
163
|
+
@foreign_keys.each do |_table, foreign_keys|
|
164
|
+
foreign_keys.delete_if { |key| key.is_a?(String) && key =~ /_type$/ }
|
162
165
|
end
|
166
|
+
end
|
163
167
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
168
|
+
# combine polymorphic foreign keys, e.g.
|
169
|
+
# [tagger_id], [tagger_type] => [tagger_id, tagger_type]
|
170
|
+
def combine_polymorphic_foreign_keys
|
171
|
+
@index_columns.each do |_table, foreign_keys|
|
172
|
+
foreign_id_keys = foreign_keys.select { |key| key.size == 1 && key.first =~ /_id/ }
|
173
|
+
foreign_type_keys = foreign_keys.select { |key| key.size == 1 && key.first =~ /_type/ }
|
174
|
+
foreign_id_keys.each do |id_key|
|
175
|
+
next unless type_key =
|
176
|
+
foreign_type_keys.detect { |type_key| type_key.first == id_key.first.sub(/_id/, '') + '_type' }
|
177
|
+
|
178
|
+
foreign_keys.delete(id_key)
|
179
|
+
foreign_keys.delete(type_key)
|
180
|
+
foreign_keys << id_key + type_key
|
181
|
+
end
|
177
182
|
end
|
183
|
+
end
|
178
184
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
185
|
+
# check if the table's column is indexed.
|
186
|
+
def not_indexed?(table, column)
|
187
|
+
index_columns = @index_columns[table]
|
188
|
+
!index_columns || index_columns.none? { |e| greater_than_or_equal(Array(e), Array(column)) }
|
189
|
+
end
|
184
190
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
+
# check if more_array is greater than less_array or equal to less_array.
|
192
|
+
def greater_than_or_equal(more_array, less_array)
|
193
|
+
more_size = more_array.size
|
194
|
+
less_size = less_array.size
|
195
|
+
(more_array - less_array).size == more_size - less_size
|
196
|
+
end
|
191
197
|
end
|
192
198
|
end
|
193
199
|
end
|
@@ -10,7 +10,17 @@ module RailsBestPractices
|
|
10
10
|
# Check all "save" calls to check the return value is used by a node we have visited.
|
11
11
|
class CheckDestroyReturnValueReview < Review
|
12
12
|
include Classable
|
13
|
-
interesting_nodes :call,
|
13
|
+
interesting_nodes :call,
|
14
|
+
:command_call,
|
15
|
+
:method_add_arg,
|
16
|
+
:if,
|
17
|
+
:ifop,
|
18
|
+
:elsif,
|
19
|
+
:unless,
|
20
|
+
:if_mod,
|
21
|
+
:unless_mod,
|
22
|
+
:assign,
|
23
|
+
:binary
|
14
24
|
interesting_files ALL_FILES
|
15
25
|
|
16
26
|
add_callback :start_if, :start_ifop, :start_elsif, :start_unless, :start_if_mod, :start_unless_mod do |node|
|
@@ -21,14 +31,13 @@ module RailsBestPractices
|
|
21
31
|
@used_return_value_of = node.right_value
|
22
32
|
end
|
23
33
|
|
34
|
+
# Consider anything used in an expression like "A or B" as used
|
24
35
|
add_callback :start_binary do |node|
|
25
|
-
# Consider anything used in an expression like "A or B" as used
|
26
36
|
if %w[&& || and or].include?(node[2].to_s)
|
27
37
|
all_conditions = node.all_conditions
|
28
38
|
# if our current binary is a subset of the @used_return_value_of
|
29
39
|
# then don't overwrite it
|
30
|
-
already_included = @used_return_value_of &&
|
31
|
-
(all_conditions - @used_return_value_of).empty?
|
40
|
+
already_included = @used_return_value_of && (all_conditions - @used_return_value_of).empty?
|
32
41
|
|
33
42
|
@used_return_value_of = node.all_conditions unless already_included
|
34
43
|
end
|
@@ -36,7 +45,8 @@ module RailsBestPractices
|
|
36
45
|
|
37
46
|
def return_value_is_used?(node)
|
38
47
|
return false unless @used_return_value_of
|
39
|
-
|
48
|
+
|
49
|
+
(node == @used_return_value_of) || @used_return_value_of.include?(node)
|
40
50
|
end
|
41
51
|
|
42
52
|
def model_classnames
|