かずおの開発ブログ(主にRuby)

日々の開発のことを色々書きます。

Rubyチートシート

たまに使おうと思うと忘れていて毎回リファレンスを見る→理解する→書くが面倒なので、自分が一瞬で思い出せるようにメモ。

文字列処理

特定の文字列を切り出したい

  • slice : 何文字目から何文字分切り出すって時に使う。戻り値は切り取られた値。[]と同義。
str = "hello"
str.slice(0, 2)  => "he" # str[0,2]と同義
str.slice(/\w+/) => "hello" # 正規表現も使える
  • match : 正規表現を引数にしてMatchDataを返す。(MatchDataに対する理解がイマイチ)
str = "おじいさんは山へ芝刈りにおばあさんは川へ洗濯に行きました。
str.match(/おじいさん/)
=> #<MatchData "おじいさん">
特定の文字列にマッチするものを全て切り出したい
  • scan : マッチしたものを全て配列に格納して返す
str = "hello goodmorning goodnight" 
str.scan(/\w+/)
=>["hello", "goodmorning", "goodnight"]

特定の文字列を特定の文字列で置換したい

  • gsub
str = "good morning hello good night"
str.gsub("good", "great")
=> "great morning hello great night"

特定の文字列を削除したい

  • delete: 特定の文字列を削除したい時に使う。削除する文字列があった場合は削除後の文字列、ない場合はnilが戻り値になる。
str = "hejamallojama"
str.delete("jama") => "hello"
str.delete("nai")    => nil

配列

配列をくっつけて文字列にしたい

  • join
array = ["東京", "大阪", "名古屋"]
array.join("|")
=> "東京|大阪|名古屋"

配列から特定の条件に合う要素を取得したい

  • select : ブロック内の条件に合う要素を配列で返す
array = %i(10 20 30 40)
array.select{|num| num > 20}
=> [30, 40]
array.select{|num| num > 30}
=> [40] #要素が1つでも配列として返るので注意が必要
array.select{|num| num > 50}
=> []

その他よく忘れる系

  • tap :tapメソッドは、ブロック変数にレシーバ自身を入れてブロックを実行します。戻り値はレシーバ自身です。メソッドチェーンの中にtapメソッドをはさみ込み、ソースコードを簡潔にする目的で使われます。ポイントはブロック内にレシーバ自身が入ること。 理解するためだけの単純な例
array = [10, 20, 30]
array.tap{ |array| array << 40 } #array自身がブロックに渡される。
=> [10, 20, 30, 40]

Railsでぺージ内タブを実装する(powerd by BootStrap)

今回はBootstrapを使ってRailsアプリにぺージ内タブを実装します。

Rails内でBootstrapを使うにはtwitter-bootstrap-rails gemを導入しておく必要がありますので、あらかじめこのあたりのぺージを参考に 導入しておいてください。

seyhunak/twitter-bootstrap-rails · GitHub

RailsにTwitter Bootstrapの導入と簡易な使い方 - Rails Webook

htmlを作成

導入できたらhtmlを作成します(別途 application.htmlはある前提です)

"nav nav-tabs"クラス配下がタブリストになっていて、"tab-content"クラス配下が各タブの内容になっています。

"active" クラスが現在選択されているタブです。

tab_sample.html

<div>
  <ul class="nav nav-tabs" role="tablist">
    <li class="active" role="presentation">
      <a aria-controls="tab_a" data-toggle="tab" href="#tab_a" role="tab">A</a>
    </li>
    <li role="presentation">
      <a aria-controls="tab_b" data-toggle="tab" href="#tab_b" role="tab">B</a>
    </li>
    <li role="presentation">
      <a aria-controls="tab_c" data-toggle="tab" href="#tab_c" role="tab">C</a>
    </li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane active" id="tab_a" role="tabpanel">
      <p>
        タブAだよ
      </p>
    </div>
    <div class="tab-pane" id="tab_b" role="tabpanel">
      <p>
        タブBです
      </p>
    </div>
    <div class="tab-pane" id="tab_c" role="tabpanel">
      <p>
        タブC!!
      </p>
    </div>
  </div>
