Статьи

Шаблоны приложений Rails в реальном мире

railstemplate

Я собираюсь начать новый проект, который будет иметь много сервисов на основе Rails (и rails-api), и я хочу, чтобы все службы создавались одинаково. В проекте есть пара других разработчиков, поэтому я не хочу, чтобы мы каждый создавали совершенно разные структуры приложений или использовали разные гемы, если на то нет веской причины.

Например, я хотел бы, чтобы мы все начали с одной и той же версии Rails, а PostgreSQL — это предпочтительный репозиторий, поэтому установка гема SQLite глупа. В средах тестирования и разработки было бы здорово иметь Rspec и Guard, готовые к работе при создании приложения. Кроме того, наличие Pry с самого начала просто умно. Множество этих и подобных изменений заставили меня захотеть изменить нашу отправную точку.

Существует множество способов сделать это, но я не хотел начинать с чего-то вроде RailsBricks или Rails Composer. Я ничего не имею против любого из них, совсем наоборот. На самом деле я писал о RailsBricks и считаю, что Дэниел Кехо (создатель учебников по RailsApps и Rails Composer) должен быть посвящен в рыцари. Проще говоря, я хочу сделать это без добавления другой зависимости, если это возможно.

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

Спойлер: Отлично работает.

Шаблон API

Шаблон Rails API — это домен-специфический язык (DSL), который поддерживается Thor . Он добавляет следующие действия поверх основных действий Thor:

  • gem — добавить драгоценный камень в Gemfile
  • gem_group — Создать group блок в Gemfile с гемами в предоставленном блоке.
  • add_source — добавление источника gem в Gemfile, например, частный сервер Gem
  • environment — добавить строку в application.rb или предоставленный файл среды. Это также относится к application . Я покажу вам пример того, как это работает в моем последнем шаблоне.
  • git — запускать команды git
  • vendor — создать файл в каталоге vendor
  • lib — создать файл или каталог в lib
  • rakefile — Создать новый Rakefile
  • initializer — создайте новый файл инициализатора с предоставленным содержимым
  • generate — запустить генератор Rails
  • rake — запустить задачу Rake
  • capify — запустить capify
  • route — создать запись в rout.rb
  • readme — печатает содержимое предоставленного файла на консоль
  • after_bundle — обратный вызов, который запускается (как вы уже догадались) после завершения bundle install

Оказывается, что этих нескольких действий вместе с основными действиями Thor более чем достаточно, чтобы создать надежную отправную точку для реального приложения Rails.

Пример

Как я уже упоминал ранее, я хочу изменить некоторые элементы из основного rails new сгенерированного сайта. Вот полный список вещей, которые я хочу адаптировать:

  • Добавьте гем pg в конфигурацию Gemfile и базы данных
  • Используйте Rails API. Это означает, что я изменю, какие основные Railties включены.
  • Используйте ROAR для моих докладчиков
  • Используйте Pry (через gem byebug ) в разработке
  • В тесте используйте Rspec, Mutant, SimpleCov и Guard вместе с запуском генераторов / установки
  • Создайте Dockerfile и docker-compose.yml для приложения
  • Добавьте лучший файл .gitignore
  • Добавьте некоторые инструменты вокруг JSON Schema. У прекрасных людей в Heroku есть тонна их
  • Добавить маршрут для конечной точки проверки работоспособности

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

 rails new service_name -m our_template.rb 

а затем идти прямо в развитие. Такие вещи, как guard будут работать немедленно, без необходимости их настройки. Как и должно быть.

Шаблон

