-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add dynamic field support #164
Conversation
@@ -3,7 +3,7 @@ | |||
FactoryBot.define do | |||
factory :user do | |||
first_name { 'Meg' } | |||
last_name { 'Jones' } | |||
last_name { 'Ryan' } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
end Users 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
end Whatever 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this!
Some comments!
@@ -38,6 +43,10 @@ def exclude_fields(field_names) | |||
end | |||
end | |||
|
|||
def *(custom_transformer) | |||
transformers << custom_transformer | |||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @amalarayfreshworks a couple more comments, but overall looking great.
lib/blueprinter/view.rb
Outdated
@@ -22,6 +23,10 @@ def inherit(view) | |||
view.excluded_field_names.each do |field_name| | |||
exclude_field(field_name) | |||
end | |||
|
|||
view.transformers.each do |transformer| | |||
self * transformer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you'll want to change this *
to the new method name.
@@ -38,6 +43,10 @@ def exclude_fields(field_names) | |||
end | |||
end | |||
|
|||
def *(custom_transformer) | |||
transformers << custom_transformer | |||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
# # other code | ||
# end | ||
# | ||
# class DynamicFieldTransformer < Blueprinter::Transformer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be DynamicTransformer
? (or transform DynamicFieldTransformer
above)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, example mismatch. would appreciate if you can make a PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, see #174
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"}}'