20 Янв

Паттерны в Ruby. Шаблонный паттерн (Template)

Краткая вводная часть

Начал читать книгу «Design Patterns in Ruby» Russ’a Olsen’a по паттернам проектирования. Книга интересная, информация, несмотря на год выпуска книги, очень актуальная (что сказать, сами паттерны были выработаны еще в 1995 году и тоже актуальны).
В общем, решил сделать статью, а. скорее всего, даже цикл статей с пометками в основном для себя, что за паттерны такие, как их готовить и с чем их едят.
Насчет примеров кода — буду местами использовать примеры из книги, местами стараться писать свои примеры, если придумаю более наглядный и более понятный. Но как получится, на самом деле

Да, по паттернам есть хороший цикл статей на medium, где тоже человек расписывал, да еще и с примерами кода для каждого из них. Но мне удобнее будет выделять какие-то ключевые мысли самостоятельно.

Паттерн «Шаблон» (или Шаблонный паттерн)

«Шаблон» применяется в тех случаях, если нужны несколько разновидностей одной и той же сущности, к примеру, несколько видов отчетов (HTML, Plain Text, XML и т. д.). Либо, в качестве альтернативного примера, если вам нужно несколько разных отчетов с разной структурой в деталях, но глобальная структура, опять же, у них будет общая. Например вам необходимо два отчета с разным количеством столбцов, но глобальная структура этих отчетов все равно будет похожа: название отчета, вывод заголовков столбцов, вывод самих данных отчета, вывод итоговой/суммарной информации.

Читать далее
29 Дек

Настройка автоматического запуска puma при перезагрузке сервера

Для своих Rails-приложений я в последнее время использую связку puma+nginx. И достаточно продолжительное время я ленился настроить функционал автоматического старта приложений при ребуте сервера. Ну, типа, раз в месяц если он перезапустится, то и фиг с ним, вручную стартану. Благо, что особо критичных к простою проектов у меня нет (на самом деле есть, но там очень быстро мне сообщают о проблеме и я её очень быстро решал). Но надо что-то менять и я решил, что пришло то самое время.

Читать далее
28 Ноя

Настройка Nginx + Puma для Rails

Просто, чтоб не забыть.
Создаем или редактируем файл /path_to_your_rails_app/config/puma.rb (всякие пути и названия нужно указать свои, само собой):

threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/tmp"
#ниже вместо your_socket.sock пишете название своего файла сокета
bind "unix://#{shared_dir}/your_sock.sock"
#перенаправляем stdout в файлы логов
stdout_redirect "#{app_dir}/log/puma.stdout.log", "#{app_dir}/log/puma.stderr.log", true
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

После этого в /etc/nginx/nginx.conf (или /etc/nginx/sites_enabled/your_conf_file):

upstream your_upstream {
  server unix:///var/www/your_app/tmp/your_sock.sock;
}

server {
 #       listen   443;
        listen   80;
        server_name  your_domain.ru;
        root /var/www/your_app/public;
        client_max_body_size 32m;

#        настройки для HTTPS
#        ssl    on;
#        ssl_certificate   /etc/letsencrypt/live/your_domain.ru/fullchain.pem;
#        ssl_certificate_key    /etc/letsencrypt/live/your_domain.ru/privkey.pem;


        location / {
          proxy_pass http://your_upstream; # match the name of upstream directive which is defined above
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# настройки ниже нужны, если используется Action Cable
#        location /cable {
#          proxy_pass http://your_upstream;
#          proxy_http_version 1.1;
#          proxy_set_header Upgrade $http_upgrade;
#          proxy_set_header Connection "upgrade";
#        }

#эти четыре настройки ниже нужны, если используете SSL
#          proxy_set_header  X-Forwarded-Proto $scheme;
#          proxy_set_header  X-Forwarded-Ssl on; # Optional
#          proxy_set_header  X-Forwarded-Port $server_port;
#          proxy_set_header  X-Forwarded-Host $host;
        }
        location ~* ^/assets/ {
          # Per RFC2616 - 1 year maximum expiry
          expires 1y;
          add_header Cache-Control public;
          add_header Last-Modified "";
          add_header ETag "";
          break;
        }

}

#для автоматического перенаправления на https (если используется): 
server {
        listen       80;
        server_name your_domain.ru;
        rewrite     ^   https://$server_name$request_uri? permanent;
}

После этого могут возникнуть только проблемы с правами доступа к файлу сокета. По логам выяснять и решать.

07 Май

Статистика по переходам в меню IVR

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

Итого, на руках имеем Freeswitch с реализованными на нем разными IVR-менюшками и всё.
В итоге решил сделать так: подключаем mod_curl, и в каждом пункте меню делаем сначала вот такой запрос

<entry action="menu-exec-app" digits="1" param="curl http://my.backend/add?anumber=${caller_id_number}&bnumber=${destination_number}&key_pressed=1" />

Это, соответственно, для единицы. Для других пунктов в нужных местах меняем цифру на необходимую.

В итоге у нас перед выполнением какого-то действия, которое запланировано при нажатии на кнопку, выполняется сначала запрос на заранее созданный бэкенд, а там уже это всё добавляется в БД. Ну и дальше мы уже с этими данными можем делать что хотим.
Да, при большом количестве меню это становится довольно муторным делом, но в целом это все занимает не так уж и много времени и всё-таки основная работа уходит на обработку статистики, а не на ее сбор.
Из нюансов могу отметить, что тут не отмечаются абоненты, которые не нажали вообще ничего. Но нам же нужна статистика по нажатиям, а не по «ненажатиям» :)

19 Июн

Неприятная проблема с миграциями в MySQL

Столкнулся с такой очень неприятной штукой на днях.
Дорабатывал я приложение у себя на локальном компе, соответственно, с development-базой на SQLite. Сделал миграцию, настроил, проверил. Всё хорошо.
Запушил на production-сервер. Пытаюсь применить миграцию (production-база на MySQL), а мне выдает следующую ошибку:
#:/var/www/project# RAILS_ENV=production rails db:migrate
/usr/local/rvm/rubies/ruby-2.4.0/bin/ruby: warning: shebang line ends with \r may cause a problem
== 20170617124016 CreateDashboards: migrating =================================
-- create_table(:dashboards)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

Mysql2::Error: Table 'dashboards' doesn't exist: SHOW FULL FIELDS FROM `dashboards`
/usr/local/rvm/gems/ruby-2.4.0/gems/mysql2-0.4.6/lib/mysql2/client.rb:120:in `_query'
/usr/local/rvm/gems/ruby-2.4.0/gems/mysql2-0.4.6/lib/mysql2/client.rb:120:in `block in query'
. . .
Ну и так далее ошибок на экрана четыре.
Что ж, гуглим. Сначала ничего непонятно, какая-то фигня вообще. С наскока решить не получилось.
Спустя полчаса гугления оказалось следующее. В этой таблице dashboards у меня был референс на другую таблицу (которая была создана пару месяцев назад). И проблема в том, что тип поля этого референса не совпадал с типом поля id в родительской таблице. Тип поля референса был bigint, в то время как тип родительского поля был int.
Произошло это, скорей всего, из-за того, что пару недель назад переехал с Rails 5.0.1 на Rails 5.1.0 (ну и версию Ruby пришлось поднять с 2.3.1 до 2.4.0). А в новой версии рельс, судя по всему, для id и для референсов используется bigint вместо int.
В итоге исправил проблему ручным прописыванием в миграции для создания таблицы dashboards типа референса примерно так:
 class CreateDashboards < ActiveRecord::Migration[5.1]
  def change
    create_table :dashboards do |t|
      t.references :hosp, type: :integer, foreign_key: true
      t.integer :day_count
. . .
Такие дела.
13 Мар

Настройка подключений к нескольким базам данных в Rails.

Решил сделать перевод на русский язык статьи Setting up multiple databases in Rails: the definitive guide

(By Roberto Ostinelli / December 2, 2015).

Переводчик я непрофессиональный, поэтому в некоторых местах буду позволять себе вольности, но общий смысл будет сохранен.

Статья написана для Rails 4, но работает и в Rails 5 тоже, по крайней мере, на момент перевода статьи. 

———————

Может быть множество разных причин, зачем вам может понадобиться подключение к нескольким базам данных в вашем Ruby in Rails приложении. В моём случае необходимо было хранить большое количество данных, отражающих поведение пользователя: клики, посещаемые страницы, изменения истории и т. д.

Такие типы баз данных обычно не критичны для основной цели, а разрастаются намного быстрее (и до гораздо бОльших размеров), чем другие БД.

Требования к ним достаточно разнообразны: к примеру, они требуют больше пространства, более терпимы к аппаратным и программным сбоям, а также у них в приоритете идёт запись в базу, а не чтение. По этим причинам иногда выгодно отделить такие БД от основной базы вашего приложения. Зачастую для таких задач используются нереляционные СУБД, что уже выходит за пределы темы, по которой писалась данная статья.

Я нашел и прочитал множество различных решений, но я так и не смог найти такого, которое бы полностью выполняло следующие условия:

  • Должны быть различные и изолированные друг от друга миграции и схемы для каждой базы данных
  • Должны использоваться генераторы Rails для создания новых миграций для каждой БД, независимо друг от друга
  • Должны присутствовать специфические для баз данных задания rake для основных операций над БД (например такие же, как и для основной БД)
  • Должна быть интеграция с заданиями spec RSpec’а.
  • Должно работать с Database Cleaner
  • Должно работать на Heroku

Это мой взгляд на то, как удовлетворить все вышеуказанные требования и получить полностью рабочее Rails-приложение с подключением к нескольким БД.

Читать далее