PageRenderTime 24ms CodeModel.GetById 15ms app.highlight 7ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/rails_best_practices/reviews/use_model_association_review.rb

http://github.com/flyerhzm/rails_best_practices
Ruby | 66 lines | 34 code | 5 blank | 27 comment | 3 complexity | 2b6ae4ade122521a652cc5e8a2e84c28 MD5 | raw file
 1# frozen_string_literal: true
 2
 3module RailsBestPractices
 4  module Reviews
 5    # review a controller file to make sure to use model association instead of foreign key id assignment.
 6    #
 7    # See the best practice details here https://rails-bestpractices.com/posts/2010/07/19/use-model-association/
 8    #
 9    # Implementation:
10    #
11    # Review process:
12    #   check model define nodes in all controller files,
13    #   if there is an attribute assignment node with message xxx_id=,
14    #   and after it, there is a call node with message "save" or "save!",
15    #   and the receivers of attribute assignment node and call node are the same,
16    #   then model association should be used instead of xxx_id assignment.
17    class UseModelAssociationReview < Review
18      interesting_nodes :def
19      interesting_files CONTROLLER_FILES
20      url 'https://rails-bestpractices.com/posts/2010/07/19/use-model-association/'
21
22      # check method define nodes to see if there are some attribute assignments that can use model association instead.
23      #
24      # it will check attribute assignment node with message xxx_id=, and call node with message "save" or "save!"
25      #
26      # 1. if there is an attribute assignment node with message xxx_id=,
27      #    then remember the receiver of attribute assignment node.
28      # 2. after assignment, if there is a call node with message "save" or "save!",
29      #    and the receiver of call node is one of the receiver of attribute assignment node,
30      #    then the attribute assignment should be replaced by using model association.
31      add_callback :start_def do |node|
32        @assignments = {}
33        node.recursive_children do |child|
34          case child.sexp_type
35          when :assign
36            attribute_assignment(child)
37          when :call
38            call_assignment(child)
39          end
40        end
41        @assignments = nil
42      end
43
44      private
45
46      # check an attribute assignment node, if its message is xxx_id,
47      # then remember the receiver of the attribute assignment in @assignments.
48      def attribute_assignment(node)
49        if node.left_value.message.is_a?(Sexp) && node.left_value.message.to_s =~ /_id$/
50          receiver = node.left_value.receiver.to_s
51          @assignments[receiver] = true
52        end
53      end
54
55      # check a call node with message "save" or "save!",
56      # if the receiver of call node exists in @assignments,
57      # then the attribute assignment should be replaced by using model association.
58      def call_assignment(node)
59        if ['save', 'save!'].include? node.message.to_s
60          receiver = node.receiver.to_s
61          add_error "use model association (for #{receiver})" if @assignments[receiver]
62        end
63      end
64    end
65  end
66end