2

Is there a way to create a chained object from loop? For example input:

["table1","table2","table3"]

output:

  db
  .select(fields)
  .from(table)
  .innerJoin("table1")
  .innerJoin("table2")
  .innerJoin("table3")

another input:

 ["table1","table2","table3","table4","table5"]

output:

  db
  .select(fields)
  .from(table)
  .innerJoin("table1")
  .innerJoin("table2")
  .innerJoin("table3")
  .innerJoin("table4")
  .innerJoin("table5")

Right now i have no idea how to do that except using eval, which isn't something i would like to do.

I need this to join multiple tables using knex, so if there any other way to do so, i will be really happy :)

Saffer
  • 152
  • 8
  • 1
    are you wanting code generation i.e output a string that looks like that or just the overall outcome? – Joe Warner Mar 03 '19 at 15:50
  • @JoeWarner thank you for asking, i forgot to clarify that i need overall outcome not the actual chained object – Saffer Mar 03 '19 at 16:06

2 Answers2

3

The way chaining works is that each of those methods returns an object that has the next method as a property. This means you can use something like reduce() to keep calling the next method on the object returned from the previous.

reduce() accepts an initial object, which you can pass to get things rolling. Something like:

var tables = ["table1","table2","table3"]
let res = tables.reduce((res, table) => res.innerJoin(table), db.select(fields).from(table))

To get a sense how this works we can create a fake db object with all the methods that return the object for the next method in the chain. The innerJoin method just adds the argument to the value property:

// fake db object with these methods
const db = {
  select(t) {
    this.val = [] // initialize val
    return this
  },
  from(t) {
    return this
  },
  innerJoin(name) {
    this.val.push("Added: " + name)
    return this
  }
}

var tables = ["table1","table2","table3"]
// call innerjoin for each of the tables
// this is the same as chaining them
let res = tables.reduce((res, table) => res.innerJoin(table), db.select().from())

// values where accumlated in the val property
console.log(res.val)
Mark
  • 90,562
  • 7
  • 108
  • 148
  • 1
    nice solution this is much better than mine when applying just one type of function. – Joe Warner Mar 03 '19 at 16:55
  • I've tried this solution and unfortunately it didn't work with knex db object, however without knex this works just fine. – Saffer Mar 03 '19 at 17:53
  • this might be beause the reduce creates a new object? i dunno tho – Joe Warner Mar 03 '19 at 18:20
  • `reduce()` doesn't necessarily return a new object @JoeWarner — it returns the object returned by the reducer function which in this case it the last invocation of `innerJoin()`. I'm not sure why this isn't working with knex — it seems like it should be identical to chaining. – Mark Mar 03 '19 at 18:26
  • yeah totally im confused also might try find out why – Joe Warner Mar 03 '19 at 18:30
  • 1
    Sorry for misguidance, when i had tried your solution i made a mistake with db object. Turns out your solutions works just fine aswell. – Saffer Mar 03 '19 at 18:56
0

I think looking into functional programming will help you here.

I used a pipe function from the internet link below but you could use lodash/Ramda/underscore or whatever your fav util library is.

the two main concepts I'm using here is currying and piping.

What is 'Currying'? Currying is when you break down a function that takes multiple arguments into a series of functions that take part of the arguments we are also making use of curring which is returning a function from another function to compose new functions.

https://medium.com/@venomnert/pipe-function-in-javascript-8a22097a538e A pipe function takes an n sequence of operations; in which each operation takes an argument; process it; and gives the processed output as an input for the next operation in the sequence. The result of a pipe function is a function that is a bundled up version of the sequence of operations.

what we're doing here is taking an array, creating a new array of functions we want to apply to a value.

So we want to do a load of joins on a db.

join => con => con.innerJoin(join);

is taking a value i.e "table1" and returning a function that takes a db connection which calls the join and returns for the next one.

const joins = toJoin.map(join => con => con.innerJoin(join)); and this creates are array of functions to pass to pipe.

const db = ({
  select: function (field) {
    console.log('select', field)
    return db
  },
  from: function (table) {
    console.log('from', table)
    return db;
  },
  innerJoin: function (join) {
    console.log('innerJoin', join)
    return db;
  }
});

const _pipe = (a, b) => (arg) => b(a(arg));
const pipe = (args) => [].slice.apply(args).reduce(_pipe);

function joinInnerMultiple(table, fields, toJoin) {
  const joins = toJoin.map(join => con => con.innerJoin(join));

  return pipe(joins)(db.select(fields).from(table)); 
}


joinInnerMultiple("User", "uuid", ["table1", "table2", "table3", "table4", "table5"])
Joe Warner
  • 3,335
  • 1
  • 14
  • 41
  • whoever downvoted mine explaining, if I've misunderstood happy to change. – Joe Warner Mar 03 '19 at 16:44
  • Quite honestly, I think a simple “for” loop would do in this case. Your answer seems too complex. Passion for FP is great, but it might create more confusion for the OP than value. – rishat Mar 03 '19 at 16:56
  • this is more coming from the point of view of chaing loads of differnet functions but yeah would be overkill in this instance. – Joe Warner Mar 03 '19 at 16:58
  • 1
    This solution worked perfectly. Note for knex users: db object is a knex connection object – Saffer Mar 03 '19 at 17:33
  • awesome glad to help :) any questions let me know – Joe Warner Mar 03 '19 at 18:34