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
Related
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'm trying to implement a query type that can search by name of the record instead of id. Here's its definition in query_type.rb.
# Get game by name
field :game_by_name, Types::GameType, null: false do
argument :name, String, required: true
end
def game_by_name(name:)
Game.where(name: name) //find a game using the name attribute
end
But when I run:
query {
gameByName(name: "League of Legends") {
id
name
}
}
I get the following error.
Failed to implement Game.id, tried:\n\n
- `Types::GameType#id`, which did not exist\n
- `Game::ActiveRecord_Relation#id`, which did not exist\n
- Looking up hash key `:id` or `\"id\"` on `#<Game::ActiveRecord_Relation:0x00007f5644442888>`, but it wasn't a Hash\n\n
To implement this field, define one of the methods above (and check for typos)\n
This is odd because the following query type works perfectly.
# Get game by ID
field :game_by_id, Types::GameType, null: false do
argument :id, ID, required: true
end
def game_by_id(id:)
Game.find(id)
end
Here's game_type.rb:
module Types
class GameType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
end
end
How do I go about fixing this? Thank you!
I stumbled upon this, chasing a similar error. Not sure if you solved it or not, but I believe the issue is in the query type definition.
You're telling it to return a type of Types::GameType, but the Active Record query returns an Active Record Relation, which is a collection. So, graphql is expecting a single instance of Game, but is instead receiving a collection. Graphql is then trying to map the returned value from the query to the type definition, but is unable to. The best hint is from this line:
Looking up hash key `:id` or `\"id\"` on `#<Game::ActiveRecord_Relation:0x00007f5644442888>`, but it wasn't a Hash..
Graphql is trying to assign :id to the ActiveRecord_Relation and it can't do it.
Two paths forward, depending on how you want the API to behave. Do you want it to return 1 record or many?
Wrapping the Types::GameType within brackets will tell graphql it's a collection and to iterate over the records
# Get game by name
field :game_by_name, [Types::GameType], null: false do
argument :name, String, required: true
end
def game_by_name(name:)
Game.where(name: name) //find a game using the name attribute
end
or have Active Record return just 1 record, something like...
# Get game by name
field :game_by_name, Types::GameType, null: false do
argument :name, String, required: true
end
def game_by_name(name:)
Game.where(name: name).limit(1).first //find a game using the name attribute
end
I know this is months old, but just putting it out there for anyone else who stumbles upon this question, like I did!
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.
I'm trying to use new Enum type, everything works well except one issue. When writing functional tests I usually use structure:
order = Order.new(o_status: :one)
post :create, order: order.attributes
# Error message:
# ArgumentError: '0' is not a valid o_status
It's ok as long as I don't have Enum attribute. The problem with enums is that instead of String value .attributes returns it's Integer value which can't be posted as enum attribute value.
In above example model can look like this:
class Order < ActiveRecord::Base
enum o_status: [:one, :two]
end
I figured out that when I do:
order = Order.new(o_status: :one)
atts = order.attributes
atts[:o_status] = "one" # it must be string "one" not symbol or integer 0
post :create, order: order.attributes
It will work OK.
Is it normal or there is some better solution?
EDIT:
The only workaround which I found looks like this:
order = { o_status: :one.to_s }
post :create, order: order
pros: It is short and neat
cons: I cannot validate order with order.valid? before sending with post
This doesn't solve issue with order.attributes when there is Enum inside.
From the Enum documentation:
You can set the default value from the database declaration, like:
create_table :conversations do |t|
t.column :status, :integer, default: 0
end
Good practice is to let the first declared status be the default.
Best to follow that advice and avoid setting a value for an enum as part of create. Having a default value for a column does work in tests as well.
I have an array of hash map. It looks like this:
params = []
CSV.foreach(......) do
one_line_item = {}
one_line_item[:sku] = "Hello"
one_line_item[:value] = "20000"
params << one_line_item
end
I want to check if :sku is in this array of hash or not. I am doing it like this:
# Reading new line of csv in a hash and saving it in a temporary variable (Say Y)
params.each do |p|
if p[:sku] == Y[:sku]
next
end
end
I am iterating through the complete list for every value of sku, and thus time complexity is going for a toss [O(n^2)], need less to say it is useless.
Is there a way I can use include??
If I can get an array of values corresponding to the key :sku from the whole array in one shot, it would solve my problem. (I know I can maintain another array for these values but I want to avoid that)
One example of params
params = [{:sku=>"hello", :value=>"5000"}, {:sku=>"world", :value=>"6000"}, {:sku=>"Hi", :value=>"7000"}]
The any? and include? methods sound like what you need.
Example:
params.any? { |param| param.include?(:sku) }
This is an efficient way to do it, as it "short circuits", stopping as soon as a match is found.
So what you want is to collect a list of all SKUs. Are you looking to key sku => value?
Hash[*params.map { |p| [p[:sku], p[:value]] }.flatten]
That will give you a map of each sku to the value, which you can then do quick key lookups with sku_hash.key?(tester)
You may use rails_param gem for doing the same. I find it a very useful utility for validation request params in controller:
https://github.com/nicolasblanco/rails_param
# primitive datatype syntax
param! :integer_array, Array do |array,index|
array.param! index, Integer, required: true
end
# complex array
param! :books_array, Array, required: true do |b|
b.param! :title, String, blank: false
b.param! :author, Hash, required: true do |a|
a.param! :first_name, String
a.param! :last_name, String, required: true
end
b.param! :subjects, Array do |s,i|
s.param! i, String, blank: false
end
end