Чи є життя без Rails? ч. 2

4 хв. читання

Інтро

Минулого разу ми створили API на основі Grape, який може зберігати та повертати наші книжки (поки ви не перезавантажите сервер). Цього разу ми вдосконалимо застосунок, додавши до нього певну структуру, Bundler, Rake і, що найголовніше, спосіб зберігати наші книги за допомогою Sequel та postgres.

Додаємо Bundler

Оскільки в нас понад дві залежності (rack та grape), ми скористаємось bundler – менеджером залежностей для Ruby.

Наш проект знаходиться всього в двох кроках від власного менеджера залежностей:

  1. Встановлюємо bundler: gem install bundler;
  2. cd у ваш каталог проекту і запускаємо bundle init;

Тепер ви повинні побачити новий файл у вашому каталозі під назвою Gemfile. Тут ми можемо додати геми, які ми використовували минулого разу:

# frozen_string_literal: true

source 'https://web.archive.org/web/20230321180801/https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'grape'
gem 'rack'

Тепер коли ми будемо виконувати bundle install, наші залежності будуть встановлені та з'явиться новий файл Gemfile.lock, який відслідковує версії гемів під час інсталяції.

Простий Sequel

Почнемо зі вбудованої (in-memory) БД Sqlite. Так, ми втрачатимемо дані після перезавантаження сервера, але пізніше зможемо налаштувати Sequel і замінити зв'язок на наш postgres-db. Додамо sequel та sqlite3 до нашого Gemfile.

# frozen_string_literal: true

source 'https://web.archive.org/web/20230321180801/https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'grape'
gem 'rack'
gem 'sequel'
gem 'sqlite3'

Примітка: Можливо, спочатку вам необхідно буде встановити sqlite3.

Наші залежності відсортовані, а ми можемо знову поглянути на наш застосунок:

# application.rb

class MyApp
  def books
    @books ||= []
  end
end

Application = MyApp.new

Нам вистачало простенького масиву @books, але використаємо по максимуму переваги БД. Для цього нам потрібна БД. Додамо новий каталог lib/initializers/ з database.rb в ньому.

# lib/initializers/database.rb

DB = Sequel.sqlite

DB.create_table?(:books) do
  primary_key :id
  String :title
  String :author
end

Якщо раніше ви використовували Rails — знайте — ви тільки що створили свою першу db/schema.rb! Вітаю! Наведений вище код досить простий:

  1. DB = Sequel.sqlite створює нову БД Sqlite3 в пам'яті й (дотримуючись кращих практик Sequel) присвоює її константі DB.
  2. DB.create_table?(:books) створює таблицю з ім'ям books. ? вказує на те, що таблиця буде створена, лише якщо таблиця books ще не існує. Це насправді не обов'язково, якщо база даних не живе довше, ніж сервер, але в майбутньому це дозволить уникнути помилок.
  3. primary_key :id додає авто-інкремент primary_key під назвою id до нашої таблиці.
  4. String :title і String :author додає два поля до нашого таблиці, назва яких title та author, які є рядками. Цікаво, що sequel не дотримується загальної конвенції Ruby, визначаючи методи з іменами, де є великі літери.

Тепер у вас є акуратна, маленька БД, щоб зберігати записи book.

Створення інтерфейсу до БД

Хоча тепер нам нічого не заважає отримати доступ до БД з будь-якого місця в нашому застосунку, нам слід застосувати інкапсуляцію, додавши клас, який буде керувати доступом до нашої БД. Ми будемо мати гарний і відокремлений код, який може стати нам в нагоді, коли ми вирішимо змінити нашу БД. Викличемо ці інтерфейси (можливо, в майбутньому їх буде набагато більше) і додамо їх в наш каталог / lib.

mkdir lib/repositories
touch lib/repositories/book_repository.rb

Додамо наш інтерфейс до цього файлу.

# lib/repositories/book_repository.rb

class BookRepository
  class << self
    extend Forwardable

    def_delegator :data_set, :to_a

    def data_set
      @data_set ||= DB[:books]
    end

    def insert(set)
      data_set.insert(set)
      self
    end

    def to_json(*_)
      to_a.to_json
    end

    alias << insert
  end
end

