Web備忘録

雑多に書いています

Ruby on Railsガイドを通読してまとめる Part.1

なかなかボリューミーなことで有名なRuby on Railsガイド。

railsguides.jp

実務未経験、Rails触り初めて4ヶ月の人間が気になった所だけ抜き出ししてまとめます。 基本的に、「理解できなかった所」や「大事だなと思った所」を中心にしてまとめようと思います。

はじめに

Rails をはじめよう

ネストさせたモデルに値を送る(7.2)

このコードの1行目が初見だったので掲載。

<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

大まかな挙動はわかるけど、体系的にはどう動くのかなって思ってたらスタックオーバーフローで質問がありました。

Ruby on Rails Form For with two parameters - Stack Overflow

ネストさせたモデルに値を送りたいときの書き方のようですね。(実際の挙動見てないので、導入するときは要確認) 結構色々なところで頻出してそうなのに初対面だったのは驚き。@articleはArticle.find(params[:id])等してキャッチしてるのが前提です。

Basic認証(9.1)

検証環境のサーバー等で制限をかけたい場合によく使われる基本的な認証の1つ、BASIC認証Railsでの実装方法は以下の通り。

class ArticlesController < ApplicationController
 
  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
 
  def index
    @articles = Article.all
  end
 
  # (以下省略)

http_basic_authenticate_withメソッドにこのように引数を与えてあげれば、「Articleコントローラーのindexとshowアクション以外でBasic認証必要」という処理になります。

Qiitaに、アプリケーション全体に設定してる方がいました。環境変数でnameとpasswordを決めておいて、ソースコードで見れないようにしてますね。こっちのほうがより安全そうです。

RailsでBasic認証の導入 〜完全版〜 - Qiita

モデル

Active Record の基礎

命名ルール(2.1)

モデル名が単数形、テーブル名が複数形という慣習があるけど、英単語によってはただ「s」をつければ複数形になるわけではないので注意が必要。

モデル名→ テーブル名
Mouse → mice
Person → people

この挙動はActiveSupportによって、Stringが拡張されて#pluralizeメソッドを貰うことで保証しているようです。

[1] pry(main)> require 'active_support'
=> true
[2] pry(main)> require 'active_support/core_ext'
=> true
[3] pry(main)> 'cat'.pluralize
=> "cats"
[4] pry(main)> 'person'.pluralize
=> "people"

