Skip to content

Commit

Permalink
Merge pull request #45 from decentraland/feature/complex-db-queries
Browse files Browse the repository at this point in the history
feat:  Customize the primary key + Complex queries
  • Loading branch information
Juan Cazala authored Feb 15, 2018
2 parents 80ccdc5 + d43dcfd commit 2a53853
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 36 deletions.
72 changes: 47 additions & 25 deletions src/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as utils from './utils'
export class Model {
static tableName = null
static columnNames = []
static primaryKey = 'id'

/**
* DB client to use. We use Postgres by default. Can be changed via Model.useDB('db client')
Expand All @@ -30,49 +31,61 @@ export class Model {
/**
* Return the rows that match the conditions
* @param {object} [conditions] - It returns all rows if empty
* @param {object} [orderBy]
* @param {object} [orderBy] - Object describing the column ordering
* @param {string} [extra] - String appended at the end of the query
* @return {Promise<array>}
*/
static async find(conditions, orderBy) {
return await this.db.select(this.tableName, conditions, orderBy)
static async find(conditions, orderBy, extra) {
return await this.db.select(this.tableName, conditions, orderBy, extra)
}

/**
* Return the row for the supplied id or searches for the condition object
* @param {string|number|object} idOrCond - If the argument is an object it uses it a s conditions. Otherwise it uses it as the searched id.
* Return the row for the supplied primaryKey or condition object
* @param {string|number|object} primaryKeyOrCond - If the argument is an object it uses it for the conditions. Otherwise it'll use it as the searched primaryKey.
* @return {Promise<object>}
*/
static async findOne(idOrCond, orderBy) {
static async findOne(primaryKeyOrCond, orderBy) {
const conditions =
typeof idOrCond === 'object' ? idOrCond : { id: idOrCond }
typeof primaryKeyOrCond === 'object'
? primaryKeyOrCond
: { [this.primaryKey]: primaryKeyOrCond }

return await this.db.selectOne(this.tableName, conditions, orderBy)
}

/**
* Count the rows for the talbe
* Count the rows for the table
* @param {object} [conditions] - It returns all rows if empty
* @param {string} [extra] - String appended at the end of the query
* @return {Promise<integer>}
*/
static async count() {
const result = await this.db.query(
`SELECT COUNT(*) as count
FROM ${this.tableName}`
)

static async count(conditions, extra) {
const result = await this.db.count(this.tableName, conditions, extra)
return result.length ? parseInt(result[0].count, 10) : 0
}

/**
* Forward queries to the db client
* @param {string} queryString
* @param {array} [values]
* @return {Promise<array>} - Array containing the matched rows
*/
static async query(queryString, values) {
return await this.db.query(queryString, values)
}

/**
* Insert the row filtering the Model.columnNames to the Model.tableName table
* @param {object} row
* @return {Promise<object>} the row argument with the inserted id
* @return {Promise<object>} the row argument with the inserted primaryKey
*/
static async insert(row) {
const insertion = await this.db.insert(
this.tableName,
utils.pick(row, this.columnNames)
utils.pick(row, this.columnNames),
this.primaryKey
)
row.id = insertion.rows[0].id
row[this.primaryKey] = insertion.rows[0][this.primaryKey]
return row
}

Expand Down Expand Up @@ -119,11 +132,12 @@ export class Model {
}

/**
* Return the row for the this.attributes id property, fordwards to Model.findOne
* Return the row for the this.attributes primaryKey property, forwards to Model.findOne
* @return {Promise<object>}
*/
async retreive() {
this.attributes = await this.constructor.findOne(this.attributes.id)
const primaryKey = this.attributes[this.constructor.primaryKey]
this.attributes = await this.constructor.findOne(primaryKey)
return this.attributes
}

Expand All @@ -135,18 +149,26 @@ export class Model {
}

/**
* Forwards to Mode.update using this.attributes. If no conditions are supplied, it uses this.attributes.id
* @params {object} [conditions]
* Forwards to Mode.update using this.attributes. If no conditions are supplied, it uses this.attributes[primaryKey]
* @params {object} [conditions={ primaryKey: this.attributes[primaryKey] }]
*/
async update(conditions = { id: this.attributes.id }) {
async update(conditions) {
if (!conditions) {
const primaryKey = this.constructor.primaryKey
conditions = { [primaryKey]: this.attributes[primaryKey] }
}
return await this.constructor.update(this.attributes, conditions)
}

/**
* Forwards to Mode.delete using this.attributes. If no conditions are supplied, it uses this.attributes.id
* @params {object} [conditions]
* Forwards to Mode.delete using this.attributes. If no conditions are supplied, it uses this.attributes[primaryKey]
* @params {object} [conditions={ primaryKey: this.attributes[primaryKey] }]
*/
async delete(conditions = { id: this.attributes.id }) {
async delete(conditions) {
if (!conditions) {
const primaryKey = this.constructor.primaryKey
conditions = { [primaryKey]: this.attributes[primaryKey] }
}
return await this.constructor.delete(conditions)
}

Expand Down
24 changes: 13 additions & 11 deletions src/db/postgres.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const postgres = {
* Forward queries to the pg client. Check {@link https://node-postgres.com/} for more info.
* @param {string} queryString
* @param {array} [values]
* @return {Promise<object>} - Object containing the matched rows
* @return {Promise<array>} - Array containing the matched rows
*/
async query(queryString, values) {
const result = await this.client.query(queryString, values)
Expand All @@ -37,22 +37,23 @@ export const postgres = {
* Counts rows from a query result
* @param {string} tableName
* @param {object} [conditions] - An object describing the WHERE clause. The properties should be the column names and it's values the condition value.
* @param {object} [orderBy] - An object describing the ORDER BY clause. The properties should be the column names and it's values the order value. See {@link postgres#getOrderValues}
* @param {string} [extra] - String appended at the end of the query
* @return {Promise<array>} - Rows
*/
count(tableName, conditions, orderBy) {
return this._query('COUNT', tableName, conditions, orderBy)
count(tableName, conditions, extra) {
return this._query('SELECT COUNT(*)', tableName, conditions, null, extra)
},

/**
* Select rows from a table
* @param {string} tableName
* @param {object} [conditions] - An object describing the WHERE clause. The properties should be the column names and it's values the condition value.
* @param {object} [orderBy] - An object describing the ORDER BY clause. The properties should be the column names and it's values the order value. See {@link postgres#getOrderValues}
* @param {string} [extra] - String appended at the end of the query
* @return {Promise<array>} - Rows
*/
select(tableName, conditions, orderBy) {
return this._query('SELECT', tableName, conditions, orderBy)
select(tableName, conditions, orderBy, extra) {
return this._query('SELECT *', tableName, conditions, orderBy, extra)
},

/**
Expand All @@ -64,7 +65,7 @@ export const postgres = {
*/
async selectOne(tableName, conditions, orderBy) {
const rows = await this._query(
'SELECT',
'SELECT *',
tableName,
conditions,
orderBy,
Expand All @@ -90,7 +91,7 @@ export const postgres = {
}

const result = await this.client.query(
`${method} * FROM ${tableName} ${where} ${order} ${extra}`,
`${method} FROM ${tableName} ${where} ${order} ${extra}`,
values
)

Expand All @@ -102,10 +103,11 @@ export const postgres = {
* @example
* insert('users', { name: 'Name', avatar: 'image.png' }) => INSERT INTO users ("name", "avatar") VALUES ('Name', 'image.png')
* @param {string} tableName
* @param {object} changes - An object describing the insertion. The properties should be the column names and it's values the value to insert
* @param {object} changes - An object describing the insertion. The properties should be the column names and it's values the value to insert
* @param {string} [primaryKey='id'] - Which primary key return upon insertion
* @return {Promise<object>}
*/
async insert(tableName, changes) {
async insert(tableName, changes, primaryKey = 'id') {
if (!changes) {
throw new Error(
`Tried to perform an insert on ${tableName} without any values. Supply a changes object`
Expand All @@ -122,7 +124,7 @@ export const postgres = {
${this.toColumnFields(changes)}
) VALUES(
${this.toValuePlaceholders(changes)}
) RETURNING id`,
) RETURNING ${primaryKey}`,
values
)
},
Expand Down

0 comments on commit 2a53853

Please sign in to comment.