Тут потрібно пояснити кілька речей:

  1. Ми додали Forwardable, оскільки ми очікуємо делегування великої кількості повідомлень до
  2. Нашого data_set. Ви можете думати про data_set як про нашу таблицю books у базі даних. Ви можете отримати доступ до всієї вашої таблиці, використовуючи DB[:"#{table_name}"];
  3. Ми повертаємо наш BookRepository для сумісності з більш ранньою версією застосунка, де ми повертали змінну @books після додавання нової книги. Ми використовуємо аліас << і insert з тієї ж самої причини;
  4. Оскільки Grape викличе to_json у всьому, що ми повернемо в блоці, ми хочемо, щоб метод to_json повертав щось корисне. Наприклад, всі книги.

Примітка: З якоїсь причини, довелося додати (*_) в визначення to_json, щоб мовчки проковтнути передані йому аргументи. Схоже, що Grape передає якийсь аргумент to_json при рендерингу {books: books} в api

Складаємо все до купи

Тепер у нас є база даних та інтерфейс для її використання. Але вона ніде не завантажується (життя без автозавантаження Rails важке, так). Тож змінимо це в нашому app.rb.

# app.rb

require 'forwardable'

require 'sqlite3'
require 'sequel'

require_relative 'lib/initializers/db'
require_relative 'lib/repositories/book_repository'

class MyApp
  def books
    @books ||= BookRepository
  end
end

Application = MyApp.new

Я вирішив запитувати (require) всі відповідні залежності для застосунку в app.rb, тоді як зберігати в api.rb все необхідне для grape.

Гаразд! Ми вимкнули наш примітивний масив за допомогою високопрофесійного і надзвичайно складного BookRepository. Так звучить набагато крутіше.

Додаємо Postgres

Застосунок працює, має реальну базу даних SQL і відчуває себе супер фантастично. Але річ, яку ми хочемо вивчити в цьому розділі, пов'язана з усіма труднощами використання БД без допомоги Rails. Замість використання нашої sqlite3 в пам'яті (або переходу на постійну базу даних sqlite3, оскільки це махлювання), ми додамо великий і могутній postgres в наш стек. І щоб все було простіше, ми додамо і dotenv. Але про це пізніше.

Примітка: Якщо ви ще не встановили postgres, тепер самий час для цього. Вам також знадобиться користувач, який може принаймні створити нову базу даних. Тож зробіть це.

Просто замініть sqlite3 на pg та додайте dotenv до вашого Gemfile.

# Gemfile

# frozen_string_literal: true

source 'https://web.archive.org/web/20230321180801/https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'dotenv'
gem 'grape'
gem 'pg'
gem 'rack'
gem 'sequel'

Керування базою даних

Ми могли б просто створити базу даних, використовуючи postgres. Але ми очікуємо, що нам доведеться багато разів створювати та видаляти цю базу даних, тож просто напишемо для цього rake обробники. Якщо ви розробляли застосунки для Rails, ймовірно, ви стикалися з завданнями в rake db:. Вони завжди гарно підходять для того, щоб створити (create), видалити (drop) та наповнити (seed) вашу базу даних для вас.

Попри те, що ми навмисно уникаємо парадигм Rails у цій серії, нам не обов'язково бути дикунами. Створимо власний rake db:create db:drop db:setup. Додайте rake у свій Gemfile і виконайте:

touch Rakefile

Тепер ви можете запустити bundle exec rake і він нічого не зробить! Агов?

Базові завдання

Коли ви встановили postgres, ви, ймовірно, встановили утиліти, які постачаються разом з ним. Нас цікавить createdb (ви можете перевірити її наявність за допомогою which createdb). Оскільки ми ліниві, ми просто дамо можливість createdb зробити всю роботу за нас!

namespace :db do
  desc 'Creates a new database based on the variables in .env'
  task :create do
    puts 'Creating database...'
    # Якщо створення успішне
    puts `createdb && echo 'Created db'`
  end
end

Додамо й rake db: drop:.

namespace :db do
  desc 'Creates a new database based on the variables in .env'
  task :create do
    puts 'Creating database...'
    # Якщо створення успішне
    puts `createdb && echo 'Created db'`
  end

  desc 'Drops the database specified in the variables in .env'
  task :drop do
    puts 'Dropping database...'
    # Якщо drop пройшов успішно
    puts `dropdb && echo 'Dropped db'`
  end
end

Тепер це не спрацює, тому що createdb і dropdb потребують аргументів для роботи. Але як ми можемо додати ці аргументи в наше rake-завдання? Якщо ви подумали про систему аргументів rakes, то ні. Це нудно та важко. Замість цього ми будемо використовувати змінні середовища!

