blog

紫陽花

ReactのVirtualDOM componentDidMount

React.jsを使っていてVirtualDOMに対してライブラリを適用させたい場面がありました

VirtualDOMなのでライブラリを適用させるタイミングに肝心のDOMがまだレンダリングされていないという状況に遭遇。
Reactの動きは崩さず、VirtualDOM群がレンダリングを終えたタイミングで発火させたい。
ReactのcomponentDidMountと呼ばれる関数を使えば解決できました。

componentWillMount()

componentWillMount()とは、

ComponentがDOMツリーに追加された状態で呼ばれるのでDOMに関わる初期化処理を行いたい時に便利です。
componentWillMountと違いserver-side renderingの時には呼ばれません。
DOMを扱う処理のほか、AjaxリクエストやsetIntervalの登録などのserver-side rendering時には必要ない初期化処理についてはこの中でやることになります。

React.jsのComponent Lifecycle - Qiita

「ComponentがDOMツリーに追加された状態で呼ばれる」ものなので、ここに呼び出し後のDOMに対して当てたい処理を定義すればOK!

var Box = React.createClass({
  getInitialState() {
    return {
      windowWidth: window.innerWidth
    };
  },
  handleResize(e) {
    this.setState({windowWidth: window.innerWidth});
  },
  componentDidMount() {
    // 描画が成功して、DOMにアクセス可能になる
  },
  componentWillUnmount() {
    // 描画が行われる直前に呼ばれる
  },
  render() {
    return <div>Current window width: {this.state.windowWidth}</div>;
  }
});
React.render(<Box />, mountNode);


すごくわかりやすい例が載っているのがこちら。
qiita.com

Rubyの配列展開 *[a, b, c]

Rubyの配列展開

array = ["a", "b", "c"]
*array
# => a
# => b
# => c

arrayのオブジェクトが入った変数の先頭に*(アスタリスク)をつけることでarray[0] array[1] array[2] の出力をしてくれる。
Rubyの中でも地味な機能ではあるけれども便利。筆者はこの機能で救われた。
動的に変わる引数に対してActiveRecordでLIKE検索を行いたい時に役に立った。

CSV.foreach(path) do |keywords|
  keywords.join(", ").split.each do |keyword|
      query << 'title LIKE  ?'
      words << "'%#{keyword}%'"
  end
  item_count = Item.where(query.join(" AND "), *words).count
end

プレースホルダーにするのはSQLインジェクションなどを考慮してです。
上記では"title LIKE ?" のクエリがkeywordsの数動的に生成され、*wordsで配列展開が行われクエリの引数がkeywordsの値分出力されてSQLになります。

まだまだRubyへの理解が足りないな。。。。

追記

メソッドの引数に*をつけると可変長になるらしい。
: **とアスタリスクを2つ並べると配列ではなくハッシュ{}形式の可変長になるそうだ!!

hash = { a: 1, b: 2, c: 3 }
p *hash
#=> [:a, 1]
#=> [:b, 2]
#=> [:c, 3]

p **hash
#=> {:a=>1, :b=>2, :c=>3}

: *が配列展開 **がハッシュ展開

qiita.com

Railsの画像遅延ロードにはlayzr.jsがいいぞ

Webサービスのパフォーマンスを向上させるために表示する画像の遅延ロードを実現したい!
そんなときにオススメなlayzr.jsを紹介します。筆者は実際にプロダクトに実装しました。

画像の遅延ロードをすることで得することは?

読み込み速度が速くなったり通信量も制限できてサーバー代なども微量ながら抑えることができるかなと思います。

環境

Ruby 2.3
Rails 5
ES6
layzr.js ver ^2.0

layzr.js

GitHub
github.com

紹介サイト
http://callmecavs.com/layzr.js/callmecavs.com

A small, fast, and modern library for lazy loading images.

画像遅延ロードに関するライブラリはいくつか存在しますが、
layzr.jsは速さ軽さに重点をおいたjQueryのライブラリです。

ダウンロード

Layzr.js : http://callmecavs.github.io/layzr.js/

