I am confused Relay.QL fragments, Relays' queries and routes? - relayjs

I am following a relay tutorial and I am confused with this part of the code:
exports.Container = Relay.createContainer(ConferenceApp, {
/* 1st id on mock database */
initialVariables: {
userToShow: 1
},
fragments: {
user: () => Relay.QL`
/* what is this fragment on 'User'? does this has to be the name on/from the UserType Schema or this could be anything? */
fragment on User {
name,
conferences(userToShow: $userToShow) {
edges {
node {
id,
name,
description
},
},
},
}
`
},
});
exports.queries = {
name: 'ConferenceQueries',
params: {}, // what is params purpose here?
queries: {
user: () => Relay.QL`query { user }` // why do we have this user query when we have the query above?, what is this user field?
},
}
And on the app root, I've read this in docs, I just need to have a strong grasp, I am having OCD with this
<Relay.RootContainer
Component={ConferenceApp.Container}
route={ConferenceApp.queries} // is this something like react router? :/
onReadyStateChange={({error}) => { if (error) console.error(error) }} />
I put my questions on comments to specifically point out. Would really appreciate explanation on those parts, thank you.

Ok then. Let me try to answer you.
1. What is this fragment on 'User'?
Well, in GraphQL, queries declare fields that exist on the root query type. On the other hand, GraphQL fragments declare fields that exist on any arbitrary type. For example, the following fragment fetches the name and conferences for some User.
2. What is params purpose here?
Get your own example. If you need to get some particular user, you need to specify the id of the user to be able to get it. So, this is why we have these params.

Related

How to insert a record with a Relationship

Apologies, if this question is obvious, but I can't seem to find sufficient documentation. I might be lacking knowledge with restful methodologies. How do I store a record with a relationship?
I have a place. I want to store posts of users made to this place. So a place can have many posts. A post belongs to one place.
I'm using Aqueduct 3.0 Pre-Release.
I have following models:
place.dart
class Place extends ManagedObject<_Place> implements _Place {}
class _Place {
#primaryKey
int id;
#Column(unique: true)
String name;
ManagedSet<Post> posts;
}
post.dart
import 'package:places_api/model/place.dart';
import 'package:places_api/places_api.dart';
class Post extends ManagedObject<_Post> implements _Post {}
class _Post {
#primaryKey
int id;
#Column()
String text;
#Relate(#posts)
Place place;
}
I try to save the post, but there is only the possibility to store a place object, and not a place_id. Obviously below post query does not work, as there is only a values.place object, and not a values.place_id property. Is it intended to load the place, and then store all the posts to it?
Also without relationship, I can't store the place_id, as it seems that Aqueduct treats the _ as something special. Can't I use database properties, that have an underscore?
Is there any example that explains this?
#Operation.post()
Future<Response> createPost() async {
final body = request.body.asMap();
final query = new Query<Post>(context)
..values.place_id = body['place_id']
..values.text = body['text'];
final insertedPost = await query.insert();
return new Response.ok(insertedPost);
}
Currently I'm sending following body as POST:
{
"place_id": 1,
"text": "My post here"
}
To following URL: http://localhost:8888/posts
Would it be better to send something like this?
{
"text": "My post here"
}
To URL: http://localhost:8888/place/1/posts
Then fetch the place first, and store the post to it?
When represented as JSON, a relationship is always a list or an object. In your case:
{
"text": "text",
"place": {
"id": 1
}
}
This allows client application parsing code to remain consistent - a related object is always an object, never a synthetic field (e.g., place_id). The underlying database does name the column place_id by joining the relationship name and its primary key name with an underscore, but that's an implementation detail that isn't exposed through the API.
When inserting an object, foreign keys are inserted because they are a column in the table. Therefore, you can write your operation method as so:
#Operation.post()
Future<Response> createPost(#Bind.body() Post post) async {
final query = new Query<Post>(context)
..values = post;
final insertedPost = await query.insert();
return new Response.ok(insertedPost);
}
If you were to use the example JSON and this code, you'd get this SQL query: INSERT INTO _post (text, place_id) VALUES ('text', 1).
When inserting the 'has' side of a relationship, you have to insert the related objects as a separate query. An update/insert query will only set/insert values on a single table. If it makes sense for your API, you may want to POST the following place JSON:
{
"name": "Place",
"posts": [
{"text": "text"}
]
}
Your code to insert this object graph might look like:
await context.transaction((t) async {
final q = Query<Place>(t)..values = body;
final insertedPlace = await q.insert();
await Future.wait(body.posts, (p) async {
final postQuery = Query<Post>(t)
..values = p
..values.place.id = insertedPlace.id;
return postQuery.insert();
});
});
Couple of other small notes: asMap has been removed and replaced with as<T> or decode<T>. You also do not need an #Column annotation if you aren't adding any flags. All fields declared in the table definition type are database columns. Transient fields are declared in the ManagedObject subclass, and can be annotated with #Serialize() if you want them to be a part of the API surface.

