Code Monkey home page Code Monkey logo

grape-entity's People

Contributors

aj0strow avatar amar4enko avatar anakinj avatar budnik avatar croeck avatar dan-corneanu avatar dblock avatar dmitrytsepelev avatar drakula2k avatar estevaoam avatar etehtsea avatar fahchen avatar fixme avatar idyll avatar james2m avatar joelvh avatar justfalter avatar kachick avatar lefnord avatar magni- avatar marshall-lee avatar mbleigh avatar nerian avatar olleolleolle avatar pravi avatar reyesyang avatar sagebomb avatar tricknotes avatar u2 avatar wyattisimo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grape-entity's Issues

Separate condition evaluations for same attribute

Consider the following:

with_options if: { kind: :short } do
  expose :name do |obj, opts|
    "#{obj.first_name[0]}. #{obj.last_name}"
  end
end

with_options if: { kind: :full } do
  expose :name do |obj, opts|
    "#{obj.first_name} #{obj.last_name}"
  end
end

Based on the above, I would expect that if :kind is :short then the value of :name will be the first initial and last name, and if :kind is :full then the value will be the first and last name.

However, since these two exposures use the same attribute name, it's not possible to evaluated them both. The :name attribute only retains the conditional options that are defined last because they replace any previously defined options for the :name key in the the exposures hash.

This means that (for the code above) if :kind is :short then the :name attribute will not be exposed at all.

My first idea was to append some sort of unique identifier to each key in the exposures hash, but this produced a bunch of errors in the specs because they access the keys in the exposures hash directly to test values, and do not know beforehand what unique ID will be added.

Any thoughts?

Discussion on how to handle reopening exposures.

Example.

class Person < Grape::Entity
  expose :user do
    expose(:in_first) { |_| 'value' }
  end
end

class Student < Person
  expose :user do
    expose(:user_id) { |_| 'value' }
    expose(:user_display_id, as: :display_id) { |_| 'value' }
  end
end
  • Current behaviour is that both exposures are merged.
  • @dblock expressed his concern that this does not follow the same logic as redefining a method: The old method gets overwritten; so if you want to call it, you would use super.

related: #63

Nested exposures are causing duplication.

When using a nested exposure the inner exposure appears twice in the output.

Once as a sub element and again as a top level element, see the output below.

expose :profile do
    expose :username
end

Result:

{"name":"Nick","profile":{"username":"nickls"},"username":"nickls"}

Expected Result:

{"name":"Nick","profile":{"username":"nickls"}}

Set error message

Hi All,

Could you please specify message for error in this line.

Without it rspec display the following useless message in my case:

  1) ClientAPI::Plans GET /plans behaves like JSON200 behaves like SuccessJSON meta status
     Failure/Error: Unable to find matching line from backtrace
     ArgumentError:
       ArgumentError
     Shared Example Group: "SuccessJSON" called from ./spec/support/client_api_shared_examples.rb:23

Thanks

Runtime exposure does not work

This example explained here in the doc does not work. Is the doc outdated or has a bug snuck into the code? :-)

class ExampleEntity < Grape::Entity
  expose :attr_not_on_wrapped_object
  # ...
private

  def attr_not_on_wrapped_object
    42
  end
end

The versions I'm testing with

✗ ack "( rails |grape)" Gemfile.lock
  remote: git://github.com/intridea/grape-entity.git
    grape-entity (0.4.5)
  remote: git://github.com/intridea/grape.git
    grape (0.9.1)
    grape-swagger (0.8.0)
      grape
      grape-entity
  grape!
  grape-entity!
  grape-swagger
  rails (= 4.1.5)

My code

module V1
  module Entities
    class Tasks < Grape::Entity
      expose :attr_not_on_wrapped_object
    end

    def attr_not_on_wrapped_object
      42
    end
  end
end

results in..

     NoMethodError:
       undefined method `attr_not_on_wrapped_object' for #<Task:0x007fae21902208>

0.4.0 - undefined method `to_set'

After a bundle update I now see this:

/home/travis/build/subledger/subledger/vendor/bundle/ruby/2.1.0/gems/grape-entity-0.4.0/lib/grape_entity/entity.rb:485:in `<class:Entity>': undefined method `to_set' for #<Array:0x000000027efa98> (NoMethodError)
  from /home/travis/build/subledger/subledger/vendor/bundle/ruby/2.1.0/gems/grape-entity-0.4.0/lib/grape_entity/entity.rb:43:in `<module:Grape>'
  from /home/travis/build/subledger/subledger/vendor/bundle/ruby/2.1.0/gems/grape-entity-0.4.0/lib/grape_entity/entity.rb:3:in `<top (required)>'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/travis/build/subledger/subledger/vendor/bundle/ruby/2.1.0/gems/grape-entity-0.4.0/lib/grape_entity.rb:3:in `<top (required)>'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/travis/build/subledger/subledger/vendor/bundle/ruby/2.1.0/gems/grape-entity-0.4.0/lib/grape-entity.rb:1:in `<top (required)>'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:135:in `require'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:135:in `rescue in require'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:144:in `require'
  from /home/travis/build/subledger/subledger/lib/subledger/domain.rb:2:in `<top (required)>'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/travis/build/subledger/subledger/lib/subledger.rb:17:in `<top (required)>'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/travis/.rvm/rubies/ruby-2.1.0/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'

Same code works fine with 0.3.0

Ideas?

Fix or workaround for duplicate exposure caveat?

I'm looking to expose using different entities if a user is anonymous or not:

class MediaEntity < Grape::Entity
  expose :id
  expose :user, using: Entities::UserEntity, if: lambda { |object, options| object.anonymous == false }
  expose :user, using: Entities::AnonymousUserEntity, if: lambda { |object, options| object.anonymous == true }
end

present :data, media, with: MediaEntity

This doesn't work as you documented in the caveat section. Is there a good workaround?

Also, do you have a solution in mind for this?

Please version bump

This gem hasn't had a version bump in ~10 months, and we need to use some of the new features that have been written (and put in the readme) since then. As far as I can tell the only way of doing that right now is running off of master (not viable) or forking (would really prefer not to). So, a version bump would be much appreciated.

Thanks!

How to pass :type options when using expose to map to another Grape Entity

I have a User Entity like this

class User < Grape::Entity
  expose :id, :username, :email, :fullname, :about, :birthday, :address, :gender
  expose :created_at, :updated_at, :confirmed_at
  expose :billing_address, if: { type: :full }
  expose :shipping_addresses, if: { type: :full }, proc: Proc.new { |user, options| user.shipping_addresses }
end

And a Shop Entity

class Shop < Grape::Entity
  expose :name, :alias, :info, :address
  expose :opening_time, if: { type: :full }
  expose :owner, using: API::Entity::User
end

I cannot find a way to pass the { type: :full } option to expose :owner, using: API::Entity::User. Without that option, the shop entity will only include user entity with fields without { type: :full }.

Expose with block ignores 'using'

class Comment < ActiveRecord::Base
  attr_accessible :id, :title, :content, :likes_count, :created_at, :updated_at
end

module API
  class Comment < Grape::Entity
    expose :id, content
  end
  class Post < Grape::Entity 
    expose :id, :title, :content
    expose :comments, using: API::Comment do |post, options|
      post.comments.page(1).per(10)
    end
  end
end

When execute present:

  present :posts, with: API::Post   

The Result:

  {
    id: 10,
    content: 'post content'
    comments: [{
      id: 1,
      title: 'comment title',
      content: 'comment content',
      likes_count: 0,
      created_at: '',
      updated_at: '',
    }]
  }

Above this, the 'using' attribute does not effect.

Entity should include rspec helpers to simplify testing of Entities.

The definition of an Entity can include logic and should be tested.

To simplify this we should put together some helpers that come with the gem to increase testability. Ensuring we expose enough of the entity to satisfy the helpers will also ensure that entities are testable if the helpers are not used.

# check to make sure we are exposing a property.
# this should make sure a mock calls its name method when represented
it { should represent(:name) }
# check to make sure we are exposing several properties
# this should make sure a mock calls its name and age methods when represented
it { should represent([:name, :age]) }
# this should make sure when the mock is represented we call name, but return it as nombre
it { should represent(:name).as(:nombre) }
# this should check to make sure that name is only call when we pass language: :french in the block
# I think we call it empty, with a bad key, with a bad value, and correctly.
it { should represent(:name).as(:nom).when(language: :french) }
# The inverse of above.
it { should represent(:age).unless(is_minor: true) }
# Checks to make sure we are using another entity.
# If we can't do them all, this is the first to exclude.
it { should represent(:person).using(People::Entity) }

Duplicated output inside 'expose' block

Hello. I'm experiencing duplicated output of an exposed field, and I can't seem to find the reason.

Here's the code:

class Course < ActiveRecord::Base
  include Grape::Entity::DSL

  default_value_for :uuid do
    SecureRandom.hex(10)
  end

  has_paper_trail

  belongs_to :user
  has_many   :chapters

  has_many   :topics,           through: :chapters
  has_many   :topic_elements,   through: :topics

  validates_with CourseUUIDValidator

  def snapshot release_name, changelog=""
    return false unless status == 'published'

    Delayed::Job.enqueue SnapshotJob.new(
      course_id:    self.id,
      release_name: release_name,
      changelog:    changelog
    )
  end

  def entity
    Entity.new self
  end

  def self_path
    "/courses/#{id}"
  end

  class Entity < Grape::Entity

    format_with(:iso_timestamp) { |dt| dt.iso8601 }

    expose :id

    expose :name,
      documentation: { type: String, desc: 'Name of the course' },
      if: ->(_, options) { field_included?( options[:fields], 'name' ) }

    expose :status,
      documentation: {
        type: String,
        values: ['published', 'draft'],
        desc: 'Status of the course (draft/published)'
      },
      if: ->(_, options) { field_included?( options[:fields], 'status' ) }

    expose :chapters, using: Chapter::Entity

    with_options(format_with: :iso_timestamp) do
      expose :created_at
      expose :updated_at
    end

    expose :_links do
      expose :self_path
    end

  end

end

And the output:

[
  -{
    id: 1
    name: "Course 1"
      -chapters: [
        -{
          id: 1
          status: "published" 
          created_at: "2014-03-28T15:31:06Z"
          updated_at: "2014-03-28T15:31:06Z"
       }
  -{
    id: 2
    status: "published"
    created_at: "2014-03-28T15:31:06Z"
    updated_at: "2014-03-28T15:31:06Z"
   }
  -{
    id: 3
    status: "published"
    created_at: "2014-03-28T15:31:06Z"
    updated_at: "2014-03-28T15:31:06Z"
    }
  ]
  created_at: "2014-03-28T15:31:06Z"
  updated_at: "2014-03-28T15:31:06Z"
    -_links: {
      self_path: "/courses/1"
    }
  self_path: "/courses/1"
  }
]

What am I doing wrong? Why is "self_path" appearing twice?

entity documentation support for array of entities?

hi there I have the following grape-entites

module HQ
  module Entities
    class Client < Grape::Entity
      expose :created_by_id, documentation: {type: Integer, desc: "Created by id"}
      expose :updated_by_id, documentation: {type: Integer, desc: "Updated by id"}
      expose :first_name,    documentation: {type: String, desc: 'First Name.'}
      expose :last_name,     documentation: {type: String, desc: 'Last Name.'}
      expose :zipcode,       documentation: {type: String, desc: 'Zip Code.'}
      expose :emails, using: HQ::Entities::Email, documentation: {type: ::Item, desc: 'Emails.'}
    end
  end
end

module HQ
  module Entities
    class Email < Grape::Entity
      expose :address, documentation: {type: Integer, desc: "Email"}
    end
  end
end

the problem that I need that swagger generate the UI with the complex form I means client has many emails

      expose :emails, using: HQ::Entities::Email, documentation: {type: Array, desc: 'Emails.'}

what type I need to use? I added array but it show me just as string field like this

https://www.evernote.com/shard/s143/sh/a53f78c8-5599-4148-811f-e6b7617cd13a/419383ac0d5d9e5bac0a06278ac481da

Update README

Maybe clarify that you can call methods on objects like this:

expose :categories, :using => CategoryEntity do | instance |
    instance.method_that_returns_categories(option_1, option_2)
end

Useful if you use a gem like ActsAsTaggableOn for tags, where you can't access the children by just calling parent.child.

Representations cannot be serialized/cached

Hey guys!
I'm having an issue using Grape with cache (Garner) and send the results with grape-entity.

The issue is: the second time I request an object Garner start working, but the call for Grape Entity have a problem, because it sets the options as nil, making grape-entity to throw an error, options is not define.

Versions
Grape 0.6.1
Grape-entity 0.4.0
Garner 0.3.2

present :with versus expose :using inconsistency

Assumed grape and grape-entity would use similar keys when providing a presenter class. They don't:

# grape
present user, with: Entities::User::Base

# grape-entity
expose :user, using: Entities::User::Base

So I did the following which fails silently and presents the entire object.

# grape-entity
expose :user, with: Entities::User::Base

Next thing I know the iOS guy emails me wtf, and sure enough secret tokens are in the user json for posts. Embarrassing!

  1. Why are they different keys when conceptually they do the same thing?
  2. Would you be down to throw an error if the class passed to :with in the expose statement descends from Grape::Entity? happy to PR.

I realize it's a pretty trivial issue, but I won't be the only one to mess this up.

Setup YardDoc

We had this in the Rakefile

#
# TODO: setup a place for documentation and then get this going again.
#
# begin
#   require 'yard'
#   DOC_FILES = ['lib/**/*.rb', 'README.markdown']
#   
#   YARD::Rake::YardocTask.new(:doc) do |t|
#     t.files   = DOC_FILES
#   end
#   
#   namespace :doc do
#     YARD::Rake::YardocTask.new(:pages) do |t|
#       t.files   = DOC_FILES
#       t.options = ['-o', '../grape.doc']
#     end
#     
#     namespace :pages do
#       desc 'Generate and publish YARD docs to GitHub pages.'
#       task :publish => ['doc:pages'] do
#         Dir.chdir(File.dirname(__FILE__) + '/../grape.doc') do
#           system("git add .")
#           system("git add -u")
#           system("git commit -m 'Generating docs for version #{version}.'")
#           system("git push origin gh-pages")
#         end
#       end
#     end
#   end
# rescue LoadError
#   puts "You need to install YARD."
# end

Duplicated output using extracted entity class

Hey again :)

I'm using this inside an AR model class:

def links
    {
      self:     topic_path(self),
      publish:  publish_topic_path(self)
    }
end

And the entity associated to this model features:

expose :links, using: 'RelLinks'

The RelLinks class:

class RelLinks < Grape::Entity

  expose :links do |hash, _|
    [].tap do |arr|
      hash.each_pair do |link_k, link_v|
        arr << { rel: link_k, href: link_v }
      end
    end
  end

end

This is so that I can have a generic rel links presenter. However, my output is:

....
 "topics": [
                {
                    "id": 2,
                    "name": "Topic 2",
                    "status": "draft",
                    "created_at": "2014-04-02T13:14:05Z",
                    "updated_at": "2014-04-02T13:14:05Z",
                    "links": {
                        "links": [
                            {
                                "rel": "self",
                                "href": "/courses/1/chapter/1/topics/2"
                            },
                            {
                                "rel": "publish",
                                "href": "/courses/1/chapter/1/topics/2/publish"
                            }
                        ]
                    }
                },
                {
                    "id": 4,
                    "name": "Topic 4",
                    "status": "draft",
           ....

How can I get rid of the links/links duplication?

Thanks!

not possible to expose collection whose items have the same class as parent entity

When one wants to expose a collection of objects whose class is the same as exposing entity, one gets "stack too deep" because we have an infinite loop.

We should be able to set custom variables when rendering these nested entitiesl
This way we can set when the entity should expose nested collections.
That's not currently possible and this bug is mention on issue #94 .

present nestedly?

I expect each response should contains code & msg in addition to normal data. Looks like this:

{
    code: 1000,
    msg: "OK",
    data: {
        // normal data goes here if exists
    }
}

I have this code:

resources :books do
    get do
        present :code, 1000
        present :msg, "OK"
        present :data do
            present :items, Book.all, with: Entities::Book
        end
    end
end

Of course this won't work & not DRY. Is there an approach instead?

NoMethodError (undefined method `represent' for [...]) when using nested entities

I'm using Grape 0.7.0 and Grape-Entity 0.4.3 and get a NoMethodError when trying to nest entities:

  module Entities

    class Client < Grape::Entity
      expose :id
      expose :number
      expose :company
      expose :address1
      expose :address2
      expose :city
      expose :zip
      expose :country
      expose :tax
      expose :email
      expose :phone
      expose :web
      expose :contacts, using: API::Entities::Contact
    end

    class Contact < Grape::Entity
      expose :id
      expose :title
      expose :name
      expose :surname
      expose :department
      expose :email
      expose :phone
      expose :client_id
    end

  end

My route:

module Routes
  class Clients < Grape::API

    resource :clients do

      desc 'Get all clients'
      params do
        optional :ids, type: String, desc: 'Comma-separated list of IDs'
      end

      get do   # GET /api/v1/clients
        authenticate!

        clients = Client.all.order(:company)
        clients = clients.where(:id => params[:ids].split(',')) if params['ids']

        error! 'There are no clients', 404 unless clients.any?
        present clients, with: API::Entities::Client

      end

  end
end

Thats the full error log:

NoMethodError (undefined method `represent' for #<Class:0x00000104b425d0>):
  activerecord (4.1.1) lib/active_record/dynamic_matchers.rb:26:in `method_missing'
  grape-entity (0.4.3) lib/grape_entity/entity.rb:457:in `value_for'
  grape-entity (0.4.3) lib/grape_entity/entity.rb:397:in `block in serializable_hash'
  grape-entity (0.4.3) lib/grape_entity/entity.rb:395:in `each'
  grape-entity (0.4.3) lib/grape_entity/entity.rb:395:in `inject'
  grape-entity (0.4.3) lib/grape_entity/entity.rb:395:in `serializable_hash'
  activesupport (4.1.1) lib/active_support/core_ext/object/json.rb:140:in `block in as_json'
  activesupport (4.1.1) lib/active_support/core_ext/object/json.rb:140:in `map'
  activesupport (4.1.1) lib/active_support/core_ext/object/json.rb:140:in `as_json'
  activesupport (4.1.1) lib/active_support/json/encoding.rb:34:in `encode'
  activesupport (4.1.1) lib/active_support/json/encoding.rb:21:in `encode'
  activesupport (4.1.1) lib/active_support/core_ext/object/json.rb:37:in `to_json_with_active_support_encoder'
  grape (0.7.0) lib/grape/formatter/json.rb:6:in `call'
  grape (0.7.0) lib/grape/middleware/formatter.rb:33:in `block in after'
  grape (0.7.0) lib/grape/middleware/formatter.rb:32:in `collect'
  grape (0.7.0) lib/grape/middleware/formatter.rb:32:in `after'
  grape (0.7.0) lib/grape/middleware/base.rb:25:in `call!'
  grape (0.7.0) lib/grape/middleware/base.rb:18:in `call'
  grape (0.7.0) lib/grape/middleware/base.rb:24:in `call!'
  grape (0.7.0) lib/grape/middleware/base.rb:18:in `call'
  grape (0.7.0) lib/grape/middleware/error.rb:26:in `block in call!'
  grape (0.7.0) lib/grape/middleware/error.rb:25:in `catch'
  grape (0.7.0) lib/grape/middleware/error.rb:25:in `call!'
  grape (0.7.0) lib/grape/middleware/base.rb:18:in `call'
  newrelic-grape (1.3.1) lib/newrelic-grape/instrument.rb:19:in `block in call!'
  newrelic_rpm (3.8.1.221) lib/new_relic/agent/instrumentation/controller_instrumentation.rb:357:in `perform_action_with_newrelic_trace'
  newrelic-grape (1.3.1) lib/newrelic-grape/instrument.rb:18:in `call!'
  grape (0.7.0) lib/grape/middleware/base.rb:18:in `call'
  rack (1.5.2) lib/rack/head.rb:11:in `call'
  rack (1.5.2) lib/rack/builder.rb:138:in `call'
  grape (0.7.0) lib/grape/endpoint.rb:157:in `call!'
  grape (0.7.0) lib/grape/endpoint.rb:145:in `call'
  rack-mount (0.8.3) lib/rack/mount/route_set.rb:152:in `block in call'
  rack-mount (0.8.3) lib/rack/mount/code_generation.rb:96:in `block in recognize'
  rack-mount (0.8.3) lib/rack/mount/code_generation.rb:68:in `optimized_each'
  rack-mount (0.8.3) lib/rack/mount/code_generation.rb:95:in `recognize'
  rack-mount (0.8.3) lib/rack/mount/route_set.rb:141:in `call'
  grape (0.7.0) lib/grape/api.rb:537:in `call'
  grape (0.7.0) lib/grape/api.rb:44:in `call!'
  grape (0.7.0) lib/grape/api.rb:40:in `call'
  actionpack (4.1.1) lib/action_dispatch/journey/router.rb:71:in `block in call'
  actionpack (4.1.1) lib/action_dispatch/journey/router.rb:59:in `each'
  actionpack (4.1.1) lib/action_dispatch/journey/router.rb:59:in `call'
  actionpack (4.1.1) lib/action_dispatch/routing/route_set.rb:676:in `call'
  apartment (0.24.3) lib/apartment/reloader.rb:18:in `call'
  newrelic_rpm (3.8.1.221) lib/new_relic/rack/error_collector.rb:55:in `call'
  newrelic_rpm (3.8.1.221) lib/new_relic/rack/agent_hooks.rb:32:in `call'
  newrelic_rpm (3.8.1.221) lib/new_relic/rack/browser_monitoring.rb:27:in `call'
  rack-cors (0.2.9) lib/rack/cors.rb:54:in `call'
  apartment (0.24.3) lib/apartment/elevators/generic.rb:22:in `call'
  rack (1.5.2) lib/rack/etag.rb:23:in `call'
  rack (1.5.2) lib/rack/conditionalget.rb:25:in `call'
  rack (1.5.2) lib/rack/head.rb:11:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/flash.rb:254:in `call'
  rack (1.5.2) lib/rack/session/abstract/id.rb:225:in `context'
  rack (1.5.2) lib/rack/session/abstract/id.rb:220:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/cookies.rb:560:in `call'
  activerecord (4.1.1) lib/active_record/query_cache.rb:36:in `call'
  activerecord (4.1.1) lib/active_record/connection_adapters/abstract/connection_pool.rb:621:in `call'
  activerecord (4.1.1) lib/active_record/migration.rb:380:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
  activesupport (4.1.1) lib/active_support/callbacks.rb:82:in `run_callbacks'
  actionpack (4.1.1) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/reloader.rb:73:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/remote_ip.rb:76:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
  railties (4.1.1) lib/rails/rack/logger.rb:38:in `call_app'
  railties (4.1.1) lib/rails/rack/logger.rb:20:in `block in call'
  activesupport (4.1.1) lib/active_support/tagged_logging.rb:68:in `block in tagged'
  activesupport (4.1.1) lib/active_support/tagged_logging.rb:26:in `tagged'
  activesupport (4.1.1) lib/active_support/tagged_logging.rb:68:in `tagged'
  railties (4.1.1) lib/rails/rack/logger.rb:20:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/request_id.rb:21:in `call'
  rack (1.5.2) lib/rack/methodoverride.rb:21:in `call'
  rack (1.5.2) lib/rack/runtime.rb:17:in `call'
  activesupport (4.1.1) lib/active_support/cache/strategy/local_cache_middleware.rb:26:in `call'
  rack (1.5.2) lib/rack/lock.rb:17:in `call'
  actionpack (4.1.1) lib/action_dispatch/middleware/static.rb:64:in `call'
  rack (1.5.2) lib/rack/sendfile.rb:112:in `call'
  railties (4.1.1) lib/rails/engine.rb:514:in `call'
  railties (4.1.1) lib/rails/application.rb:144:in `call'
  rack (1.5.2) lib/rack/content_length.rb:14:in `call'
  puma (2.8.2) lib/puma/server.rb:490:in `handle_request'
  puma (2.8.2) lib/puma/server.rb:361:in `process_client'
  puma (2.8.2) lib/puma/server.rb:254:in `block in run'
  puma (2.8.2) lib/puma/thread_pool.rb:92:in `call'
  puma (2.8.2) lib/puma/thread_pool.rb:92:in `block in spawn_thread'


  Rendered /Users/peter_goebel/.rvm/gems/ruby-2.1.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.8ms)
  Rendered /Users/peter_goebel/.rvm/gems/ruby-2.1.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (1.4ms)
  Rendered /Users/peter_goebel/.rvm/gems/ruby-2.1.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.3ms)
  Rendered /Users/peter_goebel/.rvm/gems/ruby-2.1.1/gems/actionpack-4.1.1/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (17.1ms)

Move to intridea/grape-entity?

Would you be willing to move this over to the Intridea organization? Since it's an official part of Grape I'd like to keep everything under one roof.

About nested JSON

My expectation

{"result":{"invitees":[{"id":1, "name":"Jack"}],
               "event":{"id":2, "title","Dance tonight", ....}
 }

The following works, but I can't use my entity class.

present({ invitees: users, event: event }, root: :result

I try put my entity definition in my model class or add parameter "with API::Entities::Event", both of them not work.

"present" would not auto load the entity class at above line of code.
Also not accept "with" entity parameters.

Do any way to solve this? Thanks.

Evaluate expose block in instance context

I noticed that the block version of an exposure pretty much runs without any usable context. The context it currently uses is the class of the entity, which doesn't really provide much, passing in the object being wrapped and the options.

I think it would be a lot cleaner and more intuitive if the block was evaluated in the context of the instance around here.

Here's an example use case where that cleans things up:

class ProductEntity < Grape::Entity
   expose :local_prices do
      prices.select(&:local?)
   end
   expose :web_prices do
      prices.select(&:web?)
   end
private

  def prices
    @prices ||= expensive_api_call
  end
end

This doesn't work currently because the prices method is on the instance but the block gets evaluated on the class. Wouldn't want to add the prices method to the class because it is stateful.

Alternatively (and I prefer this), you could have exposures see if the entity responds to it first, and if not delegate to the object as normal. I think Roar does this and it is much easier to build a proper facade that way. Admittedly, it is a more breaking API change. For comparison:

class ProductEntity < Grape::Entity
   expose :local_prices
   expose :web_prices
   expose :attribute_on_the_product
private

  def web_prices
    prices.select(&:web?)
  end

  def local_prices
    prices.select(&:local?)
  end

  def prices
    @prices ||= expensive_api_call
  end
end

Thoughts?

Let exceptions bubble up in test.

I have an entity which has a lambda that quires the database in order to decide if a field should be displayed. As it happens this query(an active record object) generates a a database level error. My test did not pick up the error because the exception does not bubble up to rspec. Allowing these errors to bubble up to rspec would increase spec effectiveness and make testing regressions much easier.

expose and method_missing

valid_exposure? uses respond_to?, and does not work with method_missing. Something like this would make it work

def respond_to_attribute?(object, attribute)
  object.send(attribute)
  true
rescue
  false
end

but it does seem dirty. Is it desired behavior to put the burden on exposed objects to properly respond to respond_to? or any interest in a pull request using this instead of respond_to??

`Using` cause circular dependency

I have two simple models:

class User < ActiveRecord::Base
  has_many :notebooks, dependent: :destroy
  has_many :notes, :through => :notebooks
end
class Note < ActiveRecord::Base
  belongs_to :notebook
  has_one :user, :through => :notebook
end
module Entities
class Note < Grape::Entity
  expose :id, :title, :views_count, :slug

  expose :user, :using => Entities::User
end
end

And have two simple Entity for them. And in Entities::Note, I'd like to present user model using Entities::User with the syntax :using . But I found that it only works if I require the Entities::User file before the Entities::Note file. Other wise I will get an error saying: [:error, "undefined method `represent' for #Class:0x007fde9cecc4d0"]

Is there a good way to solve this? since I am also gonna present notes inside user.

Thank you very much.

Hang in to_json with EM::Syncrhony connection pool

I've found an odd issue where Grape's present method, when used with a Grape Entity, while having an EM::Syncrhony Connection Pool to Redis appears to hang. The problem appears to arise when its time to do a to_json.

It's an odd issue that I think might be related to concurrency.

If I do present myModel, EntityClass (it hangs)
If I just do: present myModel (It works, occasionally)
If I just return the model (without using present) and don't use entity, everything works fine.
If I remove the connection pools and simply use present myModel, EntityClass (it works).

It does seem to point towards synchrony, but since it just appears to hang when using entity, I would like to know if you have any thoughts on this issue.

expose & using with a block

module Raptor
  module Entities

    class RaptorEntity < Grape::Entity
      format_with :stringify do |attribute|
        "#{attribute}"
      end
      def self.expose_id(id)
        expose id, :format_with => :stringify
      end
      def self.expose_id_as(id, pseudonym)
        expose id, :format_with => :stringify, as: pseudonym
      end
    end

    class ContactUpsheetEntity < RaptorEntity
      expose_id :id
      expose_id_as :dealership_id, :dealership
      expose(:unassigned){|upsheet, options|
        upsheet.status == "unassigned"
      }
      expose :status
      expose :temperature
      expose(:users){|upsheet, options|
        upsheet.users.map {|user| "#{user.id}"}
      }
    end

    class ContactEntity < RaptorEntity
      expose_id :id
      # This works as expected
      expose :upsheets, using: ContactUpsheetEntity
      # This exposes the whole object, rather than using ContactUpsheetEntity
      expose :sheets, using: ContactUpsheetEntity do |contact, options|
        contact.upsheets
      end
    end

  end
end

I'm having a really hard time trying to figure out why the line expose :upsheets, using: ContactUpsheetEntity works as expected, but the line expose :sheets, using: ContactUpsheetEntity doesn't use ContactUpsheetEntity and instead exposes the entire object.

screen shot 2013-10-18 at 12 06 25

Any help would be appreciated.

Hash values not serialized

The values of exposed hashes are not serialized. In the example below, a call to Person.serizable_hash() yields a hash with the key 'r:address' pointing to an Address entity instead of serializable_hash of the Address entity object.

require 'grape_entity'

class Address < Grape::Entity
  expose :street
  expose :city
end

class Person < Grape::Entity
  expose :name

  expose :_embedded do |person|
    {
      'r:address' => Address.new(person.address),
    }
  end
end

The solution I have come up with (and will PR shortly) is to update Grape::Entity.serializable_hash by adding a check for for Hash similar to what is done for Array:

def serializable_hash(runtime_options = {})
      return nil if object.nil?
      opts = options.merge(runtime_options || {})
      exposures.inject({}) do |output, (attribute, exposure_options)|
        if (exposure_options.has_key?(:proc) || object.respond_to?(attribute)) && conditions_met?(exposure_options, opts)
          partial_output = value_for(attribute, opts)
          output[key_for(attribute)] =
            if partial_output.respond_to? :serializable_hash
              partial_output.serializable_hash(runtime_options)
            elsif partial_output.kind_of?(Array) && !partial_output.map {|o| o.respond_to? :serializable_hash}.include?(false)
              partial_output.map {|o| o.serializable_hash}
            elsif partial_output.kind_of?(Hash)
              # Serialize exposed hashes
              partial_output.each do |key, value|
                partial_output[key] = value.serializable_hash if value.respond_to? :serializable_hash
              end
            else
              partial_output
            end
        end
        output
      end
    end

If there are suggestions for alternatives or improvements, or reasons why this functionality should not be included, please present them.

How to pass variable to entity?

I need to pass the variables to entity.

Here's the code

present @posts, with: Mobile::Entities::Post, :current_user_id => @current_user_id

But I don't know how to get my current_user_id inside the entity
Here's the way I tried. As wiki, seems it only support the :type for the :document field.

expose :liked do |post, options|
        p options[:current_user_id]

Besides, I try to print the options as above, there huge info inside the options object, what's the options represented?

#as_json confusion in Rails

If I'm using grape-entity in Rails, and I do something like this:

module Entities
  class User < Grape::Entity
    root :users, :user
    expose :id
    expose :name
  end
end

and then I do something like this:

Entities::User.represent(user).as_json

I'll end up with something like this:

{ "user" => { :id => 1234, :name => "Danny Boy" } }

Notice that the keys are a mixture of symbols and strings. This is because the result of Entities::User.represent(user) is a Hash, and I think the as_json method there actually comes from ActiveModel, which uses strings for the keys. But the value for the "user" key is a Grape::Entity, and Grape::Entity#as_json uses symbols for the keys.

I think it would be good to have better control over the Hash that results from using a root element. Maybe create a subclass of Hash that implements as_json and any other relevant/useful methods?

Thoughts?

Nested Expose Attribuites

module API
  class User < Grape::Entities
     expose :loginname, :email
     expose :person, using: API::Person do |user, options|
       user.person
     end
  end
end

module API
  class Person < Grape::Entites
     expose :name, :avator
  end
end

module API
  class User < Grape::API
    get '/user/:id',  do
       present User.find(params[:id]), with: API::User
    end
  end
end

Result:

{
  loginname: "test",
  email: "[email protected]",
  person: {
    name: "test",
    avator: "/uri",
    birthday: "2013/11/11"   // it is't i wanted. 
    //...some other attribuites in person model
  }
}

But, I Want:

{
  loginname: "test",
  email: "[email protected]",
  person: {
    name: "test",
    avator: "/uri"
  }
}

expose and format_with DSL broken when receiving blocks?

Seems like the DSL for expose and format_with is not working when passing blocks unless you use explicit parenthesis.

To debug this in isolation, I've use the following code snippet:

  class Comment
    include Grape::Entity::DSL

    attr_accessor :text

    entity :text
  end

  class Post
    include Grape::Entity::DSL

    attr_accessor :title, :content, :comments

    entity :title, :content do
      format_with :array_counter { |array| array.size }

      expose :comment_count { |message, options| message.comments.size }
      expose :comments, :format_with => :array_counter
    end 
  end

Trying to present a "Post" entity with two "Comments" then throws:

syntax error, unexpected '{', expecting keyword_end

The error is thrown while interpreting the format_with or expose lines (in this case format_with since it comes first)

If I change the format_with and expose declarations to include explicit parenthesis like this:

      format_with (:array_counter) { |array| array.size }

      expose (:comment_count) { |message, options| message.comments.size }

I then get the expected JSON output

{"title":"Post title","content":"Post contents","comment_count":2,"comments":2}

Tried it also with explicit creation of the entities subclassing Grape::Entity instead of including Grape::Entity::DSL, same result

Running ruby version: ruby 1.9.2p320 (2012-04-20 revision 35421) [x86_64-darwin11.4.2] under Mac OS X 10.7.5

sideloading of entities

I'm trying to sideload entities with specified roots.

For example:

class A::Entity < Entity
  root 'aa', a'
end
class B::Entity < Entity
  root 'bb', 'b'
end
...
get do
  present A.all
  present B.all
end

Should result in this hash:
{aa => [...], bb => [...]}

However, present overwrites the last results, so only 'bb' remains.

I can "fix" this by merging @Body in Endpoint#present, like so:

representation = @body.merge(representation) if @body
body representation

However, that will probably break horribly if there are no roots defined. Is there a better way to do this?

Ideally, i'd want the option to specify sideloadable relations in Grape::Entity itself and they would get pulled in automatically when calling 'present' with a Entity.

Nested exposure double-exposes a field

Hi,

class Thing
  def something
    'hello'
  end
end

class Thing::Entity < Grape::Entity
  expose :fields do
    expose :something
  end
end

returns: 

{
  "thing": {
    "fields": {
      "something": "hello"
    },
    "something": "hello"
  }
}

I would expect:

{
  "thing": {
    "fields": {
      "something": "hello"
    },
  }
}

Is this the expected behaviour? How can I get what I want?

I am using master

xml format is incorrect?

module MagicOrder
  module Entities
    class RefundProduct < Grape::Entity
      root "refund_products","refund_product"
      expose :status
      expose :errors, :if => lambda {|object,options| object.errors.present? }
    end
  end
end
present @refund_product, with: Entities::RefundProduct
<hash>
  <refund-product>
    <status>recognize</status>
    <errors>
      <error>Status 操作失败</error>
    </errors>
  </refund-product>
</hash>

The result isn't :

  <refund-product>
    <status>recognize</status>
    <errors>
      <error>Status 操作失败</error>
    </errors>
  </refund-product>

?

Or where I write the wrong?

Performance issues introduced by nested exposures

So for some reason, grape-entity has become really slow for me recently. I played a bit with the nested exposures code and if I disable them (my entities don't have them), performance increases dramatically, we're talking API response goes from 900ms to 50ms for 25 records.

Here's the stuff I disabled: https://github.com/jure/grape-entity/commit/a46c7d5019cfbc856eb5b51ea730a9e17272108a

I would love to explore these issues more thoroughly, perhaps someone can guide me in the right direction.

Entity not working properly when using formatter

Hey,

it seems that the entities with formatters only work correctly when used with an array.

small example:

tour entity

class Tour < API::V1::Entities::Base
  expose :start_at, :format_with => :utc
end

the tour model

Tour.first.start_at #  Wed, 15 Jun 2011 12:00:00 CEST +02:00
Tour.first.start_at.class # ActiveSupport::TimeWithZone

in addition, I have a formatter in my initializers:

Grape::Entity.format_with :utc do |date|
  date.utc if date
end
API::V1::Entities::Tour.represent(Tour.first).to_json 

produces: \"start_at\":\"2011-06-15 10:00:00 UTC\"

and

API::V1::Entities::Tour.represent([Tour.first]).to_json 

produces: \"start_at\":\"2011-06-15T10:00:00Z\"

note the different time formats "Z" and "UTC".

I'm not sure if i'm missing something... thanks

How to present one namespace model ?

I have one namespace model like this :

    class Message::Notification < Message
    end

And I define one entity like this:

    class Message::Notification < Grape::Entity
       expose :id
    end

There is a error when present the model:

 present @notification, with: Entities::Message::Notification

Error:

app/models/message/notification.rb:1:in `<top (required)>': superclass
 mismatch for class Notification (TypeError)

Representing Hashes

When I want to present a Hash with grape entity I have to create a Struct. To walk around this issue I did override represent for myself this way:

module CoreEntityExtension
      extend ActiveSupport::Concern

      module ClassMethods
        def represent(objects, options = {})
          if objects.is_a? Hash
            objects = OpenStruct.new(objects)
          elsif objects.respond_to?(:to_ary) && objects.to_ary.first.is_a?(Hash)
            objects = objects.to_ary.map { |object| OpenStruct.new(object) }
          end

          super objects, options
        end
      end

end

# now I can use it this way in my Grape::API

present({ items: items },
               with: Entities::Items,
               type: :default)

# and this is also possible in my Entity

class Items < Grape::Entity
  expose :items, using: Item
  expose :links, using: Entities::Link

  def links
     [ options[:env]['api.endpoint'].self_link, {href: '...', rel: '...'}]
  end
end

Maybe this code part can be added to grape entity directly.

regards
dieter

Presenting a collection with an entity fetches each table twice (2 SQL statements)

I have a Location model that has_one :address. The data is stored in Postgres. Here's the Grape API endpoint definition for fetching all locations:

resource "locations" do
  # GET /locations
  desc 'Returns all locations, 30 per page by default'
  params do
    optional :page, type: Integer, default: 1
    optional :per_page, type: Integer, default: 30
  end
  get do
    locations = Location.includes(:address).
                        page(params[:page]).per(params[:per_page])
    present locations, with: Entities::Location
  end
end

My (simplified) Location Entities:

module Entities
  class Location < Grape::Entity
    expose :address, using: Address::Entity
    expose :name
  end
end

The Address::Entity is defined within the Address model:

include Grape::Entity::DSL
entity do
  expose :id
  expose :street
  expose :city
  expose :state
  expose :zip
end

When I visit http://localhost:8080/api/locations, I get the following output in the log:

Location Load (0.6ms)  SELECT "locations".* FROM "locations" ORDER BY "locations"."id" ASC LIMIT 1 OFFSET 0
Address Load (0.5ms)  SELECT "addresses".* FROM "addresses" WHERE "addresses"."location_id" IN (2)
Location Load (6.0ms)  SELECT "locations".* FROM "locations" LIMIT 30 OFFSET 0
Address Load (0.8ms)  SELECT "addresses".* FROM "addresses" WHERE "addresses"."location_id" IN (2, 17, 41, 138, 321, 54, 61, 325, 1146, 531, 139, 326, 965, 72, 238, 140, 228, 330, 1631, 271, 241, 331, 335, 6, 1460, 336, 75, 90, 101, 818)

Note that there are two calls to each table: one to fetch the first record in the results, and then another to fetch all the results (up to the per_page amount).

If I change the endpoint definition to just return locations without any grape-entity representation, and instead, use my own as_json method in the Locations model, like this:

def as_json(options={})
  {
    name: name,
    address: address
  }
end

then, I only end up with one call to each table, as opposed to the two that grape-entity made:

Location Load (6.7ms)  SELECT "locations".* FROM "locations" LIMIT 30 OFFSET 0
Address Load (4.4ms)  SELECT "addresses".* FROM "addresses" WHERE "addresses"."location_id" IN (2, 17, 41, 138, 321, 54, 61, 325, 1146, 531, 139, 326, 965, 72, 238, 140, 228, 330, 1631, 271, 241, 331, 335, 6, 1460, 336, 75, 90, 101, 818)

This is reproducible every time I switch back and forth between the two methods of representing the JSON. It also happens if the JSON doesn't include data from other tables, and even when you're just calling Location.all (not that you would do that). It always makes 2 calls to the Locations table.

In the actual app, I need to include 8 other tables, so having an extra call to each one adds up!

Has anyone noticed this before? Is there a way to write a spec for this?

How to change root node for xml?

When using grape-entity for xml, it turns the root node into the whole chain of modules, which seems kind of weird. instead of just <user> it's <api-entities-user>. Is there a way to override this? Thanks

to_xml doesn't work out of the box

Right now grape-entity supports conversion to json by using 'multi_json'. Do you think it's a good idea to require "active_support/core_ext/hash/conversions.rb" to enable xml formatting for to_xml method?

Nesting `expose` declarations

I frequently need to present an attribute whose value is an ad hoc hash. Example:

expose :custom_info do |obj, opts|
  {
    phone:   obj.custom_phone,
    website: obj.custom_website
  }
end

This pattern is fine if the values in the hash are simple. But sometimes one of those values represents an entity and I want to use a presenter, so I've found a need for the following pattern:

expose :custom_info do
  expose :custom_phone, as: phone
  expose :custom_website, as: website
  expose :custom_address, as: address, using: Entities::Address
end

Would this be a desirable feature for other people, too? I'm wondering if I should submit a pull request or just build it as an add-on using some sort of alternate syntax like nested_expose.

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.