I am using ember 2.18, in this the update requests are PATCH. However, the backend is in SAILS and it does not support PATCH. Hence, I have to convert patch request to PUT.
I saw this issue and people seems to have solved it there. But it does not work for me. The code is as follows:
import App from './../app';
import DS from "ember-data";
import { computed } from "#ember/object";
import { camelize } from '#ember/string';
import JSONAPIAdapter from "ember-data/adapters/json-api";
export default DS.JSONAPIAdapter.extend({
coalesceFindRequests: true,
host: App.GPT.Configuration.restServer,
methodForRequest: ({ requestType }) => {
console.log('Log')
if (requestType === "updateRecord") {
return "PUT";
}
return this._super(...arguments);
},
pathForType(type) {
return camelize(type) + 's';
},
headers: computed(function () {
if (!App.StoreUtil.getSessionId()) {
if (App.GPT.ApplicationController) {
App.GPT.ApplicationController.set("hasMessages", [
{
message: "Session expired."
}
]);
App.GPT.ApplicationController.transitionToRoute("/");
} else {
window.location = "/";
}
}
return {
sid: App.StoreUtil.getSessionId()
};
}).volatile()
});
The code added to convert the request to PUT is :
methodForRequest: ({ requestType }) => {
console.log('Log')
if (requestType === "updateRecord") {
return "PUT";
}
return this._super(...arguments);
}
However, the method is not called at all. The funny part is pathForType is called for every request.
methodForRequest will only be available if you are using ember-data 2.8+ and you have the ds-improved-ajax flag enabled in environment.js
your other option is to override updateRecord()
Related
I'm following an Embercasts course (Ember + Rails). For the screencasts, they used Ember 3.0, but I'm using Octane.
In one video, a custom service is implemented. This is what my version looks like:
import Service, { inject as service } from '#ember/service';
export default class CurrentUserService extends Service {
#service store;
load() {
this.store.queryRecord('user', { me: true })
.then((user) => {
this.set('user', user);
})
.catch((e) => {
debugger;
});
}
}
In the load function, which is being called from a route, this.store.queryRecord() causes an error:
TypeError: str.replace is not a function
at Cache.func (index.js:64)
at Cache.get (index.js:774)
at decamelize (index.js:167)
at Cache.func (index.js:32)
at Cache.get (index.js:774)
at Object.dasherize (index.js:190)
at UserAdapter.pathForType (json-api.js:221)
at UserAdapter._buildURL (-private.js:293)
at UserAdapter.buildURL (-private.js:275)
at UserAdapter.urlForQueryRecord (user.js:13)
The relevant line is
var DECAMELIZE_CACHE = new _utils.Cache(1000, str => str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase());
This is the UserAdapter:
import ApplicationAdapter from './application';
export default class UserAdapter extends ApplicationAdapter {
urlForQueryRecord(query) {
if (query.me) {
delete query.me;
return `${super.buildURL(...arguments)}/me`;
}
return `${super.buildURL(...arguments)}`;
}
}
What's wrong here?
when you do super.buildURL(...arguments) you essentially do super.buildURL(query). And query is an object ({ me: true }) not a string while buildURL expects the modelName as first parameter.
So probably you wanna do something like this instead:
urlForQueryRecord(query, modelName) {
if (query.me) {
delete query.me;
return `${super.buildURL(modelName)}/me`;
}
return `${super.buildURL(modelName)}`;
}
I have component MyComponent.vue where I have data value that constantly changes. I want to pass this value to javascript file(js file should know about changes of value everytime)
Why do I do that? Because my regular js file is a service layer for axios methods. I can import this file in many other components. The file contains axios methods and urls are dynamic.
I want those urls depend on data variable. This data variable comes from MyComponent.js
So the main goal is to make dynamic urls of axios that depend on data variable
I tried some code but it doesn't work, because js file(CategoryService.js) know nothing about this.categoryNumber.
MyComponent.vue:
<script>
export default {
data() {
return {
categoryNumber: 1
}
}
}
</script>
CategoryService.js
import http from "../../../http-common";
let category = "category1";
if (this.categoryNumber === 1) {
category = "category1";
} if (this.categoryNumber === 2) {
category = "category2";
}
class CategoryService {
get(id) {
return http.get(`/${category}/${id}`);
}
update(id, data) {
return http.put(`/${category}/${id}`, data);
}
create(data) {
return http.post(`/${category}`, data);
}
delete(id) {
return http.delete(`/${category}/${id}`);
}
getAll() {
return http.get(`/${category}/all`);
}
}
export default new CategoryService();
So with a bit of refactoring, you could easily get this working.
First of all, I would put the if/else logic of your class into it.
For convenience and scalability, I would use a Vuex store that will keep track of your categoryNumber and share it accross all your components.
Then I would bind my service to my Vue instance so I can easily access it in all my components as well as the store and I would pass the latter to my class as a parameter.
For the last part, I don't know the logic in the http-common file so the code I will show you is a bit nasty. But depending on wether or not you bound 'http' to axios, you could make use of axios interceptors to call the getCategoryNumber() method in every request.
Here's an idea of the implementation I would go for:
const CategoryService = class CategoryService {
constructor(store) {
this._store = store;
this.category = "category1";
}
getCategoryNumber() {
if (this._store.state.categoryNumber === 1) {
this.category = "category1";
}
if (this._store.state.categoryNumber === 2) {
this.category = "category2";
}
console.log(this.category); // for demo puprose
}
get(id) {
this.getCategoryNumber(); // We could use axios request interceptor instead of calling that in every route, but that works !
return http.get(`/${this.category}/${id}`);
}
update(id, data) {
this.getCategoryNumber();
return http.put(`/${this.category}/${id}`, data);
}
create(data) {
this.getCategoryNumber();
return http.post(`/${this.category}`, data);
}
delete(id) {
this.getCategoryNumber();
return http.delete(`/${this.category}/${id}`);
}
getAll() {
this.getCategoryNumber();
return http.get(`/${this.category}/all`);
}
}
const store = new Vuex.Store({
state: {
categoryNumber: 1
},
mutations: {
setCategoryNumber(state, payload) {
state.categoryNumber = payload;
}
}
});
// Bind your service to the Vue prototype so you can easily use it in any component with 'this.$service'
// pass it the store instance as parameter
Vue.prototype.$service = new CategoryService(store);
new Vue({
el: "#app",
store, // dont forget to bind your store to your Vue instance
methods: {
updateCategoryNumber() {
// Put here any logic to update the number
this.categoryNumber = this.categoryNumber === 1 ? 2 : 1;
this.checkServiceCategoryValue();
},
checkServiceCategoryValue() {
// for demonstration purpose
this.$service.getCategoryNumber();
}
},
computed: {
// Look for the store value and update it
categoryNumber: {
get() {
return this.$store.state.categoryNumber;
},
set(value) {
this.$store.commit("setCategoryNumber", value);
}
}
}
});
<div id="app">
<h2>number: {{ categoryNumber }}</h2>
<button type="button" #click="updateCategoryNumber()">
updateCategoryNumber
</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#2.0.0"></script>
Thanks to #Solar
I just added one more parameter for all urls and put the number of category to it
CategoryService.js:
class CategoryOneService {
get(id, category) {
return http.get(`/${category}/${id}`);
}
getAll(category) {
return http.get(`/${category}/all`);
}
}
functions.js:
let catNum = "";
function getQuestion() {
if (this.categoryNumber === 1) {
catNum = "category1";
}
if (this.categoryNumber === 2) {
catNum = "category2";
}
let questionId = this.questionNumber;
CategoryOneService.get(questionId, catNum)
.then(response => {
this.question = response.data.question;
this.answer = response.data.answer;
})
.catch(error => {
console.log(error);
});
}
I am using nuxt and apollo together with: https://github.com/nuxt-community/apollo-module
I have a working GraphQL query (tested in GraphiQL):
(Because I want to fetch the info about my page and also some general SEO information)
{
entries(section: [pages], slug: "my-page-slug") {
slug
title
}
seomatic(uri: "/") {
metaTitleContainer
metaTagContainer
metaLinkContainer
metaScriptContainer
metaJsonLdContainer
}
}
I want to fetch this data as well with apollo in nuxt:
So I tried:
<script>
import page from '~/apollo/queries/page'
import seomatic from '~/apollo/queries/seomatic'
export default {
apollo: {
entries: {
query: page,
prefetch: ({ route }) => ({ slug: route.params.slug }),
variables() {
return { slug: this.$route.params.slug }
}
},
seomatic: {
query: seomatic,
prefetch: true
}
},
…
If I do that I will get an error message:
GraphQL error: Cannot query field "seomatic" on type "Query".
I then found this issue
https://github.com/apollographql/apollo-tooling/issues/648
and I would like to know if ths could be a problem of the apollo nuxt module.
Because following that fix indicated in the issue does not resolve anything.
I further tried to combine the two calls into one:
fragment SeoMaticFragment on Root {
seomatic(uri: "/") {
metaTitleContainer
metaTagContainer
metaLinkContainer
metaScriptContainer
metaJsonLdContainer
}
}
query myQuery($slug: String!) {
entries(section: [pages], slug: $slug) {
slug
title
}
SeoMaticFragment
}
~/apollo/queries/page.gql
But this would first throw an error
fragment Unknown type "Root"
So what is the best way to combine?
Why are the requests failing
is there an option to activate batching like described here: https://blog.apollographql.com/query-batching-in-apollo-63acfd859862
-
const client = new ApolloClient({
// ... other options ...
shouldBatch: true,
});
thank you so much in advance.
Cheers
There is actually a solution to this problem.
I found out that the result hook in vue-apollo solves this problem:
Example code that works:
<script>
import gql from 'graphql-tag'
const query = gql`
{
entries(section: [pages], slug: "my-example-page-slug") {
slug
title
}
seomatic(uri: "/") {
metaTitleContainer
metaTagContainer
metaLinkContainer
metaJsonLdContainer
}
}
`
export default {
data: () => {
return {
page: false,
seomatic: {}
}
},
apollo: {
entries: {
query,
prefetch: ({ route }) => ({ slug: route.params.slug }),
variables() {
return { slug: this.$route.params.slug }
}
},
result(result) {
this.entries = result.data.entries
this.seomatic = result.data.seomatic
}
}
}
</script>
I've followed some fairly basic tutorials to set up a basic Web Api service and a React TS app to consume this, but when my react component calls the WebApi service, I can see the Web Api gets stepped into and returns the data - as it does if I enter the API url in the browser, it returns the correct items JSON, but in the javascript code for React, the HTTP response doesn't appear to contain any data when the promise comes back from fetch, just an empty 200 response.
Ie response.data is undefined.
This must be something very basic I'm doing wrong - as like I mentioned when you enter the API url in the browser, you see the correct JSON in the browser. So why cant my react code understand the response?
My Web Api
[EnableCors("*", "*", "*")]
public class ItemsController : ApiController
{
private readonly Item[] _items = {
new Item
{
...
},
new Item
{
...
},
new Item
{
...
},
};
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, _items);
}
public HttpResponseMessage Get(long id)
{
var item= _items.FirstOrDefault(t => t.ItemId == id);
if (item== null)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Not found");
}
return Request.CreateResponse(HttpStatusCode.OK, item);
}
}
My react component
import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import '../../App.css';
import IStoreState from '../../interfaces/IStoreState';
import Item from '../../model/item';
import { getItemsReceivedAction } from './actions';
interface IItemsProps {
items: Item[],
itemsUpdated: (items:Item[]) => void
}
class Trades extends React.Component<IItemsProps> {
constructor(props:IItemsProps) {
super(props);
}
public componentDidMount() {
fetch('http://localhost:58675/api/items', {
headers: {
Accept: 'application/json',
},
method: 'GET'
}).then((t:any) =>
{
const results = t;
this.props.itemsUpdated(results.data);
} );
}
public render() {
return (
<div>
{this.props.items} items displayed
</div>
);
}
}
const mapDispatchToProps = (dispatch:Dispatch) => {
return {
itemsUpdated: (items:Item[]) => dispatch(getItemsReceivedAction(items))
}
}
function mapStateToProps(state:IStoreState) {
return {
items: state.viewingItems
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Items);
Edit: javascript results object below
body: (...)
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: "OK"
type: "cors"
url: "http://localhost:58675/api/items"
__proto__: Response
It sees to me that the problem is in the fetch part of you react component. What you get in the first then callback is a Response object.
In order to get the data from it, you need to call one of it's methods (which return a promise). In your case, since the response contains json, you need to call the json() method. After that, you chain another then where you manipulate the parsed data:
fetch('http://localhost:58675/api/items', {
headers: {
Accept: 'application/json',
},
method: 'GET'
}).then((t:any) =>
{
return t.json(); // <-- this part is missing
}
).then((t:any) =>
{
const results = t;
this.props.itemsUpdated(results.data);
}
)
I'm having a difficult time trying to test a methods in meteor that requires a connected user. Basically I need to test if a user of the app can add an article to it's cart. The methods will tests if a user is connected and, in order to test that will use Meteor.userId(). This seems to be a problem in unit testing as I get the error:
"Meteor.userId can only be invoked in method calls or publications."
So far, I tried to do what's proposed in this post: How to unit test a meteor method with practicalmeteor:mocha but I don't understand what the solution is doing.
Here is my testing method:
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { assert } from 'meteor/practicalmeteor:chai';
import { sinon } from 'meteor/practicalmeteor:sinon';
import { Carts } from '/imports/api/carts/carts.js';
import { Articles } from '/imports/api/articles/articles.js';
import '/imports/api/carts/carts.methods.js';
import { SecurityEnsurer } from '/lib/security/security.js';
function randomInt (low, high) {
return Math.floor(Math.random() * (high - low) + low);
}
if (Meteor.isServer) {
describe('Carts', () => {
describe('methods', () => {
let currentUser;
beforeEach(() => {
Factory.define('user', Meteor.users, {
name: "userTest",
currentUser: {
email: 'user#shop.info',
password: '123456',
}
});
currentUser = Factory.create('user');
sinon.stub(Meteor, 'user');
Meteor.user.returns(currentUser);
Articles.remove({});
articleId = Articles.insert({
name : "azerty",
description : "descTest",
modelNumber : "wxcvbn",
categoryName : "CatTest",
price : 1,
advisedPrice: 2,
supplierDiscount : 0,
brandId : "BrandTest",
isAvailable: true,
restockingTime: 42,
color: "Yellow",
technicals: [
{
name : "GPU",
value : "Intel"
},
],
});
Carts.insert({
owner: currentUser,
entries: [],
});
});
afterEach(() => {
Meteor.user.restore();
Articles.remove({});
Carts.remove({});
});
it('can add article', () => {
let quantity = randomInt(1,50);
const addArticleToCart = Meteor.server.method_handlers['carts.addArticle'];
const invocation = {};
addArticleToCart.apply(invocation, [articleId, quantity]);
assert.equal(Cart.find({owner: currentUser, entries: {$elemMatch: {articleId, quantity}}}).count(), 1);
});
});
});
}
If anyone can help me find out how to create my test, this would realy help me.
To fake a user when calling a Meteor Method, the only way I found is to use the mdg:validated-method package which provide a framework around Meteor methods. This framework seems to be the standard now (see the Meteor guide), but it requires to re-write your methods and the in-app calls.
After describing the methods using this framework, you are able to call them with the userId parameter when testing, using this kind of code (which verifies that my method is returning a 403 error):
assert.throws(function () {
updateData._execute({userId: myExternalUserId}, {
id: dataId,
data: {name: "test"}
});
}, Meteor.Error, /403/);
FYI, here are the packages I add when I do automated testing (Meteor 1.6 used):
meteortesting:mocha
dburles:factory
practicalmeteor:chai
johanbrook:publication-collector
Here's how I set up a fake logged in user for testing publish and methods:
1) create a user
2) stub i.e. replace the Meteor.user() and Meteor.userId() functions which return the current logged in user in methods
3) provide that user's _id to PublicationsCollector, which will send it in to your publish function.
Here's how I did it, I hope you can adapt from this:
import { Meteor } from 'meteor/meteor';
import { Factory } from 'meteor/dburles:factory';
import { PublicationCollector } from 'meteor/johanbrook:publication-collector';
import { resetDatabase } from 'meteor/xolvio:cleaner';
import faker from 'faker';
import { Random } from 'meteor/random';
import { chai, assert } from 'meteor/practicalmeteor:chai';
import sinon from 'sinon';
// and also import your publish and collection
Factory.define('user', Meteor.users, {
'name': 'Josephine',
});
if (Meteor.isServer) {
describe('Menus', () => {
beforeEach(function () {
resetDatabase();
const currentUser = Factory.create('user');
sinon.stub(Meteor, 'user');
Meteor.user.returns(currentUser); // now Meteor.user() will return the user we just created
sinon.stub(Meteor, 'userId');
Meteor.userId.returns(currentUser._id); // needed in methods
// and create a Menu object in the Menus collection
});
afterEach(() => {
Meteor.user.restore();
resetDatabase();
});
describe('publish', () => {
it('can view menus', (done) => {
const collector = new PublicationCollector({ 'userId': Meteor.user()._id }); // give publish a value for this.userId
collector.collect(
'menus',
(collections) => {
assert.equal(collections.menus.length, 1);
done();
},
);
});
});
});
}
You can also write a test for calling a Meteor method that relies on Meteor.userId():
expect(() => { Meteor.call('myMethod'); }).to.not.throw(Meteor.Error);