How to spy on a Falcor Data Model constructor in Jasmine 1

I am trying to mock the constructor returned by require('falcor'); I have two routes and one calls the other route using var dataModel = new falcor({source: this});
Code looks like so
var falcor = require('falcor');
module.exports = {
route: 'items',
get: function (pathSet) {
var dataModel = new falcor({source: this});
var ids = '1';
dataModel.get('itemIds', ids).then(function (response) {
// Code I can't get to in Jasmine 1.x tests
});
}
}
I want the constructor to return a spy so I can call Promise.resolve and send back mock data for testing purposes. I'm not sure how to do this without moving the call into another module that I can mock separately. I think some questions that may help me here are
Where do I find the constructor functions defined by modules like falcor? I have tried looking into the 'global' object but have had no luck. If I did find this constructor, could I just replace it with a spyOn(global, 'falcor').andReturn(/* object with a mocked get method*/); ?
Is there a better way that makes testing easier to call a route from inside another route?
Thanks for any help.
To start w/ question 2: yes, to get data from another route, return refs to that route. Don't instantiate another model w/i the route. E.g.
const itemsRoute = {
route: 'items[{keys:indices}]',
get(pathSet) {
// map indices to item ids, likely via DB call
// in this case, SomeDataModel handles network requests to your data store and returns a promise
return SomeDataModel.getItemsByIndices(pathSet.indices)
.then(ids => ids.map((id, idx) => ({
path: ['items', pathSet.indices[idx]],
value: {
$type: 'ref',
value: ['itemById', id]
}
})));
}
};
const itemByIdRoute = {
route: 'itemById[{keys:ids}].name',
get(pathSet) {
return new Promise((resolve) => {
resolve(pathSet.idx.map(id => ({
path: ['itemById', id, 'name'],
value: {
$type: 'atom',
value: `I am item w/ id ${id}`
}
})));
});
}
};
When a request comes in for (e.g.) [items, 2, name], it will hit the first items route, resolve [items, 2] to [itemById, someId], and resolve the remaining name key in the itemsById route.
As for question 1: rather than mocking falcor, just mock whatever you are using to make the remote call to your data source. In the above case, just mock SomeDataModel

Relay query that that depends on data from a Relay query

