I am using 'grape-entity', '~> 0.7.1'
I have a hash in the format:
temp_data = [{sheet_index: 0, other_names: []},{'sheet_index' => 1, 'other_names': ['a']}]
And I have the following entities
class Sheet < Grape::Entity
expose :sheet_index, documentation: {type: Integer, desc: "Sheet index"}
expose :other_names, documentation: {type: Array, desc: "Other names"}
end
class Sheets < Grape::Entity
present_collection true
expose :items, as: 'sheet_history', using Entities::Sheet
end
# response from the entities
present temp_data, with: Entities::Sheets
Now I need to make sure that no matter the type of keys in my Hash it should still give me the correct output for the above case
expected_response = {"sheet_history" => [{"sheet_index"=>0, "other_names"=>[]}, {"sheet_index"=>1, "other_names"=>["a"]}]}
but the response I am getting is in the format below
actual_response = {"sheet_history" => [{"sheet_index"=>0, "other_names"=>[]}, {"sheet_index"=>nil, "other_names"=>nil}]}
so in the actual response sheet_index and other_names of the second element are nil because their keys were Strings, not Symbols. (Refer to temp_data.)
I have referred to https://github.com/ruby-grape/grape-entity/pull/85 to get the above implementation but still am not able to make it work without using HashWithIndifferentAccess or OpenStructs
You are missing a colon after using, but I wouldn't set up multiple entities like that as it's likely to result in wonky behavior. Try this:
# Dummy definition of your class
class Item
include ActiveModel::Serialization
attr_accessor :sheet_index
attr_accessor :other_names
def initialize(index, names)
#sheet_index = index
#other_names = names
end
end
items = []
items << Item.new(0, [])
items << Item.new(1, ['a'])
=> [
#<Item:0x00007f860f740e40 #other_names=[], #sheet_index=0>,
#<Item:0x00007f860f513618 #other_names=["a"], #sheet_index=1>
]
# Entity Definition
class Sheet < Grape::Entity
# The first arg here is the key to use for a collection,
# the second is the key to use for a single object
root 'sheet_history', 'sheet_history'
expose :sheet_index, documentation: {
type: Integer,
desc: "Sheet index" # Plz use locales
}
expose :other_names, documentation: {
type: Array,
desc: "Other names" # Plz use locales
}
end
# Test it
representation = Sheet.represent(items)
=> {
"sheet_history"=>[
#<Sheet:70106854276160 sheet_index=0 other_names=[]>,
#<Sheet:70106854275680 sheet_index=1 other_names=["a"]>
]
}
# This is just more a more readable, but as you can see it's
# both mapping all the attributes correctly and
# setting the root key that you wanted:
representation['sheet_history'].map do |r| r.serializable_hash end
=> [
{
:sheet_index=>0,
:other_names=>[]
},
{
:sheet_index=>1,
:other_names=>["a"]
}
]
# Endpoint
get do
items = current_user.items # or whatever
present items, with: Entities::Sheet
end
You can send your array of hashes to the represent method, but it doesn't like the stringified key. Ideally you should be passing DB objects to your entity instead of hashes but, if you for some reason cannot, I would pass temp_data.map(&:symbolize_keys) as your argument to the entity to ensure the top-level keys in the hash it's parsing are symbols.
Related
module Entities
class StuffEntity < Grape::Entity
root 'stuffs', 'stuff'
...
How can I DRY up my code by reusing this entity while still having the flexibility to rename the root keys ('stuffs' and 'stuff') defined in the entity?
I might need to do this in a scenario where I'm exposing a subset of a collection represented by an existing entity or exposing an associated collection that can be represented by an existing entity.
Hiding the root key when you're exposing an associated object or collection
Let's say I have an object with a name attribute and some collection of scoped_stuff that I want to expose as some_stuffs. I could do that with an entity like this:
module Entities
class CoolStuffEntity < Grape::Entity
root 'cool_stuffs', 'cool_stuff'
expose :some_stuffs, documentation: {
type: Entities::StuffEntity
} do |object, _options|
Entities::StuffEntity.represent(
object.class.scoped_stuff,
root: false
)
end
expose :name, documentation: { type: 'string' }
end
end
Passing root: false to the represent method ensures that the nested association is represented without a root key. Here are what the representations look like with and without that argument:
# Without root: false
cool_stuff: {
some_stuffs: {
stuffs: [ /* collection represented by StuffEntity */ ]
},
name: 'Something'
}
# With root: false
cool_stuff: {
some_stuffs: [ /* collection represented by StuffEntity */ ],
name: 'Something'
}
In this instance, passing root: false ensures that the nested entity's root key isn't included in our representation.
Setting a root key name when presenting an entity with no defined root
Let's say we have this entity where we did not specify root:
module Entities
class StuffEntity < Grape::Entity
expose :name, documentation: { type: 'string' }
end
end
The serializable hash for an object represented with this entity will look like: { name: 'Object name' }
In our API, we can specify the response key like so:
get do
stuff_object = Stuff.find_by(user_id: current_user)
present stuff_object,
with: Entities::StuffEntity,
root: :stuff
end
So that our response will look like this: { stuff: { name: 'Object name' } }
Note that 'root' accepts string and symbol arguments here.
If you want to rename the root key in your API response
So what if I have an entity where I specified a root key and I want the key in my response to be different (e.g., exposing a subset of the collection)? Instead of using present, I can use represent again. Except this time, instead of disabling the root key by passing 'false', I can give it a key name:
get do
my_stuff = Stuff.find_by(user_id: current_user)
Entities::StuffEntity.represent(
my_stuff,
root: :my_stuff
)
end
Currently I have the user's query type which returns a list of all the users. I have defined a custom connection and used it in the user type.
module Types
class QueryType < Types::BaseObject
description "Get all the users"
field :users, Types::UserConnection, null: false do
argument :search, String, required: false
argument :limit, Integer, required: true
argument :offset, Integer, required: true
end
def users(search:, limit:, offset:)
User.search(search).limit(limit).offset(offset)
end
end
end
My user connection looks something like this...
class Types::UserEdgeType < GraphQL::Types::Relay::BaseEdge
node_type(Types::UserType)
end
class Types::UserConnection < GraphQL::Types::Relay::BaseConnection
edge_type(Types::UserEdgeType)
field :items_currently_on_page, Integer, null: false
field :total_pages, Integer, null: false
field :items_per_page, Integer, null: false
def items_currently_on_page
object.items.size
end
def total_pages
(User.all.count.to_f / items_per_page).ceil
end
def items_per_page
3
end
end
Currently I have hard-coded the items_per_page which corresponds to the limit defined in the user's arguments. My question is how can I get the user's limit argument and replace with the hard coded value.
If you are sending the query using variables, then you can use context.query.provided_variables. That's a hash containing the variables you sent.
In the resolve function of a field you have access to the context variable.
If you have a graphql query like this:
{
users(offset: 10, limit:10) {
id
name
}
}
you should be able to access the arguments and fields like that:
# .query: Access GraphQL::Query instance
# .lookahead: Access Class: GraphQL::Execution::Lookahead instance
# Lookahead creates a uniform interface to inspect the forthcoming selections.
# .ast_nodes: Access to Array<GraphQL::Language::Nodes::Field> (.length always 1 for one query)
# .selections: Access to Array<Nodes::Field> (.length always 1 for one query)
# .name returns to name of the query defined in query_type.rb for example "users"
# .children: Access to Class: GraphQL::Language::Nodes::AbstractNode instance
# AbstractNode is the base class for all nodes in a GraphQL AST.
# Seems to be the root of the field selection of a query.
# Contains all queried connection fields like nodes, edges, pageInfo, totalCount
# Also contains the provided arguments like first,last,after,before,limit,offset.
# nodes.selections: Access to Array<Nodes::Field>
# Contains all requested nodes like id, slug, name, [...]
arguments = context.query.lookahead.ast_nodes[0].selections[0].arguments
fields = context.query.lookahead.ast_nodes[0].selections[0].children.find {|c| c.name == "nodes"}.selections
To get the value of the limit argument this should work:
def items_per_page
context.query.lookahead.ast_nodes[0].selections[0].arguments.find {|a| a.name == "limit"}.value
end
use this function for get the count of the items_per_page
def items_per_page
object.nodes.size
end
I use swagger-blocks gem on a Rails app.
Want to set a list property but didn't find it in the swagger official data types:
https://swagger.io/docs/specification/data-models/data-types/
string
number
integer
boolean
array
object
For example, the response data will be:
{
"posts": [1, 2, 3]
}
Tried:
response 200 do
schema do
property :posts do
key :type, :string
end
end
end
It's string.
You are almost there. You just need to change the type of posts from a string to an array of integer:
response 200 do
schema do
property :posts do
key :type, :array
items do
key :type, :integer
end
end
end
end
Lets say I have a User model and I am exposing User object via Grape::Entity. So in here I want to define a dynamic key which name and its value will be based on other keys(id, name, email) value.
module API
module Entities
class User < Grape::Entity
expose :id
expose :name
expose :email
expose :dynamic_key # dynamic key name will be generated based on other keys(`id`, `name`, `email`) value
private
def dynamic_key
#dynamic key value here based on other keys(`id`, `name`) value
end
end
end
end
How could I do it?
Thanks in advance!
You have access to the instance with object:
def dynamic_key
"#{object.id}_#{object.name}"
end
Edit: misunderstood the question. Don't think you can get the dynamic key this way. Can you just do something like this:
expose "#{object.id}_#{object.name}".to_sym
If someone is looking for a solution, this is what I do :
class Trucs < Grape::Entity
expose :id
# expose custom / dynamic fields
expose :details, merge: true # merge true to avoid "details": {...} in response
private
def details
details_list = {}
list_of_fields = %w[first_key second_key third_key] # keys can be dynamically created, this is just a sample.
list_of_fields.each do |field_name|
details_list[field_name.to_sym] = "#{field_name} value" # OR object.send(field_name) OR anythig you need
end
return details_list
end
end
Result is :
{
"id": 1,
"first_key": "first_key value",
"second_key": "second_key value",
"third_key": "third_key value"
}
Or for a simple case with just one dynamic key based on id and name
def details
{"#{object.id}_#{object.title}": 'my dynamic value'}
end
Why would you want to do this? If you want something dynamic, it should be the value the key maps to -- not the key itself. The consumer of your API should be able to depend on the key names being consistent with your documented definitions, and also keep in mind that the point of keys is to have a name you can consistently reference that maps to a dynamic value. So if you want to expose some dynamic_key attribute with a dynamically generated value, you can do this:
expose :id
expose :name
expose :email
expose :dynamic_key do |object, _options|
"#{object.id}_#{object.name}"
end
The hash { id: 1, name: 'Foo', email: 'foo#bar.done' } would be represented as:
{ id: 1, name: 'Foo', email: 'foo#bar.done', dynamic_key: '1_Foo' }
My model has a custom_fields column that serializes an array of hashes. Each of these hashes has a value attribute, which can be a hash, array, string, or fixnum. What could I do to permit this value attribute regardless of its type?
My current permitted params line looks something like:
params.require(:model_name).permit([
:field_one,
:field_two,
custom_fields: [:value]
])
Is there any way I can modify this to accept when value is an unknown type?
What you want can probably be done, but will take some work. Your best bet is this post: http://blog.trackets.com/2013/08/17/strong-parameters-by-example.html
This is not my work, but I have used the technique they outline in an app I wrote. The part you are looking for is at the end:
params = ActionController::Parameters.new(user: { username: "john", data: { foo: "bar" } })
# let's assume we can't do this because the data hash can contain any kind of data
params.require(:user).permit(:username, data: [ :foo ])
# we need to use the power of ruby to do this "by hand"
params.require(:user).permit(:username).tap do |whitelisted|
whitelisted[:data] = params[:user][:data]
end
# Unpermitted parameters: data
# => { "username" => "john", "data" => {"foo"=>"bar"} }
That blog post helped me understand params and I still refer to it when I need to brush up on the details.