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

Reconfigure Collections #3

Draft
wants to merge 12 commits into
base: dev
Choose a base branch
from
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Classy MovieDB API Wrapper
## Classes
### Generic Class Methods
| Method Tags | Method Name & Params |
|:---------------------------------:|:------------------------ |
| | `constructor({data})` |
| | `assignData({data})` |
| | `toJSON( )` |
| <kbd>STATIC</kbd> | `parseFromAPI({data})` |
| <kbd>STATIC</kbd> | `parseFromDB({data})` |
| <kbd>STATIC</kbd> | `matches(...instances)` |
| <kbd>STATIC</kbd> | `combine(...instances)` |
| <kbd>PRIVATE</kbd> <kbd>GET</kbd> | `sharedMetadata( )` |

#### `constructor(data)`
This method is a typical JavaScript class instance constructor function.
It can take in a well-formatted, JSON-compatible data object, which is consumed by `assignData`.
In this case, the class returns a new class instance from the data.

The constructor can also take in an instance of its own class as its data.
When this happens, the constructor does nothing else and returns the input instance instead of creating a new one.

#### `assignDefaults( )`
This method is ran from within the constructor.
It sets default values to the instance's various attributes.
Valid default values for class instances include:
- `undefined`
- empty `Array` instances
- empty `List` instances
- key-value data `Objects`, containing further valid default values

#### `assignData({data})`
This method is ran from within the constructor, or called upon when there is new data.
It takes a well-formatted, JSON-compatible data object and applies it to the class instance and its various attributes.
A well-formatted, JSON-compatible data object is output by the `toJSON`, `parseFromAPI`, and `parseFromDB` static methods.

#### <kbd>PRIVATE</kbd> <kbd>GET</kbd> `sharedMetadata`
Hidden data that can be used by child elements.

#### <kbd>STATIC</kbd> `toJSON( )`
Consumes a class instance, and returns a well-formatted, JSON-compatible data object.

#### <kbd>STATIC</kbd> `parseFromApi({data})`
Consumes API data, and returns a well-formatted, JSON-compatible data object.

#### <kbd>STATIC</kbd> `parseFromDb({data})`
Consumes DB data, and returns a well-formatted, JSON-compatible data object.

#### <kbd>STATIC</kbd> `matches(...instances)`
Checks if one or more given instances are matching.
Returns `true` if all items match.
Returns `false` if any of the items do not match, or if there are no items.
Also returns `false` if any the items are not an instance of this class.

