Ruby on Rails - Самые распространенные ошибки новичка. Часть 2 - Контроллеры

Всем привет. Продолжаем говорить о стандартных ошибках начинающих Ruby on Rails программистов.

  1. Модели и база данных (11 ошибок всего, из них 4 важных)
  2. Контроллеры (6 ошибок всего, из них 3 важных)
  3. The Ruby Way и качество кода (15 ошибок всего, из них 5 важных)
  4. Общие ошибки без категории (8 ошибок всего, из них 4 важные)

Немного об оформлении. Все ошибки разделены на 4ре категории. Кроме того, есть Особо важные ошибки выделенные красным цветом. Эти ошибки могут критично сказаться на работе проекта и команды.

Вторая часть посвящена контроллерам. Говорить о том, что контроллеры должны быть тонкими не буду, надеюсь, об этом и так все знают.  Так как контроллер - не особо сложная сущность, то и ошибок тут не особо много.  Поехали..

Итак, новички:

Привыкли использовать инстанс переменные в контроллерах

Очень часто в контроллерах все переменные объявляют как инстанс только по привычке, даже если в этом нет необходимости. Проблема инстанс переменных в том, что если вы опечатаетесь, то не получите сообщение об ошибке.
Хорошим тоном, кстати, является использовать только одну-две инстанс переменные на экшен. Нужно больше, группируйте в Presenter.


################################
##       Это плохо!           ##
################################
  
  class API::ArticlesController
   def index
     @articles = Article.all
     render json: @articles, status: 200
   end
  end

################################
##       А вот это хорошо     ##
################################

  class API::ArticlesController
   def index
     articles = Article.all
     render json: articles, status: 200
   end
  end
Не указывают HTTP статус ответа в API контроллерах

При разработке API всегда нужно указывать статус ответа, чтобы клиентское приложение могло понять, что произошло, еще до обработки тела ответа.


################################
##       Это плохо!           ##
################################
  
  class API::ArticlesController
    def create
      article = Article.new(article_params)
      
      if article.save
        render json: article
      else
        render json: { errors: article.errors.full_messages }
      end  
    end
  end

################################
##       А вот это хорошо     ##
################################

  class API::ArticlesController
    def create
      article = Article.new(article_params)
      
      if article.save
        render json: article, status: 200
      else
        render json: { errors: article.errors.full_messages }, status: 422
      end  
    end
  end
Инициализируют или ищут объекты без связи с родителем


В контроллере очень часто доступен родитель для текущего объекта. Например, в личном кабинете пользователь должен редактировать только свои объекты. Уроки, должны искаться только в контексте курса.
Использование этой простой практики повышает стабильность приложения и немного его безопасность. Я даже не могу описать, насколько этот подход качественнее и скольких ошибок он помогает избегать. Кстати у меня пример с личным кабинетом, но про этот же подход не стоит забывать, и когда Вы просто используете nested routes. Нашли родителя, а уже от него - дочерний объект.


################################
##       Это плохо!           ##
################################
  
  
  class Users::CoursesController
    def edit
      @course = Course.find(params[:id])
    end
  end

  class Users::LessonsController
    def new
      @lesson = Lesson.new(lesson_params)
    end
  end

################################
##       А вот это хорошо     ##
################################

  class Users::CoursesController
    def edit
      @course = current_user.courses.find(params[:id])
    end
  end

  class Users::LessonsController
    def new
      course = current_user.courses.find(params[:course_id])
      @lesson = course.lessons.build(lesson_params)
    end
  end
Используют инстанс переменные в паршиалах

Изначально это кажется безобидным, но с разрастанием проекта превращается в кашу. Разобрать, откуда появилась какая переменная очень сложно. Поддерживать все зависимости становится нереально.


################################
##       Это плохо!           ##
################################
  
  
  = render 'form'

  # _form.html.haml:
  = simple_form_for [:users, @course] do |f|
    - # some form

################################
##       А вот это хорошо     ##
################################

  = render 'form', course: course

  # _form.html.haml:
  = simple_form_for [:users, course] do |f|
    - # some form
Не используют before_action

Экшены контроллера должны содержать только свою непосредственную логику. Если Вам нужно выполнить какие-то предварительные действия, либо выполнить проверку на права доступа у пользователя - это должно быть в before_action. Кроме того, это позволит избежать дублирования кода.


################################
##       Это плохо!           ##
################################
  
  
  class Users::CourseController
    def update
      if current_user.has_role?(‘trainer’)
        @course = current_user.courses.find(params[:id])

        if @course.save
          redirect_to courses_path
        else
          render :edit
        end
      else
        redirect_to root_path, notice: ‘You are not authorized’
      end
    end
  end

################################
##       А вот это хорошо     ##
################################

  class Users::CourseController
    before_action :ensure_user_has_access
    before_action :find_course

    def update
      if @course.save
        redirect_to courses_path
      else
        render :edit
      end
    end

    private

    def ensure_user_has_access
      unless current_user.has_role?(‘trainer’)
        redirect_to root_path, notice: ‘You are not authorized’
      end 
    end

    def find_course
      @course = current_user.courses.find(params[:id])
    end
  end
Не пользуются хелперами

Хелперы позволяют очень сильно упростить себе жизнь. Например, в приложении очень часто приходится использовать remote формы. Можно дописывать этот параметр к каждой форме, а можно...


################################
##   Упрощаем себе жизнь      ##
################################

  module RemoteFormHelper
    def remote_form(path, &block)
      simple_form_for path, remote: true  do |f|
        yield f
        concat f.submit 'Save',       class: 'ui save green button'
        concat link_to 'Cancel', '#', class: 'ui cancel button'
      end
    end
  end

Второе применение хелперов - сокрытие логики из вьюх

 С контроллерами пока что все, если есть что добавить - присылайте в комментариях. 

Продолжение следует!

Комментарии