ducalis 0.5.14 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +8 -10
- data/README.md +2 -4
- data/Rakefile +0 -5
- data/bin/ducalis +8 -3
- data/config/.ducalis.yml +20 -1
- data/ducalis.gemspec +0 -1
- data/lib/ducalis.rb +7 -0
- data/lib/ducalis/cli.rb +80 -19
- data/lib/ducalis/commentators/console.rb +1 -1
- data/lib/ducalis/cops/black_list_suffix.rb +3 -0
- data/lib/ducalis/cops/callbacks_activerecord.rb +7 -16
- data/lib/ducalis/cops/case_mapping.rb +8 -3
- data/lib/ducalis/cops/controllers_except.rb +0 -8
- data/lib/ducalis/cops/data_access_objects.rb +28 -0
- data/lib/ducalis/cops/enforce_namespace.rb +28 -0
- data/lib/ducalis/cops/evlis_overusing.rb +30 -0
- data/lib/ducalis/cops/extensions/type_resolving.rb +52 -0
- data/lib/ducalis/cops/fetch_expression.rb +80 -0
- data/lib/ducalis/cops/module_like_class.rb +1 -0
- data/lib/ducalis/cops/multiple_times.rb +50 -0
- data/lib/ducalis/cops/options_argument.rb +5 -22
- data/lib/ducalis/cops/possible_tap.rb +4 -0
- data/lib/ducalis/cops/preferable_methods.rb +3 -1
- data/lib/ducalis/cops/private_instance_assign.rb +4 -9
- data/lib/ducalis/cops/protected_scope_cop.rb +4 -0
- data/lib/ducalis/cops/public_send.rb +18 -0
- data/lib/ducalis/cops/regex_cop.rb +36 -6
- data/lib/ducalis/cops/rest_only_cop.rb +4 -11
- data/lib/ducalis/cops/too_long_workers.rb +3 -8
- data/lib/ducalis/cops/uncommented_gem.rb +13 -1
- data/lib/ducalis/cops/useless_only.rb +6 -8
- data/lib/ducalis/documentation.rb +28 -18
- data/lib/ducalis/passed_args.rb +2 -2
- data/lib/ducalis/version.rb +1 -1
- metadata +9 -17
- data/DOCUMENTATION.md +0 -1185
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rubocop'
|
4
|
+
require 'ducalis/cops/extensions/type_resolving'
|
4
5
|
|
5
6
|
module Ducalis
|
6
7
|
class RestOnlyCop < RuboCop::Cop::Cop
|
7
8
|
include RuboCop::Cop::DefNode
|
9
|
+
prepend TypeResolving
|
10
|
+
|
8
11
|
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
9
12
|
| It's better for controllers to stay adherent to REST:
|
10
13
|
| http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/.
|
@@ -16,23 +19,13 @@ module Ducalis
|
|
16
19
|
|
17
20
|
WHITELIST = %i(index show new edit create update destroy).freeze
|
18
21
|
|
19
|
-
def on_class(node)
|
20
|
-
_classdef_node, superclass, _body = *node
|
21
|
-
return if superclass.nil?
|
22
|
-
@triggered = superclass.loc.expression.source =~ /Controller/
|
23
|
-
end
|
24
|
-
|
25
22
|
def on_def(node)
|
26
|
-
return unless
|
23
|
+
return unless in_controller?
|
27
24
|
return if non_public?(node)
|
28
25
|
method_name, = *node
|
29
26
|
return if WHITELIST.include?(method_name)
|
30
27
|
add_offense(node, :expression, OFFENSE)
|
31
28
|
end
|
32
29
|
alias on_defs on_def
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
attr_reader :triggered
|
37
30
|
end
|
38
31
|
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rubocop'
|
4
|
+
require 'ducalis/cops/extensions/type_resolving'
|
4
5
|
|
5
6
|
module Ducalis
|
6
7
|
class TooLongWorkers < RuboCop::Cop::Cop
|
7
8
|
include RuboCop::Cop::ClassishLength
|
9
|
+
prepend TypeResolving
|
8
10
|
|
9
11
|
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
10
12
|
| Seems like your worker is doing too much work, consider of moving business logic to service object. As rule, workers should have only two responsibilities:
|
@@ -12,20 +14,13 @@ module Ducalis
|
|
12
14
|
| - __Errors handling__: Rescue errors and figure out what to do with them.
|
13
15
|
MESSAGE
|
14
16
|
|
15
|
-
WORKERS_SUFFIXES = %w(Worker Job).freeze
|
16
|
-
|
17
17
|
def on_class(node)
|
18
|
-
return unless
|
18
|
+
return unless in_worker?
|
19
19
|
check_code_length(node)
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
def worker_class?(node)
|
25
|
-
classdef_node, _superclass, _body = *node
|
26
|
-
classdef_node.source.end_with?(*WORKERS_SUFFIXES)
|
27
|
-
end
|
28
|
-
|
29
24
|
def message(length, max_length)
|
30
25
|
format("#{OFFENSE} [%d/%d]", length, max_length)
|
31
26
|
end
|
@@ -6,9 +6,14 @@ module Ducalis
|
|
6
6
|
class UncommentedGem < RuboCop::Cop::Cop
|
7
7
|
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
8
|
| Please, add comment why are you including non-realized gem version for %<gem>s.
|
9
|
+
MESSAGE
|
10
|
+
|
11
|
+
DETAILS = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
9
12
|
| It will increase [bus-factor](<https://en.wikipedia.org/wiki/Bus_factor>).
|
10
13
|
MESSAGE
|
11
14
|
|
15
|
+
ALLOWED_KEYS = %w(require group :require :group).freeze
|
16
|
+
|
12
17
|
def investigate(processed_source)
|
13
18
|
return unless processed_source.ast
|
14
19
|
gem_declarations(processed_source.ast).select do |node|
|
@@ -22,12 +27,19 @@ module Ducalis
|
|
22
27
|
|
23
28
|
private
|
24
29
|
|
25
|
-
def_node_search :gem_declarations, '(send nil :gem str
|
30
|
+
def_node_search :gem_declarations, '(send nil :gem str #allowed_args?)'
|
26
31
|
|
27
32
|
def commented?(processed_source, node)
|
28
33
|
processed_source.comments
|
29
34
|
.map { |subnode| subnode.loc.line }
|
30
35
|
.include?(node.loc.line)
|
31
36
|
end
|
37
|
+
|
38
|
+
def allowed_args?(args)
|
39
|
+
return false if args.nil? || args.type != :hash
|
40
|
+
args.children.any? do |arg_node|
|
41
|
+
!ALLOWED_KEYS.include?(arg_node.children.first.source)
|
42
|
+
end
|
43
|
+
end
|
32
44
|
end
|
33
45
|
end
|
@@ -6,6 +6,10 @@ module Ducalis
|
|
6
6
|
class UselessOnly < RuboCop::Cop::Cop
|
7
7
|
OFFENSE = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
8
8
|
| Seems like there is no any reason to keep before filter only for one action. Maybe it will be better to inline it?
|
9
|
+
MESSAGE
|
10
|
+
|
11
|
+
DETAILS = <<-MESSAGE.gsub(/^ +\|\s/, '').strip
|
12
|
+
| Compare:
|
9
13
|
|
10
14
|
| ```ruby
|
11
15
|
| before_filter :do_something, only: %i[index]
|
@@ -16,18 +20,14 @@ module Ducalis
|
|
16
20
|
| def index
|
17
21
|
| do_something
|
18
22
|
| end
|
23
|
+
|
19
24
|
| ```
|
25
|
+
|
20
26
|
MESSAGE
|
21
27
|
|
22
28
|
FILTERS = %i(before_filter after_filter around_filter
|
23
29
|
before_action after_action around_action).freeze
|
24
30
|
|
25
|
-
def on_class(node)
|
26
|
-
_classdef_node, superclass, _body = *node
|
27
|
-
return if superclass.nil?
|
28
|
-
@triggered = superclass.loc.expression.source =~ /Controller/
|
29
|
-
end
|
30
|
-
|
31
31
|
def on_send(node)
|
32
32
|
_, method_name, *args = *node
|
33
33
|
hash_node = args.find { |subnode| subnode.type == :hash }
|
@@ -43,7 +43,5 @@ module Ducalis
|
|
43
43
|
def decomposite_hash(args)
|
44
44
|
args.to_a.first.children.to_a
|
45
45
|
end
|
46
|
-
|
47
|
-
attr_reader :triggered
|
48
46
|
end
|
49
47
|
end
|
@@ -27,17 +27,25 @@ class SpecsProcessor < Parser::AST::Processor
|
|
27
27
|
|
28
28
|
def on_send(node)
|
29
29
|
_, name, _body = *node
|
30
|
-
if name == :inspect_source
|
31
|
-
source_code = remove_array_wrapping(node.to_a.last.loc.expression.source)
|
32
|
-
.split("\n")
|
33
|
-
.map { |line| remove_string_wrapping(line) }
|
34
|
-
cases << [current_it, source_code]
|
35
|
-
end
|
30
|
+
cases << [current_it, source_code(node)] if name == :inspect_source
|
36
31
|
super
|
37
32
|
end
|
38
33
|
|
39
34
|
private
|
40
35
|
|
36
|
+
def source_code(node)
|
37
|
+
prepare_code(node).tap do |code|
|
38
|
+
code.shift if code.first.empty?
|
39
|
+
code.pop if code.last.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def prepare_code(node)
|
44
|
+
remove_array_wrapping(node.to_a.last.loc.expression.source)
|
45
|
+
.split("\n")
|
46
|
+
.map { |line| remove_string_wrapping(line) }
|
47
|
+
end
|
48
|
+
|
41
49
|
def current_it
|
42
50
|
it_block = @nesting.reverse.find { |node| node.type == :block }
|
43
51
|
it = it_block.to_a.first
|
@@ -57,13 +65,11 @@ class SpecsProcessor < Parser::AST::Processor
|
|
57
65
|
end
|
58
66
|
|
59
67
|
class Documentation
|
60
|
-
RED_SQUARE = '![](https://placehold.it/10/f03c15/000000?text=+)'
|
61
|
-
GREEN_SQUARE = '![](https://placehold.it/10/2cbe4e/000000?text=+)'
|
62
68
|
SIGNAL_WORD = 'raises'
|
63
|
-
|
69
|
+
RULE_WORD = '[rule]'
|
64
70
|
|
65
71
|
def call
|
66
|
-
Dir['
|
72
|
+
Dir[File.join(File.dirname(__FILE__), 'cops', '*.rb')].sort.map do |f|
|
67
73
|
present_cop(klass_const_for(f), spec_cases_for(f))
|
68
74
|
end.flatten.join("\n")
|
69
75
|
end
|
@@ -73,18 +79,22 @@ class Documentation
|
|
73
79
|
def present_cop(klass, specs)
|
74
80
|
[
|
75
81
|
"## #{klass}\n", # header
|
76
|
-
message(klass)
|
82
|
+
message(klass) + "\n" # description
|
77
83
|
] +
|
78
84
|
specs.map do |(it, code)|
|
79
85
|
[
|
80
|
-
|
81
|
-
"```ruby\n#{code.join("\n")}\n
|
86
|
+
prepare(it).to_s, # case description
|
87
|
+
"\n```ruby\n#{mention(it)}\n#{code.join("\n")}\n```\n" # code example
|
82
88
|
]
|
83
89
|
end
|
84
90
|
end
|
85
91
|
|
86
|
-
def
|
87
|
-
it.
|
92
|
+
def prepare(it)
|
93
|
+
it.sub("#{RULE_WORD} ", '')
|
94
|
+
end
|
95
|
+
|
96
|
+
def mention(it)
|
97
|
+
it.include?(SIGNAL_WORD) ? '# bad' : '# good'
|
88
98
|
end
|
89
99
|
|
90
100
|
def message(klass)
|
@@ -101,12 +111,12 @@ class Documentation
|
|
101
111
|
)
|
102
112
|
SpecsProcessor.new.tap do |processor|
|
103
113
|
processor.process(Parser::CurrentRuby.parse(source_code))
|
104
|
-
end.cases.
|
114
|
+
end.cases.select(&method(:allowed?))
|
105
115
|
end
|
106
116
|
|
107
|
-
def
|
117
|
+
def allowed?(example)
|
108
118
|
desc, _code = example
|
109
|
-
|
119
|
+
desc.include?(RULE_WORD)
|
110
120
|
end
|
111
121
|
|
112
122
|
def klass_const_for(f)
|
data/lib/ducalis/passed_args.rb
CHANGED
@@ -4,10 +4,10 @@ module Ducalis
|
|
4
4
|
module PassedArgs
|
5
5
|
module_function
|
6
6
|
|
7
|
-
|
7
|
+
HELP_FLAGS = ['-h', '-?', '--help'].freeze
|
8
8
|
|
9
9
|
def help_command?
|
10
|
-
ARGV.any? { |arg|
|
10
|
+
ARGV.any? { |arg| HELP_FLAGS.include?(arg) }
|
11
11
|
end
|
12
12
|
|
13
13
|
def ci_mode?
|
data/lib/ducalis/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ducalis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ignat Zakrevsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git
|
@@ -84,20 +84,6 @@ dependencies:
|
|
84
84
|
- - "~>"
|
85
85
|
- !ruby/object:Gem::Version
|
86
86
|
version: 0.46.0
|
87
|
-
- !ruby/object:Gem::Dependency
|
88
|
-
name: thor
|
89
|
-
requirement: !ruby/object:Gem::Requirement
|
90
|
-
requirements:
|
91
|
-
- - "~>"
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
version: 0.20.0
|
94
|
-
type: :runtime
|
95
|
-
prerelease: false
|
96
|
-
version_requirements: !ruby/object:Gem::Requirement
|
97
|
-
requirements:
|
98
|
-
- - "~>"
|
99
|
-
- !ruby/object:Gem::Version
|
100
|
-
version: 0.20.0
|
101
87
|
description: " Ducalis is RuboCop based static code analyzer for enterprise Rails
|
102
88
|
\ applications.\n"
|
103
89
|
email:
|
@@ -113,7 +99,6 @@ files:
|
|
113
99
|
- ".rubocop.yml"
|
114
100
|
- ".ruby-version"
|
115
101
|
- ".travis.yml"
|
116
|
-
- DOCUMENTATION.md
|
117
102
|
- Gemfile
|
118
103
|
- Gemfile.lock
|
119
104
|
- LICENSE
|
@@ -134,8 +119,14 @@ files:
|
|
134
119
|
- lib/ducalis/cops/callbacks_activerecord.rb
|
135
120
|
- lib/ducalis/cops/case_mapping.rb
|
136
121
|
- lib/ducalis/cops/controllers_except.rb
|
122
|
+
- lib/ducalis/cops/data_access_objects.rb
|
123
|
+
- lib/ducalis/cops/enforce_namespace.rb
|
124
|
+
- lib/ducalis/cops/evlis_overusing.rb
|
125
|
+
- lib/ducalis/cops/extensions/type_resolving.rb
|
126
|
+
- lib/ducalis/cops/fetch_expression.rb
|
137
127
|
- lib/ducalis/cops/keyword_defaults.rb
|
138
128
|
- lib/ducalis/cops/module_like_class.rb
|
129
|
+
- lib/ducalis/cops/multiple_times.rb
|
139
130
|
- lib/ducalis/cops/only_defs.rb
|
140
131
|
- lib/ducalis/cops/options_argument.rb
|
141
132
|
- lib/ducalis/cops/params_passing.rb
|
@@ -143,6 +134,7 @@ files:
|
|
143
134
|
- lib/ducalis/cops/preferable_methods.rb
|
144
135
|
- lib/ducalis/cops/private_instance_assign.rb
|
145
136
|
- lib/ducalis/cops/protected_scope_cop.rb
|
137
|
+
- lib/ducalis/cops/public_send.rb
|
146
138
|
- lib/ducalis/cops/raise_without_error_class.rb
|
147
139
|
- lib/ducalis/cops/regex_cop.rb
|
148
140
|
- lib/ducalis/cops/rest_only_cop.rb
|
data/DOCUMENTATION.md
DELETED
@@ -1,1185 +0,0 @@
|
|
1
|
-
## Ducalis::BlackListSuffix
|
2
|
-
|
3
|
-
Please, avoid using of class suffixes like `Meneger`, `Client` and so on. If it has no parts, change the name of the class to what each object is managing.
|
4
|
-
|
5
|
-
It's ok to use Manager as subclass of Person, which is there to refine a type of personal that has management behavior to it.
|
6
|
-
Related [article](<http://www.carlopescio.com/2011/04/your-coding-conventions-are-hurting-you.html>)
|
7
|
-
|
8
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises on classes with suffixes from black list
|
9
|
-
```ruby
|
10
|
-
|
11
|
-
class ListSorter
|
12
|
-
end
|
13
|
-
|
14
|
-
```
|
15
|
-
|
16
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores classes with okish suffixes
|
17
|
-
```ruby
|
18
|
-
|
19
|
-
class SortedList
|
20
|
-
end
|
21
|
-
|
22
|
-
```
|
23
|
-
|
24
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores classes with full match
|
25
|
-
```ruby
|
26
|
-
|
27
|
-
class Manager
|
28
|
-
end
|
29
|
-
|
30
|
-
```
|
31
|
-
## Ducalis::CallbacksActiverecord
|
32
|
-
|
33
|
-
Please, avoid using of callbacks for models. It's better to keep models small ("dumb") and instead use "builder" classes/services: to construct new objects.
|
34
|
-
You can read more [here](https://medium.com/planet-arkency/a61fd75ab2d3).
|
35
|
-
|
36
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises on ActiveRecord classes which contains callbacks
|
37
|
-
```ruby
|
38
|
-
|
39
|
-
class Product < ActiveRecord::Base
|
40
|
-
before_create :generate_code
|
41
|
-
end
|
42
|
-
|
43
|
-
```
|
44
|
-
|
45
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores non-ActiveRecord classes which contains callbacks
|
46
|
-
```ruby
|
47
|
-
|
48
|
-
class Product < BasicProduct
|
49
|
-
before_create :generate_code
|
50
|
-
end
|
51
|
-
|
52
|
-
```
|
53
|
-
## Ducalis::CaseMapping
|
54
|
-
|
55
|
-
Try to avoid `case when` statements. You can replace it with a sequence of `if... elsif... elsif... else`.
|
56
|
-
For cases where you need to choose from a large number of possibilities, you can create a dictionary mapping case values to functions to call by `call`. It's nice to have prefix for the method names, i.e.: `visit_`.
|
57
|
-
Usually `case when` statements are using for the next reasons:
|
58
|
-
|
59
|
-
I. Mapping between different values.
|
60
|
-
("A" => 1, "B" => 2, ...)
|
61
|
-
|
62
|
-
This case is all about data representing. If you do not need to execute any code it's better to use data structure which represents it. This way you are separating concepts: code returns corresponding value and you have config-like data structure which describes your data.
|
63
|
-
|
64
|
-
```ruby
|
65
|
-
%w[A B ...].index("A") + 1
|
66
|
-
# or
|
67
|
-
{ "A" => 1, "B" => 2 }.fetch("A")
|
68
|
-
```
|
69
|
-
|
70
|
-
II. Code execution depending of parameter or type:
|
71
|
-
|
72
|
-
- a. (:attack => attack, :defend => defend)
|
73
|
-
- b. (Feet => value * 0.348, Meters => `value`)
|
74
|
-
|
75
|
-
In this case code violates OOP and S[O]LID principle. Code shouldn't know about object type and classes should be open for extension, but closed for modification (but you can't do it with case-statements). This is a signal that you have some problems with architecture.
|
76
|
-
|
77
|
-
a.
|
78
|
-
```ruby
|
79
|
-
attack: -> { execute_attack }, defend: -> { execute_defend }
|
80
|
-
# or
|
81
|
-
call(:"execute_#{action}")
|
82
|
-
```
|
83
|
-
|
84
|
-
b.
|
85
|
-
```ruby
|
86
|
-
class Meters; def to_metters; value; end
|
87
|
-
class Feet; def to_metters; value * 0.348; end
|
88
|
-
```
|
89
|
-
|
90
|
-
III. Code execution depending on some statement.
|
91
|
-
(`a > 0` => 1, `a == 0` => 0, `a < 0` => -1)
|
92
|
-
|
93
|
-
This case is combination of I and II -- high code complexity and unit-tests complexity. There are variants how to solve it:
|
94
|
-
|
95
|
-
a. Rewrite to simple if statement
|
96
|
-
|
97
|
-
```ruby
|
98
|
-
return 0 if a == 0
|
99
|
-
a > 0 ? 1 : -1
|
100
|
-
```
|
101
|
-
|
102
|
-
b. Move statements to lambdas:
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
->(a) { a > 0 } => 1,
|
106
|
-
->(a) { a == 0 } => 0,
|
107
|
-
->(a) { a < 0 } => -1
|
108
|
-
```
|
109
|
-
|
110
|
-
This way decreases code complexity by delegating it to lambdas and makes it easy to unit-testing because it's easy to test pure lambdas.
|
111
|
-
|
112
|
-
Such approach is named [table-driven design](<https://www.d.umn.edu/~gshute/softeng/table-driven.html>). Table-driven methods are schemes that allow you to look up information in a table rather than using logic statements (i.e. case, if). In simple cases, it's quicker and easier to use logic statements, but as the logic chain becomes more complex, table-driven code is simpler than complicated logic, easier to modify and more efficient.
|
113
|
-
|
114
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises on case statements
|
115
|
-
```ruby
|
116
|
-
|
117
|
-
case grade
|
118
|
-
when "A"
|
119
|
-
puts "Well done!"
|
120
|
-
when "B"
|
121
|
-
puts "Try harder!"
|
122
|
-
when "C"
|
123
|
-
puts "You need help!!!"
|
124
|
-
else
|
125
|
-
puts "You just making it up!"
|
126
|
-
end
|
127
|
-
|
128
|
-
```
|
129
|
-
## Ducalis::ControllersExcept
|
130
|
-
|
131
|
-
Prefer to use `:only` over `:except` in controllers because it's more explicit and will be easier to maintain for new developers.
|
132
|
-
|
133
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for `before_filters` with `except` method as array
|
134
|
-
```ruby
|
135
|
-
|
136
|
-
class ProductsController < ApplicationController
|
137
|
-
before_filter :update_cost, except: [:index]
|
138
|
-
|
139
|
-
def index; end
|
140
|
-
def edit; end
|
141
|
-
|
142
|
-
private
|
143
|
-
|
144
|
-
def update_cost; end
|
145
|
-
end
|
146
|
-
|
147
|
-
```
|
148
|
-
|
149
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for filters with many actions and only one `except` method
|
150
|
-
```ruby
|
151
|
-
|
152
|
-
class ProductsController < ApplicationController
|
153
|
-
before_filter :update_cost, :load_me, except: %i[edit]
|
154
|
-
|
155
|
-
def index; end
|
156
|
-
def edit; end
|
157
|
-
|
158
|
-
private
|
159
|
-
|
160
|
-
def update_cost; end
|
161
|
-
def load_me; end
|
162
|
-
end
|
163
|
-
|
164
|
-
```
|
165
|
-
|
166
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `before_filters` without arguments
|
167
|
-
```ruby
|
168
|
-
|
169
|
-
class ProductsController < ApplicationController
|
170
|
-
before_filter :update_cost
|
171
|
-
|
172
|
-
def index; end
|
173
|
-
|
174
|
-
private
|
175
|
-
|
176
|
-
def update_cost; end
|
177
|
-
end
|
178
|
-
|
179
|
-
```
|
180
|
-
## Ducalis::KeywordDefaults
|
181
|
-
|
182
|
-
Prefer to use keyword arguments for defaults. It increases readability and reduces ambiguities.
|
183
|
-
|
184
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if method definition contains default values
|
185
|
-
```ruby
|
186
|
-
def calculate(step, index, dry = true); end
|
187
|
-
```
|
188
|
-
|
189
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if class method definition contains default values
|
190
|
-
```ruby
|
191
|
-
def self.calculate(step, index, dry = true); end
|
192
|
-
```
|
193
|
-
|
194
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores if method definition contains default values through keywords
|
195
|
-
```ruby
|
196
|
-
def calculate(step, index, dry: true); end
|
197
|
-
```
|
198
|
-
|
199
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores for methods without arguments
|
200
|
-
```ruby
|
201
|
-
def calculate_amount; end
|
202
|
-
```
|
203
|
-
|
204
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores for class methods without arguments
|
205
|
-
```ruby
|
206
|
-
def self.calculate_amount; end
|
207
|
-
```
|
208
|
-
## Ducalis::ModuleLikeClass
|
209
|
-
|
210
|
-
Seems like it will be better to define initialize and pass %<args>s there instead of each method.
|
211
|
-
|
212
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if class doesn't contain constructor but accept the same args
|
213
|
-
```ruby
|
214
|
-
|
215
|
-
class TaskJournal
|
216
|
-
def initialize(customer)
|
217
|
-
# ...
|
218
|
-
end
|
219
|
-
|
220
|
-
def approve(task, estimate, options)
|
221
|
-
# ...
|
222
|
-
end
|
223
|
-
|
224
|
-
def decline(user, task, estimate, details)
|
225
|
-
# ...
|
226
|
-
end
|
227
|
-
|
228
|
-
private
|
229
|
-
|
230
|
-
def log(record)
|
231
|
-
# ...
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
```
|
236
|
-
|
237
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for class with only one public method with args
|
238
|
-
```ruby
|
239
|
-
|
240
|
-
class TaskJournal
|
241
|
-
def approve(task)
|
242
|
-
# ...
|
243
|
-
end
|
244
|
-
|
245
|
-
private
|
246
|
-
|
247
|
-
def log(record)
|
248
|
-
# ...
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
```
|
253
|
-
|
254
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores classes with custom includes
|
255
|
-
```ruby
|
256
|
-
|
257
|
-
class TaskJournal
|
258
|
-
include Singleton
|
259
|
-
|
260
|
-
def approve(task)
|
261
|
-
# ...
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
```
|
266
|
-
|
267
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores classes with inheritance
|
268
|
-
```ruby
|
269
|
-
|
270
|
-
class TaskJournal < BasicJournal
|
271
|
-
def approve(task)
|
272
|
-
# ...
|
273
|
-
end
|
274
|
-
|
275
|
-
private
|
276
|
-
|
277
|
-
def log(record)
|
278
|
-
# ...
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
```
|
283
|
-
|
284
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores classes with one method and initializer
|
285
|
-
```ruby
|
286
|
-
|
287
|
-
class TaskJournal
|
288
|
-
def initialize(task)
|
289
|
-
# ...
|
290
|
-
end
|
291
|
-
|
292
|
-
def call(args)
|
293
|
-
# ...
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
```
|
298
|
-
## Ducalis::OnlyDefs
|
299
|
-
|
300
|
-
Prefer object instances to class methods because class methods resist refactoring. Begin with an object instance, even if it doesn’t have state or multiple methods right away. If you come back to change it later, you will be more likely to refactor. If it never changes, the difference between the class method approach and the instance is negligible, and you certainly won’t be any worse off.
|
301
|
-
Related article: https://codeclimate.com/blog/why-ruby-class-methods-resist-refactoring/
|
302
|
-
|
303
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores classes with one instance method
|
304
|
-
```ruby
|
305
|
-
|
306
|
-
class TaskJournal
|
307
|
-
def initialize(task)
|
308
|
-
# ...
|
309
|
-
end
|
310
|
-
|
311
|
-
def call(args)
|
312
|
-
# ...
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
```
|
317
|
-
|
318
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores classes with mixed methods
|
319
|
-
```ruby
|
320
|
-
|
321
|
-
class TaskJournal
|
322
|
-
def self.find(task)
|
323
|
-
# ...
|
324
|
-
end
|
325
|
-
|
326
|
-
def call(args)
|
327
|
-
# ...
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
```
|
332
|
-
|
333
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises error for class with ONLY class methods
|
334
|
-
```ruby
|
335
|
-
|
336
|
-
class TaskJournal
|
337
|
-
|
338
|
-
def self.call(task)
|
339
|
-
# ...
|
340
|
-
end
|
341
|
-
|
342
|
-
def self.find(args)
|
343
|
-
# ...
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
```
|
348
|
-
|
349
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises error for class with ONLY class << self
|
350
|
-
```ruby
|
351
|
-
|
352
|
-
class TaskJournal
|
353
|
-
class << self
|
354
|
-
def call(task)
|
355
|
-
# ...
|
356
|
-
end
|
357
|
-
|
358
|
-
def find(args)
|
359
|
-
# ...
|
360
|
-
end
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
```
|
365
|
-
|
366
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores instance methods mixed with ONLY class << self
|
367
|
-
```ruby
|
368
|
-
|
369
|
-
class TaskJournal
|
370
|
-
class << self
|
371
|
-
def call(task)
|
372
|
-
# ...
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
def find(args)
|
377
|
-
# ...
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
```
|
382
|
-
## Ducalis::OptionsArgument
|
383
|
-
|
384
|
-
Default `options` (or `args`) argument isn't good idea. It's better to explicitly pass which keys are you interested in as keyword arguments. You can use split operator to support hash arguments.
|
385
|
-
|
386
|
-
Compare:
|
387
|
-
|
388
|
-
```ruby
|
389
|
-
def generate_1(document, options = {})
|
390
|
-
format = options.delete(:format)
|
391
|
-
limit = options.delete(:limit) || 20
|
392
|
-
# ...
|
393
|
-
[format, limit, options]
|
394
|
-
end
|
395
|
-
|
396
|
-
options = { format: 'csv', limit: 5, useless_arg: :value }
|
397
|
-
generate_1(1, options) #=> ["csv", 5, {:useless_arg=>:value}]
|
398
|
-
generate_1(1, format: 'csv', limit: 5, useless_arg: :value) #=> ["csv", 5, {:useless_arg=>:value}]
|
399
|
-
|
400
|
-
# vs
|
401
|
-
|
402
|
-
def generate_2(document, format:, limit: 20, **options)
|
403
|
-
# ...
|
404
|
-
[format, limit, options]
|
405
|
-
end
|
406
|
-
|
407
|
-
options = { format: 'csv', limit: 5, useless_arg: :value }
|
408
|
-
generate_2(1, **options) #=> ["csv", 5, {:useless_arg=>:value}]
|
409
|
-
generate_2(1, format: 'csv', limit: 5, useless_arg: :value) #=> ["csv", 5, {:useless_arg=>:value}]
|
410
|
-
|
411
|
-
```
|
412
|
-
|
413
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if method accepts default options argument
|
414
|
-
```ruby
|
415
|
-
|
416
|
-
def generate(document, options = {})
|
417
|
-
format = options.delete(:format)
|
418
|
-
limit = options.delete(:limit) || 20
|
419
|
-
[format, limit, options]
|
420
|
-
end
|
421
|
-
|
422
|
-
```
|
423
|
-
|
424
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if method accepts options argument
|
425
|
-
```ruby
|
426
|
-
|
427
|
-
def log(record, options)
|
428
|
-
# ...
|
429
|
-
end
|
430
|
-
|
431
|
-
```
|
432
|
-
|
433
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if method accepts args argument
|
434
|
-
```ruby
|
435
|
-
|
436
|
-
def log(record, args)
|
437
|
-
# ...
|
438
|
-
end
|
439
|
-
|
440
|
-
```
|
441
|
-
|
442
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores passing options with split operator
|
443
|
-
```ruby
|
444
|
-
|
445
|
-
def generate(document, format:, limit: 20, **options)
|
446
|
-
[format, limit, options]
|
447
|
-
end
|
448
|
-
|
449
|
-
```
|
450
|
-
## Ducalis::ParamsPassing
|
451
|
-
|
452
|
-
It's better to pass already preprocessed params hash to services. Or you can use `arcane` gem.
|
453
|
-
|
454
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if user pass `params` as argument from controller
|
455
|
-
```ruby
|
456
|
-
|
457
|
-
class ProductsController < ApplicationController
|
458
|
-
def index
|
459
|
-
Record.new(params).log
|
460
|
-
end
|
461
|
-
end
|
462
|
-
|
463
|
-
```
|
464
|
-
|
465
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if user pass `params` as any argument from controller
|
466
|
-
```ruby
|
467
|
-
|
468
|
-
class ProductsController < ApplicationController
|
469
|
-
def index
|
470
|
-
Record.new(first_arg, params).log
|
471
|
-
end
|
472
|
-
end
|
473
|
-
|
474
|
-
```
|
475
|
-
|
476
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if user pass `params` as keyword argument from controller
|
477
|
-
```ruby
|
478
|
-
|
479
|
-
class ProductsController < ApplicationController
|
480
|
-
def index
|
481
|
-
Record.new(first_arg, any_name: params).log
|
482
|
-
end
|
483
|
-
end
|
484
|
-
|
485
|
-
```
|
486
|
-
|
487
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores passing only one `params` field
|
488
|
-
```ruby
|
489
|
-
|
490
|
-
class ProductsController < ApplicationController
|
491
|
-
def index
|
492
|
-
Record.new(first_arg, params[:id]).log
|
493
|
-
end
|
494
|
-
end
|
495
|
-
|
496
|
-
```
|
497
|
-
|
498
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores passing processed `params`
|
499
|
-
```ruby
|
500
|
-
|
501
|
-
class ProductsController < ApplicationController
|
502
|
-
def index
|
503
|
-
Record.new(first_arg, params.slice(:name)).log
|
504
|
-
end
|
505
|
-
end
|
506
|
-
|
507
|
-
```
|
508
|
-
|
509
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores passing `params` from `arcane` gem
|
510
|
-
```ruby
|
511
|
-
|
512
|
-
class ProductsController < ApplicationController
|
513
|
-
def index
|
514
|
-
Record.new(params.for(Log).as(user).refine).log
|
515
|
-
end
|
516
|
-
end
|
517
|
-
|
518
|
-
```
|
519
|
-
## Ducalis::PossibleTap
|
520
|
-
|
521
|
-
Consider of using `.tap`, default ruby [method](<https://apidock.com/ruby/Object/tap>) which allows to replace intermediate variables with block, by this you are limiting scope pollution and make method scope more clear.
|
522
|
-
If it isn't possible, consider of moving it to method or even inline it.
|
523
|
-
[Related article](<http://seejohncode.com/2012/01/02/ruby-tap-that/>).
|
524
|
-
|
525
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for methods with scope variable return
|
526
|
-
```ruby
|
527
|
-
|
528
|
-
def load_group
|
529
|
-
group = channel.groups.find(params[:group_id])
|
530
|
-
authorize group, :edit?
|
531
|
-
group
|
532
|
-
end
|
533
|
-
|
534
|
-
```
|
535
|
-
|
536
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for methods with instance variable changes and return
|
537
|
-
```ruby
|
538
|
-
|
539
|
-
def load_group
|
540
|
-
@group = Group.find(params[:id])
|
541
|
-
authorize @group
|
542
|
-
@group
|
543
|
-
end
|
544
|
-
|
545
|
-
```
|
546
|
-
|
547
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for methods with instance variable `||=` assign and return
|
548
|
-
```ruby
|
549
|
-
|
550
|
-
def define_roles
|
551
|
-
return [] unless employee
|
552
|
-
|
553
|
-
@roles ||= []
|
554
|
-
@roles << "primary" if employee.primary?
|
555
|
-
@roles << "contract" if employee.contract?
|
556
|
-
@roles
|
557
|
-
end
|
558
|
-
|
559
|
-
```
|
560
|
-
|
561
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for methods which return call on scope variable
|
562
|
-
```ruby
|
563
|
-
|
564
|
-
def load_group
|
565
|
-
elections = @elections.group_by(&:code)
|
566
|
-
result = elections.map do |code, elections|
|
567
|
-
{ code => statistic }
|
568
|
-
end
|
569
|
-
result << total_spend(@elections)
|
570
|
-
result.inject(:merge)
|
571
|
-
end
|
572
|
-
|
573
|
-
```
|
574
|
-
|
575
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for methods which return instance variable but have scope vars
|
576
|
-
```ruby
|
577
|
-
|
578
|
-
def generate_file(file_name)
|
579
|
-
@file = Tempfile.new([file_name, ".pdf"])
|
580
|
-
signed_pdf = some_new_stuff
|
581
|
-
@file.write(signed_pdf.to_pdf)
|
582
|
-
@file.close
|
583
|
-
@file
|
584
|
-
end
|
585
|
-
|
586
|
-
```
|
587
|
-
|
588
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores empty methods
|
589
|
-
```ruby
|
590
|
-
|
591
|
-
def edit
|
592
|
-
end
|
593
|
-
|
594
|
-
```
|
595
|
-
|
596
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores methods which body is just call
|
597
|
-
```ruby
|
598
|
-
|
599
|
-
def total_cost(cost_field)
|
600
|
-
Service.cost_sum(cost_field)
|
601
|
-
end
|
602
|
-
|
603
|
-
```
|
604
|
-
|
605
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores methods which return some statement
|
606
|
-
```ruby
|
607
|
-
|
608
|
-
def stop_terminated_employee
|
609
|
-
if current_user && current_user.terminated?
|
610
|
-
sign_out current_user
|
611
|
-
redirect_to new_user_session_path
|
612
|
-
end
|
613
|
-
end
|
614
|
-
|
615
|
-
|
616
|
-
```
|
617
|
-
## Ducalis::PreferableMethods
|
618
|
-
|
619
|
-
Prefer to use %<alternative>s method instead of %<original>s because of %<reason>s.
|
620
|
-
Dangerous methods are:
|
621
|
-
`toggle!`, `save`, `delete`, `delete_all`, `update_attribute`, `update_column`, `update_columns`.
|
622
|
-
|
623
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for `delete` method calling
|
624
|
-
```ruby
|
625
|
-
User.where(id: 7).delete
|
626
|
-
```
|
627
|
-
|
628
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises `save` method calling with validate: false
|
629
|
-
```ruby
|
630
|
-
User.where(id: 7).save(validate: false)
|
631
|
-
```
|
632
|
-
|
633
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises `update_column` method calling
|
634
|
-
```ruby
|
635
|
-
User.where(id: 7).update_column(admin: false)
|
636
|
-
```
|
637
|
-
|
638
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises `toggle!` method calling
|
639
|
-
```ruby
|
640
|
-
User.where(id: 7).toggle!
|
641
|
-
```
|
642
|
-
|
643
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `save` method calling without validate: false
|
644
|
-
```ruby
|
645
|
-
User.where(id: 7).save
|
646
|
-
```
|
647
|
-
|
648
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `save` method calling without validate: false
|
649
|
-
```ruby
|
650
|
-
User.where(id: 7).save(some_arg: true)
|
651
|
-
```
|
652
|
-
|
653
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores calling `delete` with symbol
|
654
|
-
```ruby
|
655
|
-
params.delete(:code)
|
656
|
-
```
|
657
|
-
|
658
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores calling `delete` with string
|
659
|
-
```ruby
|
660
|
-
string.delete("-")
|
661
|
-
```
|
662
|
-
|
663
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores calling `delete` with multiple args
|
664
|
-
```ruby
|
665
|
-
some.delete(1, header: [])
|
666
|
-
```
|
667
|
-
|
668
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores calling `delete` on files-like variables
|
669
|
-
```ruby
|
670
|
-
tempfile.delete
|
671
|
-
```
|
672
|
-
## Ducalis::PrivateInstanceAssign
|
673
|
-
|
674
|
-
Don't use controller's filter methods for setting instance variables, use them only for changing application flow, such as redirecting if a user is not authenticated. Controller instance variables are forming contract between controller and view. Keeping instance variables defined in one place makes it easier to: reason, refactor and remove old views, test controllers and views, extract actions to new controllers, etc.
|
675
|
-
If you want to memoize variable, please, add underscore to the variable name start: `@_name`.
|
676
|
-
|
677
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for assigning instance variables in controllers private methods
|
678
|
-
```ruby
|
679
|
-
|
680
|
-
class EmployeesController < ApplicationController
|
681
|
-
private
|
682
|
-
|
683
|
-
def load_employee
|
684
|
-
@employee = Employee.find(params[:id])
|
685
|
-
end
|
686
|
-
end
|
687
|
-
|
688
|
-
```
|
689
|
-
|
690
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for memoization variables in controllers private methods
|
691
|
-
```ruby
|
692
|
-
|
693
|
-
class EmployeesController < ApplicationController
|
694
|
-
private
|
695
|
-
|
696
|
-
def catalog
|
697
|
-
@catalog ||= Catalog.new
|
698
|
-
end
|
699
|
-
end
|
700
|
-
|
701
|
-
```
|
702
|
-
|
703
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores memoization variables in controllers private methods with _
|
704
|
-
```ruby
|
705
|
-
|
706
|
-
class EmployeesController < ApplicationController
|
707
|
-
private
|
708
|
-
|
709
|
-
def catalog
|
710
|
-
@_catalog ||= Catalog.new
|
711
|
-
end
|
712
|
-
end
|
713
|
-
|
714
|
-
```
|
715
|
-
|
716
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores assigning instance variables in controllers public methods
|
717
|
-
```ruby
|
718
|
-
|
719
|
-
class EmployeesController < ApplicationController
|
720
|
-
def index
|
721
|
-
@employee = load_employee
|
722
|
-
end
|
723
|
-
|
724
|
-
private
|
725
|
-
|
726
|
-
def load_employee
|
727
|
-
Employee.find(params[:id])
|
728
|
-
end
|
729
|
-
end
|
730
|
-
|
731
|
-
```
|
732
|
-
## Ducalis::ProtectedScopeCop
|
733
|
-
|
734
|
-
Seems like you are using `find` on non-protected scope. Potentially it could lead to unauthorized access. It's better to call `find` on authorized resources scopes.
|
735
|
-
Example:
|
736
|
-
|
737
|
-
```ruby
|
738
|
-
current_group.employees.find(params[:id])
|
739
|
-
# better then
|
740
|
-
Employee.find(params[:id])
|
741
|
-
```
|
742
|
-
|
743
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if somewhere AR search was called on not protected scope
|
744
|
-
```ruby
|
745
|
-
Group.find(8)
|
746
|
-
```
|
747
|
-
|
748
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if AR search was called even for chain of calls
|
749
|
-
```ruby
|
750
|
-
Group.includes(:profiles).find(8)
|
751
|
-
```
|
752
|
-
|
753
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if AR search was called with find_by id
|
754
|
-
```ruby
|
755
|
-
Group.includes(:profiles).find_by(id: 8)
|
756
|
-
```
|
757
|
-
|
758
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if AR search was called on unnamespaced constant
|
759
|
-
```ruby
|
760
|
-
::Group.find(8)
|
761
|
-
```
|
762
|
-
|
763
|
-
![](https://placehold.it/10/f03c15/000000?text=+) ignores where statements and still raises error
|
764
|
-
```ruby
|
765
|
-
Group.includes(:profiles).where(name: "John").find(8)
|
766
|
-
```
|
767
|
-
|
768
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores find method with passed block
|
769
|
-
```ruby
|
770
|
-
MAPPING.find { |x| x == 42 }
|
771
|
-
```
|
772
|
-
|
773
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores find method with passed multiline block
|
774
|
-
```ruby
|
775
|
-
|
776
|
-
MAPPING.find do |x|
|
777
|
-
x == 42
|
778
|
-
end
|
779
|
-
|
780
|
-
```
|
781
|
-
## Ducalis::RaiseWithoutErrorClass
|
782
|
-
|
783
|
-
It's better to add exception class as raise argument. It will make easier to catch and process it later.
|
784
|
-
|
785
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises when `raise` called without exception class
|
786
|
-
```ruby
|
787
|
-
raise "Something went wrong"
|
788
|
-
```
|
789
|
-
|
790
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises when `raise` called without arguments
|
791
|
-
```ruby
|
792
|
-
raise
|
793
|
-
```
|
794
|
-
|
795
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores when `raise` called with exception class
|
796
|
-
```ruby
|
797
|
-
raise StandardError, "Something went wrong"
|
798
|
-
```
|
799
|
-
|
800
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores when `raise` called with exception instance
|
801
|
-
```ruby
|
802
|
-
raise StandardError.new("Something went wrong")
|
803
|
-
```
|
804
|
-
## Ducalis::RegexCop
|
805
|
-
|
806
|
-
It's better to move regex to constants with example instead of direct using it. It will allow you to reuse this regex and provide instructions for others.
|
807
|
-
|
808
|
-
```ruby
|
809
|
-
CONST_NAME = %<constant>s # "%<example>s"
|
810
|
-
%<fixed_string>s
|
811
|
-
```
|
812
|
-
Available regexes are:
|
813
|
-
`/[[:alnum:]]/`, `/[[:alpha:]]/`, `/[[:blank:]]/`, `/[[:cntrl:]]/`, `/[[:digit:]]/`, `/[[:graph:]]/`, `/[[:lower:]]/`, `/[[:print:]]/`, `/[[:punct:]]/`, `/[[:space:]]/`, `/[[:upper:]]/`, `/[[:xdigit:]]/`, `/[[:word:]]/`, `/[[:ascii:]]/`
|
814
|
-
|
815
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if somewhere in code used regex which is not moved to const
|
816
|
-
```ruby
|
817
|
-
|
818
|
-
name = "john"
|
819
|
-
puts "hi" if name =~ /john/
|
820
|
-
|
821
|
-
```
|
822
|
-
|
823
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores matching constants
|
824
|
-
```ruby
|
825
|
-
|
826
|
-
REGEX = /john/
|
827
|
-
name = "john"
|
828
|
-
puts "hi" if name =~ REGEX
|
829
|
-
|
830
|
-
```
|
831
|
-
|
832
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores named ruby constants
|
833
|
-
```ruby
|
834
|
-
|
835
|
-
name = "john"
|
836
|
-
puts "hi" if name =~ /[[:alpha:]]/
|
837
|
-
|
838
|
-
```
|
839
|
-
|
840
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores dynamic regexs
|
841
|
-
```ruby
|
842
|
-
|
843
|
-
name = "john"
|
844
|
-
puts "hi" if name =~ /.{#{name.length}}/
|
845
|
-
|
846
|
-
```
|
847
|
-
## Ducalis::RestOnlyCop
|
848
|
-
|
849
|
-
It's better for controllers to stay adherent to REST:
|
850
|
-
http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/.
|
851
|
-
[About RESTful architecture](<https://confreaks.tv/videos/railsconf2017-in-relentless-pursuit-of-rest>)
|
852
|
-
|
853
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for controllers with non-REST methods
|
854
|
-
```ruby
|
855
|
-
|
856
|
-
class ProductsController < ApplicationController
|
857
|
-
def index; end
|
858
|
-
def recalculate; end
|
859
|
-
end
|
860
|
-
|
861
|
-
```
|
862
|
-
|
863
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores controllers with private non-REST methods
|
864
|
-
```ruby
|
865
|
-
|
866
|
-
class ProductsController < ApplicationController
|
867
|
-
def index; end
|
868
|
-
|
869
|
-
private
|
870
|
-
|
871
|
-
def recalculate; end
|
872
|
-
end
|
873
|
-
|
874
|
-
```
|
875
|
-
|
876
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores controllers with only REST methods
|
877
|
-
```ruby
|
878
|
-
|
879
|
-
class ProductsController < ApplicationController
|
880
|
-
def index; end
|
881
|
-
def show; end
|
882
|
-
def new; end
|
883
|
-
def edit; end
|
884
|
-
def create; end
|
885
|
-
def update; end
|
886
|
-
def destroy; end
|
887
|
-
end
|
888
|
-
|
889
|
-
```
|
890
|
-
|
891
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores non-controllers with non-REST methods
|
892
|
-
```ruby
|
893
|
-
|
894
|
-
class PriceStore
|
895
|
-
def index; end
|
896
|
-
def recalculate; end
|
897
|
-
end
|
898
|
-
|
899
|
-
```
|
900
|
-
## Ducalis::RubocopDisable
|
901
|
-
|
902
|
-
Please, do not suppress RuboCop metrics, may be you can introduce some refactoring or another concept.
|
903
|
-
|
904
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises on RuboCop disable comments
|
905
|
-
```ruby
|
906
|
-
|
907
|
-
# rubocop:disable Metrics/ParameterLists
|
908
|
-
def calculate(five, args, at, one, list); end
|
909
|
-
|
910
|
-
```
|
911
|
-
|
912
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores comment without RuboCop disabling
|
913
|
-
```ruby
|
914
|
-
|
915
|
-
# some meaningful comment
|
916
|
-
def calculate(five, args, at, one, list); end
|
917
|
-
|
918
|
-
```
|
919
|
-
## Ducalis::StandardMethods
|
920
|
-
|
921
|
-
Please, be sure that you really want to redefine standard ruby methods.
|
922
|
-
You should know what are you doing and all consequences.
|
923
|
-
|
924
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises if use redefines default ruby methods
|
925
|
-
```ruby
|
926
|
-
|
927
|
-
def to_s
|
928
|
-
"my version"
|
929
|
-
end
|
930
|
-
|
931
|
-
```
|
932
|
-
|
933
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores if use defines simple ruby methods
|
934
|
-
```ruby
|
935
|
-
|
936
|
-
def present
|
937
|
-
"my version"
|
938
|
-
end
|
939
|
-
|
940
|
-
```
|
941
|
-
## Ducalis::StringsInActiverecords
|
942
|
-
|
943
|
-
Please, do not use strings as arguments for %<method_name>s argument. It's hard to test, grep sources, code highlighting and so on. Consider using of symbols or lambdas for complex expressions.
|
944
|
-
|
945
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for string if argument
|
946
|
-
```ruby
|
947
|
-
|
948
|
-
before_save :set_full_name,
|
949
|
-
if: 'name_changed? || postfix_name_changed?'
|
950
|
-
|
951
|
-
```
|
952
|
-
|
953
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores lambda if argument
|
954
|
-
```ruby
|
955
|
-
validates :file, if: -> { remote_url.blank? }
|
956
|
-
```
|
957
|
-
## Ducalis::TooLongWorkers
|
958
|
-
|
959
|
-
Seems like your worker is doing too much work, consider of moving business logic to service object. As rule, workers should have only two responsibilities:
|
960
|
-
- __Model materialization__: As async jobs working with serialized attributes it's nescessary to cast them into actual objects.
|
961
|
-
- __Errors handling__: Rescue errors and figure out what to do with them.
|
962
|
-
|
963
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for a class with more than 5 lines
|
964
|
-
```ruby
|
965
|
-
class TestWorker
|
966
|
-
a = 1
|
967
|
-
a = 2
|
968
|
-
a = 3
|
969
|
-
a = 4
|
970
|
-
a = 5
|
971
|
-
a = 6
|
972
|
-
end
|
973
|
-
```
|
974
|
-
|
975
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores non-worker classes
|
976
|
-
```ruby
|
977
|
-
class StrangeClass
|
978
|
-
a = 1
|
979
|
-
a = 2
|
980
|
-
a = 3
|
981
|
-
a = 4
|
982
|
-
a = 5
|
983
|
-
a = 6
|
984
|
-
end
|
985
|
-
```
|
986
|
-
|
987
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) accepts a class with 5 lines
|
988
|
-
```ruby
|
989
|
-
class TestWorker
|
990
|
-
a = 1
|
991
|
-
a = 2
|
992
|
-
a = 3
|
993
|
-
a = 4
|
994
|
-
a = 5
|
995
|
-
end
|
996
|
-
```
|
997
|
-
|
998
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) accepts a class with less than 5 lines
|
999
|
-
```ruby
|
1000
|
-
class TestWorker
|
1001
|
-
a = 1
|
1002
|
-
a = 2
|
1003
|
-
a = 3
|
1004
|
-
a = 4
|
1005
|
-
end
|
1006
|
-
```
|
1007
|
-
|
1008
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) accepts empty classes
|
1009
|
-
```ruby
|
1010
|
-
class TestWorker
|
1011
|
-
end
|
1012
|
-
```
|
1013
|
-
|
1014
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) also counts commented lines
|
1015
|
-
```ruby
|
1016
|
-
class TestWorker
|
1017
|
-
a = 1
|
1018
|
-
#a = 2
|
1019
|
-
a = 3
|
1020
|
-
#a = 4
|
1021
|
-
a = 5
|
1022
|
-
a = 6
|
1023
|
-
end
|
1024
|
-
```
|
1025
|
-
## Ducalis::UncommentedGem
|
1026
|
-
|
1027
|
-
Please, add comment why are you including non-realized gem version for %<gem>s.
|
1028
|
-
It will increase [bus-factor](<https://en.wikipedia.org/wiki/Bus_factor>).
|
1029
|
-
|
1030
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for gem from github without comment
|
1031
|
-
```ruby
|
1032
|
-
|
1033
|
-
gem 'pry', '~> 0.10', '>= 0.10.0'
|
1034
|
-
gem 'rake', '~> 12.1'
|
1035
|
-
gem 'rspec', git: 'https://github.com/rspec/rspec'
|
1036
|
-
|
1037
|
-
```
|
1038
|
-
|
1039
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores for gem from github with comment
|
1040
|
-
```ruby
|
1041
|
-
|
1042
|
-
gem 'pry', '~> 0.10', '>= 0.10.0'
|
1043
|
-
gem 'rake', '~> 12.1'
|
1044
|
-
gem 'rspec', github: 'rspec/rspec' # new non released API
|
1045
|
-
|
1046
|
-
```
|
1047
|
-
## Ducalis::UnlockedGem
|
1048
|
-
|
1049
|
-
It's better to lock gem versions explicitly with pessimistic operator (~>).
|
1050
|
-
|
1051
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for gem without version
|
1052
|
-
```ruby
|
1053
|
-
gem 'pry'
|
1054
|
-
```
|
1055
|
-
|
1056
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores gems with locked versions
|
1057
|
-
```ruby
|
1058
|
-
|
1059
|
-
gem 'pry', '~> 0.10', '>= 0.10.0'
|
1060
|
-
gem 'rake', '~> 12.1'
|
1061
|
-
gem 'thor', '= 0.20.0'
|
1062
|
-
gem 'rspec', github: 'rspec/rspec'
|
1063
|
-
|
1064
|
-
```
|
1065
|
-
## Ducalis::UselessOnly
|
1066
|
-
|
1067
|
-
Seems like there is no any reason to keep before filter only for one action. Maybe it will be better to inline it?
|
1068
|
-
|
1069
|
-
```ruby
|
1070
|
-
before_filter :do_something, only: %i[index]
|
1071
|
-
def index; end
|
1072
|
-
|
1073
|
-
# to
|
1074
|
-
|
1075
|
-
def index
|
1076
|
-
do_something
|
1077
|
-
end
|
1078
|
-
```
|
1079
|
-
|
1080
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for `before_filters` with only one method as array
|
1081
|
-
```ruby
|
1082
|
-
|
1083
|
-
class ProductsController < ApplicationController
|
1084
|
-
before_filter :update_cost, only: [:index]
|
1085
|
-
|
1086
|
-
def index; end
|
1087
|
-
|
1088
|
-
private
|
1089
|
-
|
1090
|
-
def update_cost; end
|
1091
|
-
end
|
1092
|
-
|
1093
|
-
```
|
1094
|
-
|
1095
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for `before_filters` with only one method as keyword array
|
1096
|
-
```ruby
|
1097
|
-
|
1098
|
-
class ProductsController < ApplicationController
|
1099
|
-
before_filter :update_cost, only: %i[index]
|
1100
|
-
|
1101
|
-
def index; end
|
1102
|
-
|
1103
|
-
private
|
1104
|
-
|
1105
|
-
def update_cost; end
|
1106
|
-
end
|
1107
|
-
|
1108
|
-
```
|
1109
|
-
|
1110
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for `before_filters` with many actions and only one method
|
1111
|
-
```ruby
|
1112
|
-
|
1113
|
-
class ProductsController < ApplicationController
|
1114
|
-
before_filter :update_cost, :load_me, only: %i[index]
|
1115
|
-
|
1116
|
-
def index; end
|
1117
|
-
|
1118
|
-
private
|
1119
|
-
|
1120
|
-
def update_cost; end
|
1121
|
-
def load_me; end
|
1122
|
-
end
|
1123
|
-
|
1124
|
-
```
|
1125
|
-
|
1126
|
-
![](https://placehold.it/10/f03c15/000000?text=+) raises for `before_filters` with only one method as argument
|
1127
|
-
```ruby
|
1128
|
-
|
1129
|
-
class ProductsController < ApplicationController
|
1130
|
-
before_filter :update_cost, only: :index
|
1131
|
-
|
1132
|
-
def index; end
|
1133
|
-
|
1134
|
-
private
|
1135
|
-
|
1136
|
-
def update_cost; end
|
1137
|
-
end
|
1138
|
-
|
1139
|
-
```
|
1140
|
-
|
1141
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `before_filters` without arguments
|
1142
|
-
```ruby
|
1143
|
-
|
1144
|
-
class ProductsController < ApplicationController
|
1145
|
-
before_filter :update_cost
|
1146
|
-
|
1147
|
-
def index; end
|
1148
|
-
|
1149
|
-
private
|
1150
|
-
|
1151
|
-
def update_cost; end
|
1152
|
-
end
|
1153
|
-
|
1154
|
-
```
|
1155
|
-
|
1156
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `before_filters` with `only` and many arguments
|
1157
|
-
```ruby
|
1158
|
-
|
1159
|
-
class ProductsController < ApplicationController
|
1160
|
-
before_filter :update_cost, only: %i[index show]
|
1161
|
-
|
1162
|
-
def index; end
|
1163
|
-
def show; end
|
1164
|
-
|
1165
|
-
private
|
1166
|
-
|
1167
|
-
def update_cost; end
|
1168
|
-
end
|
1169
|
-
|
1170
|
-
```
|
1171
|
-
|
1172
|
-
![](https://placehold.it/10/2cbe4e/000000?text=+) ignores `before_filters` with `except` and one argument
|
1173
|
-
```ruby
|
1174
|
-
|
1175
|
-
class ProductsController < ApplicationController
|
1176
|
-
before_filter :update_cost, except: %i[index]
|
1177
|
-
|
1178
|
-
def index; end
|
1179
|
-
|
1180
|
-
private
|
1181
|
-
|
1182
|
-
def update_cost; end
|
1183
|
-
end
|
1184
|
-
|
1185
|
-
```
|