20200326のRubyに関する記事は16件です。

GitHub PagesとJekyllを使ってポートフォリオページを作ろう②Jekyll構築編

前回
GitHub PagesとJekyllを使ってポートフォリオページを作ろう①GitHub Pages構築編

今回はJekyllの環境構築編です。

参考にしている良記事様
- GitHubPages + Jekyll 無料でWebページを作ってみた
- jekyllインストール

Jekyllって?

Webサイトを構成するHTMLファイル一式を出力するツールです。
特徴としては
・Rubyベースで動作する
・テーマを簡単に取得できる
MarkDownで書ける
GitHubPagesが公式サポート
といった感じです。MarkDownで書いたものをHTML出力してくれるみたいです。便利ですね。

Jekylの環境構築

①まずはRuby

JekylはRubyベースなのでまずはRubyをインストールします。これについては良い記事がたくさんあるので割愛させていただきます。
こちらの記事を参考にしてください。(投げやり)
homebrewrbenvrubyの順番でインストールする感じです。
https://qiita.com/Ficus/items/bdef5c2b504d7a4008fb

②Jekyllをインストール

ruby -vあるいはrbenv versionsでrubyの生存確認とバージョンをチェックしておきましょう。
確認ができたらgem install bundler jekyllでJekyllをインストールします。

※gemとはライブラリのことです。gemをインストール→ライブラリをインストールです。pip的なやつ。
※Bundlerはgem管理のライブラリです。詳細はこちら

Jekyllの生存確認もしておきましょう。
jekyll -v

Jekyll 使ってみよう

①Jekyllのプロジェクト作成

jekyll new [プロジェクト名]でプロジェクトを作成できます。
GithubPagesのリポジトリ名に合わせると良いでしょう。(***.github.io)
カレントディレクトリにフォルダが作成されるので、あらかじめJekyll用のディレクトリを作ることをお勧めします。

②Jekyllスタート

作成したプロジェクトディレクトリに移動し
bundle exec jekyll serveでJekyllを起動します。
起動したらhttp://127.0.0.1:4000/にアクセスします。
Welcome to Jekyll!と表示されたページにアクセスできれば成功です。

構築完了

これでJekyll構築は終了です。次回以降、実際にJekyllを使用してページを編集してみようと思います。
ありがとうございました。

前回→GitHub PagesとJekyllを使ってポートフォリオページを作ろう①GitHub Pages構築編
次回→

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails開発におけるRSpecの使用方法と基礎的な理解 〜その1〜

はじめに

当記事は以下のような方の参考になるかと思います。

  • Ruby on Rails初学者
  • テストについて学びたい方
  • テストについて一次情報を確認したが、あまり理解できなかった方
  • RSpecの基礎について学びたい方
  • RSpecを簡潔に書きたい方

執筆の理由

私はプログラミングの学習にスクールを利用しましたが、テストは他の項目と比較して理解が進んでいない方が多いような印象でした。(私を含め)
その為、自身の理解が進んだ際には、記事を投稿する事でアウトプットでより理解を深めると共に、初学者の方々の理解の助けになれればと考えていました。
これが記事投稿の理由です。

ちなみに、自身がテストについて理解が進んでいなかった理由としては、以下の3点が挙げられます。

  • テストを軽視していた。
  • カリキュラム全体を確認した結果、進捗に直接の影響を与えないと考えた。
  • その性質上、DRY(Don't Repeat Yourself「何度も同じ事を記述せず、効率的にコーディングする」の意)にすることがやや制限される為、面倒に感じた。

数ヶ月前の理解が進んでいない状態の私自身に向けて、用語解説も含めて書いていきますので、おそらく大半の方が基礎中の基礎を理解出来るようになるかと思います。

開発環境と前提

Ruby : 2.5.1
Rails : 5.2.3

なお、今回はユーザー登録機能のあるアプリケーションを想定し、Userモデルに対してのテストを行っていきます。
gem 'devise' を使用しています。

手順

大きく分けて以下の4手順です。
1.gemのインストール
2.各ファイルの作成
3.テストコードの記載
4.テストを行う

gem 'rspec-rails' のインストール 〜 ターミナル操作

gem 'rspec-rails' のインストール

まずはRails内でRSpecを使用する為に、gem 'rspec-rails' をインストールします。
Gemfileに記載する際は場所に注意してください。
テスト用のgemなので、group :development, test do 〜 end内に記載します。

Gemfile
#省略

#記載する箇所に注意
group :development, :test do
  gem 'rspec-rails'
end

#省略

上記のように記載できたら、
ターミナル(著者はmacを使用しています。windowsでしたらコマンドプロンプトでしょうか?)操作を行います。

ターミナル操作

「bundle install」を実行。

ターミナル
$ bundle install

インストールできたら、
「rails g rspec:install」を実行してください。

ターミナル
$ rails g rspec:install

上記のコマンドで、rspec関連のファイルが自動作成されます。
上手くいけば、おそらく以下のような結果がターミナルに表示されるかと思います。

ターミナル
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

これで 'rspec-rails'の導入は完了しました。

テストコードを書くファイルの作成

テストコードは先ほど自動作成された「spec」内に新たにディレクトリ(≒フォルダ)、ファイルを作成し、そこに記載していきます。
今回は、Userモデルに記載のバリデーションをテストする為、spec直下に「models」というディレクトリを作成し、さらにその直下にuser_spec.rbというファイルを作成します。
(他にもモデルテストを行いたい場合は、modelsディレクトリ直下に「モデル名_spec.rb」というファイルを用意します。以下参照)

spec/models/user_spec.rb
spec/models/モデル名_spec.rb
#例です。今回こちらのファイルは使用しません。

今回テストするバリデーションの確認

前提としてdeviseを導入済みなので、わざわざ記載しなくてもバリデーションがかかっている箇所もありますが、今回は理解の助けになるようあえて記載します。
自身のアプリの使用上「name」が「nickname」になっていたりしますが、特に結果に影響を与えないので、気にしないで下さい。

app/models/user.rb
class User < ApplicationRecord
#省略

  validates :nickname, presence: true, length: { minimum: 4 }

#省略
end

今回は、
nickname

  • presence: true・・・空欄は認めない
  • length: { minimum: 4 }・・・最小文字数4文字

以上のバリデーションをかけてみます。

テストコードの記載

例として、先程spec配下に作成したファイルに'nicknameが空欄の場合登録できない'テストコードを書いてみます。

spec/models/user_spec.rb
require 'rails_helper'      #require 〜  ・・・rubyのメソッド。外部ファイルの読み込みを行う。

describe User do
  describe '#create' do
     it 'nickname空欄の場合登録できない' do
       user = User.new(
         nickname: "", 
         email: "aaa@gmail.com", 
         password: "000000", 
         password_confirmation: "000000"
         )
       user.valid?
       expect(user.errors[:nickname]).to include("can't be blank", "is too short (minimum is 4 characters)")
     end
  end
end

最上部に「require 'rails_helper'」とありますが、これは「rails g rspec:install」をした際に自動作成された「rails_helper.rb」を読み込みます、という意味です。
あらかじめrails_helper.rbに各ファイル共通の設定を記載しておき、テストを実行する際には「require 'rails_helper'」によってその共通設定を読み込むことで、共通の設定を使用します。

rails_helper.rb内のデフォルトの記載に関してのここでの解説は、割愛させていただきます。

以下、各行の説明です。

  • describe User do
    descrive 〜 do内には何を記載しても構いません。が、コードを読む人がわかりやすいように書く必要があります。
    この場合Userモデルについての内容の為、「User」と記載してあります。
    「これからUserモデルについて書きますよ」という宣言のようなものです。

  • descrive '#create' do
    同じくdescrive 〜 do内に何を記載しても構いません。
    この場合、直上のコードと合わせて「これから(Userを)createする際の事を書きますよ」といった宣言をする内容になっています。
    ちなみに、createの前に#がついていますが、これはメソッド名を記載する際にはつけるという慣習に倣って記載しています。
    記載者以外がコードを読む際の可読性を向上させる為のものだと理解しています。

  • it 'nicknameが空欄の場合登録できない' do
    it 〜 do内にテストの内容をわかりやすく記入します。

(私なりにわかりやすく記載したつもりですが、実務で行う際は各企業のやり方に従ってください。個人開発レベルでは問題ないかと思われます。)

  • user = User.new(〜)
    この行ではテストにかける新規のインスタンスを作成しています。
    User.new(〜)でカッコ内の情報を持つインスタンスを作成し、左辺のuserに代入しています。
    今回は「nicknameが空欄の場合 登録できない 」事を確かめたいので、カッコ内のnicknameを "" として、あえて空欄にしています。
    このnicknameが空欄のuserをテストにかけていきます。

  • user.valid?
    valid?で先程作成したuserがバリデーションにかかるかどうか(実際に保存できる内容かどうか)を判定します。
    バリデーションの内容は、
    presence: true、つまり「空欄は認めない」というものですので、バリデーションにかかることが予想されます。

  • expect(user.errors[:nickname]).to include("can't be blank", "is too short (minimum is 4 characters)")
    そして、その予想を書くのがここです。エクスペクテーションといいます。
    構文としては、expect(X).to マッチャ(Y)です。(マッチャは後述で説明します)
    昔、英語の学習をする際にexpect 人 to do〜「人が〜するのを期待する/人に〜するよう要望する」というのを習った気がしますが、それに非常に似た形で作られているかと思います。というかそのままでしょうか。

今回の場合は、user(先程作成した新規インスタンス)のnicknameカラムに関してのエラーメッセージにcan't be blankis too short (minimum is 4 characters)含まれるというエクスペクテーションになっています。

細かく見ていくと、

  • user.errors[:nickname]

まずuserは先程作成したuserです。
errorsはメソッドで、ここでは先程のuser.valid?でfalseの場合の「理由=エラーメッセージ」を作成します。
よくわからない場合は、ターミナルでコンソールを立ち上げると理解が深まるかと思います。

ターミナル
---コンソール立ち上げ---
$ rails c
Running via Spring preloader in process 46362
Loading development environment (Rails 5.2.3)

---インスタンス生成&userに代入---
[1] pry(main)> user = User.new(nickname: "", email: "aaa@gmail.com", password: "000000", password_confirmation: "000000")
   (0.8ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, nickname: "", email: "aaa@gmail.com", workplace: nil, created_at: nil, updated_at: nil, image: nil>

---user.valid?の実行---
[2] pry(main)> user.valid?
  User Exists (84.4ms)  SELECT  1 AS one FROM `users` WHERE `users`.`email` = BINARY 'aaa@gmail.com' LIMIT 1
  User Exists (0.6ms)  SELECT  1 AS one FROM `users` WHERE `users`.`email` = BINARY 'aaa@gmail.com' LIMIT 1
=> false
---直上の行でfalseが出ている。作成したuserがバリデーションにかかっていることがわかる。---

---user.errorsの実行---
[3] pry(main)> user.errors
=> #<ActiveModel::Errors:0x00007fc2da5852d8
 @base=#<User id: nil, nickname: "", email: "aaa@gmail.com", workplace: nil, created_at: nil, updated_at: nil, image: nil>,
 @details={:nickname=>[{:error=>:blank}, {:error=>:too_short, :count=>4}]},
 @messages={:nickname=>["can't be blank", "is too short (minimum is 4 characters)"]}>

最後の行を見てみると、
messages={:nickname=>["can't be blank", "is too short (minimum is 4 characters)"]}
とありますが、これがバリデーションにかかった理由、エラーメッセージです。。

nicknameに関して、

  • "can't be blank"・・・「空欄にできない」
  • "is too short (minimum is 4 characters)"・・・「最小文字数4文字」

とあります。
テストコードで書いた通りの内容になっているので、このテストは通過するはずですね。

  • .to〜

「〜であること(期待する)」という意味になるメソッドです。ただ、私が確認した限り構文内のexpect(X)の後に必ず付くものなので、取り急ぎは必ず付けるとしていた方が良いかもしれません。
ちなみに、「〜でないこと(を期待する)」場合には.not_to〜もしくは.to_not〜を利用します。

  • include

この部分をマッチャと言い、この後ろに記述する内容expect(X)のカッコ内の関連性について記述します。
includeもマッチャの一つで、「〜を含む」という内容です。
この他にもeq「等しい」be_valid「バリデーションにかからない」などがありますが、こういった内容に関しては他記事に譲ることにします。

  • ("can't be blank", "is too short (minimum is 4 characters)")

ターミナルで確認出来る、バリデーションにかかった際のメッセージです。

実際にテストにかけてみる

まずは以下のコードを$ rails g rspec:installをした際に作成された.rspecファイルに追記します。

--format documentation

これを記載することによって、テストにかけた際のメッセージが理解しやすいものになります。

.rspec
--format documentation

次に、ターミナルでbundle exec rspecを実行します。
これが実際にテストにかける為のコマンドです。

ターミナル
$bundle exec rspec

すると、以下のように出力されるかと思います。

ターミナル
$bundle exec rspec
#省略

User
  #create
    nickname空欄の場合登録できない

Finished in 0.16438 seconds (files took 11.21 seconds to load)  #この行は環境によって異なります。(かかった時間を表示)
1 example, 0 failures

最下部のexampleとfailerはそれぞれ「実行したテストの数」と「テストが通過しなかった数について」表示しています。
よって、今回はテストが通過したという結果になりました。

ちなみに、、、

テストが通過しない場合は以下のような出力になります。
(安直ですが、今回はエクスペクテーション内の.to.not_toに書き換えて意図的に通過しないテストコードを書きました。)

spec/models/user_spec.rb
#書き換え部分
expect(user.errors[:nickname]).not_to include("can't be blank", "is too short (minimum is 4 characters)")

ターミナル
$bundle exec rspec
#省略

User
  #create
    nickname空欄の場合登録できない (FAILED - 1)

Failures:

  1) User#create nickname空欄の場合登録できない
     Failure/Error: expect(user.errors[:nickname]).not_to include("can't be blank", "is too short (minimum is 4 characters)")
       expected ["can't be blank", "is too short (minimum is 4 characters)"] not to include "can't be blank" and "is too short (minimum is 4 characters)"
     # ./spec/models/user_spec.rb:11:in `block (3 levels) in <top (required)>'

Finished in 0.15455 seconds (files took 6.67 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/models/user_spec.rb:7 # User#create nickname空欄の場合登録できない

今回は1つしかテストにかけていませんが、先程と違い1 example, 1 failureと出力されています。
テストに通過していないということですね。
また、Faileresということで、テストにかけた際に通過しなかったエクスペクテーションが具体的にどのテストなのかが1),2)・・・と一覧表示されます。

