Custom functions for knex - bookshelf.js

There's a number of operations that I do all the time and I was hoping there would be a way to "extend" knex to be able to do them.
I would like to something like:
oneExists
result = knex.count(id).from('table').where({'code': 25})
if (result.length === 0) return false
if (result.length === 1) return true
throw an error
I would like to be able to do something like
knex.oneExists.count('id').from('table').where({'code': 25}).
at the moment i'm writing the code like this:
KnexUtil.oneExists(knex.select('id').from('table').where({code: 25}))
which returns a promise
I've looked through the knex codebase and i'm not sure:
how to chain this (and whether i would do this in /lib/query/compiler.js)
how to just make an extension to knex so i don't need to modify the original codebase

Starting from v0.19.1, knex have build-in ability to extend QueryBuilder
import Knex from 'knex'
Knex.QueryBuilder.extend('someFn', function (arg) {
console.log('Do Smth', arg)
return this
})
const knex = Knex({ client: 'pg' })
knex.select().from('table').someFn(0).toString()

Sure you can. I would recommend just creating your own plugin, something like the following:
// my-plugin.js
'use strict';
module.exports = function (Bookshelf) {
Bookshelf.Model = Bookshelf.Model.extend({
foo: function ( bar ) {
if( bar )
console.log('Method foo was called on a model with the arguments:', arguments.join(', '));
}
});
Bookshelf.Collection = Bookshelf.Collection.extend({
foo: function ( bar ) {
if( bar )
console.log('Method foo was called on a collection with the arguments:', arguments.join(', '));
}
});
};
Then inside your main application file, add:
Bookshelf.plugin( require( './my-plugin' ) );
BookshelfJS plugins basically allow you to extend the models and collections (and more), which allows you to add your own method, or overwrite existing ones (while still being able to call the original method from within your plugin)
For a better understanding, it might be a good idea for you to look at some existing BookshelfJS plugins, some already come with bookshelf, inside the plugins directory.
Another plugin that may be good to look at to better understand how the plugins work, would be the Soft Delete Plugin. In that plugin, you can see how some BookshelfJS methods are overridden in both the models and the collections objects, with methods that execute the original version of the method, then return the parsed/modified result (Lines #37-#59 and Lines #37-#57), as well as adding completely new methods (Lines #61-#72)
Edit: Obviously this is more BookshelfJS than KnexJS, but I didn't see any way to create plugins for KnexJS, since it's just a query constructor, all the real magic is in the ORM

If using TypeScript:
import { knex, Knex as KnexOriginal } from "knex";
declare module "knex" {
namespace Knex {
interface QueryBuilder {
customFunction<TRecord, TResult>(
value: number
): KnexOriginal.QueryBuilder<TRecord, TResult>;
}
}
}
knex.QueryBuilder.extend("customFunction", function (value: number) {
console.log("Custom Function:", value);
return this;
});
const pg = knex({ client: "pg" });
pg("table").select("*").customFunction(10).toString()
Check out documentation about how to extend knex

Related

Flow error when using react-apollo Query component render prop

I have the following code:
import { Query } from 'react-apollo';
type Post = {
id: string
};
interface Data {
posts: Array<Post>;
}
class PostsQuery extends Query<Data> {}
When using the above as follows:
<PostsQuery query={POSTS_QUERY}>
{({ loading, data }) => {
...
{data.posts.map(...)}
...
}
</PostsQuery>
I get the following error from flow:
Error:(151, 27) Cannot get 'data.posts' because property 'posts' is missing in object type [1].
Any idea why?
I did use flow-typed to add apollo-client_v2.x.x.js to my project by the way
Solution to the problem
Continued from the answer explaining how to make a verifiable example and research the problem.
So it looks like this part of react-apollo isn't typed in such a way to make accessing the data contents straightforward. Okay, that's fine, we can take their recommendation on destructuring and check for empty data. At the same time, we can also add an id property to the Post type so flow stops complaining about that:
(Try - Scroll to bottom for relevant code)
type Post = {
id: string,
title: string;
};
...snip...
// Look ma, no errors
class Search extends React.Component<{}> {
render = () => (
<PostsQuery query={QUERY}>
{({ loading, error, data }) => {
if (error) {
return <p>Error</p>
}
if (loading) return <p>Loading</p>
const nonNullData = (data || {})
const dataWithAllPosts = {allPosts: [], ...nonNullData}
const {allPosts} = dataWithAllPosts
if (allPosts.length == 0) {
return <p>Empty response or something</p>
}
return (
<div>
{allPosts.map(post => {
return <div key={post.id}>{post.title}</div>;
})}
</div>
);
}}
</PostsQuery>
);
}
I'm not familiar with the react-apollo library, so I'm not sure how you want to handle that case where there are no posts. I just added a message as seen above. It's entirely possible that the case never occurs (again, you would know better than I do). If that's the case, you might want to skip some of the above steps and just assert the desired type with a cast through any.
How to make a reproducible example and research the problem
So the first thing we need to do while analyzing these types is to go lookup the typedefs in the flow-typed repo. I went ahead a copy-pasted the react-apollo typedefs into flow.org/try, modified them slightly (added an any somewhere, set gql to any), and was able to replicate your errors:
(Try - Scroll to the bottom for your code)
Referencing the relevant lines of the QueryRenderProps type, we can see why flow is throwing the error:
{
data: TData | {||} | void,
...
}
It looks like data can either be TData (probably what you want), an empty object, or undefined. Cross checking this with the typescript typings for react-apollo, we can see why the type is the way it is:
{
...
// we create an empty object to make checking for data
// easier for consumers (i.e. instead of data && data.user
// you can just check data.user) this also makes destructring
// easier (i.e. { data: { user } })
// however, this isn't realy possible with TypeScript that
// I'm aware of. So intead we enforce checking for data
// like so result.data!.user. This tells TS to use TData
// XXX is there a better way to do this?
data: TData | undefined;
...
}
Unfortunately, due to the extreme length of these links and stackoverflow's limit on answer lengths, I have to continue my answer in another answer. I guess this answer can serve as an explanation of how to start debugging the problem.

Looping api calls in redux-thunk (React redux application)

I have a JSON object similar to this in the redux store of my application:
tables: [
{
"id":"TableGroup1",
"objs":[
{"tableName":"Table1","fetchURL":"www.mybackend.[....]/get/table1"},
{"tableName":"Table2","fetchURL":"www.mybackend.[....]/get/table2"},
{"tableName":"Table3","fetchURL":"www.mybackend.[....]/get/table3"}
]
},{
"id":"TableGroup2",
"objs":[
{"tableName":"Table4","fetchURL":"www.mybackend.[....]/get/table4"},
{"tableName":"Table5","fetchURL":"www.mybackend.[....]/get/table5"},
{"tableName":"Table6","fetchURL":"www.mybackend.[....]/get/table6"}
]
}
];
To load it, i use the following call (TableApi is a mock api loaded locally, beginAjaxCalls keeps track of how many Ajax calls are currently active);
export function loadTables(){
return function(dispatch,getState){
dispatch(beginAjaxCall());
return TableApi.getAllTables().then(tables => {
dispatch(loadTablesSuccess(tables));
}).then(()=>{
//Looping through the store to execute sub requests
}).catch(error => {
throw(error);
});
};
}
I then want to loop through my tables, call the different URLs and populate a new field called data so that an object after a call looks like this;
{"tableName":"Table1","fetchURL":"www.mybackend.[....]/get/table1","data":[{key:"...",value:"..."},{key:"...",value:"..."},{key:"...",value:"..."},.....]}
The data will be frequently updated by recalling the fetch url, and the table should then re-render in the view.
Which leads me to my questions:
- Is this architecturally sound?
- How would redux handle frequent changes? (because of immutability, will i get performance issues by frequently deep copying a table instance with 10,000+ data entries)
And more importantly, what code could i place to substitute the comment so that it serves its intended purpose? Ive tried;
let i;
for(i in getState().tables){
let d;
for(d in getState().tables[i].objs){
dispatch(loadDataForTable(d,i));
}
}
This code, however doesn't seem like the best implementation and I get errors.
Any suggestions are welcome, thanks!
First of all, you don't need to make a deep copy of all tables.
For sake of immutability you need to copy only changed items.
For your data structure it would look like this:
function updateTables(tables, table) {
return tables.map(tableGroup => {
if(tableGroup.objs.find(obj => table.tableName === obj.tableName)) {
// if the table is here, copy group
retrun updateTableGroup(tableGroup, table);
} else {
// otherwise leave it unchanged
return tableGroup;
}
})
}
function updateTableGroup(tableGroup, table) {
return {
...tableGroup,
objs: tableGroup.objs.map(obj => {
return table.tableName === obj.tableName ? table : obj;
})
};
}

MeteorJS ReactiveVar with database data

Clearly, I am doing something wrong with ReactiveVar because I cannot get it to work as I expect it should.
I am trying to set the value of an ReactiveVar by calling a Meteor.call method which returns the list of usernames. But it does not update when the usernames get changed in another part of the app.
I tried both:
Template.qastatistics.created = function () {
this.trackUsernames = new ReactiveVar(false);
var instance = Template.instance();
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
};
and:
Template.qastatistics.helpers({
users: function () {
var usernames,
instance = Template.instance();
if (instance.trackUsernames.get() === false) {
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
}
usernames = instance.trackUsernames.get();
...
But neither updates the list of usernames when these change in the database.
Is this even possible with ReactiveVars or have I completely misunderstood them?
EDIT: The usernames I mention are not from Meteor.users collection, but rather a distinct call from another collection that has usernames in it.
Fist of all I would use the onCreated function instead of defining created. That's a little more extendable and it's the new API. created is just kept around for backwards compatibility.
About your problem. You are right, you seem to have misunderstood what ReactiveVars do. They are a reactive data source. That means that when you call myReactiveVar.get in some Tracker.autorun (aka. reactive computation), the computation will rerun whenever myReactiveVar.set is called.
You got the first part right. Spacebars helpers always run inside their own computation. What you got wrong is thinking that a method call is a reactive action. That means, that you could call trackUsernames and set the trackUsernames ReativeVar again and the value in your template would update itself. But a method is only run once. It doesn't do anything fancy with reactivity.
A method call only transfers data once. When you publish a set of documents (like all users) on the other hand, they will be updated dynamically. Whenever a change happens inside that set of published documents, it will be synced to the client. So in general, it's a better idea to use publications and subscriptions to sync data reactively. If you'd want to use a method for the same thing you'd need to do some kind of polling (so your back in the stone-age again).
The easiest way to implement what you are trying to do is to use Meteor.users.find().fetch(). As it says in the docs fetch registers dependencies for all the documents you are fetching if it's being called from within a reactive computation.
First you'll need to properly set up your publications, so that users can see other users usernames. I'll leave that to you. Then you need to reimplement your helper
Template.qastatistics.helpers({
users: function () {
var usernames = _.pluck(Meteor.users.find().fetch(), 'username');
...
Thanks to suggestions from #kyll, I managed to get what I wanted by publishing the data I need:
server:
cope.publish.usernamesID = Random.id();
Meteor.publish("itemsusernames", function () {
self = this;
var initializing = true;
var handle = Items.find().observeChanges({
added: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
changed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
removed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
}
});
initializing = false;
self.added("itemsusernames", cope.publish.usernamesID, Items.distinct("p4User"));
self.ready();
self.onStop(function () {
handle.stop();
});
});
client:
users: function () {
var usernames = [],
oUsernames = ItemsUsernames.find().fetch();
if (!oUsernames[0]) return [];
usernames = $.map(oUsernames[0], function (value, index) {
if (!isNaN(index)) {
return [value];
}
});
...
And ofcourse: ItemsUsernames = new Mongo.Collection("itemsusernames");

Meteor: store object in rendered() to be used in child helpers

I would like to store an object that I create during Template.myTemplate.rendered = function ( ) { ... } and use that object in Template.myChildTemplate.helpers(helpers). So far I'm resorting to using a global object, but that feels very hacky. Is there a nice Template-centric way of doing this?
You could use UI._templateInstance(); and ReactiveDict (to make your data reactive too) (added with meteor add reactive-dict
Template.myTemplate.created = function() {
this.templatedata = new ReactiveDict();
}
Template.myTemplate.rendered = function() {
this.templatedata.set("myname", "value";
};
Template.myTemplate.helpers({
myvalue: function() {
var tmpl = UI._templateInstance();
return tmpl.templatedata.get('myname');
}
});
This will allow you to use this template multiple times on the page, and still have a variable scope to each template, which global variables or non instance variables wouldn't allow.
A note of warning, the current iron router (0.7.1) breaks UI._templateInstance();, which is an open bug at the moment.
There's no such method yet, unfortunately.
The common pattern is to use reactive dict either as a file-wide variable, or in a namespace related to the template if you need the access in several files. The downside of this solution is that such variable is shared among all instances of the same template, so you have to work around this if you render this template in more than one place.
var data = new ReactiveDict();
Template.myTemplate.rendered = function() {
data.set('key', 'value');
};

Meteor client side collection needs to have all data populated before anything else

I'm trying to use a client side collection as a site configuration system. I insert documents representing my different pages, and the iron-router and navigation tabs all use them to determine what pages they are and what templates are represented by them. Each page uses a {{> contentTemplate}} inclusion helper to load it's relevant template.
It all works great, when the data has all loaded. When I restart the app on certain pages, the data hasn't loaded yet, and I receive the Exception from Deps recompute function: Error: Expected null or template in return value from inclusion function, found: undefined error.
Here's my javascript:
StoriesArray = [
{ category: 'teaching', contentTemplate: 'teachingHome', title: 'Teaching Home'},
...
];
Stories = new Meteor.Collection(null);
StoriesArray.forEach(function (story, index) {
story._id = index + '';
Stories.insert(story);
});
// in main.js
Template.teachingPost.contentTemplate = function() {
console.log(this);
console.log(this.contentTemplate);
return Template[this.contentTemplate];
};
// in router.js
this.route('teaching', {
layoutTemplate: 'teachingPost',
data: function() { return Stories.findOne({contentTemplate: 'teachingHome', category: 'teaching'}); }
});
The console logs in the contentTemplate helper above log twice, the first time as this:
Object {} main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:45
undefined main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:46
and the second time as this:
Object {category: "teaching", contentTemplate: "teachingHome", title: "Teaching Home"} main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:45
teachingHome main.js?1f560c50f23d9012c6b6dd54469bb32b99aa4285:46
so the router is simply trying to load this data too early.
I've tried putting the StoriesArray loading process into different files all over my app, including lib, and even tried putting it into Meteor.startup, but it's always the same result.
The normal iron-router waitOn/subscription pattern doesn't really apply here, since this is a client side collection built with null, that has no server representation. I don't want this to have server representation, because this is static content that there's no need to go to my server for.
How do I ensure this information is done before continuing?
Untested, but per Iron Router's docs on waitOn:
Returning a subscription handle, or anything with a ready method from the waitOn function will add the handle to a wait list.
Also in general it's better to use find with data, rather than findOne, as find will return an empty cursor when the collection is empty as opposed to findOne returning undefined. So try this:
// in router.js
this.route('teaching', {
layoutTemplate: 'teachingPost',
data: function() {
return Stories.find({contentTemplate: 'teachingHome', category: 'teaching'});
},
waitOn: function() {
var handle = {};
handle.ready = function() {
if (Stories.find().count() !== 0)
return true;
else
return false;
}
return handle;
}
});
And adjust your Template.teachingPost.contentTemplate function to work with a cursor rather than an object.

Resources