#### <kbd>STATIC</kbd> `combine(...instances)`
Combines one or more matching instances into a singular item.
First, the method leverages the `matches` method to see if all the given items match.
The method can then returns a new instance of the combined elements if they all match.
Othewise, it returns `false`.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"type": "module",
"scripts": {
"start": "node ./source/main.mjs",
"test": "node ./source/test.mjs"
"test": "node ./source/test.mjs",
"dev": "node ./source/dev.mjs"
},
"repository": {
"type": "git",
Expand Down
91 changes: 91 additions & 0 deletions source/classes/collections/country.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
import {Config} from './config.mjs'
*/
import {cleanseIsoCode} from '../helpers/conversions.mjs'

// ⚠️ [TODO] REMOVE TEMPORARY DEV CODE
const eject = (instance) => (JSON.parse(JSON.stringify(instance)))
class List extends Array { }


class Country {
/* STEP 1: INITIALIZE CLASS ATTRIBUTE STRUCTURE */
// Static config info (ℹ️ must be initialized by app)
static config = undefined

// Universal country code
'iso3166-1' = undefined

// Country name in english
name = undefined


/* STEP 2: APPLY NEW INSTANCE CONSTRUCTION */
constructor (data = { }) {
let self = this // allow the forgetting of "this"
data = {...data} // dont mutate input data

// If the data already has an instance of this class,
// then there is no point in creating a new instance.
// We can replace "self" instance, thus forgetting it.
if (data.country instanceof Country) {
self = data.country
delete data.country
}

// The "self" variable can either be "this",
// or another class instance obtained from the data.
self.assignData(data)

return self // override the returning of "this"
}


/* STEP 3: CLEAN AND ASSIGN DATA INPUT TO THE INSTANCE */
assignData ({country}) {
//+ CLEAN THE COUNTRY DATA +//
if (country == null) {
return
}
else if (country.ids == null) {
({country} = Country.parseFromAPI({country}))
}
else {
({country} = Country.parseFromDB({country}))
}

//+ ASSIGN THE COUNTRY DATA +//
this['iso3166-1'] = country['iso3166-1']
this.name = country.name
}


toJSON ( ) {
return this
}


static parseFromAPI ({country}) {
const newCountry = eject(new Country())
newCountry['iso3166-1'] = cleanseIsoCode(country.iso_3166_1)
newCountry.name = country.name
return {country: newCountry}
}


static parseFromDB ({country}) {
return {country}
}


// TODO: Add "matches"
static matches () {throw new Error('⚠️ Need to implement matches on Country!')}

// TODO: Add "combine"
static combine () {throw new Error('⚠️ Need to implement combine on Country!')}

// TODO: Add "sharedMetadata"
static #sharedMetadata () {throw new Error('⚠️ Need to implement #sharedMetadata on Country!')}
}

export {Country}
176 changes: 176 additions & 0 deletions source/classes/collections/genre.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
import {Config} from './config.mjs'
*/

// ⚠️ [TODO] REMOVE TEMPORARY DEV CODE
const eject = (instance) => (JSON.parse(JSON.stringify(instance)))
class List extends Array { }

class Genre {
/* STEP 1: INITIALIZE CLASS ATTRIBUTE STRUCTURE */
// Static config info (ℹ️ must be initialized by app)
static config = undefined

// External identification
ids = {
TMDb: undefined, // The Movie Database
RPDb: null, // Rotten Potatoes (ℹ️ never stores genres)
IMDb: null, // Internet Movie Database (ℹ️ not supported)
}

// Genre name
name = undefined


/* STEP 2: APPLY NEW INSTANCE CONSTRUCTION */
constructor (data = { }) {
let self = this // allow the forgetting of "this"
data = {...data} // dont mutate input data

// If the data already has an instance of this class,
// then there is no point in creating a new instance.
// We can replace "self" instance, thus forgetting it.
if (data.genre instanceof Genre) {
self = data.genre
delete data.genre
}

// The "self" variable can either be "this",
// or another class instance obtained from the data.
self.assignData(data)

return self // override the returning of "this"
}


/* STEP 3: CLEAN AND ASSIGN DATA INPUT TO THE INSTANCE */
assignData ({genre}) {
//+ CLEAN THE GENRE DATA +//
if (genre == null) {
return
}
else if (genre.ids == null) {
({genre} = Genre.parseFromAPI({genre}))
}
else {
({genre} = Genre.parseFromDB({genre}))
}

//+ ASSIGN THE GENRE DATA +//
this.ids.TMDb = genre.ids.TMDb
this.name = genre.name
}


toJSON ( ) {
return this
}


static parseFromAPI ({genre}) {
const newGenre = eject(new Genre())
newGenre.ids.TMDb = genre.id
newGenre.name = genre.name
return {genre: newGenre}
}


static parseFromDB ({genre}) {
return {genre}
}


static matches (...instances) {
const areAllGenres = !instances.some((instance) => (
// There is some instance that isn't a genre.
!(instance instanceof Genre)
))

// If something isn't a genre, then its not a match!
if (!areAllGenres) {
return false
}

// Use this helper for checking if pairs of instances match.
const attributesMatch = (attributeA, attributeB) => {
if (
attributeA === undefined
|| attributeB === undefined
) {
return true
}
else if (attributeA === attributeB) {
return true
}
else {
return false
}
}

// Get every combination pair of instances.
for (
let indexA = 0;
indexA < instances.length;
indexA++
) {
for (
let indexB = indexA + 1;
indexB < instances.length;
indexB++
) {
// Get this pair of instances.
const instanceA = instances[indexA]
const instanceB = instances[indexB]

// Determine of *ALL* of their attributes match.
if (
!attributesMatch(instanceA.name, instanceB.name)
|| !attributesMatch(instanceA.ids.TMDb, instanceB.ids.TMDb)
|| !attributesMatch(instanceA.ids.RPDb, instanceB.ids.RPDb)
|| !attributesMatch(instanceA.ids.IMDb, instanceB.ids.IMDb)
) {
// If it gets here, something doesn't match!
return false
}
}
}
// If the loops all finish, then literally everything matches!
return true
}


static combine (...instances) {
// Ensure all given instances are matching.
if (!Genre.matches(instances)) {
return false
}

const combined = instances.reduce((combined, current) => {
if (combined.name === undefined) {
combined.name = current.name
}
if (combined.ids.RPDb === undefined) {
combined.ids.RPDb = current.ids.RPDb
}
if (combined.ids.TMDb === undefined) {
combined.ids.TMDb = current.ids.TMDb
}
if (combined.ids.IMDb === undefined) {
combined.ids.IMDb = current.ids.IMDb
}
return combined
}, new Genre( ))

return combined
}


get #sharedMetadata ( ) {
return {
genre: this,
config: Genre.config,
}
}
}

export {Genre}
2 changes: 1 addition & 1 deletion source/classes/company.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Config} from './config.mjs'
import {Country} from './country.mjs'
import {Country} from './collections/country.mjs'
import {Logo} from './image.mjs'

class Company {
Expand Down
Loading