1日1%成長するブログ

毎日成長するために仕事/プライベートで得た学びをアウトプットするブログです

ActiveRecordのEnum型のデータをJavaScriptから保存する

enum(列挙型)の使い方

ActiveRecordenumを使うと、プログラムからは文字列(名前)でアクセスでき、 DBには数値で保存される属性を作ることができます。

Modelに属性にセットできる文字列を列挙する

class Post < ActiveRecord::Base
  
  enum status: {
    draft: 0,
    published: 1
    deleted: 2
  }

end

DBにカラムを追加する

class AddStatusToPostss < ActiveRecord::Migration
  def change
    add_column :postss, :status, :integer, default: 0
    add_index :posts, :status 
  end
end

追加されるメソッド

  • モデル名.属性名で現在の値を文字列で取得
  • モデル名.文字列?で状態チェック
  • モデル名.文字列!で状態変更
  • モデル名.属性の複数形で列挙した値を全てハッシュで取得
  • モデル名.文字列でその状態のレコードを検索
post = Post.new(status: :draft)
post.status # => draft
post.draft? # => true 
post.published! # statusをpublishedに変更

Post.statuses # => { draft: 0, published: 1 }
# Post.statuses[:draft]のようにして数値を得られる

Post.draft

JSから保存する

JSからAjaxRails側に保存する時は文字列で送れば良い。

data: {
  status: 'draft'
}

文字列にしておけばEnum型が自動で数値に変換してDBに保存してくれる。 JS側で数値を管理する必要がないので便利。

Reactでエンターキーのイベントを取得する方法

onEnterは存在しない

Reactがサポートしているイベントは以下の公式サイトに記載されています。

facebook.github.io

onClick のノリで onEnterは無いのかな?と探してみてもありません。 ただし、代わりになるキーイベントがあります。

onKeyDownを使う

それが onKeyDown です。Propertiesを見ると、keyCodeが取得できるので この値を判定すればエンターキーを判定できます。

https://facebook.github.io/react/docs/events.html#keyboard-events

キーイベントをJSXから受け取る

<textarea onKeyDown={  (e) => props.onKeyDown(e) } />

後はこんな感じでpropsで受け取ったイベントハンドラにイベントオブジェクトを引数で渡して実行します。

keyCode === 13で判定する

if (e.keycode === 13) {
  // エンターキー押した時の処理
}

あとはkeycodeの中身を判定するだけですね。簡単です。

Webアプリの新規作成の実装パターン

一覧 -> 作成画面に遷移して新規作成

フォームでPOSTして、エラーならエラーメッセージを表示、成功したら一覧画面にリダイレクトさせるというオーソドックスなパターン。

Flashメッセージの実装は必要。作成後にセッションに保存して一覧画面で取得したらすぐに消すような仕組み。大体フレームワークに用意されてることが多い。

バリデーションをどうするかも重要。 サーバーサイドでバリデーション結果をテンプレートに埋め込んで表示するタイプか、JSでバリデーションをリアルタイムに実装するか。

Vueでリアルタイムバリデーションして、システム側のエラーは例外。ユーザー起因のエラーはFlashメッセージがいいかな。

一覧からモーダルを開いて新規作成

非同期処理が完了したらJSで動的に一部の画面を切り替える

このパターンはユーザーに画面遷移をさせないで済み、変更がすぐに反映されるので一番理想。 実装方法としては以下の2つになると思う。

ただこの方法で厄介なのは、JS側でテンプレートを描写することになるため サーバーサイドのテンプレートエンジンが使えないこと。

最たる例がi18n対応。サーバーサイドで翻訳した結果を出力する形になっている場合、 JS側でもi18n用の機能を実装する必要が出てくる。

また非同期処理が完了するまで画面に何も表示されないというのも難点。 これを解決するにはローディングを出したり、取得中はDOM自体をださないとか、 gon等のライブラリを使ってDOMにJSの値を吐き出しておく等の方法がある。

またこの方法の場合、前述のFlashメッセージではなくトースターを表示する実装が必要になる。

ということからもわかるように、この方法はJSフレームワークのコード資産があまり無い段階では 実装コストが高めなのでスピードを優先する時はオススメできない。

非同期処理が完了したら画面をリロードさせる

このパターンは非同期処理が完了したら、location.href で画面をリロードさせることで変更を反映させる。 データ数が多くなければ、そこまで違和感が無い。テンプレートエンジンも流用できる。

ただ作成した時のトースターを表示したい時は一工夫が必要。 非同期処理が成功したという情報をどこかに持っておかないといけない。

自分はグローバル変数は汚したくないので、ローカルストレージを使って実装することが多い。 ローカルストレージの実装は必要になるが、JSで動的に切り替えるよりは実装コストは低いのでオススメ。

JavaScriptのオブジェクトとJSONは別物

JavaScriptのオブジェクトとJSONは見た目がほとんど変わらないので、 違いがよくわかっていなかったのですが、明確に違いがあるようなのでメモしておきます。

JavaScriptのオブジェクト

キーがクォーテーション無し & 文字列をダブルクォーテーションで囲む

{ 
  id: 0,
  title: "title",
  description:  "desc"
}

キーがクォーテーション無し & 文字列をシングルクォーテーションで囲む

{ 
  id: 0,
  title: 'title',
  description:  'desc'
}

キーをクォーテーションで囲むことも出来るけど、囲まない方が推奨。

JSON

キーをダブルクオーテーションで囲み、文字列をダブルクォーテーションで囲むのがJSONJavaScriptのオブジェクトでもある。

