ハッカーを目指す白Tのブログ

夏は白のTシャツを着ています。ラジオが好きです。(ラジオネーム: 隠れキリジダン)

React初心者のためのreact-routerの使い方

2015年1月、React ConferenceにてReact.jsでネイティブアプリが作れるようになると発表された。この発表を受けて最近何かと話題のReact.jsだが、リリースされたのは2013年であり、2009年にリリースされたAngularJSや、2010年のBackbonejsなどの他のjavascriptフレームワークに比べると新しく、まだネットに日本語の情報があまりない。ということで、React.jsのデファクトルーティングライブラリであるreact-routerの使い方についてまとめてみた。

1. そもそもreact-routerとは

react-routerは、React.jsのデファクトのルーティングライブラリである。Ember.jsのrouting APIを基に作られていて、ヘッダー、サイドバー、フッターなどが複雑にネストされたビューのルーティングの設定を容易にしてくれる。なぜ容易になるのかというと、ビューのコンポーネント(ヘッダー、サイドバー、フッター等)のネスト構造と同じように、ルーティングの設定もネスト構造で定義することで、両者の関係性が単純化されるからである。

2. react-routerのインストール方法

それでは、さっそくreact-routerを触ってみよう。インストール方法はいたって簡単で、パソコンにnode.jsさえインストールされていれば、

npm install react-router

でインストールできる。

また、以下説明する例を試すにはjsファイルの先頭において以下をrequireしなければならない。

var Router = require('react-router');
var Route = Router.Route;
var NotFoundRoute = Router.NotFoundRoute;
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
var RouteHandler = Router.RouteHandler;

これは、CommonJSという、JavaScriptで様々なアプリケーションを作るための標準仕様に則っている。

3. react-routerの使用例

では、実際にどのように使われているのか。Dashboard・Inbox・Calenderの3つのページからなる、小さなWEBアプリケーションを例に説明する。以下の図が、そのアプリケーションのUIの設計図であり、全てのページで共通のヘッダーが使われるとする。

f:id:beck23:20150218183748p:plain
このアプリケーションを、react-routerを使わずに開発する場合、そのルーティングを定義するコードは以下のようになる。

var Header = React.createClass({
  render: function () {
    return (
      <header>
        <ul>
          <li><a href="/">Dashboard</a></li>
          <li><a href="/inbox">Inbox</a></li>
          <li><a href="/calendar">Calendar</a></li>
        </ul>
        Logged in as Jane
      </header>
    );
  }
});

var DashboardRoute = React.createClass({
  render: function () {
    return (
      <div>
        <Header/>
        <Dashboard/>
      </div>
    );
  }
});

var InboxRoute = React.createClass({
  render: function () {
    return (
      <div>
        <Header/>
        <Inbox/>
      </div>
    );
  }
});

var CalendarRoute = React.createClass({
  render: function () {
    return (
      <div>
        <Header/>
        <Calendar/>
      </div>
    );
  }
});

otherRouter.route('/', function () {
  React.render(<DashboardRoute/>, document.body);
});

otherRouter.route('/inbox', function () {
  React.render(<InboxRoute/>, document.body);
});

otherRouter.route('/calendar', function () {
  React.render(<CalendarRoute/>, document.body);
});

見て分かるように、この場合各コンポーネントで共通のヘッダーを呼びださなければならず、コードの重複が増えてしまう。

これに対しreact-routerを使うと、ルーティングを定義するコードは以下のようになる。

var Router = require('react-router'); 
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
var Route = Router.Route;
var RouteHandler = Router.RouteHandler;

var App = React.createClass({
  render: function () {
    return (
      <div>
        <header>
          <ul>
            <li><Link to="app">Dashboard</Link></li>
            <li><Link to="inbox">Inbox</Link></li>
            <li><Link to="calendar">Calendar</Link></li>
          </ul>
          Logged in as Jane
        </header>

//<RouteHandler/>コンポーネントをrenderすることで、
//ルーティングで定義された全てのアクティブな子コンポーネントを呼び出す。        
        <RouteHandler/>
      </div>
    );
  }
});

//アプリケーションのurlの階層構造をネストされた<Route/>タグで定義し、
//各<Route/>のhandlerにReactのコンポーネントを紐付け、
//render時には、アクティブな<Route/>のhandlerがコンポーネントを呼び出し、
//それらが融合されて表出される。
var routes = (
  <Route name="app" path="/" handler={App}>
    <Route name="inbox" handler={Inbox}/>
    <Route name="calendar" handler={Calendar}/>
    <DefaultRoute handler={Dashboard}/>
  </Route>
);

//React RouterはリクエストされたURLに最も深くマッチする<Route/>を探し出し、
//その木構造の中での枝に含まれる全ての<Route/>をアクティブにする。
Router.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});

例として挙げたアプリケーションが単純なUIなため、コード量としてはさほど変わってはいないが、react-routerを使用することで、一箇所でルーティングを管理することが可能となり、ネストされたUIを作るときの生産性、保守性が上がる。また、ルーティングを設定しないとビューが表出されないことから、URLの構造を先に考えつつ開発を行う事ができる。これらのメリットは、UIが複雑になればなるほど役立つものとなる。

4. react-routerのコンポーネント一覧


1. RouteHandler

<RouteHandler/>は、その子要素のアクティブな <Route />のハンドラーを呼び出す。

2. Link

<Link >はtoオプションで指定したnameの <Route >のハンドラーを呼び出す。

// このようなルーティングの設定があった場合
<Route name="user" path="/users/:userId"/>

// 以下のリンクコンポーネントで、上記の<Route/>のハンドラーを呼び出せる。
<Link to="user" params={{userId: "123"}}/>

paramsオプションに対しては、渡したいRouteのpathのダイナミックセグメントと一致するname,valueのオブジェクトを渡す。

//ダイナミックセグメントとは、pathの一部で:から始まるId等を指定している箇所のこと
<Route name="user" path="/users/:userId"/>
//このRouteの場合、:userIdの部分を示す。

3. Route

<Route handler={App}>
  <Route name="about" handler={About}/>
//pathが指定されていない場合、pathはnameから自動的に補完される。
//この場合<Route name="about" path="about" handler={About}/>となる。
  <Route name="users" handler={Users}>
    <Route name="user" handler={User} path="/user/:id"/>
  </Route>
</Route>

<Route >はそのネスト構造によりアプリケーションのルーティングを定義しとhandlerでviewの階層構造を定義する。

<Route />のオプション
name: routeの名前。Linkコンポーネントで使われる。
path: ドメイン以下のurl。未定義の場合、nameから定義され、nameも未定義の場合、デフォルトで'/'になる。
handler: routeがアクティブなときに呼び出されるコンポーネント

4. DefaultRoute

<DefaultRoute>は親のrouteのpathと完全に一致する時アクティブになる。

<Routes>
  <Route path="/" handler={App}>

    <!--
  pathが'/'の時、以下の<DefaultRoute handler={Home}/>がアクティブになる。
    -->
    <DefaultRoute handler={Home}/>

    <Route name="about" handler={About}/>
    <Route name="users" handler={Users}>
      <Route name="user" handler={User} path="/user/:id"/>

      <!--pathが'/user'のとき、以下の <DefaultRoute handler={Home}/>がアクティブになる。-->
      <DefaultRoute name="users-index" handler={UsersIndex}/>

    </Route>
  </Route>
</Routes>

5. NotFoundRoute

<lNotFoundRoute>は、リクエストされたurlが親の<Route/>のpathとマッチするのに、そのいずれの子<Route/>ともマッチしない場合にアクティブになる。エラーページの作成等に使える。

<Route path="/" handler={App}>
  <Route name="course" path="course/:courseId" handler={Course}>
    <Route name="course-dashboard" path="dashboard" handler={Dashboard}/>

    <!--  `/course/123/foo` というurlがリクエストされた場合、
    以下の  <NotFoundRoute />がアクティブになる。-->
    <NotFoundRoute handler={CourseRouteNotFound} />
  </Route>

  <!--  `/flkjasdf`というurlがリクエストされた場合、
    以下の  <NotFoundRoute />がアクティブになる。 -->
  <NotFoundRoute handler={NotFound} />
</Route>

6. Redirect

<Redirect>は、fromオプションで設定したpathへのリクエストがあったときに、toオプションで指定したpathにリダイレクトするようにする。

<!--
  開発中にurlの名称等を変更したくなった場合に使える。
   `/get-in-touch`というpathにリクエストを送ると 、
 `/contact`というpathをrenderするようにする。
-->
<Route handler={App}>
  <Route name="contact" handler={Contact}/>
  <Route name="about-user" path="about/:userId" handler={UserProfile}/>
  <Route name="course" path="course/:courseId">
    <Route name="course-dashboard" path="dashboard" handler={Dashboard}/>
    <Route name="course-assignments" path="assignments" handler={Assignments}/>
  </Route>

  <!-- `/get-in-touch` -> `/contact` -->
  <Redirect from="get-in-touch" to="contact" />
</Route>

7. State

アクティブなparams,query,routeの情報を必要とするコンポーネントのためのmixin。ダイナミックセグメントを含む<Route>のhandlerが呼び出すコンポーネント等で使われる。

// route
<Route name="user" path="user/:name" handler={User} />

// handler
var User = React.createClass({
//mixinは、mixinsに配列で渡す。
  mixins: [ Router.State ],

  render: function () {
//アクティブなparamsの情報を取得している。
    var name = this.getParams().name;
    return (
      <div>
        <h1>{name}</h1>
      </div>
    );
  }
});

<State />のインスタンスメソッド
getPath: アクティブなURLを返す。
getParams: アクティブなparamsを返す。
getQuery: アクティブなqueryを返す。

5. Routerのrunメソッド

最後にRouterのrunメソッドについて紹介する。runメソッドは、リクエストされたURLとマッチするrouteを特定し、それらのhandlerに紐づくコンポーネントをラップし、コールバックにhandlerとstateを引き渡す。

Router.run(routes, [location,] callback(handler, state))

//第二引数のHistoryLocationによって、HTML5のhistory APIを使えるようになる
//第三引数のコールバック関数である無名関数の第一引数はHandlerで、
//マッチした<Route/>の全てのコンポーネントがラップされている
Router.run(routes, HistoryLocation, function (Handler) {
  // whenever the url changes, this callback is called again
  React.render(<Handler/>, document.body);
});

参照URL

rackt/react-router · GitHub

以上

AngularJSのディレクティブについて

明日AngularJSのハッカソン(AngularJS ハッカソン - AngularJS Japan User Group | Doorkeeper)に参加することになったので、AngularJSを一から勉強してみることにした。そこで今回は、AngularJSのディレクティブについて解説していきたい。

1. AngularJSのディレクティブとは

AngularJSのディレクティブとは、双方向バインドを実現するための仕組みである。

AngularJSで言うところの双方向バインドでは、具体的に以下の2つの処理が行われる。
1.Model(=scope)の変更をView(=DOM)へ反映する
2.Viewの変更時にscopeの値を変更する

具体的に、双方向バインドは、AngularJSのHTMLコンパイラが、DOM要素(クラス、属性、HTMLタグ、コメント等)からディレクティブを検出し、そこに対応するスクリプトを埋め込むことで実現される。

AngularJSには標準で組み込まれているディレクティブが多数ある。
例えば、テキストボックスの入力値とscope.nameを紐付ける場合、htmlに下記を記述するだけで、ユーザの入力値がscopeへ即時反映される。

<input type="text" ng-model="name" />
<span>{{name}}</span>

他にも、ngBind、 ngRepeat や ngClassなどのディレクティブがある。詳しくは、こちらの標準ディレクティブ一覧を参照して頂きたい。(https://docs.angularjs.org/api/ng/directive)

2. カスタムディレクティブ

実装したい機能が標準のディレクティブを使って出来ない場合は、自分で自由にディレクティブを作る事ができる。
これをカスタムディレクティブと言う。以下、作る際の注意点を二つ挙げる。

1.ディレクティブの定義には、angular.moduleのdirective関数を利用すること
2.ディレクティブをスクリプトファイルで定義する際はキャメルケースを用いるが、テンプレートで利用する時はハイフン繋ぎ(アンダースコア、コロンでも良い)にすること

以下、簡単なカスタムディレクティブを作ってみた。例としてあまり適切でないかもしれないが、下記を実行すれば、「初めてのディレクティブ」が表示されたはずだ。

//myModule.js
var myModlue = angular.module('myModlue', []);
myModule.directive('firstDirective', function(){
  return {
    template: '<span>初めてのディレクティブ</span>'
  }; 
});
<!doctype html>                                                 
<html ng-app="myModule">                                        
<head>                                                          
<script type="text/javascript" src="angular.min.js"></script>   
<script type="text/javascript" src="myModule.js"></script>      
</head>                                                         
<body>                                   
<div first-directive></div>                                     
</body>                                                         
</html>                                                         

3. ディレクティブのオプションでできることまとめ

カスタムディレクティブには、様々なオプションが用意されている。以下、それぞれのオプションでできることを一覧で示す。
なお、各オプションの詳しい解説は$compile | AngularJS 1.2 日本語リファレンス | js STUDIOを参照していただきたい。


1. ディレクティブ同士の優先順位を指定する
2. 新しいscopeをディレクティブ内で指定する
3. ひとつのディレクティブで複数のDOM要素を指定する
4. 指定するディレクティブよりpriorityの低いディレクティブの適用を外す
5. ディレクティブのscopeをcontrollerで使用可能にする
6. コントローラのコンストラクタ関数を定義する
7. 他のディレクティブで生成されたcontrollerを使う
8. ディレクティブがもつtemplate内で使えるディレクティブのcontrollerのエイリスを指定する
9. templateでのディレクティブの指定方法を定義する
10. ディレクティブがもつtemplateをhtml文字列として指定する
11. ディレクティブがもつtemplateをhtmlファイルのパスで指定する
12. templateでディレクティブを指定する際、子要素を挿入可能にする
13. scopeとtemplateを結びつける処理を指定する(compile)
14. scopeとtemplateを結びつける処理を指定する(link)

1. ディレクティブ同士の優先順位を指定する

priority: 10 //数字が大きければ大きいほど、優先度が高く先にコンパイルされる。

2. 新しいscopeをディレクティブ内で指定する

scope: true //true にすることで、新しいscopeが作成される

scope: {                              //hashオブジェクトを渡すことで、親scopeを継承しない'隔離scope'が作成される
      myDirective: '=',          // '='は、双方向バインディング
      onChange: '&',            // '&'は、指定した値を親scopeのcontextで実行する
      title: '@'                      // '@'は、ローカル(このディレクティブの)scopeの値をDOMに追加する  
    }       

3. ひとつのディレクティブで複数のDOM要素を指定する

multiElement : true   

4. 指定するディレクティブよりpriorityの低いディレクティブの適用を外す

terminal : true //priorityとともに使う。trueに設定した場合、そのディレクティブよりpriorityが小さいディレクティブは実行されない。

5. ディレクティブのscopeをcontrollerで使用可能にする

bindToController : true 

”隔離scope"がディレクティブで作成されて、controllerAsが使われているとき、bindToController : trueにすると、その作成された"隔離scope"が、controllerAsで指定されたcontrollerがインスタンス化された時に使用可能になる。

6. コントローラのコンストラクタ関数を定義する

controller : function($scope) {
        var panes = $scope.panes = [];
        $scope.select = function(pane) {
          angular.forEach(panes, function(pane) {
            pane.selected = false;
          });
          pane.selected = true;
        };

7. 他のディレクティブで生成されたcontrollerを使う

require : 'sample-directive' //ディレクティブ名を指定する

8. ディレクティブがもつtemplate内で使えるディレクティブのcontrollerのaliasを指定する

controllerAs : 'sample-controller'

9. templateでのディレクティブの指定方法を定義する

restrict :  'E'     //E:タグ  ex)<sample-directive>

//他にも、
// A : 属性      ex)   <div sample-directive>
// C : クラス      ex)  <div class="sample-directive;">
// M : コメント  ex)  <!-- directive: sample-directive -->

10. ディレクティブがもつtemplateをhtml文字列として指定する

template : '<div red-on-hover>{{delete_str}}</div>'

11. ディレクティブがもつtemplateをhtmlファイルのパスで指定する

templateUrl : 'directive.html'         //loadは非同期で行われる

12. templateでディレクティブを指定する際、子要素を挿入可能にする

transclude : true

13. scopeとtemplateを結びつける処理を指定する(compile)

compileと14のlinkオプションは同時に使うことが出来ない。詳しい使い方については、JavaScript - AngularJSのDirectiveを理解する. - Qiitaを参照頂きたい。

compile : function compile(tElement, tAttrs, transclude) {
      return {
        pre: function preLink(scope, iElement, iAttrs, controller) { ... },
        post: function postLink(scope, iElement, iAttrs, controller) { ... }
      }
    }

14. scopeとtemplateを結びつける処理を指定する(link)

link : function link(scope, element, attrs) {
    var format,
        timeoutId;

    function updateTime() {
      element.text(dateFilter(new Date(), format));
    }

    scope.$watch(attrs.myCurrentTime, function(value) {
      format = value;
      updateTime();
    });

    element.on('$destroy', function() {
      $interval.cancel(timeoutId);
    });

    // start the UI update process; save the timeoutId for canceling
    timeoutId = $interval(function() {
      updateTime(); // update DOM
    }, 1000);
  }

参照URL


JavaScript - AngularJSのDirectiveを理解する. - Qiita
https://docs.angularjs.org/api/ng/service/$compile
https://docs.angularjs.org/guide/directive
JavaScript - AngularJSのdirectiveとは - Qiita

以上

jQueryのPromiseとDeferredとは

javascriptで非同期処理がからむと、コールバックが乱立してしまい、エラーに対する例外処理を正確に書くことが難しくなった経験はないだろうか。今回は、JavaScript複数の連続する非同期処理を上手く扱うためのjQueryの標準モジュールであるjQuery.Deferredについて、その概念や使い方を解説していく。

1. jQuery.Deferredとは

jQuery.Deferredは、複数の連続する非同期処理を単純で分かり易く記述できるようにすると述べたが、それはどのようにして行われるのか。それは、複数の非同期処理ひとつひとつにPromiseオブジェクトを割り当て、それを伝播させていくことで実現される。それでは、Promiseオブジェクトとは何か。Promiseオブジェクトは、割り当てられた非同期処理が成功しているか否か等の状態の情報を持つオブジェクトである。その状態の種類は3つあり、Deferred.state()で確認することができる。

Promiseオブジェクトの状態(Deferred.state()の返り値)

1. pending : 処理が未完了。
2. resolved : 処理が成功
3. rejected : 処理が失敗

Promiseオブジェクトは、生成されたときはpendingの状態で、割り当てられた処理が完了した際に、処理が成功した場合はresolved、処理が失敗した場合はrejectedに状態に変わる。そして、resolvedまたはrejectedの状態のPromiseオブジェクトがreturnされたときに実行されるコールバックは、以下の2つのメソッドで指定できる。(なお、Promiseオブジェクトの状態の変化やPromiseオブジェクトのreturnは、ajaxやGETjsonなどのjQueryの標準の非同期処理には実装されているが、それ以外の非同期処理においては、手動で実装しなければならない。)

1.状態がresolvedのPromiseオブジェクトがreturnされたときのコールバックは、.done()メソッドで呼び出される。
2.状態がrejectedのPromiseオブジェクトがreturnされたときのコールバックは、.fail()メソッドで呼び出される。

さて、Promiseオブジェクトは、Deferredオブジェクトに内包されるものである。よって、Deferredオブジェクトのresolveメソッドを呼び出すと、内包するPromiseオブジェクトの状態がresolvedになり、Deferredオブジェクトのrejectメソッドを呼び出すと、内包するPromiseオブジェクトの状態がrejectedになる。

//1秒後にHello!を出力するDeferred対応関数。必ずresolveする

function delayHello()
{
  var d = new $.Deferred;     //Deferredオブジェクトを生成
  setTimeout(function(){
    console.log('Hello!');
    d.resolve();       //Promiseオブジェクトの状態をresolvedに変更
  }, 1000);
  return d.promise();    //Promiseオブジェクトをreturn
}

//1秒後にエラーを発生させるDeferred対応関数。必ずrejectする

function delayError() {
  var d = new $.Deferred;    //Defferedオブジェクトを生成
  setTimeout(function(){
    d.reject('Error!!');              //Promiseオブジェクトの状態をrejectedに変更
  }, 1000);
  return d.promise();            //Promiseオブジェクトをreturn
}

//.done()と.fail()

var promise = delayHello();
promise.done(function(){ /* resolvedで実行 */ }); //.done()メソッドによるコールバックが実行される
promise.fail(function(e){ /* rejectedで実行 */ });

//本題とは、関係ないがfailのメソッドで定義されているコールバック(function(e){ /* rejectedで実行 */ })において、関数の引数に引き渡されているeは、Errorオブジェクトである。

2. .then()メソッドと.when()メソッド

ここで、jQuery.Deferredの一番の特徴とも言える、.then()と$.when()について解説する。これらは、複数の連続する非同期処理の連結、その例外処理を驚くほど簡単にする。まず、.then()メソッドは、Promiseオブジェクトを返す非同期処理に対して呼び出すことで、非同期処理が成功または失敗した場合の両方に対するコールバックを指定できる。.then()メソッドの第一引数に成功した場合、第二引数に失敗した場合の処理を記述する。この.then()メソッドは、.done()メソッドや.fail()メソッドと違い、新たにPromiseオブジェクトを生成し、returnするので、非同期処理を連続させことができる。

delayHello()
.then(function(){ /* resolvedで実行 */ }, function(e){ /* rejectedで実行 */ }

//↓これは直列連結の書き方として間違い
delayHello().done(delayHello).done(delayHello); //1つめのdelayHello()がPromiseオブジェクトをreturnしたタイミングで、ふたつのコールバックが同時に呼び出されることになる

また、$.when()を使うことで、複数の非同期処理を並行して実行することができる。$.when()は、併記したDeferredの非同期処理の全てが、resolvedのPromiseオブジェクトをreturnした場合にのみ、statusがresolvedの新たなPromiseオブジェクトをreturnする。また、併記した非同期処理の中で、ひとつでもrejectedのPromiseオブジェクトをreturnするものがあった場合は、statusがrejectedの新たなPromiseオブジェクトをreturnする。$.when()も、新たにPromiseオブジェクトを生成し、returnしているので、非同期処理を連続して書くことができる。

$.when(delayHello(), delayHello(), delayHello())
.done(/*全てresolvedで実行*/);    //全てresolvedのPromiseオブジェクトを返すので実行される

.then()と$.whenによる非同期処理の連結は、その処理の順番を容易に変えることができることも大きなメリットである。

3. 連続する非同期処理の関数化

jQuery.Deferredは、さらに連続する非同期処理を関数化してまとめることができる。これによって、コードの可読性が増し、また再利用が可能になる。
以下のように、複雑な処理も、関数化することによって格段に、読みやすく、再利用しやすいコードになる。

delayHello()
.then(delayHello)
.then(function(){
  return $.when(delayHello(), delayHello(), delayHello());
})
.then(delayHello)
.then(function(){
  return $.when(delayHello(), delayHello(), delayHello());
})
.then(delayHello);

関数化すると、

//一部分だけ抜き出して関数化
function delayHelloParallel() {
  return $.when(
    delayHello(), delayHello(), delayHello()
  )
  .then(delayHello);
}

//delayHelloParallelを使って少々簡略化
delayHello()
.then(delayHello)
.then(delayHelloParallel)
.then(delayHelloParallel)

4. jQuery.Deferredを使う場面

それでは、どんな時にjQuery.Deferredを使うべきなのか。
それは、JavaScript の非同期処理を、順序立てて、連続して、複数回実行したいときである。
例えば、キーワードとして検索した住所の気象情報を取得する処理をjQueryで実装したい場合、
1. Yahoo!ジオコーダAPIで、キーワードとして検索した住所の緯度経度を取得し、
2. 1のAPIのレスポンスが返ってき次第、その緯度経度をもとに、気象情報APIで、気象情報を取得する
必要がある。この際、1の処理が終わってから、2の処理と、順番に処理を実行する必要があり、jQuery.Deferred を使うことで、この一連の処理を簡単に、分かり易く、書くことができる。

jQueryの代表的な非同期処理であるajaxについては、以下を参照して頂きたい。

参照URL


爆速でわかるjQuery.Deferred超入門 - Yahoo! JAPAN Tech Blog
jQueryのDeferredとPromiseで応答性の良いアプリをー基本編 | ゆっくりと…
結局jQuery.Deferredの何が嬉しいのか分からない、という人向けの小話 - Qiita

以上

RailsのPaperclipについてまとめてみた

Railsを使って、SNS上で共有された画像を活用したWEBサービスを作ろうと思い、画像ファイルの管理を担うgem、Paperclipについて調べてみた。

1. Paperclipとは

thoughtbot/paperclip · GitHub
Paperclipは、RailsActiveRecord用のライブラリである。Railsのアプリケーションで、ユーザーがアップロードした画像等のファイルについての煩雑な設定、すなわち、保存や削除などの設定を簡単に行うことができるようになる。
Paperclipは、ImageMagick(http://www.imagemagick.org/)と共に使うことを前提としていて、アップロードした画像を縦横比そのままでリサイズしたり、長方形の画像を、中央を起点にして正方形に切り取ったりすることができる。以下、Paperclipの使い方について説明する。

2. Paperclip::ClassMethods#has_attached_file

まず、migrationで生成した添付ファイルの保存先(attachment)へのパスをモデルに伝えるメソッド、has_attached_fileについて説明する。

has_attached_file( name, options = {} )

#以下はsample、モデルに記述する
has_attached_file :sample_image, :url =>  "/:class/:attachment/:id/:style_:filename",
                                            :styles => { :normal => "100x100#", :large => '200x200#' },
                                            :storage => :s3

このクラスメソッドは、前述のとおり、呼び出されたクラスに対して添付ファイルが保存されるべきパスを伝えるものである。添付ファイルを管理するPaperclip::Attachment オブジェクトを返す。Railsで、他のカラムに属性を定義するのと同じように添付ファイルについても、属性のような情報を定義できるようにした。新しいファイルがアップロードされると、サムネイルが作られ、saveメソッドが呼び出されることで保存される。以下、このメソッドのオプションでできることを一覧で示す。

has_attached_fileのoptionsでできること一覧

1. 添付ファイルを保存するパスを指定

:url => "/:class/:attachment/:id/:style_:filename"

urlオプションに対して、ファイルの保存先を絶対パスで指定。ドメインまで指定する必要はない。デフォルトは “/system/:attachment/:id/:style/:filename”.
また、ブラウザで表示するURLを指定することも可能。その場合、以下のようにURLをpathオプションに渡す。

path: "#{Rails.root}/public/system/:class/:id/:attachment/:style.:extension"

2. 添付ファイルがない場合のデフォルト画像のパスを指定

default_url:  "/images/default_:style_avatar.png"
User.new.avatar_url(:small) # => "/images/default_small_avatar.png"

default_urlオプションにパスを渡す。デフォルトは、“/:attachment/:style/missing.png

3. サムネイルのスタイルの指定

:styles => { :normal => "100x100#", :large => '200x200#' }

このstylesオプションに、スタイル名がkeyで、指定したいスタイルがvalueのhashを渡す。スタイルの指定方法については、ImageMagickのサイトに記載されている(www.imagemagick.org/script/command-line-options.php#resize). ImageMagickのスタイルの設定に加え、さらに “#” オプションが追加されていて、 これは指定されたサイズに最大限fitするように画像をリサイズし、中央を起点に不要な部分を切り取るという設定である。(example: “50x50#”)、 デフォルトでは、サムネイルが生成されない.

4. デフォルトのURLで使われるサムネイルのスタイル名の指定

has_attached_file :avatar, :styles => { :normal => "100x100#", :large => '200x200#' },
:default_style => :normal
user.avatar.url # => "/avatars/23/normal_me.png"

default_styleオプションに、stylesオプションで指定したスタイル名を渡す。つまるところ、ユーザーがファイルをアップロードする際に、スタイル名を指定しなかった場合に適用されるスタイルのスタイル名を指定できるオプションである。

5. ファイルを添付出来ない場合のコマンドラインエラーを表示するか否かの指定

whiny : false

whinyオプションに真偽値を渡す。デフォルトはtrue。コマンドラインエラーでアップロードファイルをpost_process出来ない場合、エラーを表示するか指定できる。

6. どのように画像を変換するか指定

has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" }
:convert_options => {
:all => "-strip",
:negative => "-negate"
}

convert_optionsオプションに、スタイル名がkeyで、画像の変換オプションがvalueのhashを渡す。画像の変換オプションの例として、'-strip' オプションは、画像からExif dataを取り除くために使われ、 “-depth 8”オプションは、ビット深度を指定するのに使われる。詳しくは、ImageMagickの convert documentation(ImageMagick: Command-line Tools: Convert) に記載。 allをkeyとするhashのvalueにoptionを指定することで、作成される全てのスタイルのサムネイルに適用するオプションを指定できる。

7. ファイルが保存されるストレージの指定

storage : :s3

storageオプションに、ストレージ名を渡す。 ストレージの選択肢としては 、:filesystem または、 :s3がある。 デフォルトは、 :filesystem.

3. 添付ファイルに対するvalidation

Paperclip::ClassMethods#validates_attachment_content_type

このクラスメソッドは、添付ファイルのファイル形式に対して、ActiveRecordのようなvalidationをかけることができる。

validates_attachment_content_type :sample_image, content_type:  ["image/jpeg", "image/gif", "image/png"],
                                                                                 message: 'ファイル形式が不正です。'

content_typeオプションに、許可するファイル形式を一つまたは、複数を配列で渡す。各typeは、String または a Regexpで指定可能. IEは、予期せぬタイプでファイルがアップロードされる場合があるから注意が必要。たとえば、JPEGはimage/pipeg、PNGはimage/x-pngでアップロードされるので、マッチする時気をつけよう。デフォルトは全てのタイプを許可。
messageオプションに、アップロードできないcontent type のfile がアップロードされた時に表示されるメッセージを渡す。

Paperclip::ClassMethods#validates_attachment_presence

このクラスメソッドは、添付ファイルの有無に対して、ActiveRecordのようなvalidationをかけることができる。

validates_attachment_presence :sample_image

Paperclip::ClassMethods#validates_attachment_size

このクラスメソッドは、添付ファイルのサイズに対して、ActiveRecordのようなvalidationをかけることができる。

validates_attachment_size :sample_image, :less_than=>1.megabyte

inオプションで、許可するファイルサイズの範囲を指定できる。
less_thanオプションで、許可する最大のファイルサイズを指定できる。
greater_thanオプションで、許可する最小のファイルサイズを指定できる。
messageオプションで、表示するエラーメッセージを指定できる。

添付ファイルの削除、論理削除

添付ファイルの削除

添付ファイルを削除するには、attributeをnilにして、saveすると削除される。

@user.avatar = nil
@user.save

また、添付ファイルが紐づくオブジェクトが削除されると、添付ファイルも削除される。

添付ファイルの論理削除

acts_as_paranoid, paranoia 等の論理削除用のgemと連動して、論理削除が行えるように、Paperclipではオプションが用意されている。
has_attached_fileのメソッドのoptionで、preserve_filesをtrueにすれば、添付ファルの論理削除が正常に行われる.

has_attached_file :some_attachment, {
:preserve_files => "true",
}

s3の設定

アマゾンの S3 に保存する際は、まず aws-sdk というgem を使う。

#Gemfile に、以下の行を追加。
gem 'aws-sdk', '~> 1.5.7'

その後、has_attached_file のオプションで、s3を指定。詳しくは、Paperclip::Storage::S3 documentation(Module: Paperclip::Storage::S3 — Documentation for paperclip (4.2.0))。

Paperclipと他のgemの比較

Paperclipと他のgemの比較については、
Paperclip と CarrierWave を結構マジメに比較してみた - 彼女からは、おいちゃんと呼ばれています

参照サイト:
Module: Paperclip::ClassMethods — Documentation for paperclip (2.3.8)
RailsでPaperclipを使ってみたメモ [俺の備忘録]

以上

jQueryのajaxについてまとめてみた

ユーザーにとって、ストレスレスで操作性の高いWEBアプリケーションを作るのにajaxは欠かせない技術である。今日は、jQueryajaxについてまとめてみた。

1. そもそもajaxとは?

ajaxとは、WEBブラウザに実装されているJavaScriptのHTTP通信機能使って、Webページのリロードを伴わずにサーバーとデータのやりとりを行う処理である。ajaxは、asynchronous JavaScript + XMLの略である。asynchronousは「非同期」という意味であるが、ajaxは、非同期・同期両方の処理方式に対応していて、字面とは違い特徴は画面遷移を伴わない通信手段であるということに限定される。ユーザの操作と並行して、画面遷移なしでサーバと通信を行うことで、サーバの存在を感じさせないような、動的なWebアプリケーションを実現することができる。そんなajaxには、ブラウザの互換性やコードとデザインの分離を簡単に実現するいくつかのフレームワークがある。今日は、その中から、jQueryajaxについて説明していきたい。jQueryでは、

$.ajax();

で、簡単にajax処理を実行することができる。

2. jQueryajaxメソッドのオプションでできること一覧

jQueryajaxメソッドは、複雑なajax処理を実装するための様々なオプションが設けられている。オプションは、以下のようにajaxメソッドの引数にhashで渡す。

$.ajax({
  type : 'GET',
  url : '/hoge/hoge.html',
  data: 'foo',
  dataType: 'json'
});

まずは、このオプションでできることを紹介していきたい。以下、その一覧。

1. HTTPリクエストの種類を指定する

//デフォルトは、'GET'
type: 'POST' //HTTPリクエストの種類をpostにする

PUT, PATCH,DELETE等も指定することができるが、全てのブラウザでサポートされていないため、推奨されない。

2. 送信するdataの内容を指定する

//形式は、hash、string、またはarray.
data : 'hoge'

3. 通信方式を同期通信にする

//デフォルトは、trueで、非同期通信。
async: false

そもそも同期通信とは、リクエストが送られている間、ブラウザ内で他のいかなるアクションも実行できない通信方式のことで、非同期通信とは、リクエストが送られている間も、他のアクションを実行できる通信方式のことである。

4. サーバーから期待するレスポンスのデータ形式を指定する

dataType : 'json'
//併記して複数のデータ形式を期待することも可能
dataType : 'json text'

期待するレスポンスのデータ形式の種類としては、
・html
・text
xml
json
・script
jsonp
が挙げられる。
この中で、jsonpとは、クロスドメインajax通信で扱うデータ形式で、取得するjsonを引数とする関数の形を取る。

//jsonデータ
{'key' : 'value', 'key2' : 'value2'}
//jsonpデータ(jsonを引数とする関数)
getJSONP({'key' : 'value', 'key2' : 'value2'})

jsonpデータは、クロスドメインXMLHTTPRequestにおける、セキュリティーの問題を解決する。

5. ajaxリクエストを送る前のコールバックを指定する

beforeSend : function(jqXHRオブジェクト、settings) {
  実行したい処理
}

例えば、9時〜17時の営業時間外の場合、ajaxの処理を実行しないようにするには、以下のように書ける。

// 通信前に時刻をチェックし、時間外である場合は通信を中止
beforeSend: function(xhr) {
   var d = new Date();
   if (d.getHours() < 9 || d.getHours() > 17) {
     return false;
   }
 }

return false、または、jqXHR.abort()で、リクエストを中断することができる。

6. HTTP認証が必要な通信を行う

//usernameとpasswordの指定を行う
username : 'user',
password : 'password'

7. クロスドメイン処理を行う

//デフォルトはfalse
crossDomain : true

クロスドメイン処理とは、その名の通り異なるドメイン間のXMLHTTPRequestのことである。

8. 通信結果をcacheしない

//デフォルトは、true
cache : false

キャッシュとは、一度アクセスしたサイトのデータをブラウザで一時的に保管し、次回より同じページにアクセスした際の表示を速くする仕組みである。
ajax通信において、cacheをfalseにすべき処理とは一体どのようなものであろう。

3. jQueryajax通信後の処理

次に、ajax通信後の処理について説明していきたい。jQueryでは、主に以下の3つのコールバックを用いて、ajax通信後の処理を指定する。

1. jqXHR.done(function( data, textStatus, jqXHR ) {});
 →ajax通信成功後に呼び出される
2. jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});   
 →ajax通信失敗後に呼び出される
3. jqXHR.always(function( data|jqXHR, textStatus, jqXHR|errorThrown ) { }); 
 →ajax通信完了後(成功後+失敗後)に呼び出される

ここで、jqXHRオブジェクトとは、XMLHTTPリクエストの通信の状態等の情報を持つオブジェクトのことである。
またtextStatusは、エラー情報を文字列化したものである。

以下、コールバックを使ったajax通信の例。

var jqxhr = $.ajax( "example.php" )
  .done(function() {
    alert( "success" ); //通信が成功した場合、successを表示
  })
  .fail(function() {
    alert( "error" ); //通信が失敗した場合、errorを表示
  })
  .always(function() {
    alert( "complete" ); //通信が完了した場合、completeを表示
  });

4. jQueryのDeffered

最後に、非同期処理を上手く扱うためのjQueryの標準モジュールであるjQuery.Defferedについて説明する。
これを使うことで、以下の3つの側面から非同期処理を扱いやすくなる。


1.同期処理を連結する際のコールバック地獄からの開放
2.エラー処理を上手く記述できる
3.一連の非同期処理を関数化して再利用しやすくできる

jQuery.Defferedについて詳しくは、以下の記事を参照して頂きたい。

参照サイト:
jQuery.ajax() | jQuery API Documentation

以上

Railsのコールバックについてまとめてみた

Railsのコールバックとは、一体何なんだろうか。今回は、Railsのコールバックの概要を解説する。

1.コールバックとは何か

Railsのコールバックは、オブジェクトの状態が変わるとき、すなわちオブジェクトが生成、更新、破壊されるときや、バリデーションを実行するときの前後に共通の処理を追加する仕組みである。これはモデル層で指定でき、処理を共通化することによって、モデルの一貫性を保て、またコード量を減らすことができる。

2. 2種類のコールバックの定義の仕方

モデルにて、コールバックを指定する方法は、2つある。
まず、最も一般的な方法がprivateでメソッドを切って、それを呼び出す方法。

class User < ActiveRecord::Base
  validates :login, :email, presence: true            #validationの指定
 
  before_validation :ensure_login_has_a_value  #callbackの指定
 
  protected
    def ensure_login_has_a_value
      if login.nil?
        self.login = email unless email.blank?
      end
    end
end

次に、コールバックにブロックを渡す方法。

class User < ActiveRecord::Base
  validates :login, :email, presence: true
 
  before_create do
    self.name = login.capitalize if name.blank?
  end
end

さらに、コールバックの定義を、オブジェクトのライフサイクル(オブジェクトの生成、更新、削除等)の中の一部の処理に関してのみ行うことができる。それは、以下のようにonオプションに指定する。

class User < ActiveRecord::Base
  before_validation :normalize_name, on: :create #createアクションにのみ、コールバックが適用される。
 
  # :on に配列も引き渡せる。
  after_validation :set_location, on: [ :create, :update ]
 
  protected
    def normalize_name
      self.name = self.name.downcase.titleize
    end
 
    def set_location
      self.location = LocationService.query(self)
    end
end

3. 使用できるコールバック一覧

まず、オブジェクトのライフサイクル、すなわちオブジェクトの生成、更新、削除の各過程において使用可能なコールバックを、呼び出される順番に、以下並べる。


オブジェクトの生成(create)


before_validation

after_validation

before_save

around_save

before_create

around_create

after_create

after_save


オブジェクトの更新(update)


before_validation

after_validation

before_save

around_save

before_update

around_update

after_update

after_save


オブジェクトの削除(destroy)


before_destroy

around_destroy

after_destroy

以上の、オブジェクトのライフサイクルに関わるコールバックの他にもいくつかのコールバックが存在する。
・オブジェクトがinitializeされたあと => after_initialize
  オブジェクトに対して、newメソッドが呼ばれたときや、データベースからレコードがloadされたとき(データベースのレコードがインスタンス化されたとき)等、オブジェクトがinitializeされた後に処理を追加するコールバック。

・データベースからレコードがloadされたあと => after_find

ただし、after_initializeとafter_findが両方定義されているとき、after_findが先に呼び出される。

class User < ActiveRecord::Base
  after_initialize do |user|
    puts "after_initializeを呼び出しました"
  end
 
  after_find do |user|
    puts "after_findを呼び出しました"
  end
end
 
>> User.new
after_initializeを呼び出しました
=> #<User id: nil>
 
>> User.first
after_findを呼び出しました
after_initializeを呼び出しました
=> #<User id: 1>

・オブジェクトにtouchメソッドが呼び出されたあと => after_touch
  オブジェクトのupdated_atカラムを現在時刻に更新するtouchメソッドが呼び出されるあとに処理を追加するコールバック。

参照URL:Active Record Callbacks — Ruby on Rails Guides

以上

Railsのmigarationについてまとめてみた

Railsでは、データベースにテーブルを作成したり、インデックスを追加したりするのに Active Record の Migration 機能を使う。今回は、Migrationについて、自分が分かりにくいと思った点を中心に解説していきたい。

1.migrationとは何か

Active Record の migrationは、データベースの状態をRubyDSLを用いて定義し、それらをcvsのようにversion管理するものである。すなわち、データベースへのテーブルやカラムの追加、削除を煩わしいSQL文ではなく、簡単なRubyDSLで定義し、その内容をその経過とともに保存できるようにしたものである。そして、このRubyDSLは、使用しているデータベースがMySQLでもSQLiteでも同じ記法で定義することができるため、データベースへのアプリケーションの依存を減らすことができる。Railsのmigrationでは、ひとつのmigrationファイルが、ひとつのversionに相当し、migrationファイルにデータベースの情報を定義していく。

2.rails migration コマンド

migrationファイルを作成する際には、rails migration コマンドを用いる。

bin/rails generate migration クラス名

コマンドで指定するクラス名には命名規則があり、それに従うとmigrationファイルの中身まで、コマンドで指定することができる。しかし、migration ファイルを生成した後に、そのファイルの中身を変更可能なため、そこまで意識する必要はない。
以下、rails migration コマンドと、それによって生成されるmigrationファイルの例を記述する。

#feedsテーブルを追加したい場合、ここではクラス名はCreateFeeds
bin/rails generate migration CreateFeeds
#生成されるファイル名 => db/migrate/年月日時分秒_create_feeds
#生成されるファイルの中身
     class CreateFeeds < ActiveRecord::Migration
       def change
         create_table :feeds do |t|
     
           t.timestamps
         end
       end
     end

#feedsテーブルにcategory_id カラムを追加したい場合、ここではクラス名はAddCategoryIdToFeeds
bin/rails generate migration AddCategoryIdToFeeds
#生成されるファイル => db/migrate/年月日時分秒_add_category_id_to_feeds
#生成されるファイルの中身
     class AddCategoryIdToFeeds < ActiveRecord::Migration
       def change
       end
     end

3.change とup, downメソッドの使い分け

migrationクラスのオブジェクトには、change, up, downのメソッドがある。
まず、changeメソッドとup,downメソッドの使い分けの例を以下にあげる。


1.create_table をするとき   ->changeメソッド
2.add_columnをするとき    ->changeメソッド
3.remove_columnをするとき  ->up, downメソッド
4.drop_tableをするとき    ->up, downメソッド

create_tableやadd_columnなど、新しいものをデータベースに追加するような動作は、逆の動作として、そのテーブルやカラムを削除すればいいだけであり、それを予測することができる。それに対し、remove_columnやdrop_tableなど、既存のデータベースの項目を削除するような動作は、逆の動作として、データベースに新しいものを追加するときに、以前指定していたカラムのデータ型やオプションなどを特定しなければならなく、逆の動作が予測できない。
ここで、changeメソッドを使うのは逆の動作が予測可能なときであり、up,downメソッドを使うのは逆の動作が予測不可能のときである。
したがって、create_tableやadd_columnのときは、changeメソッド、remove_columnやdrop_tableのときはup, downメソッドを用いる。

4.changeメソッドを使う動作と up, down メソッドを使う動作をひとつのMigrationファイルで行う

3.で、changeメソッドと、up, downメソッドの使い分けについて説明したが、ひとつのmigrationファイルでadd_columnやdrop_tableなど使うべきメソッドの違う複数の動作を行いたい場合は、changeメソッドでreversibleを用いると綺麗に定義できる。

#remove_columnなど、up, downメソッドで定義すべきものを、以下のようにreversible にブロックを渡す。
class ChangeMultipleColumsAndTables < ActiveRecord::Migration
  def change
    create_table :categories do |t|
      t.string :name
    end
 
    reversible do |dir|
      dir.up do
        remove_column :articles, :article_name
      end
      dir.down do
        add_column :articles, :article_name, :string
      end
    end
 
    add_column :users, :adress, :string
    rename_column :users, :email, :email_address
  end
end

5.create_tableする際のcreated_at, updated_at カラムの生成方法

#create_tableに引き渡すブロック内でt.timestampと定義する
class CreateCategories < ActiveRecord::Migration
  def change
    create_table :categories do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end
end

参照サイト:Active Record Migrations — Ruby on Rails Guides

以上