How to create relations via repository mixins in loopback 4 - loopbackjs

I have a requirement where models and repositories can be extended via mixins with the purpose to add fields and relations.
Regarding adding fields to models is already achieved (even with some drawbacks like not keeping hidden fields),
but i'm not able to also create a repository mixin which defines a relation on the model-mixed-in field.
Basically i can't find a way to get the repositoryGetter object to configure the relation in the mixin constructor, as per docs defined here, since decorators can't be used in the mixin constructor.
The incriminated code is the following one.
// mixin stuff above
// eslint-disable-next-line #typescript-eslint/no-explicit-any
(superClass: R, options?: GenericObject) => {
class Extended extends superClass {
// create the main relation accessor object
private user: BelongsToAccessor<OnitUser, ID>;
#repository.getter('OnitUserRepository') public onitUserRepositoryGetter: Getter<OnitUserRepository>;
// eslint-disable-next-line #typescript-eslint/no-explicit-any
constructor(...params: any[]) {
super(...params);
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore This mixin applies to a DefaultCrudRepository. We know we have this function.
this.log = this.createBelongsToAccessorFor('user', this.onitUserRepositoryGetter);
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore This mixin applies to a DefaultCrudRepository. We know we have this function.
this.registerInclusionResolver('user', this.user.inclusionResolver);
}
}
return Extended
}
The alternative way to get the repositoryGetter i need is decorating a single property, but it appear to be injected later in the instansiation flow so it is not available in the constructor.
Suggestion on how to solve this?

Found a way. In the constructor, all the code is replaced with:
const proxiedInclusionResolver: InclusionResolver<T, User> =
( sourceEntities: T[], inclusion: InclusionFilter, _options?: Options) => {
if (!this.user){
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore This mixin applies to a DefaultCrudRepository. We know we have this function.
this.user = this.createBelongsToAccessorFor('user', this.userRepositoryGetter);
}
return this.user.inclusionResolver(sourceEntities, inclusion, _options);
};
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore This mixin applies to a DefaultCrudRepository. We know we have this function.
this.registerInclusionResolver('user', proxiedInclusionResolver);
This generate the accessot on the fly the first time the relation is accessed, and in that moment the repositoryGetter is already valorized.

Related

How to update nested state in Ember Octane

So my situation is as follows:
I got a component with a couple of input fields that represent a contact and are filled with data from a service:
#service('contact-data') contact;
Each field stands for one property that is accessed via
{{contact.properties.foo}}
I have the properties saved as a JS object to easily filter out empty fields when using them and I tracked it with #tracked like so:
export default class MyService extends Service {
#tracked properties = {id: 0, foo: "", bar : "abc", };
#action updateProperty(name, value) {
this.properties[name] = value;
}
}
However, the properties do not re-render properly in the component and the textfields do not get updated.
I'd appreciate any help with this! Thanks!
Any time you have a bunch of nested state like that which needs to be tracked, just tracking the top-level object won't cause updates to the internals of that object to propagate out. You need to track the internal properties, or you need to reset the whole object you're tracking.
You have basically two rough options for dealing with updates to those internal properties:
If the object has a well-known shape, extract it into a utility class which uses #tracked on the fields, and instantiate the utility class when you create the service. Then updates to those fields will update.
If the object is really being used like a hash map, then you have two variant options:
Use https://github.com/pzuraq/tracked-built-ins, if you don't need IE11 support
Do a "pure functional update", where you do something like this.properties = { ...this.properties, foo: newValue };
Of these, (1) is pretty much always going to be the cheapest and have the best performance. Doing (2.1) will be a little more expensive, because it requires the use of a Proxy, but not enough that you would normally notice. Doing (2.2) will end up triggering a re-render for every property in the properties used anywhere in the app, even if it didn't change.
In the case you've described, it appears the fields are well known, which means you should reach for that class. The solution might look something like this:
import Service from '#ember/service';
import { action } from '#ember/object';
import { tracked } from '#glimmer/tracking';
class TheProperties {
#tracked id;
#tracked foo;
#tracked bar;
}
export default class MyService extends Service {
properties = new TheProperties();
#action updateProperty(name, value) {
this.properties[name] = value;
}
}
Note that #tracked installs getters and setters in place of plain class properties, so if you need to use this for a JSON payload somewhere or similar, you'll also want to implement toJSON on the utility class:
class TheProperties {
#tracked id;
#tracked foo;
#tracked bar;
toJSON() {
let { id, foo, bar } = this;
return { id, foo, bar };
}
}
There's another add-on that does basically the same thing for Array and Objects as tracked-built-ins.
It's a proxy that basically notifies the root that an update has occurred somewhere. The advantage against tracked-built-ins is that the nesting depth is not limited as it's common for JSON to have deep nesting.
The drawbacks are similar to tracked-built-ins in terms of performance. Use it sparingly and try not to use it in tables with hundreds/thousands of rows as re-rendering is going to be not performant.