{
  "id": 0,
  "title": "title",
  "description":  "desc"
}

JSONのフォーマットとして正しいかどうかは以下のツールを使うと、簡単に判定できるので一度使ってみてください。

lab.syncer.jp

JSON.stringify()とJSON.parse()

JSON.stringifyJavaScriptのオブジェクトをJSON文字列に変換する関数。 JSON.parseJSON文字列をJavaScriptオブジェクトに変換する関数。

出力を見ると、JSON文字列はキーがどちらもダブルクォーテーションで囲まれていることがわかると思います。

var jsVar = {
  id: 1,
  title: "title"
};

var jsonStr = JSON.stringify(jsVar);

var jsParsed = JSON.parse(jsonStr);

console.log(jsVar); // {id: 1, title: "title"}
console.log(jsonStr); // {"id":1,"title":"title"}
console.log(jsParsed); // {id: 1, title: "title"}

WebpackでReactを本番反映する時に行う設定

webpackの各種プラグインを導入

DefinePluginでprocess.env.NODE_ENVを置換する

特定の文字を任意の文字に置き換えることのできるプラグインです。

たとえば、

//webpak.config.js
plugins: [
      new webpack.DefinePlugin({
        'process.env':{
          'NODE_ENV': JSON.stringify('production')
        }
      }),
],

という定義があった時に、Reactのコード内の

if process.env.NODE_ENV !== ‘production’ { 
  //開発環境用のコード
}

if ‘production’ !== ‘production’ {
  //開発環境用のコード
}

に置き換えられる。

この設定単体だとダメで、後述のUglifyJSPluginと組み合わせることで、 未到達のコードは削除されてファイル容量を削減することができます。

UglifyJsPluginでJSをminifyする

minifyとは、コメントや改行,スペースを削除したり、変数名を短くして無駄な部分を取り除くことです。

//webpak.config.js
plugins: [
      new webpack.DefinePlugin({
        'process.env':{
          'NODE_ENV': JSON.stringify('production')
        }
      }),
      new webpack.optimize.UglifyJsPlugin()

とすると、

  WARNING in xx.js from UglifyJs
    Condition always false [./~/react/lib/React.js:29,0]
    Dropping unreachable code [./~/react/lib/React.js:29,0]
    Declarations in unreachable code! [./~/react/lib/React.js:30,0]
    Condition always false [./~/react/lib/React.js:44,0]

のような警告文が大量にでることがあるので、

//webpak.config.js
plugins: [
      new webpack.DefinePlugin({
        'process.env':{
          'NODE_ENV': JSON.stringify('production')
        }
      }),
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false
        },
        comments: require("uglify-save-license")
      })

にして、警告文を非表示にします。 またuglify-save-license を使うことでライセンスのコメントは残す設定も入れておきましょう。

その他

あとは環境に応じて、APIのリクエスト先を変えるような場合 以下のようにして置き換えられるようにしておくと良いと思います。

//webpak.config.js
plugins: [
      new webpack.DefinePlugin({
        'process.env':{
          'NODE_ENV': JSON.stringify('production'),
     'API_URL': 'https://api.xxx.com'
        }
      }),
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false
        },
        comments: require("uglify-save-license")
      })

ReduxのProviderコンポーネントとContainerコンポーネントを理解する

Providerコンポーネント

const App = () => {
  return (
    <Provider store={createStore(reducers)}>
      <View>
         <Header>
         <PostList>
         <Footer> 
      </View>
    </Provider>
  )
}

Containerコンポーネント

  • connect関数でReactコンポーネントをラッピングしたもの
  • Connected Componentとも呼ぶ
  • Reactでは「Stateを親コンポーネントに持たせる」と表現するが、それをReduxでは名前をつけただけ
  • ただし、ReduxではStateはStoreに持つので、connectしてStateをPropsとして受け取る形になる
class PostList extends React.Component {
  render() {
    //ここにstateの中身がくる
    console.log(this.props);
  }
}

const mapStateToProps = state => {
  return { posts: state.posts };
}

//PostListをconnectでラッピングしたここがContainerコンポーネント
export default connect(mapStateToProps)(PostList);

このようにすることで、stateの中のpostsのみをpropsで受け取ることができる

CSSで記号を実装する

今回は文字の前後に記号を追加したい。 CSSの疑似セレクタ(:before, :after)を使えば要素の前後に値を表示できます。

値を指定するには疑似セレクタのcontentプロパティを使う。 IE7以下は未対応らしいですが、今回は無視しています。

contentプロパティで表示できる項目

  • テキスト
  • 属性名
  • 画像

上記の3つを扱うことができる。 記号を表示するだけであれば、テキストを使う。 その他の使い方に関しては以下の記事がわかりやすい。

tenderfeel.xsrv.jp

テキストの表現方法

機種依存文字Shift_JIS等で文字化けする時があるので、実体参照を使う方が無難 (最近はUTF-8が主流なのであまり気にしなくなってきているらしい)

実体参照について

人にわかりやすい文字で表現されているので使い易いが、 文字実体参照が無い記号も多い(例: ✔) 。この場合はUnicodeの10進数/16進数で表現する数値文字参照を使うしかない。

数値文字参照の調べ方

使いたい記号の数値文字参照を調べるツールも以下の記事に記載されているので、 困ったら見てみると便利だと思います。

tenderfeel.xsrv.jp

サンプルコード

.check-mark:after
  content: "\002714"
  color: red

こんな感じで✔を表示することが出来ます。