This app's array views take the simple path

Free preview 3 min to read

The junior course "API development: Jbuilder" read this app's .json.jbuilder views. Several of them build arrays: the catalogue search behind the command palette, and the partner API's lists of learners, certificates and activity. The search view is the simplest:

json.array! @result.courses + @result.lessons do |item|
  if item.is_a?(::Catalog::Course)
    json.title item.title
    json.url catalog_course_path(item.slug)
    json.type "course"
    json.level item.level
  else
    json.title "#{item.course.title}: #{item.title}"
    json.url catalog_lesson_path(course_slug: item.course.slug, slug: item.slug)
    json.type "lesson"
    json.level item.course.level
  end
end

It's a block, called once per item. That's array!'s simplest form, and every array in the app's JSON views is built that way or with a plain map. This lesson covers what that form does, and what it can't do.

array!, from the installed gem

# jbuilder-2.15.1/lib/jbuilder/jbuilder_template.rb
def array!(collection = EMPTY_ARRAY, *args, &block)
  options = args.first

  if _partial_options?(options)
    options = options.dup
    options[:collection] = collection
    _render_partial_with_options options
  else
    _array collection, args, &block
  end
end

def _partial_options?(options)
  ::Hash === options && options.key?(:as) && options.key?(:partial)
end

It has one if. The search view's call, json.array! collection do |item| ... end, has no second argument, so options is nil and _partial_options? returns false straight away. array! goes to _array, which, given a block, maps over the collection: it yields each item to the block, captures the attributes the block sets, and appends them to a plain Array. There's no template, no partial and no cache.

The app's search results, through this path

In development:

result = Catalog::SearchQuery.call(query: "ruby", locale: :en)
renderer = ApplicationController.renderer
renderer.render(inline: 'json.array! @items do |c| json.title c.title end', type: :jbuilder, assigns: { items: result.courses })
search result: 8 courses, 8 lessons
block-form output: [{"title":"Ruby: programmer happiness"},{"title":"Ruby variables & assignment"},
  {"title":"Ruby symbols vs strings"},{"title":"Ruby data: numbers & booleans"}, ...

These are the app's own course titles, serialised through the same block-form path the search view runs on every request. SearchQuery::LIMIT = 8 is why the output stops at 8 courses and 8 lessons, and that number matters again in lesson 6.

What the block form can't do

_partial_options? only returns true when the second argument is a Hash with both :as and :partial. A call with a block and nothing else has no options hash for cached: to be a key of. Caching isn't switched off somewhere; the block path never looks for a cached: option at all, because the branch that reads it is the other one, the branch _partial_options? guards. Lesson 2 takes that branch, which none of the app's JSON views use.

Key takeaways

  • The search view, catalog/search/index.json.jbuilder, calls array! with a block and nothing else, and the partner API views build their arrays the same way or with map.
  • array! branches on _partial_options?, which needs a Hash with both :as and :partial; a bare block never satisfies it, so the call goes to _array and a plain per-item block.
  • Run on the app's SearchQuery results, this path produced the app's course titles, the same mechanism the search endpoint uses.
  • The block form has no cached: option to pass: it's not a missed setting but a branch of array! the block call never reaches.
Lesson 1 of 7 · free

Keep going with lesson 2

The partial-and-cached path this app has never used

Runs array! with partial:, as: and cached: on search data, firing one batched cache read and write.

A free account also gives you the quizzes, your progress and notes, and the discussion.

Type to start searching...