RubyGemLaravel

Laravelを使ったことがあるならわかる、CollectionをRubyに移植しながらGemの作り方を学ぶ

はじめに

Ateam Lifestyle x cyma Advent Calendar 2018 24 日目は、株式会社エイチームライフスタイル エンジニア @mziyut が担当します。

今回は、PHP を利用していると多くの場で見かける、「Love beautiful code? We do too. 」でお馴染みLaravel その Laravel に存在するCollectionsを Ruby でも使えるよう Gem を作成して行こうと思います。👏

この投稿から、PHPer の Ruby への敷居を下げることと、Rubyist の Gem 作成の手助けになり、 読んでくださった方々が Gem の作り方を学び、OSS への貢献への第一歩となればと思ってます。

Collections について

はじめにでも触れましたが、Collectionsを今回は Ruby でも使えるようにしていきます。

Laravel については割愛したいので、こちらの投稿を参照してみてください。 Laravel 入門-Laravel とは?特徴は?環境構築は? - Qiita

今回フォーカスするCollectionsは、引数渡される配列や、オブジェクト等の定義されている値を 簡単に扱えるようにしてくれる wrapper のようなものです。

ちなみに、JavaScript ではすでに npm にcollect.js - npmとして公開されています。 今回は、Ruby のため Hash、Object、Array の 3 種類に対応しようと検討中です。

Collections の実行例を理解して頂きやすい、all()sum()だけ記載します。 その他、method については、Collections - AvailableMethodsを参照してください。

/**
 * 配列の場合
 */
$collection = new Collection([1,2,3]);
echo $collection->all();
// [1,2,3]
echo $collection->sum();
// 6

/**
 * オブジェクトの場合
 */
$obj1 = new stdClass();
$obj1->number = 1;

$obj2 = new stdClass();
$obj2->number = 2;

$array = [];
$array[] = $obj1;
$array[] = $obj2;

$collection = new Collection($array);
echo $collection->sum('number');
// 3

Gem を作成

1. Gem の雛形を作成

  • まず Gem の雛形を作成しましょう
    今回は、rspec も使いたかったため、 -t オプションをつけてコマンドを実行しました。 + bundle gemの細かなオプションについてはこちらを参照ください
$ bundle gem collection_rb -t
Creating gem 'collection_rb'...
MIT License enabled in config
Code of conduct enabled in config
      create  collection_rb/Gemfile
      create  collection_rb/.gitignore
      create  collection_rb/lib/collection_rb.rb
      create  collection_rb/lib/collection_rb/version.rb
      create  collection_rb/collection_rb.gemspec
      create  collection_rb/Rakefile
      create  collection_rb/README.md
      create  collection_rb/bin/console
      create  collection_rb/bin/setup
      create  collection_rb/.travis.yml
      create  collection_rb/.rspec
      create  collection_rb/spec/spec_helper.rb
      create  collection_rb/spec/collection_rb_spec.rb
      create  collection_rb/LICENSE.txt
      create  collection_rb/CODE_OF_CONDUCT.md
Initializing git repo in /Users/mziyut/Workspace/github.com/mziyut/collection_rb
  • コンソールにも出ている通り、collection_rbディレクトリが作成され、
    中に Gem を作成する上で必要なものが一通り作成されます。
  • また、collection_rbディレクトリ以下は、
    git レポジトリとして、イニシャライズされ今回生成されたファイルがすべてステージに上がっています。
$ cd collection_rb
$ ls -la
total 72
drwxr-xr-x  15 mziyut  staff   480 Dec 15 09:31 .
drwxr-xr-x  13 mziyut  staff   416 Dec 15 09:31 ..
drwxr-xr-x  10 mziyut  staff   320 Dec 15 09:31 .git
-rw-r--r--   1 mziyut  staff    87 Dec 15 09:31 .gitignore
-rw-r--r--   1 mziyut  staff    31 Dec 15 09:31 .rspec
-rw-r--r--   1 mziyut  staff    88 Dec 15 09:31 .travis.yml
-rw-r--r--   1 mziyut  staff  2381 Dec 15 09:31 CODE_OF_CONDUCT.md
-rw-r--r--   1 mziyut  staff    98 Dec 15 09:31 Gemfile
-rw-r--r--   1 mziyut  staff  1077 Dec 15 09:31 LICENSE.txt
-rw-r--r--   1 mziyut  staff  1553 Dec 15 09:31 README.md
-rw-r--r--   1 mziyut  staff   117 Dec 15 09:31 Rakefile
drwxr-xr-x   4 mziyut  staff   128 Dec 15 09:31 bin
-rw-r--r--   1 mziyut  staff  1426 Dec 15 09:31 collection_rb.gemspec
drwxr-xr-x   4 mziyut  staff   128 Dec 15 09:31 lib
drwxr-xr-x   4 mziyut  staff   128 Dec 15 09:31 spec
  • 雛形ができたことを確認できたため、ひとまずコミットし、リモートにプッシュておきましょう
