RABL - How to create a custom child node as collection? - ruby

{
total: 250,
page: 3,
data: [
{ id: 1, name: "Foo", ...},
{ id: 2, name: "Bar", ...}
]
}
I want to create structure like this. Value of id and name are random. It is not saved any variable.

If all you are searching is how to make custom nodes is just with node method.When you declare object to be false you are free to make just custom repsonse.Here is a solution where I generate ids from 1 to 100, and name based on simple conversation to hexademical numbers.
object false
node(:total) { |m| #total }
node(:page) { |m| #page }
node(:data) do |m|
1.upto(100).map { |id| Hash[[[:id, id], [:name, (id * 143223).to_s(16)]]] }
end

Related

How to add element on graphql return fields

Im a newbie in Ruby and GraphQL
Currently i have such Mutations module
module Mutations
class ProductCreate < BaseMutation
# TODO: define return fields
# field :post, Types::PostType, null: false
type Types::ProductType
# TODO: define arguments
argument :title, String, required: true
argument :vendor, String, required: false
argument :store, ID, required: true
# TODO: define resolve method
def resolve(title:, vendor:, store:)
Product.create!(title: title, vendor: vendor, store: store)
end
end
end
and when i call
mutation {
productCreate(input: {store:"61d6f33a58c4dc4e8a1a0536", title: "Sweet new product", vendor: "JadedPixel"})
{
_id
}
}
Result is
{
"data": {
"productCreate": {
"_id": "61de591c58c4dcb08dffafa9"
}
}
}
I would like to add additional paramenter to query and also get additional paramenter in result
So, my question is
What should i change in code
mutation {
productCreate(input: {title: "Sweet new product", productType: "Snowboard", vendor: "JadedPixel"}) {
product {
id
}
}
}
to get result like this
{
"productCreate": {
"product": {
"id": "1071559610"
}
}
}
I found solutions
Just need to change code like this
module Mutations
class ProductCreate < BaseMutation
field :product, Types::ProductType, null: true
# TODO: define arguments
argument :title, String, required: true
argument :vendor, String, required: false
argument :store, ID, required: true
# TODO: define resolve method
def resolve(title:, vendor:, store:)
record = Product.create!(title: title, vendor: vendor, store: store)
{ product: record }
end
end
end
source of an example
https://www.keypup.io/blog/graphql-the-rails-way-part-2-writing-standard-and-custom-mutations

GraphQL - Relationship returning null

I started to learn GraphQL and I'm trying to create the following relationship:
type User {
id: ID!,
name: String!,
favoriteFoods: [Food]
}
type Food {
id: ID!
name: String!
recipe: String
}
So basically, a user can have many favorite foods, and a food can be the favorite of many users. I'm using graphql.js, here's my code:
const Person = new GraphQLObjectType({
name: 'Person',
description: 'Represents a Person type',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
favoriteFoods: {type: GraphQLList(Food)},
})
})
const Food = new GraphQLObjectType({
name: 'Food',
description: 'Favorite food(s) of a person',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
recipe: {type: GraphQLString}
})
})
And here's the food data:
let foodData = [
{id: 1, name: 'Lasagna', recipe: 'Do this then that then put it in the oven'},
{id: 2, name: 'Pancakes', recipe: 'If you stop to think about, it\'s just a thin, tasteless cake.'},
{id: 3, name: 'Cereal', recipe: 'The universal "I\'m not in the mood to cook." recipe.'},
{id: 4, name: 'Hashbrowns', recipe: 'Just a potato and an oil and you\'re all set.'}
]
Since I'm just trying things out yet, my resolver basically just returns a user that is created inside the resolver itself. My thought process was: put the food IDs in a GraphQLList, then get the data from foodData usind lodash function find(), and replace the values in person.favoriteFoods with the data found.
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'Root Query',
fields: {
person: {
type: Person,
resolve(parent) {
let person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3]
}
foodIds = person.favoriteFoods
for (var i = 0; i < foodIds.length; i++) {
person.favoriteFoods.push(_.find(foodData, {id: foodIds[i]}))
person.favoriteFoods.shift()
}
return person
}
}
}
})
But the last food is returning null. Here's the result of a query:
query {
person {
name
favoriteFoods {
name
recipe
}
}
}
# Returns
{
"data": {
"person": {
"name": "Daniel",
"favoriteFoods": [
{
"name": "Lasagna",
"recipe": "Do this then that then put it in the oven"
},
{
"name": "Pancakes",
"recipe": "If you stop to think about, it's just a thin, tasteless cake."
},
null
]
}
}
}
Is it even possible to return the data from the Food type by using only its ID? Or should I make another query just for that? In my head the relationship makes sense, I don't think I need to store the IDs of all the users that like a certain food in the foodData since it has an ID that I can use to fetch the data, so I can't see the problem with the code or its structure.
Calling shift and push on an array while iterating through that same array will invariably lead to some unexpected results. You could make a copy of the array, but it'd be much easier to just use map:
const person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3],
}
person.favoriteFoods = person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
return person
The other issue here is that if your schema returns a Person in another resolver, you'll have to duplicate this logic in that resolver too. What you really should do is just return the person with favoriteFoods: [1, 2, 3]. Then write a separate resolver for the favoriteFoods field:
resolve(person) {
return person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
}

