さがえもん

日々の開発で得たことを書きなぐっていきます。

GitHubAppsでjwtとアクセストークンを取得する(Rails/Ruby)

前提

  • GitHub Appsを登録していること

JWTの取得

json web tokenを発行します。

foo.rb

private_pem = File.read('/Users/ユーザー名/.ssh/hoge.pem')
private_key = OpenSSL::PKey::RSA.new(private_pem)



# Generate the JWT
payload = {
  # issued at time
  iat: Time.now.to_i,
  # JWT expiration time (10 minute maximum)
  exp: Time.now.to_i + (10 * 60),
  # GitHub App's identifier
  iss: payloadナンバー
}

jwt = JWT.encode payload, private_key, "RS256"

p jwt

これでjwtを取得できます。 pemファイルはローカルであれば.ssh配下に置いてください。

以下のコマンドで.sshディレクトリに入ってダウンロードしたファイルを置けばOKです。

cd ~/.ssh

実行すると、以下のようにjson web tokenが返ってきます。

$ ruby foo.rb
"ながーい文字列"

アクセストークンの取得

先ほど取得したjwtを使用してアクセストークンを取得します。

ただここでinstallation_idというものが必要になるのですが、GitHubのリファレンスにその在りかが書いてません。

このidを取得するには Settings > Developer settings > GitHub Apps > GitHub Apps名 でappsの編集画面に行きます。 左のナビゲーションタブに「Advanced」というタブがあるのでそちらをクリックし、「Recent Deliveries」と書かれたログの中から installation で検索かけると発見できます。

Advanced f:id:sksksksksk:20180805203733p:plain

  "installation": {
    "id": xxxxxx
  }

jwtとinstallation_idを用いてpostしてあげると、 アクセストークンを取得できます。

uri = URI.parse "https://api.github.com/installations/installation_id/access_tokens"
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{jwt}"
request["Accept"] = "application/vnd.github.machine-man-preview+json"

req_options = {
  use_ssl: uri.scheme == "https",
}

response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
  http.request(request)
end

p json = JSON.load(response.body)
p json['token']

レポジトリの取得

おまけにレポジトリまで取得してしまいましょう。 基本的にはGitHubAPIと同じコードをかけばよいです。

uri = URI.parse("https://api.github.com/installation/repositories")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "token #{json['token']}"
request["Accept"] = "application/vnd.github.machine-man-preview+json"

req_options = {
  use_ssl: uri.scheme == "https",
}

response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
  http.request(request)
end

puts JSON.pretty_generate(JSON.load(response.body))

ここではrubyの標準ライブラリである net/http を使ってますが、もう少し本格的に実装する場合はHttpartyをオススメします。

参考

developer.github.com

Two different lockfiles found: package-lock.json and yarn.lockがherokuデプロイ時に出たら?

結論

yarnとnpmロックファイルが同時に存在すると、herokuは依存関係に用いるパッケージマネージャを認識することができないために発生するエラー。 yarnかnpmのどちらか使っていない方のロックファイルを削除しましょう。

  • npmを使用するならyarnロックファイルを削除します。
git rm yarn.lock
git commit -m "Remove yarn lock file"
git push heroku master
  • yarnを使用するならnpmのロックファイルを削除します。
git rm package-lock.json
git commit -m "Remove npm lock file"
git push heroku master

発生事例

herokuにauto deployをした時に以下のエラーが発生しました。

-----> Node.js app detected
-----> Build failed
 !     Two different lockfiles found: package-lock.json and yarn.lock
       Both npm and yarn have created lockfiles for this application,
       but only one can be used to install dependencies. Installing
       dependencies using the wrong package manager can result in missing
       packages or subtle bugs in production.
       - To use npm to install your application's dependencies please delete
         the yarn.lock file.
         $ git rm yarn.lock
       - To use yarn to install your application's dependences please delete
         the package-lock.json file.
         $ git rm package-lock.json
    
       https://kb.heroku.com/why-is-my-node-js-build-failing-because-of-conflicting-lock-files
 !     Push rejected, failed to compile Node.js app.
 !     Push failed

jsのパッケージ管理にnpmとyarnを使っていたことが原因のようです。 herokuのhelpに解決策が載っていたのですぐに対応することができました。

もうちょい詳しく