<script src="layzr.js"></script>

CDN

<script src="https://cdn.jsdelivr.net/layzr.js/2.0.2/layzr.min.js"></script>

npm

npm install layzr.js --save

layzr-rails

github.com


今回Railsアプリに実装するにあたってlayzr-railsというGemをinstallします。
layzr-rails自体にはJS周りのコードは含まれていません。
これはViewHelperのimage_tagを拡張してくれるGemで、
以下のようにlazy: trueにすることによって遅延ロードを可能にしてくれます。
Rails5でも問題なく動作することを確認しています。

<%= image_tag "kittenz.png", alt: "OMG a cat!", lazy: true %>

Equals:

<img alt="OMG a cat!" data-normal="/images/kittenz.png" src="/assets/some-default-image.png">

実装

layzr.js

import Layzr from 'layzr.js'

const instance = Layzr();
 
 instance.on('src:after', element => {
   if(!element.hasAttribute('data-layzr-bg')) return;
   element.style.backgroundImage = `url("${ element.getAttribute('data-normal') }")`
   element.removeAttribute('src');
   element.removeAttribute('data-layzr-bg');
 })

 document.addEventListener('DOMContentLoaded', () => {
   instance.update().check().handlers(true)
 }, false);
View  
import Layzr from 'layzr.js'

ここでライブラリを読み込みます。
instanceを作成

optionがいくつか設けられています。
src:before や src:afterにコールバックを定義できます。

instance.on('src:after', element => {
    // ...
})

.on('src:after', image => {
    // ...
})

上記ではsrc:afterに処理を定義してCSSのbackgroundImageをsrcの読み込みの後に画像がセットされるようにしています。
src:afterとあるようにinstanceの処理が終わった後に実行される処理です。


[注意]
他にもいくつかLayzr.jsに関する紹介記事がGoogle検索をすると見つかりますが、
紹介されたlayzr.jsのバージョンが古く2.0.0以降で廃止された機能なども存在するので気をつけて下さい。


on-ze.com

・data-layzr-bg
・data-layzr-hidden
・data-layzr

このような属性は2016/10現在では削除された機能です。
GitHubのissueなどを覗くと廃止された機能の現在での対応方法が紹介されていたりするのでそこを見に行きましょう。

github.com




身についたことなど

  • 画像遅延ロード 良い
  • layzr.js 良い
  • 自分で作ろうかとも思ったらlayzr-railsというhelper拡張Gemを有志が実装し既にGithubに公開されていて良かった。助かりました!
  • ライブラリの変遷を終えた(issueなどもちゃんと見て現状の仕様を把握する癖がつき始めた)
  • もっとES6のことが知りたくなった

git cherry-pick is 良い

学びをメモ:

Gitを使っていて特定のブランチの特定のコミットのみを新しく作ったブランチに反映させたいなという場面がありました。
git rebaseもあるけどそれだと全てが過去のコミットとして反映されるから嫌だな〜、何か良い方法はないかな〜、と探していた時にgit cherry-pickを見つけました。
git cherry-pickの使い方は超簡単で、コミットを反映させたい特定のブランチで

cherry-pick コミットID

のように反映させたいコミットIDを指定すればそのコミットの履歴が反映させられます。
また必ず使う機会がくると思うの今度は迷わずcherry-pickを使いたいと思います。

そして最近思うのはどんなに技術を知っていても実際に使う場面に遭遇して、実際に使ってみないと身に染みて覚えないなと。
ゲームでもリアルでも経験値ってやっぱ大事だよね。


これでまた1つgitのレベルが上がった気が.......する。

gitは猿でもわかるんやで
www.backlog.jp

Rails select2でajax処理を行い1万件以上でもスムーズに動くようにする。

jQueryのライブラリでいい感じに選択機能を実装するselect2があります。
今回はselect2でajax処理を行う方法を紹介します。

  • 本家サイト

Select2 - The jQuery replacement for select boxes

本家サイトのExamplesにもあるようにselect2にajax処理でデータを取得し、一覧にして出す機能がもともとオプションとして存在しています。