Вот весь шаблон:

 # Add the current directory to the path Thor uses # to look up files def source_paths Array(super) + [File.expand_path(File.dirname(__FILE__))] end remove_file "Gemfile" run "touch Gemfile" add_source 'https://rubygems.org' gem 'rails', '4.2.1' gem 'rails-api' gem 'puma' gem 'pg' gem 'roar-rails' gem 'committee' gem_group :development, :test do gem 'spring' gem 'pry-rails' gem 'web-console', '~> 2.0' gem 'prmd' gem 'rspec-rails', require: false end gem_group :test do gem 'simplecov', require: false gem 'simplecov-rcov', :require => false gem 'guard-rspec' gem 'mutant' gem 'mutant-rspec' end copy_file "Dockerfile" copy_file "docker-compose.yml" remove_file ".gitignore" copy_file ".gitignore" inside 'config' do remove_file 'database.yml' create_file 'database.yml' do <<-EOF default: &default adapter: postgresql host: db port: 5432 pool: 5 timeout: 5000 user: postgres password: postgres development: <<: *default database: #{app_name}_development # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: #{app_name}_test host: 192.168.59.103 production: <<: *default database: #{app_name}_production EOF end end create_file 'schema/meta.json' do <<-EOF { "description": "Service", "id":"service-uu", "links": [{ "href" : "https://api.esalrugs.com", "rel" : "self" }], "title" : "UU Service" } EOF end # JSON Schema empty_directory "schema/schemata" rakefile("schema.rake") do <<-EOF require 'prmd/rake_tasks/combine' require 'prmd/rake_tasks/verify' require 'prmd/rake_tasks/doc' namespace :schema do Prmd::RakeTasks::Combine.new do |t| t.options[:meta] = 'schema/meta.json' # use meta.yml if you prefer YAML format t.paths << 'schema/schemata' t.output_file = 'schema/api.json' end Prmd::RakeTasks::Verify.new do |t| t.files << 'schema/api.json' end Prmd::RakeTasks::Doc.new do |t| t.files = { 'schema/api.json' => 'schema/api.md' } end task default: ['schema:combine', 'schema:verify', 'schema:doc'] end EOF end after_bundle do remove_dir "app/views" remove_dir "app/mailers" remove_dir "test" insert_into_file 'config/application.rb', after: "require 'rails/all'\n" do <<-RUBY require "active_record/railtie" require "action_controller/railtie" RUBY end gsub_file 'config/application.rb', /require 'rails\/all'/, '# require "rails/all"' application do <<-RUBY config.assets.enabled = false config.generators do |g| g.test_framework :rspec, fixture: true g.view_specs false g.helper_specs false end # Validates the supplied and returned schema. # docs: https://github.com/interagent/committee config.middleware.use Committee::Middleware::RequestValidation, schema: JSON.parse(File.read("./schema/api.json")) if File.exist?("./schema/api.json") RUBY end gsub_file 'config/environments/development.rb', /action_mailer/, '' gsub_file 'config/environments/test.rb', /.*action_mailer.*/n/, '' gsub_file 'app/controllers/application_controller.rb', /protect_from_forgery/, '# protect_from_forgery' run "spring stop" generate "rspec:install" run "guard init" # Health Check route generate(:controller, "health index") route "root to: 'health#index'" git :init git add: "." git commit: "-a -m 'Initial commit'" end 

Хм, это выглядит долго, не так ли? Давайте разберем шаблон.

 # Add the current directory to the path Thor uses # to look up files def source_paths Array(super) + [File.expand_path(File.dirname(__FILE__))] end 

Thor использует source_paths для поиска файлов, которые отправляются в действия Thor на основе файлов , такие как copy_file и remove_file . Здесь он перерисован, поэтому я могу добавить каталог шаблонов и скопировать из него файлы в сгенерированное приложение.

В стандартном Rails Gemfile есть куча вещей, которые нам не нужны (sqlite, все ресурсы, турболинки), поэтому вместо утомительного удаления гемов давайте просто явно перестроим файл. Конечно, каждый Gemfile нуждается в источнике для поиска, поэтому он добавляется в начало файла.

 # We'll be building the Gemfile from scratch remove_file "Gemfile" run "touch Gemfile" add_source 'https://rubygems.org' 

Вот все драгоценности, которые нам нужны:

 gem 'rails', '4.2.1' gem 'rails-api' gem 'puma' gem 'pg' gem 'roar-rails' gem 'committee' gem_group :development, :test do gem 'spring' gem 'pry-rails' gem 'web-console', '~> 2.0' gem 'prmd' gem 'rspec-rails', require: false end gem_group :test do gem 'simplecov', require: false gem 'simplecov-rcov', :require => false gem 'guard-rspec' gem 'mutant' gem 'mutant-rspec' end 