例えば、2つのロックファイルに同じライブラリが異なるバージョンで設定されていた時にエラーが発生する可能性があります。 その問題を回避するため、herokuはあらかじめロックファイルを1つに絞り対策を講じている、ということです。

参考

Why is my Node.js build failing because of conflicting lock files? - Heroku Help

RailsでBootstrap4のDatePickerを実装する

f:id:sksksksksk:20180802125813p:plain

Bootstrap4で関連するライブラリはまだ追いついてないと思いきやDatePickerは使えるようになっていたので、 お伝えします。

前提

bootstrap4をインストールしていること

インストール

gem 'bootstrap4-datetime-picker-rails'
gem 'momentjs-rails'
bundle install

app/assets/javascripts/application.js

//= require moment
//= require moment/ja.js # 日本語化
//= require tempusdominus-bootstrap-4.js

View

適宜、haml・slim・erbに変換してください。

<div class='input-group date' id='datetimepicker' data-target-input='nearest'>
  <div class='input-group-append' data-target='#datetimepicker' data-toggle='datetimepicker'>
    <input type='text' id='scenario_delivery_attributes_custom_delivering_at' class='form-control datetimepicker-input' data-target='#datetimepicker' name='hoge' />
  </div>
</div>

JavaScript

$(セレクタ).datetimepicker() でカレンダーが起動します。 datetimepickerとtimepickerを使い分けたい場合は、idを変えてあげれば大丈夫です。

カスタマイズもdatetimepicker()の中に設定を書いて上書きすれば反映されます。 TimePickerは format: 'LT' を指定してください。

hoge.js

$(function () {
  $('#datetimepicker').datetimepicker({
    icons: {
      time: 'fa fa-clock',
    }
  });
  $('#timepicker').datetimepicker({
      format: 'LT'
  });
});

■ 日付 + 時間

f:id:sksksksksk:20180802125832p:plain

■ 時間

f:id:sksksksksk:20180802125927p:plain

カスタマイズ用スクリプト

$('#datetimepicker-demo').datetimepicker({
  // requires moment-timezone.js
  timeZone: '',
  // date format
  // http://momentjs.com/docs/#/displaying/format/
  format: false,
  dayViewHeaderFormat: 'MMMM YYYY',
  extraFormats: false,
  // step size
  stepping: 1,
  // min/max dates
  minDate: false,
  maxDate: false,
  // uses current date/time
  useCurrent: true,
  // uses Bootstraps collapse to switch between date/time pickers
  collapse: true,
  // https://github.com/moment/moment/tree/develop/locale
  locale: moment.locale(),
  // default date
  defaultDate: false,
  // disabled dates
  // array of [date, moment, string]
  disabledDates: false,
  // enabled dates
  // array of [date, moment, string]
  enabledDates: false,
  // default icons
  icons: {
    time: 'fa fa-clock-o',
    date: 'fa fa-calendar',
    up: 'fa fa-arrow-up',
    down: 'fa fa-arrow-down',
    previous: 'fa fa-chevron-left',
    next: 'fa fa-chevron-right',
    today: 'fa fa-calendar-check-o',
    clear: 'fa fa-delete',
    close: 'fa fa-times'
  },
  // tooltip options
  tooltips: {
    today: 'Go to today',
    clear: 'Clear selection',
    close: 'Close the picker',
    selectMonth: 'Select Month',
    prevMonth: 'Previous Month',
    nextMonth: 'Next Month',
    selectYear: 'Select Year',
    prevYear: 'Previous Year',
    nextYear: 'Next Year',
    selectDecade: 'Select Decade',
    prevDecade: 'Previous Decade',
    nextDecade: 'Next Decade',
    prevCentury: 'Previous Century',
    nextCentury: 'Next Century',
    pickHour: 'Pick Hour',
    incrementHour: 'Increment Hour',
    decrementHour: 'Decrement Hour',
    pickMinute: 'Pick Minute',
    incrementMinute: 'Increment Minute',
    decrementMinute: 'Decrement Minute',
    pickSecond: 'Pick Second',
    incrementSecond: 'Increment Second',
    decrementSecond: 'Decrement Second',
    togglePeriod: 'Toggle Period',
    selectTime: 'Select Time',
    selectDate: 'Select Date'
  },
  // uses strict
  useStrict: false, 
  // displays side by side
  sideBySide: false, 
  // disabled days of the week
  daysOfWeekDisabled: false,
  // shows the week of the year
  calendarWeeks: false,
  // 'decades','years','months','days'
  viewMode: 'days',
  // toolbar placement
  toolbarPlacement: 'default',
  // enable/disable buttons
  buttons: {
    showToday: false,
    showClear: false,
    showClose: false
  },
  // widget position
  widgetPositioning: {
    horizontal: 'auto',
    vertical: 'auto'
  },
  // string or jQuery object
  widgetParent: null,
  // ignore read only input
  ignoreReadonly: false,
  // always keep open
  keepOpen: false,
  // shows on focus
  focusOnShow: true,
  // inline mode
  inline: false,
  // makes the date picker not revert or overwrite invalid dates
  keepInvalid: false,
  // debug mode
  debug: false,
  // shows on focus and icon click
  allowInputToggle: false,
  // disables time selection
  disabledTimeIntervals: false,
  // disables/enables hours
  disabledHours: false,
  enabledHours: false,
  // changes the viewDate without changing or setting the selected date
  viewDate: false,
  // allows multiple dates
  allowMultidate: false,
  // custom separator
  multidateSeparator: ','
});

参考

github.com

www.jqueryscript.net

github.com

facebookログインで「このURLのドメインはアプリのドメインに含まれていません。」が出る時

通常のhttp通信だと以下のようなエラーが発生します。

URLを読み込めません: このURLのドメインはアプリのドメインに含まれていません。
このURLを読み込むには、アプリ設定のアプリドメインに全てのドメインとサブドメインを追加してください。

facebookログインの実装には開発環境でもプロトコルhttps である必要があります。 しかし、デフォルトの開発環境では、http://localhost:3000 しか使えません。

そこで、 thin というgemを使って開発環境でもhttps通信できるように設定をしてあげましょう。

インストール

gem 'thin' # 開発環境用webサーバー
bundle install --path vendor/bundle

thinの起動

以下のコマンドを実行します。

bundle exec thin start --ssl -p 3001

Chromehttps://localhost:3001/ を開くと証明書の選択というダイアログが表示されますので「OK」を選択してください。 f:id:sksksksksk:20180731222937p:plain

「詳細設定」をクリックし

f:id:sksksksksk:20180731223209p:plain

localhostにアクセスする(安全ではありません)」を選択するとアクセスすることができます。 f:id:sksksksksk:20180731223328p:plain

参考

github.com

RailsでTinyMCEを使って高機能エディタを実装する

この記事で行うこと

TinyMCEを使って、チケット内容を記述するエディタを実装します。

TinyMCEはCMSなどでよく導入される高機能エディタです。 一からマークダウン機能がついたエディタやプレビューがついたエディタを実装するのは大変です。 TinyMCEを利用すればすぐにサービスに高機能エディタを実装することができます。

使うもの

前提

Chart.jsでおしゃれでかっこいいチャートを実装する - さがえもん を完了していること。

実装手順

インストール

Gemfile

gem 'tinymce-rails'
gem 'tinymce-rails-langs' # 日本語対応用
bundle install

初期化

app/javascript/packs/application.js

//= require tinymce

TinyMCEの設定をymlファイルで行います。

config/tinymce.yml

toolbar: # エディタのツールバーに表示されるもの
  - styleselect | bold italic | undo redo
  - image | link
plugins: # オプション機能を
  - image
  - link

app/javascript/packs/tiny-mce.js

tinyMCE.init({
  selector: 'textarea.tinymce', # エディタにしたいセレクタを指定 f.text_area :body...class: 'tinymce'
  language: 'ja' # 言語を日本語に設定
});

app/views/tickets/new.html.haml

.col-md-12
  = form_with(model: @ticket, local: true, html: { class: 'form-horizontal', role: 'form' }) do |f|
    - if @ticket.errors.any?
      ・・・・
    .form-group
      = f.label :title, class: 'col-sm-2 control-label'
      .col-sm-10= f.text_field :title, class: 'form-control', required: true
    .form-group
      = f.label :body, class: 'col-sm-2 control-label'
      .col-sm-10= f.text_area :body, class: 'tinymce', rows: 20, cols: 120, required: true # 修正
    .form-group
      = f.label :number, class: 'col-sm-2 control-label'
      .col-sm-10= f.number_field :number, class: 'form-control'
    .form-group
      = f.label :expired_at, class: 'col-sm-2 control-label'
      .col-sm-10= f.datetime_field :expired_at, class: 'form-control'
    .form-group
      .col-sm-offset-2.col-sm-10
        = f.submit class: 'btn btn-primary'
= javascript_pack_tag 'tiny-mce' # 追加

するとチケット作成画面に以下のようなエディタが表示されるようになります。

f:id:sksksksksk:20180730203731p:plain

チケット一覧画面で表示してしまうと内容で画面が埋め尽くされてしまうので、省略します。

app/views/tickets/index.html.haml

.col-xs-12
  %table.table
    %thead
      %tr
        %th
        %th チケット名
        %th 内容 # 削除
        %th.text-center チケット販売可能枚数
        %th.text-center 締切日
        %th
    %tbody
      - @tickets.each.with_index(1) do |ticket, index|
        %tr
          %td= index
          %td= ticket.title
          %td= ticket.body # 削除
          %td.text-center= ticket.number
          %td.text-center= ticket.expired_at
          %td.text-center= link_to '詳細', ticket, class: 'btn btn-block'

詳細画面では他の情報と切り離して表示をします。

app/views/tickets/show.html.haml

.col-xs-12
  .page-header{ 'ticket-id': @ticket.id }
    %h3 チケット概要
  %table.table
    %thead
      %tr
        %th チケット名
        %th.text-center チケット販売可能枚数
        %th.text-center 締切日
    %tbody
      %tr
        %td= @ticket.title
        %td.text-center= @ticket.number
        %td.text-center= @ticket.expired_at
  .row
    .col-xs-12
      = @ticket.body.html_safe

  .page-header
    %h3 チケットデータ
  .ticket-show-chart-wrapper
    %canvas{ id: 'chart', width: '900', class: 'chart' }
= javascript_pack_tag 'ticket-show-chart'

動作確認

  • 適当なWebページをコピーしてみましょう。 f:id:sksksksksk:20180730204023p:plain

  • エディタにペーストする

エディタにペーストすると以下のようにページそのものが貼り付けられたようになります。 f:id:sksksksksk:20180730204206p:plain

  • 保存して詳細ページを確認する

HTMLもそのままエスケープされて表示されます。 f:id:sksksksksk:20180730204534p:plain

参考

www.tiny.cloud

github.com

ソースコード

github.com

質疑応答

コメントしてください。 こんなコード見たいというような要望あれば記事にしますのでそちらもコメントかメッセージください。

Chart.jsでおしゃれでかっこいいチャートを実装する

この記事で行うこと

チケットの予約情報をチャートで表示します。

f:id:sksksksksk:20180729181231p:plain
デモ Chart.js

使うもの

前提

以下を完了していること。

現場では?

管理画面でよくチャートが使われます。 Chart.jsは導入もシンプルですので人気なライブラリです。

難しいのはチャートに表示させるためのデータを取得する関数や処理です。

実装手順

Chart.jsのインストール

Gemfile

gem 'chart-js-rails'
bundle install

Javascriptを使うのでwebpackerを導入します。 Railsアプリにwebpackerの導入 - さがえもん を見て導入をしてください。

app/assets/javascripts/application.js

//= require bootstrap
//= require Chart.min # 追加
//= require_tree .

# 追加 の文字は削除してください。エラーが発生します。

チャートの表示

  • チケットの詳細ページを作成

詳細ページへのリンクを追加します。

app/views/tickets/index.html.haml

.col-xs-12
  %table.table
    %thead
      %tr
        ・・・
        %th # 追加
    %tbody
      - @tickets.each.with_index(1) do |ticket, index|
        %tr
          ・・・
          %td.text-center= link_to '詳細', ticket, class: 'btn btn-block' # 追加

showページに表示するticketを取得します。

app/controllers/tickets_controller.rb

  def show
    @ticket = Ticket.find(params[:id])
  end

チケット詳細ページの要素を追加します。

app/views/tickets/show.html.haml

.col-xs-12
  .page-header
    %h3 チケット概要
  %table.table
    %thead
      %tr
        %th チケット名
        %th 内容
        %th.text-center チケット販売可能枚数
        %th.text-center 締切日
    %tbody
      %tr
        %td= @ticket.title
        %td= @ticket.body
        %td.text-center= @ticket.number
        %td.text-center= @ticket.expired_at

  .page-header
    %h3 チケットデータ

f:id:sksksksksk:20180729154118p:plain

  • チャートを表示

app/views/tickets/show.html.haml

  .page-header
    %h3 チケットデータ
  -# 追加(開始位置)
  .ticket-show-chart-wrapper
    %canvas{ id: 'chart', width: '900', class: 'chart' }
= javascript_pack_tag 'ticket-show-chart'
-# 追加(終了位置)

グラフの拡大阻止 .ticket-show-chart-wrapper でラップしてあげることでグラフが拡大するのを避けられます。 グラフの描画点 canvasid: 'chart' がグラフの描画位置を示します。 ** jsの呼び出し javascript_pack_tag で対象のグラフを呼び出します。

レスポンシブ · Chart.js 日本語ドキュメント

呼び出すJSコードの設定です。 ajaxでサーバーと通信をしています。 通信対象のアクションはtickets#showです。

app/javascript/packs/ticket-show-chart.js

showScoreChart();

// 関数化
function showScoreChart() {
  $.ajax({
    type: 'GET',
    url: `/tickets/${$('.page-header').attr('ticket-id')}`,
    dataType: 'JSON',
    success: function(data) {
      // 描画位置の取得
      var context = document.getElementById('chart').getContext('2d');
      // 背景カラーの用意
      var graphColorArray = ['rgba(244, 143, 177, 0.6)', 'rgba(255, 235, 59, 0.6)'];
      // チャートデータの生成
      var myChart = new Chart(context, {
          // 円グラフを指定
          type: 'pie',
          data: {
            // チャート上部のラベル
            labels: data.gender_names,
            datasets:  [{
              data: data.genders,
              backgroundColor: graphColorArray, // グラフカラー
              pointBackgroundColor: '#ffffff' // 背景カラー
            }]
          },
          // チャートのオプション設定
          options: {
            responsive: true, // レスポンシブ設定
            maintainAspectRatio: false, // アスペクト比の維持設定
          }
      });
    }
  });
};

通信アクションのtickets#showを実装します。 formatを使って、htmlとjsonで返せるようにします。

app/controllers/tickets_controller.rb

  def show
    @ticket = Ticket.find(params[:id])
    respond_to do |format|
      format.html
      format.json do
        render json: {
          gender_names: Users::Profile.genders_i18n.invert.keys, # genderの属性名 ['男性', '女性']
          genders: @ticket.deliveries.genders
        }
      end
    end
  end

@ticket.deliveries.genders でそのチケットを予約している男女数を配列で取得します。

app/models/delivery.rb

  belongs_to :ticket
  # -------------------------------------------------------------------------------
  # ClassMethods
  # -------------------------------------------------------------------------------
  #
  # 男女数を配列で返す
  #
  # @return  ex. [10, 20]
  #
  def self.genders
    genders = []
    users = User.where(id: pluck(:user_id))
    Users::Profile.genders.keys.each { |gender| genders << Users::Profile.where(user: users, gender: gender.to_sym).count }
    genders
  end

最後にルーティングを設定して完了です。

config/routes.rb

resources :tickets, only: %i(new create index show)

動作確認

何人かユーザーを作成し、男女に分けてプロフィールを更新しておいてください。

男性グラフにカーソルを合わせると... f:id:sksksksksk:20180729181156p:plain 女性グラフにカーソルを合わせると... f:id:sksksksksk:20180729181231p:plain

参考

Chart.js | Open source HTML5 Charts for your website github.com Chart.js · Chart.js 日本語ドキュメント

ソースコード

github.com

質疑応答

コメントしてください。 こんなコード見たいというような要望あれば記事にしますのでそちらもコメントかメッセージください。

次のチュートリアル

www.sendai-freelance.com

技術者にとって参考になるサイト

どんどん進化する技術の世界で常にエンジニアはキャッチアップを求められます。 そんな多忙なエンジニアさんのために役立つ技術記事をまとめてみました。参考になれば嬉しいです。

クックパッド開発者ブログ 発表資料

Railsを採用する最大手IT企業の開発ブログです。 内部で採用している技術スタックや開発手法を公開しています。

他社がどんな開発スタイルをとっているのか知りたいエンジニアさんには必見のページです。 static.cookpad.com

ProgateやRailsチュートリアル、プログラミングスクールを通い終えたが現場のコードはかけない、

一体どうやって書くの?と思っているエンジニアのみなさんのためのチュートリアルを公開しています。

チュートリアル