I've been really struggling getting a simple scenario running with EmberJS and Rails.
Here's what I have (combined JS):
App = Ember.Application.create
LOG_TRANSITIONS: true
App.Post = DS.Model.extend
title: DS.attr 'string'
description: DS.attr 'string'
App.StreamRoute = Ember.Route.extend
setupController: (controller, model) ->
controller.set 'posts', model
model: -> #store.find('post')
App.Router.map ->
#.route 'stream', path: '/'
Here's the template content:
{{#each posts}}
{{title}}
{{/each}}
Here's the /posts JSON for Post.all (perhaps this is wrong?):
{
"posts": [
{
"posts": {
"created_at": "2013-08-15T23:48:54+01:00",
"description": "A few months ago I helped develop these posters for research that our UX team had gathered to create personas for our customers to show who they are and who actually uses our product.",
"id": 7,
"likes_count": 1,
"slug": "16ErQ",
"thumb": {
"url": "\/posts\/1\/16ErQ\/man.png",
"medium": {
"url": "\/posts\/1\/16ErQ\/medium_man.png"
}
},
"title": "Persona Project",
"updated_at": "2013-08-15T23:48:54+01:00",
"user_id": 1,
"views_count": 0
}
},
{
"posts": {
"created_at": "2013-08-16T15:47:03+01:00",
"description": "Just a little something.",
"id": 8,
"likes_count": 0,
"slug": "VYIvn",
"thumb": {
"url": "\/posts\/2\/VYIvn\/face.jpg",
"medium": {
"url": "\/posts\/2\/VYIvn\/medium_face.jpg"
}
},
"title": "Face",
"updated_at": "2013-08-16T15:47:03+01:00",
"user_id": 2,
"views_count": 0
}
},
{
"posts": {
"created_at": "2013-08-16T17:03:10+01:00",
"description": "Some people say, he's still running.",
"id": 9,
"likes_count": 2,
"slug": "hQBnt",
"thumb": {
"url": "\/posts\/1\/hQBnt\/run.jpg",
"medium": {
"url": "\/posts\/1\/hQBnt\/medium_run.jpg"
}
},
"title": "Run, Forest, run.",
"updated_at": "2013-08-23T23:44:19+01:00",
"user_id": 1,
"views_count": 0
}
}
]
}
I thought this would be fine, but it doesn't quite work, when I run it, I get 3 post results (which is how many there are) but the columns all contain null values: http://c.daryl.im/RTzO
As you can see, I also have that error. Any ideas?
Your JSON looks wrong. It should be something like this:
{
"posts":[
{
"created_at":"2013-08-15T23:48:54+01:00",
"description":"A few months ago I helped develop these posters for research that our UX team had gathered to create personas for our customers to show who they are and who actually uses our product.",
"id":7,
"likes_count":1,
"slug":"16ErQ",
"thumb":{
"url":"/posts/1/16ErQ/man.png",
"medium":{
"url":"/posts/1/16ErQ/medium_man.png"
}
},
"title":"Persona Project",
"updated_at":"2013-08-15T23:48:54+01:00",
"user_id":1,
"views_count":0
},
...
]}
Basically you have nested posts and need to remove one layer.
Related
I am just trying JsonPath. It's great but I have a problem with a special case. I searched here but could not find a solution.
So, my JSON file is below:
[
{
"id": 1,
"images": [
{ "id": 1,"url": "http://url1.jpg" },
{ "id": 2,"url": "http://url2.jpg" }
]
},
{
"id": 2,
"images": [
{ "id": 1,"url": "http://url3.jpg" },
{ "id": 2,"url": "http://url4.jpg" }
]
},
{
"id": 3,
"images": [
{ "id": 1,"url": "http://url5.jpg" },
{ "id": 2,"url": "http://url6.jpg" }
]
}
]
With this expression $.[?(#.id=='2')], the result is
[
{
"id": 2,
"images": [
{
"id": 1,
"url": "http://url3.jpg"
},
{
"id": 2,
"url": "http://url4.jpg"
}
]
},
{
"id": 2,
"url": "http://url2.jpg"
},
{
"id": 2,
"url": "http://url4.jpg"
},
{
"id": 2,
"url": "http://url6.jpg"
}
]
But I need to restrict my query result to one level without the children. The last 3 elements are not necessary. I need only the first element.
{
"id": 2,
"images": [
{
"id": 1,
"url": "http://url3.jpg"
},
{
"id": 2,
"url": "http://url4.jpg"
}
]
}
As hinted in my comment, there are subtle differences in the implementation of the various JSONPath libraries. The same syntax may or may not produce the same result depending on the library at hand.
Nonetheless, we can adjust your query to make it work as expected by dropping the dot after the root symbol:
$[?(#.id===2)]
Note: I have tested the path above using Goessner's JavaScript implementation; the C# equivalent should look like this: $[?(#.id==2)].
The default result of rendering FastJsonApi gem serialized_json like below:
render json: FlashcardSerializer.new(flashcards).serialized_json
would be something like this:
{
"data": [
{
"id": "1",
"type": "flashcard",
"attributes": {
"question": "why?",
"answer": "pretty good",
"slug": null
}
},
{
"id": "2",
"type": "flashcard",
"attributes": {
"question": "What is 0",
"answer": "it is 0",
"slug": null
}
}
]
}
I would rather add some extra information especially for pagination and I like the result to be something like this:
{
"data": [
{
"id": "1",
"type": "flashcard",
"attributes": {
"question": "why?",
"answer": "pretty good",
"slug": null
}
},
{
"id": "2",
"type": "flashcard",
"attributes": {
"question": "What is 0",
"answer": "it is 0",
"slug": null
}
},
"count":100,
"page":1,
]
}
I am aware of other available gems that manage pagination in API, and I know how to do it without Fastjson. The main issue here is that is there any way to get the aforementioned result from this gem without changing a lot in the code. Thanks
The desired document would be invalid according to the JSON API specification. You would need to include next and previous links in a link section. The current and total_count would belong in the meta section.
{
"data": [
{
"id": "1",
"type": "flashcard",
"attributes": {
"question": "why?",
"answer": "pretty good",
"slug": null
}
},
{
"id": "2",
"type": "flashcard",
"attributes": {
"question": "What is 0",
"answer": "it is 0",
"slug": null
}
},
]
"meta": {
"page": { "current": 1, "total": 100 }
},
"links": {
"prev": "/example-data?page[before]=yyy&page[size]=1",
"next": "/example-data?page[after]=yyy&page[size]=1"
},
}
Have a look at the JSON API specification before you continue designing the API.
You can pass these information into the serializer as an options argument
class FlashcardsController < ApplicationController
def index
render json: FlashcardSerializer.new(
flashcards, { links: {}, meta: { page: { current: 1 } }
).serialized_json
end
end
How you generate the data depends what you use to paginate.
If you design a new API, I would also recommend to use cursor based pagination rather than offset pagination because of it's limitations.
https://github.com/Netflix/fast_jsonapi#compound-document
https://github.com/Netflix/fast_jsonapi/blob/master/spec/lib/object_serializer_spec.rb#L8-L32
As a marketer, I'm going through the EmailOctopus (email service provider) API docs (https://emailoctopus.com/api-documentation) and have trouble combining multiple requests in one.
Goal: Get all campaign reports for all campaigns exported to a CSV.
Step 1: Get all campaign IDs. This works.
curl GET https://emailoctopus.com/api/1.5/campaigns?api_key={APIKEY}
Step 2: Get the report for a single campaign. This works too.
curl GET https://emailoctopus.com/api/1.5/campaigns/{CAMPAIGNID}/reports/summary?api_key={APIKEY}
Step 3: Combine step 1 and 2 and export to a CSV. No idea how to proceed here.
Output step 1:
{
"data": [
{
"id": "00000000-0000-0000-0000-000000000000",
"status": "SENT",
"name": "Foo",
"subject": "Bar",
"to": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002"
],
"from": {
"name": "John Doe",
"email_address": "john.doe#gmail.com"
},
"content": {
"html": "<html>Foo Bar<html>",
"plain_text": "Foo Bar"
},
"created_at": "2019-10-30T13:46:46+00:00",
"sent_at": "2019-10-31T13:46:46+00:00"
},
{
"id": "00000000-0000-0000-0000-000000000003",
"status": "SENT",
"name": "Bar",
"subject": "Foo",
"to": [
"00000000-0000-0000-0000-000000000004",
"00000000-0000-0000-0000-000000000005"
],
"from": {
"name": "Jane Doe",
"email_address": "jane.doe#gmail.com"
},
"content": {
"html": "<html>Bar Foo<html>",
"plain_text": "Bar Foo"
},
"created_at": "2019-11-01T13:46:46+00:00",
"sent_at": "2019-11-02T13:46:46+00:00"
}
],
"paging": {
"next": null,
"previous": null
}
}
Output step 2:
{
"id": "00000000-0000-0000-0000-000000000000",
"sent": 200,
"bounced": {
"soft": 10,
"hard": 5
},
"opened": {
"total": 110,
"unique": 85
},
"clicked": {
"total": 70,
"unique": 65
},
"complained": 50,
"unsubscribed": 25
}
How can I get all campaign reports in one go and exported to a CSV?
May be this URLs be helpful
Merging two json in PHP
How to export to csv file a PHP Array with a button?
https://www.kodingmadesimple.com/2016/12/convert-json-to-csv-php.html
Continuing my previous question at: Active Model Serializer and Pundit deleting records during a Show CRUD action
I have a situation where a User should not be able to view another user's unpublished chapters belonging to a Story an author created.
E.g. If UserA creates a story called Targon and provides 2 published chapters and 2 unpublished chapters, then UserB should only see published chapters for Targon story.
Normally with Pundit policy scoping, it scopes the index CRUD action.
What I need to scope however, are Chapters belonging to a Story during the render json line:
render json: story, include: [:user, :chapters], status: :ok
I have tried:
# ---------------------------------------------------------------------------
# ActiveRecord auto-save will kick in and delete all unpublished chapters
# ---------------------------------------------------------------------------
story.chapters = policy_scope(story.chapters)
render json: story, include: [:user, :chapters], status: :ok
According to https://gist.github.com/demisx/9896113 (has_many section) the above code will delete all the unpublished chapters belonging to Targon when I reassign story.chapters:
story.chapters = policy_scope(story.chapters) # BAD
I am hoping there is some way I can do something like this:
render json: story, include: [:user, policy_scope(:chapters)], status: :ok
At the moment, without scoping the story.chapters any users who fetch for Story with ID 16 (Targon) will get back JSONAPI:
{
"data": {
"id": "16",
"type": "stories",
"attributes": {
"title": "Mount Targon",
"summary": "Mount Targon is the mightiest peak in Runeterra, a towering peak of sun-baked rock amid a range of summits unmatched in scale anywhere else in the world. Located far from civilization, Mount Targon is utterly remote and all but impossible to reach save by the most determined seeker. Many legends cling to Mount Targon, and, like any place of myth, it is a beacon to dreamers, madmen and questors of adventure. Some of these brave souls attempt to scale the impossible mountain, perhaps seeking wisdom or enlightenment, perhaps chasing glory or some soul-deep yearning to witness its summit. The ascent is all but impossible, and those hardy few who somehow survive to reach the top almost never speak of what they have seen. Some return with a haunted, empty look in their eyes, others changed beyond all recognition, imbued by an Aspect of unearthly, inhuman power with a destiny few mortals can comprehend.",
"published": true,
"published-date": "2017-11-02T10:35:33.184Z",
"created-at": "2017-11-02T10:35:33.184Z",
"updated-at": "2017-11-04T07:35:04.083Z",
"cover": {
"url": "http://res.cloudinary.com/chewedon/image/upload/v1509780931/c8ubn3tfivxziyxwynsa.png",
"standard": {
"url": "http://res.cloudinary.com/chewedon/image/upload/c_fill,g_north,h_300,w_200/c8ubn3tfivxziyxwynsa.png"
}
}
},
"relationships": {
"user": {
"data": {
"id": "1",
"type": "users"
}
},
"chapters": {
"data": [{
"id": "26",
"type": "chapters"
}, {
"id": "27",
"type": "chapters"
}, {
"id": "37",
"type": "chapters"
}, {
"id": "38",
"type": "chapters"
}]
}
}
},
"included": [{
"id": "1",
"type": "users",
"attributes": {
"username": "Chewedon",
"photo": {
"url": "http://res.cloudinary.com/chewedon/image/upload/v1509857442/nx1tqlcdxrhz6r3kjx87.jpg",
"standard": {
"url": "http://res.cloudinary.com/chewedon/image/upload/c_fill,g_north,h_150,w_150/nx1tqlcdxrhz6r3kjx87.jpg"
}
}
},
"relationships": {
"stories": {
"data": [{
"id": "1",
"type": "stories"
}, {
"id": "2",
"type": "stories"
}, {
"id": "3",
"type": "stories"
}, {
"id": "4",
"type": "stories"
}, {
"id": "5",
"type": "stories"
}, {
"id": "6",
"type": "stories"
}, {
"id": "8",
"type": "stories"
}, {
"id": "9",
"type": "stories"
}, {
"id": "10",
"type": "stories"
}, {
"id": "11",
"type": "stories"
}, {
"id": "12",
"type": "stories"
}, {
"id": "13",
"type": "stories"
}, {
"id": "14",
"type": "stories"
}, {
"id": "15",
"type": "stories"
}, {
"id": "16",
"type": "stories"
}]
}
}
}]
}
Here in the relationship section, chapters 37 and 38 are unpublished, leading to a 403 Forbidden on my Ember frontend.
Ideally the server should have scoped out these before returning the records but due to the bug I described above and in my previous Stackoverflow question, I am stuck on how to scope included fields with Pundit.
Any ideas?
Thanks to user oowowaee from previous linked question, who suggested overriding the Story serializer's chapters field (which I didn't know you could do that), the code is working now and the records don't get deleted from database.
class StorySerializer < ActiveModel::Serializer
include Pundit
attributes :id, :title, :summary, :published, :published_date, :created_at, :updated_at, :cover
belongs_to :user
has_many :chapters
# ------------------------------------------------------------------------
# Note: need to use 'object.chapters' not 'self.chapters` below.
# ------------------------------------------------------------------------
def chapters
policy_scope(object.chapters)
end
end
I'm new to the SurveyMonkey API and it hasn't been too difficult to get payloads back from API calls, but right now I'm trying to get back what responses a specific respondent gave.
I have a survey which has two respondents, the first question on the survey asks the user to enter three pieces of information: Their Name, an ID and today's date.
So, if I do a call to get_survey_details, I can see the questions just fine. For example
obj.pages[0].questions[0].answers[0].answerid: "xxxxxxxx" //some long ID
obj.pages[0].questions[0].answers[0].text: "Enter Your Name"
obj.pages[0].questions[0].answers[0].type: "row"
There's a couple more pieces of information in that object, like whether the question is visible, etc., but these seem to be the pertinent pieces to the question I have.
So! I make another call to get_responses using the same survey_id and respondent_id (there's only two so actually I get them both).
In the resulting payload I get an array of 2 objects (one to hold each respondents responses). So I look in the first (obj[0]) and I see an array of questions and the respondent id. Fine. I look in the questions array and I see one object for each question and in each of those an answers object.
so that's:
obj[0].questions[0].answers[0].col: "yyyyyy" //some long ID
obj[0].questions[0].answers[0].row: "nnnnnn" //some other long ID
No response text. just this row/col business.
At this point, I'm super-confused (which is like regular confused, but with a cape). Where the heck are the respondents actual responses?
What the heck does "row" and "column" reference? Do I have to do some other API call with the row and/or column in order to get the text of the respondent's response?
I've looked through the documentation (and will continue to do so after posting this) and through stackoverflow to see if anyone else has asked this before. There was one question that came close, but really they were just forgetting to pair 'get_responses' with 'get_survey_details'. I'm doing that, but am still lost as ever. And I don't see any documentation really explaining in detail how this row/column concept works for mapping responses to the text of the response. :/
I know this is a really long-winded question, but I'm just so confused as to how to actually get responses out of this API. :(
Thanks for reading.
The text for a given response should come through under the "text" key. e.g. for a survey that only consists of an essay style question:
{
"status": 0,
"data": [
{
"respondent_id": "123456",
"questions": [
{
"answers": [
{
"text": "This is an essay style answer.",
"row": "0"
}
],
"question_id": "78910"
}
]
}
]
}
"row" and "col" literally reference the row and column of an answer - e.g. in a matrix question, there will be a list of rows for different questions ("what did you think of the hotel?") and ratings ("bad, okay, great") - and each answer is a combination of these. For a regular multiple choice question there will be multiple rows and only one column.
Calling "get_responses" with the correct respondent_id should provide you with the text response that you want. It's only the fixed details of the answer stored in the survey itself you should have to look up (provided in get_survey_details).
Using GET : /surveys/{survey_id}/details, we can get the corresponding question Ids along with the answer Ids.
{
"pages": [
{
"href": "https://api.surveymonkey.net/v3/surveys/87263608/pages/260492760",
"description": "",
"questions": [
{
"sorting": null,
"family": "matrix",
"subtype": "rating",
"required": {
"text": "This question requires an answer.",
"amount": "0",
"type": "all"
},
"answers": {
"rows": [
{
"visible": true,
"text": "",
"position": 1,
"id": "10788526669"
}
],
"choices": [
{
"description": "Not at all likely",
"weight": -100,
"id": "10788526670",
"visible": true,
"is_na": false,
"text": "Not at all likely - 0",
"position": 1
},
{
"description": "",
"weight": -100,
"id": "10788526671",
"visible": true,
"is_na": false,
"text": "1",
"position": 2
},
{
"description": "",
"weight": -100,
"id": "10788526672",
"visible": true,
"is_na": false,
"text": "2",
"position": 3
},
{
"description": "",
"weight": -100,
"id": "10788526673",
"visible": true,
"is_na": false,
"text": "3",
"position": 4
},
{
"description": "",
"weight": -100,
"id": "10788526674",
"visible": true,
"is_na": false,
"text": "4",
"position": 5
},
{
"description": "",
"weight": -100,
"id": "10788526675",
"visible": true,
"is_na": false,
"text": "5",
"position": 6
},
{
"description": "",
"weight": -100,
"id": "10788526676",
"visible": true,
"is_na": false,
"text": "6",
"position": 7
},
{
"description": "",
"weight": 0,
"id": "10788526677",
"visible": true,
"is_na": false,
"text": "7",
"position": 8
},
{
"description": "",
"weight": 0,
"id": "10788526678",
"visible": true,
"is_na": false,
"text": "8",
"position": 9
},
{
"description": "",
"weight": 100,
"id": "10788526679",
"visible": true,
"is_na": false,
"text": "9",
"position": 10
},
{
"description": "Extremely likely",
"weight": 100,
"id": "10788526680",
"visible": true,
"is_na": false,
"text": "Extremely likely - 10",
"position": 11
}
]
},
"visible": true,
"href": "https://api.surveymonkey.net/v3/surveys/87263608/pages/260492760/questions/1044924866",
"headings": [
{
"heading": "How likely is it that you would recommend XYZ to a friend or colleague?"
}
],
"position": 1,
"validation": null,
"id": "1044924866",
"forced_ranking": false
},
{
"sorting": null,
"family": "single_choice",
"subtype": "vertical",
"required": null,
"answers": {
"choices": [
{
"visible": true,
"text": "High Interest",
"position": 1,
"id": "10788529403"
},
{
"visible": true,
"text": "Long process",
"position": 2,
"id": "10788529404"
},
{
"visible": true,
"text": "Low XYZ Amount",
"position": 3,
"id": "10788529405"
},
{
"visible": true,
"text": "Lot of Documents",
"position": 4,
"id": "10788529406"
},
{
"visible": true,
"text": "Bad customer service",
"position": 5,
"id": "10788529407"
}
]
},
"visible": true,
"href": "https://api.surveymonkey.net/v3/surveys/87263608/pages/260492760/questions/1044925207",
"headings": [
{
"heading": "What is the most important issue which we need to address for overall a better service?"
}
],
"position": 2,
"validation": null,
"id": "1044925207",
"forced_ranking": false
}
],
"title": "",
"position": 1,
"id": "260492760",
"question_count": 2
}
],
}
We can use these ids to decipher the answer we get after fetching responses using get response API(Bulk or each respondent).
For eg:,
If my survey has two questions, like
Then after fetching the responses we get a json like this:
{
"total_time": 34,
"href": "https://api.surveymonkey.net/v3/collectors/94630092/responses/5120000552",
"custom_variables": {},
"ip_address": "182.76.20.30",
"id": "5120000552",
"logic_path": {},
"date_modified": "2016-12-01T11:01:11+00:00",
"response_status": "completed",
"custom_value": "LAI100023",
"analyze_url": "http://www.surveymonkey.com/analyze/browse/EvaBWWcU9K1XTH_2FFFBTfFul4ge94MwVWvBk0eAFDJ3c_3D?respondent_id=5120000552",
"pages": [
{
"id": "260492760",
"questions": [
{
"id": "1044924866",
"answers": [
{
"choice_id": "10788526677",
"row_id": "10788526669"
}
]
},
{
"id": "1044925207",
"answers": [
{
"choice_id": "10788529404"
}
]
}
]
}
],
"page_path": [],
"recipient_id": "2743199128",
"collector_id": "94630092",
"date_created": "2016-12-01T11:00:37+00:00",
"survey_id": "87263608",
"collection_mode": "default",
"edit_url": "http://www.surveymonkey.com/r/?sm=SfTljxZSoBFvaRUeGSI6L813qctjfG_2FDCVcqCks7CDc4TcJC_2BNHqmPYD7NNTcvST",
"metadata": {
"contact": {
"first_name": {
"type": "string",
"value": "John"
},
"last_name": {
"type": "string",
"value": "Doe"
},
"email": {
"type": "string",
"value": "neeta#xyz.com"
}
}
}
}
We can map the questions and answers using their IDs in this response with the ids we got from survey details. For open ended text questions, we get direct typed responses.