How do I create a GraphQL appSubscriptionCreate mutation with the Ruby shopify_api gem? - ruby-on-rails

When I copy the example below from https://help.shopify.com/en/api/guides/billing-api/implement-your-business-model#implement-the-appsu...:
appSubscriptionCreate(
name: "Super Duper Recurring Plan"
returnUrl: "http://super-duper.shopifyapps.com"
lineItems: [{
plan: {
appRecurringPricingDetails: {
price: { amount: 10.00, currencyCode: USD }
}
}
}]
) {
userErrors {
field
message
}
confirmationUrl
appSubscription {
id
}
}
}
and run it via the "Shopify GraphiQL App", the mutation is successfully created.
I am not sure how to do it with Ruby and the shopify_api gem though (note that I am new to Ruby as well as GraphQL, so it's probably something very basic I am missing, but I have not been able to find the answer anywhere).
I attempted the following:
##client = ShopifyAPI::GraphQL.new
PAYMENT_MUTATION = ##client.parse <<-'GRAPHQL'
{
mutation {
appSubscriptionCreate(
name: "Super Duper Recurring Plan"
returnUrl: "http://super-duper.shopifyapps.com"
lineItems: [{
plan: {
appRecurringPricingDetails: {
price: {
amount: 10.00,
currencyCode: USD
}
}
}
}]
) {
userErrors {
field
message
}
confirmationUrl
appSubscription {
id
}
}
}
}
GRAPHQL
def initialize
#result = ##client.query(PAYMENT_MUTATION)
end
def confirmationUrl
#result.data.appSubscriptionCreate.confirmationUrl
end
end
I get the following error though:
GraphQL::Client::ValidationError (Field 'mutation' doesn't exist on type 'QueryRoot'):
I tried skipping the mutation part, but then I just get the error:
GraphQL::Client::ValidationError (Field 'appSubscriptionCreate' doesn't exist on type 'QueryRoot'):
This led me to have a look at the GraphQL class of the shopify_api gem, hoping to find a "mutation" method to use instead of the "query" method, but there is none.
I cannot figure it out from the graphql-client gem that shopify_api is using either - there's no mutation examples in the readme.
What am I missing?
Thanks,
-Louise

Got the answer on the Shopify forum - I just have to remove the outer {} in PAYMENT_MUTATION.
PAYMENT_MUTATION = ##client.parse <<-'GRAPHQL'
mutation {
appSubscriptionCreate(
name: "Super Duper Recurring Plan"
returnUrl: "http://super-duper.shopifyapps.com"
lineItems: [{
plan: {
appRecurringPricingDetails: {
price: {
amount: 10.00,
currencyCode: USD
}
}
}
}]
) {
userErrors {
field
message
}
confirmationUrl
appSubscription {
id
}
}
}
GRAPHQL

Related

Combining results of two tables in mongoid/mongo

Hi guys what would be the best way to combine results of two mongoid queries.
My issue is that I would like to know active users, A user can send a letter and a notification, both are separate table and a user if he sends either the letter or the notification is considered active. What I want to know is how many active users were there per month.
right now what I can think of is doing this
Letter.collection.aggregate([
{ '$match': {}.merge(opts) },
{ '$sort': { 'created_at': 1 } },
{
'$group': {
_id: '$customer_id',
first_notif_sent: {
'$first': {
'day': { '$dayOfMonth': '$created_at' },
'month': { '$month': '$created_at' },
'year': { '$year': '$created_at' }
}
}
}
}])
Notification.collection.aggregate([
{ '$match': {}.merge(opts) },
{ '$sort': { 'created_at': 1 } },
{
'$group': {
_id: '$customer_id',
first_notif_sent: {
'$first': {
'day': { '$dayOfMonth': '$created_at' },
'month': { '$month': '$created_at' },
'year': { '$year': '$created_at' }
}
}
}
}])
What I am looking for is to get the minimum of the dates and then combine the results and get the count. Right now I can get the results and loop over each of them and create a new list. But I wanted to know if there is a way to do it in mongo directly.
EDIT
For letters
def self.get_active(tenant_id)
map = %{
function() {
emit(this.customer_id, new Date(this.created_at))
}
}
reduce = %{
function(key, values) {
return new Date(Math.min.apply(null, values))
}
}
where(tenant_id: tenant_id).map_reduce(map, reduce).out(reduce: "#{tenant_id}_letter_notification")
end
Notifications
def self.get_active(tenant_id)
map = %{
function() {
emit(this.customer_id, new Date(this.updated_at))
}
}
reduce = %{
function(key, values) {
return new Date(Math.min.apply(null, values))
}
}
where(tenant_id: tenant_id, transferred: true).map_reduce(map, reduce).out(reduce: "#{tenant_id}_outgoing_letter_standing_order_balance")
end
This is what I am thinking of going with, one of the reason is that, lookup does not work with my version of mongo.
the customer created a new notification, or a new letter, and I would like to get the first created at of either.
Let's address this first as a foundation. Given examples of document schema as below:
Document schema in Letter collection:
{ _id: <ObjectId>,
customer_id: <integer>,
created_at: <date> }
And, document schema in Notification collection:
{ _id: <ObjectId>,
customer_id: <integer>,
created_at: <date> }
You can utilise aggregation pipeline $lookup to join the two collections. For example using mongo shell :
db.letter.aggregate([
{"$group":{"_id":"$customer_id", tmp1:{"$max":"$created_at"}}},
{"$lookup":{from:"notification",
localField:"_id",
foreignField:"customer_id",
as:"notifications"}},
{"$project":{customer_id:"$_id",
_id:0,
latest_letter:"$tmp1",
latest_notification: {"$max":"$notifications.created_at"}}},
{"$addFields":{"latest":
{"$cond":[{"$gt":["$latest_letter", "$latest_notification"]},
"$latest_letter",
"$latest_notification"]}}},
{"$sort":{latest:-1}}
], {cursor:{batchSize:100}})
The output of the above aggregation pipeline is a list of customers in sorted order of created_at field from either Letter or Notification. Example output documents:
{
"customer_id": 0,
"latest_letter": ISODate("2017-12-19T07:00:08.818Z"),
"latest_notification": ISODate("2018-01-26T13:43:56.353Z"),
"latest": ISODate("2018-01-26T13:43:56.353Z")
},
{
"customer_id": 4,
"latest_letter": ISODate("2018-01-04T18:55:26.264Z"),
"latest_notification": ISODate("2018-01-25T02:05:19.035Z"),
"latest": ISODate("2018-01-25T02:05:19.035Z")
}, ...
What I want to know is how many active users were there per month
To achieve this, you can just replace the last stage ($sort) of the above aggregation pipeline with $group. For example:
db.letter.aggregate([
{"$group":{"_id":"$customer_id", tmp1:{$max:"$created_at"}}},
{"$lookup":{from:"notification",
localField:"_id",
foreignField:"customer_id",
as:"notifications"}},
{"$project":{customer_id:"$_id",
_id:0,
latest_letter:"$tmp1",
latest_notification: {"$max":"$notifications.created_at"}}},
{"$addFields":{"latest":
{"$cond":[{"$gt":["$latest_letter", "$latest_notification"]},
"$latest_letter",
"$latest_notification"]}}},
{"$group":{_id:{month:{"$month": "$latest"},
year:{"$year": "$latest"}},
active_users: {"$sum": "$customer_id"}
}
}
],{cursor:{batchSize:10}})
Where the example output would be as below:
{
"_id": {
"month": 10,
"year": 2017
},
"active_users": 9
},
{
"_id": {
"month": 1,
"year": 2018
},
"active_users": 18
},

How do you define parentName, parentId, and connectionName in getConfigs for a nested connection?

I am running into some issues writing a Relay Mutation on a nested connection. Here is the type structure:
{
viewer {
entity(id) {
events // mutate connection here
}
}
}
In the getConfigs I have both the parentName and parentID pointing to the "viewer", however the connectionName "events" does not exist on the viewer it exists on the "entity" type.
As you will see in the code snippet below I am also unsure how to use variables in the getFatQuery to fetch the mutated data with an entity ID.
getConfigs and getFatQuery:
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'events',
edgeName: 'eventEdge',
rangeBehaviors: {
'': 'append'
},
}];
}
getFatQuery() {
return Relay.QL`
fragment on addEventPayload {
viewer {
entity(id: $entityId) // how do I use variables in the getFatQuery {
events(first: 20) {
edges {
node {
status
}
}
}
}
},
eventEdge
}
`;
}
I am more than happy to help clarify my question if it makes no sense so please feel free to ask questions about my question.
Thanks for the help!
For anyone in the future getting snagged on this you don't have to worry about passing in the id again or dealing with nested queries/fields. Relay will just find and update the appropriate record on the client. This issue on Github was helpful for figuring this out, especially the comments from freiksenet.
In the example above - Instead of going through the viewer we just go straight to the entity.
outputFields:
outputFields: {
eventEdge: {
type: eventEdge,
resolve: async({event}) => {
const eventsByOwner = await Event.getEventsByOwnerId(event.ownerId)
const eventIndex = eventsByOwner.findIndex(evt => evt.id == event.id);
const cursor = offsetToCursor(eventIndex);
return {
cursor: cursor,
node: event
};
}
},
entity: {
type: entity,
resolve: async({event}) => {
return Entity.getEntity(event.ownerId)
}
},
}
getConfigs and getFatQuery:
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'entity',
parentID: this.props.entityId,
connectionName: 'events',
edgeName: 'eventEdge',
rangeBehaviors: {
'': 'append'
},
}];
}
getFatQuery() {
return Relay.QL`
fragment on addEventPayload #relay(pattern: true) {
entity {
events
},
eventEdge
}
`;
}
Note: Using #relay(pattern: true) will make sure you don't run into issues when you don't pass in arguments for connection queries and will fall back to your last query of this type.

