Всем привет!
Прошло уже около года с момента опубликования крайнего видео на моем канале. За все это время на канал подписывались люди, были запросы на новые уроки. Это очень приятно, что вы заинтересованы в том что я делаю, и я решил что стоит продолжить эту затею. Сейчас в курсе по Ruby обсуждается работа с Linux и так как эта тема все-таки выпадает из обучения языку программирования, я решил что было бы правильнее вынести это в отдельный курс, который называется Демонический Linux. Я запишу несколько уроков в этом курсе, которые необходимы для продолжения обучения Ruby, и дальше продолжу снимать уроки по Ruby. Спасибо за поддержку, за ваши пальцы вверх, чем вас больше тем выше моя мотивация.
Подписывайтесь на канал и до встречи )
]]>Изменилось поведение метода round
. Разработчики добавили возможность указать в какую сторону необходимо округлять, передав в этот
метод дополнительный параметр :half
, который может принимать значения: :even, :up, :down
.
Для примера:
1 2 |
|
Эти изменения касаются также таких методов как: floor, ceil, truncate
. В них можно передать эту опцию.
Напомню что метод gets используется для получения пользовательского ввода в вашем скрипте. Вы наверняка использовали его
в задачках по Ruby. Так вот, если пользователь вводил какие либо данные, к вам в результате приходила строка с переносом \n
Чтобы его убрать использовался метод chomp. Теперь мы можем указать опцию chomp
в методе gets
1 2 3 4 5 6 |
|
Поздравляю теперь мы можем передать путь в метод empty?
и выяснить содержит ли папка/файл/путь что-то внутри
Метод File.empty?
эквивалентен методу File.zero?
, который уже давно был в библиотеке для работы с файлами.
Как по мне довольно удобный метод добавили в класс Hash. Теперь мы можем изменять значения hash в итераторе. Например вот так:
1 2 3 4 |
|
При использовании clone
или dup
, можно теперь передать дополнительный аргумент, в котором указать будет ли объект
freeze
(недоступным для изменения).
1 2 3 4 5 6 |
|
Есть такой замечательный gem pry
. Достаточно в любом месте кода поместить binding.pry
и у вас появляется возможность
использовать консоль pry для дебага вашего приложения. В Ruby 2.4 добавлена похожая фича, только нужно указать binding.irb
.
Для регулярок добавили новый метод match?
который работает аж в 3 раза быстрее чем методы match, ===, =~
.
При использовании медленных методов, руби создавал объект класса MatchData
,
а в новом методе он не создается, за счет этого повышенная скорость работы.
1 2 3 4 5 6 7 8 |
|
1 2 3 |
|
Довольно забавная фича. Позволяет сделать именованные группы в регулярном выражении, и в последствии преобразовать это все в hash с результатами. Лучше один раз увидеть.
1 2 3 |
|
Также для похожих целей можно использовать метод values_at
, который также теперь можно применять к отматченному результату
1 2 3 |
|
Кроме именованных ключей, вы можете передать индексы групп, например 1, 3
1 2 3 |
|
Как получить массив чисел составляющих число? Раньше это можно было сделать так
Преобразовать число в строку, вызвать метод chars
, через map сделать to_i
1
|
|
Теперь стало проще:
1 2 |
|
Array класс обзавелся своими методами min, max
, который работают практически в 2 раза быстрее чем соответствующие методы
Enumerable
Довольно серьезное изменение структуры языка. В Ruby 2.4 убрали классы Fixnum и Bignum и объединили их в один Integer
1 2 3 4 5 |
|
Теперь в условиях можно использовать множественное присвоение
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Неплохие добавочки в язык, точно знаю что использовал бы transform_values
, недавно был случай когда нужно было проверить
папку на наличие в ней файлов, здесь бы пригодился метод empty?
. Вижу применение для named_captures
. Остальное также может
пригодиться, но лично для меня реже. Помимо добавления функционала были улучшения производительности. Но это уже совсем другая
история. На этом все. Подписывайтесь на канал, скоро выйдет новая порция уроков. Добавляйте в закладки
Для организации файлов служат методы - load
и require
, а для организации кода - include
и extend
load
или require
load
метод, позволяет включить файл в другой файл, при этом загрузка этого файла,
будет происходить каждый раз в момент вызова кода где указано подключение. Это полезно для разработки, когда вам нужно видеть результат изменений вживую.
Для примера код ниже. Есть какой-то модуль, в отдельном файле, и мы хотим его подключить в другой файл.
1 2 3 4 5 |
|
1 2 3 4 5 6 |
|
Метод require
работает по-другому. При его использовании, подключаемый файл, загружается единоразово в память, и используется в дальнейшем из памяти.
Очевидно что при использовании такого способа подключения, программа будет работать быстрее. Но при разработке, вам придется постоянно перезапукать программу, чтобы
видеть изменения.
Используется он также как и load
, require 'test_module'
. При этом мы можем не указывать расширение .rb
. Этот метод прекрасно сработает и без него.
include
или extend
При организации кода внутри программы, нужно использовать методы - include
или extend
Если вы видите повторяющийся код в вашей программе, его можно вынести в отдельный модуль, и подключать его внутрь с использованием include
.
Например вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
C помощью include
методы модуля становятся доступными для выполнения в классе TestClass
. Т.е. таким образом мы можем избавиться от дублирования кода,
перенся его в модуль и подключив там где нам это необходимо. Мы расширили класс, внедрив в него новые методы объекта.
При использовании extend
, мы можем также внедрить новые методы, но это уже будут методы класса, а не объекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Поэтому используйте extend
если хотите расширить функционал класса и include
для добавления функционала к объекту класса.
http_cache_forever
который используется в контроллере, и позволяет закэшировать страницу.
Рассмотрим пример:
1 2 3 4 5 6 7 8 |
|
Есть контроллер Pages
, в нем action about
, который рендерит страницу ‘О нас’ нашего сайта.
Посмотрим на логи нашего приложения:
1 2 3 |
|
Видно что рендеринг страницы занимает 212.4ms
. Это довольно много, видимо на странице много контента.
Попробуем использовать http_cache_forever
.
1 2 3 4 5 6 |
|
Этот метод в качестве первого аргумента принимает список опций, а вторым блок, который может быть записан также с помощью do end
Используя этот метод, мы устанавливаем заголовки ответа, которые указывают браузеру, что ответ не изменился и можно использовать закэшированную версию страницы.
И в логах теперь будет следующее:
1 2 3 4 5 6 7 8 9 10 |
|
Происходит первый запрос, во втором браузеру отправляется заголовок ответа “304 Not Modified”, и мы видим кэшированную страницу
Рассмотрим опции которые передаются в этот метод. Если мы не укажем public: true
, то кэширование будет работать только для браузера,
в противном случае, кэширование включится также и для проксированных запросов.
Используйте опцию version
, когда вы изменили страничку, и хотите инвалидировать кэш. Указав версию отличную от предыдущей, вы заставите браузер обновить пересоздать кэш страницы. Например version: 'v2'
Посмотреть на реализацию, можно заглянув на гитхаб, в этот коммит.
redirect_to :back
.
Иногда, при использовании данного метода в контроллере, мы могли получать ошибку ActionController::RedirectBackError
,
это случалось тогда, когда не был установлен HTTP_REFERER
в заголовках запроса.
Рассмотрим пример, в котором обработаем ошибку ActionController::RedirectBackError
и заставим наше приложение редиректить на root_url
в случае отсутствия заголовка HTTP_REFERER
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
В случае возникновения ошибки, сработает приватный метод redirect_to_default
, который отредиректит на корневой path.
В Rails 5, redirect_to :back
считается deprecated, и вместо него появился новый метод который называется - redirect_back
.
В нем в качестве дополнительной опции можно передать fallback_location
, и таким образом избавиться от тех костылей которые мы реализовывали выше.
Изменим наш пример для Rails 5
1 2 3 4 5 6 7 8 9 |
|
Как видите все стало намного прозрачнее и проще!
]]>belongs_to
в модели, невозможно будет сохранить запись без наличия ассоциированного объекта.
Раньше эта опция также существовала, и теперь она включена по дефолту.
Рассмотрим пример:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
В Rails 4.x добиться такого поведения можно, указав дополнительную опцию в вызов метода
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Вернуть дефолтное поведение в Rails 5 можно, передав опцию optional: true
в вызове метода
1 2 3 4 5 6 |
|
Для того чтобы вернуть старое поведение для всего проекта, идем в папку initializers
нашего проекта,
и видим что в Rails 5 появился новый файл под названием active_record_belongs_to_required_by_default.rb
И именно в нем видим настройку, которая отвечает за новое поведение. Мы можем установить ее в false
,
чтобы отключить его.
1
|
|
Напомню, что данный файл создается при новой установке Rails 5, в том случае если вы обновляете Rails до версии 5, создайте его вручную.
]]>В Rails 5 много нововведений, одним из них является ActiveRecord Attributes. Эта фича позволяет добавлять аттрибут в модель Rails и обработать его согласно указанному типу. Посмотрим насколько полезен этот новый функционал.
Рассмотрим пример. У нас есть модель пользователей - User. Миграция и сама модель описаны ниже.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Обратите внимание на колонку male
, мы могли бы вполне сделать ее с типом boolean
, но в данном примере это сделано специально,
чтобы показать как мы сможем использовать Attributes.
Хотя бывают и реальные случаи, когда изначально в архитектуре проекта был неверно выбран тип данных, и по прошествии времени, изменение типа будет болезненным.
Давайте воспользуемся нововведением. Укажем колонку male
как аттрибут, с типом boolean
1 2 3 |
|
Теперь мы можем использовать колонку так, как будто ее тип boolean
, например:
1 2 3 4 5 6 7 8 9 10 11 |
|
Вот так легко теперь можно решить проблему несовместимости типов данных указанных в таблице БД и требований согласно бизнес логике. ActiveRecord Attributes поддерживает основные типы данных, ниже список:
Аттрибут не обязательно может быть колонкой в таблице, можно применять его и для обычных аттрибутов модели.
Добавим для модели User
аттрибут confirmed_at
, и укажем тип данных :date_time
, и все будет корректно
обрабатываться. При этом мы не указываем аттрибут через attr_accessor
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Допустим у нас есть модель Transaction
, и в поле цена, к нам приходит строка в виде $100
,
а мы хотим сохранять в базу данных значение в центах, и цифрой, а также использовать строку в запросах.
Для решения этой задачи, нам необходимо добавить свой тип данных, и в этом Attributes
нам поможет.
Создадим класс MoneyType
, и напишем свой метод type_cast
, который используется для преобразования данных.
1 2 3 4 5 6 7 8 9 10 |
|
Укажем обработчик для колонки price
1 2 3 |
|
Если все сделали верно, то теперь мы можем делать вот такие запросы, и быть уверенными что все отработает как надо
1 2 |
|
Как видите очень удобно и просто. Возьмите на заметку :)
]]>ActiveRecord::Relation#cache_key
Рассмотрим пример, в котором нам нужно получить коллекцию пользователей с определенным именем.
1
|
|
В переменной @users
мы получим некую коллекцию записей, которая будет являться объектом класса ActiveRecord::Relation
Результат нашего запроса не изменится если будут выполнены следующие условия:
Михаила
Rails комьюнити предложило реализовать кэширование для коллекций.
Метод cache_key
был добавлен в ActiveRecord::Relation
, и он учитывает множество факторов,
включая изменение запроса, updated_at
для запроса, и значение счетчика записей в коллекции.
Итак у нас есть коллекция пользователей, давайте вызовем у этой коллекции метод cache_key
1 2 |
|
Мы получим вот такую запись. Сейчас подробнее рассмотрим что она означает.
users
представляет название коллекции, основанной на модели Userquery-
не меняющаяся часть, всегда будет присутствовать67sa32b36805c4b1ec1948b4eef8d58f
md5 сумма, котрая образуется из SQL. В нашем примере это MD5("SELECT "users".* FROM "users" WHERE "users"."name" = 'Михаил'")
3
размер коллекции20160116111659084027
временная метка(timestamp). Это значение соответствует самому свежему updated_at в коллекции.Один из примеров, как можно применить cache_key
1 2 3 4 5 6 7 |
|
:updated_at
. Что делать?По умолчанию, для создания timestamp, cache_key
использует колонку :updated_at
Это дефолтная реализация. Если у вас этой колонки нет, но есть другая, с названием отличным от стандартного, вы можете
использовать ee, указав как аргумент в вызове метода cache_key
1 2 3 |
|
Рассмотрим 2 варианта.
Если мы хотим получить 3
пользователя с именем ‘Михаил’, то cache_key
корректно работает.
1 2 3 |
|
Но если мы вызовем cache_key
не извлекая записи в @users
, то получим следующую картину.
1 2 |
|
В итоге мы получаем всех пользователей, и наш limit
не учитывается. Это особенность реализации метода.
ActiveRecord::Base#collection_cache_key.
Cache_key
не меняется, при изменении порядка записей в коллекцииНапример:
1 2 3 |
|
В итоге мы получаем пользователей с ids - [5, 4, 3]
Теперь попробуем удалить пользователя с id = 3
1 2 3 4 5 |
|
Обратите внимание, что cache_key
совпадают в обоих примерах.
Это происходит потому, что ни один из параметров, влияющих на ключ кэша не изменяется. Т.е. ни количество записей, ни запрос, ни метка времени последней записи.
group
в запросахТакже как и в случае с limit
, cache_key
ведет себя по-разному, в случаях когда записи уже есть в памяти и когда их еще нет.
Допустим у нас есть несколько пользователей с одинаковыми именами:
1 2 |
|
В результате мы получаем размер записей 3
, в данном случае это будет количество записей в группе.
Теперь посмотрим что будет, если коллекция будет загружена в память
1 2 3 |
|
Теперь размер коллекции составляет 1
. Т.е кол-во групп.
Эти особенности нужно учитывать, иначе они могут сыграть злую шутку.
API социальных сетей, платежных систем, и прочее прочее. Сейчас редко крупный проект обходится без привязки к внешним сервисам. Тестировать код, связанный со внешними сервисами не всегда легко, это издержки сети, медленное соединение. Лучше изолировать эти тесты с помощью заглушек.
Допустим у нас есть некая система, которая использует внешний сервис Fixer.io для получения курсов валют, и преобразования некой суммы в ту валюту, которую укажет клиент.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Наш код прекрасно работает на production сервере. Но мы не хотим, чтобы при тестировании приложения, отправлялись реальные запросы к сервису. Давайте посмотрим какие инструменты наиболее популярны для решения этой задачи.
Webmock это библиотека для создания и использования заглушек HTTP запросов. Это довольно простой и удобный инструмент, и подходит для использования в связке с Rspec, Minitest, Test::Unit
Настройка Webmock проста. На странице проекта есть инструкция по установке. Она сводится к установке gem, и прописыванию библиотеки в
spec_helper.rb
или в test_helper.rb
1
|
|
1
|
|
Давайте посмотрим как будет выглядеть тест на Rspec, написанный с использованием Webmock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Все довольно просто. Теперь когда наш код в тесте будет обращаться по url http://api.fixer.io/latest?symbols=USD&base=EUR
, вызов сервиса будет заглушаться, и вместо реального запроса мы получим то, что указали в to_return
.
Webmock позволяет легко создавать заглушки для сервиса, но есть и подводные камни. Если вдруг ответ реального сервера поменялся, например поменялось API, то мы не сможем отследить это изменение через тест.
Решить проблему, обозначенную выше помогает - VCR. Его отличие от Webmock в том, что VCR записывает реальный HTTP-ответ от сервиса и использует его потом изолированно в тестах. Запись производится в YAML файл.
Настройка и установка также простая. Установить gem, и добавить конфигурационные строчки в test_helper.rb
или spec_helper.rb
1 2 3 4 5 6 7 8 |
|
Вот пример мини-теста с использованием VCR
1 2 3 4 5 6 7 |
|
Во время запуска этого теста, будет послан реальный запрос к сервису, а ответ записан в yml-файл c названием, которое было указано
как аргумент в VCR.use_cassette
.
Вот так примерно будет выглядеть ответ, записанный в файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
При последующем обращении к сервису, будет использоваться этот запрос, который записан в файл. Это довольно удобно, особенно когда нужно использовать один и тот же запрос в разных местах.
Еще один из способов, состоит во внедрении паттерна проектирования Dependency Injection. Посмотрим на примере нашего конвертера, как можно использовать его.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Итак, мы внедрили в конструктор класса Converter, FixerAPI класс, который представляет собой обертку для работы с сервисом Fixer. Вот так выглядит наш FixerAPI класс
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Мы вынесли работу с внешним сервисом в отдельный класс, и можем его включать в любом месте где захотим. И в этом состоит суть внедрения зависимости. Так как код стал изолирован, то и тестировать его можно, заменив например FixerAPI каким-нибудь FakeAPI.
1 2 3 4 5 6 7 8 9 10 11 |
|
Я не буду долго рассматривать этот способ, так как считаю его очень сложным. Но он имеет место быть. Если коротко, то мы можем написать тестовый сервер, который будет работать например на Sinatra. Он будет возвращать нам нужные данные. Их мы и будем использовать в наших тестах.
1 2 3 4 5 6 |
|
Проблема вполне решаема, разными способами, но лично я остановился бы на VCR. Но конечно нужно смотреть еще на целесообразность использования того или инструмента для облегчения тестирования внешних сервисов.
]]>1
|
|
Если вы не знаете, что такое ActionCable, то это библиотека, которая позволяет интегрировать WebSocket-ы c вашим Rails-приложением Т.е мы имеем необходимый инструмент как на стороне клиента, так и на стороне сервера.
Итак, нам понадобится:
sudo apt-get install redis-server
)Назовем его chat, пропускаем установку gems, с помощью опции -B
1
|
|
Теперь о необходимых гемах для работы нашего чата, нам понадобится:
Ваш Gemfile должен выглядеть примерно вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Выполняем установку gems, bundle install
В нашем проекте будут контроллеры:
При создании контроллеров будем использовать дополнительные опции, –no-helper и –no-assets, чтобы не “плодить” лишние файлы.
Создаем контроллер Sessions
1
|
|
Добавляем create action
1 2 3 4 5 6 7 8 |
|
Создаем контроллер Messages
1
|
|
Action для index не обязателен, мы его опустим, создадим action create
1 2 3 4 5 6 7 |
|
В нем мы возвращаем заголовок :ok
для ajax запроса.
Теперь пропишем маршруты для наших контроллеров в routes.rb
1 2 3 4 5 6 |
|
Корень нашего чата будет вести на логин пользователя
Для начала создадим страничку new
для SessionsController
, в которой будет простая форма с одним инпутом
1 2 3 4 5 6 |
|
Теперь создадим страницу на которой будут выводиться все сообщения и в ней будет находиться форма с созданием нового сообщения
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Здесь мы выведем имя залогинившегося пользователя, и создадим контейнер для сообщений #messages
Для работы с ActionCable необходимо создать 2 класса Connection
и Channel
Создадим их в папке app/channels/application_cable
Прописывать channels
в autoloads не нужно, Rails подгрузит их по умолчанию
1 2 3 4 |
|
1 2 3 4 |
|
ActionCable использует Redis, добавим конфигурационный файл в config/redis/cable.yml
.
Настройки довольно стандартные для Redis.
1 2 3 4 5 6 7 8 9 |
|
Так как ActionCable использует отдельный процесс, то создадим rackup файл с конфигурацией cable/config.ru
1 2 3 4 5 6 |
|
Для удобства запуска нашего ActionCable сервера, давайте добавим sh скрипт в папку bin
и назовем его cable
1 2 |
|
Не забываем поставить этому файлу права на исполнение chmod +x bin/cable
Теперь создадим MessagesChannel
, ответственный за подписку на стрим
1 2 3 4 5 |
|
Далее изменим наш action create
в MessagesController, добавим функционал по отсылке сообщений
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Создадим папку channels
в app/assets/javascripts
.
Сначала необходимо создать соединение с нашим ActionCable сервером, создаем index.coffee
1 2 3 4 5 6 |
|
Теперь подпишемся на MessagesChannel
, создаем messages.coffee
1 2 3 4 5 6 |
|
Здесь в функции received
мы получаем данные и вставляем отрендеренные сообщения в #messages
контейнер
Теперь добавим наш js(//= require channels
) в application.js
1 2 3 4 5 |
|
Запускаем наше rails приложение rails s
, потом запускаем ./bin/cable
. Открываем либо два браузера, либо
запускаем вкладку в режиме инкогнито, переходим на http://localhost:3000 и проверяем работу чата.
Ставим ruby 2.3.0-preview1 через rvm, либо rbenv
1 2 3 4 5 6 |
|
Появился новый оператор - &.
. В Ruby on Rails есть замечательный метод try!, так вот этот оператор
имеет схожую функциональность. Он выполняет проверку на nil до вызова метода у обьекта и возвращает его в случае если
сам nil, в противном случае вызывается метод после оператора.
1 2 3 4 5 6 7 8 9 |
|
Но будьте внимательны, если user
у вас будет например false
, то вы получите NoMethodError
До Ruby 2.2 строки были изменяемые, т.е мы могли взять и сделать что-то подобное str[1] = 'a'
. Если нам было необходимо
запретить изменение строки, то с помощью метода #freeze
это прекрасно получалось
Планируется использование неизменных строк по умолчанию в Ruby 3.0, разработчики хотят увеличить производительность языка,
уменьшив количество обьектов в памяти. В версии 2.3 можно включить этот режим, для этого в начало
файла нужно поместить комментарий # frozen_string_literal: true
1 2 3 4 5 6 7 |
|
Небольшие дополнения к стандартным библиотекам, которые позволяют выполнять такие вещи:
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 7 8 9 |
|
Появилась удобная вещь, в виде подсказки, которая предлагает вам варианты правильного вызова метода, если вы вдруг опечатались
1 2 3 4 5 |
|
Теперь можно сравнивать hash. Вот таким образом
1 2 3 |
|
Также можно применять и другие операторы сравнения, более подробно здесь
Hash можно преобразовать в proc обьект, причем вызвав у proc ключ из Hash вы получите значение
1 2 3 4 5 6 |
|
Иногда приходится для получения определенных значений из Hash использовать сложную конструкцию, с использованием &
это немного упрощается
1 2 3 4 5 6 7 |
|
Новый метод похож по своей функциональности на Hash#values_at
. Он позволяет получить значения по списку ключей.
Отличие fetch_values
в том, что если ключа не найдется, то будет брошен exception KeyError
, вместо возвращения nil
, как это
реализовано в values_at
1 2 3 4 5 |
|
Если вы знакомы с утилитой grep в linux системах, то в случае если мы применим опцию -v
в вызове этой консольной утилиты,
то в результате выполнения этой команды print "test" | grep t test -v
мы ничего не получим на выходе. Эта опция позволяет вывести то что не подошло,
т.е. она противоположна grep
1 2 3 4 5 6 7 |
|
Добавилось несколько методов из Rails. Названия интуитивно понятны, можно обойтись без примеров
https://www.ruby-lang.org/en/news/2015/11/11/ruby-2-3-0-preview1-released/
]]>