5 παραδείγματα της καλύτερης χρήσης της Ruby
Αναρωτηθήκατε ποτέ τι μπορούμε να κάνουμε με τη Ruby; Λοιπόν, ο ουρανός είναι μάλλον το όριο, αλλά είμαστε στην ευχάριστη θέση να μιλήσουμε για μερικές περισσότερο ή λιγότερο γνωστές περιπτώσεις...

Θα κατασκευάσουμε μια εφαρμογή βιβλιοθήκης για τη λίστα βιβλίων με (ή χωρίς) δεδομένα συγγραφέων.
Θα κατασκευάσουμε μια εφαρμογή βιβλιοθήκης για τη λίστα βιβλίων με (ή χωρίς) δεδομένα συγγραφέων. Θα υπάρχει ένα μόνο #index
δράση και μερικούς σπόρους. Αυτό θα είναι ένα παράδειγμα εφαρμογής για να δείξουμε πώς μπορείτε να δώσετε σε έναν χρήστη τον έλεγχο των περιεχόμενων υπο-πηγές σε ένα REST-ish API.
περιλαμβάνει
παράμετρος ερωτήματος για να φορτώσει τους σχετικούς πόρους (συγγραφέας
).περιλαμβάνει
Η παράμετρος ερωτήματος έχει μορφή συμβολοσειράς: λέξεις που χωρίζονται με κόμμα και αντιπροσωπεύουν ένθετους πόρους.Θα χρησιμοποιήσουμε blueprinter
ως σειροποιητή, επειδή είναι ανεξάρτητος από τη μορφή και αρκετά ευέλικτος. Αυτό είναι ένα μοναδικό κόσμημα που θα προσθέσουμε στην τυπική εργαλειοθήκη του rails.
Ας δημιουργήσουμε ένα παράδειγμα εφαρμογής. Δεν θα προσθέσουμε πλαίσιο δοκιμών, καθώς δεν είναι στο πεδίο εφαρμογής μας.
ράγες νέα βιβλιοθήκη -T
Τώρα δημιουργήστε Συγγραφέας
μοντέλο:
rails g model author name:string
#=> invoke active_record
#=> create db/migrate/20211224084524_create_authors.rb
#=> create app/models/author.rb
Και Βιβλίο
:
rails g model book συγγραφέας:αναφορές title:string
# => κλήση ενεργού_εγγραφής
# => δημιουργία db/migrate/20211224084614_create_books.rb
# => δημιουργία app/models/book.rb
Θα χρειαστούμε μερικούς σπόρους:
# 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(author: dumas, title: 'The Three Musketeers')
Book.create(author: lewis, title: 'The Lion, the Witch and the Wardrobe')
Book.create(author: martin, title: 'Clean Code')
Και τώρα είμαστε έτοιμοι να εκτελέσουμε τις μεταναστεύσεις και να σπείρουμε τη βάση δεδομένων:
rails db:migrate && rails db:seed
Ας προσθέσουμε has_many
για βιβλία σε Συγγραφέας
μοντέλο:
# app/models/author.rb
class Author < ApplicationRecord
has_many :books
end
Ήρθε η ώρα να γράψουμε έναν ελεγκτή που θα επιστρέφει τα δεδομένα μας. Θα χρησιμοποιήσουμε API
namespace, οπότε πρώτα ας προσθέσουμε ένα ακρωνύμιο στις κλίσεις:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'API'
end
Εντάξει, ας προσθέσουμε τον σειροποιητή μας στο Gemfile
:
# Προσθήκη στο Gemfile
gem 'blueprinter'
Και φυσικά να το εγκαταστήσετε:
εγκατάσταση δέσμης
Τότε μπορούμε να φτιάξουμε τα σχέδιά μας:
# app/blueprints/author_blueprint.rb
class AuthorBlueprint < Blueprinter::Base
αναγνωριστικό :id
πεδία :name
end
# app/blueprints/book_blueprint.rb
class BookBlueprint < Blueprinter::Base
αναγνωριστικό :id
πεδία :title
association :author, blueprint: AuthorBlueprint
end
Προσθέστε έναν βασικό ελεγκτή για API
:
# app/controllers/api/v1/base_controller.rb
ενότητα API
ενότητα V1
class BaseController < ActionController::API
end
end
end
Και το προσχέδιο της έκδοσής μας BooksController
:
# app/controllers/api/v1/books_controller.rb
ενότητα API
ενότητα V1
class BooksController < BaseController
def index
books = Book.all
render json: BookBlueprint.render(books)
end
end
end
end
Πρέπει επίσης να ορίσουμε τη δρομολόγηση φυσικά:
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :books, only: :index
end
end
end
Ας δοκιμάσουμε αυτό που έχουμε κάνει μέχρι τώρα:
ράγες s
curl http://localhost:3000/api/v1/books
# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "The Three Musketeers"},{"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 Κωδικός"}]
Τα δεδομένα φαίνονται να είναι εντάξει, αλλά τι γίνεται με τα αρχεία καταγραφής;
# αρχεία καταγραφής αιτήσεων (n+1)
Ξεκίνησε το GET "/api/v1/books" για 127.0.0.1 στις 2021-12-24 10:19:40 +0100
Επεξεργασία από API::V1::BooksController#index ως */*
Φόρτωση βιβλίων (0.1ms) SELECT "books".* FROM "books"
↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Φόρτωση συγγραφέων (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Φόρτωση συγγραφέων (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Φόρτωση συγγραφέων (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Ολοκληρώθηκε 200 OK σε 6ms (Views: 0.1ms | ActiveRecord: 0.4ms | Allocations: 3134)
Με τη χρήση της συσχέτισης στους σειροποιητές μας εισαγάγαμε n+1
πρόβλημα. Θέλουμε να το εξαλείψουμε προσθέτοντας στον χρήστη έναν έλεγχο για το τι ζητάει σε αυτό το τελικό σημείο. Έτσι θα πρέπει να μπορεί είτε να φορτώνει μόνο βιβλία, είτε να περνάει την παράμετρο includes και να παίρνει και συγγραφείς, αλλά κατά προτίμηση χωρίς το n+1
.
Ας ορίσουμε μια σταθερά που θα κρατάει μια πληροφορία σχετικά με το ποιες assocs των βιβλίων μπορεί να συμπεριλάβει ο χρήστης στο books#index
δράση:
# lib/constants/books/includes.rb
module Constants
module Βιβλία
module Περιλαμβάνει
ALLOWED = {
index: %i[
συγγραφέας
].freeze
}.freeze
τέλος
end
end
Στη συνέχεια, ορίζουμε έναν χώρο ονομάτων για κενές σταθερές αντικειμένων:
# lib/constants/empty.rb
module Constants
module Empty
HASH = {}.freeze
end
end
Και εδώ είναι η κύρια υπηρεσία μας για την αδειοδότηση περιλαμβάνει. Νομίζω ότι ο κώδικας είναι αρκετά αυτονόητος, μερικά κομμάτια του μαγικό
κατανέμονται μόνο σε #default_resources_key
και #default_purpose
. Αυτές οι μέθοδοι έχουν οριστεί για να μας επιτρέπουν να καλούμε την άδεια περιλαμβάνει περνώντας μόνο params στους ελεγκτές του rails. Η έξοδος θα είναι ο κατακερματισμός που αποθηκεύει true
για κάθε επιτρεπόμενη ένταξη.
# app/services/permit_includes.rb
require 'constants/empty'
require 'constants/books/includes'
class PermitIncludes
Empty = Constants::Empty
COMMA = ','
SLASH = '/'
INCLUDES_FORMAT = /A[a-z]+(,[a-z]+)*z/.freeze
ALLOWED_INCLUDES = {
books: 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 unless includes_valid?(params)
requested_includes = parse_includes(params)
allowed_includes = filter_includes(requested_includes, resources, purpose)
allowed_includes.index_with(true)
end
private
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 unless 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
Τώρα πρέπει να χρησιμοποιήσουμε τα κλειδιά για να φορτώσουμε τα includes και να περάσουμε το ίδιο το hash των inlcudes στον serializer:
# app/controllers/api/v1/books_controller.rb
ενότητα API
ενότητα V1
class BooksController < BaseController
def index
includes = PermitIncludes.new.call(params)
books = Book.includes(includes.keys).all
render json: BookBlueprint.render(books, includes: includes)
end
end
end
end
Και έτσι πρέπει να προσαρμόσουμε τον σειροποιητή μας - φορτώνουμε τη συσχέτιση μόνο αν περιλαμβάνεται:
# app/blueprints/book_blueprint.rb
class BookBlueprint (_field_name, _book, options) {
options[:includes] && options[:includes][:author]
}
end
Ας το ξαναδοκιμάσουμε:
ράγες s
curl http://localhost:3000/api/v1/books
# => [{"id":1, "title": "The Three Musketeers"},{"id":2, "title": "The Lion, the Witch and the Wardrobe"},{"id":3, "title": "Clean Code"}]
Ημερολόγια αιτήσεων # (φορτώνουμε μόνο βιβλία)
Ξεκίνησε το GET "/api/v1/books" για ::1 στις 2021-12-24 10:33:41 +0100
Επεξεργασία από το API::V1::BooksController#index ως */*
(0.1ms) SELECT sqlite_version(*)
↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Φόρτωση βιβλίων (0.1ms) SELECT "books".* FROM "books"
↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Ολοκληρώθηκε 200 OK σε 9ms (Views: 0.1ms | ActiveRecord: 0.9ms | Allocations: 4548)
Ωραία, δεν έχουμε περάσει τα περιεχόμενα, οπότε έχουμε μόνο βιβλία, χωρίς συγγραφείς. Ας τα ζητήσουμε τώρα:
curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "The Three Musketeers"},{"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"}]%
Αρχεία καταγραφής αιτήσεων # (εξαλείφθηκε n+1)
Ξεκίνησε το GET "/api/v1/books?includes=author" για ::1 στις 2021-12-24 10:38:23 +0100
Επεξεργασία από το API::V1::BooksController#index ως */*
Παράμετροι: {"includes"=>"author"}
Φόρτωση βιβλίων (0.1ms) SELECT "books".* FROM "books"
↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Φόρτωση συγγραφέων (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 `index'
Ολοκληρώθηκε 200 OK σε 17ms (Views: 0.1ms | ActiveRecord: 0.7ms | Allocations: 7373)
Ωραία! Φορτώσαμε την ένωση και εξαλείψαμε n+1
πρόβλημα. Η υπηρεσία μπορεί να χρησιμοποιηθεί για οποιονδήποτε πόρο, το μόνο που θέλουμε να κάνουμε είναι να προσθέσουμε τις επιτρεπόμενες σταθερές στην κατάλληλη μορφή και να τις προσθέσουμε στο PermitIncludes::ALLOWED_INCLUDES
.
Πρέπει να θυμόμαστε ότι αυτό θα πρέπει μάλλον να χρησιμοποιείται με σελιδοποίηση (και προσοχή), επειδή η συμπερίληψη συσχετίσεων μπορεί να "φάει" πολλή μνήμη.