I am wondering how I can use ember-data working with a Spring/Hibernate Java backend. I would usually use Jackson to return JSON but that does not appear support the specifications required by jsonapi.org.
Currently beans are returned in a hierarchical nature like this.
{
"id" : 1,
"name" : "Bill",
"surname" : "Smith",
"address" : {
"id" : 23,
"number" : 21,
"street" : "Vincent st",
"state" : {
"id" : 44,
"name" : "Victoria"
"abbreviation" : "VIC"
}
"postcode" : 9000
}
}
I am not stuck to this structure and it can be modified to suite jsonapi, however, I can't find any jackson plugin that will serialize/deserialize my objects to the jsonapi specification.
What are my options here? I know that I am able to write my own serializer/deserializer for ember-data but that would be a huge pain, surely there are other people using ember with a java backend.
This looks familiar to an issue I had parsing json with DataFX (using javaFX), json was also generated by Spring...
I ended up returning a Page which wraps your Person and creates some json that is parseable.
Just my 2cts... I added the following requestmapping to my PersonController:
#RequestMapping(value="/persons", method=RequestMethod.GET, headers="Accept=application/json, application/xml")
public #ResponseBody Page<Person> getPersons(
#RequestParam(value="page",required=false,defaultValue="0") String page,
#RequestParam(value="size",required=false,defaultValue="20") String size,
#RequestParam(value="orderby",required=false,defaultValue="name") String orderby){
PersonRepository repository = context.getBean(PersonRepository.class);
final PageRequest pr = new PageRequest( Integer.parseInt(page), Integer.parseInt(size), Direction.ASC, orderby);
Page<Person> persons = (Page<Person>) repository.findAll(pr);
return persons;
}
I'm new to ember and still in the design stages with php as a backend, but I believe I have the same problem and found what looks like a fix. You're right that messing with jackson would be a difficult approach. It seems like it's much easier to make the change on the ember side. This guide (http://lab.empirio.no/emberjs-say-hello-to-apigility.html) discusses creating our own serializer in js based on ember data's ActiveModelSerializer and then modify the RestAdapter. The example is discussing building the standard ToDo app that I'm sure you've seen used as an example already.
The problem is the backend uses this format:
{"name":"Testing","is_completed":false}
While ember uses:
{"todo":{"name":"Testing","is_completed":false}}
Here's some same code:
App.ApplicationSerializer = DS.ActiveModelSerializer.extend({
primaryKey: 'todos_id',
extract: function(store, type, payload, id, requestType) {
this.extractMeta(store, type, payload);
if(payload._embedded)
payload = payload._embedded;
if(requestType == 'updateRecord' || requestType == 'createRecord'){
var data = {};
data[type.typeKey] = payload;
payload = data;
}
var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1);
return this[specificExtract](store, type, payload, id, requestType);
}
});
... "we manipulate the payload by extending (copying from the RESTAdapter).
the createRecord-function changes the behavior on the adapter like this: "
createRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record, { includeId: true });
return this.ajax(this.buildURL(type.typeKey), "POST", { data: data[type.typeKey] });
},
I'm sure I'm missing a lot here and probably missing some steps since I haven't even tried to build the app yet, but this was a problem I knew I needed to address before I committed to ember and this seems like a viable solution. I hope its a step in the right direction for you anyway.
edit: I know you didn't want to change the format for the sake of sticking to the standard, but there really isn't a standard for JSON APIs and content types yet, not one that's accepted anyway. This guide uses HAL + JSON which doesn't look any different from what I see at jsonapi.org if that's what you were talking about. Regardless, everyone seems to be having this issue regardless of backend language or frameworks.I think the ember team recognizes this and are trying to be flexible. Also, since ember-data is still in beta, I'd be more apt to make the changes there instead of writing the java side to support a changing library. Who knows? Maybe ember-data will have better support for different backends before its 1.0 release, although I haven't heard any mention of it on the roadmap.
Related
Basically we might have some data like so
open class RealmCart : RealmObject() {
#PrimaryKey
var _id: String = UUID.randomUUID().toString()
var items: RealmList<RealmCartItem> = RealmList()
var discountCode: String? = null
var userId: String = ""
}
And we do not want people editing these by mistake. We have some failsafe like code owners, labels in the repo, but we also want to have a unit test that can also prevent a merge if the data is changed in any way (add, change, or remove data). Basically, we do not want any accidents, and people are not perfect.
What is the best way to go about such a thing?
This is what I ended up doing:
I created an extension function for my data models
fun RealmObject.testDeclaredFields(): List<String> {
val fields = this::class.java.fields.map { it.name }
return this::class.java.declaredFields
.map { it.name }
.filterNot { fields.contains(it) }
.sorted()
}
Basically this just gets the data model fields, excluding things like companion objects.
Then I was able to create a test simply like
class RealmMessageTest {
#Test
fun `RealmMessage fields match spec`() {
val item = RealmMessage().testDeclaredFields()
assertContentEquals(item, fieldSpec)
}
private val fieldSpec = listOf(
"_id",
"acknowledgeStatusValue",
"body",
"completed",
"createdAt",
"deliveryStatusValue",
"from",
"meta",
"organizationId",
"platforms",
"threadId",
"title"
).sorted()
}
Why do this? Sometimes when someone is making changes carelessly, they will not realize that they have added a field, changed a field, or removed an important field in a data model that is sync'd to the backend. This does not prevent the developer from changing it, but given that they need to now change it in two places, they will be more cognizant whether they need to make this change or not.
I noticed a lot of people questioned why you would need to do this. My answer is that, I work in a very large repo where newer developers edit this without a second thought. This is just to make them more cognizant of changes to these important models, before they break develop. There are code owners for the repo, but they may not always see these changes. It is just an extra precaution.
How about using a mechanism like githooks to prevent the editing of certain files from being committed?
I'm not familiar with githooks, so I can't show you exactly how to do it, but I think it would be good to prevent commits and inform the developer of the situation with an error message.
I am very new to testing and I'm struggling my way through all this new stuff I am learning. Today I want to write a test for a vuetify <v-text-field> component like this:
<v-text-field
v-model="user.caption"
label="Name"
:disabled="!checkPermissionFor('users.write')"
required
/>
my test should handle the following case:
an active, logged in user has a array in vuex store which has his permissions as a array of strings. exactly like this
userRights: ['dashboard', 'imprint', 'dataPrivacy']
the checkPermissionFor() function is doing nothing else then checking the array above with a arr.includes('x')
after it came out the right is not included it gives me a negotiated return which handles the disabled state on that input field.
I want to test this exact scenario.
my test at the moment looks like this:
it('user has no rights to edit other user overview data', () => {
const store = new Vuex.Store({
state: {
ActiveUser: {
userData: {
isLoggedIn: true,
isAdmin: false,
userRights: ['dashboard', 'imprint', 'dataPrivacy']
}
}
}
})
const wrapper = shallowMount(Overview, {
store,
localVue
})
const addUserPermission = wrapper.vm.checkPermissionFor('users.write')
const inputName = wrapper.find(
'HOW TO SELECT A INPUT LIKE THIS? DO I HAVE TO ADD A CLASS FOR IT?'
)
expect(addUserPermission).toBe(false)
expect(inputName.props('disabled')).toBe(false)
})
big questions now:
how can I select a input from vuetify which has no class like in my case
how can I test for "is the input disabled?"
wrapper.find method accepts a query string. You can pass a query string like this :
input[label='Name'] or if you know the exact index you can use this CSS query too : input:nth-of-type(2).
Then find method will return you another wrapper. Wrapper has a property named element which returns the underlying native element.
So you can check if input disabled like this :
const buttonWrapper = wrapper.find("input[label='Name']");
const isDisabled = buttonWrapper.element.disabled === true;
expect(isDisabled ).toBe(true)
For question 1 it's a good idea to put extra datasets into your component template that are used just for testing so you can extract that element - the most common convention is data-testid="test-id".
The reason you should do this instead of relying on the classes and ids and positional selectors or anything like that is because those selectors are likely to change in a way that shouldn't break your test - if in the future you change css frameworks or change an id for some reason, your tests will break even though your component is still working.
If you're (understandably) worried about polluting your markup with all these data-testid attributes, you can use a webpack plugin like https://github.com/emensch/vue-remove-attributes to strip them out of your dev builds. Here's how I use that with laravel mix:
const createAttributeRemover = require('vue-remove-attributes');
if (mix.inProduction()) {
mix.options({
vue: {
compilerOptions: {
modules: [
createAttributeRemover('data-testid')
]
}
}
})
}
as for your second question I don't know I was googling the same thing and I landed here!
In Loopback it is easy to include relational objects when querying for data. For example, one can include all the comments that belong to a blog post in a single call using the include filter.
But in my case I want to get data that doesn't have a relation.
I have a User Detail page. On that page a user can choose a username and there's also a dropdown list where a user can choose from what country he is.
So from the client side I do something like:
Country.find().$promise.then(function(countryData) {
$scope.countries = countryData;
});
Player.find().$promise.then(function(playerData) {
$scope.player = playerData;
}
But what if I get more lists that I want to fill? Like, city, state, colors etc.
Then I'd have to make a lot of separate calls.
Is there a way to include all this data in one call, eventhough they have no relation? Something like this:
Player.find({ filter: { include: ["countries", "colors"] } }).$promise.then(function(data) {
// some stuff
}
You may want to try using the Where filter as documented here
An example of this for querying two specific things would be:
Post.find({where: {and: [{title: 'My Post'}, {content: 'Hello'}]}},
function (err, posts) {
...
});
You could create a remote method on one of your models that makes the calls internally and packages them back up for you.
Use some promise library if not using ES6 to wait for all and then return
Model.getAll = function(next) {
var promises = [];
promises.push(Model.app.models.Country.find());
promises.push(Model.app.models.Player.find());
promises.push(Model.app.models.Color.find());
Promise.all(promises)
.then(function(results) {
next(results);
});
}
/**
Register your remote method here
*/
You could create a remote method on one of your models that makes the calls internally and packages them back up for you.
Use some promise library if not using ES6 to wait for all and then return
Model.getAll = function(next) {
var promises = [];
promises.push(Model.app.models.Country.find());
promises.push(Model.app.models.Player.find());
promises.push(Model.app.models.Color.find());
Promise.all(promises)
.then(function(results) {
next(results);
});
}
/**
Register your remote method here
*/
I have problem and try with this solution but i get error "Failed with multiple errors, see details for more information.". It seems like there is bug on Loopback while using promise.all
How can I perform a where-in type query using ember-data?
Say I have a list of tags - how can I use the store to query the API to get all relevant records where they have one of the tags present?
Something like this:
return this.store.find('tags', {
name: {
"in": ['tag1', 'tag2', 'tag3']
}
})
There isn't built in support for something like that. And, I don't think its needed.
The result that you are after can be obtained in two steps.
return this.store.find('posts'); // I guess its a blog
and then in your controller you use a computed property
filteredPosts: function('model', function() {
var tags = ['tag1', 'tag2', 'tag3'];
return this.get('model').filter(function(post) {
if ( /* post has one of tags */ ) {
}
return false;
});
});
Update: What if there are tens of thousands of tags?!
Amother option is to send a list of tags as a single argument to the back end. You'll have to do a bit of data processing before sending a request and before querying.
return this.store.find('tags', {
tags: ['tag1', 'tag2', 'tag3'].join(', ')
})
In your API you'll know that the tags argument needs to be converted into an array before querying the DB.
So, this is better because you avoid the very expensive nested loop caused by the use of filter. (expensive !== bad, it has its benefits)
It is a concern to think that there will be tens of thousands of tags, if those are going to be available in your Ember app they'll have a big memory footprint and maybe something much more advanced is needed in terms of app design.
I've kinda been struggling with this for some time; let's see if somebody can help me out.
Although it's not explicitly said in the Readme, ember-data provides somewhat validations support. You can see that on some parts of the code and documentation:
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/states.js#L411
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/states.js#L529
The REST adapter doesn't add validations support on itself, but I found out that if I add something like this in the ajax calls, I can put the model on a "invalid" state with the errors object that came from the server side:
error: function(xhr){
var data = Ember.$.parseJSON(xhr.responseText);
store.recordWasInvalid(record, data.errors);
}
So I can easily to the following:
var transaction = App.store.transaction();
var record = transaction.createRecord(App.Post);
record.set('someProperty', 'invalid value');
transaction.commit()
// This makes the validation fail
record.set('someProperty', 'a valid value');
transaction.commit();
// This doesn't trigger the commit again.
The thing is: As you see, transactions don't try to recommit. This is explained here and here.
So the thing is: If I can't reuse a commit, how should I handle this? I kinda suspect that has something to do to the fact I'm asyncronously putting the model to the invalid state - by reading the documentation, it seems like is something meant for client-side validations. In this case, how should I use them?
I have a pending pull request that should fix this
https://github.com/emberjs/data/pull/539
I tried Javier's answer, but I get "Invalid Path" when doing any record.set(...) with the record in invalid state. What I found worked was:
// with the record in invalid state
record.send('becameValid');
record.set('someProperty', 'a valid value');
App.store.commit();
Alternatively, it seems that if I call record.get(...) first then subsequent record.set(...) calls work. This is probably a bug. But the above work-around will work in general for being able to re-commit the same record even without changing any properties. (Of course, if the properties are still invalid it will just fail again.)
this may seem to be an overly simple answer, but why not create a new transaction and add the pre-existing record to it? i'm also trying to figure out an error handling approach.
also you should probably consider writing this at the store level rather than the adapter level for the sake of re-use.
For some unknown reason, the record becomes part of the store default transaction. This code works for me:
var transaction = App.store.transaction();
var record = transaction.createRecord(App.Post);
record.set('someProperty', 'invalid value');
transaction.commit()
record.set('someProperty', 'a valid value');
App.store.commit(); // The record is created in backend
The problem is that after the first failure, you must always use the App.store.commit() with the problems it has.
Give a look at this gist. Its the pattern that i use in my projects.
https://gist.github.com/danielgatis/5550982
#josepjaume
Take a look at https://github.com/esbanarango/ember-model-validator.
Example:
import Model, { attr } from '#ember-data/model';
import { modelValidator } from 'ember-model-validator';
#modelValidator
export default class MyModel extends Model {
#attr('string') fullName;
#attr('string') fruit;
#attr('string') favoriteColor;
validations = {
fullName: {
presence: true
},
fruit: {
presence: true
},
favoriteColor: {
color: true
}
};
}