In an ember model, how can you reference dynamic object keys? - javascript

In our application currently we are passing an array of objects from the server side into the model, and each element in the array has a key in it. For instance...
[
{name: "dog", sound: "bark", food: "cats"},
{name: "cat", sound: "meow", food: "mouse"}
]
In the model this is defined like so...
animals: hasMany(Animal, {key: 'name', embedded: true })
Then if we want the data for the cat, we use the findBy feature to find the one with the name = "cat" like so...
var animalName = "cat";
model.get('animals').findBy('name', animalName);
This works well, there are lots of potential types of 'animal' objects, and we already know what we're looking for.
However I'm curious for other reasons if we can pass this in as a Map from the server, which becomes a JSON object on the client that looks like this...
animals : { "dog" : {sound: "bark", food: "cat"},
"cat" : {sound: "meow", food: "mouse"}
}
It seems that in order to do this, in the model code we need to define a "has a" relationship for each potential animal type, which is dynamic and we do not want to hard code all the options here. Is there a way to say animals hasMany animals, but there in a map by name, rather then just an array?

I never figured out how to do this with inheritance the way I wanted, so I ended up defining a animals as a basic attribute...
animals: attr(),
Then putting my object in that and accessing the data with
animals.dog.sound
or
var name = "dog";
animals[name].sound
and this works, although we lose some benefits of the model inheritance system. For this application the inheritance wasn't that important.

Related

What's the purpose of keyPath in IDBObjectStore.createIndex()?

