rubocop-rails 2.6.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/config/default.yml +189 -6
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +12 -3
  5. data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -11
  7. data/lib/rubocop/cop/rails/action_filter.rb +10 -14
  8. data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
  9. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +148 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
  12. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
  13. data/lib/rubocop/cop/rails/application_controller.rb +3 -7
  14. data/lib/rubocop/cop/rails/application_job.rb +2 -1
  15. data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
  16. data/lib/rubocop/cop/rails/application_record.rb +2 -7
  17. data/lib/rubocop/cop/rails/arel_star.rb +41 -0
  18. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  19. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  20. data/lib/rubocop/cop/rails/belongs_to.rb +9 -18
  21. data/lib/rubocop/cop/rails/blank.rb +27 -27
  22. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
  23. data/lib/rubocop/cop/rails/content_tag.rb +20 -33
  24. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
  25. data/lib/rubocop/cop/rails/date.rb +10 -11
  26. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  27. data/lib/rubocop/cop/rails/delegate.rb +10 -10
  28. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
  29. data/lib/rubocop/cop/rails/dynamic_find_by.rb +13 -11
  30. data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
  31. data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
  32. data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
  33. data/lib/rubocop/cop/rails/exit.rb +4 -10
  34. data/lib/rubocop/cop/rails/file_path.rb +5 -4
  35. data/lib/rubocop/cop/rails/find_by.rb +13 -13
  36. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  37. data/lib/rubocop/cop/rails/find_each.rb +16 -14
  38. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
  39. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -7
  40. data/lib/rubocop/cop/rails/helper_instance_variable.rb +4 -2
  41. data/lib/rubocop/cop/rails/http_positional_arguments.rb +25 -21
  42. data/lib/rubocop/cop/rails/http_status.rb +7 -9
  43. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
  44. data/lib/rubocop/cop/rails/index_by.rb +11 -2
  45. data/lib/rubocop/cop/rails/index_with.rb +11 -2
  46. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  47. data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
  48. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
  49. data/lib/rubocop/cop/rails/link_to_blank.rb +20 -20
  50. data/lib/rubocop/cop/rails/mailer_name.rb +86 -0
  51. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  52. data/lib/rubocop/cop/rails/negate_include.rb +41 -0
  53. data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
  54. data/lib/rubocop/cop/rails/order_by_id.rb +52 -0
  55. data/lib/rubocop/cop/rails/output.rb +5 -2
  56. data/lib/rubocop/cop/rails/output_safety.rb +3 -2
  57. data/lib/rubocop/cop/rails/pick.rb +21 -15
  58. data/lib/rubocop/cop/rails/pluck.rb +56 -0
  59. data/lib/rubocop/cop/rails/pluck_id.rb +56 -0
  60. data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
  61. data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
  62. data/lib/rubocop/cop/rails/presence.rb +12 -13
  63. data/lib/rubocop/cop/rails/present.rb +30 -24
  64. data/lib/rubocop/cop/rails/rake_environment.rb +9 -11
  65. data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
  66. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
  67. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
  68. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
  69. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -3
  70. data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
  71. data/lib/rubocop/cop/rails/relative_date_constant.rb +20 -9
  72. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  73. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  74. data/lib/rubocop/cop/rails/request_referer.rb +7 -7
  75. data/lib/rubocop/cop/rails/reversible_migration.rb +82 -7
  76. data/lib/rubocop/cop/rails/safe_navigation.rb +12 -11
  77. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
  78. data/lib/rubocop/cop/rails/save_bang.rb +19 -22
  79. data/lib/rubocop/cop/rails/scope_args.rb +2 -1
  80. data/lib/rubocop/cop/rails/short_i18n.rb +74 -0
  81. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -11
  82. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
  83. data/lib/rubocop/cop/rails/time_zone.rb +22 -20
  84. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -10
  85. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
  86. data/lib/rubocop/cop/rails/unknown_env.rb +15 -4
  87. data/lib/rubocop/cop/rails/validation.rb +15 -14
  88. data/lib/rubocop/cop/rails/where_equals.rb +94 -0
  89. data/lib/rubocop/cop/rails/where_exists.rb +126 -0
  90. data/lib/rubocop/cop/rails/where_not.rb +97 -0
  91. data/lib/rubocop/cop/rails_cops.rb +22 -0
  92. data/lib/rubocop/rails/schema_loader.rb +4 -4
  93. data/lib/rubocop/rails/schema_loader/schema.rb +5 -5
  94. data/lib/rubocop/rails/version.rb +5 -1
  95. metadata +37 -9
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces that mailer names end with `Mailer` suffix.
7
+ #
8
+ # Without the `Mailer` suffix it isn't immediately apparent what's a mailer
9
+ # and which views are related to the mailer.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # class User < ActionMailer::Base
14
+ # end
15
+ #
16
+ # class User < ApplicationMailer
17
+ # end
18
+ #
19
+ # # good
20
+ # class UserMailer < ActionMailer::Base
21
+ # end
22
+ #
23
+ # class UserMailer < ApplicationMailer
24
+ # end
25
+ #
26
+ class MailerName < Base
27
+ extend AutoCorrector
28
+
29
+ MSG = 'Mailer should end with `Mailer` suffix.'
30
+
31
+ def_node_matcher :mailer_base_class?, <<~PATTERN
32
+ {
33
+ (const (const nil? :ActionMailer) :Base)
34
+ (const nil? :ApplicationMailer)
35
+ }
36
+ PATTERN
37
+
38
+ def_node_matcher :class_definition?, <<~PATTERN
39
+ (class $(const _ !#mailer_suffix?) #mailer_base_class? ...)
40
+ PATTERN
41
+
42
+ def_node_matcher :class_new_definition?, <<~PATTERN
43
+ (send (const nil? :Class) :new #mailer_base_class?)
44
+ PATTERN
45
+
46
+ def on_class(node)
47
+ class_definition?(node) do |name_node|
48
+ add_offense(name_node) do |corrector|
49
+ autocorrect(corrector, name_node)
50
+ end
51
+ end
52
+ end
53
+
54
+ def on_send(node)
55
+ return unless class_new_definition?(node)
56
+
57
+ casgn_parent = node.each_ancestor(:casgn).first
58
+ return unless casgn_parent
59
+
60
+ name = casgn_parent.children[1]
61
+ return if mailer_suffix?(name)
62
+
63
+ add_offense(casgn_parent.loc.name) do |corrector|
64
+ autocorrect(corrector, casgn_parent)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def autocorrect(corrector, node)
71
+ if node.casgn_type?
72
+ name = node.children[1]
73
+ corrector.replace(node.loc.name, "#{name}Mailer")
74
+ else
75
+ name = node.children.last
76
+ corrector.replace(node.source_range, "#{name}Mailer")
77
+ end
78
+ end
79
+
80
+ def mailer_suffix?(mailer_name)
81
+ mailer_name.to_s.end_with?('Mailer')
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies places where defining routes with `match`
7
+ # can be replaced with a specific HTTP method.
8
+ #
9
+ # Don't use `match` to define any routes unless there is a need to map multiple request types
10
+ # among [:get, :post, :patch, :put, :delete] to a single action using the `:via` option.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # match ':controller/:action/:id'
15
+ # match 'photos/:id', to: 'photos#show', via: :get
16
+ #
17
+ # # good
18
+ # get ':controller/:action/:id'
19
+ # get 'photos/:id', to: 'photos#show'
20
+ # match 'photos/:id', to: 'photos#show', via: [:get, :post]
21
+ # match 'photos/:id', to: 'photos#show', via: :all
22
+ #
23
+ class MatchRoute < Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
27
+ RESTRICT_ON_SEND = %i[match].freeze
28
+ HTTP_METHODS = %i[get post put patch delete].freeze
29
+
30
+ def_node_matcher :match_method_call?, <<~PATTERN
31
+ (send nil? :match $_ $(hash ...) ?)
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ match_method_call?(node) do |path_node, options_node|
36
+ return unless within_routes?(node)
37
+
38
+ options_node = path_node.hash_type? ? path_node : options_node.first
39
+
40
+ if options_node.nil?
41
+ register_offense(node, 'get')
42
+ else
43
+ via = extract_via(options_node)
44
+ return unless via.size == 1 && http_method?(via.first)
45
+
46
+ register_offense(node, via.first)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def register_offense(node, http_method)
54
+ add_offense(node, message: format(MSG, http_method: http_method)) do |corrector|
55
+ match_method_call?(node) do |path_node, options_node|
56
+ options_node = options_node.first
57
+
58
+ corrector.replace(node, replacement(path_node, options_node))
59
+ end
60
+ end
61
+ end
62
+
63
+ def_node_matcher :routes_draw?, <<~PATTERN
64
+ (send (send _ :routes) :draw)
65
+ PATTERN
66
+
67
+ def within_routes?(node)
68
+ node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
69
+ end
70
+
71
+ def extract_via(node)
72
+ via_pair = via_pair(node)
73
+ return %i[get] unless via_pair
74
+
75
+ _, via = *via_pair
76
+
77
+ if via.basic_literal?
78
+ [via.value]
79
+ elsif via.array_type?
80
+ via.values.map(&:value)
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ def via_pair(node)
87
+ node.pairs.find { |p| p.key.value == :via }
88
+ end
89
+
90
+ def http_method?(method)
91
+ HTTP_METHODS.include?(method.to_sym)
92
+ end
93
+
94
+ def replacement(path_node, options_node)
95
+ if path_node.hash_type?
96
+ http_method, options = *http_method_and_options(path_node)
97
+ "#{http_method} #{options.map(&:source).join(', ')}"
98
+ elsif options_node.nil?
99
+ "get #{path_node.source}"
100
+ else
101
+ http_method, options = *http_method_and_options(options_node)
102
+
103
+ if options.any?
104
+ "#{http_method} #{path_node.source}, #{options.map(&:source).join(', ')}"
105
+ else
106
+ "#{http_method} #{path_node.source}"
107
+ end
108
+ end
109
+ end
110
+
111
+ def http_method_and_options(node)
112
+ via_pair = via_pair(node)
113
+ http_method = extract_via(node).first
114
+ rest_pairs = node.pairs - [via_pair]
115
+ [http_method, rest_pairs]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `collection.exclude?(obj)`
7
+ # over `!collection.include?(obj)`.
8
+ #
9
+ # It is marked as unsafe by default because false positive will occur for
10
+ # a receiver object that do not have `exclude?` method. (e.g. `IPAddr`)
11
+ #
12
+ # @example
13
+ # # bad
14
+ # !array.include?(2)
15
+ # !hash.include?(:key)
16
+ #
17
+ # # good
18
+ # array.exclude?(2)
19
+ # hash.exclude?(:key)
20
+ #
21
+ class NegateInclude < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `.exclude?` and remove the negation part.'
25
+ RESTRICT_ON_SEND = %i[!].freeze
26
+
27
+ def_node_matcher :negate_include_call?, <<~PATTERN
28
+ (send (send $_ :include? $_) :!)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ return unless (receiver, obj = negate_include_call?(node))
33
+
34
+ add_offense(node) do |corrector|
35
+ corrector.replace(node, "#{receiver.source}.exclude?(#{obj.source})")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -16,8 +16,9 @@ module RuboCop
16
16
  # add_column :users, :name, :string, null: false, default: ''
17
17
  # add_reference :products, :category
18
18
  # add_reference :products, :category, null: false, default: 1
19
- class NotNullColumn < Cop
19
+ class NotNullColumn < Base
20
20
  MSG = 'Do not add a NOT NULL column without a default value.'
21
+ RESTRICT_ON_SEND = %i[add_column add_reference].freeze
21
22
 
22
23
  def_node_matcher :add_not_null_column?, <<~PATTERN
23
24
  (send nil? :add_column _ _ _ (hash $...))
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for places where ordering by `id` column is used.
7
+ #
8
+ # Don't use the `id` column for ordering. The sequence of ids is not guaranteed
9
+ # to be in any particular order, despite often (incidentally) being chronological.
10
+ # Use a timestamp column to order chronologically. As a bonus the intent is clearer.
11
+ #
12
+ # NOTE: Make sure the changed order column does not introduce performance
13
+ # bottlenecks and appropriate database indexes are added.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # scope :chronological, -> { order(id: :asc) }
18
+ # scope :chronological, -> { order(primary_key => :asc) }
19
+ #
20
+ # # good
21
+ # scope :chronological, -> { order(created_at: :asc) }
22
+ #
23
+ class OrderById < Base
24
+ include RangeHelp
25
+
26
+ MSG = 'Do not use the `id` column for ordering. '\
27
+ 'Use a timestamp column to order chronologically.'
28
+ RESTRICT_ON_SEND = %i[order].freeze
29
+
30
+ def_node_matcher :order_by_id?, <<~PATTERN
31
+ (send _ :order
32
+ {
33
+ (sym :id)
34
+ (hash (pair (sym :id) _))
35
+ (send _ :primary_key)
36
+ (hash (pair (send _ :primary_key) _))
37
+ })
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ add_offense(offense_range(node)) if order_by_id?(node)
42
+ end
43
+
44
+ private
45
+
46
+ def offense_range(node)
47
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -13,9 +13,12 @@ module RuboCop
13
13
  #
14
14
  # # good
15
15
  # Rails.logger.debug 'A debug message'
16
- class Output < Cop
16
+ class Output < Base
17
17
  MSG = 'Do not write to stdout. ' \
18
18
  "Use Rails's logger if you want to log."
19
+ RESTRICT_ON_SEND = %i[
20
+ ap p pp pretty_print print puts binwrite syswrite write write_nonblock
21
+ ].freeze
19
22
 
20
23
  def_node_matcher :output?, <<~PATTERN
21
24
  (send nil? {:ap :p :pp :pretty_print :print :puts} ...)
@@ -35,7 +38,7 @@ module RuboCop
35
38
  return unless (output?(node) || io_output?(node)) &&
36
39
  node.arguments?
37
40
 
38
- add_offense(node, location: :selector)
41
+ add_offense(node.loc.selector)
39
42
  end
40
43
 
41
44
  private
@@ -62,8 +62,9 @@ module RuboCop
62
62
  # safe_join([user_content, " ", content_tag(:span, user_content)])
63
63
  # # => ActiveSupport::SafeBuffer
64
64
  # # "&lt;b&gt;hi&lt;/b&gt; <span>&lt;b&gt;hi&lt;/b&gt;</span>"
65
- class OutputSafety < Cop
65
+ class OutputSafety < Base
66
66
  MSG = 'Tagging a string as html safe may be a security risk.'
67
+ RESTRICT_ON_SEND = %i[html_safe raw safe_concat].freeze
67
68
 
68
69
  def on_send(node)
69
70
  return if non_interpolated_string?(node)
@@ -72,7 +73,7 @@ module RuboCop
72
73
  looks_like_rails_raw?(node) ||
73
74
  looks_like_rails_safe_concat?(node)
74
75
 
75
- add_offense(node, location: :selector)
76
+ add_offense(node.loc.selector)
76
77
  end
77
78
  alias on_csend on_send
78
79
 
@@ -3,20 +3,26 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces the use `pick` over `pluck(...).first`.
6
+ # This cop enforces the use of `pick` over `pluck(...).first`.
7
+ #
8
+ # Using `pluck` followed by `first` creates an intermediate array, which
9
+ # `pick` avoids. When called on an Active Record relation, `pick` adds a
10
+ # limit to the query so that only one value is fetched from the database.
7
11
  #
8
12
  # @example
9
13
  # # bad
10
14
  # Model.pluck(:a).first
11
- # Model.pluck(:a, :b).first
15
+ # [{ a: :b, c: :d }].pluck(:a, :b).first
12
16
  #
13
17
  # # good
14
18
  # Model.pick(:a)
15
- # Model.pick(:a, :b)
16
- class Pick < Cop
19
+ # [{ a: :b, c: :d }].pick(:a, :b)
20
+ class Pick < Base
21
+ extend AutoCorrector
17
22
  extend TargetRailsVersion
18
23
 
19
24
  MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
25
+ RESTRICT_ON_SEND = %i[first].freeze
20
26
 
21
27
  minimum_target_rails_version 6.0
22
28
 
@@ -26,24 +32,24 @@ module RuboCop
26
32
 
27
33
  def on_send(node)
28
34
  pick_candidate?(node) do
29
- range = node.receiver.loc.selector.join(node.loc.selector)
30
- add_offense(node, location: range)
31
- end
32
- end
35
+ receiver = node.receiver
36
+ receiver_selector = receiver.loc.selector
37
+ node_selector = node.loc.selector
38
+ range = receiver_selector.join(node_selector)
33
39
 
34
- def autocorrect(node)
35
- first_range = node.receiver.source_range.end.join(node.loc.selector)
40
+ add_offense(range, message: message(receiver)) do |corrector|
41
+ first_range = receiver.source_range.end.join(node_selector)
36
42
 
37
- lambda do |corrector|
38
- corrector.remove(first_range)
39
- corrector.replace(node.receiver.loc.selector, 'pick')
43
+ corrector.remove(first_range)
44
+ corrector.replace(receiver_selector, 'pick')
45
+ end
40
46
  end
41
47
  end
42
48
 
43
49
  private
44
50
 
45
- def message(node)
46
- format(MSG, args: node.receiver.arguments.map(&:source).join(', '))
51
+ def message(receiver)
52
+ format(MSG, args: receiver.arguments.map(&:source).join(', '))
47
53
  end
48
54
  end
49
55
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `pluck` over `map`.
7
+ #
8
+ # `pluck` can be used instead of `map` to extract a single key from each
9
+ # element in an enumerable. When called on an Active Record relation, it
10
+ # results in a more efficient query that only selects the necessary key.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # Post.published.map { |post| post[:title] }
15
+ # [{ a: :b, c: :d }].collect { |el| el[:a] }
16
+ #
17
+ # # good
18
+ # Post.published.pluck(:title)
19
+ # [{ a: :b, c: :d }].pluck(:a)
20
+ class Pluck < Base
21
+ extend AutoCorrector
22
+ extend TargetRailsVersion
23
+
24
+ MSG = 'Prefer `pluck(:%<value>s)` over `%<method>s { |%<argument>s| %<element>s[:%<value>s] }`.'
25
+
26
+ minimum_target_rails_version 5.0
27
+
28
+ def_node_matcher :pluck_candidate?, <<~PATTERN
29
+ (block (send _ ${:map :collect}) (args (arg $_argument)) (send (lvar $_element) :[] (sym $_value)))
30
+ PATTERN
31
+
32
+ def on_block(node)
33
+ pluck_candidate?(node) do |method, argument, element, value|
34
+ next unless argument == element
35
+
36
+ message = message(method, argument, element, value)
37
+
38
+ add_offense(offense_range(node), message: message) do |corrector|
39
+ corrector.replace(offense_range(node), "pluck(:#{value})")
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def offense_range(node)
47
+ node.send_node.loc.selector.join(node.loc.end)
48
+ end
49
+
50
+ def message(method, argument, element, value)
51
+ format(MSG, method: method, argument: argument, element: element, value: value)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end