</div>

画面で確認

画面で確認してみます。

f:id:kazuooooo:20160113000435p:plain

Bootstrapっぽい、いい感じなタブになってますね!

思っていたよりも遥かに簡単でした!Bootstrapすごい!

参考: JavaScript · Bootstrap

Railsでfaviconを設定する

前回に引き続き超基本ですが、検索した時に一番上に出てきた

favicon_link_tagメソッドを使ったファビコンの設定 - Ruby on Rails入門

でうまくいかなかったのでうまくいった方法を書いておきます。

faviconを作成する

既成のものを使ってもいいですが、せっかくなので自分で作成しました。適当なペイントソフトで書いて

こちらのサイトで簡単に.icoに変換出来ました。

作ったのがこれです。うまですね。 f:id:kazuooooo:20160106002918p:plain

favicon_link_tagを追加

app/views/layouts/application.html.erbにfavicon_link_tagを追加します。

<!DOCTYPE html>
<html lang="en">
  <head>
  〜中略〜
  <%= favicon_link_tag %>
  </head>

オプションとかありますが、あまり使わないと思うので気にしません。

favicon.icoを保存する

上で作成したファビコンをfavicon.icoという名前で/app/assets/imagesフォルダに保存します。

確認

ページで確認してみましょう。出来ましたね! f:id:kazuooooo:20160106004112p:plain

MySQLのユーザーを作成する

基本中の基本ですが、僕のように初心者の人には役に立つかなと思うので残しておきます。

サーバーを起動

$ mysql.server start

rootでログイン

$ mysql --user=root;

userを作成('ユーザー名'@'ホスト名'で作成)

CREATE USER 'user'@'localhost' IDENTIFIED BY 'password';

権限付与

GRANT ALL PRIVILEGES ON *.* TO 'user'@'localhost' WITH GRANT OPTION;

ログイン確認

$ mysql --user='user' -p
$ 上で設定したパスワードを入力
mysql>

ユーザー確認(実際はrootとかも表示されます。)

mysql> select User,Host from mysql.user;
# ユーザー、ホストの一覧が表示される。上で作成したユーザーがいればOK

VCRを使って開発中にあほみたいにリクエストを飛ばさないようにする

RubyでWebサイトからスクレイピングしまくってデータを取得しまるくるようなプログラムを書いていると開発のテスト中にリクエストをあほみたいに飛ばしてしまうときがあります。

そうするとそのサーバーに迷惑がかかってしまいますし、あんまりいい気持ちのするものでもありません。そこで今回はVCRというgemを使ってこの問題を解決してみたいと思います。

VCRとは

vcr/vcr · GitHub

VCRとはテスト中に過去に投げたリクエストをカセット(cassete)に記録しておいて、その後再度同じリクエストが投げれられた場合はサーバーではなく、記録しておいたカセットからリクエストの結果を取り出すというものです。

これによって、上記のようにサーバーに迷惑がかかることもなくなりますし、テストのスピードも格段に早くなります。

簡単な使い方

とりあえずどんな感じになるのか、上記githubに載っているsampleをrspecバージョンで書き換えて簡単なサンプルを試してみたいと思います。

まず必要なgemがインストールされていない場合はしておきます。

gem install vcr rspec webmock

そして

spec --init

を実行 するとspecファイルが生成されます

create   .rspec
create   spec/spec_helper.rb

続いて、sample_spec.rbを生成します。

touch sample_spec.rb

まずはspec_helperの末尾にVCRの設定を書き込んでいきます。

# spec_helper.rb
VCR.configure do |config|
  config.cassette_library_dir = "vcr/vcr_cassettes"
  config.hook_into :webmock
end

config.cassette_library_dirで記録したリクエストのymlファイルを保存しておくディレクトリを設定して、config.hook_intoでHTTPリクエストをどのようにhookするかを設定しています。

その他の設定できる項目については公式サイトに記載がありますので興味のある方は見てみてください。 Configuration - Vcr - VCR - Relish

続いてsample_spec.rbに実行するテストを書き込んでいきます

# sample_spec.rb
require_relative './spec_helper.rb'
require 'vcr'

RSpec.describe 'sample' do
  it 'sample' do
    VCR.use_cassette("get_google") do
      100.times do
        response = Net::HTTP.get_response(URI('https://www.google.com'))
      end
    end
  end
end

googleに100回getアクセスするというプログラムです。google先生なら100回くらい大丈夫でしょう。

ポイントはVCR.use_cassette(カセット名) do endの部分です。こちらでどのカセットを使うのかを指定しています。

今はまだget_googleという名前のカセットはありませんが、1度このプログラムを実行することで、カセットが作成されます。

では1度目のテストを実行してみましょう。

rspec sample.rb

すると以下のような結果が出力されました。

Finished in 5.16 seconds (files took 0.66782 seconds to load)
1 example, 0 failures

100回getをするのに5.16秒かかりました。

ここでvcr/vcr_cassetes/ディレクトリを見てみるとget_google.ymlができているのがわかります。

中身を見てみると1回目のテストで実行されたリクエストが記録されているのがわかります。

ではもう一度テストを実行してみます

$ rspec sampler.rb

$ Finished in 0.24276 seconds (files took 0.47402 seconds to load)
1 example, 0 failures

!!!早い!!超早い!!!これは実際にリクエストを投げたわけではなくカセットに記録しておいたリクエストの結果を読み込んでいるからです。

VCRすごいですね!問題が見事に解決されました。

こんなところで簡単な使い方はOKかと思います。

よく出るエラーの対策

例えば先ほどのsample_spec.rbをこんな風に書き換えたとします。

# sample_spec.rb

require_relative './spec_helper.rb'
require 'vcr'

RSpec.describe 'sample' do
  it 'sample' do
    VCR.use_cassette("get_google") do
      100.times do
        response = Net::HTTP.get_response(URI('https://www.yahoo.co.jp')) #google→yahooに書き換え
      end
    end
  end
end

で同じテストを実行しようとするとこんなエラーが吐かれます

1) sample sample
     Failure/Error: response = Net::HTTP.get_response(URI('https://www.yahoo.co.jp'))

     VCR::Errors::UnhandledHTTPRequestError:

       ================================================================================
       An HTTP request has been made that VCR does not know how to handle:
         GET https://www.yahoo.co.jp/

       VCR is currently using the following cassette:
         - /Users/user_name/Desktop/GitHub/vcr_sample/spec/fixtures/vcr_cassettes/get_google.yml
         - :record => :once
         - :match_requests_on => [:method, :uri]

       Under the current configuration VCR can not find a suitable HTTP interaction
       to replay and is prevented from recording new requests. There are a few ways
       you can deal with this:

         * If you're surprised VCR is raising this error
           and want insight about how VCR attempted to handle the request,
           you can use the debug_logger configuration option to log more details [1].
         * You can use the :new_episodes record mode to allow VCR to
           record this new request to the existing cassette [2].
         * If you want VCR to ignore this request (and others like it), you can
           set an `ignore_request` callback [3].
         * The current record mode (:once) does not allow new requests to be recorded
           to a previously recorded cassette. You can delete the cassette file and re-run
           your tests to allow the cassette to be recorded with this request [4].
         * The cassette contains 100 HTTP interactions that have not been
           played back. If your request is non-deterministic, you may need to
           change your :match_requests_on cassette option to be more lenient
           or use a custom request matcher to allow it to match [5].

       [1] https://www.relishapp.com/vcr/vcr/v/3-0-0/docs/configuration/debug-logging
       [2] https://www.relishapp.com/vcr/vcr/v/3-0-0/docs/record-modes/new-episodes
       [3] https://www.relishapp.com/vcr/vcr/v/3-0-0/docs/configuration/ignore-request
       [4] https://www.relishapp.com/vcr/vcr/v/3-0-0/docs/record-modes/once
       [5] https://www.relishapp.com/vcr/vcr/v/3-0-0/docs/request-matching
       ================================================================================

ooops

An HTTP request has been made that VCR does not know how to handle:
GET https://www.yahoo.co.jp/

とあるようにそんなリクエストの返し方知らねーよバカって書いてます。

当たり前といえば当たり前で1回目に記録したgoogleに対するアクセス記録を使ってyahooにアクセスしようとしてるわけですから知らなくて当然です。

このことから、デフォルトでは保存されているアクセスはカセットから返して、保存されていないものは新たに記録するといったよしなな感じになっていないことが分かります。

でも結構親切に対策方法を書いてくれているので安心です。1つずつ見ていきましょう

1つ目

If you're surprised VCR is raising this error
and want insight about how VCR attempted to handle the request,
you can use the debug_logger configuration option to log more details [1].

「エラーの原因とかもうちょい詳しく知りたかったらログとか出してみたらいいと思うよ」って言ってます。

ログを取ってみましょう。spec_helper.rbにログファイルを生成する設定を記載します。

# spec_helper.rb
VCR.configure do |config|
  config.cassette_library_dir = "fixtures/vcr_cassettes"
  config.hook_into :webmock # or :fakeweb
  config.debug_logger = File.open("log","w")
end

これでテストを実行すると、logファイルが生成されるので中身を見てみます。

上略...
[Cassette: 'get_google'] Checking if [get https://www.yahoo.co.jp/] matches [get https://www.google.com/] using [:method, :uri]
[Cassette: 'get_google'] method (matched): current request [get https://www.yahoo.co.jp/] vs [get https://www.google.com/]
[Cassette: 'get_google'] uri (did not match): current request [get https://www.yahoo.co.jp/] vs [get https://www.google.com/]
[webmock] Identified request type (unhandled) for [get https://www.yahoo.co.jp/]

ひたすら100回分のgoogleに対するgetが読み込まれたのに(なのでほんとはdo loopの中にuse cassetteを書くべきでしたね。。) 最後にyahooに対するgetが呼ばれてそれは知らないとなってますね。 今回に関して言えば原因がわかっているのであまり有用な情報ではありませんが、原因がわからないときには有効な手法です。

続いて2つ目

You can use the :new_episodes record mode to allow VCR to
record this new request to the existing cassette [2].

「:new_episodesっていうオプションつけたらこの新しいリクエストをexisting cassetteに記録できるよ!」って言ってます。

これやりたかったことっぽいですね。やってみましょう

# sample_spec.rb
VCR.use_cassette("get_google", :record => :new_episodes) do #:record => :new_episodesオプションを追加
    100.times do
        response = Net::HTTP.get_response(URI('https://www.yahoo.co.jp'))
    end
end

これで実行すると先ほどのエラーは出ずに、get_google.ymlの末尾にyahooへのgetが記録されているのがファイルをみると確認できるかと思います。どんどん新しいものを同じカセットに記録していきたいならこの設定にしておけば良さそうです。

続いて

If you want VCR to ignore this request (and others like it), you can
set an `ignore_request` callback

「VCRに無視してほしかったらignore_requrestして無視してちょーだい」って言ってます。

やってみましょう。

まずsample_spec.rbを書き換えます

# sample_spec.rb
require_relative './spec_helper.rb'
require 'vcr'

RSpec.describe 'sample' do
  it 'sample' do
    VCR.use_cassette("get_google") do
      response = Net::HTTP.get_response(URI('https://github.com/')) #githubに書き換え
    end
  end
end

これをこのまま実行すると、上記のunhandledのエラーが出てしまいます。

そこでgithubへのリクエストは無視するという設定をspec_helper.rbに追加します

# spec_helper.rb
VCR.configure do |config|
  config.cassette_library_dir = "fixtures/vcr_cassettes"
  config.hook_into :webmock # or :fakeweb
  config.debug_logger = File.open("log","w")
  config.ignore_request do |request| #config.ignoreを追記
    request.uri == 'https://github.com/' #uriがgithubの場合は無視する
  end
end

これで再度テストを実行するとテストが通って、うまくgithubが無視されているのが分かります。

4つ目

The current record mode (:once) does not allow new requests to be recorded
to a previously recorded cassette. You can delete the cassette file and re-run
your tests to allow the cassette to be recorded with this request [4].

「今のrecord mode(once)だと新しいリクエストを記録できないっす。ファイル消してもう一回実行したらできます。」って言ってます。

ファイルを消してもう一回実行したらできるのは当たり前として、record modeが気になるところですね

こちらに詳しく書いていますが、ざっくりまとめると

  • :once

    • 過去のものを再生できる

    • 同名カセットファイルの記録は1度のみ

    • 同名のカセットファイルがある場合はエラーを吐く(←今回出ているエラーはこれですね)

  • :new_episodes

    • 過去のものを再生できる

    • 同名のカセットファイルがあれば追記する

  • :none

    • 過去のものを再生できる

    • 新たな記録は行わない

  • :all

    • 過去に記録されててもとにかく全部記録する

のような感じになっています。

1つ注意点としてallにすると過去のものを記録して再生できるというvcrの利点が生かされずに全てカセットに記録するということになってしまいます。

なので先ほどやったnew_episodesというオプションはこのrecord_modeを設定していたのです。

最後

The cassette contains 101 HTTP interactions that have not been
played back. If your request is non-deterministic, you may need to
change your :match_requests_on cassette option to be more lenient
or use a custom request matcher to allow it to match [5].

「カセットに101(Switching Protocol)が含まれてると再生できないよー。match_requests_onの条件もっとゆるくするかカスタムのrequest matcher定義してね。」って言ってます

要は記録したリクエストに101が含まれていると、同じリクエストを再生しても違うリクエストと判断されるからうまくリクエストmatcherを設定してくれってことだと思います。

軽く調べてみましたが、match_requests_onの条件をゆるくする方法についてはちょっと良く分かりませんでした。

また、request matchを自前で定義する方法については

describe "#fetch_info", vcr: {match_requests_on: [:method, VCR.request_matchers.uri_without_param(:timestamp)]} do
  # この場合timestampはmacherの条件がから除外される
end

こんな感じで書くといいみたいです。

最後だけちょっと曖昧になってしまいましたが、こんなところで終わりにしたいと思います。

参考:

morizyun.github.io

railsware.com

追記 VCRで記録したcasetteが間違ったレスポンスを返していることがありハマりました。もしリクエストのログとブラウザで実際アクセスしたリクエスト内容が合ってるにも関わらずレスポンスがおかしい時はcassetteの記録がおかしいことを疑ってみてもいいかもしれません。

Mechanizeを使ってページ遷移

Mechanizeを使って簡単なページ遷移を書きました。

googleのトップページから

  1. リンクをクリック
  2. フォームに値を入力してサブミット

を実行したものです。

scraper.rb

main.rb

あとは

$ ruby main.rb

で実行されてコンソールに色々表示されるはずです。 ほとんどこのサイトの翻訳なので、より詳しいところはこちら参考にしてみてください。 http://docs.seattlerb.org/mechanize/GUIDE_rdoc.html

jQueryでよく使う処理をどんどんまとめていく[随時更新]

何度も使う基本的な操作をどんどんここにまとめていく。複数処理にまたがりそうなものはUtilとして作成 ※ coffeescriptで書いてるのでjavascriptの方は随時補完してください。

Utilはこちら

DOM操作

普通のテキストボックスや数字ボックス

チェックボックス

チェックされているかを取得
$("#checkBoxId").is(":checked")
チェックをつける
$("#checkBoxId").prop("checked",true(false))

テーブル

列の削除
$(#rowId).remove()



構文

for each文

$(".someClass").each ->
    $(this). dosomething