Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Poll - What would you like to see in the future versions of objection? #1302

Closed
koskimas opened this issue Apr 17, 2019 · 63 comments
Closed

Comments

@koskimas
Copy link
Collaborator

koskimas commented Apr 17, 2019

I'm starting to plan the 2.0 release and I'd like to hear the community's opinions what we should add to objection in 2.0 or later down the road.

I'd like to reiterate here that the goal of objection was never to become Eloquent, Django's ORM, Active Record or any other known ORM. Objection's goal is said pretty well at the top of the github README. Objection is a "query builder on steroids" that never get's in the way of SQL, but still attempts to provide tools for working with repetitive stuff and especially relations.

That said, there are many things those ORMs do better than objection and we should try to bring in the features that suit objection's design goal.

SO, I'd like you to post here what you miss from any other tool you've worked with and think objection should really have. Vote the features using 👍 and 👎

Couple of things 2.0 will have:

  • A more usable and consistent static hook system that fixes most if not all issues I've seen people having. The instance hooks will still be around. They have their use cases.
  • More consistent naming of methods and concepts. For example eager is used as a verb, even though it's not, joinRelation implies it joins a single relation. joinEager sounds like joinRelation even though it does a very different thing. These will be something like withGraphFetched, joinRelated, withGraphJoined. etc. The old names will remain as aliases at least until 3.0.

https://github.com/Vincit/objection.js/projects/1

@koskimas koskimas pinned this issue Apr 17, 2019
@tonisostrat
Copy link

tonisostrat commented Apr 17, 2019

I concede the following is quite niche so feel free to shoot it down :)

One thing I've always missed (and due to that I've implemented myself) is functionality similar to JPA's @Embeddable/@Embedded for logical grouping of related fields in a single table.

If this sounds like something you might want to include in objection I would be more than happy to help you brainstorm and figure out the constraints/logic.

@koskimas
Copy link
Collaborator Author

koskimas commented Apr 17, 2019

@tonisostrat I'll try not to shoot down anything or add my own opinions here too early to not affect the discussion.

@heisian
Copy link
Contributor

heisian commented Apr 18, 2019

This may be more on the knex-level, but being able to selectively run seeds would be nice.

Also maybe seeing tools like https://github.com/Vincit/knex-db-manager, https://github.com/Vincit/objection-db-errors, and https://github.com/Vincit/objection-find become a part of the core or have better visibility and code examples in the docs, to help folks write less boiler plate initially.

Perhaps those features could be toggled via feature flags by the developer(s).

@zemccartney
Copy link

+1 to @heisian 's note for at least https://github.com/Vincit/objection-db-errors (only b/c I'm not familiar with the other 2; thanks for sharing those links, good weekend reading :) ). My coworkers and I have started to default to pulling in that plugin on our projects, really appreciate the extra support on error handling. No opinion / suggestion (helpful or otherwise) on where to go with that, but lowering the barrier to entry for that utility somehow I think would be a really solid addition.

(ps Objection is ill! Thanks for all your work here (and for polling for feedback on v2; looking forward to it))

@GreatSchool2
Copy link

Would a @column('columnName') property decorator be appropriate? You'd use it to decorate a property of a model and the default (or base class) columnNameMapper would consult this property when performing the mapping.

e.g.

class Singer extends Model {
    static table = 'singer';

    @column('pseudonym')
    name: string;
}

would yield code models with a name property whose value would be used to populate the pseudonym column when a Singer instance is insert()ed, for example.

@ferk6a
Copy link
Contributor

ferk6a commented Apr 30, 2019

Currently, I work with custom up, down methods in Model classes that I query for all my models to generate a database from scratch, and maintaining the ordering of what should be created first by hand via a simple numbering system.

But I don't ask to implement the management of that at all; I would like however, a way to generate the ordering from relationship graphs, for example.

I also agree with the naming, and would like to see custom behavior for joinRelation also (filters like we have in eager).

@venelin-mihaylov
Copy link

venelin-mihaylov commented May 4, 2019

I would like:

  1. An option to mix WhereInEagerAlgorithm and JoinEagerAlgorithm in a single query.
    Thank you for your efforts

@maxnordlund
Copy link

Support for for await (...) loops would be nice. Especially if it's backed by cursors. This could fallback to limit/offset, even though that doesn't have the same performance (but still ~constant memory pressure).

for await (let person of People.query()) {
  ...
}

You can kinda get this by using knex stream interface, as Readable streams support asyncIterator since v10.0.0. Though when I did this I had to use People.query().build().stream() which feels a bit clunky.

@tonisostrat

This comment has been minimized.

@ferk6a
Copy link
Contributor

ferk6a commented May 5, 2019

Currently, I work with custom up, down methods in Model classes that I query for all my models to generate a database from scratch, and maintaining the ordering of what should be created first by hand via a simple numbering system.
But I don't ask to implement the management of that at all; I would like however, a way to generate the ordering from relationship graphs, for example.
I also agree with the naming, and would like to see custom behavior for joinRelation also (filters like we have in eager).

I don't understand.. this library is based on knex which already handles migrations/changelogs, why don't you utilize it?

In a way, you're right. But I don't like how migrations separate the creation of tables from the Model object itself. It's perhaps a bit more cumbersome, but it's sure convenient!

Regardless, I should probably move to the knex migration way, I think the support for MS SQL is improved now, and it's way better for my own sanity. Thanks!

@devinivy
Copy link
Collaborator

devinivy commented May 6, 2019

I have a handful of thoughts. Given that objection is overall fairly stable and feature-complete, I think the focus should be on using a breaking change (i.e. v2.0) to tighten things up: focus on naming, making any notable alterations to queries, fixing API warts, work on related tooling. You know, things that are worth considering only when there isn't a ton of debt to pay off. It's my impression that there isn't!

I'll just list some ideas, and not so much propose specific solutions, although I'd be happy to talk about solutions if we want to peel-off some additional github issues.

  • Passing arguments to modifiers
    Currently modify() doesn't directly extend knex's behavior because you can't pass them arguments. I think there's an issue for this Named modifiers don't work with optional arguments #1293. There's an additional inconsistency, where there's no interface for providing arguments to modifiers referenced in relation expressions.

  • Relation modifiers coupled to eager algorithm
    Currently relation modifiers are implicitly coupled to whichever eager algorithm being used. A given modifier might make sense for the join algorithm but not the where-in algorithm due to the columns that are referenced. I also find myself liberally sprinkling tableRefFor() in these modifiers in order to avoid column name conflicts and make the modifier more generic for use with different eager algorithms. Really not sure how to approach this, but it's something I run into regularly.

  • Conflict between jsonb and join alias separator
    While there's fantastic support for both jsonb and joins, we currently use : as a separator for both, which sometimes leads to funky workarounds like overriding the separator in defaultEagerOptions.

  • Consider standardizing validators to implement tableMetadata()
    More often than not our validations directly inform table metadata, and I think if there were a blessed way for validators to implement tableMetadata() (and the built-in ajv validator providing this option), then we could remove this rare instance that we rely on some stateful magic. This might also lead to nice things like the often-desired ability to "select all columns but x, y, z."

  • Make relation and unrelation more ergonomic
    Typically relate() and unrelate() chain off of a model instance's $relatedQuery(), which means that one has to obtain a model instance to use these methods. I've used workarounds that look like this. There are a few different directions to explore to alleviate this, but I think there could be some really interesting options to make relatedQuery() more active, and not necessarily relegated solely to subqueries.

    // Fetch
    await Person.query().findById(1)
        .relatedQuery('pets').where('name', 'Fluffy');
    
    // Unrelate
    await Person.query().findById(1)
        .relatedQuery('pets').findById(42)
        .unrelate();
  • Consider folding objection-db-errors into core
    I can only echo my colleague: Poll - What would you like to see in the future versions of objection? #1302 (comment). We use this all the time and it's a better error-handling experience!

  • Focus on tooling and plugins
    This is partly on the community too, but I would love to see more quality plugins appear, especially for modeling common application features. @faustbrian has done a neat job with this in the Laravel community. Imagine plugins like "taggable", "commentable", "followable", "likeable", etc. Objection v2.0 may be able to help by considering how validation works in some of these complex cases, i.e. when a community plugin needs to play nice with existing models that may use one of several validators (we use a joi validator rather than ajv, for example). On the topic of tooling, generating knex migrations from objection models/validators is also of interest.

@koskimas
Copy link
Collaborator Author

