Mobx-state-tree: Question: Is it possible to back a reference attribute by a different attribute?

Created on 16 Jan 2018  Â·  7Comments  Â·  Source: mobxjs/mobx-state-tree

My objects come in with their foreign keys suffixed with _id by default (Rails). For example:

{
  "id": "fh123h1j13",
  "title": "Buy groceries",
  "list_id": "11j231h123h1j",
  "done": false
}

Is it possible for my model to keep the attribute list_id as it's normal type, and then have another list attribute which is the auto-resolving reference type? I'd like my result to be something like:

export const Todo = types.model('Todo', {
  id: types.identifier,
  title: types.string,
  list_id: types.string,
  list: types.reference(List), // This should use the value from `list_id`
  done: types.boolean,
})

I imagine I'll need to write a view for this, but I figured I'd see if there was a recommended approach.

Most helpful comment

Do you mean something like this would be as fast as the option 1?

  1. Computed view using resolve identifier
export const Todo = types.model('Todo')
.props({
  id: types.identifier,
  title: types.string,
  list_id: types.string,
  done: types.boolean,
})
.views(self => ({
  get list() { return resolveIdentifier(List, getParent(self), self.list_id) }, 
}))

All 7 comments

using views is the way to go indeed. Note that the resolve logic is exposed separately from the MST package, so you can still use that (see resolveIdentifier etc)

I want to take the opportunity to ask a question I had for a while regarding this scenario.

Which of these approaches is better, performance-wise?

1: Direct reference

export const Todo = types.model('Todo', {
  id: types.identifier,
  title: types.string,
  list: types.reference(List), // This stores the value from `list_id`
  done: types.boolean,
})

2: Indirect reference

export const Todo = types.model('Todo', {
  id: types.identifier,
  title: types.string,
  list_id: types.string,
  list: types.reference(List, {
    get: (identifier, parent) => getParent(parent).lists.get(parent.list_id),
    set: value => value // This may store the list_id value for example, but it's not really used.
  }),
  done: types.boolean,
})

3: Computed view

export const Todo = types.model('Todo')
.props({
  id: types.identifier,
  title: types.string,
  list_id: types.string,
  done: types.boolean,
})
.views(self => ({
  get list() { return getParent(self).lists.get(self.list_id) }, 
}))

In this case, the indirect reference looks unnecessary, but that pattern is useful in more complex scenarios where some logic needs to be added to the get function.

Anyway, there are always many options to design our Models, so I'd love to know if there is any performance drawback with any of these approaches.

  • 1, Is the fasted; it is computed, and performance it's look ups on a map
    that lives at the root of a tree

    1. Doesn't use any caching (atm), so this one is theoretically the

      slowest. However, if you use it in a reactive context, it will still

      re-evaluate only if something in the lookup path changes


    1. Same as 2, but explicitly marked computed

  • Note that 3 could be faster by using the map lookup implementation (by
    using resolveIdentifier). The advantage of that is that the identifier
    doesn't have to re-resolve if you moving objects around in the tree, as
    long as the instance stays alive (the lookup entry in the root stays the
    same)

Op di 16 jan. 2018 om 10:18 schreef Luis Herranz notifications@github.com:

I want to take the opportunity to ask a question I had for a while
regarding this scenario.

Which of these approaches is better, performance-wise?

1: Direct reference

export const Todo = types.model('Todo', {
id: types.identifier,
title: types.string

,
list: types.reference(List), // This stores the value from list_id
done: types.boolean,
})

2: Indirect reference

export const Todo = types.model('Todo', {
id: types.identifier,
title: types.string,
list_id: types.string

,
list: types.reference(List, {
get: (identifier, parent) => getParent(parent).lists.get(parent.list_id),
set: value => value // This may store the list_id value for example, but it's not really used.
}),
done: types.boolean,
})

3: Computed view

export const Todo = types.model('Todo')
.props({
id: types.identifier,
title: types.string,
list_id: types.string,
done: types.boolean,
})
.views(self => ({
get list() { return getParent(self).lists.get(self.list_id) },
}))

In this case, the indirect reference looks unnecessary, but that pattern
is useful in more complex scenarios where some logic needs to be added to
the get function.

Anyway, there are always many options to design our Models, so I'd love to
know if there is any performance drawback with any of these approaches.

—
You are receiving this because you commented.

Reply to this email directly, view it on GitHub
https://github.com/mobxjs/mobx-state-tree/issues/603#issuecomment-357899417,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABvGhCor5UKWA6wusLA7FEa3KfsymDahks5tLGmDgaJpZM4RfK9L
.

Do you mean something like this would be as fast as the option 1?

  1. Computed view using resolve identifier
export const Todo = types.model('Todo')
.props({
  id: types.identifier,
  title: types.string,
  list_id: types.string,
  done: types.boolean,
})
.views(self => ({
  get list() { return resolveIdentifier(List, getParent(self), self.list_id) }, 
}))

yes, that is pretty much what types.reference does :)

Awesome. Thanks @mweststrate :)

Well, this thread couldn't have played out any better 😄 thanks for the discussion, folks!

Was this page helpful?
0 / 5 - 0 ratings