keystonejs / keystone

The superpowered headless CMS for Node.js — built with GraphQL and React
https://keystonejs.com
MIT License
9.28k stars 1.16k forks source link

list adapter model is null #1360

Closed Olya-Yer closed 5 years ago

Olya-Yer commented 5 years ago

I tried to query list items

var CustomField = keystone.getListByKey('Field').adapter

CustomField.model.find({}).then((err, result) => {
  console.log(result);
})

but adapter's model field is null

Is there a way to query the model other than getListByKey ?

this is the console.log of the adapter is this an expected behavior ?

CustomField MongooseListAdapter {
  key: 'Field',
  parentAdapter:
   MongooseAdapter {
     config: {},
     name: 'mongoose',
     listAdapters: { Field: [Circular], Object: [MongooseListAdapter] },
     mongoose:
      Mongoose {
        connections: [Array],
        models: {},
        modelSchemas: {},
        options: [Object],
        _pluralize: [Function: pluralize],
        Schema: [Function],
        plugins: [Array] },
     listAdapterClass: undefined },
  fieldAdapters:
   [ MongoTextInterface {
       fieldName: 'Text',
       path: 'name',
       listAdapter: [Circular],
       config: {},
       getListByKey: [Function],
       dbPath: 'name',
       isRequired: undefined,
       isUnique: undefined },
     MongoCheckboxInterface {
       fieldName: 'Checkbox',
       path: 'indexed',
       listAdapter: [Circular],
       config: {},
       getListByKey: [Function],
       dbPath: 'indexed',
       isRequired: undefined,
       isUnique: undefined },
     MongoSelectInterface {
       fieldName: 'Select',
       path: 'valueType',
       listAdapter: [Circular],
       config: [Object],
       getListByKey: [Function],
       dbPath: 'valueType',
       isRequired: undefined,
       isUnique: undefined },
     MongoRelationshipInterface {
       fieldName: 'Relationship',
       path: 'refersTo',
       listAdapter: [Circular],
       config: [Object],
       getListByKey: [Function],
       dbPath: 'refersTo',
       isRequired: undefined,
       isUnique: undefined,
       refListKey: 'Object',
       refFieldPath: undefined,
       isRelationship: true },
     MongoCheckboxInterface {
       fieldName: 'Checkbox',
       path: 'many',
       listAdapter: [Circular],
       config: [Object],
       getListByKey: [Function],
       dbPath: 'many',
       isRequired: undefined,
       isUnique: undefined } ],
  fieldAdaptersByPath:
   { name:
      MongoTextInterface {
        fieldName: 'Text',
        path: 'name',
        listAdapter: [Circular],
        config: {},
        getListByKey: [Function],
        dbPath: 'name',
        isRequired: undefined,
        isUnique: undefined },
     indexed:
      MongoCheckboxInterface {
        fieldName: 'Checkbox',
        path: 'indexed',
        listAdapter: [Circular],
        config: {},
        getListByKey: [Function],
        dbPath: 'indexed',
        isRequired: undefined,
        isUnique: undefined },
     valueType:
      MongoSelectInterface {
        fieldName: 'Select',
        path: 'valueType',
        listAdapter: [Circular],
        config: [Object],
        getListByKey: [Function],
        dbPath: 'valueType',
        isRequired: undefined,
        isUnique: undefined },
     refersTo:
      MongoRelationshipInterface {
        fieldName: 'Relationship',
        path: 'refersTo',
        listAdapter: [Circular],
        config: [Object],
        getListByKey: [Function],
        dbPath: 'refersTo',
        isRequired: undefined,
        isUnique: undefined,
        refListKey: 'Object',
        refFieldPath: undefined,
        isRelationship: true },
     many:
      MongoCheckboxInterface {
        fieldName: 'Checkbox',
        path: 'many',
        listAdapter: [Circular],
        config: [Object],
        getListByKey: [Function],
        dbPath: 'many',
        isRequired: undefined,
        isUnique: undefined } },
  config: {},
  preSaveHooks: [],
  postReadHooks: [ [Function] ],
  getListAdapterByKey: [Function: bound getListAdapterByKey],
  mongoose:
   Mongoose {
     connections: [ [NativeConnection] ],
     models: {},
     modelSchemas: {},
     options: { pluralization: true },
     _pluralize: [Function: pluralize],
     Schema:
      { [Function]
        reserved: [Object],
        Types: [Object],
        ObjectId: [Function],
        base: [Circular] },
     plugins: [ [Array], [Array], [Array], [Array] ] },
  configureMongooseSchema: undefined,
  schema:
   Schema {
     base:
      Mongoose {
        connections: [Array],
        models: {},
        modelSchemas: {},
        options: [Object],
        _pluralize: [Function: pluralize],
        Schema: [Function],
        plugins: [Array] },
     obj: {},
     paths:
      { _id: [ObjectId],
        name: [SchemaString],
        indexed: [SchemaBoolean],
        valueType: [SchemaString],
        refersTo: [ObjectId],
        many: [SchemaBoolean] },
     aliases: {},
     subpaths: {},
     virtuals: {},
     singleNestedPaths: {},
     nested: {},
     inherits: {},
     callQueue: [],
     _indexes: [],
     methods: {},
     methodOptions: {},
     statics: {},
     tree:
      { _id: [Object],
        name: [Object],
        indexed: [Object],
        valueType: [Object],
        refersTo: [Object],
        many: [Object] },
     query: {},
     childSchemas: [],
     plugins: [],
     '$id': 1,
     s: { hooks: [Kareem] },
     _userProvidedOptions: { autoCreate: true, autoIndex: false },
     options:
      { autoCreate: true,
        autoIndex: false,
        typeKey: 'type',
        id: true,
        noVirtualId: false,
        _id: true,
        noId: false,
        validateBeforeSave: true,
        read: null,
        shardKey: null,
        minimize: true,
        discriminatorKey: '__t',
        versionKey: '__v',
        capped: false,
        bufferCommands: true,
        strict: true } },
  model: null,
queryBuilder: [AsyncFunction] }
jesstelford commented 5 years ago

Great question! Accessing the model directly is discouraged as it will tie your code directly to the database you're using.

Until we have programatic querying sorted out (#1275), we don't have a good answer to achieve what you're after, but here are 2 ways you could do it:

  1. Call the adapter's internal methods: keystone.getListByKey('Field').adapter._findAll(). This has a high risk of breaking in a non-major version release as they're meant to be private methods.
  2. Do a GraphQL request back to the admin server:
    const { request } = require('graphql-request');
    const { allUsers } = await request('http://localhost:3000/admin/api', `{
      allUsers {
        id
        name
      }
    }`);
timleslie commented 5 years ago

Just to follow up on this quickly, the list adapter .model will be null until you perform a keystone.connect(), at which point the database connection is established and the models become available. This is the expected behaviour. If you're seeing a null model after performing a .connect() please let us know, as this would be a bug.

As @jesstelford pointed out above, in general you're better off using the graphQL or adapter methods to interact with your backend. You should probably only drop down to the .model if you're doing something really specific which the other APIs don't support.

If you do use the .model then a) your code won't work if you switch to a different database adapter and b) you might bypass certain rules such as validation, access control, etc, which keystone is handling.

If you're OK with these considerations then by all means use .model directly. The general principle here is that Keystone will try to provide APIs for common operations (CRUD) while not preventing you from directly interacting with your database if that's what you need.