GithubHelp home page GithubHelp logo

Comments (8)

myabc avatar myabc commented on June 21, 2024

@Kris-LIBIS could you post your Representer? I'm assuming you have a block that looks something like this?

link :self, toplevel: true do |options|
  "/api/organizations?page=#{options[:page]}&per_page=#{options[:per_page]}"
end

from roar-jsonapi.

Kris-LIBIS avatar Kris-LIBIS commented on June 21, 2024

@myabc I might have something like that at a given point. In the mean time I worked around the meta problem by putting it in my Representer i.o. the user_options. But I still cannot work out the links part. I have fiddled around with my Representers so much, that I don't know anymore how I got the results above.

But I have currently this:

base.rb

require 'roar/coercion'
module Libis::Ingester::API::Representer
  module Base
    def self.included(klass)
      klass.include Roar::JSON
      klass.include Roar::Coercion
      klass.include Representable::Hash
      klass.include Representable::Hash::AllowSymbols
      klass.include Roar::JSON::JSONAPI::Mixin
      klass.property :id, exec_context: :decorator, writable: false,
                     type: String, desc: 'Object\'s unique identifier'
    end
    def id
      represented.id.to_s
    end
  end
end

item_list.rb

require_relative 'base'
module Libis::Ingester::API::Representer
  module ItemList
    def self.included(klass)
      klass.include Base
      klass.link :self do |opts|
        "#{opts[:base_url]}/#{represented.id}"
      end
      klass.link :all do |opts|
        "#{opts[:base_url]}"
      end
      [:self, :next, :prev, :first, :last, :all].each do |ref|
        klass.link ref, toplevel: true do |opts|
          opts[:links][ref] rescue nil
        end
      end
      klass.meta toplevel: true do
        property :limit_value, as: :per_page
        property :total_count
        property :current_page
        property :total_pages
        property :next_page
        property :prev_page
      end
    end
  end
end

organization.rb

require_relative 'item_list'
module Libis::Ingester::API::Representer
  class OrganizationRepresenter < Grape::Roar::Decorator
    include ItemList
    type :organization
    attributes do
      property :name, type: String, desc: 'organization name'
    end
  end
end

The reason I do it this way is because I have a lot of object types to represent and I want to keep as much as possible the object and api specific data out of the Representer.

I do get the meta section on the top level only and I get the links on the top level too, but I cannot get the links :self and :all to appear on the individual objects anymore:

        orgs = paginate(Libis::Ingester::Organization.all)
        Representer::OrganizationRepresenter.for_collection.prepare(orgs).extend(Representable::Debug).
            to_hash(pagination_hash(orgs).merge(fields_opts(declared(params).fields, {organization: [:name]})))

with the to_hash argument evaluation to:

{
  user_options: {
    base_url: "http://localhost:9393/api/organizations",
    links: {
      self: "http://localhost:9393/api/organizations?per_page=2&page=1",
      first: "http://localhost:9393/api/organizations?per_page=2&page=1",
      last: "http://localhost:9393/api/organizations?per_page=2&page=3",
      next: "http://localhost:9393/api/organizations?per_page=2&page=2"
    }
  },
  fields: {
    organization: [:name]
  }
}

Gives me:

{
  "data": [
    {
      "id": "58b7ccd7e852dd0775f51cae",
      "attributes": {
        "name": "Test Ingest Organization"
      },
      "type": "organization"
    },
    {
      "id": "58b7ccd7e852dd0775f51caf",
      "attributes": {
        "name": "Dummy test organization"
      },
      "type": "organization"
    }
  ],
  "links": {
    "self": "http://localhost:9393/api/organizations?per_page=2&page=1",
    "next": "http://localhost:9393/api/organizations?per_page=2&page=2",
    "first": "http://localhost:9393/api/organizations?per_page=2&page=1",
    "last": "http://localhost:9393/api/organizations?per_page=2&page=3"
  },
  "meta": {
    "per_page": 2,
    "total-count": 5,
    "current-page": 1,
    "total-pages": 3,
    "next-page": 2
  }
}

Note that there is no links section on the I don't see what I am doing wrong here. Especially since it used to work at some point.

from roar-jsonapi.

Kris-LIBIS avatar Kris-LIBIS commented on June 21, 2024

@myabc I also looked at the code change you committed for #24. If I interpret that correctly you are stripping both the meta and user_options for the individual document rendering, right?

I am afraid that stripping the user_options may break my code again, because the :base_url will not be available for the individual objects any more and would cause the :self and :all links to be broken.

Since one can always use if clauses and differentiate with toplevel: true I do not see any harm in passing the user_options to the individual documents. Would you consider reverting that part of the change?

from roar-jsonapi.

Kris-LIBIS avatar Kris-LIBIS commented on June 21, 2024

I played a bit more with the Representer and discovered by accident that the :self and :all links appear again when I do not supply the :fields option in the to_hash argument.

Is that a bug or is it intentional or am I doing something wrong? If it is a bug I'd gladly open a separate issue for it if you want me to.

from roar-jsonapi.

Kris-LIBIS avatar Kris-LIBIS commented on June 21, 2024

BTW the same happens when I add the :include option.

from roar-jsonapi.

twiduch avatar twiduch commented on June 21, 2024

I also have the problem with include:
Adding that removes links. Did you find any solution for that?

from roar-jsonapi.

Kris-LIBIS avatar Kris-LIBIS commented on June 21, 2024

@twiduch Yes I did. I pathced ResourceCollection#to_hash like so:

module Roar
  module JSON
    module JSONAPI
      module ResourceCollection
        def to_hash(options = {})
          meta = options.delete(:meta)
          document = super(to_a: options, user_options: options[:user_options]) # [{data: {..}, data: {..}}]

          links = Renderer::Links.new.(document, options)
          meta  = render_meta(meta: meta)
          included = []
          document['data'].each do |single|
            included += single.delete('included') || []
          end

          HashUtils.store_if_any(document, 'included',
                                 Fragment::Included.(included, options))
          HashUtils.store_if_any(document, 'links',    links)
          HashUtils.store_if_any(document, 'meta',     meta)

          document
        end
      end
    end
  end
end

Note that I refrain from using relationships and include as they do not play nice with grape-swagger in my setup. So you luck may vary using my patch.

from roar-jsonapi.

Kris-LIBIS avatar Kris-LIBIS commented on June 21, 2024

@twiduch My patch is not that different from the code change 0063d6d. I only remove the :meta information from the options because I need the :user_options when processing the data section.

Additionally, I created a special module for paginated collections:

  module Pagination

    def for_pagination
      for_collection.tap do |representer|
        representer.class_eval do
          def page_url(opts, page = nil, offset = nil)
            url = opts[:this_url]
            return url if page.nil? && offset.nil?
            return url unless opts && opts[:pagination] && opts[:pagination][:total] > 1
            page = (page || opts[:pagination][:page] rescue 1) + offset.to_i
            uri = URI::parse(url)
            uri.query_params['page'] = page
            uri.to_s
          end

          def next_url(opts)
            page_url(opts, nil, 1) if (opts[:pagination][:page] < opts[:pagination][:total] rescue false)
          end

          def prev_url(opts)
            page_url(opts, nil, -1) if (opts[:pagination][:page] > 1 rescue false)
          end

          def first_url(opts)
            page_url(opts, 1) if (opts[:pagination][:total] > 1 rescue false)
          end

          def last_url(opts)
            page_url(opts, (opts[:pagination][:total] rescue 1)) if (opts[:pagination][:total] > 1 rescue false)
          end

          link(:self) {|opts| page_url(opts)}
          link(:next) {|opts| next_url opts}
          link(:prev) {|opts| prev_url opts}
          link(:first) {|opts| first_url opts}
          link(:last) {|opts| last_url opts}

          # meta do
          #   property :current_page, exec_context: :decorator
          #   property :total_pages, exec_context: :decorator
          #   property :next_page, exec_context: :decorator
          #   property :prev_page, exec_context: :decorator
          #   property :total_count, exec_context: :decorator
          #   property :limit_value, as: :per_page, exec_context: :decorator
          # end
          #
          # def current_page
          #   respresented.respond_to?(:current_page) ? represented.current_page : nil
          # end

        end
      end
    end
  end

I include the module in the representer and now use #for_pagination instead of #for_collection and supply the pagination info in the options hash with this code snippet:

  def pagination_hash(collection, default = {})
    option_hash(default).tap do |h|
      h[:user_options].merge!(
          pagination: {
              page: collection.current_page,
              total: collection.total_pages,
              per: declared(params).per_page
          }
      )
      h.merge!(
        meta: {
            current_page: collection.current_page,
            total_pages: collection.total_pages,
            total_count: collection.total_count,
            per_page: collection.limit_value
        }
      )
      h[:meta][:next_page] = collection.next_page if collection.next_page
      h[:meta][:prev_page] = collection.prev_page if collection.prev_page
    end
  end

The option_hash() call supplies the basic options like :base_url and :this_url that I feed to any representer#to_hash call and is needed fo my code to work.

from roar-jsonapi.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.