Використання dotenv

createdb і dropdb відмінно підходять для створення сценаріїв, оскільки обидва використовують змінні середовища (якщо їх називати правильно). Отже, ви можете просто виконати своє завдання так:

PGDATABASE=mydb PGPASS=12345 PGUSER=dau ... rake db:create

І все буде добре. Але знову ж таки, ми ледачі: ми не будемо вводити змінні середовища.

Замість цього ми будемо записувати їх у файл, де ми зможемо зчитати їх знову і знову. Якщо ви не чули про dotenv, ось що він робить: він завантажує всі змінні, які ви визначаєте у файлі .env, у середовище. Не варто починати з самого .env, просто додайте файл .env.sample, який виглядає так:

# .env.sample

PGUSER=
PGPASS=
PGDATABASE=
PGHOST=
PGPORT=

Залиште значення порожніми. Цей файл буде додано в контроль версій, оскільки в ньому немає нічого, що ми б хотіли приховати.

Примітка: Якщо ви використовуєте git у своєму проекті, обов'язково додайте .env до списку .gitignore, щоб ви випадково не додали конфіденційні дані до контролю версій.

Тепер ви можете виконати:

cp .env.sample .env

і заповніть порожні значеннч! Якщо ви хочете, щоб postgres використовувала значення за замовчуванням, просто не пишіть нічого (наприклад, PGHOST та PGPORT залишаються за замовчуванням). Те, що ви повинні заповнити PGUSER, PGPASS і PGDATABASE. Якщо у вас все це заповнено (з правильною інформацією, звичайно), ваші rake завдання не будуть видавати помилки.

Ви можете сказати «Почекай, Rake не буде завантажувати ці змінні самостійно! Як він може знати про них!» І ви маєте рацію. Нашому Rakefile все бракує чогось.

desc 'Loads our .env into ENV'
task :environment do
  puts 'Loading environment...'
  require 'dotenv/load'
end

namespace :db do
  desc 'Creates a new database based on the variables in .env'
  task :create => :environment do
    puts 'Creating database...'
    # Якщо створення успішне
    puts `createdb && echo 'Created db'`
  end

  desc 'Drops the database specified in the variables in .env'
  task :drop => :environment do
    puts 'Dropping database...'
    # Якщо drop пройшов успішно
    puts `dropdb && echo 'Dropped db'`
  end

  desc 'Resets our database by dropping and creating it again.'
  task :reset => :environment do
    Rake::Task['db:drop'].invoke
    Rake::Task['db:create'].invoke
  end
end

Тепер завдання :environment вимагає dotenv/load, який завантажує змінні середовища. :create => :environment говорить про те, що :environment слід викликати раніше, ніж :create може бути викликаний. Таким чином ми гарантуємо, що змінні завантажуються і postgres має до них доступ.

Завдання :reset призначене для rake db:reset, який ми знаємо і любимо з Rails. Він видаляє БД і створює її заново.

Перехід на pg

Тепер ми можемо просто виконати:

rake db:create

і тепер у нас є наша база даних. Але наш застосунок все ще намагається використовувати sqlite-базу даних. Таким чином, ми швидко змінюємо деталі підключення в lib / initializers / database.rb:.

DB = Sequel.connect("postgres://#{ENV['PGHOST']}/#{ENV['PGDATABASE']}?user=#{ENV['PGUSER']}&password=#{ENV['PGPASS']}")

DB.create_table?(:books) do
  primary_key :id
  String :title
  String :author
end

Це поганий приклад, я визнаю, але це працює і точно показує, що відбувається.

Підбиваємо підсумки

Тепер у вас є власний маленький db-setup. І ви зробили все це самостійно! Без Rails! Випробуйте його, запустіть свій сервер, використовуючи rackup, та додайте кілька книг за допомогою curl (ви можете знову переглянути частину 1, щоб побачити, як це все точно працює), перезавантажте сервер і відвідайте http: // localhost: 9292 / books щоб побачити, що ваші книги збереглися.

І нам не доводилося нічого змінювати в нашому api.rb. Сила інкапсуляції, оуу-є!

Наступного разу

У нашому API відсутні декілька речей:

  1. Бізнес-логіка.
  2. Аутентифікація.
  3. Авторизація.
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.7K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація