2017年6月21日水曜日

作成した gem ファイルを rubygems.org で公開する手順

概要

前回 独自の gem ファイルを作成する方法を紹介しました
今回は作成した gem ファイルを rubygems.org で公開する手順を紹介します

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • gem 2.6.11
  • rake 10.4.2

サインアップ

https://rubygems.org/sign_up からアカウントを作成しましょう
メールアドレスがあれば簡単に作成できます

登録したメールアドレスに確認用の URL が送信されるのでクリックしてアカウントを承認します
問題なくログインできるか確認してください

gem の作成

とりあえず今回は何でも OK です
本記事では 前回 の記事を参考に gem を作成しています

とりあえずリリースしてみる

まず Github にソースをアップロードします
Github じゃなくても良いですが remote のリポジトリがないと怒られます

でコードを push しましょう
そして

  • bundle exec gem push

で rubygems.org に認証します
登録したメールアドレスとパスワードで認証しましょう
cat ~/.gem/credentials ができれば OK です
そして

  • bundle exec rake release

で gem ファイルアップロードできます

fstlib 0.1.0 built to pkg/fstlib-0.1.0.gem.
Tag v0.1.0 has already been created.
Pushed fstlib 0.1.0 to rubygems.org.

こんな感じになれば成功です
rubygems の自分のアカウントのページにアクセスするとアップロードした gem があると思います

バージョンアップしてみる

適当にコードを改修してバージョンアップしてみましょう

  • vim lib/fstlib.rb
require "fstlib/version"

module Fstlib
  def self.hello
    "My second rubygems"
  end
end
  • vim lib/fstlib/version.rb
module Fstlib
  VERSION = "0.1.1"
end

でコードを改修したら

  • git add .
  • git commit -m “Second commit”
  • git push -u origin master

でコミットして

  • bundle exec rake release

で再度アップロードします
アップロード完了後再度 gem のページを見てみるとバージョンが 0.1.1 に上がっていることが確認できると思います

使ってみる

新しいスクリプトを作成してアップロードした gem が使えるか確認してみましょう

  • bundle init
  • vim Gemfile
gem "fstlib"
  • bundle install

で rubygems.org からアップロードした gem がインストールできます

  • vim test.rb
require 'fstlib'

p Fstlib.hello
  • bundle exec ruby test.rb

実行すると "My second rubygems" が表示されると思います

最後に

作成した gem を rubygems.org で公開する手順を紹介しました
今回紹介した rake release の流れは「ビルド -> コミットがあるかチェック -> リモートに push -> rubygems.org にプッシュ」という流れをやってくれています
https://github.com/bundler/bundler/blob/master/lib/bundler/gem_helper.rb#L55

なので bundle exec gem push を実施しないでいきなり release しても初回であれば rubygems.org に対する認証を聞かれると思います
うまくログインができない場合は個別で gem push を実行すると良いと思います

また削除する場合はコマンドで行います

  • gem install gemcutter
  • gem yank fstlib -v 0.1.1
  • gem yank fstlib -v 0.1.0

という感じで全バージョン削除すると rubygems.org から消えます

参考サイト

2017年6月20日火曜日

RSpec で undefined method 'get' for が出たときの対処法

概要

エラーの詳細は以下の通り

