SlightlyLoony
Tera Contributor

find_real_file.pngYesterday I showed you how you could make a method that could be called like this (assuming, again, that gr contains a user record):


var gru = new GRUtil();
var roles = gru.getM2MList(gr, 'sys_user_has_role',
'user', 'role', 'sys_user_role');
while (roles.next())
gs.log(roles.name);


That code works just fine, but it requires you (the code's author) to know several little details that are annoying to have to go look up: the many-to-many table name, and the parent and child references within that many-to-many table. Wouldn't it really be nice if you could just write this instead?

var gru = new GRUtil();
var roles = gru.getM2MList(gr, 'sys_user_role');
while (roles.next())
gs.log(roles.name);


Well, you can — by inferring the many-to-many table name and reference fields.

The "trick" here is to realize that the sys_collection table (navigate to System Maintenance → Collections) has all the information a piece of script would need to infer the rest. All you need to do is find a pair of records in the sys_collection table that were both for the same many-to-many table, and where one points to the parent table and the other to the child table. Then you know the many-to-many table and can read the reference fields directly from sys_collection. Woo hoo! The code below is a modified version of the method I showed you yesterday:

/*
* Returns a GlideRecord instance containing the records related to the given parent (a GlideRecord)
* in the given child table related through the given m2m_table with the given parent reference and
* child reference fields. If only the parent and the child table are given, the other parameters
* are inferred from the sys_collection table. Returns null if no collection could be inferred.
*/
getM2MList: function(parent, some_table, parent_reference, child_reference, child_table) {
if (arguments.length == 5)
return this._getM2MList(parent, some_table, parent_reference, child_reference, child_table);

// if we just have the parent and the target table, infer the rest from the collection table...
else if (arguments.length == 2) {
var col_gr = new GlideRecord('sys_collection');
var tables = this.getTables(parent.getTableName());
tables = tables.concat(this.getTables(some_table)); // some_table is really the child table!
col_gr.addQuery('name', tables);
col_gr.query();
var pairer = {};
while (col_gr.next()) {
var tbl = '' + col_gr.collection;
if (pairer[tbl]) {
// figure out which reference is to our parent and which is to our child...
var parent_ref = '' + col_gr.join_field;
var child_ref = pairer[tbl];
if (this._array_has(this.getTables(some_table), '' + col_gr.name)) {
parent_ref = pairer[tbl];
child_ref = '' + col_gr.join_field;
}
return this._getM2MList(parent, tbl, parent_ref, child_ref, some_table);
}
pairer[tbl] = '' + col_gr.join_field;
}
return null; // we couldn't infer a collection...
}
},

/*
* Returns a GlideRecord instance containing the records related to the given parent (a GlideRecord)
* in the given child table related through the given m2m_table with the given parent reference and
* child reference fields.
*/
_getM2MList: function(parent, m2m_table, parent_reference, child_reference, child_table) {
var m2m_gr = new GlideRecord(m2m_table);
m2m_gr.addQuery(parent_reference, parent.sys_id);
m2m_gr.query();
var kids = [];
while (m2m_gr.next())
kids.push('' + m2m_gr.getValue(child_reference));
var kids_gr = new GlideRecord(child_table);
kids_gr.addQuery('sys_id', kids);
kids_gr.query();
return kids_gr;
},

/*
* Returns true if the given array contains the given value.
*/
_array_has: function(array, val) {
for (var i = 0; i < array.length; i++)
if (val == array)
return true;

return false;
},


You'll see that there are now two additional methods: _array_has() and _getM2MList(). The former is just a simply little utility method to see if a value appears in a list. The latter is the getM2MList() method from yesterday, just renamed. The leading underscore ("_") is a convention that we use to indicate a "private" method — that is, a method that we only expect to use within the same class.

The new getM2MList method has some things in it worth understanding. First, note the use of the arguments object. We check its length to see how many arguments someone actually called our method with. If we got five arguments, then it's a call exactly like the one I showed you yesterday, and we simply call the _getM2MList to do exactly what we did yesterday. On the other hand, if we're called with two arguments, then we're going to try to infer the missing arguments from the sys_collection table. That's what the code after the else if (arguments.length == 2) does.

With this change, the getM2MList method can be called with either five arguments (just as in yesterday's blog post) or with two arguments, and it will do the right thing in both cases!