procore-oss / blueprinter

Simple, Fast, and Declarative Serialization Library for Ruby

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using implicit default view with config.sort_fields_by = :definition leads to SystemStackError

colszowka opened this issue · comments

  • I guess this is related to #197
  • I tested this on currently latest blueprinter 0.25.2 and Ruby 2.7.2

Background: We'd like to gradually move to using views on some of our blueprints that have naturally grown to be fairly complex, and need the fields in the output to be sorted by definition order

As we have realized blueprinter has an implicit view :default that could be used to basically convert from the top-level fields into views using view :default and include_views in there, so we don't have to adjust any calling code to pass an explicit diffferent view.

However, when config.sort_fields_by = :definition is set this leads to a stack level to deep exception. Here's a simple example:

require "json"
require "blueprinter"
require "pp"
require "ostruct"

Blueprinter.configure do |config|
  config.sort_fields_by = :definition
end

record = OpenStruct.new id: 123, name: "foo", address: "bar"

class MyBlueprint < Blueprinter::Base
  identifier :id

  view :detailed do
    field :address
  end

  view :full do
    include_view :detailed
    field :name
  end

  view :default do
    include_view :detailed
    field :name
  end
end

pp MyBlueprint.render_as_hash(record, view: :full)
# => {:id=>123, :address=>"bar", :name=>"foo"}

pp MyBlueprint.render_as_hash(record)
# => stack level too deep (SystemStackError)

Passing view: default explicitly in the second example leads to the same problem unfortunately. Without the sort_fields_by config change it works as expected

Thanks for reporting this @colszowka!

@mcclayton Thanks for this great library :)

For what it's worth, I'm not sure this really is a bug in blueprinter since using the default view like this is not even on the documentation so this is mostly me trying to stretch things a bit in order to avoid having to pass the view explicitly in existing code, so maybe this should actually be closed.

We were able to work around the problem we were facing by adding this override on our ApplicationBlueprint:

    def self.prepare(object, view_name:, **rest)
      view_name = :standard if view_name == :default && view_collection.has_view?(:standard)
      super object, view_name: view_name, **rest
    end

Now we can do view :standard and basically achieve what I outlined above, and otherwise the internal default view works as usual so it fixes our problem here

That makes sense @colszowka, glad you got a workaround figured out. I think the larger thing this brings up is the lack of documentation for the implicitly defined :default view and for the core maintainers team to think through how it should behave in edge cases like this. Thanks again for bringing it to our attention.

Closing this issue, creating a documentation task #264