Performance
Any use of extensions has some performance implications, irrespective of what the extension code is doing. This can range anywhere from “essentially zero” to “noticible”. (See Blueprinter::Extension for documentation about which hooks are the most and least expensive.)
Given the number of extension hooks available, there are frequently multiple ways to implement a given behavior. Some of these will be more efficient than others.
Example: Camel case converter
Let’s say your source objects store fields in camelCase, but you want to serialize them using snake_case. You could define every field in your Blueprints like this:
field :some_field, source: :someField
But that’s tedious and error-prone. Let’s write an extension to do it for us.
Manually extract each field
The around_field_value hook and friends are the simplest way to implement it:
class CamelCaseSource < Blueprinter::Extension
# @param ctx [Blueprinter::V2::Context::Field]
def around_field_value(ctx)
attr = ctx.field.source_str.camelize
if ctx.object.is_a? Hash
ctx.object.key?(attr) ? ctx.object[attr] : ctx.object[attr.to_sym]
else
ctx.object.public_send(attr)
end
end
alias around_object_value around_field_value
alias around_collection_value around_field_value
end
But what about the performance? These hooks will run for every field on the Blueprint. If you’re adding this extension only to specific Blueprints that might be fine.
But what if someone adds it to ApplicationBlueprint? Then it will run for every field in every Blueprint in your application!
Transform each Blueprint’s result
The around_blueprint hook allows you to intercept and modify the serialized hash output of each Blueprint. If we define our Blueprints in camel case:
field :someField
Then we can convert the output to snake case:
class CamelCaseSource < Blueprinter::Extension
# @param ctx [Blueprinter::V2::Context::Object]
def around_blueprint(ctx)
# Get the serialized output from Blueprinter
result = yield ctx
# Convert it
snake_case result
result
end
def snake_case(hash)
hash.transform_keys! { |k| k.to_s.underscore }
hash.each_value { |v| snake_case v if v.is_a? Hash }
end
end
This runs only once per serialized object, so it’s a big improvement. But that could still be hundreds of times, or more, in a large result. Can we do better?
Change field config before render
What if we could alter the field definitions programatically, before anything is serialized? The around_blueprint_init hook runs only once per Blueprint during a render. It’s quite cheap, and it can alter field definitions.
class CamelCaseSource < Blueprinter::Extension
# @param ctx [Blueprinter::V2::Context::Init]
def around_blueprint_init(ctx)
ctx.fields.each do |field|
field.source = field.source_str.camelize.to_sym
end
yield ctx
end
This is effectively the same as setting source on each field in the Blueprint. There’s essentially zero runtime cost.