koskimas commented May 18, 2019

@devinivy Thanks! You have alot of great ideas there.

Passing arguments to modifiers

This will be fixed at least for the modify function's part. I'd like allow arguments for modifiers in relation expressions too. Once modify is fixed for named modifiers, you can at least do this:

class Pet extends Model {
  static get modifiers() {
    return {
      onlySpecies: (query, species) => query.where('species', species)
    }
  }
}

Person
  .query()
  .eager('[pets, children.pets(onlyDogs)]', {
    onlyDogs: query => query.modify('onlySpecies', 'dogs')
  })

That's still pretty verbose, but it allows you to "bind" arguments to existing modifiers. What do you think? Could that be enough? Another option would be a mechanism like this:

Person
  .query()
  .eager('[pets, children.pets(onlyDogs)]')
  .bindModifier('onlySpecies', 'onlyDogs', 'dogs')

but is that really better? It intruduces yet another concept.

We could also add helpers like these:

Person
  .query()
  .eager('[pets, children.pets(onlyDogs)]', {
    onlyDogs: bindModifier('onlySpecies', 'dogs')
  })

@devinivy Do you have any other ideas for this?

I've also thought about making modifiers query-wide, so that they could be used with joinRelation etc. So instead of this

Person
  .query()
  .eager('[pets, children.pets(onlyDogs)]', {
    onlyDogs: query => query.modify('onlySpecies', 'dogs')
  })

you'd say this:

Person
  .query()
  .eager('[pets, children.pets(onlyDogs)]'),
  .modifiers({
    onlyDogs: query => query.modify('onlySpecies', 'dogs')
  })

Relation modifiers coupled to eager algorithm

This is a tricky one. I also hate that this happens. That's one of the reasons why I'll deprecate the defaultEagerAlgorithm and eagerAlgorithm() proerties and methods. You need to explicitly use withGraphFetched or withGraphJoined to make it clearer that you are using a completely different algorithm.

Fixing this would again require objection to duplicate the knex API which I've been trying to avoid. We would need to wrap all where*, groupBy, join* etc. methods, parse the inputs and basically build an AST tree to be able to figure out what the user means when he/she uses column name in a query. Is it from a joined table? Is it from a parent query in a subquery situation? The MVP solution would be to wrap all where methods and automatically add ${tableRefFor(modelClass)}.column for each column that doesn't have an explicit table, but that would already require objection to duplicate and test the where API of knex.

If you have any good ideas, I'm all ears.

One thing we could do is add a Model.ref function that makes it cleaner to use tableRefFor. Model actually already has that, but it's not public yet. It allows you to do this:

class Person extends Model {
  static modifiers = {
    boys: query => {
      const ref = Person.ref
      query.where(ref('gender'), 'male').where(ref('age'), '<', 18)
    }
  }
}

Conflict between jsonb and join alias separator

This is another complete design flaw I'll be addressing. We can use -> as a separator for either the json references or the joinEager aliases. Which one would you prefer?

Consider standardizing validators to implement tableMetadata()

The Validator API is bad. This is a good idea, but overriding validators doesn't seem to be that common, so I'll probably leave this for later (3.0?) unless you'd like to take a stab at this problem @devinivy? You guys have probably written the most comprehensive custom validator in schwifty.

Make relation and unrelation more ergonomic

// Fetch
await Person.query().findById(1)
    .relatedQuery('pets').where('name', 'Fluffy');

// Unrelate
await Person.query().findById(1)
    .relatedQuery('pets').findById(42)
    .unrelate();

I agree with the title, but not with either of these examples. They would be confusing. The .relatedQuery('pets') in those calls completely changes the query. a where call before that affects the parent query, while where after it affects the "child" query. I'd rather just make creating model instances from just a relation property (primary key most of the time) easier. Something like this:

await Person.fromId(1).$relatedQuery('pets').where('name', 'Fluffy');
await Person.fromId(1).$relatedQuery('pets').findById(42).unrelate()

Consider folding objection-db-errors into core

Will be done. This was also brought up by @heisian

Focus on tooling and plugins

This is pretty vague. I'd like to see more plugins and 3rd party tools, but I don't know how to help there. The Validator interface is one thing, but I don't think that is the biggest limiting factor right now.

@phormio
Copy link
Contributor

phormio commented May 25, 2019

