5 Ruby labākā lietojuma piemēri
Vai esat kādreiz aizdomājušies, ko mēs varam darīt ar Ruby? Iespējams, debesis ir neierobežotas, taču mēs labprāt pastāstīsim par dažiem vairāk vai mazāk zināmiem gadījumiem...
Mēs izveidosim grāmatu plaukta lietojumprogrammu, lai uzskaitītu grāmatas ar (vai bez) autoru datiem.
Mēs izveidosim grāmatu plaukta lietotni, lai uzskaitītu grāmatas ar (vai bez) autoriem. dati. Būs viens #index darbība un dažas sēklas. Šī būs piemērs, lai parādītu, kā lietotājam var piešķirt kontroli pār iekļautajām programmām. apakšresursus REST-veida API.
ietver vaicājuma parametrs, lai ielādētu saistītos resursus (autors).ietver vaicājuma parametra formāts ir virkne: komatā atdalīti vārdi, kas apzīmē ievietotos resursus.Mēs izmantosim blueprinter kā serializatoru, jo tas ir formātneatkarīgs un diezgan elastīgs. Tas ir vienīgais dārgakmens, ko mēs pievienosim rails standarta rīku komplektam.
Izveidosim lietotnes piemēru. Mēs nepievienosim testēšanas ietvaru, jo tas ir ārpus mūsu darbības jomas.
sliedes jauns grāmatu plaukts -T
Tagad izveidojiet Autors modelis:
rails g modeļa autora nosaukums:string
#=> invoke active_record
#=> create db/migrate/2021122224084524_create_authors.rb
#=> create app/models/author.rb
Un Grāmatu:
rails g modelis grāmata autors:references nosaukums:string
# => invoke active_record
# => create db/migrate/2021122224084614_create_books.rb
# => create app/models/book.rb
Mums būs vajadzīgas dažas sēklas:
# db/seeds.rb
dumas = Author.create(name: 'Alexandre Dumas')
lewis = Author.create(name: 'C.S. Lewis')
martin = Author.create(name: 'Robert C. Martin')
Book.create(autors: dumas, nosaukums: 'Trīs musketieri')
Book.create(autors: lewis, nosaukums: 'The Lion, the Witch and the Wardrobe')
Book.create(autors: martin, nosaukums: 'Clean Code')
Tagad mēs esam gatavi migrāciju palaišanai un db sēklas izveidei:
rails db:migrate && rails db:seed
Pievienosim has_many par grāmatām Autors modelis:
# app/models/author.rb
klase Author < ApplicationRecord
has_many :books
end
Ir pienācis laiks uzrakstīt kontrolieri, kas atgriezīs mūsu datus. Mēs izmantosim API vārdu telpa, tāpēc vispirms pievienosim saīsinājumu locījumiem:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'API'
end
Labi, pievienosim mūsu serializatoru pie Dārgakmens fails:
# Pievienot dārgakmeņu datnei
gem 'blueprinter'
Un, protams, instalējiet to:
instalēt paketi
Tad mēs varam veidot savus plānus:
# app/blueprints/author_blueprint.rb
klase AuthorBlueprint < Blueprinter::Base
identifier :id
lauki :name
end
# app/blueprints/book_blueprint.rb
klase BookBlueprint < Blueprinter::Base
identifikators :id
lauki :title
asociācija :author, blueprint: AuthorBlueprint
beigas
Pievienojiet bāzes kontrolieri API:
# app/controllers/api/v1/base_controller.rb
modulis API
modulis V1
klase BaseController < ActionController::API
end
end
end
Un mūsu BooksController:
# app/controllers/api/v1/books_controller.rb
modulis API
modulis V1
klase BooksController < BaseController
def index
books = Book.all
render json: BookBlueprint.render(grāmatas)
end
end
end
end
Protams, mums ir jādefinē arī maršrutēšana:
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :books, only: :index
end
end
end
Pārbaudīsim līdz šim paveikto:
sliedes s
curl http://localhost:3000/api/v1/books
# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "Trīs musketieri"},{"id":2, "author":{"id":2, "name": "C.S. Lewis"}, "title": "The Lion, the Witch and the Wardrobe"},{"id":3, "author":{"id":3, "name": "Robert C. Martin"}, "title": "Clean Kods"}]
Dati, šķiet, ir kārtībā, bet kā ir ar žurnāliem?
# pieprasījumu žurnāli (n+1)
Sākts GET "/api/v1/books" 127.0.0.0.1 2021-12-24 10:19:40 +0100
API::V1::BooksController#index apstrādā kā */*
Grāmatu ielāde (0.1ms) SELECT "books".* FROM "books"
↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Autoru ielāde (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ? [[["id", 1], ["LIMIT", 1]]]
↳ app/controllers/api/v1/books_controller.rb:7:in `in index'
Autoru ielāde (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ? [[["id", 2], ["LIMIT", 1]]]
↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Autoru ielāde (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ? [[["id", 3], ["LIMIT", 1]]]
↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Pabeigts 200 OK pēc 6 ms (skatījumi: 0,1 ms | ActiveRecord: 0,4 ms | piešķīrumi: 3134)
Izmantojot asociāciju mūsu serializatoros, mēs ieviesām n+1 problēma. Mēs vēlamies to novērst, pievienojot lietotājam kontroli pār to, ko viņš pieprasa šajā galapunktā. Tātad viņam vajadzētu būt iespējai ielādēt tikai grāmatas vai arī nodot parametru includes un saņemt arī autorus, bet vēlams bez parametra n+1.
Definēsim konstanti, kas saglabās informāciju par to, kādus grāmatu asociāciju lietotājs var iekļaut grāmatas#index rīcība:
# lib/constants/books/includes.rb
modulis Constants
modulis Books
modulis Includes
ALLOWED = {
index: %i[
autors
].freeze
}.freeze
beigas
beigas
end
Tālāk mēs definējam nosaukumu telpu tukšo objektu konstantēm:
# lib/constants/empty.rb
modulis Constants
modulis Empty
HASH = {}.freeze
beigas
beigas
Un šeit ir mūsu galvenais pakalpojums atļauju izsniegšanai. Es domāju, ka kods ir diezgan pašsaprotams, dažas daļas burvju tiek piešķirti tikai #default_resources_key un #default_purpose. Šīs metodes ir definētas, lai mums lai izsauktu atļauju, ietver tikai parametru nodošanu rails’ kontrolieri. Izvades rezultāts būs hash, kas glabā patiess par katru atļauto iekļaušanu.
# app/services/permit_includes.rb
require 'constants/empty'
require 'constants/books/includes'
klase PermitIncludes
Empty = Constants::Empty
COMMA = ','
SLASH = '/'
INCLUDES_FORMAT = /A[a-z]+(,[a-z]+)*z/.freeze
PERMITTED_INCLUDES = {
grāmatas: Constants::Books::Includes::ALLOWED
}.freeze
def call(params, resources: default_resources_key(params), purpose: default_purpose(params))
return Empty::HASH unless includes_sent?(params)
return Empty::HASH, ja vien includes_valid?(params)
requested_includes = parse_includes(params)
allowed_includes = filter_includes(requested_includes, resources, purpose)
allowed_includes.index_with(true)
beigas
privāts
def default_resources_key(params)
raise(ArgumentError, 'params :controller key must be a string') unless params[:controller].is_a?(String)
params[:controller].split(SLASH).last&.to_sym
end
def default_purpose(params)
raise(ArgumentError, 'params :action key must be a string') unless params[:action].is_a?(String)
params[:action].to_sym
end
def includes_sent?(params)
params.key?(:includes)
end
def includes_valid?(params)
return false, ja vien params[:includes].is_a?(String)
params[:includes].match?(INCLUDES_FORMAT)
end
def parse_includes(params)
params[:includes].split(COMMA).map(&:to_sym)
end
def filter_includes(requested_includes, resources_key, purpose)
requested_includes & ALLOWED_INCLUDES[resources_key][purpose]
end
end
Tagad mums ir nepieciešams izmantot atslēgas, lai ielādētu iekļauj un nodot inlcudes hash pati serializer:
# app/controllers/api/v1/books_controller.rb
modulis API
modulis V1
klase BooksController < BaseController
def index
includes = PermitIncludes.new.call(params)
books = Book.includes(includes.keys).all
render json: BookBlueprint.render(books, includes: includes)
beigas
end
end
end
Un tas ir tas, kā mums ir jāpielāgo mūsu serializators - mēs ielādējam asociāciju tikai tad, ja tā ir iekļauta:
# app/blueprints/book_blueprint.rb
klase BookBlueprint (_field_name, _book, options) {
options[:includes] && options[:includes][:author]
}
end
Pārbaudīsim to vēlreiz:
sliedes s
curl http://localhost:3000/api/v1/books
# => [{"id":1, "title": "Trīs musketieri"},{"id":2, "title": "Lauva, ragana un drēbju skapis"},{"id":3, "title": "Tīrs kods"}]
# pieprasījumu žurnāli (mēs ielādējam tikai grāmatas)
Sākts GET "/api/v1/books" ::1 2021-12-24 10:33:41 +0100
Apstrādā API::V1::BooksController#index kā */*
(0.1ms) SELECT sqlite_version(*)
↳ app/controllers/api/v1/books_controller.rb:8:in `in index'
Grāmatu ielāde (0.1ms) SELECT "books".* FROM "books"
↳ app/controllers/api/v1/books_controller.rb:8:in `in `dex'
Izpildīts 200 OK pēc 9ms (skatījumi: 0,1ms | ActiveRecord: 0,9ms | piešķīrumi: 4548)
Labi, mēs neesam nodevuši ietver, tāpēc saņēmām tikai grāmatas, bez autoriem. Tagad pieprasīsim tos:
curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "Trīs musketieri"},{"id":2, "author":{"id":2, "name": "C.S. Lewis"}, "title": "The Lion, the Witch and the Wardrobe"},{"id":3, "author":{"id":3, "name": "Robert C. Martin"}, "title": "Clean Code"}]%
# pieprasījumu žurnāli (novērsts n+1)
Sākts GET "/api/v1/books?includes=author" attiecībā uz ::1 2021-12-24 10:38:23 +0100
Apstrādā API::V1::BooksController#index kā */*
Parametri: {"includes"=>"author"}
Grāmatu ielāde (0.1ms) SELECT "books".* FROM "books"
↳ app/controllers/api/v1/books_controller.rb:8:in `in index'
Autoru ielāde (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" IN (?, ?, ?) [["id", 1], ["id", 2], ["id", 3]]]
↳ app/controllers/api/v1/books_controller.rb:8:in `in index'
Pabeigts 200 OK pēc 17 ms (skatījumi: 0,1 ms | ActiveRecord: 0,7 ms | piešķīrumi: 7373)
Forši! Mēs saņēmām asociācija ielādēts un novērsta n+1 problēma. Pakalpojumu var izmantot jebkuram resursam, viss, ko mēs vēlamies darīt, ir pievienot atļautās konstantes pareizā formātā un pievienot tās pie PermitIncludes::ALLOWED_INCLUDES.
Jāatceras, ka tas, iespējams, ir jāizmanto kopā ar pagination (un piesardzīgi), jo asociāciju iekļaušana var “apēst” daudz atmiņas.