viewはslimですが以下のように実装を行いました
f.collection_selectionはrails標準のhelperです。select2を当てるbook_searchをclassとして定義しています
multipleをtrueにすることで複数選択にしています。ここらへんはお好みで

= f.collection_select :book_ids, books, :id, :name, { include_blank: "" }, multiple: true, class: "form-control book_search"

Javascriptは本家サイトのExampleを参考に実装しました。
ajax処理を行い受け取ったobjのidとname を返しています。

  $(".book_search").select2({
    ajax: {
      url:'/books/search.json',
      dataType: 'json',
      delay: 50,
      data: function(params) {
        return { q: params.term };
      },
      processResults: function (data, params) {
        return { results: $.map(data, function(obj) {
            return { id: obj.id, text: obj.name };
          })
        };
      }
    },
    theme: "bootstrap",
    width: "100%",
    placeholder: "本選択",
  });


controller側は以下のようにしました。あくまで参考程度に
jsonで値を返すようにしています。

class BooksController < ApplicationController
  def book_search
    respond_to do |format|
      format.json { render json: @books = Books.search(params[:q]) }
    end
  end
end

class Books < ActiveRecord::Base
  scope :search, lambda { |query| where('name LIKE ?', "%#{query}%").limit(100) }
end


これで1万件ほどのselect表示をajax処理で検索させることによってページのローディングはかなり改善されました。
データが1万件近くあるとselectのoptionに値をセットするのに時間がかかりviewの表示に20秒ほどかかっていました。。。。(遅すぎ)

ちょっと雑な書き方になりましたが、他に似たように実装したいと考えている人の参考になれば幸いです。
ちなみに自分は以下の記事を参考にさせていただきました。


blog.scimpr.com

7thpocket.2-d.jp

wp.tech-style.info

Rails kaminariにモンキーパッチをあててmetaタグをいい感じにする。

Railsでページネーションを実装するライブラリ、kaminariに対してモンキーパッチを当てました。
人生初のモンキーパッチでもあったのでやったことをまとめておこうと思います。

なにをやるのか?

環境は、

Ruby 2.3.0
Rails 5.0.0
kaminari 0.17.0

今回モンキーパッチを当てたのはRailsでよく用いられるGemのkaminariです。
kaminariに実装してあるrel_next_prev_link_tagsというメソッドの動きを変えて課題を解決しました。

github.com

どうやったのか?

kaminariにはページネーションを実装した際に次ページリンクのmetaタグを簡単に実装してくれるメソッドrel_next_prev_link_tagsがあります。
このメソッドの引数にオブジェクトを渡せば簡単に実装できるため非常に便利です。

    #   Somewhere in body:
    #   <% content_for :head do %>
    #     <%= rel_next_prev_link_tags @items %>
    #   <% end %>
    #
    #   #-> <link rel="next" href="/items/page/3" /><link rel="prev" href="/items/page/1" />
    #

    def rel_next_prev_link_tags(scope, options = {})
      next_page = Kaminari::Helpers::NextPage.new self, options.reverse_merge(:current_page => scope.current_page)
      prev_page = Kaminari::Helpers::PrevPage.new self, options.reverse_merge(:current_page => scope.current_page)

      output = String.new
      output << tag(:link, :rel => "next", :href => next_page.url) if scope.next_page.present?
      output << tag(:link, :rel => "prev", :href => prev_page.url) if scope.prev_page.present?
      output.html_safe
    end
<%= rel_next_prev_link_tags @items %>

<link rel="next" href="/items?page=3" />
<link rel="prev" href="/items?page=1" />

上記のlinkが生成されます。楽チンですね
しかしこのリンクではGoogle検索エンジン側ではあまり良しとはされていないようで、ドメインなども含めたリンクを生成したいという課題にぶつかりました。
rel="next"とrel="prev" の使い方
Google、ページネーション問題を解決するrel=“next”タグとrel=“prev”タグをサポート開始 | 海外SEO情報ブログ

課題

生成されるlinkをドメインも含めたものにしたい。

現状

<link rel="next" href="/items?page=3" />
<link rel="prev" href="/items?page=1" />

これを、

GOAL

<link rel="next" href="http://hoge.js/items?page=3">
<link rel="prev" href="http://hoge.js/itemspage=1">

にしたいと思います。

方法

/config/initializers/kaminari_config.rb

Kaminari.configure do |config|
  config.default_per_page = 15
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
end

module KaminariPatch
  def rel_next_prev_link_tags(scope, options = {})
    url = root_url
    query_hash = build_next_prev_query
    url << "?#{query_hash.to_query}" if query_hash.present?

    output = ""
    output << '<link rel="next" href="' + url + "&page=#{scope.next_page}" + '"/>' if scope.next_page
    output << '<link rel="prev" href="' + url + "&page=#{scope.prev_page}" + '"/>' if scope.prev_page
    output.html_safe
  end
end

::ActionView::Base.prepend(KaminariPatch)

initializers以下にあるkaminari_config.rbはRails起動時に必ず最初に呼び出されるファイルです。
ここに処理を変更するメソッドを定義し、ActionView::Baseクラスにprependして動きを変えます。
上記では、build_next_prev_queryが定義されていますが。
これはシステム側の動きをこのメソッドでまとめて汲み取っているだけなのであまり気にしなくても大丈夫です。
この辺りは各々の環境でいい感じにしてください。

なぜモンキーパッチを当てたのか?

なぜモンキーパッチを当てたのかというそもそもの話をすると、モンキーパッチを当てて課題解決したほうが良い感じに落ち着きそうだったからです。
本来、モンキーパッチ自体はあまり好まれたテクニックではありません。
なぜなら、モンキーパッチをあてることによって本来の動きを変えてしまうことによって起きる弊害があるからです。
この弊害の話は技術書などでも口酸っぱく書かれていたりもします。
はじめは別メソッドに定義して解決することも考えましたが、なるべく無駄なコードは作りたくなかったのと、
kaminari側で処理が存在しているならばその処理に対して修正を加えて利用するほうがシンプルに収まるだろうと考えたからです。

おわり

今回が初のモンキーパッチを当てて課題を解決する経験でとても学びが多かったです。
・別メソッドを定義する考えからモンキーパッチで解決する方向への切り替え
・kaminari本来の動きを追うためのソースコードを読むこと
・PRの意図を探ること
オープンクラス、メソッド探索、メソッドのオーバーライド、prepend、クラス継承などなどメタプログラミングRuby 第2版で学んだ知識が所々に現れてきました
・モンキーパッチの良し悪しを決めるライン
・実際に課題を解決する経験

kaminariのGitHubにちょっと気になるissueがありました。
rel_next_prev_link_tags ignores host option · Issue #496 · amatsuda/kaminari · GitHub

今回参考にした記事

techlife.cookpad.com

engineer.crowdworks.jp

クックパッドさんとクラウドワークスさんのモンキーパッチに関する記事を参考しました。
とても勉強になったので記事を書いてくださった方々には感謝しかないです。ありがとうございます。


これでRubyエンジニアとしてのレベルをまた1つ上げた気がする。がんばろう

 

DBひでんのしょ

データベース(以下:DB)に関する自分の知見を整理しておきます。
DB秘伝の書という記事タイトルですが、公開した時点でもはや秘伝でもなんでもないですね。
ここにはDB設計に関するごく普通の考え方をまとめて書いています。

前述

なぜDBが必要なのか?
DBを使わないシステムは無い。
データとは、ある形式にそろえられた事実と定義
企業でのデータは資産である。


種類
1. RDB
2. オブジェクト指向DB
3. XMLDB
4. key-value Store
5. 他

DBを設計することは、システム開発を制する。

システム開発を制する」というと少し大げさかもしれませんが、あながち間違ってもないかなと思っています。
なぜならDBはアプリケーションの根幹であり、ここが破綻していると全てが破綻していると言っても過言ではないからです。
また、DBのリファクタリングは難しく。一度データが入って本稼働してしまえば変更を加えることは容易ではありません。

システムは、データのフォーマットに合わせられて作られる。
POA(プロセス中心アプローチ)ではなく、DOA(データ中心アプローチ)が基本。


DB破綻を起こすと、プログラムでのリカバリーは、ほぼ絶望的だということを認識しておく。

necojackarcさんのブログでサービス開発を始める上でなぜDB設計が重要かについて、
わかりやすく実際の経験を交えて紹介されています。

necojackarc.hatenablog.com

こちらの記事を参考にしてみてください。

エンティティとは

組織全体もしくはシステム開発における静的な管理対象(実態という)

6W6Hを定義(表現する)

リソース系

  • who 誰が
  • whom 誰を
  • what 何
  • where どこで
  • how to 方法

イベント系

  • how どのように
  • how long 期間

属性候補

  • when いつ
  • how many どのくらい(定量/たまに定性)
  • how much いくら

ビジネス上、正規化対象

  • why なぜ (〜のため)

レビュー観点

  • how in the future 将来的にどうなるのか


エンティティの種類
独立エンティティ

  • 他のエンティティに依存しない(顧客、商品など)

従属エンティティ

  • 他のエンティティに依存している(受注、受注明細)

リソース/イベントエンティティ
リソース系エンティティ

  • マスタ

  簡単にいうと、人、モノ、金、企業組織の活動基盤を表すデータ

イベント系エンティティ

  日々の業務など企業組織の活動によって、発生する出来事データ

リレーションシップ
エンティティ同士のつながり

  • 依存型

  独立エンティティから従属エンティティに向けて引く線

  • 非依存型

  独立エンティティから他の独立エンティティに向けて引く線

  • 凡化型

  親子エンティティが凡化関係(あまりおすすめしない)
  DBとプログラム(しかも凡化はオブジェクト指向)とは、考え方は別物
  DBは集合論

Amazon CAPTCHA

ERDに関しては、こちらの本がおすすめです。2006年に発売されたので10年前になりますが、現在でも十分に使える知識が詰まっています。
上記に記したことはこちらの本を主に参考にしました、

データ・モデル

種類
1. サブジェクトエリアモデル
2. 概念データ・モデル
3. 論理データモデル
4. 物理データモデル

目的
静的なビジネスの全体像を把握すること。
パターンによる抽象化は、ビジネスユーザーによって、わかりにくい。
ビジネスの把握が可能な範囲に適用する。

概念データモデリング

目的

ビジネスの全体像をつかむ(超絶重要!!!)

主な工程

  • イベント系エンティティの抽出と定義(必須作業)
  • リソース系エンティティの抽出と定義(任意作業・この時点で全くでてこない)
  • サブジェクトエリアに分割(仕分けみたいなもの)

 - データ・モデル全体を幾つかに区切った場合の各領域(業務、事業単位など)
 - 目的
  - データ・モデルを見やすくする
  - 必要のないのに無理に分割する必要なし(目安は10~30あたり(好みにもよる))

  • リレーションシップの線引く

 リレーションシップの意味の定義分は '〜する' という動詞句で統一する
 矢印の方向に沿って、'親は子を〜する' という書き方(1:Nの関係)

  • 必ず定義する!定義しないことは、十中八九破綻する!
  • マスタ管理方法を決める!
  • ビジネス・ルール整備&定義する!

死守(絶対に守るライン)

概念データ・モデルは余程のことが無い限り作成する。
さもないと、木を見て、森をみないことになり、
後工程で手戻りが生じる恐れあり(見える化できていないため)
結果的に恐ろしいほどの運用コストを跳ね上げることになる。
たとえリリースできたとしても、赤字システム(負の遺産)になるのが関の山

補足

ビジネスの静的側面を管理する指標、設計となるため、
PJ単位ではなく、組織単位で概念データ・モデルを作成したほうがいい。
これを実践している企業は、自社のビジネスモデルを作成維持できている。
業務系とコンシューマ系との垣根がなくなってきており、コンシューマ系でも近年かなり実施されている。