How to pass a #tracked object from an Ember route model hook

My question is two-fold:
Where is the best place to put some kind of polling logic - in the route file right?
How do I pass this constantly updating value from the Route to some child component? Labeling some variable as "#tracked" and then passing the tracked variable via the model hook?
Let's say I have something like this:
routes/index.js
export default class IndexRoute extends Route {
#tracked
recent: {
A: 0,
...
},
constructor() {
super(...arguments);
this.getRecent();
}
getRecent() {
// poll data / fetch latest
const {A, ...partialObject} = this.recent;
this.recent = { ...partialObject, A: <some new value fetched>};;
later(this, this.getRecent, 2000);
}
model() {
return this.recent;
}
}
application.hbs
<p>constantly updating "this.recent": {{ this.model.A }} </p>
I thought if I use the model hook like this, it would be tracked and therefore auto-update but that was not the case. I have this sample Ember Twiddle that emulates what I'm trying to do. I tried to force a re-compute by reassigning the entire variable but it didn't work.
This question is a deeper dive from my initial question here.
You are returning a reference to object stored in this.recent in your model hook. But the getRecent method does not change that object but overrides this.recent. After the first execution of getRecent method the model of the route and this.recent aren't the same object anymore. The model of the route, which you can access through this.modelFor(this.routeName) is the initial value and this.recent is the new value.
You want to mutate the object returned from model hook instead.
The object given in your example has a fixed schema. This allows you to mark the property A as tracked:
recent: {
#tracked A: 0,
...
}
As currently you return the value of this.recent in your model hook. But instead of overwriting it in getRecent method, you are only changing the value of it's property A:
getRecent() {
this.recent.A = <some new value fetched>;
later(this, this.getRecent, 2000);
}
If you don't know the schema of the object returned in model hook or if you are dealing with an array, it's a little bit more complicated. You wouldn't have a property to decorate with #tracked. I would recommend to use the tracked-built-ins package in that case.
For arrays you can also fallback to legacy MutableArray from #ember/array/mutable package. But you must make sure in that case that you use it's custom methods to manipulate the array (e.g. pushObject instead of push).

Loopback: detect a change in the model

My overall goal in the hook I'm trying to use here in my /models/LocatableUser.js is to figure out if there's an actual change I need to update for, and if there is, do something (make another api call).
I have a structure of custom models that inherit from this custom model, and so it works for both sub models when defining the before save hook in the parent model. Here's a sample of the method I defined in the parent model, LocatableUser:
LocatableUser.observe('before save', function (ctx, next) {
if (ctx.instance){ // new record
ctx.instance._address.getGeopoint(function (error, location) {
setLocation(error, location, ctx.instance, next);
});
} else if (ctx.currentInstance) { // this is an update, currentInstance is treated as immutable
LocatableUser.findById(ctx.currentInstance.id, function(err, data) {
console.log('Locatable User: current data is: ', err, data)
})
console.log('Locatable User: ctx is:', ctx);
ctx.currentInstance._address.getGeopoint(function (error, location) {
setLocation(error, location, ctx.data, next);
});
} else {
console.warn('no context instance');
}
});
The problem with this code is, since there are no concrete classes of a LocatableUser, calling LocatableUser.findById() won't find anything, because the actual class will be some child class of LocatableUser. The only thing I've found that works is defining this method in both child classes, but that duplicates code.
Is there a way to have the derived classes' findById method called from the LocatableUser class?
Loopback version 2.22.0
So it turns out I was going about this the wrong way:
In the PUT call, ctx.currentInstance comes in as the currently stored instance, removing the need for me to query for that same instance by ID. The ctx.data object is the actual payload coming from the call to the rest API, so I could just compare the data coming in from that to the currentInstance, in order to determine if I need to run some update logic.

