2013年11月2日土曜日

[Angular.js]factoryとserviceの使い分けについて

最近、プライベートのプロダクトでAngular.jsを勉強がてら使っている。
Yeomanでgenerator-angularを使う方法でやってたり。
で、結構悩んで調べたことがあったので、メモ。
※使い始めて間もないので、間違いがあったらご指摘ください。

サービスを作るやり方、Angular.jsだと色々あります。
yeoman-generatorのREADMEを参考に挙げると

service, factory, provider, value, constant
と、実にたくさん。

providerとvalueとconstantは、名前からなんとなく使い分けが判るんだけど、
serviceとfactoryの違いって何よ?って結構悩んだ。
簡単な実装例だと、「結局どっちも書き方が違うだけで同じ事してるやん」って思ってしまって…。

悩んだ時はソース嫁、という事で読んで見ました。
実装はこのへん。

factoryは、渡したfunctionをfunctionのままサービスに登録する。
serviceは、渡したfunctionをコンストラクタとしてインスタンスを生成し、そのインスタンスをサービスに登録する。

という違いがある様子。

それで何が変わるのか?というと。

serviceはインスタンスにしてから登録する。
JavaScriptのオブジェクトにはprivateが無いので、serviceで登録したコンストラクタで定義したメソッドは、何処からでも参照する事が出来てしまう。

一方、factoryはfunctionをそのまま登録する。
そして、factoryで登録したサービスから見えるのは、function内でreturnしたオブジェクトだけ。
なので、以下のように書いておくと、privateのような事が出来る。
angular.module('sampleServices', []).
    factory('nyanService', function() {
        var privateValue = 0;
        var privateMethod = function() {
            privateValue++;
        };
        return {
            publicMethod: function() {
                privateMethod();
                return privateValue();
            }
        };
    });
テストはこんな感じのが通る。
  describe('factory tests', function() {
      it('publicMethodがサービス経由で見えること', function() {
          expect(nyanService.publicMethod).toBeDefined();
      });
      it('privateMethodがサービス経由で見えないこと', function() {
          expect(nyanService.privateMethod).toBeUndefined();
      });
      it('privateValueがサービス経由で見えないこと', function() {
          expect(nyanService.privateValue).toBeUndefined();
      });
      it('1回実行すると1が帰ってくること', function() {
          expect(nyanService.publicMethod()).toBe(1);
      });
      it('2回実行すると2が帰ってくること', function() {
          nyanService.publicMethod();
          expect(nyanService.publicMethod()).toBe(2);
      });
  });
なので、使い分けとしては
  • service: ある程度まとまった関数群をまとめたUtilオブジェクト向け
  • factory: ビジネスロジックをしっかり書いたりするようなモデル向け
という感じでいいんじゃないかと。

Yeomanが作ってくれるfactoryのコードにも
return{}の前に"Public API here"ってコメント書いてあるし、この使い分けで間違いない…かな?

2013年10月18日金曜日

VirtualBox上のUbuntuでChromeが固まる問題の対処法

最近、Windows上で開発してたんですが、やっぱりイヤンな感じが消えなかったので、Ubuntu環境復活させることにしました。
とはいえ、専用マシンは家に無いので、VirtualBox上に入れてます。

その、VirtualBox上のUbuntu13.04で動いてるChrome、
サイトを開いて1秒程度で、描画領域が完全に固まってしまいます。

個人的にFireFox肌に合わないので、代替でFireFoxを使うという選択肢は無いので、
Google先生に聞いてみました。
解決はしたんだけど、日本語の情報がサクッと出てこなかったのでメモメモ。

VirtualBoxの3Dアクセラレーション関係?が原因らしい。
(この辺は真面目に調べてないので怪しい。)

とりあえず、起動オプションに

--blacklist-accelerated-compositing

を付けると、固まらなくなります。

Unityのランチャに登録する場合は

cp /usr/share/applications/google-chrome.desktop ~/.local/share/applications/google-chrome.desktop

こんなかんじで、ランチャを自分のhomeディレクトリにコピってきて、エディタで開いて、Execの所に起動オプションを追記。
(複数あるので全部追記してください。)
その後、chmod +xで実行権限を与えて、手動で実行。
実行後、Unityのランチャ上のアイコンを右クリックして、Launcherに登録とすればOK。

参考にしたのは以下のフォーラムの内容です。
http://ubuntuforums.org/showthread.php?t=1974800

いじょ。


Ubuntu専用マシン欲しいなぁ…(・ω・`

2013年8月31日土曜日

Ubuntu13.04 で usermod -G やらかした後の復旧まとめ

Ubuntuでやりがち(だよね?)なミス、usermod -Gでユーザグループ追加しようとして、管理グループからユーザを外してしまいsudo出来なくなるアレ。
最近、自宅の仮想環境上のUbuntuに触ってなかったんですが、久々にログインしたらsudo権限が無く……。
前に吹っ飛ばしたままだったようなので、復旧させました。

せっかくなんで手順メモ。
SSはとり忘れたッス。


  1. 再起動してリカバリーモードで起動しなおす。
    Shift押しっぱなしにすると、GRUBが開くので、
    Advanced options for Ubuntuを選択。
    一覧から(recovery mode)とついたOSを選択。たくさん出たら上にあるやつでいいです。
  2. fsckを選択して、Read/Writeモードでマウントしなおす。
    リカバリーモード起動時は、ファイルシステムがリードオンリーなので、
    このままだと/etc/groupに書き込みすることが出来ず、グループの追加が行えません。
    なので、書き込み可能状態で再マウントする必要があります。
    本来、fsckコマンドは、ファイルシステムをチェックするコマンドですが、
    リカバリーモードの場合、実行後にファイルシステムを再マウントしてくれます。
  3. rootを選択して、ターミナルを起動。
  4. gpasswdコマンドでsudoグループにユーザを追加。
    gpasswd -a user_name sudo
    コマンドは上記のものを使いましょう。
    usermod -GはUbuntuで利用すると、指定したグループ以外を消してしまうようなので。
    普段使うときもgpasswdのほうがいいです。Ubuntuだったら。(Debianはどうなんだろう…)
  5. resumeで再起動。
  6. sudoして確認してみる。
こんな感じ。
ポイントをまとめると、
  • リカバリーモード起動直後は、リードオンリーで書き込みが出来ない。
    書き込み可能で再マウントするにはfsckを実行する。
  • Ubuntu 13.04の場合、sudo可能なグループはsudo
    昔はadminだった気がする…。いつ変わったんだろう?
こんな感じですかねー。

Ubuntu、日本だけかもしれないけど、9~11位のバージョンのときが流行のピークで
Google先生に聞いてもそれくらいのバージョンのときの解決方法が上のほうに出てくるんですよね。
すると、今の最新とは結構違った内容が出てきちゃったりします。

まぁ、GnomeからUnityになったあたりと、Macが開発用端末として流行り始めた時期から、
ユーザが減ったんだろうけど…。

出始めは悪評多かったUnityですけど、最新だと結構きれいに動くし、
VirtualBox上でも設定さえしっかりすればきれいに動いてくれるので
個人的には、これからも最新バージョンを追って使い続けたいディストリだったりします。

2013年8月28日水曜日

Symfony2のblog tutorialを2.3に対応させる。

Symfony2、これまでほぼノータッチだったんですが、触る必要が出てきたので、
ざっと基本的な部分を把握するために、blogチュートリアルをやってみました。

URLはこちら。
※海外有志の方が作成した、symblogチュートリアルじゃない方です。

こちら、ヘッダ部分に記載されているとおり、バージョン2.0.7向けの内容です。
ですが、現在最新のSymfony2は2.3.4。
deprecatedになっているメソッドがあったり、そもそも存在しないAPIがあったりするので、
いくつか、気づいた変更点をまとめておきます。

  1. getEntityManegerメソッドがdeprecated。
    DB操作を行う、EntityManagerを取得するメソッド名が変更になっています。
    使い方はそのままなので、単純にメソッド名を変えれば良さそう。
    http://docs.symfony.gr.jp/sf2-blog-tutorial/05-list-page.html
    例えば、上のページのDefaultController.phpのindexActionは以下のようになります。
    public function indexAction()
    {
    //    $em = $this->getDoctrine()->getEntityManager();  // 旧
        $em = $this->getDoctrine()->getManager();  // 新
        $posts = $em->getRepository('MyBlogBundle:Post')->findAll();
  2. FormのbindRequestメソッドが廃止。
    リクエストをバインドする時は、単純にbindメソッドを使えば良いよう。
    http://docs.symfony.gr.jp/sf2-blog-tutorial/07-create-form.html
    例えば、上のページのDefaultController.phpのnewActionのバリデーション周りは以下のようになります。
    // バリデーション
    $request = $this->getRequest();
    if ('POST' === $request->getMethod()) {
    //    $form->bindRequest($request);  // 修正前
        $form->bind($request);    // 修正後
        if ($form->isValid()) {
    
  3. フラッシュメッセージ関連
    Session周りのAPIが変更になった影響で、フラッシュメッセージの設定方法、表示方法共に変更となっています。
    まず、大きな変更として、フラッシュメッセージは常に配列で扱われるようになった、という所があるようです。
    ですので、表示の際は配列であることを意識したビューの書き方をする必要があります。

    フラッシュメッセージを設定するController側の変更です。
    http://docs.symfony.gr.jp/sf2-blog-tutorial/customize/03-session-flash.html
    上のページのDefaultController.phpのnewActionは以下のようになります。
    public function newAction()
        {
            // ...
    //        $this->get('session')->setFlash('my_blog', '記事を追加しました');  // 修正前
            $this->get('session')->getFlashBag()->add('my_blog', '記事を追加しました');    // 修正後
            return $this->redirect($this->generateUrl('blog_index'));
            // ...
        }
    ちなみに、FlashBagにmessageを設定するメソッドは、add以外にsetもあります。
    違いは何かというと、FlashBag.phpのソースを直接見るとわかりやすいのですが、
    add: $this->flashes[$type][] = $message;
    set: $this->flashes[$type] = $messages;
    と、addは指定したtypeの配列にメッセージ(文字列)を追加するのに対し、
    setは指定したtypeの配列そのものを上書きする形になります。
    なので、普通に使う分にはadd使ったほうが良いんじゃないかと思います。

    次、テンプレート側の変更。
    http://docs.symfony.gr.jp/sf2-blog-tutorial/customize/03-session-flash.html
    同じく上のページのindex.html.twigのフラッシュメッセージ周りは以下のようになります。
    {% for type, messages in app.session.flashbag.all() %}
        {% for message in messages %}
            
    {{ message }}
    {% endfor %} {% endfor %}
    ちょっとこれは、変更前/後の比較表示が難しいので、変更後だけ。
    一見するとコードが増えて複雑なように見えますが、システムが複雑化してきて、
    フラッシュメッセージの量や種類が増えてくると、便利なんじゃないかな、と。
    (とはいえ、一度に大量のフラッシュメッセージが表示されるサービスは、微妙なようにも思いますが…ww)
  4. FormTypeのgetDefaultOptionsメソッドが廃止。
    変わりに追加となった、setDefaultOptionsを利用することになります。
    http://docs.symfony.gr.jp/sf2-blog-tutorial/customize/05-create-form-class.html
    上のページの"PostType.php"のgetDefaultOptionsは以下のようになります。
    // use周りも追加・変更があるので注意
    use Symfony\Component\Form\AbstractType;
    // use Symfony\Component\Form\FormBuilder; // 削除
    use Symfony\Component\Form\FormBuilderInterface; // 追加
    use Symfony\Component\OptionsResolver\OptionsResolverInterface; // 追加
    
    // 中略
    
        public function setDefaultOptions(OptionsResolverInterface $resolver) {
            $resolver->setDefaults(array(
                'data_class' => 'My\BlogBUndle\Entity\Post',
            ));
        }
    戻り値として返すのではなく、$resolverのsetDefaultsに設定する形になった、と覚えればOKでしょう。たぶん。
    ちなみに、同時に廃止となったgetAllowedValuesメソッドの役割も$resolverに対して行うようです。
気づいたのは大体こんな感じですね。
deprecatedなメソッドは、もしかしたら見落としがまだあるかもしれません。
詳しくは、SymfonyのUPGRADE-2.x.mdを見るのがいいと思います。
2.1, 2.2までは有志の方が日本語に訳してくださったものがあるのでそれを参考にするとわかりやすいと思います。
上で書かれている内容は、2.1のタイミングで変更になったものばかりでした。

2013年4月23日火曜日

Highcharts 3.0で追加された Axis.toPixels() が色々捗る件


Highchartsという、jQueryベースのグラフ描画ライブラリが有ります。
グラフ描画に特化したライブラリの中では、一番、かっこよくてダサくなく実用的なライブラリだと個人的には思っています。
商用利用時は有料ですが、個人利用であれば無料で使えます。
http://www.highcharts.com/
公式サイトはこちら。

最近?だと、PHPMyAdminのグラフ描画なんかでも使われてますね。
※データベースの「状態」や、クエリのプロファイリングで見れます。

ライセンスがどうしても気に食わない、とか
もっとスゲー事やりたい!って人は
D3.jsをどうぞ。
グラフ化よりもう少し上、データビジュアライゼーションのためのライブラリです。
http://ja.d3js.node.ws/
※jQueryに依存してません。

さて。
Highcharts、2013/03/22に3.0にバージョンがあがりました。
パッと見一番解りやすいのは、デフォルトのグラフの色が変わったことなんですが
その他にも、バブルチャートが追加されたり、色々追加されています。
http://www.highcharts.com/component/content/article/2-news/54-highcharts-3-0-released
大きな変更点はこちらの公式ニュースを読んでください。

細かいところでも、いくつかAPIに追加があるのですが、
今回は、タイトルに有るとおり、3.0で追加されたAxis.toPixels()のお話です。

http://api.highcharts.com/highcharts#Axis.toPixels()
公式APIドキュメントが上記です。
APIドキュメント上にサンプルが無いので、
公式デモの一番最初のグラフのソースをベースに紹介しますね。

グラフの下に、謎の数字が出ているかと思います。
これが、「X軸が3の値をプロットするときのX座標」を表しています。
このグラフだと、X軸にcategoryで名前を与えているので、ちょうどAprの所になります。
2にするとMarの所のX座標になります。
小数点でもOKなので、例えば2.5なんて入れると、MarとAprの中間点の座標が取れます。

コレが、もー本当に捗るんですよ!
こいつが後1ヵ月遅ければ、今の私はデスマっていたかもしれないレベルで。

…先ほどのサンプルだけだと何が捗るかよーわからんと思うので、説明しますね。

例えば、先ほどのグラフで、東京の5月の気温をうまくとることが出来ず、データ上0になってしまったとします。
グラフ上すごいV字になってしまいます。
大体の人は「なんか障害があって取れなかったのかな?」と思ってくれるでしょうが、気持ち悪いので、
ちゃんと理由を吹き出しで明示したくなりました。

グラフの下に、HTMLで普通に記述しても良いのですが、
せっかくなので、Excel(笑)のオートシェイプみたいに吹き出しを上に乗せたい!

という時に、こんな感じで書けるんです。

重要なポイントは、chart.events.loadとchart.events.redrawで描画ファンクションを呼ぶ事。
特に、redrawが重要で、ここで再描画するようにしておく事で、
画面のサイズ変更時にも適切な位置に配置してくれます。
Edit in JSFiddleをクリックして、新しいウィンドウででデモ表示して横幅を変えると解りやすいです。
jQueryのwindow resizeのイベントにバインドして再描画しようとしても、上手くできないので注意。

後、y軸の値、今回のケースでは0だと解り切っていましたが、
場合によっては変動する可能性もあるので、4行目のようにして取得すると良いです。

今回、吹き出しのCSSが面倒だったんでAAにしちゃいましたが、
#textのCSSを弄れば、イケてる吹き出しにももちろん出来ます。
widthとかmargin-leftも、動的に取るようにすれば本当に自由に吹き出しが表示出来ますね。
また、#textはただのDIV要素なので、onclick等のイベントも設定出来ます。
「吹き出しをクリックしたら、具体的な障害内容が出る」とかも、jQueryの知識だけで簡単に出来ますね!

というわけで、Highcharts 3.0で追加されたAPIがすごく捗るお話でした。

2013年4月10日水曜日

CakePHP 2系で、独自カラムに対してPaginatorを使ってソートする方法

 
 このやり方が正しいかは解りませんが、
 そこそこ綺麗に解決したような気がするので、覚書として載せておきます。
 
 今回、割と特例的な対応だったので、全部MVCの中で完結させていますが、
 頻繁に使う用であれば、ComponentにするとかPaginator拡張するとかしても良いと思います。
 
 
 さて、まずは簡単な仕様の場合。
 
 CakePHPにはバーチャルフィールドという便利な仕組みがあります。
 「カラムaとカラムbをくっつけてカラムcにしたい!」なんて時にはこう書きます。
public $virtualFields = array(
    'column_c' => 'CONCAT(Hoge.column_a, " ", Hoge.column_b)'
);
参照:http://book.cakephp.org/2.0/en/models/virtual-fields.html
 
 見ての通り、SQL文をそのまま書く事になります。
 具体的には、SELECT句のフィールドに、そのまま
 SELECT CONCAT(Hoge.column_a, " ", Hoge.column_b) AS Hoge__column_c ...
 と記述されるようなイメージです。
 
 SQL文そのままなので、RDBMSが変わると動かなくなってしまうという欠点もあるのですが、
 バーチャルフィールドで定義したカラムは、通常のカラム同様に扱う事が出来るというメリットが有ります。
 なので、find時のorder句に、'column_c DESC'とか書いても、ちゃんとソートしてくれるのです。
 
 便利!
 
 
 さて、ちょっと複雑な仕様のカラムを追加する必要が出てきたとします。
 
 SQL文をそのまま書く、という事は、SQL文で解決できないような値はバーチャルフィールドで扱えません。
 例えば、MySQLの場合、順位を取得するクエリって簡単にはかけないんですよね。
 「複数のカラムが有り、それぞれのカラムの順位を合計した値を「ランク」として表示したい」
 なんて仕様は、さすがにバーチャルフィールドでは荷が重いわけです。
 
 そういう場合、どうするか?というと、
 Modelのコールバックメソッドの、afterFindの中で計算して設定します。
 
 諸々省略して雰囲気だけ伝えるコードを書くと
public function afterFind($results, $primary = false) {
 foreach($results as $key => $result) {
  // なんか計算したりして追加するカラムの値を作る
  $hoge = $aaa + $bbb;
  $results[$key][$this->alias]['hoge'] = $hoge;
 }
 return $results;
}
こんな感じで、hogeってカラムを増やします。
 参照:http://book.cakephp.org/2.0/en/models/callback-methods.html
 
 さて、ここで増やしたカラムに対して、Paginatorでソートしたい場合はどうすれば良いでしょうか?
 
 Helperはさすがにここまでやってくれないので、コントローラ辺りで自前でソートすることになります。
 幸い、CakePHPにはSet(2.2以降だとHashですね)という優秀なコアライブラリがあり、
 上記のafterFindが'Fuga'というモデル内の物だったとすると
$data = $this->Fuga->findAll(/* 省略 */);
$data = Set::sort($data, '/Fuga/hoge');
って記述で簡単にソートしてくれます。
 参照:http://book.cakephp.org/2.0/en/core-utility-libraries/set.html
 
 Paginatorでソートする場合、ソートのパラメータはデフォルトだとnamedに入るので、
 $this->request->named['sort']の値が特定の物だった場合に、
 Set::sortでソートしてやればよい事になります。
 
 先ほどから例に出している、Fugaモデルのhogeカラムで独自ソートしたい場合は、
$data = $this->paginate();
if (isset($this->request->named['sort']) && $this->request->named['sort'] == 'Fuga.hoge') {
 Set::sort($data, '/Fuga/hoge', $this->request->named['dir']);
}
$this->set('data', $data);

 とすればソートされて表示されます。
 
 
 ヤッター!これで独自カラムでソートができたぞー!
 
 と喜び勇んでいると、「デフォルト時にhogeでソートしておいてほしい」
 なんて要求が来たりするわけです。
 
 Paginatorでソートする場合の、クエリの条件は、Controllerの$paginateプロパティに記述します。
 
 これが、もし、fugasテーブル上のpiyoという、存在するカラムに対するソートであれば、
$paginate = array(
 'order' => array('piyo' => ASC)
);
と記述してしまえば良いのですが、こちらのパラメータ、そのまま、SQLのORDER BYに入るので、
 hogeカラムのように、テーブル上にもバーチャルフィールド上にも存在しないカラム名を指定すると
 「そんなカラムねーよばーか」ってMySQLに怒られてしまいます。
 
 仕方がないので、独自ソートするときのif文条件を変更して、
 sortパラメータが無い場合も、hogeでソートする
 というように変更するのが、手っ取り早い対応になるのですが、
 これを行うと、HTML上のasc/descの表示が狂ったり、クリック時の挙動がおかしくなります。
 
 何故か、というと、CakeRequestのparamsに格納された、pagingという値(連想配列です)を元に、
 「今ソートされているキーはどれか?」を判定して、表示上に取り入れているからなんですね。
 具体的には、PaginatorHelperのsortKeyというメソッドで判定しているので、その辺を読んでみてください。
 参照:http://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::sortKey
 
 (書いていて気づいたのですが、デフォルト時のソート云々抜きにして、
 Set::sortでデータだけソートしている状態だと、挙動がおかしくなっていた可能性は高いですねー。
 検証してないので、一見うまく動いてくれてる可能性もありますけども。)
 
 ここを解決するためには、Set::sortで独自ソートした後で、$this->request->params['paging']['order']の値を書き換えます。
 paramsの値は直接アクセスして書き換えることが出来ないので、CakeRequestのoffsetSetを使います。
 纏めると、先ほどのif文の中身は、こうなります。
if(/* 省略 */) {
 Set::sort($data, '/Fuga/hoge', $this->request->named['dir']);
 // 現在セットされているpagingパラメータを取得、値を上書きしてから再セット
 $pagingParams = $this->request->offsetGet('paging');
 $pagingParams['Fuga']['order'] = array('Fuga.hoge' => $dir);
 $this->request->offsetSet('pagin', $pagingParams);
}
これにて、無事、独自追加したフィールドに対するページングがうまく働くようになりました。
 
おわり!

2013年3月22日金曜日

Jenkinsの「定期的に実行」で地味に感動した

ラベルの通り完全に小ネタ。


皆さんご存知の通り、Jenkinsのビルド・トリガの「定期的に実行」は、crontabの書式で、スケジュールを記述します。
crontabの書式はWikipediaでも見ておいてください。この辺。
http://ja.wikipedia.org/wiki/Crontab

さて、この入力欄ですが。
良く見なくても、複数行入力出来ますね?

複数行入力すると、ちゃんと、全部の行実行してくれます。
どういう事かというと…。

例えば、「平日毎日、朝9時にレポートを送りたい。でも、金曜は夕方の17時にもう1回レポートを送りたい」みたいなのが有った時、
レポート送信に/home/hoge/fuga.shなんてシェルを使うとすると、crontabに

0 9 * * 1-4 /home/hoge/fuga.sh
0 9,17 * * 5 /home/hoge/fuga.sh

なんて、書く事になるじゃないですか。

これが、Jenkinsだと、1つのジョブの定期的に実行のスケジュールの所に

0 9 * * 1-4
0 9,17 * * 5

って書くだけで良いわけです。
同じ事やってんのに、スケジュールのためだけに複数設定する必要が無いので
整理出来て見やすいし、混乱も少ない。

良いですね。


単純に便利で感動したのでメモでした。


(´-`).oO(混沌としたcrontab、全部Jenkinsに移したくなるわぁ…)