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

5 хв. читання

Інтро

Після приблизно 4-5 років роботи з Ruby on Rails, я все ще отримую задоволення від роботи з цим фреймворком. Екосистема чудова, загальна архітектура задоволняє вимогам, а команда знає, як розвивати проект (ActionCable і API-режим в Rails 5 показують, що проект не намагається конкурувати з javascript-фреймворками).

Водночас шкодую, що застряг: я не можу написати простий веб-застосунок на Ruby без цього фреймворка. Тому я подумав, чому б не спробувати щось нове і не написати JSON-API повністю без Rails.

Вимоги

Наразі їх мінімум. Ми реалізуємо цифрову бібліотеку книг. Що нам потрібно зараз:

  1. Маршрутизація
  2. Обробка паролів
  3. Рендеринг JSON

Деякі речі, які я притримаю для наступних частин:

  1. БД
  2. Незалежна бізнес-логіка
  3. Аутентифікація
  4. Авторизація
  5. GUI
  6. Тести

Що буде використано

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

  1. Grape – JSON-API фреймворк для Ruby. Він має багато схожого з Rails. Він досить чітко визначений, але не нав'язує жорсткої структури та (у цьому прикладі) буде відповідати лише за маршрутизацію та обробку паролів.
  2. Rack для розгортання сервера.

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

Починаємо з простого

Щоб побачити, що ми могли б зробити, якби не використовували Grape, і щоб звикнути до ітераційної розробки, ми реалізуємо найпростіший JSON-API.

application = proc do
  json = { 'message' => 'Hello, world!' }
  header = { 'Content-Type' => 'application/json' }
  status = 200

  [status, header, [json.to_s]]
end

run application

Тепер ми можемо запустити наш сервер за допомогою rackup:

$ cd the-folder-where-the-config-file-is/
$ rackup
Puma starting in single mode...
* Version 3.10.0 (ruby 2.3.3-p222), codename: Russell's Teapot
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://localhost:9292
Use Ctrl-C to stop

Ми можемо побачити, працює він чи ні, використовуючи Curl:

$ curl http://localhost:9292
{"message"=>"Hello, world!"}

Що тут відбувається? Ми реалізували «Rack-інтерфейс», створивши об'єкт, який відповідає на #call (env) і повертає масив [status, headers, body]. «Зачекай!» – скажете ви. Де ми передаємо env? Ми не маємо необхідності використовувати proc. Це досить простий аспект Procs:

Для procs, створених з використанням lambda або -> (), виникає помилка, якщо неправильна кількість параметрів передається в proc. Для procs, створених з використанням Proc.new або Kernel.proc, додаткові параметри відкидаються, а відсутні параметри мають значення nil.

Основи Grape

Тепер у нас є робочий API! Але це досить нудно. І оскільки я хотів показати Grape, ми швидко замінимо наш Proc чимось складнішим. Оскільки ми поки не маємо ніякого реального управління залежностями, переконайтеся, що:

$ gem install grape

А потім замініть ваш proc на базовий API-інтерфейс grape:

# config.ru
require 'grape'

class MyApi < Grape::API
  get '/' do
    {message: 'hello'}
  end
end

run MyApi

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

# config.ru
require 'grape'

class MyApi < Grape::API
  get '/' do
    {message: 'hello'}
  end

  route_param :name do
    get do
      {message: "Hello #{params[:name]}!"}
    end
  end
end

run MyApi

Ми додали параметр маршруту name, який ми потім інтерпретувати в повідомлення. Ми можемо отримати доступ до всіх параметрів через об'єкт params, незалежно від того, чи є вони частиною маршруту чи ні. Тепер ми перезапускаємо наш сервер і вводимо curl:

$ curl http://localhost:9292/paul
{"message" => "Hello, paul!"}

Таким чином, ми можемо визначати маршрути та аналізувати параметри. Що далі? Нам варто подивитися, чи можемо ми відправити щось на сервер. Як щодо книги. І поки ми на ньому, ми повинні почати завантаження нашої логіки з config.ru.

# api.rb
class BookAPI < Grape::API
  format :json

  helpers do
    def books
      @books ||= []
    end
  end

  resource :books do
    get '/' do
      {books: books}
    end

    params do
      requires :title, type: String
      requires :author, type: String
    end
    post do
      books << { author: params[:author], title: params[:title] }
    end
  end
end

Ми використовуватимемо config.ru тільки для завантаження залежностей, а потім запустимо наш додаток.

# config.ru
require 'grape'

require_relative 'api'

run BookAPI

Ми перенесли API до іншого файлу та зробили кілька коригувань.

  1. Ми визначили books помічника, щоб відслідковувати всі книги.
  2. Ми використали resource метод для визначення наших маршрутів в області імен /books.
  3. Ми додали POST маршрут, щоб опублікувати книгу в наших книгах.
  4. Ми додали індекс маршруту, щоб побачити, якщо наш POST нічого не зробив.
  5. Ми додали format :json, щоб grape автоматично перетворював значення, які повертаються, в JSON.

Ви знаєте, що робити: перезапустіть сервер і отримайте curl:

$  curl --data "title=Lord of the Rings&author=J. R. R. Tolkien" http://localhost:9292/books
[{"author":"J. R. R. Tolkien","title":"Lord of the Rings"}]
$ curl http://localhost:9292/books
{"books":[]}

Загальна картина

Це не спрацювало. Причина полягає в тому, що аналогічно контролерам Rails, екземпляри Grape :: API не зберігаються через кілька запитів. Таким чином, @books скидається з кожним запитом. Що має сенс, якщо є кілька людей, які запитують кілька речей. Ви не хочете, щоб вони поділилися інформацією про умови запиту. Один з способів обійти це – мати об'єкт, який існує незалежно від API для обробки даних.

# app.rb
class MyApp
  def books
    @books ||= []
  end
end

Application = MyApp.new

Тут ми призначили екземпляр MyApp в ролі константи, і це дає нам змогу:

  1. Посилатися на нього глобально.
  2. Заборонити збирати сміття після кожного запиту.
# config.ru
require 'grape'

require_relative 'app'
require_relative 'api'

run BookAPI
# api.rb
class BookAPI < Grape::API
  format :json

  helpers do
    def books
      Application.books
    end
  end

  resource :books do
    get '/' do
      {books: books}
    end

    params do
      requires :title, type: String
      requires :author, type: String
    end
    post do
      books << { author: params[:author], title: params[:title] }
    end
  end
end

Після всіх цих рядків, наш сервер нарешті працює! Ура!

$  curl --data "title=Lord of the Rings&author=J. R. R. Tolkien" http://localhost:9292/books
[{"author":"J. R. R. Tolkien","title":"Lord of the Rings"}]
$ curl http://localhost:9292/books
{"books":[{"author":"J. R. R. Tolkien","title":"Lord of the Rings"}]}

Поки цього досить. В наступній частині ми додамо БД, використовуючи sequel та rake.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 869
Приєднався: 1 рік тому
Коментарі (0)

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

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

Вхід