Using Cloud Datastore with Ruby

This page of the Bookshelf tutorial shows how the sample app stores its persistent data in Cloud Datastore. The sample code provides examples of how to create, read, update, and delete (CRUD) data stored in Cloud Datastore.

This page is part of a multipage tutorial. To start from the beginning and read the setup instructions, go to Ruby Bookshelf app.

Installing dependencies and configuring settings

  1. Go to the getting-started-ruby/2-cloud-datastore directory, and enter the following command:

    bundle install
    
  2. Copy the database.example.yml file.

    cp config/database.example.yml config/database.yml
    
  3. To configure the app to use your Cloud Datastore, edit the config/database.yml file. Replace [YOUR_PROJECT_ID] with your project ID.

    For example, suppose your project ID is my-project. Then the development and production sections of your database.yml file would look similar to this:

    development:
      dataset_id: my-project
    
    production:
      dataset_id: my-project
    

Cloud Datastore is a fully managed service that is automatically initialized and connected to your App Engine app. No further configuration is required.

Running the app on your local machine

  1. Start a local web server.

    bundle exec rails server
    
  2. In your web browser, enter the following address:

    http://localhost:3000

Now you can browse the app's web pages to add, edit, and delete books.

To exit the local web server, press Control+C .

Deploying the app to the App Engine flexible environment

  1. Compile the JavaScript assets for production.

    RAILS_ENV=production bundle exec rake assets:precompile
    
  2. Deploy the sample app.

    gcloud app deploy
    
  3. In your web browser, enter the following address.

    https://[YOUR_PROJECT_ID].appspot.com
    

If you update your app, you can deploy the updated version by entering the same command you used to deploy the app the first time. The new deployment creates a new version of your app and promotes it to the default version. The older versions of your app remain, as do their associated VM instances. Be aware that all of these app versions and VM instances are billable resources.

You can reduce costs by deleting the non-default versions of your app.

To delete an app version:

  1. In the GCP Console, go to the Versions page for App Engine.

    Go to the Versions page

  2. Select the checkbox for the non-default app version you want to delete.
  3. Click Delete to delete the app version.

For complete information about cleaning up billable resources, see the Cleaning up section in the final step of this tutorial.

Application structure

The following diagram shows the app's components and how they connect to each other.

Bookshelf app deployment process and structure

Understanding the code

This section walks you through the sample code and explains how it works.

Interact with Cloud Datastore

The Bookshelf sample app uses the google-cloud RubyGem, which is a client library for accessing Google Cloud Platform (GCP) services.

To interact with Cloud Datastore, the app configures a Dataset object based on the project ID that you entered in the config/database.yml file.

The following sections explain how the app uses the Dataset to query Cloud Datastore for lists of books.

require "google/cloud/datastore"

class Book

  attr_accessor :id, :title, :author, :published_on, :description

  # Return a Google::Cloud::Datastore::Dataset for the configured dataset.
  # The dataset is used to create, read, update, and delete entity objects.
  def self.dataset
    @dataset ||= Google::Cloud::Datastore.new(
      project_id: Rails.application.config.
                        database_configuration[Rails.env]["dataset_id"]
    )
  end

List books

When you visit the app's home page, you are routed to the index action of the BooksController class. This is configured in config/routes.rb.

Rails.application.routes.draw do

  # Route root of application to BooksController#index action
  root "books#index"

  # Restful routes for BooksController
  resources :books

end

The BookController#index action retrieves a list of books from Cloud Datastore. The Book.query method returns an array of Book objects and a query cursor that you can use to retrieve additional entities. Read more about query cursors.

class BooksController < ApplicationController

  PER_PAGE = 10

  def index
    @books, @cursor = Book.query limit: PER_PAGE, cursor: params[:cursor]
  end

The Book.query method queries Cloud Datastore for entities of kind Book and returns them as instances of the Book class:

# Query Book entities from Cloud Datastore.
#
# returns an array of Book query results and a cursor
# that can be used to query for additional results.
def self.query options = {}
  query = Google::Cloud::Datastore::Query.new
  query.kind "Book"
  query.limit options[:limit]   if options[:limit]
  query.cursor options[:cursor] if options[:cursor]

  results = dataset.run query
  books   = results.map {|entity| Book.from_entity entity }

  if options[:limit] && results.size == options[:limit]
    next_cursor = results.cursor
  end

  return books, next_cursor
end

The Book.query method creates a Google::Cloud::Datastore::Query object and configures it to request entities of kind Book, with a limit set on the number of results it can return. If a query cursor is provided, the query returns results starting at the cursor.

After Book.query fetches a list of books, the embedded Ruby code in books/index.html.erb renders the list.

<% @books.each do |book| %>
  <div class="media book">
    <%= link_to book_path(book) do %>
      <div class="media-body">
        <h4><%= book.title %></h4>
        <p><%= book.author %></p>
      </div>
    <% end %>
  </div>
