ducalis 0.5.14 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +8 -10
  3. data/README.md +2 -4
  4. data/Rakefile +0 -5
  5. data/bin/ducalis +8 -3
  6. data/config/.ducalis.yml +20 -1
  7. data/ducalis.gemspec +0 -1
  8. data/lib/ducalis.rb +7 -0
  9. data/lib/ducalis/cli.rb +80 -19
  10. data/lib/ducalis/commentators/console.rb +1 -1
  11. data/lib/ducalis/cops/black_list_suffix.rb +3 -0
  12. data/lib/ducalis/cops/callbacks_activerecord.rb +7 -16
  13. data/lib/ducalis/cops/case_mapping.rb +8 -3
  14. data/lib/ducalis/cops/controllers_except.rb +0 -8
  15. data/lib/ducalis/cops/data_access_objects.rb +28 -0
  16. data/lib/ducalis/cops/enforce_namespace.rb +28 -0
  17. data/lib/ducalis/cops/evlis_overusing.rb +30 -0
  18. data/lib/ducalis/cops/extensions/type_resolving.rb +52 -0
  19. data/lib/ducalis/cops/fetch_expression.rb +80 -0
  20. data/lib/ducalis/cops/module_like_class.rb +1 -0
  21. data/lib/ducalis/cops/multiple_times.rb +50 -0
  22. data/lib/ducalis/cops/options_argument.rb +5 -22
  23. data/lib/ducalis/cops/possible_tap.rb +4 -0
  24. data/lib/ducalis/cops/preferable_methods.rb +3 -1
  25. data/lib/ducalis/cops/private_instance_assign.rb +4 -9
  26. data/lib/ducalis/cops/protected_scope_cop.rb +4 -0
  27. data/lib/ducalis/cops/public_send.rb +18 -0
  28. data/lib/ducalis/cops/regex_cop.rb +36 -6
  29. data/lib/ducalis/cops/rest_only_cop.rb +4 -11
  30. data/lib/ducalis/cops/too_long_workers.rb +3 -8
  31. data/lib/ducalis/cops/uncommented_gem.rb +13 -1
  32. data/lib/ducalis/cops/useless_only.rb +6 -8
  33. data/lib/ducalis/documentation.rb +28 -18
  34. data/lib/ducalis/passed_args.rb +2 -2
  35. data/lib/ducalis/version.rb +1 -1
  36. metadata +9 -17
  37. 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 triggered
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 worker_class?(node)
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
- IGNORE_WORDS = ['[bugfix]'].freeze
69
+ RULE_WORD = '[rule]'
64
70
 
65
71
  def call
66
- Dir['./lib/ducalis/cops/*.rb'].sort.map do |f|
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) # description
82
+ message(klass) + "\n" # description
77
83
  ] +
78
84
  specs.map do |(it, code)|
79
85
  [
80
- "\n#{color(it)} #{it}", # case description
81
- "```ruby\n#{code.join("\n")}\n```" # code example
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 color(it)
87
- it.include?(SIGNAL_WORD) ? RED_SQUARE : GREEN_SQUARE
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.reject(&method(:ignored?))
114
+ end.cases.select(&method(:allowed?))
105
115
  end
106
116
 
107
- def ignored?(example)
117
+ def allowed?(example)
108
118
  desc, _code = example
109
- IGNORE_WORDS.any? { |word| desc.include?(word) }
119
+ desc.include?(RULE_WORD)
110
120
  end
111
121
 
112
122
  def klass_const_for(f)
@@ -4,10 +4,10 @@ module Ducalis
4
4
  module PassedArgs
5
5
  module_function
6
6
 
7
- RUBOCOP_FLAGS = %w(-D).freeze
7
+ HELP_FLAGS = ['-h', '-?', '--help'].freeze
8
8
 
9
9
  def help_command?
10
- ARGV.any? { |arg| (Thor::HELP_MAPPINGS - RUBOCOP_FLAGS).include?(arg) }
10
+ ARGV.any? { |arg| HELP_FLAGS.include?(arg) }
11
11
  end
12
12
 
13
13
  def ci_mode?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ducalis
4
- VERSION = '0.5.14'
4
+ VERSION = '0.6.0'
5
5
  end
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.5.14
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: 2017-12-22 00:00:00.000000000 Z
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
@@ -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
- ```