gem и gem_group предоставляются API-интерфейсом Rails Template и делают именно то, что вы ожидаете.

Одна из целей этого шаблона — сделать Dockerizing приложением очень простым. Объяснение Docker и его преимуществ (и недостатков) выходит за рамки этой статьи. Вместо этого я призываю вас заглянуть в Docker (я надеюсь написать статью об этом в ближайшее время) и посмотреть на следующее, поскольку мне просто нужно было скопировать некоторые пользовательские файлы в сгенерированное приложение.

 copy_file "Dockerfile" copy_file "docker-compose.yml" 

Dockerfile и docker-compose.yml — это файлы в моем каталоге шаблонов. Файлы просто копируются с использованием действия Тора copy_file .

Давайте сделаем блестящий, новый файл .gitignore . Причина, по которой файл удаляется первым, заключается в том, что команда rails new предлагает пользователю перезаписать файл:

 remove_file ".gitignore" copy_file ".gitignore" 

В том, что может быть немного более спорным решением, есть основной файл database.yml, который я хотел бы распространять. Благодаря Docker, мы можем немного стандартизировать это. Метод cool inside гарантирует, что файл окажется в нужной папке:

 inside 'config' do remove_file 'database.yml' create_file 'database.yml' do <<-EOF default: &default adapter: postgresql host: db port: 5432 pool: 5 timeout: 5000 user: postgres password: postgres development: <<: *default database: #{app_name}_development # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: #{app_name}_test host: 192.168.59.103 production: <<: *default database: #{app_name}_production EOF end end 

Обратите внимание, что я использую app_name для создания имен базы данных. app_name и app_path являются доступными переменными для шаблона.

Пришло время использовать некоторые инструменты JSON Schema, чтобы убедиться, что у меня есть хорошая схема и генерация документов, а также некоторая проверка. Поток JSON Schema все еще немного неуклюж в Rails, но цель оправдывает средства, IMO:

 create_file 'schema/meta.json' do <<-EOF { "description": "Service", "id":"service-uu", "links": [{ "href" : "https://api.esalrugs.com", "rel" : "self" }], "title" : "UU Service" } EOF end # JSON Schema empty_directory "schema/schemata" rakefile("schema.rake") do <<-EOF require 'prmd/rake_tasks/combine' require 'prmd/rake_tasks/verify' require 'prmd/rake_tasks/doc' namespace :schema do Prmd::RakeTasks::Combine.new do |t| t.options[:meta] = 'schema/meta.json' # use meta.yml if you prefer YAML format t.paths << 'schema/schemata' t.output_file = 'schema/api.json' end Prmd::RakeTasks::Verify.new do |t| t.files << 'schema/api.json' end Prmd::RakeTasks::Doc.new do |t| t.files = { 'schema/api.json' => 'schema/api.md' } end task default: ['schema:combine', 'schema:verify', 'schema:doc'] end EOF end 

Сначала создайте файл meta.json, который будет содержать метаданные об этом сервисе. Заголовок, URL и т. Д. Затем создайте каталог схемы / схем для хранения файлов схемы для моих ресурсов. Я не буду вдаваться в подробности JSON Schema в этом посте, но призываю вас ознакомиться с ним. Наконец, я добавляю некоторые грабли для генерации документов схемы и разметки, которые будут документировать API. Метод rakefile предоставляется API-интерфейсом Rails Application Template, и это здорово.

Теперь мы достигли обратного вызова после bundle . После комплектации сделано очень много, некоторые из которых, вероятно, могут быть перенесены в комплектацию, но мы здесь. Это приложение Rails API, поэтому нам не нужны представления, и почта не будет обрабатываться этими службами. Также мы используем Rspec, поэтому просто удалите тестовый каталог:

 after_bundle do remove_dir "app/views" remove_dir "app/mailers" remove_dir "test" 

Поскольку Rails API не использует все основные Railties, мы можем добиться некоторой производительности, требуя только то, что нам нужно. Классный метод Тора insert_into_file позволяет нам разместить его там, где мы хотим. После этого мы можем закомментировать строку, которая включает все, используя gsub_file :

 insert_into_file 'config/application.rb', after: "require 'rails/all'\n" do <<-RUBY require "active_record/railtie" require "action_controller/railtie" RUBY end gsub_file 'config/application.rb', /require 'rails\/all'/, '# require "rails/all"' 

Поскольку нет активов или представлений, отключение активов в целом является хорошей идеей. Кроме того, хотя я хочу Rspec в качестве тестовой среды, я не хочу просматривать спецификации. Здесь я использую приложение-метод Rails Template API, которое, если вы помните, является просто псевдонимом для environment :

 application do <<-RUBY config.assets.enabled = false config.generators do |g| g.test_framework :rspec, fixture: true g.view_specs false g.helper_specs false end 

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

 # Validates the supplied and returned schema. # docs: https://github.com/interagent/committee config.middleware.use Committee::Middleware::RequestValidation, schema: JSON.parse(File.read("./schema/api.json")) if File.exist?("./schema/api.json") RUBY end 

Жемчужина комитета предоставляет промежуточное программное обеспечение для проверки схемы JSON в запросах и ответах. Подтверждение ввода является отличным.

Затем избавьтесь от всех настроек Action Mailer:

 gsub_file 'config/environments/development.rb', /.*action_mailer.*/n/, '' gsub_file 'config/environments/test.rb', /.*action_mailer.*/n/, '' 

Так как мы не будем использовать куки с API (потому что это безумие), метод protect_from_forgery не нужен:

 gsub_file 'app/controllers/application_controller.rb', /protect_from_forgery/, '# protect_from_forgery' 

Я хотел бы иметь готовую к запуску среду тестирования / спецификации, так что это означает запуск генератора Rspec и инициализацию Guard. Я с трудом узнал, что если я не остановлю Spring, генератор зависнет:

 run "spring stop" generate "rspec:install" run "guard init" 

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

 # Health Check route generate(:controller, "health index") route "root to: 'health#index'" 

Почти готово, просто нужно настроить Git:

 git :init git add: "." git commit: "-a -m 'Initial commit'" end 

Это упаковка! Я могу сказать нашей команде разработчиков использовать мой шаблон для генерации их Rails-сервисов, и у нас будет общая основа вместе с готовой средой. Просто cd в новый каталог приложения и guard или docker-compose up и все готово. Неплохо для очень маленькой работы. Я положил много вещей на место, чтобы поощрить некоторые лучшие практики и избавиться от бесполезности. Когда люди говорят, что Rails делает вас более продуктивным, это отличный пример, по моему скромному мнению.

Gotchas

Конечно, есть пара вещей, которые нужно знать, используя этот конкретный пример. Самое главное, поскольку я решил скопировать файлы из каталога шаблонов, вы не можете использовать конечную точку HTTP для файла шаблона. Другими словами, это не сработает:

 rails new service_name -m https://raw.githubusercontent.com/skookum/rails-api-template/master/rails-api-template.rb 

потому что он не найдет Dockerfile для копирования. Это можно исправить, вставив все содержимое файла в сам шаблон с помощью heredocs. Поэтому, если вы собираетесь использовать это, обязательно клонируйте репозиторий в локальную область и обратитесь к локальному пути в параметре -m .

Кроме того, некоторые из вас могут спросить, почему я не использую rails-api new service_name . Это хороший вопрос. По сути, я посмотрел, что генерирует rails-api new и заставил мой шаблон сделать что-то похожее. Для этого стоит передать тот же параметр -m в генератор rails-api и вы окажетесь в аналогичном месте.

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

Шаблон Все Вещи

Надеемся, что этот пост побудит вас принять аналогичный подход к вашей командной разработке Rails. Мы только начинаем с шаблонов, так что я уверен, что что-то пропустил, или у кого-то из вас есть хороший совет, как сделать это еще лучше. Поместите это в комментариях!

Спасибо за прочтение!

ОБНОВЛЕНИЕ: В предыдущей версии этой статьи неправильно указывалось, что Rails Composer был создан М. Хартлом, хотя на самом деле он был создан Дэниелом Кехо. Приносим свои извинения за то, что ошиблись. Ответственные были уволены.