TES Blog

株式会社テクニカルエンジニアリングサポートに所属する社員が、自身が携わるテクノロジーやイベントに関する情報を発信しています。

2018年8月 アプリケーション開発向けの学生インターンシップの内容と、実施結果について

はじめに

TESでは毎年学生インターンシップの受入れを行っています。
今回は2018年8月20日から8月31日にかけての10日間、アプリケーション開発者向けをテーマとしたインターンシップを行い、日本工学院八王子専門学校様から4名の生徒さんに参加していただきました。

今回の記事ではインターンシップの活動内容と、その結果について紹介します。

目次

取り組んでもらった課題

端的にまとめると、デタラメな作りをしたアプリケーションに対して、それぞれ改善案を出して実際に修正するとしました。
私の方で用意したアプリケーション(粗悪)を対象にしてグループで話し合いながら解決手段を模索する、といった内容です。

ソースコード自体はパブリックリポジトリにて公開しています。
(意図してデタラメに作っているので粗探しはご勘弁を…🙇) github.com

開発環境

  • OS
    • Windows 7
  • 仮想環境
    • Docker
  • プログラム言語
    • Ruby 2.4
  • 開発フレームワーク
    • Ruby on Rails 5.2
  • データベース
    • MySQL 5.7
  • バージョン管理
    • Git
  • 利用サービス
    • GitHub(ソースコード管理と課題管理)
    • Slack(チャットでの相談)

Windowsをホストとして、Docker上で動作するRailsアプリケーションを構築した状態で配布しました。
今までのインターンシップではネイティブ環境上に構築することが多く、参加者の方への負担が増えて結果的に作業時間が無くなることが多々あったので、今回はDocker環境の配布を選択しました。結果的に環境構築に手間取ることがなかったため、この選択は正解だったかと思います。

スケジュール

スケジュールは以下のとおりに行いました。

  • Day 1:会社説明・案内、課題説明、開発環境構築
  • Day 2:改善案の洗い出し、GitHubのissue作成
  • Day 3〜Day 9:修正作業
  • Day 6:振り返り(KPT)実施
  • Day 10:成果発表

日々の日課として朝会(スタンディングミーティング)を開き、前日の成果と当日の作業予定の報告を行ってもらいました。
また、こちらはカリキュラムではありませんが、学生が学校に向けて提出する日報の作成を帰宅前に行っています。

Day 1

初日は会社で作業するにあたっての注意事項等について、管理本部から説明を行い、その後は会社の設備案内を行いました。
以降は私の方からインターンシップで行う活動内容の説明と、各自で開発環境の構築を行ってもらいました。

Day 2

無事開発環境が整ったところで、各自でサイトとソースコードの状態を確認して、問題点とそれに対する改善案を出してもらいました。
また、挙がったアイデアをGitHubのissueに登録して、それぞれ担当割り振りを行うように指示しました。
GitHubを利用したことのない方も居たため、issueの使い方に悩んでいましたが、一つのissueに対して一つの改善案を作成するようにアドバイスをし、整理した内容を登録してもらいました。

改善のアイデア自体については、こちらからは制限することなく自由に行ってもらったため、私ではなかなか思いつかない学生ならではのアイデアが出てきたのが非常に面白かったです。

Day 3〜Day 9

この期間はソースコードの修正作業を行ってもらいました。
皆さん本格的にRuby・Railsに触れた経験がないため、苦戦しながらの作業となり少し高いハードルだったかなと思いましたが、
担当するissueを見直し、自分の力量に見合った課題について対応を行っていました。
自主的にペアプロを行い効率化を図りながら作業をしていたり、バックエンドの実装が苦手ならフロントエンドの実装を行うなど、役割分担の工夫が印象的でした。

このフェーズでは質問を受け、開発中に発生した問題に対して「私ならこう修正する」というニュアンスのアドバイスを行いました。
現役プログラマーとしてなるべく恥ずかしくないコードを見せたつもりですが、大変不安です...😰

余談ですが、事前に埋め込んだバグに対して意図通りに引っかかってくれたので、してやったり感はありました。勿論、アドバイス時には意地悪せずに回答しています🐧

Day 6