Initializing empty relationships in entities

I have entities with 1:1 or 1:M relations to other entities. All relations however are nullable.
I want to proxy some operations to the related entity. I'm giving example below. The problem is that if the relation still does not exist, I have null, so I'm ending up constantly checking for nulls, which obviously is wrong. What I would like to do is to hydrate my entities with empty objects. Reasons:
Doctrine knows what instance should be created for the field anyway. So it should just provide empty instance instead of null
I don't want to fill my code with initializations, like
$object->setSettings(new SettingsEntity)
If the requests should be proxied is somehow disputable, but I want to hide the DB representation from the client code. If my direction however is totally wrong, please point me to the right direction. I may accept that this is responsibility of the model, not of the entity, but Doctrine always returns entities to me
Sure, I can add the initialization either in the constructor of the entity, or to provide getter that creates a new instance of the object, if such does not exists. There are couple of reasons I don't want this:
I don't know how objects are actually hydrated. I assume such initialization should happen in an event and not in the constructor
I don't want to write the code for each entity (at some point, someone will forget to add the initialization in the getter) and want to make it automatically for each relation instead.
Some example code:
/**
* SomeObject
* #ORM\Entity()
* #ORM\Table(
name="some_object"
* )
*/ class SomeObject implements DataTransfer {
/**
* #ORM\OneToOne(targetEntity="Settings", mappedBy="SomeObject")
*/
protected $settings;
public function getSettings() {
return $this->settings;
}
public function get() {
$record = new \stdClass();
$record->id = $this->getId();
...
$settingsObject = $this->getSettings();
$record->someKey = $settingsObject ? $settingsObject->getSomeKey() : null;
$record->someOtherKey = $settingsObject ? $settingsObject->getSomeOtherKey() : null;
return $record;
}
Any suggestions, including hacking Doctrine, are welcome.
P.S. Doctrine-ORM version is 2.3. I can upgrade if this will help solving the problem.
I won't discuss your proxy-thingie-theory: your code, your design, I don't have enough knowlegde of these to have an opinion.
About you knowing how Doctrine hydrates its entities, you can see how it's done in \Doctrine\ORM\UnitOfWork::createEntity. It doesn't seem to invoke the constructor (uses \ReflectionClass::newInstanceWithoutConstructor, which obviously shouldn't use the constructor), but you may be interested in listening to Doctrine's post-load event (part of the lifecycle events logic).
About initializing your null properties, i.e. the code that your post-load event should trigger, you should begin by having a superclass over all of your entities: instead of class SomeObject implements DataTransfer {...}, you'd have class SomeObject extends MyEntity {...} (and have MyEntity implement DataTransfer to keep your interface). This MyEntity class would be a "mapped superclass", it would be annotated with #HasLifecycleCallbacks, and declare a method annotated with #PostLoad. There you have your hook to run your null-to-something code.
For this code to be generic (as it'd be coded from this superclass), you can rely on Doctrine's entity metadata, which retains association mappings and all data that the Unit Of Work needs to figure out its low-level DB-accessing business. It should look like the following:
/** #HasLifecycleCallbacks #MappedSuperclass ... */
public class MyEntity implements DataTransfer {
...
/** #PostLoad */
public function doPostLoad(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) { //the argument is needed here, and is passed only since 2.4! If you don't want to upgrade, you can work around by using event listeners, but it's more complicated to implement ;)
$em = $event->getEntityManager();
$this->enableFakeMappings($em);
}
private function enableFakeMappings(\Doctrine\ORM\EntityManager $em) {
$mappings = $em->getClassMetadata(get_class($this))->getAssociationMappings(); //try and dump this $mappings array, it's full o'good things!
foreach ($mappings as $mapping) {
if (null === $this->{$mapping['fieldName']}) {
$class = $mapping['targetEntity'];
$this->{$mapping['fieldName']} = new $class(); //this could be cached in a static and cloned when needed
}
}
}
}
Now, consider the case where you have to new an entity, and want to access its properties without the null values checks: you have to forge a decent constructor for this job. As you still need the Entity Manager, the most straightforward way is to pass the EM to the constructor. In ZF2 (and Symfony I believe) you can have a service locator injected and retrieve the EM from there. Several ways, but it's another story. So, the basic, in MyEntity:
public function __construct(\Doctrine\ORM\EntityManager $em) {
$this->enableFakeMappings($em);
}
Doing this, however, would probably confuse Doctrine when the entity is persisted: what should it do with all these instantiated empty objects? It'll cascade-persist them, which is not what you want (if it is, well, you can stop reading ;)). Sacrificing cascade-persisting, an easy solution would be something like this, still in your superclass:
/** #PrePersist */
public function doPrePersist(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) {
$em = $event->getEntityManager();
$this->disableFakeMappings($em);
}
/** #PreUpdate */
public function doPreUpdate(\Doctrine\Common\Persistence\Event\LifecycleEventArgs $event) {
$em = $event->getEntityManager();
$this->disableFakeMappings($em);
}
private function disableFakeMappings(\Doctrine\ORM\EntityManager $em) {
$uow = $em->getUnitOfWork();
$mappings = $em->getClassMetadata()->getAssociationMappings();
foreach ($mappings as $mapping) {
if (!$this->{$mapping['fieldName']} instanceof MyEntity) {
continue;
}
//"reset" faked associations: assume they're fake if the object is not yet handled by Doctrine, which breaks the cascading auto-persist... risk nothing, gain nothing, heh? ;)
if (null === $uow->getEntityState($this->{$mapping['fieldName']}, null)) {
$this->{$mapping['fieldName']} = null;
}
}
}
Hope this helps! :)

Doctrine 2 self-referencing entity won't return the parent id

I've set up a self-referencing entity per the manual here:
http://www.google.com/url?sa=D&q=http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html%23one-to-many-self-referencing
My class is Page (instead of Category, like in the docs). In my entity
class I have a toArray() method that I've implemented that will give
me back the values of my member variables. For those fields that are
associations, I've made sure to grab the associated class object then
grab the id. I'm doing this to populate a form. Here is the code from
my toArray() method in my Page entity as well as my PageService
function to grab a Page object and my Page Controller code that calls
toArray() to populate my form.
http://pastie.org/1686419
As I say in the code comments, when the toArray() method is called in
the Page Controller, all values get populated except for parent id.
page_type is also a ManyToOne association and it gets populated no
problem. Explicitly grabbing the parent id from the Page object
outside of the toArray() method (in the Page Controller) does return
the parent id value. (See code.)
As a side note, I'm using __get() and __set() in my Page entity instead of full blown getters/setters.
I think it is because you are getting caught out by proxies. When you have an association in Doctrine 2, the related objects are not returned directly as objects, but as subclasses which do not fill their properties until a method is called (because of lazy loading to save database queries).
Since you are calling the property directly (with $this->parent->id) without invoking any method the object properties are all empty.
This page http://www.doctrine-project.org/docs/orm/2.0/en/tutorials/getting-started-xml-edition.html#a-first-prototype has a warning about this type of thing in the warning box. Although yours isn't a public property, you are accessing as though it were because that object is of the same class and the same problem is occuring.
Not sure of exactly what is causing your described behavior, but you're probably better anyway to have your toArray() method call getters/setters rather than having toArray() operate directly on the class properties. This will give you consistency so that if you implement custom getters for certain properties, you'll always get back the same result from toArray() and the getter.
A rough example:
<?php
/** #Entity */
class MyEntity {
// ....
/** #Column */
protected $foo;
public function setFoo($val)
{
$this->foo = $val;
}
public function getFoo()
{
return 'hello ' . $this->foo;
}
public function toArray()
{
$fields = array('foo');
$values = array();
foreach($fields as $field) {
$method = 'get' . ucfirst($field);
if (is_callable(array($this, $method)) {
$fields[$field] = $this->$method();
} else {
$fields[$field] = $this->$field;
}
}
return $fields;
}
}
Now you get the same result:
<?php
$e = new MyEntity;
$e->setFoo('world');
$e->getFoo(); // returns 'hello world'
$e->toArray(); // returns array('foo' => 'hello world')