うまとま君の技術めも

2015年新卒入社した社畜の勉強内容などなど

React + Redux 入門

Introduction

React・Reduxの基本的な仕組み・使い方ついて解説します。

Attention
自分もまだ使い始めたばかりなので、 間違っているところなどあればご指摘お願いします :bow:


React

A JavaScript library for building user interfaces

  • あくまでViewを作るためのライブラリ
  • Angular, Vue.jsなどのフレームワークとも組み合わせ可能
    • ex. Viewの所だけReactにする

https://facebook.github.io/react/

f:id:umatomakun:20161112212553p:plain

Pros

  • VirtualDOMという仕組みによりUnitTestを書くことが容易になる
    • Debugもしやすくなります
  • Componentを組み合わせることで複雑なUIを簡単に構築可能
    • また、Componentはアプリケーション内で再利用可能
  • 他のJSライブラリと組み合わせることが出来る
    • ex. Angular + React
  • パフォーマンス向上が望める
    • Angular, VueJS 辺りもVirtualDOM採用している筈なので差は縮まってきているかも?

Cons

  • 比較的学習コストが高い
    • 個人的にはAngularの方がやってること多いので難しい気が...
  • 依存ツールが多い
    • Babel, Webpack, CommonJS, ...
  • 記述が冗長になりがち
  • フルスタックなフレームワークではない
    • View以外の必要な処理は別途実装する必要がある
  • デザイナーとの役割分担が難しい
    • JSXを覚えてもらえれば何とか...

Features

Declarative

あるべきViewを宣言するように記述できる

Component-Based

Componentを組み合わせることでViewを構築

Learn Once, Write Anywhere

Rectを覚えれば様々な環境で使用可能 (ex. React Native)


Declarative

命令的

  • :scream: JSだけで完結せず、様々なものにViewが依存
  • :scream: テスト・デバッグが困難
$('button').on('click', () => {
  $('h1').text('hello');
});

宣言的

  • :smile: Viewがstateのみに依存
  • :smile: テスト・デバッグが容易
render() {
  return (
    <div>
      <h1>{this.state.title}</h1>
      <button onClick={() => {
        this.setState({ title: 'hello' });
      }}>
        Click
      </button>
    </div>
  );
}

Component-Based

ReactではComponentを組み合わせてViewを構築します。
これにより、複雑なUIを簡単に作ることが可能となります。

複数Component組み合わせイメージ

const Header = () => (
  <header>
    // This is header...
  </header>
);

const SearchForm = () => (
  <form>
    // This is search form...
  </form>
);

const MainContent = () => (
  <div>
    <LeftNav /> // 左ナビComponent
    <TopMenu /> // メニューComponent
    <Ad /> // 広告Component
  </div>
);

class App extends Component {
  render() {
    return (
      <div>
        <Header /> // ヘッダーComponent
        <SearchForm /> // 検索フォームComponent
        <MainContent /> // メインComponent
      </div>
    );
  }
}

f:id:umatomakun:20161112211217p:plain

※ 実際にReactは使われていません


Reactを使ってみよう!

Hello World

render 関数に表示したいElementと、表示先のDOMを指定すればOK!

ReactDOM.render(
  <h1>Hello World!!</h1>,
  document.getElementById('root')
);

この様なHTMLが出力されるはずです。

<body>
  <div id="root">
    <h1 data-reactroot="">Hello World!!</h1>
  </div>
</body>

CodePen

今すぐにReactを動かしてみたい方はこちらからどうぞ
既に実行できる環境が整っています!

http://codepen.io/gaearon/pen/ZpvBNJ?editors=0010


DOM描画

ReactではComponentを組み合わせてUIを構築していきます。

このComponentを元にインスタンスであるElementを生成し、
これらのVirtualDOMを通して実際のDOMに描画していきます。

// Component定義
var Hello = function Hello() {
  return React.createElement(
    "h1",
    null,
    "Hello World!!"
  );
};
// Element生成 & 描画
ReactDOM.render(
  React.createElement(Hello, null),
  document.getElementById('root')
);