undefined method `get' for #<RSpec::ExampleGroups::MyApp:0x00000002fc48e0>
Did you mean?  gets
               gem

Sinatra など get リクエストを送信するテストが実行できない状況です

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • rspec 3.6.0

対処方法

  • rspec --init

で .rspec ファイルを作成します
中身は

--require spec_helper

となっているので .rspec ファイルを作成しないで spec_helper.rb で require しても大丈夫だと思います

2017年6月19日月曜日

Sinatra で設定ファイルを使う方法

概要

sinatra-contrib というパッケージを使うと簡単に設定ファイルを読み込むことができます
実際に設定ファイルを使ったサンプルの sinatra アプリケーションを紹介します

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • sinatra 2.0.0
  • sinatra-contrib 2.0.0

ライブラリのインストール

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "sinatra-contrib"
  • bundle install

また今回のディレクトリ構成は以下の通りです

.
├── app.rb
├── config.ru
├── config.yml
├── Gemfile
├── Gemfile.lock
└── views
    └── hello.erb

アプリの作成

  • vim app.rb
require "sinatra/base"
require "sinatra/config_file"

class MyApp < Sinatra::Base
  register Sinatra::ConfigFile
  config_file './config.yml'

  get '/' do
    @message = settings.message
    @friends = settings.friends
    erb :hello
  end
end

Sinatra::Base を継承して独自のコントローラクラスを定義します
そして、設定ファイルを読み込むために Sinatra::ConfigFile を登録します
読み込みに成功した後は settings というオブジェクトを参照することで値を取得することができます
今回は取得した値をテンプレートに渡してテンプレート側で値を確認します

設定ファイルの作成

  • vim config.yml
message: "My friends"
friends:
  - name: 'bob'
    age: 20
    favorites:
      - 'baseball'
      - 'soccer'
  - name: 'tom'
    age: 19
    favorites:
      - 'soccer'

app.rb と同じディレクトリに配置します
YAML 形式で記述します
今回は配列も使っています

テンプレートファイルの作成

  • mkdir views
  • vim views/hello.erb
<h2>"<%= @message %>"</h2>
<% @friends.each do |f| %>
<div>
  <div>name: <%= f['name'] %></div>
  <div>age: <%= f['age'] %></div>
  <div>favorites:</div>
  <% f['favorites'].each do |fa| %>
    <div>- <%= fa %></div>
  <% end %>
</div>
<% end %>

app.rb から呼び出されるテンプレートファイルです
設定ファイルの内容を出力しています
ハッシュとして渡ってくるので ruby のハッシュを参照する方法で値を取り出すことができます

アプリ起動用の rackup ファイル作成

  • vim config.ru
require 'bundler'
Bundler.require

require './app'
run MyApp

rackup コマンドでアプリを起動するためのスクリプトです

動作確認

  • bundle exec rackup
  • curl localhost:9292

で config.yml に書かれた情報が HTML で出力されると思います

最後に

sinatra で YAML で書かれた設定ファイルを読む込む方法を紹介しました
値の参照は settings オブジェクトを使って参照しますがスコープは sinatra アプリケーション内のみとなります
なので、自分で定義したライブラリの関数などに値を渡したい場合は引数などで渡す必要があります (ライブラリ側で settings を参照することはできません)

参考サイト

2017年6月17日土曜日

独自のコントローラクラスを作成している sinatra を rspec でテストする

概要

Sinatra::Base を継承して独自のコントローラクラスを作成している場合に rspec を使ってテストする方法を紹介します
少し工夫が必要でした

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • rspec 3.6.0
  • sinatra 1.4.8

インストール

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "rspec"
gem "rack-test"
  • bundle install

独自のコントローラクラスの作成

  • vim app.rb
require 'sinatra/base'

class MyApp < Sinatra::Base
  get '/' do
    'myapp'
  end
end

このクラス内でアプリを起動 (run) することはしません

rackup で起動させる

  • vim config.ru
require 'bundler'
Bundler.require

require './app'
run MyApp

これで rackup コマンドで MyApp アプリケーションが起動します
こうすることで独自のコントローラクラスを切り離しテストし易いようにします
もし MyApp 内で run! をしてしまうと rspec でテストを実行したときにアプリケーションが起動してしまいテストが進まなくなってしまいます

今回は直接アプリを起動しませんがもし起動させたい場合は

  • bundle exec rackup

で 127.0.0.1:9292 で起動します

テストの作成

  • rspec --init
  • vim spec/app_spec.rb
# -*- coding: utf-8 -*-
ENV['RACK_ENV'] = 'test'

require './app.rb'
require 'rspec'
require 'rack/test'

describe "MyApp のテスト" do
  include Rack::Test::Methods

  def app
    MyApp
  end

  it "root にアクセスしたときに myapp を返却する" do
    get '/'
    expect(last_response.status).to eq 200
    expect(last_response.body).to eq 'myapp'
  end
end
  • bundle exec rspec -f d spec/
MyApp のテスト
  root にアクセスしたときに myapp を返却する

Finished in 0.02609 seconds (files took 0.18599 seconds to load)
1 example, 0 failures

こんな感じになれば成功です

おまけ spec_helper.rb を使って記述を楽にする

今のままだと describe するたびに include して def しなければなりません
そんなときに spec_helper.rb を使って設定しておくと省略することができます

  • vim spec/spec_helper.rb
ENV['RACK_ENV'] = 'test'

require File.expand_path '../../app.rb', __FILE__
require 'rack/test'
require 'rspec'

module RSpecMixin
  include Rack::Test::Methods

  def app
    MyApp
  end
end

RSpec.configure do |config|
  config.include RSpecMixin
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.shared_context_metadata_behavior = :apply_to_host_groups
end

デフォルトのコメントはすべて削除しました
要するにテスト内でやっていた処理を module 化して、それを Rspec.configure にあらかじめ設定してあげる感じです

テスト側は以下のように変更します

  • vim spec/app_spec.rb
# -*- coding: utf-8 -*-
require File.expand_path '../spec_helper.rb', __FILE__

describe "MyApp のテスト" do
  it "root にアクセスしたときに myapp を返却する" do
    get '/'
    expect(last_response.status).to eq 200
    expect(last_response.body).to eq 'myapp'
  end
end

かなりすっきりしました
冒頭で spec_helper.rb を require するだけになります

あとは同じようにテストを実行すれば OK です

  • bundle exec rspec -f d spec/

最後に

Sinatra で独自のコントローラクラスを定義している場合に rspec でテストする方法を紹介しました
ポイントはアプリケーションの起動を rackup を使うということでした

参考サイト

2017年6月16日金曜日

rspec のインストールとサンプルテストの実行

概要

rspec に入門しました
Getting Started の手順を紹介します

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222
  • rspec 3.6.0

インストール

  • bundle init
  • vim Gemfile
gem "rspec"
  • bundle install

テスト対象のクラス作成

  • vim car.rb
class Car
  def initialize(gas, kilo)
    @gas = gas
    @kilo = kilo
  end

  def available
    @gas / @kilo
  end
end

テスト作成

  • vim car_spec.rb
require './car.rb'

RSpec.describe '車のスペック' do
  it 'ガソリン 100 リットルなら 10 km 走れます' do
    c = Car.new(100, 10)
    expect(c.available).to eq 10
  end
end

テスト実行

  • rspec -f d car_spec.rb

もしくは

  • bundle exec rspec -f d car_spec.rb
車のスペック
  ガソリン 100 リットルなら 10 km 走れます

Finished in 0.00202 seconds (files took 0.08629 seconds to load)
1 example, 0 failures

おまけ spec_helper を使う

  • bundle exec rspec --init
  • mv car_spec.rb spec/
  • rspec -f d spec/
  • vim spec/spec_helper.rb

=begin から =end の部分を削除します

  • bundle exec rspec -f d spec/

そして再度テストを実行してみると spec_helper.rb の設定が反映され結果の表示が変わると思います

最後に

ゼロから rspec をとりあえず実行できるところまでやってみました
mock 系や他の Tips もあれば別記事で紹介したいと思います

参考サイト

2017年6月15日木曜日

Swift3 で画像を良い感じにキャッシュしてくれる Kingfisher を使ってみた

概要

UITableView の UITableViewCell 内に ImageView がありテーブルをスクロールするたびに画像をダウンロードするアプリがありました
Android と同様でセルが表示されるたびに画像をダウンロードしてしまう仕様でかなりいけていませんでした
そんなとき Kingfisher という素晴らしいライブラリに出会いこれがやりたいことを完璧にやってくれたので使い方を紹介します

環境

  • macOS 10.12.5
  • Xcode 8.3.3 (8E3004b)
  • Kingfisher 3.3.3

インストール

Podfile に以下を追記して install するだけです

pod 'Kingfisher'

使いたい .swift ファイルの先頭で import しましょう

import Kingfisher

画像を読み込む

今まで以下のようなコードで UITableViewCell 内の ImageView に画像をダウンロードして設定していました

let imageURL: NSURL? = NSURL(string: img["src"]!)
let req = NSURLRequest(url: imageURL! as URL)
NSURLConnection.sendAsynchronousRequest(req as URLRequest, queue: OperationQueue.main) { (res, data, err) in
    let image = UIImage(data: data!)
    imageView.image = image
}

これが cellForRowAt メソッドで実装されているためセルが表示されるたびに sendAsynchronousRequest が使われるため無駄に通信を行っていました

これを Kingfisher を使って以下のように書き換えます

imageView.kf.indicatorType = .activity
imageView.kf.setImage(with: URL(string: img["src"]!),
                      // キャッシュがある場合はキャッシュを使用する
                      completionHandler: {
                        (image, error, cacheType, imageURL) in
                        imageView.image = image
})

まだ画像がダウンロードできていない場合には URL から画像をダウンロードします
そして、ダウンロード中はインジケータを表示します
画像がダウンロードできているセルにスクロールした場合には completionHandler 側がコールされすでにキャッシュされている image を imageView に設定するだけでキャッシュが利用できます

動作確認

アプリをビルドして確認してみましょう
改修前はスクロールするたびに画像をダウンロードするため、回線状況の悪い場合だとセルに正しい画像が表示されなかたり、画像の表示が追いつかなかったりします

Kingfisher を使った改修後はセルをスクロールしまくってもダウンロード中にインジケータが表示されるし、ダウンロードが完了した画像はキャッシュから表示してくれるため、画像の表示が遅れたりすることが一切ありません

比較のスクリーンショットがないのが残念ですがかなり快適な感じになると思います

またアプリを kill して再度立ち上げてもキャッシュが残っている状態だったのですでにダウンロード済みの画像に関してはすぐに表示されました

最後に

Swift で Kingfisher を使ってみました
かなり良い感じのライブラリでビックリしました
Swift には他にも同じようなライブラリがあるようで Moa や Vincent, MapleBacon などあるようです
今回 Kingfisher を使ったのは単純に一番人気そうだったので使いましたが他のやつも代用可能だと思います

画像ダウンロード系はもう全部 Kingfisher を使っていこうと思います

参考サイト

2017年6月14日水曜日

ruby の標準 logger でクラスの変数情報を出力する方法

概要

答え -> inspect を使います

環境

  • CentOS 7.3.1611
  • ruby 2.3.3p222

サンプルコード

require 'logger'

class Sub
  def initialize(hoge)
    @hoge = hoge
  end
end


logger = Logger.new(STDOUT)
s = Sub.new 'hoge'
logger.info "#{s.inspect}"

出力

I, [2017-06-14T15:56:06.326706 #26690]  INFO -- : #<Sub:0x00000002454b90 @hoge="hoge">