この結果をもってテストコードの内容が間違っているのか、スペルミスがあるのか、テストコードが間違っていないのであれば、そもそもの仕様に問題があるのかという検証に入るのかと思われます。

編集後記

今回の内容は私が通っているスクールのカリキュラム内容に沿ったものになっているのですが、やや疑問を感じ自身で調べたところ、登録できない事を確かめるエクスペクテーションに
expect(user.save).to be_falseyというものが出てきました。
(userは当記事の内容に沿った表記としています)

つまり、nicknameが空のuserは保存(登録)できない。という内容です。
まさに今回書きたい内容ですし、より簡潔にかけるなぁと思ったんです。

これでいいのでは?とスクールのメンターに質問したところ、「今回は保存できないことが明示的で、後にコードを見返した時に保存できない理由がひと目でわかるように書く必要がある」との事でした。
実務ではそういった意識が必要であることを痛感しました。

最後に

factory_botというgemを利用する事でより簡潔にテストコードを書く方法があります。
その内容については、また別の記事で書いていこうと思います。

今回が初めての投稿なので不備があるかもしれませんが、ご容赦ください。
出来る限り「備忘録」的な内容でなく、他の初学者の方が読んだ際の参考になるよう書いてきました。
どこかの誰かのお役に立てることを切に願います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

accepts_nested_attributes_forを理解する

Railsのaccepts_nested_attributes_forというヘルパーについて、日本語で分かりやすい記事がなかなか見つからないのでアウトプットしておきます。
特に関係は無いですが、viewファイルはhamlで記載していきます。

環境

ruby 2.6.5
Rails 5.2.4.2

概要

そもそもaccepts_nested_attributes_forとは何なのか?
Rail APIガイド
一番上の解説をそのまま翻訳してみます。

入れ子になった属性を使用すると、親を介して関連するレコードに属性を保存することができます。デフォルトでは、入れ子になった属性の更新はオフになっており、accept_nested_attributes_forクラス・メソッドを使用して有効にすることができます。入れ子になった属性を有効にすると、モデル上に属性ライターが定義されます。

属性ライターの名前はアソシエーションにちなんで付けられます。

入れ子になった属性Nested attributesと書かれているのでそういう名称だと捉えておきましょう。
要点を絞ると「親を介して関連するレコードに属性を保存することができる」ようです。
これだけだと分かりにくいですが、すぐ下のOne-to-oneのところにあるコードを読むと何となく意味が分かります。

member.rb
class Member < ActiveRecord::Base
  has_one :avatar
  accepts_nested_attributes_for :avatar
end
controller
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'

Memberモデル(親)にAvatarモデル(子)がネストしていて、Member.createの処理だけでAvatarの保存が完了しています(member.avatar.idがある=avatarのレコードが作成されている)

つまりモデルAの保存時に、それに紐づくモデルBをまとめて保存できるというのがこのヘルパーの役割のようです。

モデルの実装

teamモデルに多数のmemberが属している想定でコードを記載していきます。
モデルを作るとかルーティングを書くとか本記事に関係無い部分は省略してます。

モデルではアソシエーションの記述に加えて、親モデルに以下の記述を追記します。

team.rb
has_many :members
accepts_nested_attributes_for :members

この記述によって、Teamモデルのレコード保存時にMemberモデルのレコードをまとめて保存ができるようになります。
また、デフォルトで更新はできるけど削除できず、削除したかったらallow_destroyオプションをつけるようです。

team.rb
accepts_nested_attributes_for :members, allow_destroy: true

Memberモデル側はbelongs_to :teamとアソシエーションを書くだけで完了です。

ビューの実装

ビューではfields_forというヘルパーを使い、team用のform_with(またはform_for)の中にmembers用の入力フィールドを用意します。
Railsガイド

new.html.haml
= form_with model: @team, local: true do |f|
  %div team用のフォーム
  = f.label :name
  = f.text_field :name
  %hr
  ここからmembers
  = f.fields_for :members do |m|
    %div
      = m.label :nickname
      = m.text_field :nickname
    %div
      = m.label :age
      = m.number_field :age
  ここまでmembers
  %hr
  = f.submit

このように@team用のform_withの中にmembers用のfields_forが入っている形で記述します。

as1.jpg
しかしこのフォームには問題点があります。コントローラで@teamを作ってみましょう。

controller
def new
  @team = Team.new
end

するとなぜかmembers用のフィールドが消えてしまいました・・・
as2.jpg
実はfields_forというのは親モデルに紐づく子モデルの数だけ回るeach文のような動きをします。
今回の場合だと@team.membersの数だけfields_forの中身は実行されるということです。
@teamを作ったことでフォームが適切に機能し始め、@team.membersが無いのでfields_forの中身が実行されなかったということです。
Railsガイドにもコントローラで最低1つの子モデルのインスタンスを作成しておくのが常套手段だと書かれています。

controller
def new
  @team = Team.new
  @team.members.new
end

こうしておくとmembers入力用のフィールドが最初から1つ生成された状態になります。
timesメソッドなどを使って複数回@team.members.newを実行すれば、その数だけfields_forが入力フィールドを生成してくれます。

コントローラの実装

コントローラの実装は非常にシンプルです。
@teamを保存する処理と、fields_forの値を受け取ることができるストロングパラメータを実装すれば完成です。

controller
  def create
    @team = Team.new(team_params)
    if @team.save
      redirect_to root_path
    else
      render :new
    end
  end

  private
  def team_params
    params.require(:team).permit(:name, members_attributes: [:nickname, :age])
  end

ストロングパラメータの形が独特です。
これを理解するために、ビューから送られてくるparamsがどうなっているか見てみましょう。

params
"team"=>{
  "name"=>"Tema1",
  "members_attributes"=>{
    "0"=>{
      "nickname"=>"Member1",
      "age"=>"22"
    }
  }
}

teamの中にmembers_attributesがネストして、
その中に"0"がネストして、
さらにその中にmembers用のパラメータが入っています。
ここでRailsガイドの解説を引用します

:addresses_attributesハッシュのキーはここでは重要ではありません。各アドレスのキーが重複していなければそれでよいのです。

この記事ではaddressesではなくmembersですが、要するに"0"の部分はmembersが増えるにつれて重複なく設定できれば何でも良いということですね。
そう考えれば"0"を無視して、送られてくるparamsとストロングパラメータの形(ネストの仕方)が一致していることが分かります。

ここまでの実装で、実際にフォームを使って送信してみると・・・
as3.jpg
@team.saveしか書いてないのにMemberもcreateされてます!
membersテーブルのteam_idカラムにもきちんと今作ったteamのidが入っています。

編集できるようにする

ここでRailsガイドもAPIリファレンスも解説が少なく、途方に暮れそうですが実装は至ってシンプルです。
まずはedit用のビューファイルを作成し、newと使い回せるようにしておきます。

new.html.haml
= render "form"
edit.html.haml
= render "form"
_form.html.haml
= form_with model: @team, local: true do |f|
  %div team用のフォーム
  = f.label :name
  = f.text_field :name
  %hr
  ここからmembers
  = f.fields_for :members do |m|
    %div
      = m.label :nickname
      = m.text_field :nickname
    %div
      = m.label :age
      = m.number_field :age
  ここまでmembers
  %hr
  = f.submit

コントローラもRailsの基本的なものを書いておきます。

controller
  def edit
    @team = Team.find(params[:id])
  end

  def update
    @team = Team.find(params[:id])
    if @team.update(team_params)
      redirect_to root_path
    else
      render :edit
    end
  end

ここで既存レコード更新について、Rails APIリファレンスから引用します。
このリファレンスではmemberが親モデルでpostsが子モデルです。

If the hash contains an id key that matches an already associated record, the matching record will be modified:

member.attributes = {
  name: 'Joe',
  posts_attributes: [
    { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
    { id: 2, title: '[UPDATED] other post' }
  ]
}

member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
member.posts.second.title # => '[UPDATED] other post'

要するにposts_attributesの中にidというキーが含まれていて、そのidのレコードが実際にpostsテーブルにあれば更新される、と。

そして今度はRailsガイドから引用します。

関連付けられたオブジェクトが既に保存されている場合、fields_forメソッドは、保存されたレコードのidを持つ隠し入力を自動的に作成します。

関連付けられたオブジェクトが既に保存されている場合=>つまりeditでは
保存されたレコードのidを持つ隠し入力を自動的に作成します=>APIリファレンスで必要と書かれていたidキー用のinputを自動生成します。

実際にeditの画面で検証ツールを開くと、作った覚えの無いinputができてます。
as4.jpg
必要なキーが自動生成されてるってことはもしかしてもう実装終わり?Rails凄すぎない?
と思って送信してみたら期待とは違ったことになりました。
as5.jpg
Teamはupdateですが、Memberはcreateとなっています。実際に既存レコードの更新ではなく1件増えてしまいました。
しかし、こうなってしまった原因もこの中に書いていますね。
Unpermitted parameter: :id
idというキーでparamsは送られてきたが、permitできなかった。
つまりストロングパラメータでidを許可していないことが原因です。

controller
def team_params
  params.require(:team).permit(:name, members_attributes: [:nickname, :age, :id])
end

:ageの後ろに:idを追加しました。
そして再度editからフォームを送信してみると・・・
as6.jpg
Memberもupdateになりました!やっぱりRails凄すぎない?(10分ぶり2回目)
実際にDBの値を確認すると更新されていたのでこれで実装完了です。

削除できるようにする

Railsガイドに丁寧に書いてあったのでそのまま引用&実装していきます。

accepts_nested_attributes_forにallow_destroy: trueを渡すと、関連付けられたオブジェクトをユーザーが削除することを許可できるようになります。

これはモデルの実装で書いたので大丈夫ですね。

あるオブジェクトの属性のハッシュに、キーが_destroyで値が1またはtrueの組み合わせがあると、そのオブジェクトは削除されます。

要するに_destroyというキーの入力欄を作り、1かtrueが送られてくれば削除できる、と。
数字の入力欄でも良いですが、公式に倣いチェックボックスで作ってみます。

_form.html.haml
= f.fields_for :members do |m|
  %div
    = m.label :nickname
    = m.text_field :nickname
  %div
    = m.label :age
    = m.number_field :age
# ここから下を追記
  %div
    = m.check_box :_destroy

updateの時の失敗でレコードが2件に増えてますが、fields_forが2周動いて入力欄とチェックボックスが2個ずつ生成できているのが分かります。
これにチェックを入れればそのmemberは削除される、という寸法です。
as7.jpg
そして最後に、ストロングパラメータに_destroyを追加します。
updateの時の失敗は繰り返さない!

controller
  def team_params
    params.require(:team).permit(:name, members_attributes: [:nickname, :age, :id, :_destroy])
  end

試しにチェックボックスにチェックを入れて送信してみると・・・
as8.jpg
削除されました。Rails凄すぎない?(20分ぶり3回目)
削除機能も実装完了です。

編集画面で新規追加もできるようにする

今のままだとeditページでは新規メンバーの追加ができません。
既存のteamに新しいメンバーを追加するためのフィールドも用意したいところです。

fields_forは子レコードの数だけ回るeach文だと最初の方で書きました。
editでは子レコードの数=既存メンバーの数なので、追加でもう1つインスタンスを作っておけば良さそうですね。

controller
def edit
  @team = Team.find(params[:id])
  @team.members.new # ここを追記
end

editの定番処理@team = Team.find(params[:id])に加えて、newの時に書いたmemberのインスタンスを作る処理を追加しました。
as9.jpg
既存メンバーが1人のteamのeditでこのようになりました。いい感じです。
as10.jpg
実際に既存メンバーのupdate+新規メンバーのcreateができています。解決!

...と思ったのですが。こんな落とし穴がありました
as11.jpg
editで新規メンバーを追加したくないので空けたまま送信します。
as12.jpg
名無しさんがcreateされてしまいました。
これは良くないですね。バリデーションをきちんと実装した場合、そもそも保存に失敗しそうです。
どうしようかと思ったらRailsガイドに書いてありました。

ユーザーが何も入力しなかったフィールドを無視できれば何かと便利です。これは、:reject_if procをaccepts_nested_attributes_forに渡すことで制御できます。このprocは、フォームから送信された属性のハッシュ1つ1つについて呼び出されます。このprocがfalseを返す場合、Active Recordはそのハッシュに関連付けられたオブジェクトを作成しません。

※falseと書いてますが参考コード的にtrueだと思われます

procというのはブロックのようなものです(厳密には違います)
つまり
1. accepts_nested_attributes_for
2. reject_ifオプションを追加し、
3. 空だったらtrueを返す式をproc(ブロック)で書いておく
以上で、空のレコードが生成されないようにできるようです。
ブロックの渡し方は色々あるようですが、個人的にメソッド化する方法がしっくり来たのでその方針で実装します。
Rails APIガイドreject_ifで検索すると書き方がいくつか載ってます)