It's only a small feature, but how about a method called $loadRelatedUnlessLoaded, which is like Model#$loadRelated but doesn't load any data which is already in the object.

To see why this is useful, consider the following function.

async function f(user) {
  const u = user.$clone();
  await u.$loadRelatedUnlessLoaded('favouriteBook.author');
  // Do something with u.favouriteBook.author
}

I want to be able to pass a User object to f without having to care about which relations have been loaded. But if I call $loadRelated in f, then the application is doing unnecessary work if the data has already been loaded.

$loadRelatedUnlessLoaded may not be a good name; feel free to think of a better one.

@koskimas
Copy link
Collaborator Author

@phormio That's a good idea 👍 That has been requested before.

$loadRelated will be replaced by $fetchGraph in 2.0 (the old name will remain at least until 3.0). $fetchGraph will take an options object as the second argument. We can add an option for this. Something like

await u.$fetchGraph('favoriteBook.author', { reloadExisting: false })

@devinivy
Copy link
Collaborator

devinivy commented May 27, 2019

@koskimas thanks for the thoughtful response 🙏

Passing arguments to modifiers

I like the example you gave, and I think that's totally fine! I like the bindModifiers() helper too :) Not a huge fan of the query-wide eager modifiers but also not so hard against it, especially if the current interface remains in existence.

Relation modifiers coupled to eager algorithm

I agree, I think this is a really hard problem to solve and it's not easy to justify the effort. Going down that path would generate a lot of questions that I don't have answers to. I would be afraid of making the generated queries harder to control and more opaque. Making tableRefFor() more friendly sounds like it's low-hanging fruit, and would be a nice improvement. I would be happy to contribute a section to the docs that addresses this—I get the sense that a lot of people run into this issue then avoid it rather than solve it since tableRefFor() doesn't have a lot of visibility in the docs.

Conflict between jsonb and join alias separator

Would -> make a better separator for jsonb or joinEager aliases? Hmm... that's hard for me to say. I struggle with the idea of using that as a separator since it's so close in meaning to postgres's own jsonb operator. If I had to choose one, I think I would suggest using it for jsonb. Is ~> too weird? I don't have a full understanding of knex's limitations here, but another option might be to see what it would take to loosen those limitations in knex.

Consider standardizing validators to implement tableMetadata()

I'll look into it!

Make relation and unrelation more ergonomic

Fair! I don't think these two options are mutually exclusive, and the example you gave is totally straightforward / would be a nice improvement. At the same time it does feel a little "off" to me that in order to build some queries I need to create a model instance with some fields filled-in. It works, but it doesn't seem super objection-y (obviously this is just a matter of opinion—whatever you decide is "objection-y" by definition!) . I still think an API similar to what I proposed would be in the spirit of objection as it relates to query laziness and composability. Does it seem more reasonable if it's re-written in steps?

/* select * from users where type = 'musician' */
const musicians = User.query().where('type', 'musician');

/* select * from animals where
     type = 'dog' and
     "ownerId" in (select "ownerId" from users where type = 'musician') */
const musiciansDogs = musicians.relatedQuery('pets').where('type', 'dog');

/* update animals set "ownerId" = null where
     type = 'dog' and
     "ownerId" in (select "ownerId" from users where type = 'musician') */
const unrelateMusiciansFromDogs = musiciansDogs.unrelate();


// An alternative idea, just for fun.

const musicians = User.query().where('type', 'musician');
const musiciansDogs = User.relatedQuery('pets').per(musicians).where('type', 'dog');
const unrelateMusiciansFromDogs = musiciansDogs.unrelate();

Consider folding objection-db-errors into core

👍

Focus on tooling and plugins

Agree I was pretty vague there. I'm mostly trying to think of things that could fragment efforts to write generic tooling for objection, so that they can be squashed proactively in v2. Maybe there's not a lot in the way!

@chalcedonyt
Copy link

It's only a small feature, but how about a method called $loadRelatedUnlessLoaded, which is like Model#$loadRelated but doesn't load any data which is already in the object.

To see why this is useful, consider the following function.

async function f(user) {
  const u = user.$clone();
  await u.$loadRelatedUnlessLoaded('favouriteBook.author');
  // Do something with u.favouriteBook.author
}

