mmyoji's diary

プログラミングとか日々のどうでもいいこととか

ES6のclassでクラス内定数みたいなものを使う

Classes - JavaScript | MDN

jsでRailsのmodel層っぽいvalidationとか書きたいなーとか夜な夜な考えてて朝起きて色々試したらなんとなく「こんな感じかな?」ってのができたので残しておきます。

ダメ出し食らうの覚悟で書いてます。。。

実装説明

  • BaseModel クラスを定義
    • newしたタイミングで _validation() を呼び、propertyに過不足がないかチェック(エラーの書き方は適当)
    • _validation() の中でpresenceのチェックなどを行う(ここではpresenceのみ)
  • validationしたいkeyの名前などは各クラスで異なるので継承先で書き換えられるように

実装詳細

BaseModel.js

import _ from "lodash"

class BaseModel { // ActiveRecord::Base 的ノリ
    constructor(props) {
        if (this._validate(props)) {
            this.props = props
        } else {
            throw new Error("validation failed!")
        }
    }

    _validate(props) {
        return !_.isEmpty(props) && this._validatePresenceOf(props)
    }

    _validatePresenceOf(props) {
        //  変数に入れる意味は特にないです
        let keys = this.constructor._validatePresenceKeys()
        return _.every(keys, (key) => !_.isEmpty(props[key]))
    }

    static _validatePresenceKeys() { return [] }
}

export default BaseModel

this.constructor._validateXXX としているのは継承考えないなら BaseModel._validateXXX と同じ意味です。

継承先で再定義したものを使いたかったのでこうしてます。

参考: javascript - es6 call static methods - Stack Overflow

User.js

import BaseModel from "./BaseModel"

class User extends BaseModel {
    constructor(props) {
        super(props)

        // user.props.username ってやると冗長なのでここで再定義
        this.username = props.username
        this.email = props.email
    }

    // BaseModelのメソッドを上書き
    static _validatePresenceKeys() {
        return [
            "username",
            "email"
        ]
    }
}


// validation通る
var user = new User({
    username: "mmyoji",
    email: "mmyoji@example.com"
})
user.username //=> "mmyoji"

// validation通らない
var user2 = new User({
    email: "foo@example.com"
})
// => "Uncaught Error: validation failed!"

最初各ファイルに const VALIDATION_PRESENCE_KEYS とか定義して見れるようにしたらいけんじゃなイカ?って思いましたが、継承先で継承元の定数が参照されて使えなかったので static with _ でクラス内の静的なプライベート(的)メソッドとしてみました。

別に this で呼ぶならインスタンスメソッドでも良さそうな気はしたんですが、 static って名前に惹かれて使いたかっただけです(キリッ

関数名とかは完全にRails意識してます

別の方法

いちいちvalidationのためのメソッドをクラス内に定義するのがうざいので、validation自体は別クラスにして実装。

そっちの方がマシかも。

validate.js

import _ from "lodash"

class Validator {
  constructor(props, config) {
    this.props = props
    this.presenceKeys = config["presence"] // Array<String> or null
    this.formatKeys = config["format"]     // Array<Object> or null
    this.lengthKeys = config["length"]     // Array<Object> or null
  }

  execute() {
    let results = []
    if (!_.isEmpty(this.presenceKeys)) {
      results.push(this._validatePresence())
    }
    if (!_.isEmpty(this.formatKeys)) {
      results.push(this._validateFormat())
    }
    if (!_.isEmpty(this.lengthKeys)) {
      results.push(this._validateFormat())
    }

    return _.every(results)
  }

  // _validatePresence() { ... }

  // _validateFormat() { ... }

  // _validateLength() { ... }
}



function validate(props, config) {
  let validator = new Validator(props, config)
  return validator.execute()
}

export default validate

BaseModel.js

import _ from "lodash"
import validate from "./validate"

// 各クラスでこれを特定のフォーマットに従って定義して持っておく
const validateConfig = {
    presence: ["username", "email"],
    format: [
        { key: "username", pattern: /a-zA-Z/ },
        { key: "email", pattern: /@/ }
    ],
    length: [
        { key: "username", min: 1, max: 12 }
    ]
}

class BaseModel {
    constructor(props) {
        // validationを行う
        if (validate(props, validateConfig)) {
            this.props = props
        } else {
            throw new Error("validation failed!")
        }
    }
}

export default BaseModel

結論

TypeScript使えばもうちょっと楽なんじゃね?(汗

今まで基本動的型付けの言語しか触ってなかったのでアレですが、型チェック欲しいなって初めて思いました。

(今回のvalidationに関しては型チェックではないですが...)

あとどうでもいいですが、クラス内の関数呼ぶ時に this. とか付けずに呼びたい。。。