(require 'active_support' だけじゃNoMethodError出たので、'active_support/core_ext'もrequireしました。参考記事:Qiita:https://qiita.com/seri_k/items/4818af527bd0e94cc860

スキーマのルール(2.2)

テーブルのカラム名について、created_atなどは予約語なので独自に定めるべきではない。他の予約語として、以下のものがある。

  • 楽観的ロックのための lock_versionカラム。
  • 単一継承テーブルのための typeカラム。
  • ポリモーフィック関連のための「関連付け名_typeカラム」
  • 親が持つ子の数をキャッシュしておくための「テーブル名_countカラム」

これらはRailsルールがあるので、意図せずカラム名にしないよう注意。

命名ルールを上書きする(4)

以下のように記述することで、命名ルールを上書きできる。

class Product < ApplicationRecord
  self.table_name = "PRODUCT"
end
class Product < ApplicationRecord
  self.primary_key = "product_id"
end

Active Record マイグレーション

マイグレーションの概要(1)

マイグレーションがエラーを吐いたさい、全てがロールバックされるとは限らないので注意が必要。

(引用)スキーマ変更のステートメントを利用できるトランザクションがデータベースでサポートされている場合、マイグレーショントランザクションの内側にラップされて実行されます。これらがデータベースでサポートされていない場合は、マイグレーション中に一部が失敗した場合にロールバックされません。その場合は、変更を手動でロールバックする必要があります。

ん、これって…どこかで聞いたような…

ワイや!

スキーマ変更(カラム追加等)のDDL文がエラー吐いたらこうなるのはやはり一部RDBMSでは仕様のようです。 おとなしく手動でロールバックしましょう!

テーブルを作成する(3.1)

テーブルを作るさい:commentオプションというものがあるらしい。初めて見た。 db/schema.rbを直接見るか、DB用のGUIツールを使えば確認できるそうです。 使い方としては、「nameってカラム名だけど何のことだっけ?」みたいなものに、注釈を付けておける、といった効果がある。

案外使いやすそう?

class CreatePatients < ActiveRecord::Migration[5.0]
  def change
    create_table :patients do |t|
      t.string :name, comment: "患者のニックネーム"
      t.string :first_name, comment: "患者の下の名前"
      t.string :last_name, comment: "患者の名字"

      t.timestamps
    end
  end
end

参考記事: [Rails 5] マイグレーション時にデータベースのカラムにコメントを追加する|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

テーブル結合を作成する(3.2)

中間テーブルをサクッと作りたいあなたへ。

create_join_table :products, :categories

productテーブルとcategoryテーブルの中間テーブル(外部キー保証)が出来ます。 インデックスや、独自のテーブル名を設定することも可能。詳細はRailsガイドへどうぞ。

テーブルを変更する(3.3)

完全に知らなかったので全て引用してます。

既存のテーブルを変更するchange_tableは、create_tableとよく似ています。基本的にはcreate_tableと同じ要領で使いますが、ブロックに対してyieldされるオブジェクトではいくつかのテクニックが利用できます。たとえば次のようになります。

change_table :products do |t|
  t.remove :description, :name
  t.string :part_number
  t.index :part_number
  t.rename :upccode, :upc_code
end

上のマイグレーションではdescriptionとnameカラムが削除され、stringカラムであるpart_numberが作成されてインデックスがそこに追加されます。そして最後にupccodeカラムをリネームしています。

ただ、ここまで一気にテーブル構造変えるのは保守性落ちそうですね。 作り直しがどうしても出来ない場合の策か。

データベースをリセットする(4.3)

bin/rails db:resetは一度テーブルをドロップしたあと、db/schema.rbに従って再びテーブルを作成しているだけで、マイグレーションをもう一度実行しているわけではないらしい。知らなかった。(db/schema.rbをスキーマダンプというらしい。後で説明がある)

既存のマイグレーションを変更する(5)

テーブルのカラム名などを変更したいときに、既存のマイグレーションファイルに手を加えて再実行するのはNG。コミットしてマージされてしまったマイグレーションファイルに対する変更をしてしまうとチームメンバーに影響がある。

新しいマイグレーションファイルを作成して対応しましょう。

スキーマダンプの種類(6.2)

db/schema.rbとして保存されているものを「スキーマダンプ」と呼ぶ。これは現在のDBの状態をダンプ(出力)したもの。 設定次第でdb/structure.sqlというsql文で書かれたスキーマダンプを手に入れることも出来る。(DBの高度な機能を使うためにはそうした方がいい。)

マイグレーションファイルとスキーマダンプとの関係を表す参考記事置いてあったので、貼っておきます。 schema.rbとmigrationファイルの関係をまとめてみたよ - Qiita

ActiveRecordバリデーション

バリデーション(1.1)

データをデータベースに保存する前に行えるバリデーションの種類は大別すると4つである。

  1. クライアントサイド(JS等)でのバリデーション

    • バリデーションとしての信頼性は低いが、ユーザーへの即座なフィードバックが可能である。モダンなアプリとかだったら、「そのパスワードの強度は低いですよ」みたいなのをリアクティブに教えてくれるものが多い気がする。もちろん、JSでの実装。
  2. コントローラーレベルでのバリデーション

    • 非推奨。テストと保守が困難になるため、避けたほうがいい。
  3. モデルレベル

    • 最も重要。さまざまなバリデーションが元々アクティブレコードに用意されている。また、自分でカスタムしたものを使うことも可能。テスト・メンテナンスも容易。バリデーションを行いたいときの基本である。
  4. データベースレベル

    • テストや保守を行うのは難しくなるが、Rails以外のアプリケーションからそのDBにアクセスするようなことがあるのなら、DB自体にバリデーションを付与しておくのは良い方法である。また、ユニークネス制約など、他の層のバリデーションでは実装が難しく、かつ使用頻度が高いものもあるので、覚えておこう。

モデル層での色々なバリデーション

acceptance(2.1)

class User < ActiveRecord::Base
  validates_acceptance_of :terms_of_service
end

> user = User.new(terms_of_service: true)
> user.valid?
#=> true

> user.terms_of_service = '1'
> user.valid?
#=> true

validates_acceptance_ofバリデーションは、利用規約同意のチェックボックス等に使える。 チェックボックスがチェックされて、その値が「true」か「"1"」なら通る(試してないけどInteger型の「1」でも通りそう) DBに存在していないカラムについても弾ける。「利用規約に同意しているかどうかカラム」なんて作ったところで、全部「true」に決まってるので、モデル層で弾く設計にした方が良いということでこのバリデーションは良さげですね。

参考記事: Rails 5 accepts 1 or true for acceptance validation | BigBinary Blog

confirmation(2.3)

パスワード確認用に使えるバリデーション。

class Person < ApplicationRecord
  validates :email, confirmation: true
  validates :email_confirmation, presence: true  #2行目のバリデーションを働かせるためnil不許可
end

これでビューテンプレートに「email」と「email_confirmation」に関するフィールドを作ればOK。同じ値であることを保証する。

format(2.5)

withオプションで与えられた正規表現とマッチするものだけを通す。

class Product < ApplicationRecord
  validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
    message: "英文字のみが使えます" }
end

メールアドレスのバリデーションでよく出てくる気がする。

uniquness(2.11)

モデル側で定める一意性制約。どうやって挙動を保証してるのかな〜と思ったらSQLクエリを発行しているようですね。まあ、DB見ないとユニークネスかどうかわからないのは当たり前か。(もしかして何か裏技あるのかと思ってた)

validates_with(2.12)

カスタムバリデーションを使う際の記載。

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if options[:fields].any?{|field| record.send(field) == "Evil" }
      record.errors[:base] << "これは悪人だ"
    end
  end
end
 
class Person < ApplicationRecord
  validates_with GoodnessValidator, fields: [:first_name, :last_name]
end

ちょっと複雑なコード。 言葉にしてみると、「Personモデルのインスタンスが保存されるさい、GoodnessValidatorにバリデーション処理が移譲される。移譲先ではvalidateメソッドが打たれることが保証され、引数としてインスタンス自身(record)が渡される。」ということ。

 if options[:fields].any?{|field| record.send(field) == "Evil" } 

この部分は、「もしオプションにfiledsという項目があったなら、そのfieldsという項目の数だけ、後ろのブロック{ record.send(field) =="Evil" }を評価し、その戻り値が結果とせよ。」という感じかな。record.send(field)は、recordにfield(:first_nameとか)のメソッドを打てということ。

ここ解説少なかったので掘り下げました。

(補足)

sendの挙動

[1] pry(main)> str = "str"
=> "str"
[2] pry(main)> str.send(:upcase)
=> "STR"

validates_each(2.13)

カスタムバリデーションを使うほどではないけど、既存のバリデーションじゃ不十分なときに使う。

class Person < ApplicationRecord
  validates_each :name, :surname do |record, attr, value|
    record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
  end
end

属性ごとに後ろのブロックを評価。ブロック引数として、「インスタンス自身(record)」、「インスタンスの属性名(attr)」、「属性の値(value)」が固定で渡される。順番大事。

:ifや:unlessでバリデージョンの前に呼び出すメソッドを指定

一番良く使うオプションらしい。

class Order < ApplicationRecord
  validates :card_number, presence: true, if: :paid_with_card?
 
  def paid_with_card?
    payment_type == "card"
  end
end

支払い方法がカードのときのみカードナンバーを入力させたいときは上のような書き方。 if:にProcオブジェクトを渡すことも可能。そうすると、そのオブジェクトの処理はバリデーションの前に呼び出されて評価される(ブロックの評価値はtrueかfalseにしておく。)

カスタムバリデータ(6.1)

個別の要素を検証したいときは、カスタムバリデータに「ActiveModel::EachValidator」を継承させるのが一番手っ取り早い。

(2.12で示したように、「ActiveModel::Validator」を継承させたあと、処理を委譲させてもよい。)

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "はメールアドレスではありません")
    end
  end
end
 
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end

呼び出す側のモデルで「validates :email, email:true」とするだけで、簡単にカスタムバリデータさんを呼び出せる。

バリデーションエラーについて

valid?だったりsaveを打ったオブジェクトはActiveModel::Errorsクラスのインスタンスを一つ手に入れる。それはrecord.errorsで参照できる。

record.errorsに打てるメソッドとしては、[:attribute]、add、size、full_messages等…

詳しくはAPIガイドへ!

ActiveModel::Errors

感想

思ってたより読みやすい…すげえよ公式ガイド…。

「どういう挙動をするか」というコード例が助かります。

あと解説も想像してたよりは丁寧でありがたい(とはいえ、Rails学んだばかりの人だと難しいと思う。)

基礎部分が15記事あって、今回4つまとめたからPart.4までは続けようと思います。

Rails勉強してる方は、一度読んでみて「理解できるな」と思ったら読んでみると良いと思います!(ただ、作りながら覚える形式じゃないため、挙動を想像できない場合は非推奨かも。)

ゼロからVue.jsを学んでいる(用語整理)

以下の2冊で勉強しています。

Vue.js入門 基礎から実践アプリケーション開発まで

Vue.js入門 基礎から実践アプリケーション開発まで

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

Vue.js、学習コストが低いという噂を聞きつけてノコノコとやってきたんですが。

別に簡単ではない!!

未知の概念の連続っ…!! 

しかし、漠然とフロントエンドエンジニアという分野に持っていた誤った考え、つまり「フロントエンジニアとは、マークアップがちゃんとしているオシャレなサイトを作る分野だ」という誤解が氷解していき、一気にフロントにも興味が持て始めてきました。

見通しのよい設計でコードを書きつつ、レンダリングのパフォーマンス等を加味しながらUI/UXの向上を図る…みたいな感じやろ!?知らんけど…。

自分自身のキャリアとして完全なフルスタックを目指すつもりはないですが、チームに参画したときにパフォーマンスを出せる人間になりたい(「 出来るFWは他のポジションの役割も理解している」ように。)っていうのと、個人としての開発力を上げたいっていうのがフロントを学んでいる動機です。

とりあえず用語だけまとめながら本を読んでコード書こうと思います。

用語まとめ

リアクティブデータとは?

Vue.jsのgetまたはsetにフックして反応できるデータのこと。

ディクレティブとは?

Vue.jsの場合だとv-bindなどがそれにあたるが、コンパイルしてテンプレートを作るさいの補足情報のこと。 ディレクティブは指令という意味。Vue.jsへの指令。

v-showとv-ifの違い

v-showはCSSのdisplayプロパティを切り替えているだけで、DOM要素自体は残っている。 v-ifはDOM要素自体の削除。そのため描画コストがかかる。

this.$setの使い所

名前を持たない配列の値に直接オブジェクトを置きたい場合、またはそのオブジェクトのプロパティに新しいものを追加する場合に使う。

this.$set(更新する対象, インデックス番号 or 新しいプロパティ名, 新たなオブジェクト or 新たな値) 

イベントハンドラとは?

Vue.jsの場合だと、v-on:で紐付けるイベント対象によって実行される処理のこと。例: v-on:click="eventHandler"

算出プロパティとは?

computedで定義しておく関数。先に定義しておくと「this.definedProperty(定義しておいたプロパティ名)」で呼べるから便利。 メソッドと違い一度処理した値をキャッシュしておくため、呼び出すたびに処理が走ることはない。 (依存するdataの値が書き換わると再処理) 基本的にはdataの値を貰っておくゲッタープロパティだが、セッターとしての処理を書けばdataの値を書き換えることも出来る。

ウォッチャーとは?

特定のデータまたは算出プロパティを監視しておき、その変化にトリガーして実行される処理のこと。 監視しながらDOM操作したいならカスタムディレクティブの方がよい。

カスタムディレクティブとは?

v-bindのようなディレクティブを自作するための機能。direvtivesで定義する。ただし、カスタムディレクティブで受け取った値は$elや$refsと同じで、仮想DOMではないため、描写の最適化は行われない。 要素や値を監視しながらDOM操作したいときに使おう。

$el、$refsとは?

$elがidと結びついているDOM要素すべて。 $refsは$elが結びついてるDOM要素の中で、ref="foo"といった要素について$refs.fooで結びつけるために使う。

nextTickとは?

監視先のDOM要素について、データがバインディグされて処理されたあとのものをとってこれる。

<div id="app">
  <button v-on:click="list.push(list.length+1)">追加</button>
  <ul ref="list">
    <li v-for="item in list">{{ item }}</li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    list: []
  },
  watch: {
    list: function () {
      // 更新後のul要素の高さを取得できない…
      console.log('通常:', this.$refs.list.offsetHeight)
      // nextTickを使えばできる!
      this.$nextTick(function () {
        console.log('nextTick:', this.$refs.list.offsetHeight)
      })
    }
  }
})

コンポーネントとは?

JSとテンプレートをセットにしたもの。コンポーネントを使えばコードの再利用性が高い。 コンポーネントはそれぞれスコープを持っている。コンポーネントコンポーネントを呼び出すことも可能(親子関係になる)

コンポーネント間の通信方法

親から子へ渡す

子どもは親の属性(val='hoge')をprops: ['val']といったように指定すれば、そのhogeを扱うことができる 親のデータを渡したいときは親側にv-bind:val="message"といったふうに記述し、子側でprops:['val']と指定すれば、親側のdataに入っているmessageの値を貰えるといった動きになる。

子から親へ渡す

カスタムイベントと、$emitを使う。 親側でv-on:custom-event='eventHandler'とテンプレートにあった場合、子のコンポーネント側はthis.$emit('custom-event')で発火しにいける。(コンポーネント側でthis.$emitを呼ぶためにメソッドなど必要)。これでthis.$emit('custom-event')で親側のメソッドeventHandlerを実行させることが出来る。 emitは発火させるって意味。

親子関係でないコンポーネント間はイベントバスを使う

イベントバスは通信用のためだけに作られるVueインスタンス。そいつをフックにコンポーネント間をやりとりする。 状態管理がややこしくなるので非推奨っぽい。

スロットとは?

親となるコンポーネント側から子ども側のコンポーネントのテンプレートの一部を差し込む機能のこと。

<div class="comp-child">
   <slot>ここに親側のテンプレートが入る</slot>
</div>

スコープ付きスロット

スロットは定義した側(親側)のスコープが優先されるが、slot-scopeを使えば子側でのスコープでデータを受け取れる。

例:

子側のテンプレート

<ul class="comp-child">
  <slot v-for="item in list"  v-bind:item="item"></slot>
</ul>

親側のテンプレート

<comp-child>
  <li slot-scope="props">{{ props.item }}</li>
</comp-child>

propsという変数名を指定しているが、これは任意でいい。子側のコンポーネントのスコープを表す。

トランジションとは?

Vue.jsにおいては、CSSトランジション/アニメーション効果をより簡単に使いやすくサポートする機能のこと。

<div id="app">
  <p><button v-on:click="show=!show">切り替え</button></p>
  <transition>
    <div v-show="show">
      トランジションさせたい要素
    </div>
  </transition>
</div>
/* 1秒かけて透明度を遷移 */
.v-enter-active, .v-leave-active {
  transition: opacity 1s;
}
/* 見えなくなるときの透明度 */
.v-enter, .v-leave-to {
  opacity: 0;
}

webpackとは?

モジュール化した複数のファイルをまとめるバンドルツールのこと。

まだ多分続きますが

一旦、ここで終わろうと思います。 覚えることがたくさんありますねえ。

作りたいアプリの開発環境としては、RailsAPI+Vue.js(+もしいければ開発中のLINE用APIアプリケーション)って感じなので、もう少しVue.js触ってからRails動かしていこうかな、と思います。

がんばります!

「EFFECTIVE TESTING WITH RSPEC 3: BUILD RUBY APPS WITH CONFIDENCE」の1章を読んだ

あとから読みなおせるようにブログにまとめつつ読んでいます。

Effective Testing with RSpec 3: Build Ruby Apps with Confidence

Effective Testing with RSpec 3: Build Ruby Apps with Confidence

EFFECTIVE TESTING WITH RSPEC 3: BUILD RUBY APPS WITH CONFIDENCE 

は、直訳すると「RSpec3による効率的なテスト: 自信をもってRubyのアプリケーションを組み立てる」みたいな感じでしょうか。

英語版で読む理由

  1. 翻訳されてない
  2. 英語学習のため
  3. 英語で体系的な学習をする練習をするため

注意点

自分の解釈だったり誤訳も含んで日本語にしています。

序文(Foreword)

ソフトウェア開発は覚えることがたくさんあって大変だ。テストを書くこともそのうちの一つかもしれない。

しかし、ソフトウェア開発にテストを書くことは、欠かせない。

そして(Railsで)よいテストコードを書くためにRSpecは欠かせない。

RSpecは単なるテストフレームワークでなく、批判的に、根気よく、体系的に自分のコードの設計について考える方法と、体系的な手法でソフトウェアを開発する方法を学ぶためのツールでもある。

はじめに(Introduction)

「俺らのテストがまた落ちたよ!」

「なんでこの一連のコードが通るまでにこんなに時間がかかるんだ?」

「こんなテストになんの価値があるんだろう?」

テクノロジーは変わったけどテストへの不満の内容は変わらない。

この本では「効果的な(EFFECTIVE)」なテストの書き方を教えるよ。

効果的っていうのは、「わざわざ時間を割いて書く意味がある」ってことだ。

1章 RSpecをはじめよう
(Chp1 Getting Started With RSpec)

RSpecは生産的なテストフレームワークだ。スタイル、API、ライブラリ、設定、それら全てが最高のソフトウェア開発を手助けしてくれる。

いいテストから得られる効果は少なくともこれだけある。

最初のSpec

サンドウィッチを例にあげて考える。

RSpecはdescribeとitによって、まるで会話文のように表現することができる。

"Describe an ideal sandwich" (理想的なサンドウィッチについて説明してくれ)

"First, it is delicious" (はじめに、それはおいしい)

RSpecではこう書く。

RSpec.describe 'An ideal sandwich' do
  it 'is delicious' do
  end
end

テストケースを加えたものが以下だ。

