blog

紫陽花

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単位ではなく、組織単位で概念データ・モデルを作成したほうがいい。
これを実践している企業は、自社のビジネスモデルを作成維持できている。
業務系とコンシューマ系との垣根がなくなってきており、コンシューマ系でも近年かなり実施されている。

Rails5 に Draper 導入した際にエラーが出た

Rails5を使ってて出たエラーをメモ

Rails5が正式にリリースされてもうすぐ2ヶ月ほど経とうとしています。

Ruby on Rails

最近新しく作り始めたアプリケーションにRails5を採用して開発を始めました。
そこで開発を効率よく進めていくために色々とGemを導入していたのですが、導入の際にDraperでエラーが出たのでそれら紹介します。

Draper

DraperはRailsにDecorator機能を設けてくれるGemです。
Viewに渡したデータの整形を行いたい時にによしなにやってくれて、viewを汚さず実装できるようにしてくれます。
2016年8月25日現在では、Rails5では機能変更によって、最新版のDraperをinstallしたとしてもエラーになってしまいます。
導入でつまづいた時にはどうしようか迷ったのですが、色々と調べてみると解決策を見つけました。以下にのように書いて動かしました。
Gemfile

gem 'activemodel-serializers-xml', git: 'https://github.com/rails/activemodel-serializers-xml'
gem 'draper', github: 'audionerd/draper', branch: 'rails5'

どうやらRails5からは、active_model/serializers/xmlがgem化されてデフォルトではインストールされなくなっているようです。
なので上記のように別でインストールすると解決できます。

また、draper3.0.0pre1が公開されており、そちらをインストールしてもいけると思います。
3.0.0pre1ではdraperと一緒にactivemodel-serializers-xmlもインストールしてくれます。

Rails 複数のカラムに 一意制約 (ユニーク)を設ける

Rails ActiveRecordで複数のカラムに対して一意制約を設ける。複合一意制約

複数のカラムの組み合わせがユニークであってほしい場面に遭遇しました。

例えば、Siteテーブル Keywordテーブルが存在する時、

Keywordテーブルにある site_id と name そして date の組み合わせはユニークでなければいけない状況でした。

site_id, name, date の組み合わせがユニー

この組み合わせのユニークを担保するために、Railsアプリケーション側でユニークであることを確認するvalidate処理を設けて解決させました。

class Keyword < ActiveRecord::Base
  validates :site_id,  uniquness: { scope: [:name, :date]  }
end

上記の様に書くことで、データのinsertを行う際にvalidationが走り、値がユニークであるかどうかを確認するようになります。

既に組み合わせが存在していた場合にはデータのinsert処理をRollBackされます。

scopeの配列に値を追加すればさらに複数のカラムの制約を追加することができます。

もう一つ、一意制約の状態を担保する手段としてDB側にunique indexを追加する方法があります。

class CreateKeyword < ActiveRecord::Migration
  def  change
    create_table  :keywords do |t|
        t.integer  :site_id,  null: false
        t.string    :name,    null: false
        t.date      :date,    null:  false
        t.timestamp
    end
    add_index  :keywords, [:site_id, :name, :date], unique: true
  end
end

add_indexで site_id, name, date の3つのカラムにuniqueであることを追加しています。
上記でデータをInsetする際にDB側で処理が制限されるようになります。

以上のようにDBに保存するデータのユニーク状態を担保する方法は、

1. アプリケーション側でvalidateを設けて制御する
2. DB側でカラムに対して制約を設けて制御する

という2つの方法が存在します。

方法としては2つ存在しますが、個人的には2つとも制約を付けるべきだと考えています。

理由としてはアプリケーションレイヤーでのエラーなのか、DBレイヤーでのエラーなのかをはっきりさせることで未然にバグや、処理の抜け漏れに対応することができるからです。

制約を設けなかったが故に不正な値が存在してしまい、その修正対応で追われて苦労しないためにも、DBの制約はしっかり定義しましょう。

Rubyのあれこれ、

あれこれ

普段Rubyを触っていてこれは学びとして記録しておきたいなと思ったものを紹介していきます。

OpenStruct

Rubyには手軽に構造体を作成することができるOpenStructクラスが用意されています。

require 'ostruct'
ab = OpenStruct.new
ab.foo = 25
p ab.foo          # => 25
ab.bar = 2
p ab.bar          # => 2
p ab              # => <OpenStruct foo=25, bar=2>
ab.delete_field("foo")
p ab.foo          # => nil
p ab              # => <OpenStruct bar=2>

使いたい場合には require 'ostruct' で宣言をします。

ab.foo = 25 

このように要素に値をセットしていきます。

OptionParser

コマンドラインのオプションを扱うためのライブラリ

require 'optparse'
opt = OptionParser.new

opt.on('-a') {|v| p v }
opt.on('-b') {|v| p v }

opt.parse!(ARGV)
p ARGV

ruby sample.rb -a foo bar -b baz
# => true
     true
     ["foo", "bar", "baz"]

require 'optparse' で宣言を行う
OptionParserクラスのインスタンスを作成し、
インスタンスに引数があった場合の動きを定義します。

parseメソッドが呼ばれた時に、コマンドラインにオプションが指定されていればそのオプションの動きが実行される。

NoImplementedError

Rubyでプログラムを実装する際にovverrideすることを強制するためにNoImplementErrorを強制的に呼び出す方法。
Baseクラスに主機能をまとめておき、それを継承する形で子classを実装する。
子クラスのインスタンスでメソッドを呼び出した際に、overrideで実装されていなければエラーが起きるように親クラスにNoImprementedErrorを定義する。

class Pokemon
  attr_reder :name

  def initialize(name)
    @name = name
  end

  def show
    "#{name}#{revel} レベルで #{type} タイプだ!"
  end

  private

  # サブクラスで必ず実装する
  def revel
    raise NotImprementedError.new("You must implement #{self.class}##{__method__}")
  end

  def type
  raise NotImprementedError.new("You must implement #{self.class}##{__method__}")
  end
end

class Hushigidane < Pokemon
  private 

  def level
    "5"
  end
end

class Zenigame < Pokemon
  private
  
  def type
    "みず"
  end
end

class Hitokage < Pokemon
  private

  def level
    "12"
  end

  def type
    ""
  end
end

Hushigidane.new("フシギダネ").show
#=> NotImplementedError: You must implement Hushigidane#type

Zenigame.new("ゼニガメ").show
#=> NotImplementedError: You must implement Zenigame#level

Hitokage.new("ヒトカゲ").show
#=> "ヒトカゲ は 12 レベルで 火 タイプだ!"