Skip to content
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

More possibilities with collections for easier composing of complex JSON #166

Closed
Samuelfaure opened this issue Jul 11, 2019 · 4 comments
Closed

Comments

@Samuelfaure
Copy link

Samuelfaure commented Jul 11, 2019

Hi there,

First, thank you all for your work. I love this gem, it seems perfect for clear concise code.

My issue

I find hard to compose complex JSON with Blueprinter.
In Jbuilder, you can simply reference a jbuilder view into another, which makes composition of complex JSON very easy. I have doodled quite some time with Blueprinter, and it seems like there is no way to achieve this in a satisfactory way.

Use case

It seems Blueprinter is oriented towards making 1 blueprint for 1 activerecord model, and stop there. But it is very common to want to send back a collection, and while blueprinter can return that collection as an array of the records, that is not enough for the kind of complex JSON that I need to return. For example, I didn't manage to setup meta-data or a root for the collection, I can only use those options for individual records of the collection.

Right now the only way I seem to build a semi-complex JSON is by creating the payload into my controller and just use Blueprinter's render_as_json for the lines where I want my records. This is not satisfying as I would like that complexity inside my serializations instead.

I think returning just an array for collections does not cover a lot of use cases.

A possible feature ?

Let's say I want to return a collection of books. I would like to reference BookBlueprint inside BooksBlueprint, as well as other fields of my choosing (like pagination fields, meta-data on a collection level, ect).

Would this be a direction where the gem could go ? Or is it already possible and I missed it completely somehow ?

Again thanks for the work, I'd love to see this gem grow.

@philipqnguyen
Copy link
Contributor

philipqnguyen commented Jul 17, 2019

@Samuelfaure
Yup and we might be able to piggy back on some work being done in this PR #164 . The PR is going to introduce a Transformer that get's called just before the object or collection of objects gets serialized. It would yield the object for you to make changes, which potentially can be meta data fields for example.

@emptyflask
Copy link

emptyflask commented Sep 30, 2019

I'm looking for a way to do this as well -- specifically, adding a header that can reference the collection. Something like:

  field :header do |collection, options|
    {
      version: (options[:version] || collection.max_by(:updated_at).updated_at),
      count:   collection.length
    }
  end

I wouldn't mind working on this feature, but since I've only looked at Blueprinter for a day or two, I'm not sure what the best interface for doing this sort of thing would look like. Obviously I don't want to change the behavior of field, so we'd have to figure some other way to create keys/values at the collection level.

@adamwells
Copy link
Contributor

adamwells commented Jul 20, 2020

You might be able to use a meta attribute for what you're trying to do.

class UserBlueprint < Blueprinter::Base
  fields :first_name, :last_name
end

users = [
  {
    first_name: 'John',
    last_name: 'Smith'
  },
  {
    first_name: 'John',
    last_name: 'Doe'
  }
]

UserBlueprint.render(users, root: :users, meta: { count: users.count })
=> {"users"=>
  [{"first_name"=>"John", "last_name"=>"Smith"},
   {"first_name"=>"John", "last_name"=>"Doe"}],
 "meta"=>{"count"=>2}}

If that doesn't suit your needs though we're totally open to adding an official API for this if you want to take a stab at it. Apologies for the delayed response here

@taschetto
Copy link

Another way to do it is to keep this meta-data away from the response body. Look at this excerpt that uses https://github.com/ddnexus/pagy and https://github.com/procore/blueprinter.

  after_action :merge_pagy_headers, only: [:index]

  def index
    @pagy, @users = pagy(User.all)
    render json: UserBlueprint.render(@users)
  end

The catch is: collection meta-data (e. g. Link, Current-Page, Page-Items, Total-Pages, Total-Count) is added to the response headers (by merge_pagy_headers), leaving just serialized data inside response body.

Extra #1: How to add custom headers in controller actions https://redgreenrepeat.com/2019/02/22/rails-custom-headers/
Extra #2: If you make CORS requests, don't forget to add these custom headers to your cors.rb file.

Rails.application.config.middleware.insert_before 0, Rack::Cors, debug: true do
  allow do
    origins 'mydomain.com'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      expose: %w[Link Current-Page Page-Items Total-Pages Total-Count]
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants