やなぎにっき

学んだことの記録

rails newした後に使わないAction Cableを外す

Action Cableを使わない場合はrails new時に-skip-action-cableをするのが一般的ですが、自分の場合はrails newした後にレビューでAction Cableは不要なので外した方がいいとの指摘を貰いました。

rails newした後にAction Cableを外す方法が調べても案外出てこなかったので、削除するべきディレクトリを調査した方法とその結果をメモします。

環境

ruby 3.0.2
Rails 6.1.4.1

調査した方法

-skip-action-cableを付けない場合と付ける場合でそれぞれrails newし、2つのディレクトリを比較してskip-action-cableをつけたアプリにのみに存在するファイルを調べます。

1. -skip-action-cableを付けない場合と付ける場合でそれぞれrails newする

$ rails new app_a -d postgresql --skip-action-mailer

$ rails new app_b -d postgresql --skip-action-mailer --skip-action-cable

2. 2つのアプリを比較する
$ diff -r app_a app_b という単純なdiffコマンド大量に結果が出てくるため差分が把握しにくいです。 GUIでファイルの差分を確認できるXcodeに入ってるFileMergeを使ったり、diffコマンドをgrepして絞ったりして調べました。

削除するディレクトリ/ファイル・コード行

skip-action-cableをつけた場合のみに存在したファイルやコード行を削除します。

ディレクトリ/ファイルの削除

/app/channels
/app/javascript/channels
/config/cable.yml
/test/channels (Minitestの場合)

コード行を削除

/app/javascript/packs/application.js

import "channels"

/config/application.rb

これは削除ではなくてコメントでいいかも

require "action_cable/engine"

/config/environments/production.rb

# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]

Gemfile

# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'

/package.json

"@rails/actioncable": "^6.0.0",

Ruby on RailsでTwitter認証だけのログイン機能を実装する

既にいろんなTwitter認証を実装する記事がありますが、2021年に行われたomniouth2系の仕様変更で詰まったので自分なりにまとめておきます。

環境

Railsのバージョン : Rails 6.1.4.1

Rubyのバージョン : v3.0.2

使用したgem

gem 'dotenv-rails'
gem 'meta-tags'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-twitter'

仕様

  • Twitter認証のみでログインする(devise等は使わない)

参考記事

Railsアプリに簡単なTwitterログインを実装する - Qiita

Ruby on RailsでTwitter認証機能を実装してみる - Reasonable Code

第9章 発展的なログイン機構 - Railsチュートリアル

TwitterAPIの取得

Twitterと連携するためには、TwitterAPIキーとAPIシークレットキーが必要です。

申請方法などはこの記事では省略します。比較的最新の記事を参照することをおすすめします。

2021/9に申請を出したところ、すぐに承認メールがきました。用途や申請内容によって待ち時間が変わってくるようです。

TwitterAPIキーとAPIシークレットキーを設定

APIの認証情報を登録する前に、GitHunにアクセスキーを公開させないために 'dotenv-rails' をインストールします。

github.com

gem 'dotenv-rails'

本番環境でherokuなどを使う場合は、Gemfileはgroup :development, :testの中に入れます。 別途heroku側で環境変数を設定する必要があります。

.env ファイルを作成し、取得した各種キーを設定します。

TWITTER_API_KEY = 'hogehgoe'
TWITTER_API_SECRET = 'fugafuga'

omniauth-twitter導入

omniauth-twitter を導入します。

github.com

gem 'omniauth-twitter'

config/initializers/omniauth.rbを作成し、APIキーとAPIシークレットキーを設定します。

config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET']
end

ログイン画面にリダイレクトできるか確認

ここまで設定したら、Twitterログイン画面に遷移できるか確認をします。

http://localhost:3000/auth/twitter に遷移できることを確認します。 直接URLを叩くか、ビューにリンクを作成して画面上から遷移してください。

<%= link_to 'ログイン', "/auth/twitter", method: :get %>

omniauth のバージョンが2系の場合*1rails serverでサーバーを起動して直接URLを叩いてみるとエラーになり、No route matches [GET] "/auth/twitter" というエラー画面に遷移するはずです。

というのも、OmniAuth1系までは認可画面へリダイレクトする際、デフォルトでGET/POSTどちらも有効でしたが、CSRF脆弱性の対応に伴い2.0からはPOSTのみに変更になったためです。

参考:

github.com

zenn.dev

2.0 のメジャーバージョンのリリースが2021年1月12日にあったため、それ以前の記事ではGETで実装できているようです。そのことに気づかず自分はハマりました……。

この変更点の対応として、Cookpad社が公開しているCSRF脆弱性対応用のGemを追加します。

gem 'omniauth-rails_csrf_protection'`

認可画面URLへのリダイレクトさせるリンクへのアクセス方法をGETからPOSTに変更します。

home#index などのビューに

<%= link_to 'ログイン', "/auth/twitter", method: :post %>

を記載し、画面上から確認します。

画面からログインボタンをクリックすると見たことがあるであろう画面が出てくるはずです。 (ここでまたエラー画面が出る場合は認証キーの設定が間違えているなどの他の原因が考えられます)

f:id:yana_g:20211206154222p:plain

Twitter認証後の処理を実装する

認可画面に遷移できたので、今度は「連携アプリを認証」ボタンを押した後の処理を作成していきます。 認証ボタンを押した後はTwitter側がコールバックURLに遷移します。

最終的にはログインしたユーザーをサーバーに保存したりログイン状態にしたりしますが、その前に一度ログイン後に自分のアプリの画面に遷移することだけを確認していきます。

コールバックするまでを確認する

「連携アプリを認証」ボタンを選択したときのアクションを作成します。

SessionsController を作成します。

$ rails generate controller Sessions
class SessionsController < ApplicationController
  def create
    user_data = request.env['omniauth.auth']
    session[:nickname] = user_data[:info][:nickname]
    redirect_to root_path, notice: 'ログインしました'
  end
end

request.env['omniauth.auth'] にはTwitter認証したユーザーの情報が格納されています。

取得できる情報はomniauth-twitterのREADMEを参照してください。

github.com

今回は試しにnicknameを参照します。

nicknameがちゃんと取得できているか、ビューファイルから確認してみます。

app/views/homes/index.html.erb

<h1>Twitter連携</h1>
<% if flash[:notice] %>
  <p><%= flash[:notice] %></p>
<% end %>
<% if session[:nickname] %>
  <p><%= "#{session[:nickname]}さん、こんにちは" %></p>
<% end %>

config/routes.rb

Rails.application.routes.draw do
  root 'homes#index'
  get '/homes', to: 'homes#index'
  get '/auth/:provider/callback', to: 'sessions#create'
end

これでログインしたユーザーのidが表示されたhome画面に遷移することができました!

現在はただログインしたユーザーの情報を取得しているだけなので、今度はUserテーブルに登録できるようにしていきます。

Userモデルを作成する

$ rails g model User uid:string nickname:string name:string image:string
$ rails db:migrate

ユーザー登録ができるようにする

セッション用のメソッドはapp/helpers/sessions_helper.rb に記載していきます。

Helperメソッドはデフォルトでビュー内で使用することができますが、コントローラー内では使用することができません。 コントローラーでも使えるようにします。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include SessionsHelper
end

セッション用のヘルパーメソッドを作成します。 (Railsチュートリアルの8章を参考にしています)

app/helpers/sessions_helper.rb

module SessionsHelper
  # 渡されたユーザーでログインする
  def log_in(user)
    session[:uid] = user.uid
  end

  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:uid]
      @current_user ||= User.find_by(uid: session[:uid])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end

  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:uid)
    @current_user = nil
  end
end

ログイン、ログアウト処理実装

app/controllers/sessions_controllers.rb

classSessionsController< ApplicationController
  def create
user= User.find_or_create_from_auth_hash(auth_params)
    ifuser
log_in(user)
      flash[:notice] = 'ログインしました'
    else
      flash[:notice] = '失敗しました'
    end
    redirect_to root_path
  end

  def destroy
    log_out if logged_in?
    flash[:notice] = 'ログアウトしました'
    redirect_to root_path
  end

  def failure
    flash[:notice] = 'キャンセルしました'
    redirect_to root_path
  end

  private
  def auth_params
    request.env['omniauth.auth']
  end
end

app/models/user.rb

classUser< ApplicationRecord

  def self.find_or_create_from_auth_hash(auth_hash)
    find_or_create_by(uid: uid) do |user|
            user.uid =auth_hash[:uid]
            user.twitter_id =auth_hash[:info][:nickname]
            user.twitter_name =auth_hash[:info][:name]
            user.twitter_icon =auth_hash[:info][:image]
    end
  end
end

ログイン、ログアウト用のリンクの追加

<% if logged_in? %>
  <%= link_to 'ログアウト', logout_path %>
<% else %>
  <a href="/auth/twitter">ログイン</a>
<% end %>

これでTwitterでログイン・ログアウトができるようになりました 🎉


何か間違えているところ等あればコメントやTwitterでメンションください。

*1:2021年12月時点でバージョン指定せずにomniauth-twitter をインストールするとomniauth のバージョンは2系になる

チェリー本輪読会を完走しました

フィヨルドブートキャンプ内で開催されていたトミーさん主催のプロを目指す人のためのRuby入門、通称チェリー本の輪読会を最後まで読み終えたのでその感想です。

書きたいことが多く文章がかなり長くなってしまいましたが、それだけ得るものの多い輪読会になりました。

目次

輪読会の概要

フィヨルドブートキャンプ(以下フィヨルド)の教材でもあるチェリー本を、主催のトミー(id:eatplaynap329)さんや自分をはじめとしたフィヨルドで学ぶ受講生8人程度で読み進めていました。ラジオ参加の方を含めると10~15人程度参加する日もあり、比較的賑やかな雰囲気の輪読会だったと思います。

開催日時は平日の毎朝9:00〜10:00、Discord上で行いました。5月の下旬から開始し9月の半ばに終わったため、約4ヶ月ほどで読み終わったことになります。

輪読会は基本的に参加者で本の内容を音読しながら、ドライバーがirbでサンプルコードを実行するというモブプロ形式で進めていました。

参加した経緯

フィヨルドでは以前から輪読会がいくつか開催されており、私も何度か参加したことがありましたが、最初からレギュラーメンバーとして参加するのは今回の輪読会が始めてのことでした。

参加した目的はもちろんRubyの理解を深めたいこともありましたが、何より誰かとRubyやプログラミングのことを話せる場が欲しかったのが1番の理由です。 というのも、輪読会に参加する前まで自分はフィヨルドのカリキュラムをもくもくと1人で進めていたため(もちろんメンターのサポートもありましたが)、プログラミングの楽しさを誰かと共有したいと思っていたためです。

輪読会を完走した結果としては、その目的を十分に実現できた輪読会になりました。

輪読会を終えてみて

できるようになったこと

本の深掘りができた

チェリー本は以前に1度読んでいましたが、自分1人で読むよりもずっと輪読会で読むことでチェリー本の内容の理解が深まりました。1人で読んでいる時にはさらっと読み流してしまうところも、参加者から出た疑問点を起点に内容を深掘りすることでRubyの理解を深めることができました。

本の内容に関連して、参加者同士で「そういえばレビューでこういう指摘を貰った」「bootcampではここで使われている」と実践的なエピソードの共有もよくしていました。

具体的にどういった深掘りをしていたかは、タマキさんが週ごとに(!)まとめてくれていいます。

shirotamaki.hatenablog.com

コードをirbで動かしながら理解した

本に出てくるサンプルコードは読むだけでなく、実際にドライバーがirbでコードを実行していく形で進めていました。

輪読会の最初の方はただ読むだけだったのですが、この形式にしてからの方が本の内容を理解しやすくなったと感じています。

ただサンプルコードを試すだけでなく、「この値を変えたらどうなる?」「このメソッドを試してみたらどうなる?」と他の条件も試すことでより深い理解ができました。自分には思いつかなかったような視点を他の参加者から共有して貰えるのも、複数人で本を読む輪読会ならではのよさだと思います。

次第に参加メンバーの深掘り力がついてきたおかげか、たびたび本に書かれていること以上の深掘りをしてしまい本の内容が全く進まなかった……ということもありました。笑

輪読会でirbに触り慣れたおかげで、輪読会以外の場面でもRubyの挙動を調べるためにirbを使うことが多くなりました。

公式リファレンスを読む習慣がついた

本に書いてあることの理解でとまらず、公式リファレンスも一緒に確認することも輪読会を進めていく中で習慣化していきました。

分からないことや本に書いていない内容は大体るりまに書いてあったことが何度もあり、公式リファレンスをあたることの重要性を認識しました。

質問をすることに抵抗がなくなった

今まで自分は質問するということに苦手意識がありました。というのも「こんなことが分からないのは自分だけじゃないのか」「時間を割いてしまって申し訳ない」と思っていたためです。

質問の重要性に気づいたのは、輪読会で質問をされる立場になったことがきっかけです。

参加メンバーから挙がった疑問点や質問は、自分も分かった気になっていただけで分かっていなかったことや、質問の答えをメンバーで突き詰めていくことでちゃんと理解できていなかったことに気づく場面が何度もありました。

このように、他者の疑問が自分の理解にもつながり、また自分の疑問も誰かの理解の助けになるのだと気づいたことから、質問をすることに抵抗がなくなりました。

よかったこと

メンバーと輪読会を最後まで読むことができた

この輪読会では毎日参加していたレギュラーメンバーが序盤から最後まで欠けることなく完走することができました。平日毎日1時間でやっていたことを考えるとすごいことだと思います。正直なところ、輪読会が始まる前は自分が最後まで参加できるか不安なところがありました。

最後まで楽しく完走できた理由を自分なりに考えてみたのですが、輪読会の振り返りを行ったこと、またメンバーそれぞれの得意なことがうまく作用した輪読会になったことの2つがあったからだと思っています。

輪読会の振り返り

今回の輪読会では、毎日5分程度の時間を使って輪読会自体の振り返りを行いました。そこで「○○さんの司会が進めやすかった」「ここは深掘りしなくてもよかった」といった風にいいところも改善すべきところも振り返りを行うことで、回を増すごとによりよい輪読会にできたと思います。

また、輪読会の時間とは別にKPTを行う機会を定期的に設けました。ここで毎日の振り返りでは出なかったような問題や改善点を挙げることでより良い輪読会にすることができました。

それぞれの得意なことがうまく作用した

レギュラーメンバーが8人程度いたのですが、毎日輪読会を行う中でそれぞれの得意なことが分かってきました。

  • 人に分かりやすい言葉で説明できる人
  • 話をまとめてくれる人
  • 議論が停滞した時に話を切り出してくれる人
  • 話を聞くのがうまい人
  • なんとなくの理解でとどまらず、そもそもの段階から疑問に思う人
  • 分からないことを素直に共有できる人
  • 調べるのが上手い人

などいろいろな方がいました。

それぞれの得意なことがうまく作用したおかげで輪読会が有意義で楽しい時間になりました。

フィヨルドのコミュニティに積極的に入れるようになった

輪読会に参加して最もよかったことの1つです。

フィヨルドブートキャンプはプログラミングスクールではありますが、よくコミュニティに例えられることがあります。

自分はフィヨルド卒業生の方々が作ってきたコミュニティが活発だったからこそフィヨルドのカリキュラムの終盤までこれたと感じていますし、自分もこのコミュニティを作っていく1人になりたいと思っていました。しかし、輪読会に参加する前はあまり行動に移せていませんでした。

今ではチェリー本輪読会に参加したおかげで気兼ねなく話せる友人ができましたし、メンバーの方と交流していることが自分のことを知ってもらえるきっかけになり、メンバー以外の方とも関わりやすくなったと感じています。 今では自分もコミュニティを盛り上げている中の1人になれているのかなと思っています。

また、チェリー本輪読会がきっかけでフィヨルドのDiscord内でもくもく会が開催され、現在は恒常でもくもく会が開かれています。このもくもく会のおかげで以前より生徒やメンターと日頃から雑談しやすくなりました。

詳しくはいっしーさんのブログを読んでみてください。

isshi-hasegawa.hatenablog.com

好きなRubyのメソッドができた

Ruby界隈でよく自己紹介をするときに好きなメソッドをあげることがよくあります。自分は今まで「なんとなく便利だからmapかな」程度にしか思っていなかったのですが、輪読会でさまざまなRubyのメソッドを知る中で自分の好きなメソッドに出会えました。

ちなみに自分の好きなメソッドはString#[]です。

scrapbox.io

著者である伊藤さんが輪読会に参加してくれた

チェリー本の著者であり、フィヨルドブートキャンプのメンターである伊藤(id:JunichiIto)さんが輪読会に何度かいらしてくれました。著者が参加してくれる輪読会はなかなかないと思います……!

輪読会の最終回にも顔を出してくださり、なんと著者にあとがきを読んで頂くというエモい最終回になりました。

近いうちにチェリー本の改訂版がでるとのことです!自分も早速予約しました。

blog.jnito.com

輪読会がきっかけでやったこと

トミーさんが作ったbotにPRを送った

輪読会主催のトミーさんが輪読会で使うDiscordのbotを作ってくれました。 botはDiscordの音声チャットにいるユーザーの中からランダムで誰かを選んでくれるというものです。

eatplaynap329.hatenablog.jp

チェリー本の輪読会では毎日司会者や感想の発表者をランダムに選んでいて、今までirbでrandomを使って指名していたところを、このbotのおかげでディスコードのテキストチャット上でコマンドを実行するだけで指名できるようにりました。便利すぎる!

輪読会で実際に使ってみて「指名する人数を選べるといいよね」という話が上がったので、自分がPRを出したところマージして貰いました。

調べるのもコード書くのが楽しかった・botの作成者に喜ばれた・自分も便利になった・輪読会もスムーズに進んだ、とお得な体験でした。OSSコントリビューターだ✌️

まとめ

Rubyの理解が深まっただけでなく、他にも得るものが多い輪読会になりました。勇気を出して参加してよかったな〜としみじみ感じています。

チェリー本を読み終わった現在は同じ時間帯にやつはしさん主催で現場Railsの輪読会をしています。チェリー本と同じように試行錯誤してより良い輪読会ができるように自分なりに関わっていきたいと思っています。

コマンドライン上でGitHubの草を生やすnpmを作った

f:id:yana_g:20210602142503p:plain フィヨルドブートキャンプではJavaScriptのプラクティスとしてnpmを作る課題があります。

自分はGitHubの草をコマンドライン上で表示するnpmを作りました。

www.npmjs.com

作ったもの

ユーザー名を指定すると草を表示させることができます。

1回でもコントリビューションすれば草🌱を生やします。

$ emoji-grass <username>
    🌱  🌱🌱🌱    🌱🌱🌱    🌱    🌱  🌱🌱🌱🌱          🌱  🌱        🌱🌱              🌱      🌱  
  🌱  🌱        🌱          🌱🌱            🌱    🌱          🌱    🌱  🌱      🌱      🌱🌱🌱      
    🌱        🌱🌱      🌱      🌱🌱  🌱        🌱    🌱      🌱        🌱  🌱    🌱      🌱  🌱    
        🌱        🌱            🌱    🌱🌱🌱              🌱    🌱              🌱    🌱  🌱        
          🌱              🌱      🌱    🌱  🌱🌱🌱            🌱  🌱🌱🌱🌱🌱🌱  🌱🌱🌱🌱  🌱  🌱  🌱
🌱🌱🌱    🌱    🌱                    🌱  🌱  🌱  🌱        🌱  🌱        🌱  🌱        🌱  🌱  🌱🌱
  🌱      🌱🌱  🌱    🌱      🌱  🌱      🌱                  🌱        🌱🌱🌱  🌱    🌱🌱    🌱  🌱

デフォルトでは草🌱ですが、オプションで好きな文字を指定することもできるようにしてみました。

$ emoji-grass -c 🍺 <username>
    🍺  🍺🍺🍺    🍺🍺🍺    🍺    🍺  🍺🍺🍺🍺          🍺  🍺        🍺🍺              🍺      🍺  
  🍺  🍺        🍺          🍺🍺            🍺    🍺          🍺    🍺  🍺      🍺      🍺🍺🍺      
    🍺        🍺🍺      🍺      🍺🍺  🍺        🍺    🍺      🍺        🍺  🍺    🍺      🍺  🍺    
        🍺        🍺            🍺    🍺🍺🍺              🍺    🍺              🍺    🍺  🍺        
          🍺              🍺      🍺    🍺  🍺🍺🍺            🍺  🍺🍺🍺🍺🍺🍺  🍺🍺🍺🍺  🍺  🍺  🍺
🍺🍺🍺    🍺    🍺                    🍺  🍺  🍺  🍺        🍺  🍺        🍺  🍺        🍺  🍺  🍺🍺
  🍺      🍺🍺  🍺    🍺      🍺  🍺      🍺                  🍺        🍺🍺🍺  🍺    🍺🍺    🍺  🍺

1文字ならなんでも表示できるようにしているので絵文字に限らず文字を指定できます。

文字通り(?)草を生やすことも可能です。

$ emoji-grass <username> -c 草
    草  草草草    草草草    草    草  草草草草          草  草        草草              草      草  
  草  草        草          草草            草    草          草    草  草      草      草草草      
    草        草草      草      草草  草        草    草      草        草  草    草      草  草    
        草        草            草    草草草              草    草              草    草  草        
          草              草      草    草  草草草            草  草草草草草草  草草草草  草  草  草
草草草    草    草                    草  草  草  草        草  草        草  草        草  草  草草
  草      草草  草    草      草  草      草                  草        草草草  草    草草    草  草

使い方

追記(2021/06/11)

インストールの詳細を同じフィヨルドブートキャンプ生であるトミーさん(id:eatplaynap329)に分かりやすくまとめていただきました!ありがたい〜!

eatplaynap329.hatenablog.jp

JavaScriptやnpm、トークンのことを分かっていなくても、こちらの手順を見ればどなたでも使うことができると思います!!


グローバルインストールをします。

$ npm install -g emoji-grass

実行するにはトークンの設定が必要です。

https://github.com/settings/tokens でアクセストークンを発行してください。

権限のスコープは何も設定しなくても実行できます。

privateリポジトリを含めた結果を表示したい場合はrepo:statusにチェックを入れてください。

個人アクセストークンを使用する - GitHub Docs

トークンを発行したら、以下のコマンドを実行してトークンを設定します。

$ emoji-grass settoken <token>

~/.grass_envトークンを書く方法でも設定できます。

$ echo 'GITHUB_TOKEN = <token>' > ~/.grass_env

設定が完了したら

$ emoji-grass <username>

で実行できます🌱

-c オプションを付ければ表示する文字を変更できます。

$ emoji-grass -c 🍺 <username>

苦戦したこと

contributionの取得

もともとほとんどAPIを使ったことがなかったんですが、草の情報(contribution)を取得するAPIはREST形式がなくGraphQL形式のみ提供されていたためGraphQLの使い方に苦戦しました。

GraphQLを使った手順のことは別の記事に書いてみました。

yana-g.hatenablog.com

1文字の判定

$ emoji-grass -c 🍺 <username>のように他の文字を表示するコマンドでは表示する文字を1文字にするバリデーションをつけています。 この「1文字」の判定が思った以上に難しく苦戦しました。

単純に'🌱'.lengthをしてみると1じゃなくて2が表示されてしまうことに気づき、文字コードに関する記事を読んでlengthは「カーソルが 1 つ移動する分」ではなく「コードポイントの数」を取得していることを知りました。

文字コードのことは全然理解できていないです……。)

結果的には「カーソルが 1 つ移動する分」として見えている文字の数をカウントできるライブラリを使う方法で解決しました。

参考

JavaScript における文字コードと「文字数」の数え方 | blog.jxck.io

graphemesplit - npm

作った感想

今までフィヨルドブートキャンプの課題では決められた仕様のアプリを作るのが中心だったので、今回のように自分でアプリや仕様を考えて作るのは初めてのことでした。 もちろん決められた仕様のものを作る楽しさもあるんですが、自分で考えたアプリを作るのは考える段階からわくわくするほど楽しかったです!

自分が欲しいと思うもの・あったら面白いなと思うものを形にすることは、絵を描くことや工作をするものづくりに通づる面白さがありますね。

自分はカリキュラムが後半の方で卒業まで残すプラクティスはチーム開発は自作サービス作りなのですが、そこでチーム開発や自作サービスだからこそ得られる楽しさを感じることができたらいいな〜と思っています。

追記(2021/06/11)

この記事を書いてフィヨルドブートキャンプのDiscordやTwitterで宣伝したところ、たくさんの人に使っていただきました!

Twitterに投稿していただいた方のツイートを抜粋。



自分が欲しいなと思うものを作ったら、作るのが楽しい→完成したら達成感がある→使って貰えて嬉しい……と全ての過程でいいことばかりでした。

上記に書いたGraphQLや文字コードに苦戦したり、プラクティスの想定される所要時間以上に時間を使ってしまったりと苦労したこともあったんですが、いろんな方に使って貰えた喜びを得られたので良しとします。

仕様が決まっていない、不確定要素の多いものを作る側の苦労というものも知り、サービスやOSSを作る人の立場も少しだけですが知ることができました。

自分が作ったもの・関わったに対して反応がくるというのは何ものにも代えがたい嬉しさがあるので、自分もこれから何かしらの形で作り手に敬意を伝えることもしていきたいです。

Node.jsでGitHub GraphQL APIを実行する

GitHubの草をコマンドライン上で表示するnpmを作りました。

www.npmjs.com

このnpmではGitHubの草情報を表示するために、日別のContribution数を取得しています。 今回初めてGraphQLを使ったので使い方をメモしておきます。

最初はAPIを使わず https://github.com/users/<username>/contributionssvgをfetchして配列形式で取得していたのですが、このページが正式のものか分からなかったこともあり、GraphQL APIでContributionが取得することにしました。

Contributionの取得はREST APIの方では見当たらなかったので現時点ではおそらくGraphQL APIでしかできないようです。

今回Node.jsでGitHub GraphQL APIを使うにあたって、GitHubが提供しているnpm @octokit/graphqlを使用しました。

github.com

サンプルコード

公式READMEに記載されているサンプルコードです。

GraphQLを実行するためのトークンと、欲しいデータを指定するクエリを設定する必要があります。

const graphqlWithAuth = graphql.defaults({
  headers: {
    authorization: `token secret123`,
  },
});
const { repository } = await graphqlWithAuth(`
  {
    repository(owner: "octokit", name: "graphql.js") {
      issues(last: 3) {
        edges {
          node {
            title
          }
        }
      }
    }
  }
`);

トークンの設定

トークンの取得

GitHubのGraphQLを実行するためにはGitHubの個人アクセストークンが必要です。

自分のケースの場合、権限のスコープは何も設定していなくても問題なく実行できましたが、用途に応じて設定します。

個人アクセストークンを使用する - GitHub Docs

トークンの設定

トークンはGitHub上に公開してしまわないよう直書きにせず.envファイルに環境変数としてトークンを設定し、dotenvライブラリを使って取得します。

motdotla/dotenv

.env

GITHUB_TOKEN = <your_token>

jsファイル

const dotenv = require('dotenv')

// .envファイルから環境変数を読み込む
dotenv.config();

const graphqlWithAuth = graphql.defaults({
    headers: {
      authorization: `token `token ${process.env.GITHUB_TOKEN}``
    }
  })

npmとして公開する場合

npmとして公開する場合はユーザー自身のトークンを使います。

今回のnpmではグローバルインストールしてどのディレクトリでも実行したかったため、~/.fooenvのように固有名の環境ファイルをホームディレクトリに作成しました。

環境設定ファイルを読み込むdotenv.config()はデフォルトの場合コマンドを実行しているディレクトリ配下の.envを参照するので、読み込む先を変更したい場合は引数に指定します。

const ENV_PATH = process.env.HOME + '/.grass_env'

await fs.writeFile(ENV_PATH, `GITHUB_TOKEN = ${token}`)
await dotenv.config({ path: ENV_PATH })

クエリの作成

GitHubにはExplorer - GitHub Docsというページがあり、 左側のExplorerから欲しい項目をチェックするだけで自動でクエリを作成してくれます。

今回は日毎のContributionが欲しいので、

user > contributionsCollection > contributionCalendar > weeks > contributionDays の

contributionCountとdateをチェックしました。

f:id:yana_g:20210601155654p:plain

query getContribution {
  user(login: "yana-gi") {
    contributionsCollection {
      contributionCalendar {
        weeks {
          contributionDays {
            contributionCount
            date
          }
        }
      }
    }
  }
}

▶︎ボタンを押すと以下のような結果が返ってきます。

{
  "data": {
    "user": {
      "contributionsCollection": {
        "contributionCalendar": {
          "weeks": [
            {
              "contributionDays": [
                {
                  "contributionCount": 0,
                  "date": "2020-05-31T00:00:00.000+00:00"
                },
                {
                  "contributionCount": 0,
                  "date": "2020-06-01T00:00:00.000+00:00"
                },
                {
                  "contributionCount": 0,
                  "date": "2020-06-02T00:00:00.000+00:00"
                },
                {
                  "contributionCount": 0,
                  "date": "2020-06-03T00:00:00.000+00:00"
                },
                {
                  "contributionCount": 0,
                  "date": "2020-06-04T00:00:00.000+00:00"
                },
                {
                  "contributionCount": 0,
                  "date": "2020-06-05T00:00:00.000+00:00"
                },
                {
                  "contributionCount": 0,
                  "date": "2020-06-06T00:00:00.000+00:00"
                }
              ]
            },
            {
              "contributionDays": [
                {
                  "contributionCount": 0,
                  "date": "2020-06-07T00:00:00.000+00:00"
                },
-省略-

クエリには変数を指定することもできます。

query getContribution($userName:String!) {
  user(login: $userName) {
    contributionsCollection {
      contributionCalendar {
        weeks {
          contributionDays {
            contributionCount
            date
          }
        }
      }
    }
  }
}
// QUERY VARIABLES
{
  "userName": "yana-gi"
}

実際のコード

トークンとクエリを設定すれば、データを取得することができます。

実際に今回作ったnpmの該当コードを切り取ってみました。(一部改変)

const { graphql } = require('@octokit/graphql')
const dotenv = require('dotenv')

dotenv.config()

const fetchContribution = async ({ userName }) => {
  const graphqlWithAuth = graphql.defaults({
    headers: {
      authorization: `token `token ${process.env.GITHUB_TOKEN}``
    }
  })

  const query =
    `query getContribution($userName:String!) {
      user(login: $userName) {
        contributionsCollection {
          contributionCalendar {
            weeks {
              contributionDays {
                contributionCount
                date
              }
            }
          }
        }
      }
    }`
  return await graphqlWithAuth(query, { userName: userName })
}
async function main (username) {
  const response = await fetchContribution({ userName: username })
  const contributionWeeks = response.user.contributionsCollection.contributionCalendar.weeks
  console.log(contributionWeeks)
}

main(‘yana-gi’)

参考

GitHubの草の情報をAPIで取得する方法

Node.js で GitHub GraphQL API を使用する (@octokit/graphql)|まくろぐ

GraphiQLを使ってGitHubのGraphQL APIをさわってみた | DevelopersIO

間違えてリモートリポジトリにmainブランチでpushしてしまった時の対処

自分が作業したコミットをpushしプルリクエストを作成しようとしたところ、プルリクエスト作成画面が表示されずにそのままリモートリポジトリのmainにコミットがマージされてしまいました。

原因はリモートリポジトリのmainからブランチを切らずにmainに直接コミットしてしまったこと、またそのままgit push origin mainでpushしたことでした。
同じbranchにpushした時は自動でマージされることを忘れていた……。

私は現在FJORD BOOT CAMP で勉強をしているのですが、Slackのwakaranチャンネル(文字通り分からないことがあった時に質問できる)でヘルプを出したところ色々教えていただき、無事解決したので手順などを書いておこうと思います。

前提

mainに直接追加したコミットをpushしてマージされてしまった状態から、コミットを別のブランチとしてpushしてPullRequestを作成したい

手順

Gitのリモートリポジトリに間違ってpushしてしまった時の対処法 - deepblue-will’s diary を参考にさせていただきました。

イメージとしてはざっとこんな形
リモートへのpushなどの説明は省略してます
f:id:yana_g:20210216190602j:plain
f:id:yana_g:20210216191148j:plain
f:id:yana_g:20210216190628p:plain


以下実際に行った手順

元に戻りたいコミットを確認
今回の場合、fb12441まで戻りたいとする

$ git log --oneline

f61f954 自分が追加したcommit3
1a6f04f 自分が追加したcommit2
0afad59 自分が追加したcommit1
fb12441 commit

mainブランチ上でブランチをコピーする
この時に本来つけたかったブランチ名を指定する

$ git checkout -b develop 

コピーしたブランチに想定通りのコミットがあるか念のため確認する

$ git log --oneline

コピーしたブランチをpushする

$ git push origin develop

ブランチをmainにチェックアウトする(忘れがちだけど大事)

$ git checkout main

間違えてpushしてしまったコミットをresetして取り消す
自分が変更する前の状態のコミットを指定する

$ git reset --hard fb12441

強制pushする(普通にpushしようとすると怒られる)

$ git push -f origin main 


これで無事間違えてpushした変更が元に戻り、プルリクエスト作成画面が表示されるようになりました🎉

コミットをresetしたり強制pushするのは神経を使うので、今後mainブランチを変更する際はちゃんとブランチを切ることを忘れないようにしたいと思います。



【追記】

FJORD BOOT CAMP のSlackにてさらにManaging a branch protection ruleでブランチの保護設定ができることを教えて貰いました!

リポジトリ画面のSettings(設定)→Branches(ブランチ)からmainブランチに直接pushできないように設定しました。
いろいろ設定できるようですが個人学習用のリポジトリなのでとりあえずこの最低限の2つで。 f:id:yana_g:20210218120517p:plain

「GitHubでログイン」の流れをざっくり理解する

これは「フィヨルドブートキャンプ Advent Calendar 2020」の10日目の記事です。
フィヨルドブートキャンプ Part 1 Advent Calendar 2020 - Adventar

昨日は Yusuke さんの【Ruby】puts病の私がメソッド化を少しできるようになった話 でした。

Part2 はこちら
フィヨルドブートキャンプ Part 2 Advent Calendar 2020 - Adventar

概要

私は現在Webエンジニアになるためにフィヨルドブートキャンプで勉強をしています。

フィヨルドブートキャンプでは「omniauth を使って GitHub 認証を実装する」というRailsのプラクティスがあります。
私は今このプラクティスを進めているのですが、今までのプラクティスで1番難しいと感じているところです。

というのもOAuthの理解がなかなか出来なかったためです。
OAuthをなぜ使うのか・どう手順を踏んでいるのか理解しようとするも、初めて見る単語や用語が多く、ドキュメントや記事も読んでもなかなか頭に入りませんでした。
「リソースサーバー」「認証サーバー」「アクセストークン」……?
今ではなんとか用語とその関係が理解ができたものの、そこまでに2,3日はかかってしまいました。

自分が「OAuthのことをまったく知らない」状態から「OAuthがなんとなく理解できた」状態になっただろうと考えてみると、「リソースサーバー等の言葉を使わず」に「身近なところで何が行われているのか」をざっくり知ることなのではないかと考えました。

そこで今回は「GitHubでログイン」ボタンを押してからログインが完了するまでにどのようなことが行われているのか、「リソースサーバー」等の言葉を使わずにできるだけ簡略化した形で説明してみます。

※この記事の説明はざっくりした内容に留まります。初心者が書いたもので正確性には欠けるのでご承知ください。致命的な間違い等あればご指摘ください!

Github認証ができるまで

AさんがGithubのアカウントでなんらかのアプリケーションをログインしたいとします。
Qiitaでいうとログインページのこのボタンです。 f:id:yana_g:20201209161244p:plain ログインボタンを押してからログインが完了するまでにどのようなことが行われているのか、ざっくり見ていきます。

登場人物

今回の登場人物です。 f:id:yana_g:20201209185233j:plain

AさんGithubとアプリケーションのユーザー
〇〇アプリGithubのアカウントでログイン機能を持つアプリケーション
GitHub:ログイン機能を提供するプロバイダー

※OAuthのドキュメントや記事では次のような言葉で説明されることが多いです。
今回は「ざっくり流れを理解する」ことが目的なので、上記の呼び方で説明していきます。
Aさん:エンドユーザー
〇〇アプリ:クライアント
Github:リソースサーバー

※本来はリソースサーバーと認可サーバーは独立して考えますが、今回は同一サーバーという想定で説明します。

GitHubでログインができるまでの流れ

f:id:yana_g:20201209185315j:plain 1. Aさんが〇〇アプリの「Githubのログインボタン」を押します。
2. 〇〇アプリはGithubに対してAさんのアクセス権を要求します
f:id:yana_g:20201209185333j:plain 3. GithubはAさんに対して「Githubへのアクセス権をアプリに委譲すること」についてAさんに確認します。
それがこの画面です。(アプリは自分が作成したテスト用のもの)
f:id:yana_g:20201209153819p:plain 4. Aさんは権限委譲に同意します。 f:id:yana_g:20201209185354j:plain 5.Githubは権限が委譲された証として、「Aさんの代わりにアクセスできる許可証」(=アクセストークン)を発行し〇〇アプリに渡します。

f:id:yana_g:20201209185408j:plain 6. アプリは許可証(アクセストークン)を持ってAさんのプロフィール情報にアクセスするを要求します。
f:id:yana_g:20201209140941j:plain 7.Githubは許可証(アクセストークン)の有効性と権限が正しいか確認し、問題がなければ〇〇アプリにAさんのプロフィール情報を渡します。

f:id:yana_g:20201209185421j:plain 8. 〇〇アプリはGitHubからもらったプロフィール情報を元にログイン処理を行い、ログイン後の画面を表示させます。

以上がGitHubでログインするざっくりとした流れになります。

OAuthとは結局なんなのか

OAuthは許可証(アクセストークン)を使ってIDとパスワードを渡さずに「特定のデータへの操作を許可」(=認可)する仕組みのことです。
図で言うとここです。

f:id:yana_g:20201209185441j:plain IDとパスワードで「本人確認」(=認証)する仕組みではありません。
ですがGitHubはプロフィール情報を提供するAPIがあり、アプリはこのAPIから取得したユーザー識別子の情報などを利用し、OAuthでの認証を可能にしています。
(ここ説明が適切か自信ないです……)

さいごに

自分がやっと理解できたと思った内容も、いざ説明してみると想像以上に難しかったです。(簡単な言葉で説明しようとなるとなおさら……!)

色々な資料を読んでみて認証まわりのことをもっと知りたいと思ったところなので、将来もっといい記事が書けるように頑張ります。

参考

雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本 - Auth屋 - BOOTH

OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本 - Auth屋 - BOOTH

Authorizing OAuth Apps - GitHub Docs

GithubのOAuth2.0の仕様について理解する(Githubログイン) - ぺい
OAuth 2.0 の解説サイトを漁る前に - Qiita