5日間経過した段階で振り返り会を行いました。
今回はKPT法を用いて各自に Keep(赤色)Problem(黄色)Try(青色) の各付箋にそれぞれ考えていることを記載してもらいました。
厳密なKPTではなく、良かったこと・改善する必要があること・これから頑張りたいこと、といったニュアンスで考えてもらいました。

Keepと Problem

f:id:t-miyahara11:20180831165343p:plain

Try

f:id:t-miyahara11:20180831165026p:plain

前週まではグループとして課題に取り組むための会話量が少なく、なかなかコミュニケーションが図れてない場面が多かったのですが、
今回のKPTを通してお互いが何を考えているのか、どんな問題を抱えているのか話せる場ができたことで、この日以降グループとしてのまとまりが出来てきたかと思います。

Day 10

最終日はこれまでの成果についての報告会を開きました。
弊社社員の前で実際にアプリケーションの動作を見せながら、各自追加・修正した機能の説明をしてもらいました。
直前の修正でマージミスが発覚したため開始直前までドタバタしていましたが、なんとか10日間の成果を発表できていました。
報告会の終盤に質疑応答の場を設け、弊社社員からの質問に答えてもらう際、それぞれ出来たこと・工夫したことを話してもらいました。

おわりに

インターンシップに参加して良かったと思ってもらえるように準備してきましたが、最後に作成してもらった日報の中で「来ていなかったら後悔していた」とコメントがあったので、適切な応対ができたかなと思います。

今回のインターンシップの目的は、技術的なことについて主眼に置かず、グループワークや課題解決の手法について考えを巡らせる意図で企画した内容となっています。
その中で苦戦しながらもグループ開発を行い、それぞれが成長できたことが実感できて嬉しく思います。

少しでも弊社のインターンシップに興味が湧いた方がいれば幸いです。

Capistranoデプロイ実行時に、Microsoft TeamsのIncoming Webhookコネクタに向けて(Gemを拡張しつつ)通知を送る

はじめに

弊社では社内コミュニケーションツールとしてMicrosoft Teamsを利用しています。
日々アップデートしている様子もあり、リリース当初からするとSlackとの差をあまり感じなくなってきました(遊び心はSlackに軍配が上がりますね👌)

本記事ではCapistranoによるデプロイ実行時に、Teamsのチャネルに向けて作業開始の通知を送る方法についてまとめてみました。

開発環境

開発環境は以下のとおりです。

  • Ruby 2.4.2
  • Ruby on Rails 5.2
  • Capistrano 3.11.0

Incoming Webhookコネクタの導入

まずはTeamsのチャネルに対してコネクタの追加を行い、Incoming Webhookの通知先リソース名を取得します。

  • チャネル名右側の [...] から「コネクタ」を選択します。

    f:id:t-miyahara11:20180725082429p:plain

  • Incoming Webhook項目の「構成」を選択します。

    f:id:t-miyahara11:20180725082615p:plain

  • 名前(あとでTeams上で表示されます)を入力し、「作成」を選択します。

    f:id:t-miyahara11:20180725082741p:plain

  • リソース名をクリップボードにコピーします。

    f:id:t-miyahara11:20180725083058p:plain

上記手順によって取得したリソースが通知先になります。

通知用のGem

今回Incoming Webhookへ向けて通知を送る際に、こちらのGemを利用させて頂きました。

github.com

Capistranoのデプロイタスクに通知処理が組み込まれる、大変便利なGemです(ありがとうございます🙏)。
configに記載する内容はそれほど多くないため、容易に導入できると思います。

しかしそのままでは…

今回導入するIncoming Webhookコネクタに対してメッセージを送る場合は、少しだけ手を加える必要があります。
通常Incoming WebhookへメッセージをPOSTする際は、 payload をキーとしてデータを送信する必要がありますが、
Incoming Webhookコネクタに対してメッセージをPOSTする場合、payload キーを指定せずに直接JSON形式のデータを送信する必要があります。
詳しくは下記をご覧ください。

docs.microsoft.com

Gemの拡張手段

方法としては以下の手段が考えられます。

  1. 公開されているリポジトリから、ディレクトリ・ファイル構造をそのまま移植してコード修正
  2. Rubyのクラス拡張によるコード修正
  3. RubyのRefinementを使用したクラス拡張によるコード修正

1は流石に気が咎めるので却下ですね。
2でそのままモンキーパッチをする勇気はないので、3を選択しました。

追加したコード

lib/capistrano/tasks/webhook.rake

まずは lib/capistrano/tasks/webhook.rake ファイルを作成します。

# Capistrano::Hook::Webのパッチモジュール
module CapistranoHookWebRefine
  refine Capistrano::Hook::Web do
    def post(params)
      http.start do
        req                 = Net::HTTP::Post.new(uri.path)
        req['Content-Type'] = 'application/json'
        req.body            = params.to_json
        res                 = http.request(req)
        res
      end
    end
  end
end

上記コードの追加により、該当クラスのメソッドを再定義しています。
今回は post メソッドの内容を書き換えて、パラメータのHashデータをJSONに変換してbodyに渡すように修正しました。
payload キーの存在は、ここで忘れてしまいましょう。

# 独自に定義したタスクを優先させるため、gem側のcapistranoタスクを削除
Rake::Task['webhook:post:starting'].clear

namespace :webhook do
  namespace :post do
    using CapistranoHookWebRefine

    # gemのwebhookのままだと、usingで呼び出したモジュールが優先されず、
    # 拡張していない Capistrano::Hook::Web が使用されるので、別名で定義
    def web_hook(url, payload)
      return if url.nil? || payload.nil? || payload.empty?
      info "POST #{url} payload='#{payload}'"
      result = Capistrano::Hook::Web.client(url).post(payload)
      message = "HTTP #{result.code} #{result.message} body='#{result.body}'; "
      if result.is_a?(Net::HTTPSuccess)
        info message
      else
        error message
      end
    end

    desc 'Post a starting message if :webhook_url and :webhook_starting_payload are present'
    task :starting do
      run_locally do
        url     = fetch(:webhook_url)
        payload = fetch(:webhook_starting_payload)
        web_hook(url, payload)
      end
    end

    before 'deploy:starting', 'webhook:post:starting'
  end
end

上記はGemで提供されているrake taskの定義を改変したものです。
もしかすると、web_hook を定義せずにもう少しスマートにできるのかもしれませんが…。

config/deploy/production.rb
# TeamsのIncoming Webhookコネクタへの通知設定
base_paylod_params = {
  '@context' => 'http://schema.org/extensions',
  '@type' => 'MessageCard',
  themeColor: '0072c6',
  title: 'Release Production',
}

set :webhook_url, {取得したIncoming Webhookの通知先リソース}
set :webhook_starting_payload, base_paylod_params.merge(text: 'Now, deploying...')

Incoming Webhookへ渡すパラメータの指定は以下を参考にしつつ、:webhook_url には取得した通知先リソースを指定します。
Part 2: Working With Office 365 Groups And Connectors – Microsoft MVP Award Program Blog

デプロイの実行結果

普段どおり、bundle exec cap production deployを実行すると…。

f:id:t-miyahara11:20180818153224p:plain

無事メッセージが通知されました👌

課題

目標は達成できたものの、経緯を把握していない人がコードを見た時に負債だと感じる面が否めません。
Gemの構造が難しいものではないため、コードを参考にしつつ自前のGemを作成して組み込む、というのが個人的には最適解だと思いました。
いずれ時間ができた時にトライしてみようと思います。

CircleCI 2.0 で ESLint を動かす config.yml の紹介(簡単な解説と実行例も)

はじめに

こんにちは、Web エンジニアの Hayato Yamashita です。

ESLint って便利ですよね。
Prettier と併用してエディタで動くようにすると JavaScript 開発が捗ります。

eslint.org

この ESLint を個人ではなくプロジェクト単位で強制化したい場合、その多くは CI で動かす方法が適しています。
CI を使うことで、個人のエディタの環境に依存することなく、ソースコードの品質を守ることができるからですね。

CI はいくつも種類がありますが、弊社でよく使うのは CircleCI です。

circleci.com

今回の記事では、その CircleCI 2.0 で ESLint を動かすだけの config.yml を紹介します。

とにかく CircleCI 2.0 で ESLint を動かしたい時に適した内容を記載しておきますので、宜しければご活用ください。
簡単な解説と実行例も載せてます。

続きを読む

Ruby on Rails の Active Record で結合先のテーブルを COUNT する場合は joins/left_joins が良い

はじめに

こんにちは、Web エンジニアの Hayato Yamashita です。
最近は暑い日が続き、35度を超える日もあるそうなので、みなさん熱中症に気をつけてくださいね。

さて、今回は Ruby on Rails の Active Record に関する記事をあげます。
Qiita でも投稿している 内容ですが、会社のブログにもあげておきます。

Active Record で COUNT する方法

Ruby on Rails の Active Record って便利ですよね。
SQL から解放されたかのようにデータアクセスできるので、心穏やかに DB と向き合えます。

でも、以下のような SQL をイメージしてデータを取得したい場合はどうでしょうか。

SELECT
  users.*,
  COUNT(thanks.id) AS thanks_count
FROM
  users
  LEFT OUTER JOIN
    thanks
  ON  users.id = thanks.user_id
GROUP BY
  users.id
;

usersthanks は親子関係のテーブルで、 1:n の関係性を持っているケースです。

users に紐づく thanks の件数を取得したいと考え、例えば以下のようなコードを書いてしまうと、よく聞く n+1 問題が発生してしまいます。

User.all.thanks.each { |thank| thank.count }

この対処法としてよく聞くのが eager_loadjoins などです。

eager_load とか joins とか includes とか、いろいろあって何がどうだったかよく分からなくなっちゃいますよね。
それぞれの特徴に関して、私はよく以下の記事を参考にしております。お世話になっております。

qiita.com

結論としては、結合先のテーブルで COUNT をしたい場合は joins が有効です。
eager_load では、結合先テーブルの全カラムを select に入れてしまい、集計関数が使えなくなってしまうためです。

なので、上記の SQL 的なデータアクセスをする場合は、以下のように書きます。

User.all.left_joins(:thanks).group(:id).select('users.*, COUNT(`thanks`.`id`) AS thanks_count')

もし、これを複数箇所で使う場合、さらに他のテーブルなどにも汎用性をもたせたい場合、以下のように Model にメソッド化しておきたいですね。

class User < ActiveRecord::Base
  scope :add_count_column, -> (join_table_name) do
    scope = current_scope || relation
    scope = scope.select("`#{table_name}`.#{Arel.star}") if scope.select_values.blank?
    scope.left_joins(join_table_name).group(:id).select('COUNT(`#{join_table_name}`.`id`) AS #{join_table_name}_count')
  end
end

上記の書き方は、以下の記事を参考にさせていただきました。ありがとうございます。

www.techscore.com

おわりに

Ruby on Rails の Active Record は良いツールではあるのですが、それだけで完結できないこともあります。
どこかで SQL を考え、パフォーマンスを意識しておかないといけないのかもしれません。

今回紹介した joins や、eager_loadincludes を使いこなせると、こうした問題に立ち向かえる力が増すと思いますよ。

Docker 18.03でMySQL5.7コンテナ起動時に[File ./ib_logfile101: 'aio write' returned OS error 122.]メッセージが表示されたときの対処法

クイックスタート・ガイド:Docker Compose と Rails — Docker-docs-ja 17.06.Beta ドキュメント

上記のドキュメントを参考にしつつ、 DockerにてRails + MySQLの環境を構築する際に、 docker-compose run 実行時に下記のエラーメッセージが表示されました。

% docker-compose up
Recreating hoge_db_1 ... done
Recreating hoge_web_1 ... done
Attaching to hoge_db_1, hoge_web_1
db_1   | Initializing database
db_1   | 2018-07-06T02:46:35.367468Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
db_1   | 2018-07-06T02:46:35.379436Z 0 [Warning] Setting lower_case_table_names=2 because file system for /var/lib/mysql/ is case insensitive
db_1   | 2018-07-06T02:46:37.409832Z 0 [ERROR] InnoDB: Operating system error number 22 in a file operation.
db_1   | 2018-07-06T02:46:37.410358Z 0 [ERROR] InnoDB: Error number 22 means 'Invalid argument'
db_1   | 2018-07-06T02:46:37.410704Z 0 [ERROR] InnoDB: File ./ib_logfile101: 'aio write' returned OS error 122. Cannot continue operation
db_1   | 2018-07-06T02:46:37.411234Z 0 [ERROR] InnoDB: Cannot continue operation.
hoge_db_1 exited with code 3

無駄だと思いつつ再度 docker-compose run を実行すると…

% docker-compose up
Recreating hoge_db_1 ... done
Recreating hoge_web_1 ... done
Attaching to hoge_db_1, hoge_web_1
db_1   | Initializing database
db_1   | 2018-07-06T02:47:39.513408Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
db_1   | 2018-07-06T02:47:39.517116Z 0 [ERROR] --initialize specified but the data directory has files in it. Aborting.
db_1   | 2018-07-06T02:47:39.517646Z 0 [ERROR] Aborting
db_1   |
hoge_db_1 exited with code 1

やはりダメですね😥

エラーが発生するdocker-compose.yml

上記のエラーが発生する時の docker-compose.yml の状態は、下記の通りです。

version: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - .mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: hogehoge
    user: '1000:50'
  web: &web_base
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/hoge
    ports:
      - '3000:3000'
    depends_on:
      - db

調査

似たような事例を探してみると「パーミッションの問題」とか「ファイル配置がイケてない」的な意見が多かったのですが、今回は特に当てはまらず...。

MySQL docker 5.7.6 and later fails to initialize database · Issue #69 · docker-library/mysql · GitHub

調査を進めていくと、MySQLの設定に innodb_use_native_aio=0 を追加したほうが良いという書き込みを発見。

Issues with Docker db images on Windows 10 Home · Issue #55 · farmOS/farmOS · GitHub

docker-compose.ymlの修正

下記を参考にして、MySQL起動時のコマンドに上記のオプションを追加してみました。
Bountysource (ここを見る限りDockerのバージョンも怪しいですね…)

version: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - .mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: hogehoge
    user: '1000:50'
    command: --innodb-use-native-aio=0 # <- 追加!
  web: &web_base
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/hoge
    ports:
      - '3000:3000'
    depends_on:
      - db

再度 docker-compose run を実行すると…無事起動することができました🐧

MySQL :: MySQL 5.7 Reference Manual :: 14.6.8 Using Asynchronous I/O on Linux
最初に出力されたログでは、Linuxの非同期I/Oを利用しようと試みた結果、volumes で指定したディレクトリ配下のファイルへの読み書きが正常に行えないことがログに出力されています。
今回はローカル環境ということでオプション指定して対処してみましたが、実運用ではまた異なる設定となるので注意したいと思います。

Unicodeの文字プロパティを指定した正規表現をみてみる(ECMAScript2018)

はじめに

Unicodeは、世界で使われる文字を利用できるようにすることを目的としています。 そのため、ラテン文字はもちろん、漢字、ハングル、キリル文字、タイ文字、(なんと!)絵文字までもがコード化されています。

一方、JavaScript(ECMAScript2018)ではUnicodeの文字プロパティにマッチングする正規表現の書き方が取り入れられます。
 関連:https://github.com/tc39/proposal-regexp-unicode-property-escapes
Unicodeの文字プロパティとは、Unicodeの規格で定められた各コードポイントの属性のことです。

まず簡単に見せると、以下のようなことができるようになります。

// 絵文字が含まれているかチェックする
/\p{Emoji}/u.test("😃");    // → true
/\p{Emoji}/u.test("絵文字"); // → false

// 平仮名が含まれているかチェックする
/\p{sc=Hiragana}/u.test("ひらがな");         // → true
/\p{sc=Hiragana}/u.test("漢字カタカナLatin"); // → false

夢が広がりますね!
本記事では、解説をしたのち、いくつかのサンプルを書いていきます。

※Unicode 10.0を前提にしています。
※本記事にあるサンプルコードは参考程度に留め、自己責任で使用してください。
※Chrome、Safari(記事投稿時点)で使えます。対応状況は、こちらでも確認できます。
http://kangax.github.io/compat-table/es2016plus/

続きを読む

社員旅行でベトナムに行ってきました!

こんにちは!普段はインフラ業務を携わらせて頂いている者です。

最近は雨が多くなってきていよいよ梅雨…と思いきや、いきなり晴れて熱くなったり忙しい天気が多いですね。 温度調整など体調には気を付けてお過ごしください!

弊社はというと…

6月にベトナムのダナンへ社員旅行に行ってまいりました!

f:id:manabkr:20180628101029j:plain
ベトナムに行きました♨

今回はその様子をお伝えしたいと思います!

続きを読む