team.rb
accepts_nested_attributes_for :members, allow_destroy: true, reject_if: :reject_members

private
def reject_members(attributes)
  attributes[:nickname].blank? || attributes[:age].blank?
end

reject_membersメソッドを実装し、:nicknameもしくは:ageが空だったらtrueを返すようにしました。
今回はnicknameとageの両方を入力して欲しいのでこの形にしています。
メソッド化しておけばこの辺りの条件をカスタマイズする時に読みやすそうなのでこの方法を選択しました。
as13.jpg
これでageに100を入力すると、paramsは送信できていますがmemberのcreateはされず・・・
as14.jpg
nicknameとageが揃っていればmemberがcreateされるようになりました!
最低限やりたいことが実装できたため、今回の実装はここまでにします。

終わりに

この先はチェックボックスを消した状態で操作したり、新規追加用のフィールドを動的に追加したり・・といったことをJSで実装していくケースが多いかと思います。
本記事は一切JSに触れていませんが、結局JSで書くべきコードはaccepts_nested_attributes_forを使うための操作をJSで実現する、というだけです。
ここの理解を固めてからJSを書かないと、エラーが出てもデバッグが進まないと思います。

そしてここまで書いて気づきましたが、APIリファレンスとRailsガイドで完結してしまいました。
公式で完結するから参考記事が少ないんでしょうね。やっぱり一次ソースは大切です。

参考サイト

Rails APIリファレンス
Railsガイド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby単体でDB接続したいとき(MySQL)

はじめに

RubyでDBに接続したい時があるかと思います。

例えばスクレイピングしてきた情報をDBに格納したい時など。

ActiveRecordを使うと実装できます。

ActiveRecordはRailsに搭載されているので、皆さんご存知かとは思いますが、

ACtiveRecordを単体で使ったことがない方は結構いらっしゃるかと思いますので、ご紹介します。

今回はMySQLを使います。

ActiveRecordを単体で使ってみる

準備

まずはDBを用意しましょう。MySQLにログインします。

mysql -u root

データベースを作ります。名前は今回sampleにします。

mysql> create database sample;

sampleデータベースに入ります。

mysql> use sample;

テーブルを作ります。名前は今回usersにします。Rubyの規約上、テーブル名は複数形にしてください(sをつける)

mysql> create table product (id int auto_increment not null primary key, name varchar(10));

一旦抜けましょう

mysql> exit

続いてActiveRecordをインストールしましょう

$ gem install activerecord

mysql2もインストールします

$ gem install mysql2

実装

これで準備ができたのでRubyのファイルを作ります

$ vi sample.rb

avtive_recordのgemを呼び出し、DBの接続設定を以下のように記します

sample.rb
# ↓ ファイルに書く時はアンダースコアがいるので注意
require 'active_record' 

#DB接続設定
 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

更にUserクラスを設定します。これでDBのusersテーブルがいじれるようになります

sample.rb
require 'active_record' 

 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

#以下追記
class User < ActiveRecord::Base
end

試しにUserテーブルにレコードを作りましょう

sample.rb
require 'active_record' 

 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

class User < ActiveRecord::Base
end

#以下追記
User.create(name: "taro")

実行してみます

$ ruby sample.rb

ファイルを変更してレコードができたか確認しましょう

sample.rb
require 'active_record' 

 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

class User < ActiveRecord::Base
end

#以下変更
puts User.find_by(name: 'taro').name

実行

$ ruby sample.rb

以下のように出ればOKです。

taro

終わりに

今回は一つのファイルの中に設定など全て書きましたが、実際に使う時は、DB接続設定を他のRubyファイルに書いてrequireするか、もしくは、YMLファイルに設定を書いて読み込んだりなどして使ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby単体でDB接続したい時(MySQL)

はじめに

RubyでDBに接続したい時があるかと思います。

例えばスクレイピングしてきた情報をDBに格納したい時など。

ActiveRecordを使うと実装できます。

ActiveRecordはRailsに搭載されているので、皆さんご存知かとは思いますが、

ACtiveRecordを単体で使ったことがない方は結構いらっしゃるかと思いますので、ご紹介します。

今回はMySQLを使います。

ActiveRecordを単体で使ってみる

まずはDBを用意しましょう

mysql -u root

データベースを作ります。名前は今回sampleにします。

mysql> create database sample;

sampleデータベースに入ります。

mysql> use sample;

テーブルを作ります。名前は今回usersにします。Rubyの規約上、テーブル名は複数形にしてください(sをつける)

mysql> create table product (id int auto_increment not null primary key, name varchar(10));

一旦抜けましょう

mysql> exit

続いてActiveRecordをインストールしましょう

gem install activerecord

mysql2もインストールします

gem install mysql2

続いてrubyのファイルを作ります

$ vi sample.rb

avtive_recordのgemを呼び出し、DBの接続設定を以下のように記します

sample.rb
# ↓ ファイルに書く時はアンダースコアがいるので注意
require 'active_record' 

#DB接続設定
 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

更にUserクラスを設定します。これでDBのusersテーブルがいじれるようになります

sample.rb
require 'active_record' 

 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

#以下追記
class User < ActiveRecord::Base
end

試しにUserテーブルにレコードを作りましょう

sample.rb
require 'active_record' 

 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

class User < ActiveRecord::Base
end

#以下追記
User.create(name: "taro")

実行してみます

ruby sample.rb

ファイルを変更してレコードができたか確認しましょう

sample.rb
require 'active_record' 

 ActiveRecord::Base.establish_connection( 
  adapter:  "mysql2", 
  host:     "localhost", #ローカルのDBに接続します。
  username: "root", #ユーザー名
  password: "",  #設定したMySQLのパスワード
  database: "sample",  #接続したいDB名
)

class User < ActiveRecord::Base
end

#以下変更
puts User.find_by(name: 'taro').name

実行

ruby sample.rb

以下のように出ればOKです。

taro

終わりに

今回は同じファイルの中に設定など全て書きましたが、実際に使う時は、DB接続設定を他のRubyファイルに書いてrequireするか、もしくは、YMLファイルに設定を書いて読み込んだりなどして使ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gmail api を ServiceAccount ( G Suite 管理下の ) で行うのは大変

目的

  • Gmail API でのメール送信
  • G suite 管理下のドメイン配下からの送信
  • ユーザーのトークンでは無くて ServiceAccount 経由

必要な事

  • サービスアカウントの詳細で、「G suite ドメイン全体の委任を有功にする」
  • G suite管理画面より、Api scopes を設定する( 不明瞭な点あり

Api scopes

http://www.google.com/m8/feeds/contacts/,http://www.google.com/m8/feeds/groups/,https://mail.google.com/

( なんでか判らないけど、mail.google.com スコープだけだと権限エラーになってしまうので、この三つのスコープが必須

sample code ( ruby

# frozen_string_literal: true

class GmailDeliveryMethod
  attr_reader :settings

  def initialize(settings)
    @settings = settings
  end

  def deliver(mail)
    credentials = Google::Auth::ServiceAccountCredentials.new(
      token_credential_uri: settings[:token_uri],
      audience: settings[:token_uri],
      scope: Google::Apis::GmailV1::AUTH_SCOPE,
      issuer: settings[:client_email],
      signing_key: OpenSSL::PKey::RSA.new(settings[:private_key]),
      project_id: settings[:project_id],
    )
    credentials.sub = mail.from.first
    gmail = Google::Apis::GmailV1::GmailService.new
    gmail.authorization = credentials
    gmail.send_user_message(
      mail.from.first,
      upload_source: StringIO.new(mail.to_s),
      content_type: 'message/rfc822',
    )
  end
  alias deliver! deliver
end


message = Mail.new(
  from: 'from@example.com',
  to: 'to@example.com',
  subject: 'TEST MAIL',
  body: "TEST MAIL #{Time.current}",
)

GmailDeliveryMethod.new({...}).deliver(message)

test_–_IAM_と管理_–.png
Admin_console.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

読みやすいコードを書く

読みやすいコードとは

読みやすいコードが書けずに困っているため、現段階で学んだことなどを書いていきたい。

客観的にコードを読む

コードは自分だけがわかれば良いものではない。全く関係のない人が見ても理解に時間のかからないコードを書く必要がある。
そのためには自分の頭をリセットして客観的に自分のコードを読む

わかりやすい変数名、メソッド名を書く

ここに一番苦戦している。ボキャブラリーが少ないせいか、わかりやすい名前をつけることができない。
そのため、ここに汎用的に使えそうな変数名などをメモしていきたいと思う。

範囲の指定

firstとlast
限界値を明確にするには
minとmaxを使う

何かを出力するメソッド

output ~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1行で素数列挙

はじめに

ふと思いつきで1行で素数を求めるプログラムを書いてみました。効率ではなくコード量重視。

Ruby

filter かと思ったら Range の場合 select なんですね。(Ruby 2.5)

(2..99).select {|x| (2...x).select {|i| x % i == 0} == []}

Python

filter でもいいけれどリスト内包表記の方がすっきり書けます。

[x for x in range(2, 100) if [i for i in range(2, x) if x % i == 0] == []]

Haskell

Haskell は何年たっても勉強中の初心者から抜け出せません。

[x | x <- [2..100], [i | i <- [2..(x - 1)], mod x i == 0] == []]

まとめ

リスト内包表記ができないRubyが不利かと思ったら、意外なことにぼくが書いた中では一番短かった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル〜5章〜ふざけてる時間ないので真面目に勉強する

レイアウトを作成する。

5章目に入っていきます。4章はRailsっていうよりもRubyの基本的な文法や考え方についてやっていましたが今回からは具体的にsample_appのレイアウトを整えていく手法を勉強していきます。

ナビゲーション

まずサンプルアプリケーションにリンクとスタイルを追加しないといけない。
新たにapplication.html.erbにHTML構造を追加する。

<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application',
                               'data-turbolinks-track': 'reload' %>
    <!--[if lt IE 9]>
      <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
      </script>
    <![endif]-->
  </head>
  <body>
    <header class="navbar navbar-fixed-top navbar-inverse">
      <div class="container">
        <%= link_to "sample app", '#', id: "logo" %>
        <nav>
          <ul class="nav navbar-nav navbar-right">
            <li><%= link_to "Home",   '#' %></li>
            <li><%= link_to "Help",   '#' %></li>
            <li><%= link_to "Log in", '#' %></li>
          </ul>
        </nav>
      </div>
    </header>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

<!DOCTYPE html>はHTML5であることの宣言に使用する。

<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
  </script>
<![endif]-->

これは、Microsoft Internet Explorer (IE) のバージョンが9より小さい場合 (if lt IE 9) にのみ、囲まれている行を実行する。これにより、Firefox、Chrome、Safariなどの他のブラウザに影響を与えずに、IEのバージョンが9未満の場合にのみHTML5を読み込める。

それ以外はprogateやってればすぐわかる内容。

<%= link_to "sample app", '#', id: "logo" %>
<nav>
  <ul class="nav navbar-nav navbar-right">
    <li><%= link_to "Home",   '#' %></li>
    <li><%= link_to "Help",   '#' %></li>
    <li><%= link_to "Log in", '#' %></li>
  </ul>
</nav>
ここで埋め込みRubyコード出現。

リンクを生成するために、Railsヘルパーのlink_toを使用。 (リンクテキスト・URLを引数としている)
このURLは名前付きルート (Named Routes) るが今はひとまずURL「'#'」を置いておく。
第3引数はオプションハッシュで、この場合はサンプルアプリのリンクでCSS id logoを指定している(他の3つのリンクにはオプションハッシュが指定されていませんが、必須ではないので構わない。)

<nav>
  <ul class="nav navbar-nav navbar-right">
    <li><%= link_to "Home",   '#' %></li>
    <li><%= link_to "Help",   '#' %></li>
    <li><%= link_to "Log in", '#' %></li>
  </ul>
</nav>
divの内側の2番目の要素は、リストアイテムタグliと
順不同リストタグulによって作られた、ナビゲーションリンクのリスト。

navタグ=「その内側がナビゲーションリストである」という意味

Railsが埋め込みRubyを評価し、レイアウトを描画するとリストが次のように置き換わる。

<nav>
  <ul class="nav navbar-nav navbar-right">
    <li><a href="#">Home</a></li>
    <li><a href="#">Help</a></li>
    <li><a href="#">Log in</a></li>
  </ul>
</nav>
これがブラウザに返される。

<div class="container">
  <%= yield %>
</div>

上と同様、containerクラスもBootstrapにおいて特別な意味を持ちます。yieldメソッドはWebサイトのレイアウトにページごとの内容を挿入する。
これでフッターを除いてレイアウトは完成する。

サインアップページへのリンクがあるHomeページ
app/views/static_pages/home.html.erb

<div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", '#', class: "btn btn-lg btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails logo"),
            'http://rubyonrails.org/' %>

第7章でサイトにユーザーを追加するときに備えて、最初のlink_toで次のような仮のリンクを生成します。

<a href="#" class="btn btn-lg btn-primary">Sign up now!</a>

2番目のlink_toでは、引数として画像ファイルのパスと任意のオプションハッシュをとるimage_tagヘルパーの能力が示されています。このヘルパーでは、シンボルを使ってalt属性などを設定できます。

画像のダウンロード

$ curl -o app/assets/images/rails.png -OL railstutorial.jp/rails.png

このコマンドラインを入力するとRailsはimage_tagヘルパーを使っているので、該当する画像ファイルをアセットパイプラインを通してapp/assets/images/ディレクトリの中から探してくれる。

ネコ画像をインターネットからダウンロードする

$ curl -OL cdn.learnenough.com/kitten.jpg

演習

1.は答え書いてあるので省略

2.mvコマンドを使って、ダウンロードしたkitten.jpgファイルを適切なアセットディレクトリに移動してください

mv kitten.jpg app/assets/images/

3.image_tagを使って、kitten.jpg画像を表示してみてください (図 5.4)。

<%= image_tag("kitten.jpg", alt: "cat")%

ちょっと待って。。これだけ言わせて。。。

猫クッソかわええええぇえええええぇぇぇぇ

永遠に愛でていられる、、。
。。。。は!!!!!いかんいかん。
私には時間がないのです。先に進まなくては。。。。

BootstrapとカスタムCSS

次は上記を追加していきます。ここらへん俺progateでやってないんだよな。しっかりまとめないと。
まずBootstrapを追加する。、bootstrap-sass gemを使ってRailsアプリケーションに導入することで使用することができる。
Bootstrapフレームワークでは、動的なスタイルシートを生成するためにLESS CSS言語を使っている。
RailsのAsset Pipelineはデフォルトでは (LESSと非常によく似た) Sass言語をサポートするそのため、bootstrap-sassは、LESSをSassへ変換し、必要なBootstrapファイルを現在のアプリケーションですべて利用できるようにする。

Gemfileにbootstrap-sassを追加する

source 'https://rubygems.org'

gem 'rails',          '5.1.6'
gem 'bootstrap-sass', '3.3.7'
.
.
.

終わったら
$ bundle installを実行。

rails generateコマンドを実行することでコントローラーごとに分けられたCSSファイルが自動的に生成されますが、これらのファイルを正しい順序で読み込ませるのは至難の技らしい。
よってすべてのCSSを1つにまとめる方針を採っています。カスタムCSSを動かすための最初の一歩は、カスタムCSSファイルを作ることである。

$ touch app/assets/stylesheets/custom.scss

app/assets/stylesheets/
Asset Pipelineの一部であり、このディレクトリに置かれたスタイルシートはapplication.cssの一部としてWebサイトのレイアウトに読み込まれます。さらに、ファイル名のcustom.scssには.scssという拡張子も含まれています。この拡張子は「Sass (Sassy CSS)」と呼ばれるCSSを拡張した言語で、アセットパイプラインはこのファイルの拡張子を見て、Sassを処理できるようにしている。

カスタムCSS用のファイルを作成したら、リスト 5.6のように@importを使って、Bootstrap (とそれに関連するSprockets) を読み込みます11。

Bootstrap CSSを追加する

app/assets/stylesheets/custom.scss
---------------------------------
@import "bootstrap-sprockets";
@import "bootstrap";

Bootstrap CSSのフレームワークを導入する。導入後、Webサーバを再起動させると、アプリケーションに反映させることができます。

Ctrl-Cを押してWebサーバを停止させた後、rails server

ここからはCSSを使ってレイアウトの変更が行われる
ここでの演習は確認のみなので省略

パーシャル

レイアウトのコードを整理していく。
HTMLヘッダーは論理的な単位として分けられるため、一箇所にまとめた方が便利です。Railsではパーシャル (partial) という機能でこのような課題を解決できます。

レイアウトにshimとheaderのパーシャルを追加する

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application',
                               'data-turbolinks-track': 'reload' %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

次のようにrenderと呼ばれるRailsヘルパー呼び出しだけを使って、HTML shimのスタイルシート行を置換しています。

<%= render 'layouts/shim' %>

この行では、app/views/layouts/_shim.html.erbというファイルを探してその内容を評価し、結果をビューに挿入しています

ファイル名 _shim.html.erb の先頭にあるアンダースコア
このアンダースコアは、パーシャルで使う普遍的な命名規約であり、また、一目見ただけでディレクトリ中のすべてのパーシャルを識別することが可能となる。

パーシャルが動作するためには、それに対応するファイルとコンテンツを記述する。

リスト 5.13: HTML shim用のパーシャル
app/views/layouts/_shim.html.erb
-----------------------------------------------------
<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
  </script>
<![endif]-->

ヘッダーの情報もパーシャルに移動し、renderを呼び出してレイアウトに挿入することができる

リスト 5.14: header用のパーシャル
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", '#', id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   '#' %></li>
        <li><%= link_to "Help",   '#' %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </div>
</header>

今度はヘッダーに対応するフッタを同じ方法で追加しましょう。_footer.html.erb で、layoutsディレクトリ作成する。

リスト 5.15: footer用のパーシャル
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    The <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   '#' %></li>
      <li><%= link_to "Contact", '#' %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>

ヘッダーの場合と同様に、フッターの中でもlink_toメソッドを使って、AboutページとContactページへの内部リンクを追加する。

リスト 5.16: レイアウトにfooterパーシャルを追加する
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application',
                               'data-turbolinks-track': 'reload' %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>

Sassとアセットパイプライン

Rails開発者の視点からは、アセットディレクトリ、マニフェストファイル、プリプロセッサエンジンという、3つの主要な機能が理解の対象となる。

アセットディレクトリ

Railsのアセットパイプラインでは、静的ファイルを目的別に分類する、標準的な3つのディレクトリが使われています。

app/assets: 現在のアプリケーション固有のアセット
lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
vendor/assets: サードパーティのアセット
これらのディレクトリには、それぞれのアセットクラス用のサブディレクトリがあります。例えばapp/assetsの場合、次のような画像用、JavaScript用、CSS用のサブディレクトリがあります。

$ ls app/assets/
images/  javascripts/  stylesheets/

マニフェストファイル

静的ファイル (アセット) を上記の場所へそれぞれ配置すれば、マニフェストファイルを使って、それらをどのように1つのファイルにまとめるのかをRailsに指示することができます。なお、実際にアセットをまとめる処理を行うのはSprocketsというgemです。また、マニフェストファイルはCSSとJavaScriptには適用されますが、画像ファイルには適用されない。

リスト 5.19: アプリケーション固有のCSS用マニフェストファイル
app/assets/stylesheets/application.css
/*
 * This is a manifest file that'll be compiled into application.css, which
 * will include all the files listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets,
 * vendor/assets/stylesheets, or vendor/assets/stylesheets of plugins, if any,
 * can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear
 * at the bottom of the compiled file so the styles you add here take
 * precedence over styles defined in any styles defined in the other CSS/SCSS
 * files in this directory. It is generally better to create a new file per
 * style scope.
 *
 *= require_tree .
 *= require_self
 */

上の行で重要な部分は、実はCSSコメントの中にあります。コメント内の次の部分は、Sprocketsが適切なファイルを読み込むために使われます。

/*
 .
 .
 .
 *= require_tree .
 *= require_self
*/

例えばこの行は、

 *= require_tree .
app/assets/stylesheetsディレクトリ (サブディレクトリを含む) 中のすべてのCSSファイルが、アプリケーションCSSに含まれるようにしています。

また、次の行は、

 *= require_self
CSSの読み込みシーケンスの中で、application.css自身もその対象に含めています。

Railsには実用的なデフォルトのマニフェストファイルが付属しているので、Railsチュートリアルでは変更を加える必要がありませんが、もし必要な場合は、Railsガイドの「アセットパイプライン」で詳細な情報を参照できます。

プリプロセッサエンジン

必要なアセットをディレクトリに配置してまとめた後、Railsはさまざまなプリプロセッサエンジンを介してそれらを実行し、ブラウザに配信できるようにそれらをマニフェストファイルを用いて結合し、サイトテンプレート用に準備します。Railsはどのプリプロセッサを使うのかを、ファイル名の拡張子を使って判断します。

最も一般的な拡張子は、
Sass用の.scss
CoffeeScript用の.coffee
埋め込みRuby (ERb) 用の.erb

プリプロセッサエンジンは、繋げて実行する (chain) ことができます。

foobar.js.coffee

上の拡張子の場合、CoffeeScriptプロセッサ経由で実行されます。

foobar.js.erb.coffee

上の拡張子の場合は、CoffeeScriptとERbの両方で実行されます (コードは右から左へと実行されますので、この例ではCoffeeScriptが最初に実行される)。

本番環境での効率性

Asset Pipelineがすべてのスタイルシートを1つのCSSファイル (application.css) にまとめ、すべてのJavaScriptファイルを1つのJSファイル (javascripts.js) にまとめてくれる。不要な空白やインデントを取り除く処理を行い、ファイルサイズを最小化してくれる。

素晴らしい構文を備えたスタイルシート

Sassが提供する2つの重要な機能、ネストと変数について説明する。

ネスト

スタイルシート内に共通のパターンがある場合は、要素をネストさせることができます。例えばリスト 5.7では、次のように.centerと.center h1の両方に対してルールがあります。

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

上のルールは、Sassを使って次のように書き換えることができます。

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

上の例では、ネストの内側にあるh1というルールは、.centerのルールを継承しています。

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}
上のコードでは#logoというidが2回使われています。1回目はロゴ自身を定義するために、2回目はhover属性を定義するために使われています (なおhover属性は、該当する要素の上にマウスポインタをかざしたときの表示を定義します)。2つ目のルールをネストするためには、親属性である#logoを参照する必要があります。このような場合、SCSSでは次のようにアンパーサンド&を使って実現できます。

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: #fff;
    text-decoration: none;
  }
}

Sassは、SCSSをCSSに変換する際に、&:hoverを#logo:hoverに置き換えています。

これらのネスト機能は、フッターのCSSでも使えます。SCSSを使って次のように書き換えることができる。

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #777;
  a {
    color: #555;
    &:hover {
      color: #222;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

変数

Sassでは、冗長なコードを削除し、より自由な表現を可能にするために、変数が定義できるようになっている。

h2 {
  .
  .
  .
  color: #777;
}
.
.
.
footer {
  .
  .
  .
  color: #777;
}

$light-grayのような変数名は、#777のような値よりも分かりやすいので、たとえその変数が繰り返し使われないとしても、変数名を与えることは多くの場合有用です。実際、Bootstrapフレームワークでは、多くの色に対して変数名を定義しています。定義されている変数はBootstrapページの「LESS変数一覧」で参照することができます。

@gray-light: #777;
これはつまり、bootstrap-sassというgemを使えば、SCSSでも同様に$gray-lightという変数が使えることを意味しています。先ほど定義した$light-grayというカスタム変数の代わりに、用意された変数を使ってみましょう。

h2 {
  .
  .
  .
  color: $gray-light;
}
.
.
.
footer {
  .
  .
  .
  color: $gray-light;
}
ネストや変数を使って初期のSCSSファイルを書き直した結果
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $gray-light;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}


/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

レイアウトのリンク

次にレイアウトのリンクを書き換えていく。
'#'で代用していたリンクを書き換えてみる。
もちろん、次のようにリンクを直接記述することもできる。

<a href="/static_pages/about">About</a>

しかし、上の記法はRails流ではありません。まず、aboutページへのURLは /static_pages/about よりも /about の方がよいでしょう。さらに、Railsでは次のようなコードでは名前付きルートを使うのが慣例となっています。

<%= link_to "About", about_path %>

上のようにすることでコードの意味がわかりやすくなり、about_pathの定義を変えればabout_pathが使われているすべてのURLを変更できるため、柔軟性が高まる。

今後使う予定のURLとルーティング (route) とのマッピングを、表に示す。

ページ名    URL      名前付きルート
Home     /           root_path
About    /about      about_path
Help     /help        help_path
Contact  /contact    contact_path
Sign up  /signup     signup_path
Log in   /login      login_path

RailsのルートURL

名前付きルートをサンプルアプリケーションの静的ページで使うために、ルーティング用のファイル (config/routes.rb) を編集していく。

ルートURLを定義すると、root_pathやroot_urlといったメソッドを通してURLを参照することができます。ちなみに前者はルートURL以下の文字列を、後者は完全なURLの文字列を返します。

root_path -> '/'
root_url  -> 'http://www.example.com/'

なお、Railsチュートリアルでは一般的な規約に従い、基本的には_path書式を使い、リダイレクトの場合のみ_url書式を使うようにします。これはHTTPの標準としては、リダイレクトのときに完全なURLが要求されるためです。ただしほとんどのブラウザでは、どちらの方法でも動作します。
デフォルトのルーティングはやや回りくどいので、HelpページやAboutページ、Contactページなどの名前付きルートを定義していきましょう。具体的には、getルールを使って定義していきます

例えば次のようなルーティングは、

get 'static_pages/help'

このように変換します。

get  '/help', to: 'static_pages#help'

このようにgetルールを使って変更すると、GETリクエストが /help に送信されたときにStaticPagesコントローラーのhelpアクションを呼び出してくれるようになります。また、ルートURLのときと同様に、help_pathやhelp_urlといった名前付きルートも使えるようになります。

help_path -> '/help'
help_url  -> 'http://www.example.com/help'

他の静的ページについても同様にルーティングを変更していくと、リスト 5.23はリスト 5.27のようなコードになります。

リスト 5.27: 静的なページのルーティング一覧 red

config/routes.rb

Rails.application.routes.draw do
  root 'static_pages#home'
  get  '/help',    to: 'static_pages#help'
  get  '/about',   to: 'static_pages#about'
  get  '/contact', to: 'static_pages#contact'
end

'static_pages/home'という以前のルールを削除している点に注意する。今後は常にroot_pathまたはroot_urlを使っていきます。

リスト 5.28: StaticPagesで扱う新しい名前付きルートに対するテスト green
test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  test "should get home" do
    get root_path
    assert_response :success
    assert_select "title", "Ruby on Rails Tutorial Sample App"
  end

  test "should get help" do
    get help_path
    assert_response :success
    assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
  end

  test "should get about" do
    get about_path
    assert_response :success
    assert_select "title", "About | Ruby on Rails Tutorial Sample App"
  end

  test "should get contact" do
    get contact_path
    assert_response :success
    assert_select "title", "Contact | Ruby on Rails Tutorial Sample App"
  end
end

名前付きルート

リスト 5.27でルートを定義したことにより、レイアウトの中で名前付きルートが使えるようになりました。早速、link_toメソッドの2番目の引数で、適切な名前付きルートを使ってみる。

<%= link_to "About", '#' %>

このように置き換えます。

<%= link_to "About", about_path %>

最初に、HomeページとHelpページへのリンクを持つheaderパーシャル _header.html.erb (リスト 5.30) から取り掛かります。headerパーシャルでは、Web共通の慣習に従って、ロゴにもHomeページへのリンクを追加します。

リスト 5.30: headerパーシャルにリンクを追加する

app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",    root_path %></li>
        <li><%= link_to "Help",    help_path %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </div>
</header>

[Log in] リンクの名前付きルートは第8章で作成するため、今の段階では'#'のままにしておきます。

footerパーシャル _footer.html.erb にもリンクがあります。これらはAboutページとContactページへのリンクです (リスト 5.31)。

リスト 5.31: footerパーシャルにリンクを追加する
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    The <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   about_path %></li>
      <li><%= link_to "Contact", contact_path %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>

5.3.4 リンクのテスト
レイアウト内のいくつかのリンクを埋めることができたので、これらのリンクが正しく動いているかどうかチェックするテストを書いてみましょう。

まず統合テストの作成

$ rails generate integration_test site_layout
      invoke  test_unit
      create    test/integration/site_layout_test.rb

このとき、Railsは渡されたファイル名の末尾に _test という文字列を追加することに注目してください。

今回の目的は、アプリケーションのHTML構造を調べて、レイアウトの各リンクが正しく動くかどうかチェックすることです。つまり、

ルートURL (Homeページ) にGETリクエストを送る.
正しいページテンプレートが描画されているかどうか確かめる.
Home、Help、About、Contactの各ページへのリンクが正しく動くか確かめる.
Railsの統合テストでは、上のステップをコードに落とし込んでいくことになります (リスト 5.32)。具体的には、まずassert_templateメソッドを使って、Homeページが正しいビューを描画しているかどうか確かめます16。

リスト 5.32: レイアウトのリンクに対するテスト green
test/integration/site_layout_test.rb
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
  end
end

assert_selectメソッドの高度なオプションを使っていく。
今回のケースでは、特定のリンクが存在するかどうかを、aタグとhref属性をオプションで指定して調べていく。

assert_select "a[href=?]", about_path
上のコードでは、Railsは自動的にはてなマーク "?" をabout_pathに置換しています (このとき "about_path" 内に特殊記号があればエスケープ処理されます)。これにより、次のようなHTMLがあるかどうかをチェックすることができます。

...
一方で、ルートURLへのリンクは2つあることを思い出してください (1つはロゴに、もう1つはナビゲーションバーにあります)。このようなとき、

assert_select "a[href=?]", root_path, count: 2
といった風に書くことで、リスト 5.30で定義したHomeページのリンクの個数も調べることもできます

assert_selectには色々な指定の仕方があります。その代表例をいくつか表 5.2で紹介します。assert_selectは柔軟でパワフルな機能で、ここでは紹介し切れないほど他にも多くのオプションがあります。しかし経験的には、このメソッドで複雑なテストはしない方が賢明です。今回のようなレイアウト内で頻繁に変更されるHTML要素 (リンクなど) をテストするぐらいに抑えておくとよいです。

Code    マッチするHTML
assert_select "div" <div>foobar</div>
assert_select "div", "foobar"   <div>foobar</div>
assert_select "div.nav" <div class="nav">foobar</div>
assert_select "div#profile" <div id="profile">foobar</div>
assert_select "div[name=yo]"    <div name="yo">hey</div>
assert_select "a[href=?]", '/', count: 1    <a href="/">foo</a>
assert_select "a[href=?]", '/', text: "foo" <a href="/">foo</a>

assert_selectのいくつかの使用例
リスト 5.32で追加した統合テストが通るかどうかは、次のようにRakeタスクを実行することで試すことができます。

リスト 5.33: green
$ rails test:integration
統合テストが成功したら、今度はすべてのテストを流して greenするかどうか確かめてみてください。

リスト 5.34: green
$ rails test
レイアウトのリンクをテストする統合テストが追加されたことで、リンクに間違った変更が加えられたらすぐに気付けるようになりました。

class ActiveSupport::TestCase
  fixtures :all
  include ApplicationHelper
  .
  .
  .
end

ユーザー登録:最初のステップ

ユーザー登録ページへのルーティングを作成する。

Usersコントローラ

最初のコントローラであるStaticPagesコントローラを作成しました。今度は2番目のコントローラであるUsersコントローラを作成しましょう。以前のときと同様に、generateを実行して、現時点での要求である新規ユーザー用のユーザー登録ページ (スタブ) を持つ、最も簡単なコントローラを作成します。Railsで好まれているRESTアーキテクチャの規約に従い、新規ユーザー用のアクションをnewとする。したがって、generate controllerの引数にnewを渡して、自動的にアクションを作成する。

リスト 5.38: Usersコントローラの生成 (newアクションを追加)
$ rails generate controller Users new
      create  app/controllers/users_controller.rb
       route  get 'users/new'
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  test_unit
      create    test/controllers/users_controller_test.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.coffee
      invoke    scss
      create      app/assets/stylesheets/users.scss

newアクションを持つUsersコントローラと、スタブのユーザービューを作成します。このとき、新しいUserページ用の小さなテストも生成されていて、この時点ではパスするはずです。

newアクションを持つ最初のUsersコントローラ

app/controllers/users_controller.rb
class UsersController < ApplicationController

  def new
  end
end
リスト 5.40: Users用の最初のnewアクション
app/views/users/new.html.erb
<h1>Users#new</h1>
<p>Find me in app/views/users/new.html.erb</p>
リスト 5.41: Userページ用の最初のテスト green
test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get users_new_url
    assert_response :success
  end
end

この時点では、テストは greenになっているはずです。

演習

1.users_new_urlではなくsignup_pathを使えるようにしてみてください。

require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "should get new" do
    get singup_path
    assert_response :success
  end
end

greenになるよう修正します。
ユーザー登録用URL
コードにより、新規ユーザー用の動作するページが/users/new にできました。ここで表 5.1を思い出していただきたいのですが、URLは/users/newではなく表のとおりに/signupにしたいと思います。例に従い、ユーザー登録URL用にget '/signup'のルートを追加します.

ユーザー登録ページのルート red
config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
  get  '/help',    to: 'static_pages#help'
  get  '/about',   to: 'static_pages#about'
  get  '/contact', to: 'static_pages#contact'
  get  '/signup',  to: 'users#new'
end
リスト 5.44: Usersコントローラのテストで名前付きルートを使うようにする green
test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get signup_path
    assert_response :success
  end
end

次に、新しく定義された名前付きルートを使って、Homeページのボタンに適切なリンクを追加します。他のルートと同様、get ’/signup’と記述したことでsignup_pathという名前付きルートができ、それをリスト 5.45で使います。なお、signupページへのテストは演習に回すことにします (5.3.2.1)。

リスト 5.45: ボタンにユーザー登録ページへのリンクを追加する
app/views/static_pages/home.html.erb
<div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails logo"),
            'http://rubyonrails.org/' %>
最初のユーザー登録ページ (スタブ)
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>This will be a signup page for new users.</p>

とりあえず足早に行きたかったので今日はここらへんにしときます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

国会会議録検索システム 検索APIをRubyで使う

国会の会議の議事録を検索するAPIがある。昔は使い勝手が悪かったようだが、今アクセスしてみたところ、以前指摘を受けていた点は修正されていて、使いやすくなっている。

会議単位簡易出力の例

会議の一覧が返ってくる。

require 'open-uri'
require 'json'

def to_query(hash)
  hash.map { |k, v| "#{k}=#{v}" }.join('&')
end

params = {
    from: '2020-01-20',
    until: '2020-01-20',
    maximumRecords: 5,
    startRecord: 1,
    recordPacking: 'json'
}

endpoint = 'https://kokkai.ndl.go.jp/api/meeting_list?'
meetings = []
start = 1

10.times do |loop|
  params['startRecord'] = start
  url = endpoint + to_query(params)
  puts "Fetch #{url}"
  res = JSON.parse(URI.open(url).read)

  if res['meetingRecord'].is_a?(Array)
    meetings.concat(res['meetingRecord'])
  end

  if meetings.size >= res['numberOfRecords']
    break
  end

  start += 5
end

meetings.each do |meeting|
  puts "第#{meeting['session']}回国会 #{meeting['nameOfHouse']} #{meeting['nameOfMeeting']} #{meeting['issue']} #{meeting['date']}"
end
第201回国会 衆議院 消費者問題に関する特別委員会 第1号 2020-01-20
第201回国会 衆議院 政治倫理の確立及び公職選挙法改正に関する特別委員会 第1号 2020-01-20
第201回国会 衆議院 地方創生に関する特別委員会 第1号 2020-01-20
第201回国会 衆議院 東日本大震災復興特別委員会 第1号 2020-01-20
第201回国会 衆議院 北朝鮮による拉致問題等に関する特別委員会 第1号 2020-01-20
第201回国会 衆議院 災害対策特別委員会 第1号 2020-01-20
第201回国会 衆議院 沖縄及び北方問題に関する特別委員会 第1号 2020-01-20
第201回国会 衆議院 科学技術・イノベーション推進特別委員会 第1号 2020-01-20
第201回国会 衆議院 議院運営委員会 第1号 2020-01-20
第201回国会 衆議院 原子力問題調査特別委員会 第1号 2020-01-20
第201回国会 参議院 沖縄及び北方問題に関する特別委員会 第1号 2020-01-20
第201回国会 参議院 議院運営委員会 第1号 2020-01-20
第201回国会 参議院 災害対策特別委員会 第1号 2020-01-20
第201回国会 参議院 政治倫理の確立及び選挙制度に関する特別委員会 第1号 2020-01-20
第201回国会 参議院 政府開発援助等に関する特別委員会 第1号 2020-01-20
第201回国会 参議院 政府開発援助等に関する特別委員会 第1号 2020-01-20
第201回国会 参議院 東日本大震災復興特別委員会 第1号 2020-01-20
第201回国会 参議院 北朝鮮による拉致問題等に関する特別委員会 第1号 2020-01-20
第201回国会 衆議院 本会議 第1号 2020-01-20
第201回国会 参議院 本会議 第1号 2020-01-20

JSONのレスポンスは下記のようになる。

[{
  "issueID": "120104536X00120200120",
  "imageKind": "会議録",
  "searchObject": 0,
  "session": 201,
  "nameOfHouse": "衆議院",
  "nameOfMeeting": "消費者問題に関する特別委員会",
  "issue": "第1号",
  "date": "2020-01-20",
  "closing": null,
  "speechRecord": [
    {
      "speechID": "120104536X00120200120_000",
      "speechOrder": 0,
      "speaker": "会議録情報",
      "speechURL": "https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=0&single"
    }
  ],
  "meetingURL": "https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120",
  "pdfURL": "https://kokkai.ndl.go.jp/#/detailPDF?minId=120104536X00120200120"
}]

会議単位出力の例

会議単位簡易出力のレスポンスを利用することで、各会議の詳細を取得することができる。

meetings.each do |meeting|
  params = {
      sessionFrom: meeting['session'],
      nameOfHouse: CGI.escape(meeting['nameOfHouse']),
      nameOfMeeting: CGI.escape(meeting['nameOfMeeting']),
      issueFrom: meeting['issue'].match(/(\d+)/)[1],
      maximumRecords: 5,
      startRecord: 1,
      recordPacking: 'json'
  }

  url = 'https://kokkai.ndl.go.jp/api/meeting?' + to_query(params)
  puts "Fetch #{url}"
  res = JSON.parse(URI.open(url).read)

  res['meetingRecord'].each do |meeting_detail|
    puts "第#{meeting['session']}回国会 #{meeting['nameOfHouse']} #{meeting['nameOfMeeting']} #{meeting['issue']} #{meeting['date']}"
    meeting_detail['speechRecord'].each do |speech|
      text = speech['speech'].slice(0, 100).gsub(/[\s ]+/, ' ')
      puts "    #{speech['speechOrder']} #{speech['speaker']} group=#{speech['speakerGroup']} position=#{speech['speakerPosition']} role=#{speech['speakerRole']} text=#{text} #{speech['speechURL']}"
    end
  end

  sleep 5
end
第201回国会 衆議院 消費者問題に関する特別委員会 第1号 2020-01-20
    0 会議録情報   本特別委員会は令和二年一月二十日(月曜日)議院において、消費者の利益の擁護及び増進等に関する総合的な対策を樹立するため設置することに決した。 一月二十日 本特別委員は議長の指名で、次のとおり選任 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=0&single
    1 冨岡勉   ○冨岡委員 これより会議を開きます。 衆議院規則第百一条第四項の規定によりまして、委員長が選任されるまで、私が委員長の職務を行います。 これより委員長の互選を行います。 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=1&single
    2 青山大人   ○青山(大)委員 動議を提出します。 委員長の互選は、投票によらないで、土屋品子君を委員長に推薦いたします。 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=2&single
    3 冨岡勉   ○冨岡委員 ただいまの青山大人君の動議に御異議ありませんか。 〔「異議なし」と呼ぶ者あり〕 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=3&single
    4 冨岡勉   ○冨岡委員 御異議なしと認めます。よって、土屋品子君が委員長に御当選になりました。 土屋品子君に本席を譲ります。 〔土屋委員長、委員長席に着く〕 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=4&single
    5 土屋品子   ○土屋委員長 この際、一言御挨拶申し上げます。 ただいま委員各位の御推挙によりまして、今国会も本特別委員会の委員長の重責を担うこととなりました。 委員各位の御指導と御協力を賜りまして、引き続き https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=5&single
    6 土屋品子   ○土屋委員長 これより理事の互選を行います。 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=6&single
    7 青山大人   ○青山(大)委員 動議を提出します。 理事は、その数を八名とし、委員長において指名されることを望みます。 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=7&single
    8 土屋品子   ○土屋委員長 ただいまの青山大人君の動議に御異議ありませんか。 〔「異議なし」と呼ぶ者あり〕 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=8&single
    9 土屋品子   ○土屋委員長 御異議なしと認めます。よって、委員長は、理事に 穴見 陽一君 勝俣 孝明君 武村 展英君 冨岡 勉君 永岡 桂子君 青山 大 https://kokkai.ndl.go.jp/#/detail?minId=120104536X00120200120&spkNum=9&single

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

もっとも使われているgem 1-20位の日本在住コントリビューターは誰なのか調べてみた

Libraries.io Open Dataから もっとも利用されているgem の上位20位を取得し、その中で日本在住のコントリビューターが何人いるか調べてみた。

もっとも利用されているgemの上位20位までは下記の通り。

+------------------+--------------------------+------------------------+-------------------------------+----------------------------------------+-----------------------------------------------+
| left(`Name`, 50) | Dependent Projects Count | Repository Stars Count | Repository Contributors Count | left(`Repository Name with Owner`, 50) | left(`Repository URL`, 50)                    |
+------------------+--------------------------+------------------------+-------------------------------+----------------------------------------+-----------------------------------------------+
| rake             | 74892                    | 1627                   | 164                           | ruby/rake                              | https://github.com/ruby/rake                  |
| bundler          | 67413                    | 4762                   | 628                           | bundler/bundler                        | https://github.com/bundler/bundler            |
| rspec            | 55095                    | 2559                   | 21                            | rspec/rspec                            | https://github.com/rspec/rspec                |
| pry              | 12848                    | 5426                   | 157                           | pry/pry                                | https://github.com/pry/pry                    |
| rails            | 12290                    | 44743                  | 2647                          | rails/rails                            | https://github.com/rails/rails                |
| activesupport    | 11998                    | 44743                  | 2647                          | rails/rails                            | https://github.com/rails/rails                |
| minitest         | 11740                    | 2703                   | 2                             | seattlerb/minitest                     | https://github.com/seattlerb/minitest         |
| simplecov        | 10864                    | 3883                   | 162                           | colszowka/simplecov                    | https://github.com/colszowka/simplecov        |
| sqlite3          | 8694                     | 526                    | 55                            | sparklemotion/sqlite3-ruby             | https://github.com/sparklemotion/sqlite3-ruby |
| rubocop          | 8480                     | 10493                  | 601                           | rubocop-hq/rubocop                     | https://github.com/rubocop-hq/rubocop         |
| json             | 7829                     | 548                    | 65                            | flori/json                             | https://github.com/flori/json                 |
| nokogiri         | 7452                     | 5328                   | 168                           | sparklemotion/nokogiri                 | https://github.com/sparklemotion/nokogiri     |
| webmock          | 7166                     | 3238                   | 196                           | bblimke/webmock                        | https://github.com/bblimke/webmock            |
| yard             | 6601                     | 1542                   | 117                           | lsegal/yard                            | https://github.com/lsegal/yard                |
| jeweler          | 6501                     | 1482                   | 73                            | technicalpickles/jeweler               | https://github.com/technicalpickles/jeweler   |
| thor             | 5845                     | 4434                   | 173                           | erikhuda/thor                          | https://github.com/erikhuda/thor              |
| activerecord     | 5503                     | 44743                  | 2647                          | rails/rails                            | https://github.com/rails/rails                |
| rdoc             | 4971                     | 568                    | 78                            | ruby/rdoc                              | https://github.com/ruby/rdoc                  |
| rspec-rails      | 4499                     | 4152                   | 284                           | rspec/rspec-rails                      | https://github.com/rspec/rspec-rails          |
| guard-rspec      | 4428                     | 1171                   | 91                            | guard/guard-rspec                      | https://github.com/guard/guard-rspec          |
+------------------+--------------------------+------------------------+-------------------------------+----------------------------------------+-----------------------------------------------+

注意点

GitHub APIの仕様上、コントリビューターの人数がとても多いリポジトリについては全員分は取得できないようです。ただし、コントリビューター数の降順でソートされているため、上位の貢献者は正しく集計できています。

rake

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

bundler

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

rspec (rspec/rspec-core)

※rspecはさらに分割されているため、代表してrspec-coreを集計しました。

rspec-coreのコントリビューター 274人のうち、日本在住の方は16名でした。そのうち上位5名は下記の通りです。

yujinakayama (Yuji Nakayama) 55 contributions

moro (MOROHASHI Kyosuke) 5 contributions

JuanitoFatas (Juanito Fatas) 2 contributions

walf443 (Keiji, Yoshimi) 2 contributions

yui-knk (Yuichiro Kaneko) 2 contributions

pry (pry/pry)

pryのコントリビューター 158人のうち、日本在住の方は8名でした。そのうち上位5名は下記の通りです。

yui-knk (Yuichiro Kaneko) 62 contributions

amatsuda (Akira Matsuda) 3 contributions

tricknotes (Ryunosuke Sato) 2 contributions

mtsmfm (Fumiaki MATSUSHIMA) 1 contributions

eagletmt (Kohei Suzuki) 1 contributions

rails

多すぎて時間がかかるためスキップ。

activesupport

多すぎて時間がかかるためスキップ。

minitest (seattlerb/minitest)

メインコミッタがコードを書き直してコミットしており、正確なコントリビューター数が分からないためスキップ。

simplecov (colszowka/simplecov)

simplecovのコントリビューター 167人のうち、日本在住の方は18名でした。そのうち上位5名は下記の通りです。

amatsuda (Akira Matsuda) 46 contributions

yui-knk (Yuichiro Kaneko) 4 contributions

ryu39 (ryu39) 3 contributions

hanazuki (Kasumi Hanazuki) 2 contributions

yujinakayama (Yuji Nakayama) 2 contributions

sqlite3 (sparklemotion/sqlite3-ruby)

sqlite3のコントリビューター 54人のうち、日本在住の方は4名でした。

gazayas (Gabriel Zayas) 14 contributions

nobu (Nobuyoshi Nakada) 4 contributions

yahonda (Yasuo Honda) 2 contributions

kamipo (Ryuta Kamizono) 1 contributions

rubocop (rubocop-hq/rubocop)

rubocopのコントリビューター 416人のうち、日本在住の方は30名でした。そのうち上位5名は下記の通りです。

koic (Koichi ITO) 693 contributions

yujinakayama (Yuji Nakayama) 363 contributions

pocke (Masataka Pocke Kuwabara) 271 contributions

hoshinotsuyoshi (hoshino tsuyoshi) 39 contributions

wata727 (Kazuma Watanabe) 28 contributions

json

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

nokogiri (sparklemotion/nokogiri)

nokogiriのコントリビューター 167人のうち、日本在住の方は16名でした。そのうち上位5名は下記の通りです。

knu (Akinori MUSHA) 253 contributions

nobu (Nobuyoshi Nakada) 25 contributions

kou (Sutou Kouhei) 4 contributions

iwadon (IWATSUKI Hiroyuki) 3 contributions

eitoball (Eito Katagiri) 2 contributions

webmock (bblimke/webmock)

webmockのコントリビューター 189人のうち、日本在住の方は9名でした。そのうち上位5名は下記の通りです。

koic (Koichi ITO) 7 contributions

hsbt (Hiroshi SHIBATA) 4 contributions

uiur (Kazato Sugimoto) 3 contributions

taiki45 (Taiki Ono) 3 contributions

rochefort (rochefort) 3 contributions

yard (lsegal/yard)

yardのコントリビューター 112人のうち、日本在住の方は13名でした。そのうち上位5名は下記の通りです。

kou (Sutou Kouhei) 40 contributions

amatsuda (Akira Matsuda) 12 contributions

koic (Koichi ITO) 2 contributions

tomoasleep (Tomoya Chiba) 2 contributions

toshimaru (Toshimaru) 2 contributions

jeweler (technicalpickles/jeweler)

jewelerのコントリビューター 67人のうち、日本在住の方は2名でした。

muratayusuke (Yusuke Murata) 35 contributions

amatsuda (Akira Matsuda) 1 contributions

thor (erikhuda/thor)

thorのコントリビューター 165人のうち、日本在住の方は14名でした。そのうち上位5名は下記の通りです。

takkanm (Mitsutaka Mimura) 4 contributions

akm (Takeshi Akima) 4 contributions

koic (Koichi ITO) 2 contributions

hsbt (Hiroshi SHIBATA) 2 contributions

amatsuda (Akira Matsuda) 1 contributions

activerecord

多すぎて時間がかかるためスキップ。

rdoc

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

rspec-rails (rspec/rspec-rails)

rspec-railsのコントリビューター 290人のうち、日本在住の方は20名でした。そのうち上位5名は下記の通りです。

yujinakayama (Yuji Nakayama) 25 contributions

takashi (Takashi Nakagawa) 4 contributions

okuramasafumi (OKURA Masafumi) 3 contributions

hasimo (Tohru Hashimoto ) 2 contributions

ta1kt0me () 2 contributions

guard-rspec (guard/guard-rspec)

guard-rspecのコントリビューター 83人のうち、日本在住の方は7名でした。そのうち上位5名は下記の通りです。

yujinakayama (Yuji Nakayama) 3 contributions

chocoby (Kenta Okamoto) 2 contributions

pocke (Masataka Pocke Kuwabara) 1 contributions

odaillyjp (Suguru Odai) 1 contributions

y310 (Yusuke Mito) 1 contributions

付録

もっとも使われているgemの集計に使ったSQL

select left(`Name`, 50), `Dependent Projects Count`, `Repository Stars Count`, `Repository Contributors Count`, left(`Repository Name with Owner`, 50), left(`Repository URL`, 50)
from projects
order by cast(`Dependent Projects Count` as unsigned) desc
limit 20;

集計に使ったRubyコード

require 'octokit'

regexp = /日本|japan|北海道|hokkaido|青森|aomori|岩手|iwate|宮城|miyagi|秋田|akita|山形|yamagata|福島|fukushima|茨城|ibaraki|栃木|tochigi|群馬|gunma|埼玉|saitama|千葉|chiba|東京|tokyo|神奈川|kanagawa|新潟|niigata|富山|toyama|石川|ishikawa|福井|fukui|山梨|yamanashi|長野|nagano|岐阜|gifu|静岡|shizuoka|愛知|aichi|三重|mie|滋賀|shiga|京都|kyoto|大阪|osaka|兵庫|hyogo|奈良|nara|和歌山|wakayama|鳥取|tottori|島根|shimane|岡山|okayama|広島|hiroshima|山口|yamaguchi|徳島|tokushima|香川|kagawa|愛媛|ehime|高知|kochi|福岡|fukuoka|佐賀|saga|長崎|nagasaki|熊本|kumamoto|大分|oita|宮崎|miyazaki|鹿児島|kagoshima|沖縄|okinawa/
client = Octokit::Client.new(access_token: 'TOKEN')

repo = 'pry/pry'
members = client.contributors(repo); members.size
users = members.map { |member| client.user member.login }; users.size
japanese = users.select do |user|
  user.location.to_s.downcase.match? regexp
end; japanese.size

japanese.take(5).map do |user|
  member = members.find { |member| member.login == user.login }
  <<~MD
    **#{user.login} (#{user.name})** #{member.contributions} contributions
    <img src="#{user.avatar_url}" width="50">
  MD
end.each { |str| puts str };
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

もっとも使われているgem 1-20位の日本在住コントリビューターを集計してみた

Libraries.io Open Dataから もっとも利用されているgem の上位20位を取得し、その中で日本在住のコントリビューターが何人いるか調べてみた。

もっとも利用されているgemの上位20位までは下記の通り。

+------------------+--------------------------+------------------------+-------------------------------+----------------------------------------+-----------------------------------------------+
| left(`Name`, 50) | Dependent Projects Count | Repository Stars Count | Repository Contributors Count | left(`Repository Name with Owner`, 50) | left(`Repository URL`, 50)                    |
+------------------+--------------------------+------------------------+-------------------------------+----------------------------------------+-----------------------------------------------+
| rake             | 74892                    | 1627                   | 164                           | ruby/rake                              | https://github.com/ruby/rake                  |
| bundler          | 67413                    | 4762                   | 628                           | bundler/bundler                        | https://github.com/bundler/bundler            |
| rspec            | 55095                    | 2559                   | 21                            | rspec/rspec                            | https://github.com/rspec/rspec                |
| pry              | 12848                    | 5426                   | 157                           | pry/pry                                | https://github.com/pry/pry                    |
| rails            | 12290                    | 44743                  | 2647                          | rails/rails                            | https://github.com/rails/rails                |
| activesupport    | 11998                    | 44743                  | 2647                          | rails/rails                            | https://github.com/rails/rails                |
| minitest         | 11740                    | 2703                   | 2                             | seattlerb/minitest                     | https://github.com/seattlerb/minitest         |
| simplecov        | 10864                    | 3883                   | 162                           | colszowka/simplecov                    | https://github.com/colszowka/simplecov        |
| sqlite3          | 8694                     | 526                    | 55                            | sparklemotion/sqlite3-ruby             | https://github.com/sparklemotion/sqlite3-ruby |
| rubocop          | 8480                     | 10493                  | 601                           | rubocop-hq/rubocop                     | https://github.com/rubocop-hq/rubocop         |
| json             | 7829                     | 548                    | 65                            | flori/json                             | https://github.com/flori/json                 |
| nokogiri         | 7452                     | 5328                   | 168                           | sparklemotion/nokogiri                 | https://github.com/sparklemotion/nokogiri     |
| webmock          | 7166                     | 3238                   | 196                           | bblimke/webmock                        | https://github.com/bblimke/webmock            |
| yard             | 6601                     | 1542                   | 117                           | lsegal/yard                            | https://github.com/lsegal/yard                |
| jeweler          | 6501                     | 1482                   | 73                            | technicalpickles/jeweler               | https://github.com/technicalpickles/jeweler   |
| thor             | 5845                     | 4434                   | 173                           | erikhuda/thor                          | https://github.com/erikhuda/thor              |
| activerecord     | 5503                     | 44743                  | 2647                          | rails/rails                            | https://github.com/rails/rails                |
| rdoc             | 4971                     | 568                    | 78                            | ruby/rdoc                              | https://github.com/ruby/rdoc                  |
| rspec-rails      | 4499                     | 4152                   | 284                           | rspec/rspec-rails                      | https://github.com/rspec/rspec-rails          |
| guard-rspec      | 4428                     | 1171                   | 91                            | guard/guard-rspec                      | https://github.com/guard/guard-rspec          |
+------------------+--------------------------+------------------------+-------------------------------+----------------------------------------+-----------------------------------------------+

注意点

GitHub APIの仕様上、コントリビューターの人数がとても多いリポジトリについては全員分は取得できないようです。ただし、コントリビューター数の降順でソートされているため、上位の貢献者は正しく集計できています。

rake

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

bundler

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

rspec (rspec/rspec-core)

※rspecはさらに分割されているため、代表してrspec-coreを集計しました。

rspec-coreのコントリビューター 274人のうち、日本在住の方は16名でした。そのうち上位5名は下記の通りです。

yujinakayama (Yuji Nakayama) 55 contributions

moro (MOROHASHI Kyosuke) 5 contributions

JuanitoFatas (Juanito Fatas) 2 contributions

walf443 (Keiji, Yoshimi) 2 contributions

yui-knk (Yuichiro Kaneko) 2 contributions

pry (pry/pry)

pryのコントリビューター 158人のうち、日本在住の方は8名でした。そのうち上位5名は下記の通りです。

yui-knk (Yuichiro Kaneko) 62 contributions

amatsuda (Akira Matsuda) 3 contributions

tricknotes (Ryunosuke Sato) 2 contributions

mtsmfm (Fumiaki MATSUSHIMA) 1 contributions

eagletmt (Kohei Suzuki) 1 contributions

rails

多すぎて時間がかかるためスキップ。

activesupport

多すぎて時間がかかるためスキップ。

minitest (seattlerb/minitest)

メインコミッタがコードを書き直してコミットしており、正確なコントリビューター数が分からないためスキップ。

simplecov (colszowka/simplecov)

simplecovのコントリビューター 167人のうち、日本在住の方は18名でした。そのうち上位5名は下記の通りです。

amatsuda (Akira Matsuda) 46 contributions

yui-knk (Yuichiro Kaneko) 4 contributions

ryu39 (ryu39) 3 contributions

hanazuki (Kasumi Hanazuki) 2 contributions

yujinakayama (Yuji Nakayama) 2 contributions

sqlite3 (sparklemotion/sqlite3-ruby)

sqlite3のコントリビューター 54人のうち、日本在住の方は4名でした。

gazayas (Gabriel Zayas) 14 contributions

nobu (Nobuyoshi Nakada) 4 contributions

yahonda (Yasuo Honda) 2 contributions

kamipo (Ryuta Kamizono) 1 contributions

rubocop (rubocop-hq/rubocop)

rubocopのコントリビューター 416人のうち、日本在住の方は30名でした。そのうち上位5名は下記の通りです。

koic (Koichi ITO) 693 contributions

yujinakayama (Yuji Nakayama) 363 contributions

pocke (Masataka Pocke Kuwabara) 271 contributions

hoshinotsuyoshi (hoshino tsuyoshi) 39 contributions

wata727 (Kazuma Watanabe) 28 contributions

json

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

nokogiri (sparklemotion/nokogiri)

nokogiriのコントリビューター 167人のうち、日本在住の方は16名でした。そのうち上位5名は下記の通りです。

knu (Akinori MUSHA) 253 contributions

nobu (Nobuyoshi Nakada) 25 contributions

kou (Sutou Kouhei) 4 contributions

iwadon (IWATSUKI Hiroyuki) 3 contributions

eitoball (Eito Katagiri) 2 contributions

webmock (bblimke/webmock)

webmockのコントリビューター 189人のうち、日本在住の方は9名でした。そのうち上位5名は下記の通りです。

koic (Koichi ITO) 7 contributions

hsbt (Hiroshi SHIBATA) 4 contributions

uiur (Kazato Sugimoto) 3 contributions

taiki45 (Taiki Ono) 3 contributions

rochefort (rochefort) 3 contributions

yard (lsegal/yard)

yardのコントリビューター 112人のうち、日本在住の方は13名でした。そのうち上位5名は下記の通りです。

kou (Sutou Kouhei) 40 contributions

amatsuda (Akira Matsuda) 12 contributions

koic (Koichi ITO) 2 contributions

tomoasleep (Tomoya Chiba) 2 contributions

toshimaru (Toshimaru) 2 contributions

jeweler (technicalpickles/jeweler)

jewelerのコントリビューター 67人のうち、日本在住の方は2名でした。

muratayusuke (Yusuke Murata) 35 contributions

amatsuda (Akira Matsuda) 1 contributions

thor (erikhuda/thor)

thorのコントリビューター 165人のうち、日本在住の方は14名でした。そのうち上位5名は下記の通りです。

takkanm (Mitsutaka Mimura) 4 contributions

akm (Takeshi Akima) 4 contributions

koic (Koichi ITO) 2 contributions

hsbt (Hiroshi SHIBATA) 2 contributions

amatsuda (Akira Matsuda) 1 contributions

activerecord

多すぎて時間がかかるためスキップ。

rdoc

rubygemsに含まれており、正確なコントリビューター数が分からないためスキップ。

rspec-rails (rspec/rspec-rails)

rspec-railsのコントリビューター 290人のうち、日本在住の方は20名でした。そのうち上位5名は下記の通りです。

yujinakayama (Yuji Nakayama) 25 contributions

takashi (Takashi Nakagawa) 4 contributions

okuramasafumi (OKURA Masafumi) 3 contributions

hasimo (Tohru Hashimoto ) 2 contributions

ta1kt0me () 2 contributions

guard-rspec (guard/guard-rspec)

guard-rspecのコントリビューター 83人のうち、日本在住の方は7名でした。そのうち上位5名は下記の通りです。

yujinakayama (Yuji Nakayama) 3 contributions

chocoby (Kenta Okamoto) 2 contributions

pocke (Masataka Pocke Kuwabara) 1 contributions

odaillyjp (Suguru Odai) 1 contributions

y310 (Yusuke Mito) 1 contributions

付録

もっとも使われているgemの集計に使ったSQL

select left(`Name`, 50), `Dependent Projects Count`, `Repository Stars Count`, `Repository Contributors Count`, left(`Repository Name with Owner`, 50), left(`Repository URL`, 50)
from projects
order by cast(`Dependent Projects Count` as unsigned) desc
limit 20;

集計に使ったRubyコード

require 'octokit'

regexp = /日本|japan|北海道|hokkaido|青森|aomori|岩手|iwate|宮城|miyagi|秋田|akita|山形|yamagata|福島|fukushima|茨城|ibaraki|栃木|tochigi|群馬|gunma|埼玉|saitama|千葉|chiba|東京|tokyo|神奈川|kanagawa|新潟|niigata|富山|toyama|石川|ishikawa|福井|fukui|山梨|yamanashi|長野|nagano|岐阜|gifu|静岡|shizuoka|愛知|aichi|三重|mie|滋賀|shiga|京都|kyoto|大阪|osaka|兵庫|hyogo|奈良|nara|和歌山|wakayama|鳥取|tottori|島根|shimane|岡山|okayama|広島|hiroshima|山口|yamaguchi|徳島|tokushima|香川|kagawa|愛媛|ehime|高知|kochi|福岡|fukuoka|佐賀|saga|長崎|nagasaki|熊本|kumamoto|大分|oita|宮崎|miyazaki|鹿児島|kagoshima|沖縄|okinawa/
client = Octokit::Client.new(access_token: 'TOKEN')

repo = 'pry/pry'
members = client.contributors(repo); members.size
users = members.map { |member| client.user member.login }; users.size
japanese = users.select do |user|
  user.location.to_s.downcase.match? regexp
end; japanese.size

japanese.take(5).map do |user|
  member = members.find { |member| member.login == user.login }
  <<~MD
    **#{user.login} (#{user.name})** #{member.contributions} contributions
    <img src="#{user.avatar_url}" width="50">
  MD
end.each { |str| puts str };
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHubリポジトリからコントリビューターの一覧を取得する

RubyでGitHub APIにアクセスするには octokit gemを使う。

gem 'octokit'

あるリポジトリのコントリビューターを取得する。

require 'octokit'
client.auto_paginate = true
members = client.contributors('rails/rails')
users = users = members.map{|member| client.user member.login }

rails/railsのコントリビューターはブラウザで見ると3988人いるが、APIで取得できたのは380人までだった。

members.size
# => 380

取得したコントリビューターから、ロケーションが日本のどこかになっているユーザーを取得する。

regexp = /日本|japan|北海道|hokkaido|青森|aomori|岩手|iwate|宮城|miyagi|秋田|akita|山形|yamagata|福島|fukushima|茨城|ibaraki|栃木|tochigi|群馬|gunma|埼玉|saitama|千葉|chiba|東京|tokyo|神奈川|kanagawa|新潟|niigata|富山|toyama|石川|ishikawa|福井|fukui|山梨|yamanashi|長野|nagano|岐阜|gifu|静岡|shizuoka|愛知|aichi|三重|mie|滋賀|shiga|京都|kyoto|大阪|osaka|兵庫|hyogo|奈良|nara|和歌山|wakayama|鳥取|tottori|島根|shimane|岡山|okayama|広島|hiroshima|山口|yamaguchi|徳島|tokushima|香川|kagawa|愛媛|ehime|高知|kochi|福岡|fukuoka|佐賀|saga|長崎|nagasaki|熊本|kumamoto|大分|oita|宮崎|miyazaki|鹿児島|kagoshima|沖縄|okinawa/
users.select {|user| user.location.to_s.downcase.match? regexp }

railsのコントリビューターのうち、日本をロケーションに指定しているのは下記の方々でした。
rails開発ありがとうございます。

users.select do |user|
  user.location.to_s.downcase.match? regexp
end.map do |user|
  member = members.find{|member| member.login == user.login }
<<~MD
**#{user.login} (#{user.name})** #{member.contributions} contributions
<img src="#{user.avatar_url}" width="50">
MD
end.each {|str| puts str }

kamipo (Ryuta Kamizono) 2713 contributions

amatsuda (Akira Matsuda) 843 contributions

yui-knk (Yuichiro Kaneko) 234 contributions

kennyj (Toshinori Kajihara) 233 contributions

sikachu (Prem Sichanugrist) 223 contributions

yahonda (Yasuo Honda) 205 contributions

yhirano55 (yhirano55) 152 contributions

JuanitoFatas (Juanito Fatas) 92 contributions

koic (Koichi ITO) 83 contributions

kenta-s (kenta-s) 40 contributions

giraffate (Takayuki Nakata) 34 contributions

mtsmfm (Fumiaki MATSUSHIMA) 30 contributions

soartec-lab (Shodai Suzuki) 27 contributions

tricknotes (Ryunosuke Sato) 25 contributions

shioyama (Chris Salzberg) 21 contributions

take (Takehiro Adachi) 20 contributions

willnet (Shinichi Maeshima) 18 contributions

akihiro17 (Hiroaki Izu) 14 contributions

riseshia (Shia) 10 contributions

ttanimichi (Tsukuru Tanimichi) 10 contributions

ryohashimoto (Ryo Hashimoto) 9 contributions

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ショッピングサイトアプリの購入機能(model)をRSpecとFactory�botで登録テストの構築をしてみた。

はじめに

ショッピングサイトアプリ(疑似フリマアプリ)を作成していて、購入機能を担当することになりました。
「RSpecでmodelのテストコードを作成せよ」とのお達しがありましたので、取り組んでみました。

その結果、associationで「belongs_to」の関係にあるモデルの連携に
ハマりましたので、備忘録もかねて投稿します。

前提

ここでは支払処理が完了した後に購入記録を登録する機能のテストを説明しています。
支払機能(payjplなどのAPI)について触れませんのでご了承ください。

参考記事

【FactoryBot】associationの使い方

開発環境

ruby 2.5.1
rails 5.2.4.1
RSpec 3.9.0
factory_bot 5.1.1
devise 4.7.1

ER図

ショッピングサイトアプリのテーブル構成を次のように定義するとします。

スクリーンショット 2020-03-25 22.19.32.png

一見するとわかりにくいかもしれませんが、
このような考え方になります。

  1. 出品者

    • ユーザー登録をする。(user)
    • 発送元住所の登録をする。(address)
    • 売りたいモノを出品する。 (item)
  2. 購入者

    • ユーザー登録をする。(user)
    • 発送先住所の登録をする。(address)
    • 出品されたモノを購入する。 (trade)

ここで注意しなければならないことは

  • userとaddressは出品者と購入者で必要であること。
  • itemは出品者のみでよいこと。
  • tredeは出品者となるitemと、購入者であるuserとaddressが必要であること。

となります。

他の開発者が作成したfactoryデータコードを流用しようとしましたが、出品者と購入者の2種類を同時に
テストするように想定されていないため、使い回しができませんでした(横着をしてはいけない)。

よって、購入機能専用に出品者と購入者のコードを書くことにしました。

Factorybotデータを作ろう

まずはuserから

spec/factories/users.rb
FactoryBot.define do
  # 出品者用データ factory名は「seller」とします。
  # 「sequence」はspec.rbから呼び出されるたびにカウントアップしてくれる機能です。
  factory :seller, class: User do
    sequence(:nickname)        { |i| "出品者_#{i}"}
    sequence(:email)           { |i| "seller_#{i}@test.com"}
    password                   {"00000000b"}
    last_name                  {"苗字"}
    first_name                 {"名前"}
    last_name_kana             {"ミョウジカナ"}
    first_name_kana            {"ナマエカナ"}
    birthday                   {"20190101"}
    telephone_number           {"1234567890"}
  end

  # 購入者用データ factory名は「buyer」とします。
  factory :buyer, class: User do
    sequence(:nickname)        { |i| "購入者_#{i}"}
    sequence(:email)           { |i| "byuer_#{i}@test.com"}
    password                   {"00000000c"}
    last_name                  {"苗字"}
    first_name                 {"名前"}
    last_name_kana             {"ミョウジ"}
    first_name_kana            {"ナマエ"}
    birthday                   {"20190101"}
    telephone_number           {"1234567890"}
  end
end

続いてarea

spec/factories/areas.rb
FactoryBot.define do
  # 出品者地域 
  factory :seller_area, class: Area do
    name {"北海道"}
  end

  # 購入者地域 
  factory :buyer_area, class: Area do
    name {"東京都"}
  end
end

さらにaddresses

spec/factories/addrsses.rb
FactoryBot.define do
  # 出品者住所
  factory :seller_address, class: Address do

    zip_code         {"1234567"}
    city             {"city_1"}
    number           {"number_1"}
    building         {"building_1"}
    last_name        {"出品"}
    first_name       {"太郎"}
    telephone_number {"03-1234-5678"}

    #出品者のuserとaddressと連携します。
    association :area, factory: :seller_area
    association :user, factory: :seller

  end

  # 購入者住所
  factory :buyer_address, class: Address do

    zip_code         {"3214567"}
    city             {"city_2"}
    number           {"number_2"}
    building         {"building_2"}
    last_name        {"購入"}
    first_name       {"次郎"}
    last_name_kana   {"ジロウ"}
    first_name_kana  {"コウニュウ"}
    telephone_number {"03-1234-5678"}

    #購入者のuserとaddressと連携します。
    association :area, factory: :buyer_area
    association :user, factory: :buyer

  end
end

やっとitems

spec/factories/items.rb
FactoryBot.define do

  # 出品者用データ
  factory :seller_item, class: Item do

    sequence(:title) { |i| "product_#{i}"}
    sequence(:description) { |i| "description_#{i}"}

    price            {1000.000}

    #出品者のaddressと連携します。
    association :address, factory: :selladdress
    #userはaddress内にあるuserと連携します。
    user             {address.user}

  end
end

最後にtrades

spec/factories/trades.rb
FactoryBot.define do
  #購入用データ
  factory :trade do
    status_num  {0}

    #出品者のitemと連携します。
    association :item, factory: :seller_item

    #購入者のaddressと連携します。
    association :address, factory: :buyer_address
    #購入者のuserは購入者のaddress内にあるuserと連携します。
    user        {address.user}
  end

end

RSpecのテストコードを作ろう

tradeのテストコード
たったこれだけ

spec/models/trade_spec.rb
require 'rails_helper'

RSpec.describe Trade, type: :model do
  describe "#create" do
    #associationで関連付けした場合は、buildではなくcreateを使います。
    let(:trade)    { create(:trade) }
    it "is valid trade" do
      #ここでtradeを呼び出すことで、「let(:trade)」が実行されます。
      expect(trade).to be_valid
    end
  end
end

ターミナルでテストを実行すると

こうなりました。

ターミナル
% bundle exec rspec spec/models/trade_spec.rb

Trade
  #create
    is valid trade

Finished in 1.12 seconds (files took 5.19 seconds to load)
1 examples, 0 failures
% 

「is valid trade」と表示されたので、
登録テストが正常終了したことがわかりました。

まとめ

  • 購入機能テスト用のfactoryデータは、出品者と購入者それぞれに適用するように作成する必要があること。
  • 他の開発者が作成したfactoryコードは流用ができないこと。(そもそも購入機能を想定して作られたモノではなかったため)

となりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ショッピングサイトアプリの購入機能テスト(model)をRSpecとFactory�botで構築してみた。

はじめに

ショッピングサイトアプリ(疑似フリマアプリ)を作成していて、購入機能を担当することになりました。
「RSpecでmodelのテストコードを作成せよ」とのお達しがありましたので、取り組んでみました。

その結果、associationで「belongs_to」の関係にあるモデルの連携に
ハマりましたので、備忘録もかねて投稿します。

前提

ここでは支払処理が完了した後に購入記録を登録する機能のテストを説明しています。
支払機能(payjplなどのAPI)について触れませんのでご了承ください。

参考記事

【FactoryBot】associationの使い方

開発環境

ruby 2.5.1
rails 5.2.4.1
RSpec 3.9.0
factory_bot 5.1.1
devise 4.7.1

ER図

ショッピングサイトアプリのテーブル構成を次のように定義し、
rails db:migrateコマンドじ実行とmodelの構築が済んだものとします。

スクリーンショット 2020-03-25 22.19.32.png

一見するとわかりにくいかもしれませんが、
このような考え方になります。

  1. 出品者

    • ユーザーを登録する。(user)
    • 発送元住所を登録する。(address)
    • 売りたいモノを出品する。 (item)
  2. 購入者

    • ユーザーを登録する。(user)
    • 発送先住所を登録する。(address)
    • 出品されたモノを購入する。 (trade)

ここで注意しなければならないことは

  • userとaddressは出品者と購入者それぞれに必要であること。
  • itemは出品者のみでがuserとaddressに従属すること。
  • tredeは出品者のitemと、購入者のuserとaddressが必要であること。

となります。

他の開発者が作成したfactoryデータコードを流用しようとしましたが、出品者と購入者の2種類を同時に
テストするように想定されていないため、使い回しができませんでした(横着をしてはいけない)。

よって、購入機能専用に出品者と購入者のコードを書くことにしました。

Factorybotでテストデータを作ろう

まずはusers

spec/factories/users.rb
FactoryBot.define do
  # 出品者用データ factory名は「seller」とします。
  # 「sequence」はspec.rbから呼び出されるたびにカウントアップしてくれる機能です。
  factory :seller, class: User do
    sequence(:nickname)        { |i| "出品者_#{i}"}
    sequence(:email)           { |i| "seller_#{i}@test.com"}
    password                   {"00000000b"}
    last_name                  {"苗字"}
    first_name                 {"名前"}
    last_name_kana             {"ミョウジカナ"}
    first_name_kana            {"ナマエカナ"}
    birthday                   {"20190101"}
    telephone_number           {"1234567890"}
  end

  # 購入者用データ factory名は「buyer」とします。
  factory :buyer, class: User do
    sequence(:nickname)        { |i| "購入者_#{i}"}
    sequence(:email)           { |i| "byuer_#{i}@test.com"}
    password                   {"00000000c"}
    last_name                  {"苗字"}
    first_name                 {"名前"}
    last_name_kana             {"ミョウジ"}
    first_name_kana            {"ナマエ"}
    birthday                   {"20190101"}
    telephone_number           {"1234567890"}
  end
end

続いてareas

spec/factories/areas.rb
FactoryBot.define do
  # 出品者地域 
  factory :seller_area, class: Area do
    name {"北海道"}
  end

  # 購入者地域 
  factory :buyer_area, class: Area do
    name {"東京都"}
  end
end

さらにaddresses

spec/factories/addrsses.rb
FactoryBot.define do
  # 出品者住所
  factory :seller_address, class: Address do

    zip_code         {"1234567"}
    city             {"city_1"}
    number           {"number_1"}
    building         {"building_1"}
    last_name        {"出品"}
    first_name       {"太郎"}
    telephone_number {"03-1234-5678"}

    #出品者のareaとuserを連携します。
    association :area, factory: :seller_area
    association :user, factory: :seller

  end

  # 購入者住所
  factory :buyer_address, class: Address do

    zip_code         {"3214567"}
    city             {"city_2"}
    number           {"number_2"}
    building         {"building_2"}
    last_name        {"購入"}
    first_name       {"次郎"}
    last_name_kana   {"ジロウ"}
    first_name_kana  {"コウニュウ"}
    telephone_number {"03-1234-5678"}

    #購入者のareaとuserを連携します。
    association :area, factory: :buyer_area
    association :user, factory: :buyer

  end
end

やっとitems

spec/factories/items.rb
FactoryBot.define do

  # 出品者用データ
  factory :seller_item, class: Item do

    sequence(:title) { |i| "product_#{i}"}
    sequence(:description) { |i| "description_#{i}"}

    price            {1000.000}

    #出品者のaddressと連携します。
    association :address, factory: :selladdress
    #出品者のaddress内にあるuserと連携します。
    user             {address.user}

  end
end

最後にtrades

spec/factories/trades.rb
FactoryBot.define do
  #購入用データ
  factory :trade do
    status_num  {0}

    #出品者のitemと連携します。
    association :item, factory: :seller_item

    #購入者のaddressと連携します。
    association :address, factory: :buyer_address
    #購入者のaddress内にあるuserと連携します。
    user        {address.user}
  end

end

RSpecでテストコードを作ろう

tradeのテストコードはたったこれだけ

spec/models/trade_spec.rb
require 'rails_helper'

RSpec.describe Trade, type: :model do
  describe "#create" do
    #factoryデータを呼び出すときに、associationで関連付けした場合は、createを使います。
    #buildを使うとテストそのものが失敗します。
    let(:trade)    { create(:trade) }
    it "is valid trade" do
      #ここでtradeを呼び出すことで、「let(:trade)」が実行されます。
      expect(trade).to be_valid
    end
  end
end

ターミナルでテストを実行すると

こうなりました。

terminal
% bundle exec rspec spec/models/trade_spec.rb

Trade
  #create
    is valid trade

Finished in 1.12 seconds (files took 5.19 seconds to load)
1 examples, 0 failures
% 

「is valid trade」と表示されたので、
登録テストが正常終了したことがわかりました。

まとめ

  • 購入機能テスト用のfactoryデータは、出品者と購入者それぞれに必要な範囲で作成しなければならないこと。
  • 他の開発者が作成したfactoryコードは流用しない方が良いこと。(そもそも購入機能を想定して作られたモノではないことがほとんど)

となりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]active_hash

はじめに

現在個人で作成しているアプリで都道府県のデータをテーブルに持たせて使用していたのですが、調べたところ
どうやらactive_hashなる便利なものがあると知ったため、実際にその導入するまでを書きたいと思います。

active_hash

gemをインストールすることで使用ができます。
モデルにハッシュでデータを持たせることで、そのデータをActiveRecordと同じような感覚で使えます。
つまり都道府県など静的データを扱う際、わざわざテーブルを作成する必要がなくなります。
便利ですね。

Gemインストール

Gemfileに以下を記載して,bundle installをします。

Gemfile.
gem 'active_hash'

モデル作成

今回はLetterモデルとPrefectureモデルを作成し、Prefectureモデルに都道府県データを持たせます。
letterモデルには都道府県のデータを保存するカラムprefecture_idを作成します。

rails g model letter prefecture_id:integer  #letterモデル作成

rails db:migrate  #letterテーブルの作成

これでletterモデルは作成できました。
次にPrefectureモデルですが、これはテーブルを持たないためrails g modelで作成するのではなく、自分で作成します。
なおその際、ActiveHash::Baseを継承させます。
モデルを作成したら使用する都道府県のデータをハッシュの形で記述します。
すると以下のようになります

app/models/prefecture.rb
class Prefecture < ActiveHash::Base
  self.data = [
      {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
      {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
      {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
      {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
      {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
      {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
      {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
      {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
      {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
      {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
      {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
      {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
      {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
      {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
      {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
      {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
  ]
end

アソシエーションの定義

prefecture.rbに記述は必要ないのですがletter.rbにのみアソシエーションの定義をします。その際active_hashのbelongs_to_active_hashメソッドを使用して定義をします。そして、active_hashを使用するためextend ActiveHash::Associations::ActiveRecordExtensionsも追記します。すると以下のようになります。

app/models/letter.rb
class Letter < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :prefecture
end

これで使用できるようになります。
Prefectureモデルのテーブルはありませんがテーブルのデータを扱う感覚で以下のように書くことで使用することもできます。

active_hash.html.haml
= f.collection_select :prefecture_id, Prefecture.all, :id, :name

おわり

静的データのためにテーブルをもつ必要がなく、かつハッシュに格納したデータをテーブルのデータのように扱うこともできるという利点はとても魅力的ですよね。
最後まで読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む