blueprinter
blueprinter copied to clipboard
Private method helpers?
class Api::V1::CountrySerializer < ActiveModel::Serializer
attributes :name
attribute :currency_name do
currency&.name
end
attribute :currency_code do
currency&.iso_code
end
private
def currency
@currency ||= ISO3166::Country[object.alpha2]&.currency
end
end
AMS has 'global' access to object
inside the class.
Does blueprinter have something similar? I haven't found anything in the readme.
I've only managed to do it without lookup
field :currency_name do |country|
ISO3166::Country[country.alpha2]&.currency.name
end
field :currency_code do |country|
ISO3166::Country[country.alpha2]&.currency.iso_code
end
But that could be expensive in some cases.
I could also do
field :currency do |country|
c = ISO3166::Country[country.alpha2]
{
name: c&.name,
code: c&.iso_code
}
end
but i'd prefer not.., and in some cases that might not be feasible
I think to accomplish something similar in Blueprinter, you'd want something like:
class Api::V1::CountryBlueprint < Blueprinter::Base
field :currency_code do |object|
currency_for(object)&.iso_code
end
private
def self.currency_for(obj)
@@currency[obj.name] ||= ISO3166::Country[obj]&.currency
end
end
Will try later this week! Will let you know :)
Hm, getting an error
"exception": "#<NameError: uninitialized class variable @@currency in Api::V1::CountrySerializer\nDid you mean? currency_for>",
code
field :currency_code do |country|
currency_for(country)&.iso_code
end
field :currency_name do |country|
currency_for(country)&.name
end
private
def self.currency_for(obj)
@@currency[obj.alpha2] ||= ISO3166::Country[obj.alpha2]&.currency
end
Not sure how to initialize it before usage? 🤔
I also tried @currency ||=
, but that caches it once "forever" and every subsequent call won't give me the correct value.
im fine with this for now:
def self.currency_for(obj)
ISO3166::Country[obj.alpha2]&.currency
end
the lookup is fast, and at least i solved the duplication. But would be nice to know how to fix it :D
This should initialize correct class variable:
@@currency = {}
However class variable will be bloated over time so unless you are sure it will be garbage collected it's not safe to do this.
Since Blueprinter doesn't instantiate Blueprint
class while constructing json and uses blocks instead you are probably supposed to build currency
object outside of Blueprint
and then pass it as an option:
CountryBlueprint < Blueprinter::Base
field :currency_code do |_country, options|
options[:currency]&.iso_code
end
field :currency_name do |_country, options|
options[:currency]&.name
end
end
CountryBlueprint.render(country, currency: ISO3166::Country[country.alpha2])
But you also can use this options hash to share the context between fields, despite it's a hack you may still want to use it:
class CountryBlueprint < Blueprinter::Base
field :currency_name do |country, options|
currency_for(country, options)&.currency.name
end
field :currency_code do |country, options|
currency_for(country, options)&.currency.iso_code
end
private
def self.currency_for(obj, opts)
opts[:currency] ||= ISO3166::Country[obj.alpha2]
end
end
Agree with the desire for these kinds of helpers. Would it be possible to consider a more structural change to the API of blueprinter, to avoid the heavy use of class methods? That would make helpers more intuitive.
For instance: the recommendations above that call for defining helpers as class methods all make the mistake of presuming that private
affects class methods. It does not.
@mcclayton / @sl4vr
private
def self.currency_for(obj);end
This still results in CountryBlueprint.currency_for
being a public method. I think this points to one of the huge drawbacks of using class methods and (IMO) it would be an improvement to restructure the api to use regular old instances. (The api could be made backwards compatible by making the class methods instantiate under the covers)
I'll admit that I hoped/assumed the blueprint api was more similar to AMS and graphql-ruby's apis; which both support attribute overrides via method definition (which I think is an improvement over blocks) and more streamlined delegation support.
Hm, building it outside the helper like CountryBlueprint.render(country, currency: ISO3166::Country[country.alpha2])
doesn't work for collections :)
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.