Elasticsearch sort option not supported

I'm using elastic search in Rails. I am trying to sort a list of customers by their total dollars spent descending. This is my ruby code:
query = {
bool: {
filter: {
term: { store_id: store.id } # Limits customers by current store
}
}
}
sort = {
sort: { "total_spent": { order: "desc" }}
}
response = Contact.search(query: query, sort: sort)
This returns with an error of sort option [total_spent] not supported I've tried with other fields to make sure it wasn't just something wrong with the total_spent field. Thanks.
I'm not really sure, but I think this may be related to incorrect usage of the ES::DSL.
What happens when you try this:
query = {
bool: {
filter: {
term: { store_id: store.id } # Limits customers by current store
}
}
}
sort = {
sort: [{ "total_spent": { order: "desc" }}] #https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html
}
response = Contact.search(query, sort)
We can sort specific to the field, refer https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html.
so we can use like,
query = {
bool: {
filter: {
term: { store_id: store.id } # Limits customers by current store
}
},
sort: { total_spent: { order: :desc }}
}
response = Contact.search(query)

how to implement mutation responses on a local falcor Model dataset

Given that I have an example Model:
var model = new falcor.Model({
cache: {
userById: {
"1": {
name: "User",
email: "user#email.com"
}
},
users: {
current: null
}
}
});
This is a local model that I'm using for testing purposes, and I would like to implement it on a call to users.login so the user so that I can call:
model.call(['users', 'login'], ['user', 'password'])
I realized that if I do this:
var model = new falcor.Model({
cache: {
userById: {
"1": {
name: "User",
email: "user#email.com"
}
},
users: {
current: null,
login: function(user, password) {
console.log('this code is reached', user, password);
// what to return in order to mutate model?
}
},
}
});
When I do the call it gets there, but I can't figure out how to mutate the model as part of the response; on the server side we return the paths with values and invalidates, and it just works, but here I tried:
// trying returning as a jsonGraph response, don't work
login: function() {
return {
jsonGraph: {
users: {
current: {$type: "ref", value: ['userById', '1']}
}
},
paths: [['users', 'current']]
}
}
// trying returning as a path set mutation list, don't work
login: function() {
return [{path: ['users', 'current'], value: {$type: "ref", value: ['userById', '1']}}]
}
// trying force call to set on the model, don't work
login: function() {
this.set([
{path: ['users', 'current'], value: {$type: "ref", value: ['userById', '1']}}
])
}
// trying using ModelResponse, got an example on some external sources, don't work
login: funtion() {
return new ModelResponse((observer) => {
observer.onNext({
jsonGraph: {
users: {
current: {$type: "ref", value: ['userById', '1']}
}
},
paths: [['users', 'current']]
});
observer.onCompleted();
});
}
Now I don't know what else to try; I need a simple way to declare mutations after a call into a local model, if you know how to solve this, please let me know here.
Thanks.
The client model cache only supports JSONGraph, which b/c it is essentially just JSON with some conventions, doesn't support functions. So, when working with a falcor model cache and no dataSource/middle tier router, it is not possible to implement calls.
This can be kind of annoying when prototyping/testing, as a router is conceptually more difficult than a simple JSON cache object. I ran into this a while ago, so I wrote a dataSource module to support it: falcor-local-datasource. The dataSource is initialized with a graph object that does support function nodes, and as with your above examples, will mutate the graph based on the function's returned JSONGraphEnvelope or an array of PathValues.

Wrap parameters in AngularJS service for update API request

I'm trying to do update with AngularJS and API
Service: expense.js
angular
.module('timeTrackerApp.services',[])
.service('Expense', ['$resource', function ($resource) {
return $resource('/api/v1/expenses/:id', {id:'#id'}, {
'update': {
method: 'PUT'
},
'destroy': {
method: 'DELETE'
}
})
}])
Controller: expenses_controller.rb
def permitted_params
params.require(:expense).permit(:name, :price)
end
So expected JSON format is { expense: { name: "value", price: value } }
but i'm getting { name: "value", price: value }
So can anyone help me wrap this into root node ( expense ) ?
Rails automatically does wrap parameters when controller name matches a model name. Check doc.
If ever it fails, you can do it manually, in your controller:
wrap_parameters :expense, include: [:name, :price]
so if you receive:
{ name: 'name', price: 'price' }
Controller will give you:
{ name: 'name', price: 'price', expense: { name: 'name', price: 'price' } }
So do this server side since its neat and simple.

Resources