Kolekcje

4

procent przetłumaczone

W tym rozdziale dowiesz się:

  • O reaktywnych kolekcjach, fundamentalnej funkcjonalności Meteora.
  • Zrozumiesz jak działa synchronizacja danych w Meteorze.
  • Zintegrujesz kolekcje z szablonami.
  • Jak przekształcić nasz podstawowy prototyp w prawdziwą aplikację czasu rzeczywistego!
  • W pierwszym rozdziale rozmawialiśmy o podstawowej funkcjonalności Meteorą jaką jest synchronizacja danych między klientem i serwerem.

    W bieżącym rozdziale przyjrzymy się bliżej tej funkcjonalności i sposobie działania kluczowego elementu technologii, która to umożliwia, kolekcji Meteora.

    Budujemy aplikację społeczonościową wyświetlającą wiadomości, zatem pierwszą rzeczą, którą chcemy osiągnąć, jest stworzenie listy listy linków do postów. Nazwiemy każdy z tych linków “postem”.

    Oczywiście potrzebujemy te posty gdzieś przechowywać. Meteor domyślnie pracuje z bazą danych MongoDB, która jest uruchomiona po stronie serwera i jest twoim stałym magazynem danych.

    Z tego względu, mimo to, że przeglądarka może przechowywać pewien stan aplikacji (na przykład bieżącą stronę, lub aktualnie wpisywany komentarz), serwer, a szczególniej Mongo zawiera stały, kanoniczny magazyn danych. Przez kanoniczny rozumiemy, że jest taki sam dla wszystkich użytkowników: każdy użytkownik może być na innej stronie, ale główna lista postów jest taka sama dla wszystkich użykowników.

    Te dane są przechowane w Kolekcji Meteora. Kolekcja jest specjalną strukturą danych, która poprzez publikacje i subskrypcje troszczy się o synchronizację danych w czasie rzeczywistym między każdą podłączoną przeglądarką po stronie klienta i bazą danych Mongo. Przyjrzyjmy się temu bliżej.

    Chcemy, aby nasze posty były stałe i aby były współdzielone między wszystkich użytkowników, zatem zaczniemy od utworzenia kolekcji Posts, w której będziemy je przechowywali. Jeżeli jeszcze tego nie zrobiłeś, utwórz folder collections/ w głównym folderze aplikacji i plik posts.js w tym folderze. Następnie dodaj:

    Posts = new Meteor.Collection('posts');
    
    collections/posts.js

    Zatwierdź 4-1

    Dodano kolekcję posts.

    Kod, który nie znajduje się ani w folderze client/ ani w server/ będzie uruchamiany po obu stronach. Zatem kolekcja Posts jest dostępna zarówno po stronie klienta i serwera. Jednakże, sposób działania kolekcji po stronach znacznie różni się od siebie.

    Używać Var czy nie?

    W Meteorze słowo kluczowe var ogranicza zagres obiektu do bieżącego pliku. Ponieważ chcemy udostępnić kolekcję Posts całej aplikacji, nie używamy słowa kluczowego var.

    Po stronie serwera kolekcja kontaktuje się z bazą danych Mongo, czyta i zapisuje dowolne zmiany. W ten sposób może być porównana do standardowej biblioteki bazy danych. Po stroni klienta kolekcja jest bezpieczną kopią pozdbioru prawdziwej, kanonicznej kolekcji. Kolekcja po stronie klienta jest na bieżąco i (w większości) niewidowicznie uaktualniana w czasie rzeczywistym z oznaczonym podzbiorem danych.

    Konsola vs Konsola vs Konsola

    W niniejzym rozdziale zaczniemy korzystać z konsoli przeglądarki, której nie należy mylić z terminalem czy konsolą Mongo. Poniżej znajdziesz szybkie wprowadzenie do każdej z nich.

    Terminal

    Terminal
    Terminal
    • Wołany na poziomie systemu operacyjnego
    • Serwerowe funkcje console.log() wypisują tutaj informacje.
    • Tzw. znak zachęty to: $. (Linux, Mac OSX)
    • Znany również jako: Shell, Bash

    Konsola przeglądarki

    The Browser Console
    The Browser Console
    • Wołana z poziomu przeglądarki, uruchamia kod JavaScript
    • Klienckie funkcje console.log() wypisują tutaj informacje.
    • Znak zachęty to: .
    • Znana również jako: Konsola JavaScript, Konsola DevTools

    Konsola Mongo

    Konsola Mongo
    Konsola Mongo
    • Uruchamiana z terminala za pomocą meteor mongo lub mrt mongo.
    • Daje bezpośredni dostęp do bazy danych aplikacji.
    • Znak zachęty: >.
    • Również znana jako: Mongo Shell

    Zauważ, że w każdym przypadku, nie jest wymagane wpisywanie znaku zachęty ($, , czy >), jako części komendy. Możesz także przyjąć, że jakakolwiek linia nie zaczynająca się od znaku zachęty jest wynikiem wywołania poprzedniej komendy.

    Kolekcje po stronie serwera

    Po stronie serwera kolekcja działa jako API dla bazy danych Mongo. Pozwala to na pisanie w Twoim kodzie wykonywanym na serwerze poleceń takich jak Posts.insert() czy Posts.update(), które zmienią dane w kolekcji posts zapisanej w bazie danych Mongo.

    Aby zajrzeć bezpośrednio do bazy danych Mongo, otwórz drugie okno terminala (podczas gdy meteor jest uruchomiony w pierwszym oknie) i przejdź do głównego folderu aplikacji. Następnie wykonaj polecenie meteor mongo aby uruchomić konsolę Mongo, w której możesz wpisywać standardowe polecenia Mongo (jak zwykle możesz opuścić terminal korzystając z kombinacji klawiszy ctrl+c. Przykładowo wstawmy nowy post do bazy danych:

    > db.posts.insert({title: "A new post"});
    
    > db.posts.find();
    { "_id": ObjectId(".."), "title" : "A new post"};
    
    Konsola Mongo

    Mongo na Meteor.com

    Zauważ, że gdy umieszczasz swoją aplikację na *.meteor.com, możesz mieć również dostęp do produkcyjnej bazy danych za pomocą polecenia meteor mongo mojaAplikacja.

    Możesz również przeczytać logi aplikacji przez wpisanie polecenia meteor logs mojaAplikacja.

    Składnia Mongo wydaje się być znajoma, pownieważ używa interfejsu Javascript. Nie będziemy przeprowadzali kolejnych zmian w konsoli Mongo, ale od czasu do czasu sprawdzimy czy oczekiwane dane faktycznie znajdują się w bazie.

    Kolekcje po stronie klienta

    Kolekcje stają się bardziej interesujące po stronie klienta. Gdy deklarujesz Posts = new Meteor.Collection('posts'); tworzysz po stronie klienta lokalny, dostępny z poziomu przeglądarki, cache prawdziwej kolekcji znajdującej się w bazie danych Mongo. Gdy wspominamy, że kolekcje po stronie klienta działają jako cache, mamy na myśli, że zawierają podzbiór danych oraz bardzo szybki dostęp do tych danych.

    Ważne jest, aby zrozumieć to w tym miejscu, ponieważ jest to fundamentalna zasada działania Meteora. Ogólnie rzecz biorąc, kolekcje po stronie klienta składają się z podzbioru wszystkich dokumentów zapisanych w kolekcji Mongo (nie chcemy przecież wysyłać całej bazy danych klientowi).

    Po drugie, te dokumenty są przechowywane w pamięci przeglądarki, co oznacza że dostęp do nich jest praktycznie natychmiastowy. Nie ma zatem powolnego oczekiwania na odpowiedź serwera lub bazy danych na pobranie danych po zawołaniu Posts.find() po stronie klienta, ponieważ dane są już załadowane i dostępne.

    Wprowadzenie do MiniMongo

    Implementacja bazy danych Mongo po stronie klienta ma nazwę MiniMongo. Nie jest to jeszcze doskonała implementacja i można napotkać pewne ograniczenia, które nie działają jeszcze w Minimongo. Mimo wszystko, wszystkie pojęcia opisane w tej książce działają podobnie w Mongo i MiniMongo.

    Komunikacja między Klientem i Serwerem

    Kluczowym zagadnieniem jest sposób, w jaki kolekcja po stronie klienta synchronizuje dane z kolekcją serwerową o tej samej nazwie (w naszym przypadku 'posts').

    Zamiast wyjaśniać to szczegółowo zobaczmy co dzieje się podczas synchronizacji.

    Otwórz dwa okna przeglądarki i uruchom konsolę przeglądarki w każdej z nich. Następnie uruchom konsolę Mongo z linii komend. W tym momencie, powinieneś zobaczyć w każdym z trzech miejsc pojedyńczy dokument, który utworzyliśmy wcześniej.

    > db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    
    Konsola Mongo
     Posts.findOne();
    {title: "A new post", _id: LocalCollection._ObjectID};
    
    Konsola pierwszej przeglądarki

    Utwórzmy nowy post. W jednym z okien przeglądarki wykonaj komendę insert:

     Posts.find().count();
    1
     Posts.insert({title: "A second post"});
    'xxx'
     Posts.find().count();
    2
    
    Konsola pierwszej przeglądarki

    Bez większych niespodzianek post został dodany do lokalnej kolekcji. Sprawdźmy teraz bazę Mongo:

    ❯ db.posts.find();
    {title: "A new post", _id: ObjectId("..")};
    {title: "A second post", _id: 'yyy'};
    
    Konsola Mongo

    Jak widać, post został przeniesiony z powrotem do bazy danych Mongo po stronie serwera bez potrzeby pisania pojedyńczej linii kodu (tak naprawdę to napisaliśmy jedną linię kodu: new Meteor.Collection('posts')). Ale to nie wszystko!

    Przejdź do drugiej instancji przeglądarki i wpisz w konsoli:

     Posts.find().count();
    2
    
    Druga konsola przeglądarki

    Post również jest tutaj dostępny! Pomimo tego, że nie odświeżyliśmy strony czy w jakimkolwiek stopniu działaliśmy z drugą przeglądarką, jak również nie napisaliśmy nowego kodu, który miałby przesłać uaktualnione dane. Wszystko to stało się automagicznie – oraz natychmiastowo, stanie się to oczywiste później.

    Co dokładnie się stało - nasza kolekcja po stronie serwera została poinformowana przez klienta o nowym poście i przejęła zadanie dystrybucji powyższego posta do bazy danych Mongo i do wszystkich podłączonych kolekcji post.

    Czytanie postów z poziomu konsoli przeglądarki nie jest za bardzo użyteczne. Nauczymy się, jak przekazać dane szablonom i konsekwentnie przekształcić nasz pierwszy prototyp HTML w aplikację webową działającą w czasie rzeczywistym.

    Działanie w czasie rzeczywistym

    Obserwacja zawartości kolekcji w konsoli przeglądarki to jedno, a wyświetlanie i zmienianie danych na ekranie, to całkiem co innego. Aby to osiągnąć przekształcimy naszą aplikację z prostej strony wyświetlającej statyczne dane w aplikację webową czasu rzeczywistego z dynamicznymi, zmieniającymi się danymi.

    Spójrzmy poniżej, jak to osiągnąć.

    Zapełnienie bazy danych

    Pierwszą rzeczą, którą zrobimy, będzie wstawienie danych do bazy danych. Osiągniemy to za pomocą pliku zawierającego dane początkowe (ang. fixture). Plik zawiera uporządkowany zbiór danych, który zostanie wstawiony do bazy podczas pierwszego uruchomienia serwera.

    Upewnijmy się najpierw, że baza danych jest pusta. Użyjemy polecenia meteor reset, które opróżnia bazę danych i resetuje cały projekt. Oczywiście musisz być bardzo ostrożny z wykonywaniem tej komendy gdy zaczniesz pracę z prawdziwymi projektami.

    Zatrzymaj serwer Meteora (za pomocą ctrl-c), a następnie wykonaj z linii komend polecenie:

    $ meteor reset
    

    Polecenie reset całkowicie usuwa bazę danych Mongo. Jest to użyteczne polecenie podczas implementacji aplikacji, gdzie istnieje duże prawdopodobieństwo wprowadzenia bazy danych w niespójny stan.

    Teraz, skoro baza danych jest pusta, możemy dodać poniższy kod, który wczyta trzy posty po każdym starcie serwerea gdy kolekcja Posts jest pusta:

    if (Posts.find().count() === 0) {
      Posts.insert({
        title: 'Introducing Telescope',
        author: 'Sacha Greif',
        url: 'http://sachagreif.com/introducing-telescope/'
      });
    
      Posts.insert({
        title: 'Meteor',
        author: 'Tom Coleman',
        url: 'http://meteor.com'
      });
    
      Posts.insert({
        title: 'The Meteor Book',
        author: 'Tom Coleman',
        url: 'http://themeteorbook.com'
      });
    }
    
    server/fixtures.js

    Zatwierdź 4-2

    Dodane dane do kloekcji posts.

    Umieściliśmy powyższy plik w folderze server/, zatem nie zostanie on nigdy załadowany do przeglądarki użytkownika. Kod zostanie natychmiastowo uruchomiony po starcie serwera i wykona funkcje insert, które dodadzą trzy proste posty do kolekcji Posts. Ponieważ nie zatroszczyliśmy się jeszcze o bezpieczeństwo danych, nie ma żadnej różnicy między uruchomieniem tych poleceń po stronie serwera i klienta.

    Teraz uruchom ponownie serwer za pomocą polecenia meteor i powyższe trzy posty zostaną wprowadzone do bazy danych.

    Przekazanie danych z bazy do HTML za pomocą helperów szablonu

    Teraz, gdy otworzymy konsolę przeglądarki, zobaczymy wszystkie trzy posty wczytane do MiniMongo:

     Posts.find().fetch();
    
    Konsola przeglądarki

    Aby przekształcić te dane w renderowany HTML, możemy użyć funkcji pomocniczych szablonu (helper). W rozdziale 3 zobaczyliśmy jak Meteor pozwala na połączenie kontekstu danych z szablonami Handlebards aby budować strony zawierające proste struktury danych. W ten sam sposób możemy je połączyć z danymi z kolekcji. Po prostu zamienimy statyczny obiekt postsData przez dynamiczną kolekcję.

    Skoro o tym mowa, możesz usunąć kod postsData. Oto jak posts_list.js powinien teraz wyglądać:

    Template.postsList.helpers({
      posts: function() {
        return Posts.find();
      }
    });
    
    client/views/posts/posts_list.js

    Zatwierdź 4-3

    Połączenie kolekcji z szablonem `postsList`.

    Znajdowanie i pobieranie danych z bazy

    Funkcja find() w Meteorze zwraca kursor, który jest reaktywnym źródłem danych. Jeżeli chemy pobrać jego zawartość, możemy użyć funkcję fetch() na tym kursorze, która przekształci go w tablicę z danymi.

    W obrębie aplikacji Meteor jest sprytny na tyle, aby iterować po kursorze bez potrzeby jawnego przekształcania go w tablicę. Z tego powodu nie zobaczysz pojawiającej się często w kodzie Meteora funkcji fetch() (i jest to powód, dla którego nie użyliśmy jej w powyższym przykładzie).

    Teraz, zamiast wczytywać statyczną tablicę z listą postów za pomocą zmiennej, zwracamy kursor do helpera posts. Ale co tym sposobem osiągneliśmy? Jeżeli wrócimy do przeglądarki, zobaczymy:

    Użycie zmiennych danych
    Użycie zmiennych danych

    Widzimy wyraźnie, że helper {{#each}} przeiterował wszystkie dostępne dane z kolekcji Posts i wyświetlił je na ekranie. Kolekcja po stronie serwera wczytała posty z bazy danych Mongo, przesłała je do kolekcji klienta, a helper Handlebars przekazał je do szablonu.

    Pójdziemy jeden krok naprzód; dodajmy kolejny post w konsoli:

     Posts.insert({
      title: 'Meteor Docs', 
      author: 'Tom Coleman', 
      url: 'http://docs.meteor.com'
    });
    
    Konsola przeglądarki

    Spójrz na przeglądarkę – powinieneś zobaczyć:

    Dodawanie postów przez konsolę
    Dodawanie postów przez konsolę

    Właśnie pierwszy raz byłeś naocznym świadkiem działania reaktywności. Gdy nakazaliśmy Handlebars iterować po kursorze Posts.find(), framework wiedział jak obserwować kursor na zachodzące zmiany i patchować wynikowy HTML w najprostszy sposób, aby wyświetlać prawidłowe dane na ekranie.

    Inspekcja zmian w DOM

    W tym przypadku, najprostszą zmianą było dodanie kolejnego <div class="post">...</div>. Jeżeli chcesz się upewnić, że to się na pewno wydarzyło, otwórz DOM inspector i zaznacz element <div> odpowiadający jednemu z istniejących postów.

    Teraz, w konsoli JavaScript, wstaw kolejny post. Jeżeli przejdziesz z powrotem do inspectora DOM, zobaczysz dodatkowy element <div> odpowiadający nowemu postowi, ale nadal będzie zaznaczony ten sam element <div> co poprzednio. Jest to użyteczny sposób na sprawdzenie, kiedy elementy zostały przerenderowane i kiedy nie ulegają zmianie.

    Łączenie kolekcji: Publikacje i Subsrkrypcje

    Do tej pory używaliśmy pakietu autopublish, który nie jest zamierzony do użycia w środowisku produkcyjnym. Jak nazwa wskazuje, pakiet ten automatycznie publikuje w całości wszystkie kolekcje każdemu podłączonemu klientowi. Nie jest to, co chcemy osiągnąć, więc wyłączmy to.

    Otwórz nowe okno terminala i wpisz:

    $ meteor remove autopublish
    

    Ma to efekt natychmiastowy. Jeżeli będziesz obserwował przeglądarkę, zobaczysz, że wszystkie posty zniknęły! Dzieje się tak, poniewaź polegaliśmy na pakiecie autopublish, który tworzył lustrzane odbicie wszystkich postów z bazy danych w kolekcji po stronie klienta.

    W końcu będzieli musieli się upewnić, że przesyłamy wyłącznie te posty, które użytkownik będzie miał zobaczyć (biorąc również po uwagę np. dzielenie dokumentu na strony). Na teraz, ustawimy publikację kolekcji Posts w całości.

    Aby to osiągnąć, utworzymy prostą funkcję publish(), która zwraca kursor posiadający referencję do wszystkich postów:

    Meteor.publish('posts', function() {
      return Posts.find();
    });
    
    server/publications.js

    Po stronie klienta musimy zasubskrybować publikację. Dodamy poniższą linię do main.js:

    Meteor.subscribe('posts');
    
    client/main.js

    Zatwierdź 4-4

    Usunięto `autopublish` i ustawiono podstawową publikację.

    Jeżeli ponownie sprawdzimy przeglądarkę, zobaczymy ponownie nasze posty. Świetnie!

    Konluzja

    Co zatem osiągnęliśmy? Pomimo tego, że jeszcze nie posiadamy interfejsu użytkownika, mamy funkcjonalną aplikację webową. Moglibyśmy wystawić aplikację na zewnętrznym internetowym serwerze i (używając konsolę przeglądarki) zacząć dodawać posty, które byłyby widziane w każdej podłączonej do strony przeglądarki na całym świecie.