// JSX ver.
const Hello = () => (
  <h1>Hello World!!</h1>
);
ReactDOM.render(
  <Hello />,
  document.getElementById('root')
);

JSX

Elementを生成する、HTML風の独自syntax (※ HTMLではないです)
JSXを使うことにより面倒なElement作成を簡単に行うことができます。

公式でも利用が推奨されているので、積極的に使っていきましょう!

<h1 id="test" hoge={false}>Hello World!!</h1> // JSX
React.createElement(
  "h1",
  { id: "test", hoge: false },
  "Hello World!!"
);

Babel Online Compiler

リアルタイムでBabelによるトランスパイルを行ってくれる環境があるので、
今すぐJSXを試してみたい方はこちらからどうぞ!

https://babeljs.io/repl/


Props & State

ReactではProps・Stateと呼ばれるデータによって状態を管理していきます。

Props State
データの更新 NG OK
定義元 親Component 自Commponent
イメージ Constructorから渡された引数 Privateなインスタンス変数

Props

Componentは外部からデータを受け取ることが可能となっていて、
その入力データの事をPropsと呼びます。

受け取ったPropsの値に応じて表示する内容を変化させたり、
クリック時のCallback等にも使用することができます。

class Welcome extends Component {
  render() { // propsを元にViewを宣言
    return <h1>Hello, {this.props.name}</h1>;
  }
}

class App extends Component {
  render() { // props指定
    return <Welcome name="Tom">;
  }
}

ただし、同じPropsが渡されたら同じViewが表示されるべきであるため、
値を更新しない ように注意してください。

State

Propsは親コンポーネントなどの外部から渡されるデータでしたが、
StateはComponent自身が保持しているローカルなデータになります。

また、Propsとは異なりデータの更新が可能となっています。
ただし、更新を行う際は必ずsetState関数を使用してください。
直接値を更新してしまうと更新時のイベントが呼ばれずViewが再描画されなくなります。:scream:

class Counter extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 }; // stateの初期値を定義
  }
  render() {
    return (
      // クリックしたらインクリメント
      // ※ this.state.count++だと元の値を書き換えるのでNG
      <button
        onClick={() => {
          this.setState({ count: this.state.count + 1 });
        }}
      >
        {this.state.count}
      </button>
    );
  }
}

Lifecycle

ComponentにはLifecycle関連のメソッドが幾つか用意されていますが、
それらがどの様に呼ばれていくのか確認していきたいと思います。

Mounting

  • constructor()
    • state, propsなどの初期化処理
  • componentWillMount()
    • mountされる前に1回だけ呼ばれる
    • SSR (Server-Side-Renderring) 時にも使用可能
    • 主にSSR用の処理を記述 (Client側での初期化処理はconstructor)
  • render()
    • DOM描画
    • 単一のElementを返す必要がある
    • null, false 等を返した場合は何も描画されない
  • componentDidMount()
    • mountされた後に1回だけ呼ばれる
    • Ajaxでの初期データ読み込みや、描画したDOMへのイベント追加に使える

Updating

  • componentWillReceiveProps(nextProps)
    • Propsが更新されたときに呼ばれる
    • 変更後のPropsに応じてStateも変更するときなどに使用可能
  • shouldComponentUpdate(nextProps, nextState)
    • 返り値によって再描画する・しないを制御可能
    • true: 描画する、false: 描画しない
  • componentnWillUpdate(nextProps, nextState)
    • DOMの更新前に呼ばれる
    • ここではStateの更新ができない
  • render()
  • componentDidUpdate(prevProps, prevState)
    • DOMが更新された後に呼ばれる
    • DOMの更新に応じて処理をしたい場合などに使用可能

Unmounting

  • componentWillUnmount()
    • unmountされる前に呼ばれる
    • Component内で使用していたDOMイベントなどを解除するのに使用可能

f:id:umatomakun:20161112212348p:plain


Event Handler

"ボタンをクリックしたとき"・"フォームをSubmitした時"等の
イベント処理に関して見ていきたいと思います。

クリックしたら何かしてみる

下記のようにonClickイベントハンドラーを渡すだけで、
クリック時の挙動を制御することが可能となります。

const Hello = () => (
  <button onClick={() => { alert('Hello!!'); }}>
    Click Me
  </button>
);

サポートされているイベント一覧

  • Clipboard Events
  • Composition Events
  • Keyboard Events
  • Focus Events
  • Form Events
  • Mouse Events
  • Selection Events
  • Touch Events
  • UI Events
  • Wheel Events
  • Media Events
  • Image Events
  • Animation Events
  • Transition Events

https://facebook.github.io/react/docs/events.html


Unit Test

公式からUnitTest用のサポートツールが提供されていますが、
今回はairbnbから提供されているenzymeを使ったテストを紹介したいと思います。

https://github.com/airbnb/enzyme

import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: props.initCount }; // stateの初期値を定義
  }
  render() {
    return (
      // クリックしたらインクリメント
      // ※ this.state.count++だと元の値を書き換えるのでNG
      <button
        onClick={() => {
          this.setState({ count: this.state.count + 1 });
        }}
      >
        {this.state.count}
      </button>
    );
  }
}

const App = ({ onClick }) => (
  <div>
    <h1>Hello World!!</h1>
    <div className="container">
      <Counter initCount={1} />
    </div>
    <button className="btn" onClick={onClick}>
      Click Me
    </button>
  </div>
);

describe('<App />', () => {
  it('タイトルにHello Worldが表示される', () => {
    // タグ名で検索
    const wrapper = shallow(<App />);
    expect(wrapper.find('h1')).to.have.length(1);
    expect(wrapper.find('h1').text()).to.equal('Hello World!!');
  });

  it('containerクラスが描画される', () => {
    // タグのクラス名で検索
    const wrapper = shallow(<App />);
    expect(wrapper.find('.container')).to.have.length(1);
  });

  it('Counterが描画される', () => {
    // Containerで検索
    const wrapper = shallow(<App />);
    expect(wrapper.find(Counter)).to.have.length(1);
  });

  it('Counterの初期値は1に設定される', () => {
    // Containerで検索
    const wrapper = shallow(<App />);
    expect(
      wrapper.find('Counter').prop('initCount')
    ).to.equal(1);
  });

  it('.container内にbuttonが描画される', () => {
    // Full DOM Rendering であれば依存Componentの内部まで認識可能
    const mountWrapper = mount(<App />);
    expect(
      mountWrapper.find('.container button')
    ).to.have.length(1);

    // Shallow Rendering した場合はCounterの中身を認識できない
    const shallowWrapper = shallow(<App />);
    expect(
      shallowWrapper.find('.container button')
    ).to.have.length(1); // 通らない
  });

  it('ボタンをクリックしたらCallbackが呼ばれる', () => {
    // クリックイベントをシミュレーション
    const onClick = sinon.spy();
    const wrapper = shallow(<App onClick={onClick} />);
    wrapper.find('.btn').simulate('click');
    expect(onClick.calledOnce).to.be.true;
  });
});

Redux

ReactではStateにより状態を管理することが可能となっています。

ですが、各Componentでバラバラに状態管理してしまうと、
アプリケーションが巨大になるに連れてあっという間に複雑化してしまいます。

では、 単一の親コンポーネントで全ての状態を管理してみる のはどうでしょうか?
良さそうに見えますが、一箇所にロジックが集中してしまい、いずれは管理しきれなくなりそうです :scream:

f:id:umatomakun:20161112211415p:plain

https://html5experts.jp/koba04/20839/

そこで、この様な問題に対処するため、
アプリケーション内のデータフローを一方向のみで扱う Fluxと呼ばれる
新しいアーキテクチャが考案されました。

f:id:umatomakun:20161112211346p:plain

Fluxから派生して作られた新たなアーキテクチャにReduxと呼ばれるものがあります。
Fluxでは複数のStoreを持つことを許容していましたが、
Reduxにおいては単一のStoreで全ての状態を管理していきます。

f:id:umatomakun:20161112211425p:plain

https://html5experts.jp/koba04/20839/


Three Principles

Single source of truth

The state of your whole application is stored in an object tree within a single store.

State is read-only

The only way to change the state is to emit an action, an object describing what happened.

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers.


構成

  • Actions
  • Action Creators
  • Reducers
  • Store

Reduxにおいて全ての状態はStoreと呼ばれる単一のStateで管理されています。
この、 Storeを更新する際は必ずActionを発行 し、
Reducerと呼ばれる純粋な関数 によって新しいStateを生成する必要があります。
また、 Actionを発行する役割を持っているのがAction Creator となります。

f:id:umatomakun:20161112211425p:plain

https://html5experts.jp/koba04/20839/

Actions

ActionはStoreを更新する際に必要となる情報であり、Storeから見た情報源は全てここに集約されています。

下記が簡単なActionの例です。

{ type: 'ADD_TODO', text: 'Do something...' }

Actionは純粋なJavaScriptのObjectであり、typeプロパティを持っている必要があります。
この、typeに応じてどの様な処理を行うべきかを判断します。

Action Creators

Action CreatorはActionを生成するただの関数です。

function addTodo() {
  return { type: 'ADD_TODO', text: 'Do something...' };
}

Reducer

Actionからはどの様な行動が発生したのか認識できますが、どの様にStateを変更すべきかは明示していません。
この役割を担うのがReducerとなります。

Reducerは純粋な関数であり、現在のStateとActionから新たなStateを作成します。

function todoApp(state = initialState, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return Object.assign({}, state, {
        todos: [...state.todos, action.text]
      });
    default:
      return state;
  }
}

また、Reducer内で次のような処理を含めてはいけません。

  • 引数を編集する
  • API呼び出しやページ遷移など、副作用のある処理
  • Date.now()Math.random()などpureでない関数を呼ぶ

同じ引数が与えられたら、同じStateが返ってくるべきです。
そのためには、引数の編集や副作用のある処理は行わない必要があります。

Store

Storeは次の役割を担っています。 - アプリケーションのStateを保持 - getState()経由でのアクセスを許可 - dispatch(action)経由での更新を許可 - subscribe(listener)経由でlistenerを登録 - subscribe(listener)からreturnされたfunction経由でlistenerを解除

アプリケーション内のStoreはただ一つです。
データの扱いを分割したい場合は複数のStoreを作成するのではなく、reducerを組み合わせてください。

Storeを作成するのは簡単で、下記のようなコードで実現することができます。

import { createStore } from 'redux';
import todoApp from './reducers';
const store = createStore(todoApp);

Dispatching Actions

Storeを更新したい場合にはstore.dispatch()にActionを渡せばOKです。
現在のStateと渡されたActionを元にReducerが新しいStateを作成します。

import { createStore } from 'redux';
import todoApp from './reducers';
import { addTodo } from './actions';
const store = createStore(todoApp);
store.dispatch(addTodo('Learn about Redux'));

Redux with React

React用のRedux実装ライブラリとしてreact-reduxが提供されています。

$ npm install react-redux --save

Presentational and Container Components

Presentational Components Container Components
目的 見た目を定義 (markup, styles) ビジネスロジックを定義 (data fetching, state updates)
Redux依存 依存しない 依存する
データの参照方法 props経由で参照 Redux Stateを参照
データの更新方法 propsのCallbackを呼び出す ActionをDispatch

DevTools

Babel

https://babeljs.io/

Webpack

https://webpack.github.io/


参考文献

MeCabで形態素解析 Node.jsバインディングを作ってみた

概要

Node.jsを使ったアプリケーションで形態素解析を行う必要があるので、MeCabを勉強してみようと思いました。 しかし、公式で提供されている言語バインディングにNode.jsは含まれていません(;´∀`)

公式以外で提供されているOSSも幾つかあるようですが、 外部コマンドを実行していたり、最終更新が5年前だったりと中々良さそうなものが見つかりませんでした...

www.npmjs.com

www.npmjs.com

そこで、今回は勉強ついでにMeCabのNode.jsバインディングを作ってみたいと思います。

環境

What is MeCab?

MeCab: Yet Another Part-of-Speech and Morphological Analyzer

MeCab京都大学情報学研究科−日本電信電話株式会社コミュニケーション科学基礎研究所 共同研究ユニットプロジェクトを通じて開発されたオープンソース 形態素解析エンジンです。 言語, 辞書,コーパスに依存しない汎用的な設計を 基本方針としています。 パラメータの推定に Conditional Random Fields (CRF) を用 いており, ChaSenが採用している 隠れマルコフモデルに比べ性能が向上しています。また、平均的に ChaSen, Juman, KAKASIより高速に動作します。 ちなみに和布蕪(めかぶ)は, 作者の好物です。

Node.js Native Addon (V8 Addon)

Node.jsではnode-gypというツールを使うことによって簡単にNative Addonを作ることが可能となっています。
www.npmjs.com

MeCab公式から提供されているC/C++ライブラリをこれでラッピングしてあげれば、Node.jsからMeCabが使えるようになります。
MeCab C/C++ ライブラリの使い方はこちら http://taku910.github.io/mecab/libmecab.html

node-mecab-native

実際に作ってみたNode.jsバインディングはこちらとなります。

github.com

C/C++の知識は殆どないので、色々とイケてない部分はあるかと思います...
間違ってる部分などありましたらご指摘いただけると助かります...

使い方

var MeCab = require('node-mecab-native');
var tagger = MeCab.Tagger('-Owakati');

// Parse text
tagger.parse('太郎は次郎が持っている本を花子に渡した。', function(err, res) {
  console.log(res);
});

// Parse text to Nodes
tagger.parseToNode('太郎は次郎が持っている本を花子に渡した。', function(err, res) {
  console.log(res);
});

// Get Dictionary Info
tagger.dictionaryInfo(function(err, res) {
  console.log(res);
});

まとめ

初めてNode.js Native Addonを作ってみましたが、思いの外簡単に作成することができました。
今まで、どの様にしてNative Addonが作られているのか分からなかったですが、
大まかな仕組みは理解できたので良かったかと思います。

ですが、やはりC/C++が分からないと出来ることが限られるので
勉強し直したほうが良さそうです(;・∀・)

Node.jsでリトライ処理を実装してみる

概要

HTTPリクエストが失敗した場合や、DBへの接続に失敗した場合など
何回かリトライ処理を含めたい場合ってありますよね?
そこで、Node.jsを使ったリトライ処理用モジュールを試しに作ってみようと思います。

環境

Node.js v4.4.7 OS Mac OS X 10.11.5

リトライ処理用モジュール

特に深い理由は無いですが、
Promiseベースのリトライ処理が出来るようなものを作ってみたいと思います。

作るもの

  • Promiseで結果が受け取れる
  • リトライ数が設定できる
  • リトライ間隔が設定できる
  • デバッグ用のログが出せる

promise-retry.js

'use strict';

class Retry {
  constructor() {
    this._maxTimes = 3;
    this._interval = 100; // ms
    this._debug = false;
  }

  /**
   * @public
   */
  maxTimes(count) {
    this._maxTimes = count;
    return this;
  }

  /**
   * @public
   */
  interval(ms) {
    this._interval = ms;
    return this;
  }

  /**
   * @public
   */
  debug(flag) {
    this._debug = flag;
    return this;
  }

  /**
   * @public
   */
  execute(fn) {
    let retryCount = 0;
    let maxTimes = this._maxTimes;
    return new Promise((resolve, reject) => {
      this.doRetry(fn, retryCount, resolve, reject);
    });
  }

  /**
   * @private
   */
  doRetry(fn, retryCount, resolve, reject) {
    let maxTimes = this._maxTimes;
    fn(retryCount)
      .then(function() {
        // Don't use arrow function.
        // In that case, `arguments` will be global scope variable.
        resolve.apply(Promise, arguments);
      })
      .catch((err) => {
        if (retryCount >= maxTimes) {
          reject(err);
        } else {
          retryCount++;
          setTimeout(() => {
            this.log(retryCount, err);
            this.doRetry(fn, retryCount, resolve, reject);
          }, this._interval);
        }
      });
  }

  /**
   * @private
   */
  log(retryCount, err) {
    if (this._debug) {
      let hrtime = process.hrtime();
      console.log(
        'retry cnt: %d, at: %d.%d, prev_err: %s',
        retryCount,
        hrtime[0],
        hrtime[1],
        err.message
      );
    }
  }
}

module.exports = () => new Retry();

リトライ処理をやってみる

では、作ったモジュールを使ってリトライ処理を実装してみたいと思います。

  • 最大リトライ数:5
  • リトライ間隔:20ms
  • デバッグモード:ON

index.js

'use strict';

const knex = require('knex')({ ... });
const retry = require('./promise-retry');

retry.debug(true)
  .maxTimes(5)
  .interval(20)
  .execute(retryCount => {
    return knex.first('*')
      .from('users')
      .where('id', 'umatoma');
  })
  .then(user => console.log(user))
  .catch(err => console.log(err));

地味にハマったポイント

ES6から導入された アロー関数 を使うと this と同様に arguments も束縛されてしまうんですね。
知りませんでした...

developer.mozilla.org

まとめ

単純な処理ではありますが、複数箇所で同じようなことをやる可能性も高いと思うので、
モジュール化して、使いまわすといいかもしれないですね。

Chef-Zero 入門 (Local Mode)

概要

Chef-Client/Server構成で最近開発を行っているのですが、
Cookbook等を開発している時に毎回Serverにアップロードしたりするのは面倒くさいです。。。

そこで、Chef Zeroを使用すればローカル(Chef-Client側)のみで完結するChef環境が作れるらしいので試してみたいと思います。

環境

What is Chef Zero?

https://www.chef.io/blog/2014/06/24/from-solo-to-zero-migrating-to-chef-client-local-mode/

Chef Zero is a full, in-memory, fast-start Chef server intended for development purposes; it persists no data to disk, nor does it have any authentication or authorization. Later, Chef Zero was rolled into Chef Client 11.8.0, thereby giving us Chef Client local mode, which you run with the --local-mode parameter to chef-client.

メモリ上で起動する簡易的なChef-Serverみたいなものらしい

Chef Zeroを試してみる

※ 下記コマンドは全てCentOS上で行っています。

まずは、Chef-DKをインストールします。
方法はいくつかあると思いますが、今回はwgetrpmを取ってきてインストールしたいと思います。

// Install chef-dk
$ wget https://packages.chef.io/stable/el/7/chefdk-0.14.25-1.el7.x86_64.rpm
$ sudo rpm -Uvh chefdk-0.14.25-1.el7.x86_64.rpm

続いて、Chef Repositoryを生成し、
Cookbook・Environment・Roleをそれぞれ作っていきます。

// Create chef-repo
$ chef generate repo chef-repo && cd chef-repo

// Create Cookbook
$ knife cookbook create -o ./cookbooks/ sample
$ vi cookbooks/sample/recipes/default.rb
$ cat cookbooks/sample/recipes/default.rb
file '/tmp/chef-sample.txt' do
  content node[:sample][:echo]
end

// Create env file
$ vi environments/dev.json
$ cat environments/dev.json
{
    "name": "dev",
    "description": "This is dev environment defined as JSON",
    "chef_type": "environment",
    "json_class": "Chef::Environment",
    "default_attributes": {
        "sample": {
            "echo": "HelloWorld!!"
        }
    },
    "override_attributes": {
    },
    "cookbook_versions": {
        "sample": "= 0.1.0"
    }
}

// Create role file
$ vi roles/app.json
$ cat roles/app.json
{
    "name": "app",
    "description": "This is app role defined as JSON",
    "chef_type": "role",
    "json_class": "Chef::Role",
    "default_attributes": {
    },
    "override_attributes": {
    },
    "run_list": [
        "recipe[sample]"
    ]
}

ここで、一旦local-modeでchef-clientコマンドを実行します。
これにより、chef-repoのnodesディレクトリ内にjsonファイルが生成されます。

// Setup node (create nodes/<hostname>.json file)
$ sudo chef-client -z

node情報を登録することができたので、nodeに対してrun_list・envを設定します。
今回はrun_list: role[app]、env: devとします。

// Add run_list env
$ sudo knife node run_list set localhost role[app] -z
$ sudo knife node environment set localhost dev -z
$ knife node show localhost -z
Node Name:   localhost
Environment: dev
FQDN:        localhost
IP:          192.168.0.5
Run List:    role[app]
Roles:
Recipes:
Platform:    centos 7.2.1511
Tags:

これで、local-modeで実行するための準備が整ったので
-z (--local-mode)オプションを付けて、chef-clientコマンドを実行します。

// Deploy
$ sudo chef-client -z
Starting Chef Client, version 12.10.24
resolving cookbooks for run list: ["sample"]
Synchronizing Cookbooks:
  - sample (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...
Converging 1 resources
Recipe: sample::default
  * file[/tmp/chef-sample.txt] action create
    - create new file /tmp/chef-sample.txt
    - update content in file /tmp/chef-sample.txt from none to f4e9ad
    --- /tmp/chef-sample.txt    2016-06-05 00:09:29.390000000 +0900
    +++ /tmp/.chef-chef-sample.txt20160605-13608-14dhopg   2016-06-05 00:09:29.390000000 +0900
    @@ -1 +1,2 @@
    +HelloWorld!!
    - restore selinux security context

Running handlers:
Running handlers complete
Chef Client finished, 1/1 resources updated in 01 seconds

無事、意図したtmpファイルを生成することができました。

// Check file
$ cat /tmp/chef-sample.txt
HelloWorld!!

まとめ

今まで、Cookbookを開発するときの良い方法がわからず、開発中でもChef-Serverに対してCookbookをアップロードしたりしていました。。。
Chef-Zeroであれば、いちいちServerに対してアップロードしなくても動作確認することができるようなので、今後はこちらを開発に使っていきたいと思います。

sequelizeでidに0が入ってしまう

sequelizeでidに0が入ってしまう

環境

概要

SequelizeでdialectとしてMySQLをした際に、 AUTO_INCREMENTカラムを含むテーブルに対してINSERTすると0が入ってしまう可能性がある。

Sequelize

SequelizeはNode.js用のORMである。

まず、下記のようなテーブルがあるとする

CREATE TABLE `Users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

ここで、sequelizeで定義したModelからnameカラムのみを指定してinsertすると 下記のようなSQLが発行される

INSERT INTO `Users` (`id`,`name`) VALUES (DEFAULT,'sequelize');

この時、設定によってはUsersに追加されるレコードは下記のようなものになる場合がある

select * from Users;
+----+-----------+
| id | name      |
+----+-----------+
|  0 | sequelize |
+----+-----------+

NO_AUTO_VALUE_ON_ZERO

MySQLではsql_modeという設定があります。

https://dev.mysql.com/doc/refman/5.6/ja/faqs-sql-modes.html

  1. サーバー SQL モードとは何ですか。
  2. サーバー SQL モードは、MySQL でサポートされる SQL 構文、および実行されるデータ妥当性チェックの種類を定義します。

sql_modeの1つにNO_AUTO_VALUE_ON_ZEROがあり、 これを設定すると、INSERT時にAUTO_INCREMENTカラムに対して0を指定しても自動採番されなくなります。

NO_AUTO_VALUE_ON_ZERO は AUTO_INCREMENT カラムの処理に影響します。通常は、NULL または 0 をカラムに挿入することによって、カラムの次のシーケンス番号を生成します。NO_AUTO_VALUE_ON_ZERO は 0 のこの動作を抑制するため、NULL のみが次のシーケンス番号を生成します。

https://dev.mysql.com/doc/refman/5.6/ja/sql-mode.html

また、MySQLでは数値型の初期値は0なので、DEFAULTは0にります。 https://dev.mysql.com/doc/refman/5.6/ja/data-type-defaults.html

なので、NO_AUTO_VALUE_ON_ZEROを設定している状態でidに対してDEFAULTを指定すると 必ずidに0が入るようになってしまいます。

対応

下記SQLsql_modeを確認、設定することができます。 状況に応じて、適切なsql_modeを設定すれば良さそう?

SELECT @@GLOBAL.sql_mode;
SELECT @@SESSION.sql_mode;

SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';

まとめ

Sequelizeを使うときは、sql_modeの設定を事前に確認しましょう。 MySQLに対しての知識が全然ないので、地味にハマりました。。。

RMagickでフォントが使用できない、unable to read font `(null)'

環境

unable to read font `(null)'

rmagickでテキストを表示しようと思ったら何故かunable to read font(null)'`とかいうエラーが。。。

rmagickで使用できるフォントを見てみると、使用可能なフォントが1つもない。。。

require 'RMagick'

Magick.fonts
=> []

設定ファイルを確認

設定ファイルを確認してみると、--with-gs-font-dir=/usr/local/share/ghostscript/fontsという部分がある。 どうやら、ここにあるフォントを読み取って使用しているらしいが、そんなディレクトリは存在しない。。。

$ cat /usr/local/lib/ImageMagick/config-Q16/configure.xml

ghostscript

無いのであればinstallしてみる

$ brew install ghostscript

無事フォントが読み込まれました

require 'RMagick'

Magick.fonts
=> [#<struct Magick::Font name="AvantGarde-Book", ...]

.btn-groupにtooltipを使うとデザインがズレる

環境

.btn-groupにtooltipを使うとデザインがズレる

.btn-group内のbuttontooltipを使用すると、 tooltipが発動した時に1px程度ズレが生じてしまいます。

f:id:umatomakun:20150321171659g:plain

<div class="btn-group" role="group">
    <button
        type="button"
        class="btn btn-default"
        data-toggle="tooltip"
        title="Tooltip">
        Left
    </button>
    <button type="button" class="btn btn-default">Middle</button>
    <button type="button" class="btn btn-default">Right</button>
</div>

何故ズレが生じているのか?

Bootstrapのソースコードを見てみると、隣接している.btnmargin-left: -1px;が指定されているようです。

github.com

.btn-group {
  .btn + .btn,
  .btn + .btn-group,
  .btn-group + .btn,
  .btn-group + .btn-group {
    margin-left: -1px;
  }
}

しかし、tooltipを使用するとtooltipの対象となっている.btnの後にDOMが追加されてしまい、.btn同士が隣接している状態ではなくなってしまいます。 そうすると、上記cssが適応されないのでレイアウトがズレてしまっているようです。

f:id:umatomakun:20150321203149p:plain

ズレが生じないようにする

対処法としてはdata-containerbody等に設定してあげればOKです。

<div class="btn-group" role="group">
    <button
        type="button"
        class="btn btn-default"
        data-toggle="tooltip"
        data-container="body"
        title="Tooltip">
        Left
    </button>
    <button type="button" class="btn btn-default">Middle</button>
    <button type="button" class="btn btn-default">Right</button>
</div>

参考

Bootstrapの公式サイトにもdata-containerを指定するよう載っていますね。

getbootstrap.com

When using tooltips on elements within a .btn-group or an .input-group, you'll have to specify the option container: 'body' (documented below) to avoid unwanted side effects (such as the element growing wider and/or losing its rounded corners when the tooltip is triggered).