<% end %>

<% if @cursor %>
  <nav>
    <ul class="pager">
      <li><%= link_to "More", books_path(cursor: @cursor) %></li>
    </ul>
  </nav>
<% end %>

To query and retrieve results from Cloud Datastore, Book.query calls Dataset#run, which executes the query and returns a collection of Entity objects.

Entity objects returned from Cloud Datastore are represented in the following form:

{
  key: {
    dataset_id: "your-project-id",
    kind: "Book",
    id: "5726256557457408"
  },
  properties: {
    title: "A Tale of Two Cities",
    author: "Charles Dickens"
  }
}

The Book class includes methods for translating between Entity objects and Book objects. For example, Book.from_entity takes an Entity object and returns an instance of Book.

def self.from_entity entity
  book = Book.new
  book.id = entity.key.id
  entity.properties.to_hash.each do |name, value|
    book.send "#{name}=", value if book.respond_to? "#{name}="
  end
  book
end

Display book details

When you click an individual book on the web page, the BookController#show action calls Book.find to retrieve the book from Cloud Datastore.

def show
  @book = Book.find params[:id]
end

The Book.find method fetches the book from Cloud Datastore by its autogenerated integer id key.

# Lookup Book by ID.  Returns Book or nil.
def self.find id
  query    = Google::Cloud::Datastore::Key.new "Book", id.to_i
  entities = dataset.lookup query

  from_entity entities.first if entities.any?
end

After Book.find retrieves the books, the embedded Ruby code in books/show.html.erb renders the list.

<div class="media">
  <div class="media-body">
    <h4><%= @book.title %> | &nbsp; <small><%= @book.published_on %></small></h4>
    <h5>By <%= @book.author || "unknown" %></h5>
    <p><%= @book.description %></p>
  </div>
</div>

Create books

When you click Add book on the web page, the BooksController#new action creates a new Book. The embedded Ruby code in new.html.erb points to _form.html.erb, which displays the form for adding a new book.

<%= form_for @book do |f| %>
  <div class="form-group">
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div class="form-group">
    <%= f.label :author %>
    <%= f.text_field :author %>
  </div>
  <div class="form-group">
    <%= f.label :published_on, "Date Published" %>
    <%= f.date_field :published_on %>
  </div>
  <div class="form-group">
    <%= f.label :description %>
    <%= f.text_area :description %>
  </div>
  <button class="btn btn-success" type="submit">Save</button>
<% end %>

When you submit the form, the BooksController#create action saves the book in Cloud Datastore. If the new book is saved successfully, the book's page is displayed. Otherwise, the form is displayed again along with error messages. The book_params method uses strong parameters to specify which form fields are allowed. In this case, only the book's title, author, published_on, and description are allowed:

def create
  @book = Book.new book_params

  if @book.save
    flash[:success] = "Added Book"
    redirect_to book_path(@book)
  else
    render :new
  end
end

private

def book_params
  params.require(:book).permit(:title, :author, :published_on, :description)
end

The title field is required for a book to be considered valid. Only valid books are saved in Cloud Datastore.

# Add Active Model validation support to Book class.
include ActiveModel::Validations

validates :title, presence: true

The Book.create method calls Book.save, which translates the Book object to an Entity object, and then uses the Datastore dataset to save the book in Cloud Datastore.

# Save the book to Datastore.
# @return true if valid and saved successfully, otherwise false.
def save
  if valid?
    entity = to_entity
    Book.dataset.save entity
    self.id = entity.key.id
    true
  else
    false
  end
end

Edit books

When you click Edit book on the web page, the BooksController#update action retrieves the book from Cloud Datastore. The embedded Ruby code in edit.html.erb points to _form.html.erb, which displays the form for editing the book.

def update
  @book = Book.find params[:id]

  if @book.update book_params
    flash[:success] = "Updated Book"
    redirect_to book_path(@book)
  else
    render :edit
  end
end

The BooksController#update action calls the update method of the Book instance.

# Set attribute values from provided Hash and save to Datastore.
def update attributes
  attributes.each do |name, value|
    send "#{name}=", value if respond_to? "#{name}="
  end
  save
end

If the book details are updated successfully, the user is directed to the book's page. Otherwise, the form is displayed again with error messages displayed.

Delete books

When you click Delete Book on the web page, the BooksController#destroy action deletes the book from Cloud Datastore and displays the list of books.

def destroy
  @book = Book.find params[:id]
  @book.destroy
  redirect_to books_path
end

The BooksController#destroy action calls the destroy method of the Book instance.

def destroy
  Book.dataset.delete Google::Cloud::Datastore::Key.new "Book", id
end
Czy ta strona była pomocna? Podziel się z nami swoją opinią:

Wyślij opinię na temat...