RSpec.describe 'An ideal sandwich' do
  it 'is delicious' do
    sandwich = Sandwich.new('delicious', [])

    taste = sandwich.taste

    expect(taste).to eq('delicious')
  end
end

specはspecification(仕様書)の短縮形だ。

RSpec.describe ブロックは「例の集合*1」をつくる。

例の集合が何をテストしているか明らかにして、(今回だとサンドウィッチ)、関連するspecsを一つにまとめるんだ。

ネストされたブロック 'is delicious' はサンドウィッチの使い方の例だ。(他のフレームワークだとテストケースって呼ぶかもしれない)

コラム: テスト対スペック対イグザンプル (Tests vs. Specs vs. Examples)

テストはコードが正確に動くかvalidate(検証)する

スペックはコードに振る舞ってほしい動きをdescribe(説明)する

イグザンプルは特定のAPIがどのように使われるかを明らかにする

さて、ここでおさらいしておこう。

  • RSpec.describe が例の集合をつくる
  • it が例単体をつくる
  • expect が期待される結果をあらわす

この3つがRSpecの核だ。どんなに長い道のりも、この3つを使えば前には進むことが出来る。

また、サンドウィッチspecから得られる目的は2つ。

  • サンドウイッチに振る舞ってほしい挙動のドキュメント化
  • サンドウィッチがあるべき姿かどうかのチェック

サンドウィッチspecのよいところは、「あたらしくプロジェクトに参画した人」のドキュメントになるってことだ。 specを読めば、サンドウィッチはおいしくなきゃならないってのがすぐにわかる。

セットアップの共有(Sharing Setup)

  • RSpecフックはテストの間、自動的に特定の回数実行される。
  • ヘルパーメソッドはRubyの標準メソッドで、いつ実行されるかコントロールできる。
  • letは必要に応じてデータを初期化する。

一つずつ順に見ていこう。

RSpecフック(before)

beforeは、itごとに準備されるRSpecフックだ。

Sandwich = Struct.new(:taste, :toppings)
RSpec.describe 'An ideal sandwich' do

  before { @sandwich = Sandwich.new('delicious', [])}

  it 'is delicious' do
    taste = @sandwich.taste

    expect(taste).to eq('delicious')
  end

  it 'let me add toppings' do
    @sandwich.toppings << 'cheese'
    toppings = @sandwich.toppings

    expect(toppings).not_to be_empty
  end

end

ここでは、@sandwichを使ったけど、インスタンス変数には実は欠点もある。

  1. もし@sandwichをミススペルして参照しても、Rubyは暗黙的にnilを返す。このことが、エラーの原因が単なるタイポであることをエラー文から読み取りづらくする。
  2. もしsandwichという変数を、@sandwichというインスタンス変数に置き換えたい場合は、ファイル全体で置き換える必要がある。
  3. beforeフックでインスタンス変数を使うと、必要ないときも@sandwichを生み出し、セットアップのためにコストがかかる。これは大規模プロジェクトではときに見逃せない不効率性になる。

別のアプローチを試してみよう。

ヘルパーメソッド

RSpecは便利だから、ときにそれがRuby支配下であることを忘れるけど、メソッドを定義することだって可能なんだ。

def sandwich
  @sandwich ||= Sandwich.new('delicious', [])
end

このようなテクニックはメモ化(memoization)*2と呼ぶ。

この手法は便利だけど、落とし穴がないわけじゃない。||=は@sandwichがnilかfalseのときに働くから、@sandwichにnilかfalseを格納することはできなくなる。

こういった問題を解決する手段として、RSpecにはletが用意されている。

letでオブジェクトを共有する(Sharing Objects with let)

さきほどのsandwichメソッドを以下のコードに置き換えてみよう。

let(:sandwich) { Sandwich.new('delicious', [])}

letは「sandwichという名前と、ブロック{}を結びつけるもの」と考えてもいい。 このletによって、ちょうど先程のメモ化したヘルパーメソッドのように、それぞれのイグザンプルはsandwichが呼ばれた「最初のとき」だけブロックを評価するんだ。

コード共有テクニックを使うさいの注意点

これらのテクニックは、保守性を向上させ、ノイズを減らし、コードを明瞭にするために使おう。

あなたの番(Your Turn)

エクササイズ

  1. RSpecにおけるコードの重複を防ぐための3つの主要な手法(フック、ヘルパーメソッド、let)を紹介したけど、今回のケースだとどれが一番よいと思う? そしてそれはなぜだろう? そして、あなたが選択しなかった手法が、他の状況では望ましくなるのはどんなときだろう?

  2. rspec --help を実行し、いくつかsandwichイグザンプルで試してみてほしい。

