Add dynamic field support#164
Conversation
| factory :user do | ||
| first_name { 'Meg' } | ||
| last_name { 'Jones' } | ||
| last_name { 'Ryan' } |
philipqnguyen
left a comment
There was a problem hiding this comment.
I'm not sure if I like this merge option. I don't think it explains itself very well what merge is doing without context.
I think the second proposal of what @ritikesh mentioned in the gitter here https://gitter.im/blueprinter-gem/community?at=5d1c81b2f0f22f66450825c4 would fall more in line with current blueprinter's APIs.
Considering that we already have existing features like this:
field :something, if: -> { ... }so doing something like
field :something, name: -> { ... }would be pretty self explanatory because it sticks with current patterns.
|
@philipqnguyen But aren't we giving additional responsibility to the attribute name, by giving a proc which will return name and value. If at all in future, if we have to support a feature where you can dynamically define the name/key of the hash, won't this approach be ideal for that scenario, where you return the dynamic name through the proc? |
|
@amalarayfreshworks I'm not sure if I fully understand. In this case: field :something, name: -> { |obj, _opt| obj.dynamic_field_name }The Or am I missing something or not understanding you? |
|
@philipqnguyen can you pls check Gitter? She has elaborated the use-case there. |
|
I have elaborated the usecase in gitter https://gitter.im/blueprinter-gem/community?at=5d24b57317cc7b05ca9c8249. Please do let me know how can we implement the use case by being in line with current blueprinter's APIs. |
|
The team has been deliberating on how we’d like to address something like this. We are thinking of adding a new Transformer class module Blueprinter
class Transformer
attr_reader :primary_obj, :hash, :options
def initialize(hash, primary_obj, options={})
@primary_obj = primary_obj
@hash = hash
@options = options
end
def transform
fail NotImplementedError, "A Transformer must implement #transform"
end
end
endUsers can then implement transformer classes and declare them in a Blueprint as follows: class UserBlueprint < Blueprinter::Base
fields :first_name, :last_name
transform DynamicFieldTransformer
end
class DynamicFieldTransformer < Blueprinter::Transformer
def transform
hash.merge!(primary_obj.dynamic_fields)
end
endWhatever is returned by the set |
|
Can anyone please re trigger the build for ruby 2_5 and ruby 2_4 as the build failed at installing ruby dependencies. |
philipqnguyen
left a comment
There was a problem hiding this comment.
Thanks for doing this!
Some comments!
|
|
||
| def *(custom_transformer) | ||
| transformers << custom_transformer | ||
| end |
There was a problem hiding this comment.
I've never seen * used for anything but multiplication. Is this common style?
Otherwise, can we come up with a method name that makes clear that it is appending transformer here?
I'm worried we will run out of symbols to define new methods 🤣
There was a problem hiding this comment.
I am usually not into operator overloading, but then I was inspired by the method written for adding fields into the view
def <<(field)
fields[field.name] = field
end
I was under the impression that its a style blueprinter follows for adding setters. I agree to all the reviewers @philipqnguyen and @mcclayton when you feel that we can run out of operators to overload. For now I will change the method name to transform :)
There was a problem hiding this comment.
can you rename to add_transformer? Otherwise it seems like view.transform is already invoking the transformer, which is not entirely accurate since it's just storing it to be called later.
philipqnguyen
left a comment
There was a problem hiding this comment.
thanks @amalarayfreshworks a couple more comments, but overall looking great.
| end | ||
|
|
||
| view.transformers.each do |transformer| | ||
| self * transformer |
There was a problem hiding this comment.
you'll want to change this * to the new method name.
|
|
||
| def *(custom_transformer) | ||
| transformers << custom_transformer | ||
| end |
There was a problem hiding this comment.
can you rename to add_transformer? Otherwise it seems like view.transform is already invoking the transformer, which is not entirely accurate since it's just storing it to be called later.
| end | ||
| end | ||
| it('returns json with values derived from options') { should eq(result) } | ||
| end |
There was a problem hiding this comment.
Would you also add a test to ensure that the transformers are also inherited? There's some inheritance specs here that you can add to: https://github.com/procore/blueprinter/blame/4ba12dd14edf340f9a4a95f1668785adc8c21ca6/spec/integrations/base_spec.rb#L326-L348
There was a problem hiding this comment.
Test case has been added.
1. Changed setter of transformer from “transform” to “add_transformer” 2. Invoked add_transformer instead of * in view inherit. 3. Added test cases to verify transformer inheritance
| # # other code | ||
| # end | ||
| # | ||
| # class DynamicFieldTransformer < Blueprinter::Transformer |
There was a problem hiding this comment.
Should this be DynamicTransformer? (or transform DynamicFieldTransformer above)?
There was a problem hiding this comment.
Yup, example mismatch. would appreciate if you can make a PR.

We have a use case in our product where the key names of the result hash are defined at run time, so in order to avoid introducing an unnecessary extra level/key, I have made some changes.
I have introduced one custom field option called "merge". If this option is present we can add the value returned by the "method/name" directly to the obj hash through a merge.
Eg :
let(:blueprint) do
Class.new(Blueprinter::Base) do
identifier :id
field :dynamic_fields, merge: true
end
end
def dynamic_fields
{"field1" => "val1"}
end
blueprint.render(obj) would return '{"id":' + obj_id + ', "field1": "val1"}'
instead of
'{"id":' + obj_id + ', "dynamic_fields": {"field1": "val1"}}'