ruby-grape / grape-entity Goto Github PK
View Code? Open in Web Editor NEWAn API focused facade that sits on top of an object model.
License: MIT License
An API focused facade that sits on top of an object model.
License: MIT License
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>
Moved over from Grape - ruby-grape/grape#75
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
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.
At the moment when one sets :type when calling #present helper method, that type is not going to change throughout nested entities. It should though.
For example: when exposing a nested entity, one might not want it to have the same type set when #present helper method is called.
Also check bug #93.
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)
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"
}
}
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
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?
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!
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?
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?
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.
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?
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?
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?
Moved over from Grape - ruby-grape/grape#89
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.
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!
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!
: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.
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.
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?
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
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.
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
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
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?
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
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?
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?
subj
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) }
what :default and :full values means?
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.
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"}}
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.
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.
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.
Any help would be appreciated.
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 .
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
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
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
super
.related: #63
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 }
.
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)
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?
?
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.
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?
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
.
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
.
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.