Multiple polymorphic relationships in Ember (Octane) models and Mirage - javascript

In our Rails backend, we have a lot of Concerns server side. For example, several models import an AppointableConcern which allows the resources to be associated with Appointments. Each model can import an unlimited number of Concerns.
In our Ember Octane models, we use inheritance instead, and I'm not sure whether that's a good idea. For example, we have these two models...
// models/groupable.js
import { belongsTo } from '#ember-data/model';
import ApplicationModel from './application'
export default ApplicationModel.extend({
group: belongsTo('group', { inverse: 'groupables' }),
});
// models/appointable.js
import { hasMany } from '#ember-data/model';
import Groupable from './groupable';
export default Groupable.extend({
appointments: hasMany('appointment')
});
... as well as some models extending either Appointable or Groupable.
To me it seems inheritance has been abused to share logic between classes that are not closely related: An Appointable is not a specific implementation of a Groupable (multiple Groupables can form a Group) at all.
Now I wonder:
Is this a recommended/standard way to share this logic between models?
Is there a more flexible alternative? Considering my example above, directly using group: belongsTo('group', { inverse: 'groupables' }) in any model that should be groupable and appointments: hasMany('appointment') in all models that appointments should be allowed to associate with seems like a simple solution.
edit:
We encountered a problem with the solution i sketched out above. It is related to EmberCLI Mirage tests: I wanted to make one more model groupable, but it does not fit in the inheritance chain well. We couldn't make it inherit from the Groupable model. So we added this line directly to the new model: group: belongsTo('group', { inverse: 'groupables' })
This works in the browser, but in Mirage I got this error:
The 'article' type does not implement 'groupable' and thus cannot be assigned to the 'groupables' relationship in 'group'. Make it a descendant of 'groupable' or use a mixin of the same name.
But as there is no multiple inheritance, I can't make Article a descendant of Groupable and there are no mixins in Mirage at all. So I removed { inverse: 'groupables' } from my new model, which is ok for the moment, but we shouldn't need to take away functionality from our code just to make a test work.

Related

Ember Data - Lazy loading child data of a model without re-creating previously created objects again

I'm new to ember-data. I'm trying to load comment list from a API using multiple API calls. The comment list feature works like below,
A comment object can have a parent comment or children comments (replies)
All comments (children & parent) from different comment threads are list down in a single comment list using a 1st API call.
If user click on specific comment from above list it will prompt respective comment thread. Respective parent or children comments loading using 2nd API call
The comment model is implemented as below,
export default CommentModel.extend( {
parent: computed(function() {
return get(this, 'store').queryRecord('comment', {
_overrideURL: `comments/${get(this, 'id')}/parent`,
});
}),
children: computed(function() {
return get(this, 'store').query('comment', {
_overrideURL: `comments/${get(this, 'id')}/children`,
});
}),
...
As this implementation, if user click on child comment (reply) from the comment list, the 2nd API call with load the respective parent comment and parent comment will load its children comments again. That behaviour cause reload the comment list component in UI.
Is there any other way in ember-data to lazy load relationship without creating already existing objects?
If you really need to go that road, you may try to perform a findRecord instead of a queryRecord and use adapterOptions to customize your model's adapter urlForFindRecord method.
TL;DR
Why you shouldn't:
IMHO, you have a data flow problem in your proposed design.
You shouldn't be performing async code inside a computed property (nor returning immutable object as queryRecord response).
Tasks work great for that purpose.
You shouldn't be having your model to load data (that should be route's responsibility), which violates both MVC and DDAU principles.
There is this great article from 2015 on that
As a matter of fact since ember octane, you shouldn't be using computed properties at all, they have been replaced by actual getters and tracked properties.
More on that
Ember is a great framework, good luck on your journey!

Using Redux with vanilla JS how can I reach it without passing it to every component?

I'm using Redux in a vanilla JS project. I have a bunch of small modular UI files and controllers and such. In those UI files I might have code like:
const ExampleForm = function (StoreInstance) {
return $('<form />', {
submit: () => {
StoreInstance.dispatch({
type: 'EXAMPLE_DISPATCH',
post: {
message: $TextareaComponent.val()
}
})
return false
}
})
}
The issue is I have a lot of simple view files like this and many of them are nested and I'm finding it to be ugly and error prone to have the store passed as a param to everything.
For example, I trimmed it for brevity but the form component has form element components such as a textarea. Currently I see two options of managing the Store:
Setting it to window when creating it in my entry file (index.js) and then just accessing Store globally. This seems the nicest, although not "best practice" and makes unit testing and server side rendering a bit harder.
Passing it to every component tediously. This is my example above. This I'd consider as "best practice" but it's pretty annoying to do for every file you make almost.
I'm wondering if there's any alternatives or tricks to passing the store instance. I'm leaning towards just making it global.
You could use the constructor pattern and create every view as new ConnectedView(). The ConnectedView would have a memoized instance of the store (this.store within the view), so it doesn't need to be global.

Dependency Injection in a distributable Javascript library?

We are using Backbone to create reusable components. We create controllers in order to setup bindings between models and views. We wish to offer people the ability to replace the models and views with their own implementations.
Since the majority of people will use the the components we provide, I don't want to force developers to configure or create anything that isn't different from the defaults.
This means that someone should be able to pass an instance of an object they want to use as a model or view to the controller, all configured and setup and ready to go, or they can pass in some configuration to what the controller will use by default.
I am not sure what the best approach is.
// Idea #1
var controller = new Controller({
dependencyA: {
conf: { // config for depedencyA }
},
dependencyB: {
conf: { // config for dependencyB }
class: custom.implement.Class
}
});
In this approach, the user doesn't have control over how to instantiate the object. What's bad about this is, for example, Backbone models take two arguments in the constructor while views only take one.
// Idea #2
var controller = new Controller({
dependencyA: {
args: ['arg1',{
opt1: 'opt-value',
}]
},
dependencyB: {
args: ['a','b','c']
class: custom.implement.Class
}
});
Args would be the arguments passed to a constructor. This means the controller calls the constructor with the args array, and again this only really benefits you if you're passing in custom configuration for default dependencies. If you want to pass your own implementation it's more awkward.
// Idea #3
var controller = new Controller({
dependencyA: new outOfBoxModel({ // configuration }),
dependencyB: new custom.imeplement.Class('a','b','c')
});
In this approach, the user is forced to instantiate the out of box model. If the model's default settings are all appropriate though, then the user is doing unnecessary work. The only bit they HAVE to do is create an instance of their own custom implementation.
I am not sure what the best approach would be here?
Of the three approaches, I most prefer approach number 3. Here is why:
It is more consistent than the other approaches. In the 3rd approach, the user only has to learn to pass in constructed instances of dependencies into the controller. In the other approaches, the user has to pass in either args, or args and a class name.
It does not violate the Single Responsibility Principle. In the first two approaches, your controller is made responsible for constructing and configuring its dependencies. This doesn't feel like dependency injection at all! I think it's better, and simpler, to leave the construction of dependencies to the user or another part of your application. In my opinion, its not a terrible thing to force the user to construct their own implementations - it gives them the freedom to define their constructors however they want, rather than forcing you to define and maintain constructor APIs for the Controllers dependencies, and forcing the user to conform to them.
A different idea:
If you have this freedom in your application, I would consider putting your Controller construction logic in a factory class or method:
var createController = function(config) {
// Parse, validate, extract relevant config items
// var a = create dependency a
// var b = create dependency b
return new Controller(a, b);
}
This approach allows you to be as fancy as you want with your definition of config - you could support all three of the config definitions you provided in your original post - although I wouldn't recommend that :-). At a minimum, I would have the factory method support a zero args invocation (in which case it would return the default construction of Controller) and one of your preferred config definitions.

Storing model or collection-specific views inside model or collection constructors

I realize there is many solutions to this but I was wondering what the community's opinion is.
I have a series of models and collections. Each model has a number of views like details, edit, print, aside, help, etc. Collections have views that often have the same names (ie: aside, help, etc).
One requirement I have is that I need to structure my code in modules. The application should have no trace of a module's functionality if the module is NOT loaded. This may happen if, say, a user has no permissions to view, edit, etc other users. So the "Users" module would not even be loaded.
So...
I thought a good place to store view definitions for a model could be the model's constructor and for a collection in the collection's constructor. For example:
var User = (function(){ // module definition
// model definition
var Model = Backbone.Model.extend({
initialize: function() {
// ...
}
},{
Views: {
Details: Backbone.View.extend({
// ...
}),
Aside: Backbone.View.extend({
// ...
}),
Help: Backbone.View.extend({
// ...
})
}
});
// collection definition
var Collection = Backbone.Collection.extend({
model: Model,
initialize: function() {
// ...
}
},{
Views: {
Aside: Backbone.View.extend({
// ...
}),
Help: Backbone.View.extend({
// ...
})
}
});
// add more code here
return { // make model and collection public
Model: Model,
Collection: Collection
};
})(); // end module definition
I realize I could have my views live elsewhere but would this approach have any considerable drawbacks that I may not be aware of? Perhaps memory leaks or something less obvious?
Thank you!
I think you would be better off not adding your views as "class methods" onto your models and collections. Because of the nature of JavaScript's prototypical inheritance, you aren't really adding class methods so much as properties to the constructor functions for your model types. As to whether or not this is going to cause you issues like memory leaks, I can't say.
I would instead say that, unless you have an unlisted compelling reason for using this structure, you are better off just grouping your views on simple objects.
If the goal is to modularize your code I would take advantage of something like require.js or Marionette modules or just grouping "related" code in an IIFE.
If you are interested in knowing more about what exactly happens to the classProperties that are passed into the Backbone.Model.extend method then I would recommend looking directly at the annotated source.
Have a look at require.js. With it you should be able to add logic that deals with module loading. In general you should still have a look at it, works great for organising backbone applications, especially with the text plugin.

Ember.js createRecord possibly not firing

I have an application that saves a user's search criteria in localStorage, where each saved search is represented as an instance of an Ember.js model:
Checklist.SavedSearch = DS.Model.extend({
id: DS.attr('string'),
filters: DS.attr('string')
});
When the "save" button is pressed, the controller creates a model instanced and creates a record for it:
Checklist.savedSearchController = Ember.ArrayController.create({
[..]
save: function(view) {
var saved_seach = Checklist.SavedSearch.createRecord({
id: 'abcd',
filters: '<json>'
});
Checklist.local_store.commit();
}
});
Checklist.local_store is an adapter I created (this is unsurprisingly where the problem probably begins) that has a basic interface that maps createRecord, updateRecord, etc. to a bunch of get/set methods that work with localStorage (loosely based on a github fork of ember-data). The adapter appears to work fine for some basic tests, particularly as findAll has no issues and returns values added manually to localStorage.
Here is the relevant method within Checklist.local_store:
createRecord: function(store, type, model) {
model.set('id', this.storage.generateId);
var item = model.toJSON({associations: true});
this.storage.setById(this.storage_method, type, id, item);
store.didCreateRecord(model, item);
}
The problem is that when createRecord is called by the controller, absolutely nothing occurs. Running it through the debugger, and logging to console, seems to show that the method isn't called at all. I imagine this is a misunderstanding on my part as to how Ember.js is supposed to work. I'd appreciate help on why this is happening.
I come from a ruby and php background, and have perhaps foolishly dived straight in to a JS framework, so any other comments on code style, structure and anything in general are welcome.
Ember Data doesn't change createRecord on the controller so it shouldn't behave any differently. It's possible that there was something related to this in the past, but it's certainly not the case anymore.

Categories

Resources