Laravel 5.5 - Merge 2 collections into one based on id?

So I have 2 models Books and Classes:
$books = Books::limit(3)->get(['id','classable_id','easy_book']);
// Books returned:
{ id: 200,
classable_id: 2,
easy_book: false
},
{ id: 201,
classable_id: 3,
easy_book: true
},
{ id: 202,
classable_id: 4,
easy_book: false
}
$classIds = $books->pluck('classable_id');
$classes = Classes::whereIn('id', $classIds);
// Classes returned:
{ id: 2,
subject: Math,
students: 30
},
{ id: 3,
subject: History,
students: 30
},
{ id: 4,
subject: Physics,
students: 30
}
Then trying to get the following output (without combining the queries, but keeping them separate like above, and just using php logic to output):
Classes returned:
{ id: 2,
subject: Math,
students: 30.
easy_book: false }, // trying to merge this!
{ id: 3,
subject: History,
students: 30.
easy_book: true}, // trying to merge this!
{ id: 4,
subject: Physics,
students: 30.
easy_book: false } // trying to merge this!
Basically, I am trying to merge the easy_book field from books returned to the respective class returned based on class.id == books.classable_id. Any idea how to merge it?
Add a relationship to your Books model like so:
public function class() {
return $this->belongsTo(Classes::class, 'id', 'classable_id);
}
Then you can do:
Book::with('class')->select('id', 'classable_id', 'easy_book')->limit(3)->get();
Each collection item will then have a collection of classes where applicable.
If after that you want to manipulate them, you can use the map function as documented here: https://laravel.com/docs/5.7/collections#method-map

How can I compare two rethinkdb objects to create a new object that only contains their differences?

Say I have two objects stored in rethinkdb I wish to compare, let's call them old_val and new_val. As an example, let's say these values represent a TODO task that has changed owner and status:
{
old_val: {
status: 'active',
content: 'buy apples',
owner: 'jordan'
},
new_val: {
status: 'done',
content: 'buy apples',
owner: 'matt'
}
}
When I compare old_val and new_val, I'd like to yield a new object where new_val only contains the fields that differ from old_val. I want to do this in order to save bytes on the wire; and make rendering changes on my client easier. The result of the query should look something like this:
{
old_val: {
content: 'buy apples',
owner: 'jordan',
status: 'active'
},
new_val: {
owner: 'matt',
status: 'done'
}
}
How would I do this?
There are three separate parts to solving this problem:
Generate a list of fields to compare
Compare between common fields, include only fields which differ
Create a new object
(1) A list of fields can be generated by using the keys() method. We can filter these fields to (2) only include those which exist in both old_val and new_val and whose values differ. We can then pass this sequence to concatMap() to build an array of key/value pairs like [key0, value0, key1, value1]. Finally, a new object can be constructed (3) from this sequence by applying it as arguments (using r.args()) to the r.object() function.
It comes together like this:
r.expr({
old_val: {
status: 'active',
content: 'buy apples',
owner: 'jordan'
},
new_val: {
status: 'done',
content: 'buy apples',
owner: 'matt'
}
}).do((diff_raw) =>
r.expr({
old_val: diff_raw('old_val'),
// build an object only containing changes between old_val and new_val:
new_val: r.object(r.args(
diff_raw('new_val')
.keys()
// only include keys existing in old and new that have changed:
.filter((k) =>
r.and(
diff_raw('old_val').hasFields(k),
diff_raw('new_val')(k).ne(diff_raw('old_val')(k))
)
)
// build a sequence of [ k0, v0, k1, v1, ... ]:
.concatMap((k) => [k, diff_raw('new_val')(k)])
))
})
)
This will return:
{
"new_val": {
"owner": "matt" ,
"status": "done"
} ,
"old_val": {
"content": "buy apples" ,
"owner": "jordan" ,
"status": "active"
}
}

writing a rspec test to add an item to a hash

I am getting a NoMethodError
for my code but I have defined the add method it says it is missing.
I am trying to add an item to a hash that already exists.
The hash is the dishes and I am trying to use the add method.
The test:
require 'menu'
describe Menu do
it 'has a menu' do
expect(subject.respond_to?(:dishes)).to be true
end
it 'displays dishes and prices' do
expect(subject.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
it 'can add dishes to it' do
menu = Menu.new
menu.add_dish("Icecream", 4.80)
expect(subject.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 },
{ name: 'icecream', price: 4.80 }
]
end
end
the methods
class Menu
def initialize
#dishes = []
end
def dishes
#dishes = [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
def add_dish(name, price)
#dishes << { name: name, price: price }
end
end
Thank you
The answer of Ryan-Neal Mes solves the NoMethodError, but there are many other problems in your code.
You repeat your self, and you should make your code dry (Don't Repeat Yourself principle)
while you want to add a hash to the list of dishes which is it self a list of hashes, you force the object that needs to call the add method to provide the parameters in a particular order, than the method constructs the hash, so every time you need to call it you need to return to it to see the order of parameters.
the dishes method is wrong, because each time you call it, it assigns the initial array to the #dishes variable. In this case the add_dishes method will have no effect, since the added dish will be deleted the next time you call the dishes method.
your examples are not expressive, so if a test did not pass, you cannot know from the printed messages what's the problem. OK, this is not a big deal in this small example, but in a big application, specs expressiveness is of a higher value.
here the test examples
require 'menu'
describe Menu do
# every time you call the function dishes in an example
# it will be declared and it will return this array
let :dishes do
[
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
# explicit definition of the subject
subject { Menu.new }
# a shorter yet more expressive version of
# expect(subject.respond_to?(:dishes)).to be true
it { is_expected.to respond_to(:dishes) }
# You should always group the examples that test
# the same method
describe '#dishes' do
# it 'displays dishes and prices' do
it 'returns the list of dishes' do
expect(subject.dishes).to eq dishes
end
end
describe "#add_dish" do
# it 'can add dishes to it' do
it "adds the given dish to the list of dishes" do
new_dish = {name: 'salad', price: 4.0 }
expect {
subject.add_dish(new_dish)
}.to change(subject.dishes, :count).by(1)
expect(subject.dishes).to include new_dish
end
end
end
so here is the class definition
class Menu
# you don't need to declare the method dishes
# since this is what attr_reader will do
attr_reader :dishes
def initialize
# this will set the #dishes only once
# but you code #dishes = [...] will return
# the same list every time you call it and
# all the dishes you add through the #add method
# will be deleted.
#dishes = [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
# dish is a hash {name: '', price: ''}
def add_dish(dish)
#dishes << dish
end
end
so now run rspec --format doc --color and see who expressive are the messages.
It's a bit difficult to get this working without your code, but the problem is pretty straight forward.
Try the edited code below. Note the changes to the spec initialize the menu and add method adds to the instance variable #dishes.
require 'menu'
describe Menu do
it 'displays dishes and prices' do
expect(Menu.new.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
it 'can add dishes to it' do
menu = Menu.new.add("Icecream", 4.80)
expect(menu.dishes).to eq [
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 },
{ name: 'icecream', price: 4.80 }
]
end
end
class Menu
def initialize
#dishes = []
end
def dishes
#dishes ||=
[
{ name: 'Burger', price: 10.95 },
{ name: 'Pizza', price: 14.00 },
{ name: 'Salad', price: 7.60 },
{ name: 'fries', price: 2.90 }
]
end
def add(name, price)
#dishes << { name: name, price: price }
end
end
Hope this helps
It looks like you've got a couple of problems with this code. First, because the add method is not declared as a class method (i.e. def self.add) you can't call it as a class method (as you've seen, Menu.add says NoMethodError). Instead, you'll need to create an instance of the Menu class in your test, perhaps using let:
describe Menu do
let(:menu) { Menu.new }
it 'can add dishes to it' do
menu.add("Icecream", 4.80)
# test your expectations...
end
end
Lastly, as the add method is currently defined, it doesn't modify #dishes but rather just returns a new hash, so your expectation will fail. You'll need to make the add method append the values, perhaps like this:
def add(name, , price)
#dishes << {name: name, price: price}
end
Ah I see your problem. You need to initialize your menu. Add is not a static method. So you need something like,
Menu.new.add(blah, blah)
Look at:
Menu.add("Icecream", 4.80)
This method is wrong. It needs to be:
Menu.new.add("Icecream", 4.80)
or you need something like:
menu = Menu.new
menu.add("Icecream", 4.80)

Resources