さて、RSpecのお気に入りの使い方と出会う準備は出来てる? 

サンドウィッチ休憩をとったら、次のチャプターで会おう。

エクササイズと自分の感想

エクササイズ1をやってみた

1はbeforeでよさそうだな〜。@sandwichの各属性に対するテストだからitごとにインスタンス変数を準備しておくことに違和感を感じない。

複数のインスタンスを複合的に使ったりする場面ではletのがいいのかも?

エクササイズ2をやってみた

rspec -e STRINGを実行した。 STRINGに指定した文字列に対して、itの後ろの文字列( 'is delicious'等)がマッチしたものだけテストが走る。 ヘルプに「何回も使うかもね」って書いてた。

感想

読んで訳してブログにするのに4時間ぐらいかかったーー。 作業自体は(疲れるけど)楽しいから、ちょっと頭の体操したいときの息抜きかなあ。 そして、著者がRSpec大好きなことが伝わってきて読んでて面白い。

頑張って2章もやりたい…!!

*1:example group

*2:Wikipedia: メモ化(英: Memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。

「メタプログラミングRuby」を読んだ感想

メタプログラミングRuby」を読みました。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

物語形式で進む技術書で、主な登場人物は二人。ボブとビルです。

ボブは新入社員で、Rubyを触り始めて数週間(の割にはすごい理解力ある)。その上司である「経験豊かなビル」が、実務上の課題を解決する上でのメタプログラミングの使用法をボブに教える、という形式で進んでいきます。

メタプログラミングというタイトルが「なんかやばそう」なオーラを出してますが、内容としては思っていたよりは理解が追いついてちゃんと読めて、凄く面白かったです。

いままで、「Ruby超入門」、「たのしいRuby 5版」、「プロを目指す人のRuby入門」とやってきて、4冊目としてとてもいいレベル感のものに手を出せたなーと思ってます。我ながら素晴らしい

理解が難しい所もありましたが、Rubyについて実践的な知識が得られる本じゃないですかね?(といっても本当に実践的かどうかはまだ実務してないのでわからないですが)

学んだこと(覚えてる範囲で)

  • クラスとモジュールの継承関係
  • 動的メソッド・ゴーストメソッド
  • クロージャ(ブロック)を利用した動的処理の受け渡し
  • スコープとself
  • 特異クラス
  • フックメソッド
  • ActiveRecord::Baseの構成要素

印象に残った概念とかコードとか

うーむ、10日ぐらい前に読んだので、すぐにぱぱっと思い出せない。 プログラミングあるあるだと思うんだけど、挙動ベースで覚えるから、こう、言葉にできない。言葉にする力、ダイジダネ。

下の概念は、この本で学んだ、個人でRuby書いてく上でも瞬間的に役に立ってるって思ってる部分です。

  • メソッドの探索経路
  • Object#method_missingをオーバーライド
  • スコープとself

メソッドの探索経路

これでRubyのメソッド探索の理解度がぐんと上がりました。

(自分でまとめた図。字が汚いのは原著リスペクトです。本気で書いてこんなもんってわけじゃないよ!!(大声))

f:id:fujiten3:20190323144243j:plain

改めて読むと読み解き辛くて笑える。 過去の自分の呟きに読み方が書いてました。

お、おう。

うん、つまりたぶんこういうことだと思う。まずあるクラスがあるとする。そして、そのクラスのスコープ範囲内で定義されたメソッドは、そのクラスに生成されたインスタンス達がそのメソッドが持ってるかのような挙動(インスタンスメソッド的)であるが、この探索経路を踏まえて考えると、あくまでもそのメソッドを持っているのは定義されたクラスで、インスタンスは「メソッドの探索経路の中で、親クラスで定義されているメソッドを捕まえるだけである」と考えてもいいということだ。

……まあ、このへんは実際に本買ったほうがわかりやすいですわ…。

Object#method_missingをオーバーライドする

Object#method_missingは例外NomethodErrorを返すメソッドです。 そいつをオーバーライドすることでそのクラスのインスタンス達はあらゆるメソッドに反応できる力を得ます。

class Dog 

  def method_missing(method, *args)
    puts "呼び出したメソッド: #{method}(#{args.join(',')}) "
    puts "(ブロックも渡したね)" if block_given?
  end

end
dog = Dog.new
dog.wow!
=> 呼び出したメソッド : wow!()
dog.wowow!{ "BowWow!!" }
=> 呼び出したメソッド : wowow!()
(ブロックも渡したね)

これでNomethodErrorからおさらば! このような「コード上に存在しないメソッド」のことをゴーストメソッドというそうです。 今回の場合だと、Dog#wow!や、Dog#wowow!がゴーストメソッド。

(ただし危険性も高いので、出来る限り「動的メソッド」で対応した方が望ましいとのことです。詳しくは原著へ。)

深い

結論から言うと、Ruby、ありえん深みがある。(というかどのプログラミング言語も深みがありそう)

また追記するかもしれないですが、今日のところは以上です。

Ruby、もうちょっと深く知りたいな」って方はぜひ読んでみて下さい!

おわり。

「abceed」という英語勉強アプリがとてもよい

2ヶ月ぐらい前にインストールしたんですが、とてもよいアプリなんです、これが。

TOEICに特化したアプリですが、普通のリスニング教材としても優秀だと思ってます。

ちなみにアフィリエイトとかではないです。ただの紹介です。 あ、課金要素はあります。

なにがよいか

  • レベル感がちょうどいい
  • スキマ時間に簡単に問題が解ける
  • 解説が充実してる(スクリプト完備)
  • UI/UXがよい

といった感じです。

一つ一つ解説していきます。


レベル感がちょうどいい

自分はたぶん今TOEIC750点ぐらいの実力だと思うんですが、(家での模試で800点だった) 英語の勉強で苦労してるのが、Podcastとかでネイティブの会話聞いてても、 「聞き取れないレベルの早さと単語のやり取り」が頻発してくることなんですね。

TEDの動画で以前見た語学力向上法として、「『ギリギリ聞き取れる&理解できるレベルの会話』を聞きまくって、その上で意味を咀嚼しようと努力することが大事」ってのがあったんですが、正直、今の僕のレベルではネイティブが普通に会話してる英語は、じゃっかんハードです。というかスーパーハードです。 そんな僕のレベルでも大丈夫な感じに仕上がってます。このアプリは。

アプリの概要としては、中にいろんな電子書籍が入ってて、買い切り型(またはプレミアム会員型)で、その本の問題を気軽に解けるっていう形式なので、自分のレベルに応じた電子書籍を買えますしね。


スキマ時間に簡単に問題が解ける

一番はこれですかね。 電車でささっと解けるんですよ。 画面みても分かりづらいかもしれませんが、リスニングの問題とかを下みたいな画面でささっと解けます。

f:id:fujiten3:20190313181941p:plain

問題形式で出題してくれるメリットは「なんとなく聞いている」状態では正解率が下がるということ。 正解しにいく姿勢(≒聞き取ろうとする姿勢)が養われます。 ていうか、アプリで問題解けるって本当にいい時代ですよね〜。 自分が受験生の頃は単語帳でしたからねー。(老害アピ)


解説が充実してる(スクリプト完備)

これも偉いポイント。

f:id:fujiten3:20190313182124j:plain

充実の解説力。 重要単語を下にまとめてくれているのもいいですね!

あと、フルスクリプトなので、会話を単語のニュアンスで理解しようとせず、「一言一句完璧に聞きに行く」という練習ができます。 これ大事だと思ってます。英語は冠詞一つでニュアンスが変わる繊細な言語なので、完璧に聞きにいく訓練は絶対必要だと思ってます。 有識者の方々、そうですよね? このへんは諸説かもしれないですが、僕はそう信じてるので「一言一句完璧に聞く練習」が出来るのはアドです。

UI/UXがよい

2つ画面挙げたのでわかっていただけるでしょうが、問題を解くまでのハードルが低いです。

「電車でいそいそと単語帳取り出し→読む」等に比べて、「スマホのアプリですぐ問題が解ける」ということがでかいです。 問題も1単語レベルから2分ぐらいの会話文まであるので、そのときの気分に応じて選べます。 (リーディングの問題もありますが、僕はあんまそっちはやってません)

僕はめちゃくちゃ面倒くさがりで、基本財布をカバンから出すのも面倒だと思ってるタイプです。IC決済大好きです。 電車で本を出すのは大変なので、スマホでサクッと問題が解けるっていうのはもはや革命です。

結論

abceedはいいぞ! 僕はプレミアム会員的なのに入ってますが、電子書籍買い切り型にしといた方が得だったかもなって今は思ってます。 というのが、プレミアム会員はだいたい月1500円かかるんですが、これ1月に1冊終わらせるぐらいのペースじゃないと元とれないです。 なので、中で電子書籍買ってアプリ形式で解くのが最強かもしれない。

まあ、Podcastと併用するのが一番ですね。 Podcastは単語力とか上げに行きづらいのと、その会話の「答え(意図)」が何だったのかっていう答え合わせの時間がないですから、その点を補ってくれるアプリです。

おわり。