I'm trying to perform a Relay query that that depends on data from another relay query
Assuming this runs under a url like /job/{jobID}
React
render() {
const job = this.props.job
return(
<h1>{job.name}</h1>
<TasksOutstanding
project={job.project}
from={job.startDate}
to={job.finishDate} />
)
}
Relay
fragments: {
job: () => Relay.QL`
fragment on Job {
name
startDate
finishDate
project {
${TasksOutstanding.getFragment('project')}
}
}
`,
So I need to get startDate and finishDate into the fragment, something like ${TasksOutstanding.getFragment('project',{from, to})}
But these values (from to) are unknown on the initial fetch ( all I have then is the jobID)
How are people dealing with this? Should I just execute a second request on component did mount once I have the startDate and finishDate values ?
You need to create field with arguments, this is GraqhQL feature and you should be able to do it with your tool for modeling the schema.
Relay's variables also will be useful. They will solve the problem that you don't know them on initial fetch.
So declare project field with from and to arguments, query the field using arguments are return appropriate data for project field. Your container should look like this:
initialVariables: {
from: null,
to: null
},
fragments: {
job: () => Relay.QL`
fragment on Job {
name
startDate
finishDate
project(from: $from, to: $to) {
${TasksOutstanding.getFragment('project')}
}
}
`,
}
Then during the application life, you can set your variables using setVariables to job.startDate and job.finishDate and have proper project fetched.

Refreshing lookups in SPA with Breeze.js when value has changed

I have an application that records a transaction and the user can pick the category from a drop down.
Categories are loaded up at application startup as they are "mostly" static / rarely going to change.
So, in my datacontext.js I do the usual and prime my data;
var primeData = function () {
var promise = Q.all([
getLookups(),
getBankAccountPartials(null, true)])
.then(applyValidators);
return promise.then(success);
function success() {
datacontext.lookups = {
categories: getLocal('Categories', 'name', true),
transactiontypes: getLocal('TransactionTypes', 'name', true),
payees: getLocal('Payees', 'name', true)
};
log('Primed data', datacontext.lookups);
}
function applyValidators() {
model.applyBankAccountValidators(manager.metadataStore);
}
};
function getLookups() {
return EntityQuery.from('Lookups')
.using(manager).execute()
.then(processLookups)
.fail(queryFailed);
}
Now, occasionally in an Admin screen the user can edit and add a category.
In the categoryadd.js viewmodel my save code looks something like this (extract shown);
save = function () {
isSaving(true);
datacontext.saveChanges()
.then(goToEditView).fin(complete);
function goToEditView(result) {
router.replaceLocation('#/categorydetail/' + category().id());
}
function complete() {
isSaving(false);
}
},
How do I refresh just the Categories lookup data? Or, am I just doing this wrong and should perhaps NOT have categories as a lookup?
Thanks.
Breeze.js synchronises automatically and knows to search out the Category and update it in its lookup list.
I checked this by calling datacontext.lookups from the browser console after the save had been performed and inspecting the objects it showed me the category name had been refreshed.

What is the proper way to do multi-parameter AJAX form validation with jQuery and ASP.NET MVC?

I have a registration form for a website that needs to check if an email address already exists for a given company id. When the user tabs or clicks out of the email field (blur event), I want jQuery to go off and do an AJAX request so I can then warn the user they need to pick another address.
In my controller, I have a method such as this:
public JsonResult IsEmailValid(int companyId, string customerNumber)
{
return Json(...);
}
To make this work, I will need to update my routes to point directly to /Home/IsEmailValid and the two parameters {companyId} and {customerNumber}. This seems like I'm "hacking" around in the routing system and I'm guessing perhaps there is a cleaner alternative.
Is there a "proper" or recommended way to accomplish this task?
EDIT: What I meant by the routes is that passing in extra parameter ({customerNumber}) in the URL (/Home/IsEmailValid/{companyId}/{customerNumber}) won't work with the default route mapping.
You can use the jQuery Validation Plugin to do that.
You're gonna have to implement your own method though like this :
$.validator.addMethod("checkCompanyEmail", function(value, element) {
var email = value;
var companyID = //get the companyID
var result;
//post the data to server side and process the result and return it
return result;
}, "That company email is already taken.");
Then register your validation method :
$("#the-form").validate({
rules: { email: { required: true, "checkCompanyEmail" : true } }
});
PS. I don't understand why you need to "hack around" with routing for that.
from the validate documentation under remote method
remote: {
url: "check-email.php",
type: "post",
data: {
username: function() {
return $("#username").val();
}
}
}

Resources