window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster już istnieje') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Polimorfizm w Ruby i GraphQL - The Codest
The Codest
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Branże
    • Fintech i bankowość
    • E-commerce
    • Adtech
    • Healthtech
    • Produkcja
    • Logistyka
    • Motoryzacja
    • IOT
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
Strzałka w tył WSTECZ
2022-01-13
Software Development

Polimorfizm w Ruby i GraphQL

Łukasz Brzeszcz

W tym artykule przedstawię zastosowanie polimorfizmu w GraphQL. Zanim jednak zacznę, warto przypomnieć czym są polimorfizm i GraphQL.

Polimorfizm

Polimorfizm jest kluczowym składnikiem programowanie obiektowe. Upraszczając, opiera się ona na fakcie, że obiekty różnych klas mają dostęp do tego samego interfejsu, co oznacza, że od każdego z nich możemy oczekiwać tej samej funkcjonalności, ale niekoniecznie zaimplementowanej w ten sam sposób. W Programiści Ruby może uzyskać polimorfizm na trzy sposoby:

Dziedziczenie

Dziedziczenie polega na utworzeniu klasy nadrzędnej i klas podrzędnych (tj. dziedziczących z klasy nadrzędnej). Podklasy otrzymują funkcjonalność klasy nadrzędnej, a także umożliwiają zmianę i dodanie funkcjonalności.

Przykład:

class Document
  attr_reader :name
koniec

class PDFDocument < Dokument
  def extension
    pdf
  end
end

class ODTDocument < Document
  def extension
    odt
  end
end

Moduły

Moduły w Ruby mają wiele zastosowań. Jednym z nich są mixiny (przeczytaj więcej o mixinach w Ostateczny podział: Ruby vs. Python). Mixiny w Rubim mogą być używane podobnie do interfejsów w innych językach programowania (np. w Java) można na przykład zdefiniować w nich metody wspólne dla obiektów, które będą zawierały dany mixin. Dobrą praktyką jest umieszczanie w modułach metod tylko do odczytu, czyli takich, które nie będą modyfikować stanu tego obiektu.

Przykład:

moduł Taxable
  def tax

     cena * 0.23
  koniec
koniec

class Samochód
  include Taxable
 attr_reader :price
end

class Book
  include Taxable

 attr_reader :price
koniec

Pisanie na klawiaturze

Jest to jedna z kluczowych cech języków dynamicznie typowanych. Nazwa pochodzi od słynnego testu: jeśli wygląda jak kaczka, pływa jak kaczka i kwacze jak kaczka, to prawdopodobnie jest kaczką. Test programista nie musi interesować się, do jakiej klasy należy dany obiekt. Ważne są metody, które można wywołać na tym obiekcie.

Korzystając z klas zdefiniowanych w powyższym przykładzie:

class Samochód
  attr_reader :price

 def initialize(price)
    @price = price
   end
end

class Book
  attr_reader :price

 def initialize(price)
    @price = price
  end
end

car = Car.new(20.0)
book = Book.new(10.0)

[car, book].map(&:price

GrapQL

GraphQL to stosunkowo nowy język zapytań dla interfejsów API. Jego zalety obejmują fakt, że ma bardzo prostą składnię, a ponadto klient decyduje, co dokładnie chce uzyskać, ponieważ każdy klient otrzymuje dokładnie to, czego chce i nic więcej.

Przykładowe zapytanie w GraphQL:

{
  allUsers {
     users {
        id
        login
        email

       }
     }
   }

Przykładowa odpowiedź:

{
  "allUsers": {
    "users": [
     {
        "id": 1,
        "login": "user1",
        "email": "[email protected]"
      },
      {
        "id": 2,
        "login": "user2",
        "email": "[email protected]"
      },
    ]
  }
}

To chyba wszystko, co powinniśmy wiedzieć w tym momencie. Przejdźmy więc do rzeczy.

Przedstawienie problemu

Aby jak najlepiej zrozumieć problem i jego rozwiązanie, stwórzmy przykład. Dobrze by było, gdyby przykład był zarówno oryginalny, jak i dość przyziemny. Taki, z którym każdy z nas może się kiedyś zetknąć. Może... zwierzęta? Tak! Świetny pomysł!

polimorfizm ruby i grapql - zwierzęta

Załóżmy, że mamy aplikację backendową napisaną w języku Ruby on Rails. Jest on już przystosowany do obsługi powyższego schematu. Załóżmy również, że mamy już GraphQL skonfigurowany. Chcemy umożliwić klientowi złożenie zapytania w ramach następującej struktury:

{
 allZoos : {
    zoo: {
      nazwa
      miasto
      zwierzęta: {
        ...
      }
    }
  }
}

Co należy wstawić zamiast trzech kropek, aby uzyskać brakujące informacje - dowiemy się później.

Wdrożenie

Poniżej przedstawię kroki potrzebne do osiągnięcia tego celu.

Dodawanie zapytania do QueryType

Najpierw należy zdefiniować, co dokładnie oznacza zapytanie allZoos. Aby to zrobić, musimy odwiedzić plikapp/graphql/types/query_type.rb i zdefiniuj zapytanie:

   moduł Typy
      class QueryType < Types::BaseObject
       field :all_zoos, [Types::ZooType], null: false

       def all_zoos
          Zoo.all
       end
    end
 end

Zapytanie jest już zdefiniowane. Teraz nadszedł czas, aby zdefiniować typy zwracane.

Definicja typów

Pierwszym wymaganym typem będzie ZooType. Zdefiniujmy go w pliku app/graphql/types/ zoo_type.rb:

moduł Typy
  class ZooType < Types::BaseObject
    pole :name, String, null: false
    field :city, String, null: false
    field :animals, [Types::AnimalType], null: false
  end
end

Teraz nadszedł czas na zdefiniowanie typu AnimalType:

moduł Typy
  class AnimalType < Types::BaseUnion
   possible_types ElephantType, CatType, DogType

     def self.resolve_type(obj, ctx)
       if obj.is_a?(Elephant)
          ElephantType
       elsif obj.is_a?(Cat)
         CatType
       elsif obj.is_a?(Dog)
        DogType
      end
    end
  end
end

Co widzimy w kod powyżej?

  1. AnimalType dziedziczy po Types::BaseUnion.
  2. Musimy wymienić wszystkie typy, które mogą tworzyć dany związek.
    3.zastępujemy funkcję self.resolve_object(obj, ctx),która musi zwracać typ danego obiektu.

Następnym krokiem jest zdefiniowanie typów zwierząt. Wiemy jednak, że niektóre pola są wspólne dla wszystkich zwierząt. Uwzględnijmy je w typie AnimalInterface:

moduł Typy
  moduł AnimalInterface
    include Types::BaseInterface

    field :name, String, null: false
    field :age, Integer, null: false
  end
end

Mając ten interfejs, możemy przystąpić do definiowania typów konkretnych zwierząt:

moduł Typy
  class ElephantType < Types::BaseObject
    implementuje Types::AnimalInterface

    field :trunk_length, Float, null: false
  end
end

moduł Typy
  class CatType < Types::BaseObject
   implementuje Types::AnimalInterface

   pole :hair_type, String, null: false
  end
end

moduł Typy
  class DogType < Types::BaseObject
    implementuje Types::AnimalInterface

     field :breed, String, null: false
  end
end

To jest to! Gotowe! Ostatnie pytanie: jak możemy wykorzystać to, co zrobiliśmy od strony klienta?

Tworzenie zapytania

{
 allZoos : {
   zoo: {
      nazwa
      miasto
      zwierzęta: {
        __typename

        ... on ElephantType {
          nazwa
          wiek
          trunkLength
        }

         ... on CatType {
          nazwa
          wiek
          hairType
         }
         ... on DogType {
          nazwa
          wiek
          rasa
         }
       }
     }
   }
 }

Możemy tutaj użyć dodatkowego pola __typename, które zwróci dokładny typ danego elementu (np. CatType). Jak będzie wyglądać przykładowa odpowiedź?

{
  "allZoos": [

   {
      "name": "Natura Artis Magistra",
      "city": "Amsterdam",
      "zwierzęta": [
        {
          "__typename": "ElephantType"
          "name": "Franco",
          "age": 28,
          "trunkLength": 9.27
         },
         {
         "__typename": "DogType"
         "name": "Jack",
         "age": 9,
         "breed": "Jack Russell Terrier"
        },
      ]
    }
  ]
} 

Analiza

Widoczna jest jedna wada tego podejścia. W zapytaniu musimy wpisać nazwę i wiek w każdym typie, mimo że wiemy, że wszystkie zwierzęta mają te pola. Nie jest to uciążliwe, gdy kolekcja zawiera zupełnie różne obiekty. W tym przypadku jednak zwierzęta mają prawie wszystkie pola wspólne. Czy można to jakoś poprawić?

Oczywiście! Dokonujemy pierwszej zmiany w pliku app/graphql/types/zoo_type.rb:

moduł Typy
  class ZooType < Types::BaseObject
    pole :name, String, null: false
    field :city, String, null: false
    field :animals, [Types::AnimalInterface], null: false
  end
end

Nie potrzebujemy już związku, który zdefiniowaliśmy wcześniej. Zmieniamy się Types::AnimalType do Types::AnimalInterface.

Następnym krokiem jest dodanie funkcji zwracającej typ z pliku Typy :: AnimalInterface a także dodać listę orphan_types, czyli typów, które nigdy nie są bezpośrednio używane:

moduł Typy
  moduł AnimalInterface
    include Types::BaseInterface

   field :name, String, null: false
   field :age, Integer, null: false

   definition_methods do
      def resolve_type(obj, ctx)
        if obj.is_a?(Elephant)
          ElephantType
        elsif obj.is_a?(Cat)
          CatType
        elsif obj.is_a?(Dog)
          DogType
        end
      end
    end
    orphan_types Types::ElephantType, Types::CatType, Types::DogType
  end
end

Dzięki temu prostemu zabiegowi zapytanie ma mniej skomplikowaną formę:

{
  allZoos : {
   zoo: {
      nazwa
      miasto
      zwierzęta: {
        __typename
        nazwa
        wiek

       ... on ElephantType {
          trunkLength

       }
       ... on CatType {
          hairType

       }
       ... on DogType {
          rasa

        }
      }
    }
  }
}

Podsumowanie

GraphQL to naprawdę świetne rozwiązanie. Jeśli jeszcze go nie znasz, spróbuj. Zaufaj mi, warto. Świetnie radzi sobie z rozwiązywaniem problemów pojawiających się np. w REST API. Jak pokazałem powyżej, polimorfizm nie jest dla niego prawdziwą przeszkodą. Przedstawiłem dwie metody radzenia sobie z tym problemem.
Przypomnienie:

  • Jeśli operujesz na liście obiektów o wspólnej bazie lub wspólnym interfejsie - użyj interfejsów,
  • Jeśli operujesz na liście obiektów o innej strukturze, użyj innego interfejsu - użyj unii

Czytaj więcej

GraphQL Ruby. Co z wydajnością?

Szyny i inne środki transportu

Rails Development z TMUX, Vim, Fzf + Ripgrep

Powiązane artykuły

Software Development

Tworzenie przyszłościowych aplikacji internetowych: spostrzeżenia zespołu ekspertów The Codest

Odkryj, w jaki sposób The Codest wyróżnia się w tworzeniu skalowalnych, interaktywnych aplikacji internetowych przy użyciu najnowocześniejszych technologii, zapewniając płynne doświadczenia użytkowników na wszystkich platformach. Dowiedz się, w jaki sposób nasza wiedza napędza transformację cyfrową i biznes...

THEECODEST
Software Development

10 najlepszych firm tworzących oprogramowanie na Łotwie

Dowiedz się więcej o najlepszych łotewskich firmach programistycznych i ich innowacyjnych rozwiązaniach w naszym najnowszym artykule. Odkryj, w jaki sposób ci liderzy technologiczni mogą pomóc w rozwoju Twojej firmy.

thecodest
Rozwiązania dla przedsiębiorstw i scaleupów

Podstawy tworzenia oprogramowania Java: Przewodnik po skutecznym outsourcingu

Zapoznaj się z tym niezbędnym przewodnikiem na temat skutecznego tworzenia oprogramowania Java outsourcing, aby zwiększyć wydajność, uzyskać dostęp do wiedzy specjalistycznej i osiągnąć sukces projektu z The Codest.

thecodest
Software Development

Kompletny przewodnik po outsourcingu w Polsce

Wzrost liczby outsourcing w Polsce jest napędzany przez postęp gospodarczy, edukacyjny i technologiczny, sprzyjający rozwojowi IT i przyjazny klimat dla biznesu.

TheCodest
Rozwiązania dla przedsiębiorstw i scaleupów

Kompletny przewodnik po narzędziach i technikach audytu IT

Audyty IT zapewniają bezpieczne, wydajne i zgodne z przepisami systemy. Dowiedz się więcej o ich znaczeniu, czytając cały artykuł.

The Codest
Jakub Jakubowicz CTO & Współzałożyciel

Subskrybuj naszą bazę wiedzy i bądź na bieżąco!

    O nas

    The Codest - Międzynarodowa firma programistyczna z centrami technologicznymi w Polsce.

    Wielka Brytania - siedziba główna

    • Office 303B, 182-184 High Street North E6 2JA
      Londyn, Anglia

    Polska - lokalne centra technologiczne

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polska

      The Codest

    • Strona główna
    • O nas
    • Nasze Usługi
    • Case Studies
    • Nasze Know How
    • Kariera
    • Słownik

      Nasze Usługi

    • Konsultacje IT
    • Software Development
    • Backend Development
    • Frontend Development
    • Zespoły IT
    • Backend Dev
    • Inżynierowie rozwiązań chmurowych
    • Inżynierowie danych
    • Inne
    • Inżynierowie QA

      Raporty

    • Fakty i mity na temat współpracy z zewnętrznym partnerem programistycznym
    • Z USA do Europy: Dlaczego amerykańskie startupy decydują się na relokację do Europy?
    • Porównanie centrów rozwoju Tech Offshore: Tech Offshore Europa (Polska), ASEAN (Filipiny), Eurazja (Turcja)
    • Jakie są największe wyzwania CTO i CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Warunki korzystania z witryny

    Copyright © 2025 by The Codest. Wszelkie prawa zastrzeżone.

    pl_PLPolish
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek pl_PLPolish