Bookshelf relation with through and custom key columns - bookshelf.js

This is my relation chain.
applications.applicant_id -> users.id -> skills_users.user_id
skills_users.id <- ratings.rateable_id
I want get an Application with a custom relation applicant_skills, and preferably join in ratings. So I was hoping something like this would work:
applicant_ratings: function () {
return this.belongsToMany('SkillUser').through('User')
},
But I cannot get it to work. I have tried different combinations of "key"-parameters, but none seem to work for me.

The issue is that 'users' is not a pivot, but 'skills_users' is. More like:
applications.applicant_id -> users.id <- skills_users.user_id
skills_users.id <- ratings.rateable_id
Try perhaps:
var Application = bookshelf.Model.extend({
tableName: 'applications',
applicant_ratings: function () {
return this.belongsTo('User', 'applicant_id')
.hasMany('Rating')
.through('SkillUser','rateable_id');
}
});

Related

Searching in YDN-DB as LIKE in SQL Query

I am using YDN-DB for my web POS, library using clone from following source:
https://raw.githubusercontent.com/yathit/ydn-db/master/jsc/ydn.db-dev.js
All is going well, incorporation with indexed db.
The only concern is, i want to use LIKE for my query as shows below, but it does not work and return sql parsing error, it looks LIKE does not work in YDN-DB, can you please advise any solution to achieve this?
APP.db.executeSql("SELECT * FROM products WHERE name LIKE '%test%' ").then(function(results) {
console.log(results);
});
However following works good.
APP.db.executeSql("SELECT * FROM products WHERE name = 'test'").then(function(results) {
console.log(results);
});
i have tried the other way aswell, but available operator only supports the start of string search, as shows below:
var q = APP.db.from( 'products' ).where('name', '^', 'New');
var limit = 10000;
var result = [];
q.list( limit ).done( function( objs ) {
result = objs;
console.log(result);
});
Can any of you please help me out to achieve the nieche.

bookshelf.js recursive query plus ordering

I've found one example of recursive tree fetch but ordering does not work. What is the best way to fetch all items recursively with ordering at each level?
Model
knex.schema.createTable('items', function (table) {
table.increments();
table.string('title');
table.integer('parent');
table.integer('order');
});
var Item = bookshelf.Model.extend({
tableName: 'items',
items: function() {
return this.hasMany(Item, 'parent');
}
});
Recursive but only to certain amount of level, all would be preferred instead of specifying depth. This works but doesn't order related items correctly. Reference
Item.query({where: {parent: null}, orderBy: 'id'})
.fetchAll(
{
withRelated: ['items.items.items.items.items'],
orderBy: 'id'
});
Edit
I'm updating this with a recursive query that works for SQLite (note I'm using id for ordering because my bad choice of column name). Solution maybe to use bookshelf for crud operations and a raw query to populate objects that I need.
with recursive tc( i )
as ( select id FROM items WHERE parent IS NULL
UNION SELECT id FROM items, tc WHERE items.parent = tc.i ORDER BY id
)
select * FROM items WHERE id IN tc;

Joining a table through another join table using withRelated

I have 3 tables:
Order: id
Item: id
OrderItems: order_id, item_id
I want to be able to fetch a particular Order and get all the Items related to that Order, without the clutter introduced by doing Order.where(...).fetch({withRelated: ['orderItem.item']).
Ideally I'd like to do Order.where(...).fetch({withRelated: ['items']) and the items relationship knows to go through the OrderItems table to get the information.
I have tried the relationship
items() {
return this.hasMany('Item').through('OrderItem')
}
but that doesn't seem to work as I'd expected.
Is this possible using Bookshelf's API without writing a manual join?
The relationship you tried:
items() {
return this.hasMany('Item').through('OrderItem')
}
Means to Bookshelf:
Item(id, OrderItem_id) -> OrderItem(id, Order_id) -> Order(id)
Or a 1:n:m relationship.
But your database says:
Item(id) <- OrderItem(Item_id, Order_id) -> Order(id)
This kind of relationship on Bookshelf maps better as belongsToMany():
bookshelf.plugin('registry') // to ease the cyclic mapping
const Item = bookshelf.Model.extend({
tableName: 'item',
orders: function() {
this.belongsToMany('Order', 'OrderItem')
},
})
bookshelf.model('Item', Item)
const Order = bookshelf.Model.extend({
tableName: 'order',
items: function() {
//return this.hasMany('Item').through('OrderItem')
return this.belongsToMany('Item', 'OrderItem')
},
})
bookshelf.model('Order', Order)
Note that until you start placing useful data on the intermediate table you don't need to create a model for it.
With that a Order.where(...).fetch({withRelated: 'items'}).then(...) query should work as expected.

I'd like to create a reactive filter for textbox input - best practice?

I have a list of company names I'm populating from a collection
the helper function I have is:
Template.companyList.helpers({
companies: function () {
return Companies.find({owner: Meteor.userId()}, {sort: {a: 1}, name:1, createdAt:1});
}
});
It's looped through using a
{{#each companies}}
which outputs
<LI> Company Name </LI>
Above this I have a text box, and would like to filter the list of companies by what I type in the textbox - I'd prefer to have a "containing" filter as opposed to "starting with" filter, but i'll take either one - is there an established way of doing this in Meteor? If not, is there a plugin that someone wrote that does this?
Also, whatever answer you give, please consider the fact that I've been using Meteor for, oh, 5 days now, and i'm still learning it, so, a Newbie style answer would be great.
Thanks for Reading!
edit
This is the updated answer I came up with - combining David's answer with my previous companies helper:
Template.companyList.helpers({
companies: function () {
var query = Session.get('query');
selector = {owner: Meteor.userId()};
options = {sort: {a: 1}, companyName:1, createdAt:1};
if (query && query.length) {
var re = new RegExp(query, 'i');
selector.companyName = re;
}
return Companies.find(selector, options);
}
});
Here is the outline for a simple search-as-you-type interface:
Template.myTemplate.helpers({
companies: function() {
// build a regular expression based on the current search
var search = Session.get('search');
var re = new RegExp(search, 'i');
selector = {owner: Meteor.userId()};
// add a search filter only if we are searching
if (search && search.length)
selector.name = re;
options = {sort: {createdAt: -1}};
return Companies.find(selector, options);
}
});
Template.myTemplate.events({
'keyup #search': function() {
// save the current search query in a session variable as the user types
return Session.set('search', $('#search').val());
}
});
This assumes:
You are trying to search Companies by name.
You have an input with an id of search.
Please modify as needed for your use case. Let me know if you have any questions.

Transforming on excluded field in meteor Mongo Collection

Meteor.publish("thing", function(options) {
return Collection.find({}, {fields: {anArray: 0}})
})
I exclude "anArray" because it contains userids not meant to be seen by each user. However it could contain the logged in user itself, in which case the user needs to know it.
Collection = new Mongo.Collection("thing", {
transform: function(document) {
_.each(document.anArray, function(item) {
item = true
})
return document
}
})
Above I try to transform the collection(simplified) but because the "anArray" is excluded, "anArray" is simply not defined.
How can I let the user know he is in "anArray" without compromising all other users in "anArray"? (I tried to do that in the transform.)
You can use the package I developed, meteor-middleware. It provides a nice pluggable API for this. So instead of just providing a transform, you can stack them one on another. This allows for code reuse, permissions checks (like removing or aggregating fields based on permissions), etc.
For example, for your particular problem, you could do (in CoffeeScript):
thing = new PublishEndpoint 'thing', (options) ->
Collection.find {}
class HideAnArrayMiddleware
added: (publish, collection, id, fields) =>
fields.anArray = _.intersection fields.anArray, [publish.userId] if fields.anArray
publish.added collection, id, fields
changed: (publish, collection, id, fields) =>
fields.anArray = _.intersection fields.anArray, [publish.userId] if fields.anArray
publish.changed collection, id, fields
thing.use new HideAnArrayMiddleware()
As described in this answer, here is how you access document fields before publishing them:
// server: publish the rooms collection
Meteor.publish("rooms", function () {
var self = this;
var handle = Rooms.find({}).observeChanges({
added: function(id, fields) { self.added("rooms", id, fields); },
changed: function(id, fields) { self.changed("rooms", id, fields); },
removed: function(id) { self.added("rooms", id); },
}
});
self.ready();
self.onStop(function () { handle.stop(); });
});
In your case, maybe you can do something like this:
added: function(id, fields) {
if (fields.anArray)
if (fields.anArray.indexOf(self.userId) !== -1)
fields.anArray = [self.userId];
else
delete fields.anArray;
self.added("rooms", id, fields);
},
You'll also have to take care of the changed function in a similar way.
It's not possible to include or exclude elements of an array, so your best bet is to define an explicit Boolean field in the document for the user being in the array.
Also, because transforms on the server are ignored (please vote for this issue), you'll have to set that field in the database if it's dynamically computed. Similar SO questions: 1, 2, 3.
An alternative is to define a non-database-backed collection. Have a look at the counts-by-room example.

Resources