Skip to content

Commit

Permalink
Docs with mdbook - wip
Browse files Browse the repository at this point in the history
Signed-off-by: Jordan Hollinger <[email protected]>
  • Loading branch information
jhollinger committed Feb 8, 2025
1 parent 1bc93ff commit b92a4af
Show file tree
Hide file tree
Showing 23 changed files with 1,796 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ spec/dummy/db/*.sqlite3-journal
spec/dummy/log/*.log
spec/dummy/tmp/
doc/
docs-dist/
.yardoc/
Gemfile.lock
.vscode
Expand Down
17 changes: 17 additions & 0 deletions book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[book]
authors = ["Procore Technologies, Inc."]
language = "en"
multilingual = false
src = "docs"
title = "Blueprinter"

[build]
build-dir = "docs-dist"

# https://rust-lang.github.io/mdBook/format/configuration/renderers.html?highlight=top%20bar#html-renderer-options
[output.html]
additional-css = ["theme/hljs-overrides.css"]
default-theme = "light"
preferred-dark-theme = "navy"
no-section-label = true
git-repository-url = "https://github.com/procore-oss/blueprinter"
24 changes: 24 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Summary

- [Introduction](./introduction.md)

- [Blueprinter DSL](./dsl/index.md)
- [Fields](./dsl/fields.md)
- [Views](./dsl/views.md)
- [Partials](./dsl/partials.md)
- [Formatters](./dsl/formatters.md)
- [Options](./dsl/options.md)
- [Extensions](./dsl/extensions.md)

- [Rendering](./rendering.md)

- [Blueprinter API](./api/index.md)
- [Extensions](./api/extensions.md)
- [Reflection](./api/reflection.md)
- [Extractors](./api/extractors.md)
- [Context Objects](./api/context-objects.md)

---

- [Updating to V2](./updating-to-v2.md)
- [Legacy/V1 Docs](./v1.md)
45 changes: 45 additions & 0 deletions docs/api/context-objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Context Objects

Context objects are the arguments passed to APIs like [field blocks](../dsl/fields.md#field-blocks), [option procs](../dsl/options.md), [extension hooks](./extensions.md), and [extractors](./extractors.md). They contain the current blueprint, the object being rendered, and more (depending on the specific API).

### blueprint

The current Blueprint instance. You can use this to access the Blueprint's name, options, reflections, and instance methods.

_Present_: Always

### field

A struct of the field, object, or collection being rendered. You can use this to access the field's name and options. See [rubydoc.info/gems/blueprinter](https://www.rubydoc.info/gems/blueprinter) for more information about the `Field`, `ObjectField`, and `Collection` structs.

_Present_: In field blocks, option procs, extractors, and certain extension hooks

### value

Depending on the API, this will be either the extracted value of the current field, or the entire output of the current Blueprint.

_Present_: Certain option procs and extension hooks

### object

The object currently being rendered.

_Present_: Always

### options

The frozen options Hash passed to `render`. An empty Hash if none was passed.

_Present_: Always

### store

A Hash for extensions to cache data in. Note that Blueprinter uses this store internally, so be careful not to overwrite Blueprinter's keys (they're all object ids).

_Present_: Always

### instances

A Hash-like interface for creating/fetching class instances. This allows Blueprinter to reuse the same blueprint and extractor instances during a render. You're free to use it, too. See `InstanceCache` in [rubydoc.info/gems/blueprinter](https://www.rubydoc.info/gems/blueprinter) for more details.

_Present_: Always
292 changes: 292 additions & 0 deletions docs/api/extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
# Extensions

Blueprinter has a powerful extension system with hooks for every step of the serialization lifecycle. In fact, many of Blueprinter's features are implemented as built-in extensions!

Simply extend the `Blueprinter::Extension` class, define the hooks you need, and [add it to your configuration](../dsl/extensions.md#using-extensions).

```ruby
class MyExtension < Blueprinter::Extension
# Use the exclude_field? hook to exclude certain fields on Tuesdays
def exclude_field?(ctx)
ctx.field.options[:tuesday] == false && Date.today.tuesday?
end
end
```

## Hooks

Hooks are called in the following order. Most are passed a [context object](./context-objects.md) as an argument.

- [collection?](#collection)
- [around](#around)
- [input_object](#input_object) | [input_collection](#input_collection)
- [around_blueprint](#around_blueprint)
- [prepare](#prepare)
- [blueprint_fields](#blueprint_fields)
- [blueprint_input](#blueprint_input)
- [field_value](#field_value) | [object_value](#object_value) | [collection_value](#collection_value)
- [exclude_field?](#exclude_field) | [exclude_object?](#exclude_object) | [exclude_collection?](#exclude_collection)
- [blueprint_output](#blueprint_output)
- [output_object](#output_object) | [output_collection](#output_collection)

## collection?

> **@param Object** The object passed to `render`\
> **@return Boolean** Whether the object is a collection or a single item\
> **@cost** Low - run exactly once per render (not called for `render_object` or `render_collection`)
If any extension with this hook returns `true`, the object will be considered a collection and rendered as such. For example, the [blueprinter-activerecord](https://github.com/procore-oss/blueprinter-activerecord) extension uses this hook to indicate that an `ActiveRecord::Relation` should be considered a collection.

```ruby
def collection?(object)
object.is_a? ArrayLikeThing
end
```

## around

> **@param Context** Fields `blueprint`, `object`, `options`, `instances`, `store`\
> **@cost** Low - run exactly once per render
Wraps the entire rendering process. Rendering happens during `yield`, allowing the hook to run code before and after the render. If `yield` is not called exactly one time, a `BlueprinterError` is thrown.

```ruby
def around(ctx)
# do something before render
yield # render
# do something after render
end
```

## input_object

> **@param Context** Fields `blueprint`, `object`, `options`, `instances`, `store`\
> **@return Object** A new or modified version of `context.object`\
> **@cost** Low - run exactly once per render
Run when `render` is called with a non-collection object, or when `render_object` is called. This hook allows you to modify the object before rendering, or even return a new one. **Whatever object is returned will be rendered and used as context.object in subsequent hooks.**

```ruby
def input_object(ctx)
ctx.object
end
```

## input_collection

> **@param Context** Fields `blueprint`, `object`, `options`, `instances`, `store`\
> **@return Object** A new or modified version of `context.object`, which will be array-like\
> **@cost** Low - run exactly once per render
Run when `render` is called with a collection object, or when `render_collection` is called. This hook allows you to modify the array-like object before rendering, or even return a new one. **Whatever collection is returned will be rendered and used as context.object in subsequent hooks.**

```ruby
def input_object(ctx)
ctx.object
end
```

## around_blueprint

> **@param Context** Fields `blueprint`, `object`, `options`, `instances`, `store`\
> **@cost** Medium - run every time any blueprint is rendered
Wraps the rendering of a every blueprint. This could be the top-level blueprint or one from an association N levels deep. For collections, it will be called once for each item in the collection.

Rendering happens during `yield`, allowing the hook to run code before and after the render. If `yield` is not called exactly one time, a `BlueprinterError` is thrown.

```ruby
def around_blueprint(ctx)
# do something before render
yield # render
# do something after render
end
```

## prepare

> **@param Context** Fields `blueprint`, `object`, `options`, `instances`, `store`\
> **@cost** Low - run once for _every blueprint class_ during render
Allows an extension to perform any expensive setup operations for a given blueprint class. `context.store` is a good place to cache data since it is shared across all hooks and extensions.

```ruby
def prepare(ctx)
ctx.store[:my_ext] ||= {}
ctx.store[:my_ext][ctx.blueprint.object_id] = setup ctx
end
```

## blueprint_fields

> **@param Context** Fields `blueprint`, `object`, `options`, `instances`, `store`\
> **@return Array<Field | ObjectField | Collection>** The fields you want to render in the order you want to render them\
> **@cost** Low - run once for _every blueprint class_ during render
Customize the order fields are rendered in - or strip out certain fields entirely. The included, optional [Field Order extension](../dsl/extensions.md#field-order) uses this hook.

The default behavior is to render all fields in the order they were defined.

This example uses the [Reflection API](./reflection.md) to get the fields from the current view, then sort them by name:

```ruby
def blueprint_fields(ctx)
ref = ctx.blueprint.class.reflections[:default]
ref.ordered.sort_by(&:name)
end
```

## blueprint_input

> **@param Context** Fields `blueprint`, `object`, `options`, `instances`, `store`\
> **@return Object** A new or modified version of `context.object`\
> **@cost** Medium - run every time any blueprint is rendered
Run before each blueprint is rendered, allowing you to modify, or return a new, object used for the render. **Whatever object is returned will be rendered and used as context.object in subsequent hooks.**

```ruby
def blueprint_input(ctx)
ctx.object
end
```

## field_value

> **@param Context** Fields `blueprint`, `field`, `value`, `object`, `options`, `instances`, `store`\
> **@return Object** The value to be rendered\
> **@cost** High - run for every field (not object or collection fields)
Run after a field value is extracted from `context.object`. The extracted value is available in `context.value`. **Whatever value you return is used as context.value in subsequent field_value hooks, then run through any formatters and rendered.**

```ruby
def field_value(ctx)
case ctx
when String then ctx.strip
else ctx.value
end
end
```

## object_value

> **@param Context** Fields `blueprint`, `field`, `value`, `object`, `options`, `instances`, `store`\
> **@return Object** The object to be rendered for this field\
> **@cost** High - run for every object field
Run after an object field value is extracted from `context.object`. The extracted value is available in `context.value`. **Whatever value you return is used as context.value in subsequent object_value hooks, then rendered.**

```ruby
def field_value(ctx)
case ctx
when String then ctx.strip
else ctx.value
end
end
```

## collection_value

> **@param Context** Fields `blueprint`, `field`, `value`, `object`, `options`, `instances`, `store`\
> **@return Object** The array-like collection to be rendered for this field\
> **@cost** High - run for every collection field
Run after a collection field value is extracted from `context.object`. The extracted value is available in `context.value`. **Whatever value you return is used as context.value in subsequent collection_value hooks, then rendered.**

```ruby
def collection_value(ctx)
ctx.value.compact
end
```

## exclude_field?

> **@param Context** Fields `blueprint`, `field`, `value`, `object`, `options`, `instances`, `store`\
> **@return Boolean** Truthy to exclude the field from the output\
> **@cost** High - run for every field (not object or collection fields)
If any extension with this hook returns truthy, the field will be excluded from the output. The formatted field value is available in `context.value`.

```ruby
def exclude_field?(ctx)
ctx.field.options[:tuesday] == false && Date.today.tuesday?
end
```

## exclude_object?

> **@param Context** Fields `blueprint`, `field`, `value`, `object`, `options`, `instances`, `store`\
> **@return Boolean** Truthy to exclude the field from the output\
> **@cost** High - run for every object field
If any extension with this hook returns truthy, the object field will be excluded from the output. The field object value is available in `context.value`.

```ruby
def exclude_field?(ctx)
ctx.field.options[:tuesday] == false && Date.today.tuesday?
end
```

## exclude_collection?

> **@param Context** Fields `blueprint`, `field`, `value`, `object`, `options`, `instances`, `store`\
> **@return Boolean** Truthy to exclude the field from the output\
> **@cost** High - run for every collection field
If any extension with this hook returns truthy, the collection field will be excluded from the output. The field collection value is available in `context.value`.

```ruby
def exclude_field?(ctx)
ctx.field.options[:tuesday] == false && Date.today.tuesday?
end
```

## blueprint_output

> **@param Context** Fields `blueprint`, `value`, `object`, `options`, `instances`, `store`\
> **@return Hash** The Hash to use as this blueprint's serialized output\
> **@cost** Medium - run every time any blueprint is rendered
Run after each blueprint is serialized to a Hash, allowing you to modify the output. The Hash is available in `context.value`. **Whatever Hash is returned will be used as the serialized output for this blueprint.**

```ruby
def blueprint_output(ctx)
ctx.value.merge({ extra: "data" })
end
```

## output_object

> **@param Context** Fields `blueprint`, `value`, `object`, `options`, `instances`, `store`\
> **@return Hash** The Hash to use as the final serialized output\
> **@cost** Low - run once per `render` (for objects) or `render_object`
Run after the top-level object is fully serialized to a Hash, allowing you to modify the output. The Hash is available in `context.value`. **Whatever Hash is returned will be the final serialized output.**

```ruby
def output_object(ctx)
ctx.value.merge({ extra: "data" })
end
```

## output_collection

> **@param Context** Fields `blueprint`, `value`, `object`, `options`, `instances`, `store`\
> **@return Hash | Array<Hash>** The Hash, or array of Hashes, to use as tthe final serialized output\
> **@cost** Low - run once per `render` (for collections) or `render_collection`
Run after the top-level collection is fully serialized to an array of Hashes, allowing you to modify the output. The array of Hashes is available in `context.value`. **Whatever is returned will be the final serialized output.**

```ruby
# Wrap the output array in a Hash
def output_collection(ctx)
{
data: ctx.value,
extra: "metadata"
}
end

# Or modify each element
def output_collection(ctx)
ctx.value.map { |item| item.merge({ extra: "data" }) }
end
```
Loading

0 comments on commit b92a4af

Please sign in to comment.