1

I have this structure: Books that have Chapters (ancestor=Book) that have Pages (ancestor=Chapter)

It is clear for me that, to search for a Chapter by ID, I need the book to search by ancestor query. And I've learn today that if I have all the keys, I can retrieve directly the entity without need of get first the book, then the chapter and then the page, in this way:

page_key = ndb.Key('Book', long(bookId), 'Chapter', long(chapterId), 'Page', long(pageId))
page = page_key.get()

My doubt is: to get the page by, for example, page number, must I get first the chapter?

For example :

class Book(ndb.Model):
    name = ndb.StringProperty(required=True)

    # Search by id
    @classmethod
    def by_id(cls, id):
        return Book.get_by_id(long(id))

class Chapter(ndb.Model):
    name = ndb.StringProperty(required=True)

    # Search by id
    @classmethod
    def by_id(cls, id, book):
        return Chapter.get_by_id(long(id), parent=book.key)

class Page(ndb.Model):
    pageNumber = ndb.IntegerProperty(default=1)
    content = ndb.StringProperty(required=True)

    # Search by page
    @classmethod
    def by_page(cls, number, chapter):
        page = Page.query(ancestor=chapter.key)
        page = page.filter(Page.pageNumber == int(number))
        return page.get()

Actually, when I need to search the Page to display its contents, I'm doing this:

getPage?bookId=5901353784180736&chapterId=5655612935372800&page=2

So, in the controller, I make this:

def get(self):
    # Get the id parameters
    bookId = self.request.get('bookId')
    chapterId = self.request.get('chapterId')
    pageNumber = self.request.get('page')

    if bookId and chapterId and pageNumber:
        # Must be a digit
        if bookId.isdigit() and chapterId.isdigit() and pageNumber.isdigit():
            # Get the chapter
            chapter_key = ndb.Key('Book', long(bookId), 'Chapter', long(chapterId))
            chapter = chapter_key.get()

            if chapter:
                # Get the page
                page = Page.by_number(pageNumber, chapter)

Is this the right way or there is a better way I'm missing where I can do only an access to datastore, instead two?

If this is right, I suppose that this way of work, using NDB, does not have any impact on the datastore, because repeated calls to this page always hit the NDB cache for the same chapter and page (because I'm using get() method, it is not a fetch() command). Is my suppose right?

Eagle
  • 978
  • 1
  • 14
  • 27

1 Answers1

2

You can do this in one go by doing an ancestor query, rather than a get:

chapter_key = ndb.Key('Book', long(bookId), 'Chapter', long(chapterId))
page = Page.query(Page.pageNumber==pageNumber, ancestor=chapter_key)
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Great man!!, it works perfectly. I was missunderstanding a bit the keys with the ancestors, but I think now I understand it better. In my **by_page** method I was passing the chapter entity but searching by _ancestor=chapter.key_ which is idiotic, because I only need the key. I will change all my code to use ONLY keys and not full entities in the parts I only need keys, which will save me a lot of database hits. – Eagle May 30 '13 at 13:30
  • About the last question, was I right? Even calling 3 different entities, because I'm using get(), NDB is getting them from cache, minimizing the hit to database? – Eagle May 30 '13 at 13:32
  • 1
    To a certain extent, yes, but you can't guarantee things are actually in the cache so it's always better to minimize datastore access as much as you can. – Daniel Roseman May 30 '13 at 13:33