Вышел релиз Rails 4.1. Некоторые тонкости переезда

    image
    8 апреля в официальном блоге Ruby on Rails появилось сообщение об официальном выходе Rails 4.1. Весь функционал уместился в 5200 коммитов.

    На хабре уже был обзор бета версии. Также можно почитать Release notes и A Guide for Upgrading Ruby on Rails.

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


    Active Record Enums


    class Conversation < ActiveRecord::Base
      enum status: [ :active, :archived ]
    end
    
    # conversation.update! status: 0
    conversation.active!
    conversation.active? # => true
    conversation.status  # => "active"
    
    # conversation.update! status: 1
    conversation.archived!
    conversation.archived? # => true
    conversation.status    # => "archived"
    
    # conversation.update! status: 1
    conversation.status = "archived"
    
    # conversation.update! status: nil
    conversation.status = nil
    conversation.status.nil? # => true
    conversation.status      # => nil
    

    Теперь мы имеем вот такой синтаксис для перечислений. Очень вкусно, очень удобно, но важно понимать несколько моментов.

    1. Перечисление сохраняется как целое число в базе, но может быть получено по имени. (Enum values map to integers in the database, but can be queried by name).
    Вот так будет выглядеть миграция примера выше:
    create_table :conversations do |t|
      t.column :status, :integer
    end
    

    При этом в SQL-запросах тоже придется использовать целые числа:
    where('status <> ? OR status <> ?', 0, 1)
    where('status <> ? OR status <> ?', STATUS[:resolved], STATUS[:rejected])
    Обратите внимание, что в даном случае STATUS — это константа класса, добавляемая макросом автоматически при создании перечисления.

    2. Перечисление не использует ENUM тип, реализованный в некоторых в базах данных. Как следствие — нельзя изменять порядок значений в перечислении после создания записей в базе. Это приведет к путанице и конфликтам. Нельзя также просто удалить ненужное значение. Для этого соответствие между строковыми и численными значением должно быть задано явно.
    class Bug < ActiveRecord::Base
      enum status: {
        new: 0,
        #removed status with id=1
        in_progress: 2,
        resolved: 3,
        rejected: 4,
        reopened: 5
      }
    end
    

    Получить этот хэш можно через Bug.statuses

    3. Есть ограничения по именам значений перечисления.
    Нельзя называть значения enum'ов именами существующих scopes, ассоциаций (что понятно) и уже существующих перечислений в рамках одной модели (что неочевидно). Например:
    class Bug < ActiveRecord::Base
      enum status: [ :new, :closed ]
      enum code_review_status: [ :new, :finished ] # Так делать нельзя
    end
    


    Новое поведение Default scopes


    Теперь default_scope по умолчанию влияет на все остальные scopes, если явно не указано обратное.
    class User < ActiveRecord::Base
      default_scope { where state: 'pending' }
      scope :active, -> { where state: 'active' }
      scope :inactive, -> { where state: 'inactive' }
    end
    

    Теперь User.active будет эквивалентно User.where(state: 'pending').where(state: 'active'). Т.е. по умолчанию все scopes цепляются(chains) к default_scope.
    Чтобы избежать этого нужно явно «отцепить» scope от default_scope с помощью unscope, except, rewhere:
    class User < ActiveRecord::Base
      default_scope { where state: 'pending' }
      scope :active, -> { unscope(where: :state).where(state: 'active') }
      scope :inactive, -> { rewhere state: 'inactive' }
    end
    


    Расширенная CSRF защита.


    Теперь по умолчанию CSRF защита включена и на get запросах с форматом .js.
    Для честного разработчика есть два прикладных следствия из этого.
    Во-первых, валятся все тесты, которые проверяли формирования правильного JS кода. Бороться с этим несложно.
    Вместо
    post :create, format: :js
    Пишем
    xhr :post, :create, format: :js
    А во-вторых, если все-таки надо разрешить сторонним сайтам запрашивать js код, то надо явно указать это, добавив фильтр в контроллер:
    skip_before_filter :verify_authenticity_token, only: [:stats, :visitor]
    

    Ну и в каждом action надо добавить в заголовок ответа:
    response.headers['Access-Control-Allow-Origin'] = '*'
    

    Либо вместо звездочки указать имя сайта, которому разрешен доступ.



    Пока это всё, с чем пришлось повозиться самому. Конечно же, функционал нового релиза Rails 4.1 гораздо шире. Но там как раз все вроде понятно, про это уже писали, к тому же, описание новой функциональности как всегда на высоте.
    Но на всякий случай приведу список остальных Major Features:
    • Добавлен прелоадер Spring по умолчанию в новые проекты
    • Добавлен файл config/secrets.yml вместе с функционалом для хранения секретов приложений.
    • Action Pack Variants — возможность использования разных ответов для различных типов устройств (tablet, desktop, phone, etc.)
    • Action Mailer Previews — интеграция gem'а MailView в Rails — удобная работа с шаблонами писем.
    • Message Verifiers — обмен и проверка подлинности важных сообщений.
    • Module#concerning — удобное разделение ответственности между классами (честно говоря, я пока не нахожу ситуации, когда бы мне понадобилась эта функциональность)

    Официально прекращена поддержка MySQL Server 4.1 (это если еще кто-то, кроме меня вынужден работать с этим раритетом). В реальности пока еще работает.
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 21
    • +2
      Ждем публикаций от Егора chikey?
      • +1
        Теперь можно путешествовать во времени без Timecop. github.com/rails/rails/pull/12824

        Module#concerning это как ActiveSupport::Concern, но в том же файле, чтобы не нужно было искать что где реализовано. Использовать его нужно когда вы не собираетесь includ'ить concern в несколько моделей.
        • +5
          Так же замечательно обновился.
          Честно говоря, я бы оценил релиз не самым интересным, вот почему:
          — Spring все могли использовать и до этого, кто-то даже предпочитал Zeus, я понимаю DHH, что он пользует это при разработке Basecamp, но все же история похожа на Turbolinks, хотя в целом диктат в таких вещах понятен, он приводит хотя бы к стандарту, но какой ценой
          — функционал Enums не то, что бесполезен в сравненнии с State Machine, но и вреден, например задали список значений, заполнили базу, а потом передумали или случайно изменили порядок, приват каша
          — Mail Preview так же похоже на историю со Spring, есть замечательно работающий mailcatcher который удобен и намного шире функционалом, призадуматься бы как его привнести в разработку в Rails по умолчанию или не тащить ничего такого лишнего
          — secrets.yml вообще выглядит как наивная шутка, особенно стоит ожидать фэйлов в ближайшем времени на поске по github кто обновит проекты с версий < 4.1 и забудет в gitignore добавить

          — самое отличное обновление это default_scope давно пора было это исправить и сделать нормально, спасибо.

          Ну и при обновлении был только маленький нюанс с State Machine, но он легко патчится github.com/pluginaweek/state_machine/issues/251
          • +2
            Мне кажется enums неправильно рассматривать как аналог state machine, он скорее был введен для поддержки однородных списков, которым не нужны колбеки, валидации и транзакционность. Например такое поле как пол (мужской, женский), будет очень удобно реализовать через enums.
            • +1
              Наверное было бы удобно, если бы не легкость сломать это все.

              было:
              enum sex: [ :man, :woman]

              решили и добавили для Таиланда через полгода собранных отзывов:
              enum sex: [:ladyboy, :man, :woman]

              и все пошло не так.
              • +1
                человеческий фактор может поломать что угодно ;) но такая вот реализация должна свести на минимум подобные ошибки
                enum sex: {woman: 0, man: 1}
                • +2
                  И уже 50% прелести потерялось :)
                • +1
                  нет. все может работать.
                  enum sex: [:man, :woman, :ladyboy]

                  с добавлением в конец проблем никаких нет.
                  • 0
                    Добавл в начало, как раз чтобы показать ненадежность и легкость совершить ошибку, это же массив, тут даже перестановка может поломать все.
                    • 0
                      Добавление в начало массива (или изменение порядка) ломает много гемов. Например easy_roles (когда mask метод используется). Так что многие уже привыкли к такому поведению. Да и комментарий рядом написать с предупреждением никто не мешает. И code review тоже никто не отменял.
                      • 0
                        Так что многие уже привыкли к такому поведению.

                        Это я понимаю, что сломать можно, что угодно, особенно в Ruby с таким богатым инструментарием, но ведь плохоже же тянуть потенциальную проблему с таким решеним в стандартную установку Rails 4, разве нет?

                        Если что-то может сломаться, это обязательно сломается © 1-ый закон Мёрфи
                  • +2
                    У вас неправильный порядок: всякому известно, что man это единичка, а woman — нолик.
                    • 0
                      Но если уже создали так, то лучше не менять, я слышал, так можно что-то поламать :-).
                • 0
                  А зачем добавлять secrets.yml в gitignore? Как раз удобно иметь его в репозитории, чтобы избежать трюкачества при деплое. И генератор нового приложения в Rails 4.1, кстати, его в gitignore не добавляет.
                  • 0
                    Чтобы потом ключами не светить на весь github например.
                    • 0
                      Ну так для этого переменные окружения есть.
                      • 0
                        Все пошло по новой, ок, а secrets.yml для чего тогда?
                        • 0
                          Это официальный способ хранения конфируации теперь (в том числе секретных ключей, но не только). Вы смотрели на secrets.yml, который создается генератором нового приложения? Там по-умолчанию пробиты значения для test и development окружения, а для production берется из окружения (через ERB). Соответсвенно, когда разработчик берет код из репозитория, у него локально все сразу работает. При деплое тоже не надо создавать/линковать никаких файлов, достаточно выставить переменные окружения. Ну и при этом доступ к конфигурации из приложения через Rails.application.secrets. Вроде все удобно (если, конечно, в gitignore не добавлять).
                          • 0
                            ОК, ваши доводы, что если делать правильно ясны.
                            А я вам говорю, что без этого костыля можно было обходиться как и раньше используя Figaro например, как раз это и отнес к сомнительным нововведениям. А тут заведомый trap, так что давайте поживем и посмотрим сколько ключей уплывут таким образом в github, а не через окружение.
                            • 0
                              Ну вот, по-моему, основная ценность как раз в том, что теперь есть официальный способ делать это без костылей. Раньше кто-то использовал Figaro, кто-то RailsConfig, кто-то свои yaml-ы, инициалайзеры и другие костыли. Нравится вам Figaro — используйте, никто не заставляет переходить. Но для тех кто начинает новый проект на рельсах, теперь на одну проблему меньше. На мой взляд, это хорошее нововведение, а никак не костыль.
                • 0
                  Минимальная версия gem'а mysql2 теперь 0.3.13. При неподходящей версии выводит сообщение, что gem не подключен или неправильная версия.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.