<commit_before>import Immutable from "immutable"
import Joi from "joi"

export class Model<T extends Object> {
  protected _data: Immutable.Map<any, any>
  protected _originalData: Immutable.Map<any, any>

  private _isproxy: boolean = false

  constructor(data = Immutable.Map(), originalData = data) {
    this._data = data
    this._originalData = originalData
  }

  /**
   * Loads model data from provided object. This method is usefull
   * for loading data from storage.
   */
  public load(data: Object) {
    const result = this.loadSchema.validate(data)

    return new (this.constructor as typeof Model)(
      Immutable.fromJS(result.value)
    )._createProxy() as this & T
  }

  /**
   * Creates a new model with provided data
   */
  public create(data: T) {
    return this._update([data], this.createSchema, true)._createProxy()
  }

  /**
   * Immutably update model with data
   *
   * @param {...Partial<T>[]} data multiple partials of a data to update with
   * @returns {Model<T>} newly created instance of a model
   */
  public update(...data: Partial<T>[]) {
    return this._update(data)._createProxy()
  }

  /**
   * Handler called after model has been updated
   */
  public postUpdate(model: this): this & T | void {
    return
  }

  /**
   * Updates model in place
   *
   * @param {...Partial<T>[]} data multiple partials of a data to update with
   * @param {...Partial<T>[]} data
   * @returns {this}
   */
  public updateInPlace(...data: Partial<T>[]) {
    return this._update(data, this.updateSchema, true)
  }

  /**
   * Gets model value
   *
   * @returns {T} model value as object
   */
  public value(): T {
    return this._data.toJS() as T
  }

  /**
   * Checks whether model has a property set
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public has(prop: string | Array<string> = "") {
    if (prop === "") {
      return !this._data.equals(Immutable.Map())
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    return this._data.hasIn(prop)
  }

  /**
   * Checks whether object has a property set on initial value.
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public hasOld(prop: string | Array<string> = ""): boolean {
    if (prop === "") {
      return !this._originalData.equals(Immutable.Map())
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    return this._originalData.hasIn(prop)
  }

  /**
   * Commits model changes. This operation resets change tracking on model to
   * current model data.
   *
   * @returns {Model<T>} newly created instance of a model
   */
  public commit() {
    return new (this.constructor as typeof Model)(
      this._data
    )._createProxy() as this & T
  }

  /**
   * Commits model changes in place. This operation resets change tracking on model to
   * current model data.
   *
   * @returns {this}
   */
  public commitInPlace() {
    this._originalData = this._data

    return this._createProxy()
  }

  /**
   * Gets value of a property.
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public get(prop: string | Array<string> = ""): any {
    if (prop === "") {
      return this._data
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    if (!this._data.hasIn(prop)) {
      throw new ReferenceError(
        `Missing property '${prop.toString()}' on model '${this.constructor
          .name}'`
      )
    }

    return this._data.getIn(prop)
  }

  /**
   * Gets initial value of a property
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public getOld(prop: string | Array<string> = ""): any {
    if (prop === "") {
      return this._originalData
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    return this._originalData.getIn(prop)
  }

  /**
   * Checks whether prpperty has changed
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {boolean} whether property has changed
   */
  public hasChanged(prop: string | Array<string> = ""): boolean {
    const value1 = this.has(prop) ? this.get(prop) : undefined
    const value2 = this.getOld(prop)

    if (Immutable.isImmutable(value1)) {
      return !value1.equals(value2)
    }

    return value1 !== value2
  }

  /**
   * Checks whether model equals other model
   *
   * @param {Model} model model to compare with
   * @returns {boolean} whether model equals other model
   */
  public equals(model: this): boolean {
    return model._data.equals(this._data)
  }

  /**
   * Creates clone of a model
   *
   * @returns {Model<T>} cloned model
   */
  public clone() {
    return new (this.constructor as typeof Model)(
      this._data,
      this._originalData
    )._createProxy() as this & T
  }

  private _update(
    data: Array<Partial<T>>,
    schema = this.updateSchema,
    inPlace = false
  ): this {
    let newData = this._data

    for (const entry of data) {
      newData = newData.mergeDeep(entry)
    }

    const result = schema.validate(newData.toJS())

    if (result.error) {
      throw result.error
    }

    let model
    if (inPlace) {
      this._data = newData

      model = this
    } else {
      model = new (this.constructor as typeof Model)(<commit_msg>fix: fix in place updates<commit_after>import Immutable from "immutable"
import Joi from "joi"

export class Model<T extends Object> {
  protected _data: Immutable.Map<any, any>
  protected _originalData: Immutable.Map<any, any>

  private _isproxy: boolean = false

  constructor(data = Immutable.Map(), originalData = data) {
    this._data = data
    this._originalData = originalData
  }

  /**
   * Loads model data from provided object. This method is usefull
   * for loading data from storage.
   */
  public load(data: Object) {
    const result = this.loadSchema.validate(data)

    return new (this.constructor as typeof Model)(
      Immutable.fromJS(result.value)
    )._createProxy() as this & T
  }

  /**
   * Creates a new model with provided data
   */
  public create(data: T) {
    return this._update([data], this.createSchema, true)._createProxy()
  }

  /**
   * Immutably update model with data
   *
   * @param {...Partial<T>[]} data multiple partials of a data to update with
   * @returns {Model<T>} newly created instance of a model
   */
  public update(...data: Partial<T>[]) {
    return this._update(data)._createProxy()
  }

  /**
   * Handler called after model has been updated
   */
  public postUpdate(model: this): this & T | void {
    return
  }

  /**
   * Updates model in place
   *
   * @param {...Partial<T>[]} data multiple partials of a data to update with
   * @param {...Partial<T>[]} data
   * @returns {this}
   */
  public updateInPlace(...data: Partial<T>[]) {
    return this._update(data, this.updateSchema, true)
  }

  /**
   * Gets model value
   *
   * @returns {T} model value as object
   */
  public value(): T {
    return this._data.toJS() as T
  }

  /**
   * Checks whether model has a property set
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public has(prop: string | Array<string> = "") {
    if (prop === "") {
      return !this._data.equals(Immutable.Map())
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    return this._data.hasIn(prop)
  }

  /**
   * Checks whether object has a property set on initial value.
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public hasOld(prop: string | Array<string> = ""): boolean {
    if (prop === "") {
      return !this._originalData.equals(Immutable.Map())
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    return this._originalData.hasIn(prop)
  }

  /**
   * Commits model changes. This operation resets change tracking on model to
   * current model data.
   *
   * @returns {Model<T>} newly created instance of a model
   */
  public commit() {
    return new (this.constructor as typeof Model)(
      this._data
    )._createProxy() as this & T
  }

  /**
   * Commits model changes in place. This operation resets change tracking on model to
   * current model data.
   *
   * @returns {this}
   */
  public commitInPlace() {
    this._originalData = this._data

    return this._createProxy()
  }

  /**
   * Gets value of a property.
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public get(prop: string | Array<string> = ""): any {
    if (prop === "") {
      return this._data
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    if (!this._data.hasIn(prop)) {
      throw new ReferenceError(
        `Missing property '${prop.toString()}' on model '${this.constructor
          .name}'`
      )
    }

    return this._data.getIn(prop)
  }

  /**
   * Gets initial value of a property
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {*} property value
   */
  public getOld(prop: string | Array<string> = ""): any {
    if (prop === "") {
      return this._originalData
    }

    if (typeof prop === "string") {
      prop = prop.split(".")
    }

    return this._originalData.getIn(prop)
  }

  /**
   * Checks whether prpperty has changed
   *
   * @param {(string | Array<string>)} [prop=''] Path to a property, which can be dot separated string or array
   * @returns {boolean} whether property has changed
   */
  public hasChanged(prop: string | Array<string> = ""): boolean {
    const value1 = this.has(prop) ? this.get(prop) : undefined
    const value2 = this.getOld(prop)

    if (Immutable.isImmutable(value1)) {
      return !value1.equals(value2)
    }

    return value1 !== value2
  }

  /**
   * Checks whether model equals other model
   *
   * @param {Model} model model to compare with
   * @returns {boolean} whether model equals other model
   */
  public equals(model: this): boolean {
    return model._data.equals(this._data)
  }

  /**
   * Creates clone of a model
   *
   * @returns {Model<T>} cloned model
   */
  public clone() {
    return new (this.constructor as typeof Model)(
      this._data,
      this._originalData
    )._createProxy() as this & T
  }

  private _update(
    data: Array<Partial<T>>,
    schema = this.updateSchema,
    inPlace = false
  ): this {
    let newData = this._data

    for (const entry of data) {
      newData = newData.mergeDeep(entry)
    }

    const result = schema.validate(newData.toJS())

    if (result.error) {
      throw result.error
    }

    let model
    if (inPlace) {
      this._data = Immutable.fromJS(result.value)

      model = this
    } else {
      model = new (this.constructor as typeof Model)(
{'fraction_non_alphanumeric': 0.13333916236775378, 'fraction_numerical': 0.0012240972282941331, 'mean_word_length': 2.6070640176600444, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2436058', 'n_tokens_mistral': 3578, 'n_tokens_neox': 3495, 'n_words': 1091}