I've been reading all around the MDN, but I get stuff like:
keyPath
The key path for the index to use. Note that it is possible to create an index with an empty keyPath, and also to pass in a sequence (array) as a keyPath.
Well, no s#!t, keyPath is a key path. But what is that?
In all examples, it's the same thing as the name of the column (or index, as they call it):
objectStore.createIndex("hours", "hours", { unique: false });
objectStore.createIndex("minutes", "minutes", { unique: false });
objectStore.createIndex("day", "day", { unique: false });
objectStore.createIndex("month", "month", { unique: false });
objectStore.createIndex("year", "year", { unique: false });
They say that you can pass:
An empty string - ""
A valid JavaScript identifier (I assume this means valid JS variable name)
Multiple javascript identifiers separated by periods, eg: "name.name2.foo.bar"
An array containing any of the above, eg.: ["foo.bar","","name"]
I can't imagine what purpose does this serve. I absolutely do not understand what keyPath is and what can I use it for. Can someone please provide example usage where keyPath is something else than the name of the column? An explanation what effect do values of keyPath have on the database?
Examples might help. If you use a key path with an object store, you can have keys plucked out of the objects being stored rather than having to specify them on each put() call. For example, with records that just have an id and name, you could use the record's id as a primary key for the object store:
store = db.createObjectStore('my_store', {keyPath: 'id'});
store.put({id: 987, name: 'Alice'});
store.put({id: 123, name: 'Bob'});
Which gives you this store:
key | value
------+-------------------
123 | {id: 123, name: 'Bob'}
987 | {id: 987, name: 'Alice'}
But if you want to look record up by name, you create an index:
name_index = store.createIndex('index_by_name', 'name');
Which gives you this index:
key | primary key | value
--------+-------------+--------------------------
'Alice' | 987 | {id: 987, name: 'Alice'}
'Bob' | 123 | {id: 123, name: 'Bob'}
(The index doesn't really store a copy of the value, just the primary key. But it's easier to visualize this way. This also explains the properties you'll see on a cursor if you iterate over the index.)
So now you can look up a record by name with:
req = name_index.get('Alice')
When records are added to the store, the key path is used to generate the key for the index.
Key paths with . separators can be used for lookups in more complex records. Key paths that are arrays can either produce compound keys (where the key is itself an array), or multiple index entries (if multiEntry: true is specified)
A great way to understand things is to think how you would design it yourself.
Let's take a step back, and look at how a very simple NoSQL database would store data, then add indices.
Each Store would look like a JavaScript object (Or dictionary in python, a hash in C# ) etc...
{
"1": {"name": "Lara", "age": "20"},
"2": {"name": "Sara", "age": "22"},
"3": {"name": "Joe", "age": "22"},
}
This structure is basically a list of values, plus an index to retrieve the values, like so:
//Raw JS:
var sara = data["2"]
// IndexedDB equivalent:
store.get(2)
This is super fast for direct object retrieval, but sucks for filtering. If you want to get people of a specific age, you need to loop over every value.
If you knew in advance you would be doing your queries by age, you could really speed up such queries by creating a new object which indexes the value by age:
{
"20": [<lara>],
"22": [<joe>, <sara>],
...
}
But how would you query that? You can't use the default indexing syntax (e.g. data[22] or store.get(22)) because those expect the key.
One way would be to name that second index, e.g. by_age_index and give our store object a way to access that index by name, so you could to this:
store.index('by_age_index').get(22) // Returns Joe and Sara
The last bit of the puzzle would be telling that index how to determine which records go against which key (because it has to keep itself updated when records are added/changed/removed)
In other words, how does it know Joe and Sara go against key 22, but Lara goes against key 20?
In our case, we want to use the field age from each record. This is what we mean by a keyPath.
So when defining an index, it makes sense that we would specify that as well as the name, e.g.
store.createIndex('by_age_index', 'age');
Of course, if you want to access your index like this:
store.index('age').get(22) // Returns Joe and Sara
Then you need to create your index like this:
store.createIndex('age', 'age');
Which is what most people do, which is why we see that in examples, which gives the impression that the first argument is the keyPath (whereas it's actually just the arbitrary name we give that index) leaving us unsure about what the second argument might be for.
I could have explained all this by saying:
The first parameter is the handle by which you access the index on the store, the second parameter is the name of the field on the record by which that index should group its records.
But maybe this rundown will help other people too :-)
A keypath is how you indicate to indexedDB which properties of your object play a special role. Similar to how you would indicate to an SQL database that a certain column in a table is the primary key of the table, or how you could tell a database to create an index on one or more particular columns in a table.
In other words, it is the path that the indexedDB implementation should follow when determining which property should be used for some calculation. For example, when searching for a value with a given key.
It is a path, and not a simple key, because it considers that object property values can also be objects. In other words, there is a hierarchy. For example, {a:{b:1}}. The "path" to the value 1 is "a.b". The path is the sequence of properties to visit to get to the value.
The key part of the name signifies that the columns play an important role. For example, in identifying the primary key property, or a particular indexed property.
Properties that are not part of the keypath are ignored in the sense that the indexedDB implementation just treats the whole object as a bag of properties, and only pays attention to those, or gains awareness of those, that are a part of a keypath.

The correct structure for linking objects

I've just started getting into OOP and was wondering the correct structure for linking objects together.
Say for example I had an object called "Business":
function Business(name, sector, capital, employees, type, id) {
var self = {
name: name,
sector: sector,
capital: capital,
employees: employees,
stock: 0,
type: type,
id: id,
};
self.produce = function() {
return self.capital * self.employees;
}
Now in the example above using the properties capital and employees the method 'self.produce' produces output. This output I want to store depending upon the sector.
As a result, would I create a seperate "Sector" object? Or alternatively, within my business object, would it inherit the "Sector" object?
I understand inheritance is used to the attributes of similar objects for example a pet would inherit from an Animal. But in my case of "Business" and "Sector" I'm slightly confused.
In this case you want to use composition, business "has a" sector but a business "is not" a sector. Business would have an attribute sector, this is passed in the constructor so you can pass different sectors to different businesses. So if the output of produce depends on sector, the code would be in sector classes.

Executing a list of functions on an array

I'm getting to grips with using functional programming beyond a simple map or two. I have a situation where I want to be able to filter some elements from an array of objects, based on a particular field of those objects. It's a contrived example, but here goes:
I have a list of field definitions, and I want to extract two of them based on their title.
const toSearch = [
{ title: "name", description: "Their name" },
{ title: "age", description: "Their age" },
{ title: "gender", description: "Their gender" }
]
const fieldsToFind = ["name", "age"]
let filterObjects = R.map(R.objOf('title'), fieldsToFind)
let filterFuncs = R.map(R.whereEq(R.__), filterObjects)
let found = R.map(R.filter, filterFuncs)
console.log("Filter objects:", JSON.stringify(filterObjects))
console.log("Filter functions:", JSON.stringify(filterFuncs))
console.log("Found:", found[0](toSearch))
console.log("Found:", found[1](toSearch))
If I run this, the last of the output is the two elements of toSearch that I'm looking for, but it's not exactly neat. I've been trying to get another map working to get around executing found elements manually, but I also feel that even leading up to that point I'm taking an overly circuitous route.
Although it's a contrived example, is there a neater way of accomplishing this in a functional style?
One fairly simple way of doing this is:
R.filter(R.where({R.title: R.contains(R.__, ['name', 'age'])}))(toSearch);
//=> [
// {"description": "Their name", "title": "name"},
// {"description": "Their age", "title": "age"}
// ]
or, equivalently,
R.filter(R.where({title: R.flip(R.contains)(['name', 'age'])}))(toSearch);
One advantage, especially if you import the relevant functions from R into your scope, is how closely this reads to your problem domain:
var myFunc = filter(where({title: contains(__, ['name', 'age'])}));
myFunc = filter where the title contains 'name' or 'age'.
You can see this in action on the Ramda REPL.
I have a situation where I want to be able to filter some elements
from an array of objects, based on a particular field of those
objects.
For your situation, its quite simple
var filteredItems = toSearch.filter(function(value){ return fieldsToFind.indexOf(value.title) != -1 });
You could use .reduce to accomplish your task. MDN has a very brief explanation of the reduce function

Adding data to custom dataLayer variable in GTM

Update note: I believe this is different from the linked "duplicate" answer because I'm not asking how to use a variable name as the "name" (since I've shown I know how to do that), but rather, how to achieve the intended result below.
I currently have the custom data layer variable as an array that is populated in the following notation:
myArray.push({"name":"value"});
That said, the expected output should be like so:
[{"name1":"value1", "name2":"value2", "name3":"value3"}]
Now my current dilemma is that I need to add another item in the array, however, my "name" is stored in a variable.
Using the variable name leads to the variable name being used as the "name", so that won't work.
myArray.push({varName:"value"});
Results in:
[{"varName":"value"}]
I've also tried creating a new object, and inserting that in, but that just adds the object into the array without the correct "name".
var myObject = {};
myObject[varName] = "value";
myArray.push(myObject);
Results in:
[{"Message": {"varName":"value"}}]
Now, I'm out of ideas on how to go about with this, so any help is much appreciated!
TIA
If you can use ECMAScript 2015 you can do with computed property names:
myArray.push({[varName]:"value"});
I couldn't understand your question if you wanted to delete the variable "name" inside myArray or update it?
For adding new variables inside myArray just do another .push for name
myArray.push = [{
'name': 'value',
'name1': 'value',
'name2': 'value',
'name3': 'value',
}];
For removing the variable name this code would do it for more information about flushing variable read this Simo Ahava's blog http://www.simoahava.com/gtm-tips/remember-to-flush-unused-data-layer-variables/
myArray.push = [{
'name': undefined,
'name1': 'value',
'name2': 'value',
'name3': 'value',
}];

JavaScript syntax issue: [{ }] hierarchy

I came across the following nested array and am little confused as to why it uses this particular syntax:
var allQuestions = [{
question: "Which company first implemented the JavaScript language?",
choices: ["Microsoft Corp.", " Sun Microsystems Corp.", "Netscape Communications Corp."],
correctAnswer: 2
}];
Full example: http://jsfiddle.net/alxers/v9t4t/
Is it common practice to use
[{...}]
having declared a such a variable?
The definition is an array with an object literal in it. It is not realy a nested array. The
{
question: "Which company first implemented the JavaScript language?",
choices: ["Microsoft Corp.", " Sun Microsystems Corp.", "Netscape Communications Corp."],
correctAnswer: 2
}
is an object literal, which your array contains. In the fiddle you linked to there are several of these defined in the allQuestions array. By doing this it makes it easy to loop over the array of questions and display each in turn.
What's happening there is listing the object inside an array, example:
[{id:1, value:"any"}, {id:2, value:"any any"}]
So here we have declared array with two objects in it. Another so called "traditional" approach would be:
var arr = [];
var obj1 = {id:1, value:"any"};
arr.push(obj1);
...
The allQuestions variable is supposed to be "an array of questions", where each question is an object with properties like question, choices or correctAnswer.
If it was declared just as var allQuestions = {question: ..., choice: ...}, it would be just the one object. Further code which want to know the number of questions allQuestions.length or access e.g. the first question's test as allQuestions[0].question would not work.
Try adding more questions and you will see what the extra brackets are for:
var allQuestions = [
{ question: "1st...", ...},
{ question: "2nd...", ...},
...
];
allQuestions is just an array of objects and yes, it is common practise.

Categories

Resources