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