$ git commit -m "creating gem 'collection_rb'"
[master (root-commit) d137bc9] creating gem 'collection_rb'
 15 files changed, 213 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .rspec
 create mode 100644 .travis.yml
 create mode 100644 CODE_OF_CONDUCT.md
 create mode 100644 Gemfile
 create mode 100644 LICENSE.txt
 create mode 100644 README.md
 create mode 100644 Rakefile
 create mode 100755 bin/console
 create mode 100755 bin/setup
 create mode 100644 collection_rb.gemspec
 create mode 100644 lib/collection_rb.rb
 create mode 100644 lib/collection_rb/version.rb
 create mode 100644 spec/collection_rb_spec.rb
 create mode 100644 spec/spec_helper.rb
$ git remote add origin git@github.com:mziyut/collection_rb.git
$ git push -u origin master
git push -u origin master
Enumerating objects: 21, done.
Counting objects: 100% (21/21), done.
Delta compression using up to 4 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (21/21), 5.17 KiB | 331.00 KiB/s, done.
Total 21 (delta 0), reused 0 (delta 0)
To github.com:mziyut/collection_rb.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

2. collection_rb.gemspec の更新

  • Gem の雛形を作成した際に、TODOと記載されている以下の部分を変更しましょう。
    • spec.summary
    • spec.description
    • spec.homepage
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'collection_rb/version'

Gem::Specification.new do |spec|
  spec.name          = "collection_rb"
  spec.version       = CollectionRb::VERSION
  spec.authors       = ["Yuta Mizui"]
  spec.email         = ["me@mziyut.com"]

  spec.summary       = %q{TODO: Write a short summary, because Rubygems requires one.}
  spec.description   = %q{TODO: Write a longer description or delete this line.}
  spec.homepage      = "TODO: Put your gem's website or public repo URL here."
  spec.license       = "MIT"

  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
  # to allow pushing to a single host or delete this section to allow pushing to any host.
  if spec.respond_to?(:metadata)
    spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
  else
    raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
  end

  spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.12"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "rspec", "~> 3.0"
end

3. Gem に機能を実装

  • それでは一通り準備が整ったため、機能を実装していきましょう。

依存パッケージのインストール

  • 普段開発を行うときと同じように、bundle installを実行しましょう
$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/..
Resolving dependencies...
Using bundler 1.12.5
Using collection_rb 0.1.0 from source at `.`
Installing rspec-support 3.8.0
Installing diff-lcs 1.3
Installing rake 10.5.0
Installing rspec-expectations 3.8.2
Installing rspec-core 3.8.0
Installing rspec-mocks 3.8.0
Installing rspec 3.8.0
Bundle complete! 4 Gemfile dependencies, 9 gems now installed.
Bundled gems are installed into ./vendor/bundle.

実装の検討

  • 今回は、collections#method-allを実装していきます。
    • 引数に、配列、ハッシュ、オブジェクト等を予め渡しておき、allを call すると渡しておいた値をそのまま返却します。
PHP での動作イメージは以下となります
$collection = new Collection([1,2,3]);
$collection->all();
// => [1, 2, 3]
同じふるまいを ruby で実現させた場合の動作イメージは以下となります
collection = CollectionRb::Collection.new([1,2,3])
collection.all
# => [1, 2, 3]

テストの作成

  • それではある程度実装のイメージができたところでテストを書いていきましょう。
require 'spec_helper'

describe CollectionRb do
  it 'has a version number' do
    expect(CollectionRb::VERSION).not_to be nil
  end

  describe '.all' do
    let(:collection_rb) { CollectionRb::Collection.new(params)  }
    subject { collection_rb.all }
    describe '渡される値が配列の場合' do
      context '配列の中身がIntの場合' do
        let(:params) { [1,2,3,4,5] }
        it { is_expected.to eq params  }
      end
      context '配列の中身がObjectの場合' do
        let(:params) do
          objects = []
          objects << Object.new
          objects << Object.new
          objects
        end
        it { is_expected.to eq params  }
      end
    end
    context '渡される値がHashの場合' do
      let(:params) { { a: 1, b: 2, c: 3 } }
      it { is_expected.to eq params }
    end
  end
end

ロジックを記述しましょう

  • テストを書いたので、肝心なロジックを作っていきます
  • ただ、今回は値をただ返却するだけなのでとてもシンプルです。
require "collection_rb/version"

module CollectionRb
  class Collection
    def initialize(params)
      @params = params
    end

    def all
      @params
    end
  end
end

Gem を公開

Rubygems に公開

  • まだまだ、機能は足りていませんが、作成したものはRubygemsに公開してみましょう
  • 公開するには、以下コマンドを実行すると公開することができます。
    • マシンスペックによって時間がかかります
$ bundle exec rake release

おわりに

  • 今回は、Gem の作成から公開までを書きました。
  • Gem を作成するのは非常に簡単ため、みなさんもぜひ作ってみてはいかがでしょうか。
  • また、今回作成出来なかった.all以外の105 の機能についてはお正月休みのときに全部作りきろうと思ってます 😇

Ateam Lifestyle x cyma Advent Calendar 2018 25 日目アドベントカレンダー最終日は @sakupa80 さんです!

お知らせ

エイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。 https://www.a-tm.co.jp/recruit/