I want to be able to pass a User object to f without having to care about which relations have been loaded. But if I call $loadRelated in f, then the application is doing unnecessary work if the data has already been loaded.

$loadRelatedUnlessLoaded may not be a good name; feel free to think of a better one.

This would be such a useful feature to have and reduce many unneeded queries!

@koskimas
Copy link
Collaborator Author

@caiquecastro That's a good idea. @phormio Could you create a separate issue about that feature? That's something we can add after 2.0 since it's not breaking.

@phormio

This comment has been minimized.

@vladshcherbin
Copy link
Contributor

A small list I can think about to upvote some suggestions:

  • add db-errors to core. Super useful library a lot of users will appreciate
  • move to using native stuff, e.g. Bluebird -> Promise, lodash -> native functions, etc. This is already in todo list
  • drop node 6 support, use >= 8. Seems to be done from todo list
  • validation feels weird. I had an issue with nested models (Nested model validation #478), moved to validation outside of objection. Maybe something has changed, but this was an issue. I also use another validation library (yup)

This is probably all I can think about, objection gives a joy using database and I'll recommend using it instead of other orms any day. Thank you for it ❤️

@vladshcherbin
Copy link
Contributor

vladshcherbin commented Jun 16, 2019

What about setting useLimitInFirst to true by default ?
Docs say: Defaults to false for legacy reasons.

Any downsides/cons? 🤔

@koskimas
Copy link
Collaborator Author

@vladshcherbin That's a good idea! I'll add that to the 2.0 TODO list.

@hirikarate
Copy link

I am expecting model instance's ability to detect changes, and provides a save() method which internally calls patch() to write changed properties to database.

I have been using ObjectionJS for Anemic model and it's going well. Now I am implementing Domain-driven model following "Backed By a State Object" pattern suggested by Vaughn Vernon - author of popular DDD books. I am too lazy to switch to Sequelize ORM in one microservice while the rest are still using Objection.

@koskimas
Copy link
Collaborator Author

@hirikarate Sorry, but that will never be added to objection. If you want an ORM like that, objection is probably a very bad match for you.

@AaronNGray
Copy link

Hi, I am going to need the following for a project :-
Objection / knex / PostgreSQL support for :-
- Arrays https://www.postgresql.org/docs/9.5/arrays.html
- Types https://www.postgresql.org/docs/9.5/rowtypes.html
- ROW Security Policies https://www.postgresql.org/docs/9.5/ddl-rowsecurity.html https://www.postgresql.org/docs/9.5/sql-createpolicy.html

That's my wish list ;)

@Vincit Vincit deleted a comment from AaronNGray Aug 1, 2019
@dustinnewman
Copy link

Remove Bluebird dependency in favor of native Promise! If you check the composition of objection, Bluebird is not a trivial contributor! https://bundlephobia.com/[email protected]

@AaronNGray

This comment has been minimized.

@koskimas

This comment has been minimized.

@Vincit Vincit deleted a comment from johncmunson Nov 9, 2019
@koskimas

This comment has been minimized.

@ferk6a
Copy link
Contributor

ferk6a commented Dec 10, 2019

I have a few things now:

  • Have a way to marshall types, in and out: I want to use js-joda, and big.js. It would be nice to be able to transform values before going to the database driver, and when it comes back (perhaps it could even be advanced to the point of issuing automatic cast(as string) mappings, but that would too good).

Why? Because drivers suck at this. Tedious/MSSQL doesn't have a solution for this. Postgres has a parsing solution, but no encoding solution. SQLite has nothing. Dealing with dates and decimal values is error-prone without some support for this. Typeorm has a transformer option, but supports poorly complex types like js-joda's (anything with a recursive object, really).

  • Filter inside relations. Now this plagues all my codebase. This is the reason I use joinEager, because I can where child values, and if there aren't any child values present, the parent value doesn't show up, this is awesome, but is not very versatile.

  • More support for the data mapper pattern. We could get rid of extends Model. We could get rid of calling .query() on the model itself, and having methods at all in the Model. What we have currently is much more like ActiveRecord, and when we need to use a transaction, we need to query(trx) (and never forget it). Our multitenancy pattern is also weak in this regard, instead we could issue a query builder with a model, not a query builder from a model, making it more composable.

  • Support for nested transactions with soft transactions (maybe?), support for multiple transactions running at the same time with a connection pool. Drivers suck at this too. But this can be more easily circumvented (by having slower software).

@koskimas
Copy link
Collaborator Author

koskimas commented Dec 11, 2019

Have a way to marshall types, in and out: I want to use js-joda, and big.js. It would be nice to be able to transform values before going to the database driver, and when it comes back (perhaps it could even be advanced to the point of issuing automatic cast(as string) mappings, but that would too good).

You can already do this. See the hook docs.

Filter inside relations. Now this plagues all my codebase. This is the reason I use joinEager, because I can where child values, and if there aren't any child values present, the parent value doesn't show up, this is awesome, but is not very versatile.

There is no way to do this with the normal eager/withGraphFetched. It's impossible. You need to use joinEager/withGraphJoined or subqueries. In addition to the examples behind the link, you can pass those relation subqueries virtually everywhere.

More support for the data mapper pattern.

Not going to happen 😄 All the issues listed here can easily be solved in other "objectiony" ways. Objection is not any other ORM, nor does it try to mimic them just because they are popular.

Support for nested transactions with soft transactions (maybe?), support for multiple transactions running at the same time with a connection pool. Drivers suck at this too. But this can be more easily circumvented (by having slower software).

Already supported. Simply use knex's way to nesting transactions or trx.raw to create a savepoint. I use them all the time.

Answers by @elhigu (an active knex maintainer)

Postgres has a parsing solution, but no encoding solution.

I just wanted to point out that pg driver actually does have encoding solution for incoming data that can be overridden. Most simple way to use it is to add function .toPostgres to your object for serialization. https://github.com/brianc/node-postgres/blob/master/lib/utils.js#L45

Anyways it is true that there is no common way for all databases to do that and it could probably be implemented as a objection.js plugin since there are already lifecycle methods where mappers could be called.

What we have currently is much more like ActiveRecord

“The active record pattern is an approach to accessing data in a database. A database table or view is wrapped into a class. Thus, an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database. When an object is updated the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view.”

Objection is a bit like active record in a sense that class presents a table and instance a row, but objection does not create or update object back to database that way (and it has been design decision from the start to prevent having too much magic going on behind the scenes with caching and tracking changes in model attributes).

Support for nested transactions with soft transactions (maybe?), support for multiple transactions running at the same time with a connection pool.

Knex supports nested transactions through single connection and they have been implemented by using save points. So objection supports them too. Multiple transactions in same pool work also just fine.

We could get rid of calling .query() on the model itself, and having methods at all in the Model. What we have currently is much more like ActiveRecord, and when we need to use a transaction, we need to query(trx) (and never forget it).

If you don't bind base Model to any knex instance, then you can use models in a multitenant way. In my own projects I never use global binding to single knex instance, but always give knex to .query(knex) when doing queries. That way you can also easily decide if you want to choose to send query to some read replicas instead of master. That also prevents the errors where you forget to pass trx parameter to queries which should go to specific transaction (if you await just .query() it will throw an error).

@ferk6a

This comment has been minimized.

@ferk6a

This comment has been minimized.

@kedarmoghe
Copy link

kedarmoghe commented Jan 27, 2020

  • official built-in option for keyset pagination

@asergey87
Copy link

I would like to see in next version of Objection is conditinal clauses like in Laravel Eloquent https://laravel.com/docs/7.x/queries#conditional-clauses

This feature will avoid to use if/switch expressions in light queries, like:

const searchBooks = (year, searchTerm) => Books
.query()
.when(searchTerm.length, qb => qb.where('title', 'like', `%${searchTerm}%`)
.when(year !== undefined, qb => qb.where('year', '>=', year)
.orderBy('id', 'DESC')
.page(0, 100)

@ameinhardt
Copy link

ameinhardt commented Apr 13, 2020

@asergey87 : you could already use a custom QueryBuilder that adds conditions like:

export default class ExtendedQueryBuilder extends QueryBuilder {
  when(test, fnif, fnelse) {
    return test ? fnif(this) : (fnelse ? fnelse(this) : this);
  }

  whenClient(name, ...args) {
    const not = name[0] === '!';
    if(not) {
      name = name.slice(1);
    }
    return this.if(!!(not ^ (this.knex().client.config.client === name)), ...args);
  }
})

@marziply
Copy link

marziply commented Jun 7, 2020

It would be nice to have the option to leverage the ES6 named export syntax, now that Node 14 supports ESM without the experimental flag.

This:

import { Model, QueryBuilder } from 'objection'

is much nicer than:

import objection from 'objection'

const { Model, QueryBuilder } = objection

@vvo
Copy link

vvo commented Jun 9, 2020

Hey just chiming in, I saw some talks about unrelate and wanted to give this insight too:

  • with a many to many relation
  • I used user.$relatedQuery("teams").relate(teamId); and it felt natural
  • Then I tried user.$relatedQuery("teams").unrelate(teamId); and it failed telling me to use some where clause. Which felt weird because I thought it would have just worked like relate

Maybe there are challenges in making this more symmetrical, if so we could explain it in the doc if that's something we can't change.

Thanks!

@vladshcherbin

This comment has been minimized.

@koskimas
Copy link
Collaborator Author

koskimas commented Jun 9, 2020

@vvo Unrelate follows the same logic as delete and it is in fact a delete in case of many to many relationships. You can do stuff like this:

user.$relatedQuery('teams').unrelate().where('teams.members', '<', 10)

or use whatever SQL query you want. You wouldn't be able to do that if we changed the API to take an id.

We could add a unrelateById method?

@vladshcherbin
You can already do that using two queries.

await user.$relatedQuery('teams').unrelate()
await user.$relatedQuery('teams').relate(newIds)

but off course that will be slow if each user can have thousands of teams.

upsertGraph is exactly what you want.

await User.query().upsertGraph(
  {
    id: user.id,
    teams: newIds.map(id => ({ id })),
  },
  { relate: true, unrelate: true }
)

@vvo

This comment has been minimized.

@koskimas

This comment has been minimized.

@vladshcherbin

This comment has been minimized.

@vvo

This comment has been minimized.

@devinivy

This comment has been minimized.

@koskimas

This comment has been minimized.

@Kinrany
Copy link

Kinrany commented Aug 5, 2020

Not a specific request, but after using Objection for a couple months I feel like the API offers too many ways to do the same thing. For example, I'm currently trying to figure out the difference between fetchGraph and withGraphFetched, and why would I choose one over the other.

If Objection intentionally provides several different approaches to ORM, it would be nice to make this explicit. Otherwise I'd very much like to see as many methods as possible deprecated.

@capaj
Copy link
Contributor

capaj commented Aug 5, 2020

@Kinrany I disagree. The difference between these two is pretty clear.

  • fetchGraph is used on an instance of a model. So you'd use it when you have fetched something from a table, but for some reason you need to fetch additional data via relations.
  • withGraphFetched is on QueryBuilder. You use that if you know upfront that you will need data from those relations.

@cpgo
Copy link

cpgo commented Aug 13, 2020

Maybe this is more on knex level but I would love to see something like the Sandbox Adapter from ecto (https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html).

This allows for pretty painless and fast integration tests.

@koskimas koskimas unpinned this issue Sep 17, 2020
@kapouer
Copy link
Contributor

kapouer commented Jun 14, 2021

q.where(ref('data:prop.list').arrayLength())

@devilme300
Copy link

@koskimas
I would like to see a section which run micro query directly from query param of API calls.
I have created a patter for micro query -> then I need to convert this patter to raw query and finally use objection object .whereRaw() to execute the same.

@capaj
Copy link
Contributor

capaj commented Jul 14, 2021

Change the api for $fetchGraph. At the moment it accepts an option {skipFetched: true}.
I would very much prefer if it instead by default behaved like when {skipFetched: true} and to force refetch of related items, you would use a different method-could be something like
$fetchGraphForce('user')
To me seeing a code with a different method name is more explicit than the opt object.
IMHO in general usecase where you use objection on an API, you typically don't need to always refetch relations.

@devilme300
Copy link

Change the api for $fetchGraph. At the moment it accepts an option {skipFetched: true}.
I would very much prefer if it instead by default behaved like when {skipFetched: true} and to force refetch of related items, you would use a different method-could be something like
$fetchGraphForce('user')
To me seeing a code with a different method name is more explicit than the opt object.
IMHO in general usecase where you use objection on an API, you typically don't need to always refetch.

Thank you… will surely look into this… 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests