めるノート

一児の母 兼 へっぽこWebエンジニアの内省ノート

外部サイトにリダイレクトするときは、オープンリダイレクトに注意しよう

すごく当たり前の話なんですけど、最近うっかりやってしまったので反省文として書きます。

まだリリースしていないサービスの初期実装で、現在のURLをパラメータにとって、ログインしたらリダイレクトで戻ってくる処理を作りました。

サービスにアクセスした時、ログインしていなければ、以下のアドレスにリダイレクトされます。
redirect_uri パラメータの中身は、最初にアクセスしたURLになります。

https://www.example.com/login?redirect_uri=https://www.example2.com/

ここで、普段おとなしい同僚から、こっそり声をかけられて、危険ですよ、というアドバイスをいただきました。
実際、作っている時もなんか危ないな、という気はしていたのですが、具体的に何かは思い出せなかったのでした。

そう、URLにある redirect_uri パラメータの中身って、なんにでも変えられちゃうんですよね。以下のように。

https://www.example.com/login?redirect_uri=https://www.cho-warui-site.com/

リダイレクト先をコントロールできてしまい、意図されていないページへのリダイレクトも許してしまいます。
これをオープンリダイレクトといいます。

オープンリダイレクトは、Webアプリケーション自体には問題がないように見えるのですが、たとえば、想定したリダイレクト先のサービスとデザインを似せたページを作って、ログイン情報を再度入力させることができれば、ログイン情報を盗み取れてしまいます。
こうしてフィッシング詐欺などに悪用される恐れがあるのです。
また、リダイレクト先を自由に設定できてしまうということは、HTTPヘッダインジェクションにもつながります。

なので、このようなリダイレクトをするときは

  • なるべくURLパラメータを使わず、リダイレクト先URLのホワイトリストを作って、そこから選ぶようにする
  • どうしてもURLパラメータを利用しなくてはならない場合
    • ドメイン名などパラメータの内容を正規表現等で厳密にチェックする(サブドメイン等も注意)
    • クッションページを設けて、外部サイトに行くよ!と注意喚起をする

以上のことを心がけるのが大切です。

いやはや、リリース前でほんとうによかったです。
今度、会社で徳丸本の輪読会をするので、きっちり読み返します。

参考 https://www.websec-room.com/2014/06/22/1899

「設計やチームビルドについて相談し合う会」に参加しました

参加したきっかけ

旦那さま(@polidog)や上司が参加するということで、こちらのイベントに参加してみました。 ハッシュタグ#dev_sodanでした。

symfony.connpass.com

Presented by 日本Symfonyユーザー会、ということで、自分はたまに社内プロジェクトでSymfonyのコードを読んだりはしますし、結婚するにあたってSymfony2入門を読んで写経したことはありますが、業務での開発はほとんど(Web制作会社でEC-CUBE3くらいしか)したことないんですよね。

しかし、ユーザー会の旦那さまいわく
「今回はSymfonyの話に限らず、表題の通り、設計やチームビルドについて話をするので、来ればいいじゃん」
ということで、参加させていただきました。ありがたや。

会場とか雰囲気

会場は五反田駅から徒歩圏内にあるサイボウズスタートアップスさんでした。

形式としては、15人ほどで輪になって座って、せたさん(@kseta19)の司会で、トピックひとつ30分程度を目安にディスカッションしていく感じでした。
最初は緊張しましたが、おそるおそる発言してもあたたかく受け入れていただける雰囲気だったので、とても楽しかったです。

ディスカッションの内容

会社を立ち上げたのだけれど、2人目のエンジニアが取れない

自分より技術力のある人を採りたい。
現在はツイッターのフォロワーを増やす活動をしており、勉強会の主催・会場提供もする予定。

  • うちは月に40〜50面接やってて、それで1〜2人くらい採用している
    • 2次では技術試験もやっている
  • うちはそんなに面接していないから、書類でめっちゃ落としてるのかも
    • ある程度ニッチな技術を前面に出して売り出した
    • 事業として最終的にどれくらいの数のエンジニアが必要なのか考えてみて
      • 最終的にあまりエンジニア組織を拡大しなくていいなら、ニッチを攻めたほうがいい
    • 2人目のエンジニアは自分より下の人しか取れないと思ったほうがいい
    • 自分たちの技術にふさわしいものはなにか?から考えたほうがいい
    • エンジニアの半分はリファラ
  • 勉強会に行って、出会ったエンジニアと飲みにいく
    • 難しいけど、伸び代のある人をとったほうがいい
    • 勉強会はスポンサーするのもアリだと思う
  • 自社でもくもく会を開いて、よく来る人を口説くとかよさそう
  • 業務委託の優秀な人に紹介してもらうのが良さそう
    • エンジニアにとって働きやすい環境を整えて、認知してもらうとか
  • うちが会社を立ち上げたときは、最初はたまたま成功報酬の媒体で取れた
    • その後は勉強会で会った人を口説いていった
    • 自分は経営とエンジニアの橋渡しを最高品質でやるCTOでいようと意識している

チームとプロダクトが増えたので、うまく情報共有ができなくなってきた

最近、急激にエンジニアが増えてきた。
各システムに分かれて開発をしているけれど、PM同士のつながりがなかったりして、連携している部分を共有できず見落としたりすることがある。
技術の知見や、開発手法もうまく共有できていない気がする。

  • うちは全部のプロダクトを自分(CTO)が見るようにしている
    • すぐに解決するのは難しいので、やっちゃいけないブラックリストを作ると良いかも
  • 実際はシステムが疎結合になっていないのに、チームが疎結合がなってしまっているのでは
  • うちではオフラインの会話が生まれやすい状況を作っている
    • カンバンで、困っていることを話す
    • 開発者がスタンドアップでみんな集まってやっている
  • 月1で報告会をやるようにしていた
  • 毎日6時になると立って、ビジネス側のメンバーも含めて夕会をしている
    • 規模としては15人くらい
    • また、みんなで社内システムを確認する会をしている
      • そこで一番社内システムを知っている人から、歴史的経緯や知らない部分を教えてもらっている
  • ドキュメントはメンテナンスコストがかかるからやめようっていう雰囲気になってる
  • ペアプロやモブプロをすれば、開発手法は共有できそう
  • 自分は、みんなで話すよりも1on1の方が話しやすいかも
    • どちらがいいかは、人によるのでは

新人にOOPを効率的に学んでもらう方法について悩んでいる

これまで「OOPオブジェクト指向プログラミング)ができること」を基準にエンジニアを採用していた。
エンジニアの採用が大変になってきたので、育成枠の採用を始めたいのだけれど、OOPを効率的に学んでもらう方法がないか悩んでいる。

  • イケてる(すぐ育つ)ジュニアエンジニアは、何であれ、すぐにできるようになる
  • うちは学生アルバイトの時給を高くしているので、イケてるアルバイトが取れていると思う
    • そもそもハイレベルな人を取った方がいい
    • 採用するときには、自分で勉強してるかどうかをまず聞くようにしている
  • なんでOOPが必要なのかを、まず教えないといけないのでは
  • うちは技術顧問などの強い人が、ペアプロでしっかりと見てあげている
    • 新卒だとまだ真っ白で我流がないので、いい感じに染まってくれると思う
    • ペアプロをして、ジュニアとシニアが二人三脚で、ひとつのものを開発するようにしている
    • 早い人は3ヶ月くらいでできるようになる
    • 新卒なら育てやすいと思うけれど、いい新卒は「すごいエンジニアと働きたい」と言って他社に取られてしまう
    • 入社して少し経ってから、こういう本を読んだらいいよってすすめる
  • まずは伸びる人を伸ばす、それが成功するとラクになってくる

レビュー地獄から解放されたい

レビュー地獄とは、自分がコードレビューを担当するエンジニアが多すぎるなどの原因により、レビューしなければならないPull Requestが多すぎて、コードレビューだけで1日をほとんど使い果たしてしまい、他の仕事ができなくなってしまうこと。

  • 先ほどの「まずは伸びる人を伸ばす」に通ずるけど、自分はレビューできる人を増やして、チームを分割した
    • 最近は楽になった
  • うちはコードレビューを担当するリーダーはいなくて、レビュイーがコードを見てもらいたい人に依頼する
  • うちは、くじびきでレビュアーを決めている
  • レビューに時間がかかるのは、その前に設計について相談していないからでは
    • プルリクエストで突然のRejectをくらわないようにちゃんと相談しよう
    • 自分の設計に対して不安に思っていれば相談するのでは
    • 相談しないということは、問題ない設計だと思っていたのでは
    • 設計を考える目的がシェアできているかどうか
  • うちは機能拡張するときに困らないように設計する、という考えがシェアされている
    • 本当に欲しいものは何なのか、長い時間をかけてビジネス側にきくようにしている
    • サービスレベルに影響するかどうか、影響するところは設計を含めてきっちり作っていく
    • 他のところはスピード感重視でやっていくので、リファクタリングデーを別途設けている

フロントエンドの設計をどうしたらいいのか分からない

  • props地獄がつらい
  • フロントエンドの設計には3種類ある気がする
  • そもそも、捨てるためのフロントエンドを作っていくべき
  • どんなテストを書くのが適切なのか
    • UIのテストはメンテが大変
  • Vue.js入門の後半に設計手法が書かれていてとても良かった
    • Atomic design
      • デザイナーの傾向として、このようなプログラマブルな考え方を受け入れない人が多いかも?
  • 後悔しないためのVueコンポーネント設計も良かった

まとめ

上記の他にも、自分からは「女性エンジニア(がチームにいることについて)どう思いますか?」というテーマをあげました。
燃えやすい話題なので詳細は省きますが(笑)、参加者のみなさんからいい話がたくさん聞けました。

また、全体的に、せたさんを始めとしたスタッフの皆さんがいい感じにディスカッションを進めてくださったので、非常に有意義で楽しかったです。

エンジニアとしてのキャリアが浅くて、まだまだ手を動かすエンジニアとしてやることがたくさんあると思っている身としては、「設計」と「チームビルド」というのは非常にハードルが高いトピックです。
だって、設計といえばマサカリが絶えず飛び交っていて怖いイメージですし(ひどい偏見)、チームビルドのようなマネジメント的な話は、(マネジメントも問題解決なのに)どうしてもエンジニアリングな話と対立しがちですから。

けれど、自分のフェーズがちょうど

  • 動くものは書けるけど、書いていて「この設計ではいけない」と思うことが増えたので設計の勉強をしている
  • チーム内のコミュニケーションで先輩っぽい振る舞いをすることが増えてきたので、全体最適を考えている

というのもあったので、ある程度長い経験をお持ちの「CTO」や「マネージャー」、「シニアエンジニア」などの皆さんと「設計」や「チームビルド」について非常に濃いお話をすることができて、ほんとうによかったです。ありがとうございました!

別々のdocker-composeに属するコンテナ同士で疎通させる

もう3ヶ月くらい前になるのですが、Dockerを雰囲気でやっていた自分が、ローカル上でそんなことをすることになりました。
当時の自分がこのタイトルを見ても「ハァ???」みたいな気持ちになったと思います。。。

dockerを理解するために読んだもの、読みたかったもの

最初の時点で、インフラに詳しいメンバーから
「たぶん network を設定するんだと思いますよー」
と言われていたんですが、とにかく自分はDockerについての知識が浅かったので、まずは docker-compose.yml が読めるようになるまで勉強してみました。

読んだもの

読みたかったもの

いろいろ勉強して解決した後に読んだけど、これを先に読みたかったw

booth.pm

booth.pm

docker-compose のnetworkについて

DockerとComposeの基本的なところを勉強したら、インフラに詳しいメンバーから教えてもらった network について調べつつ、疎通させる方法も一緒に調べていきました。

以下のドキュメント・記事を参考にさせていただきました。

Compose のネットワーク機能 — Docker-docs-ja 17.06.Beta ドキュメント

qiita.com

qiita.com

qiita.com

o21o21.hatenablog.jp

疎通の方法について

ということで、上記の記事と同じような話にはなりますが、別々のdocker-composeに属するコンテナ同士で疎通させる方法についてです。

それぞれ docker-compose がdefaultで持っているnetworkに加えて、疎通するための新しいdocker networkを作成します。

docker network create common_network

と、自分だけが開発しているものであればこれでいいのですが、開発しているRailsアプリケーションは、docker-composeコマンドを含めた一連の立ち上げコマンドをMakefileでまとめています。
なので、コンテナとRailsサーバーを起動するmakeコマンドの中に、上記のnetwork作成コマンドを入れておきました。

次に、docker-compose.ymlにnetworkまわりの設定を追加します。
今回は両方のdocker-compose.ymlのservice nameが全く同じ構成だったので、aliasを追加しました。

ひとつめのdocker-compose.yml

version: '3'
services:
  db:
    image: mysql:5.7
    ports:
      - "3306"
    environment:
      MYSQL_ROOT_PASSWORD: "hogehoge"
    volumes:
      - db:/var/lib/mysql
  web:
    build:
      context: .
      dockerfile: docker/web/Dockerfile
    command: bundle exec rails server --port=3000 --binding='0.0.0.0'
    volumes:
      - .:/first_web
      - bundle:/usr/local/bundle
      - ./node_modules:/second_web/node_modules:delegated
    ports:
      - "3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
    networks:
      default:
      common_network:
        aliases:
          - first_web
volumes:
  bundle:
  db:
networks:
  common_network:
    external: true

ふたつめのdocker-compose.yml

version: '3'
services:
  db:
    image: mysql:5.7
    ports:
      - "3306"
    environment:
      MYSQL_ROOT_PASSWORD: "hogehoge"
    volumes:
      - db:/var/lib/mysql:delegated
  web:
    build:
      context: .
      dockerfile: docker/web/Dockerfile
    command: bundle exec rails server --port=3000 --binding='0.0.0.0'
    volumes:
      - .:/second_web
      - bundle:/usr/local/bundle
      - ./node_modules:/second_web/node_modules:delegated
    ports:
      - "3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
    networks:
      default:
      common_network:
        aliases:
          - second_web
volumes:
  bundle:
  db:
networks:
  common_network:
    external: true

RailsのAction CableとWebpackerとVue.jsを使ってチャットを作成してみる

もう2ヶ月くらい前になりますが、Action Cableを用いたチャットの実装にチャレンジする機会がありました。
このときには、Railsの中でVue.jsをどう扱うのかについての一例を見ることもできたので、記念(?)にざっくりしたコードを残しておきます。

1. rails newと、webpackerでVueの初期化

rails new したあと、Gemfileに gem "webpacker" を追記し、bundle install した後、以下のコマンドを実行します。

$ bundle exec rails webpacker:install
$ bundle exec rails webpacker:install:vue

2. モデルを作る

$ bundle exec rails db:create
$ bundle exec rails g model message content:text user_id:integer
$ bundle exec rails g model user name:string email:string
$ bundle exec rails db:migrate
class User < ApplicationRecord
  has_many :messages, dependent: :restrict_with_error
end
class Message < ApplicationRecord
  belongs_to :user

  validates :content, presence: true

  after_create_commit { MessageBroadcastJob.perform_later self }
end

3. コントローラの作成

class HomeController < ApplicationController
  def index
    @messages = Message.all
  end
end

4. Actioncableのインストール

$ yarn add actioncable

5. Viewを作る(Vueコンポーネントの実装)

app/javascript/packs/application.js

import Vue from 'vue/dist/vue.esm'
import UserChat from '../components/user-chat.vue'
import ActionCable from 'actioncable';

const cable = ActionCable.createConsumer('ws:localhost:3000/cable');
Vue.prototype.$cable = cable;

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#main-container',
    data: {},
    components: { UserChat }
  })
})

app/javascript/packs/components/user-chat.vue

<template>
<div>
  <div>
      <div v-for="item in messages" :key="item.message.id">
        <div>
          <div>{{ item.message.content }}</div>
      </div>
    </div>
  </div>
  <div>
    <div>
      <input type="text" v-model="message" placeholder="入力してください ...">
      <span>
        <button type="button" v-if="userMessageChannel" @click="speak">Send</button>
      </span>
    </div>
  </div>
</div>
</template>

<script>
export default {
  data() {
    return {
      message: "",
      messages: [],
      userMessageChannel: null,
    };
  },
  props: ['userId'],
  created() {
    this.userMessageChannel = this.$cable.subscriptions.create( "UserMessageChannel", {
      received: (data) => {
        this.messages.push(data)
        this.message = ''
      },
    })
  },
  methods: {
    speak() {
      this.userMessageChannel.perform('speak', { 
        message: this.message,
        user_id: this.userId,
      });
    },
    // 以下、今回のViewでは使っていませんが、LINEのように自分からのメッセージと他ユーザーからのメッセージでスタイルを分けるときなどに使います
    messageClass (user_id) {
      return {
        "right": user_id === Number(this.userId)
      }
    },
    dataClass (user_id) {
      return {
        "float-right": user_id === Number(this.userId)
      }
    }
  },
};
</script>

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>VueActioncable</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all' %>
    <%= javascript_pack_tag 'application' %>
  </head>

  <body>
    <div id="main-container">
      <%= yield %>
    </div>
  </body>
</html>

app/views/home/index.html.erb

<% @messages.each do |message| %>
  <div>
    <%= message.content %>
  </div>
<% end %>
<!-- current_userはdevise gemを入れるなどでログイン実装することによって使えます -->
<user-chat user-id="current_user.id"></user-chat>

6. チャンネルを作る

$ bundle exec rails g channel user_message speak
class UserMessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "user_message_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
  
  def speak(data)
    Message.create!(
      user_id: data['user_id'],
      content: data['message']
    )
  end
end

7. Jobの作成

$ bundle exec rails g job MessageBroadcast
class MessageBroadcastJob < ApplicationJob
  queue_as :default

  def perform(message)
    ActionCable.server.broadcast "user_message_channel", message: message
  end
end

8. ルーティングを追加する

Rails.application.routes.draw do
  root to: 'home#index'

  mount ActionCable.server => '/cable'
end

以上です。

参考にさせていただいた情報

qiita.com

qiita.com

qiita.com

Railsで、Action Mailerとletter_opener_webを初めて使いました

Action Mailer、すごくベーシックな実装なはずなのに、なんか、今まで縁がなかったんです。
Railsチュートリアルでやったのかな? そのへんも記憶にない...

ということで今回は、ローカル環境でletter_opener_webを使ってメールを確認するところまで、やっていきます。

1. Gemfileletter_opener_webを追加する

group :development do
  gem 'letter_opener_web', '~> 1.0'
end

追加したら、コマンドラインbundle installします。

$ bundle install

メール確認用画面を、config/routes.rb に追加します。

Rails.application.routes.draw do
  root to: "home#index"

  # 省略...

  mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
end

2. config/environments/development.rb に設定を追加

Rails.application.configure do

  # 省略...

  config.action_mailer.perform_caching = false

  config.action_mailer.default_url_options = { host: 'localhost:3000' }
  config.action_mailer.delivery_method = :letter_opener_web

  # 省略...
  
end

production.rbにはメールの設定なんかを書いたりしますが、今回は割愛。

3. Mailer を作成する

コマンドラインで生成します。

$ rails g mailer UserMailer

生成したメーラーを、必要に応じて調整します。

app/mailers/user_mailer.rb

class UserMailer < ActionMailer::Base
  default from: "hoge@gmail.com"

  def send_message_to_user(user)
    @user = user
    mail to: @user.email,
         subject: "メールの件名が入ります"    
  end
end

4. メール本文を作成する

app/views/user_mailer/send_message_to_user.text.erb を作成します。
メソッド名.text.erb という名前になるので注意しましょう。 ファイルの内容は以下のような感じです。

<%= @user.name %> さま

いつもお世話になっております。
株式会社●●です。
この度は、キャンペーンにご応募いただきまして、ありがとうございました。
当選発表は、商品の発送をもってかえさせていただきます。

株式会社●●

5. メール送信処理

メール送信をしたいタイミングで、Mailerのメソッドを実行します。

class EntryController < ApplicationController

  def create
    @entry = Entry.new(entry_params)
    if @entry.save
      # ここでメールを送信する
      UserMailer.send_message_to_user(@entry.user).deliver_now
      redirect_to root_url
    else
      render 'new'
    end
  end
end

6. メールが送られたのか確認する

最初に config/routes.rb に追加した、メール確認用画面を開きます。 http://localhost:3000/letter_opener を開くと、以下のようが画面が開き、送信処理があったメールを一覧できます。(実際に送信されているわけではありません)

f:id:c5meg1012:20180729195634p:plain

レバテックキャリアさんに、前職在籍時に書いた記事を取り上げていただいた

レバテックキャリアさんの記事はこちらです。 また、取り上げていただいた記事そのものはこちらです。

c5meru.hatenablog.jp

2017年2月21日、ということで、ちょうど1年と5ヶ月前の記事になりますが、取り上げていただけるなんて、ありがたい話ですね。
Web制作会社と自社サービス会社の違いについて、少しでも知る手がかりになれば幸いです。

さて、偶然なことに、まさに今も「転職して4ヶ月くらい経った」タイミングだったりします。
今回は「自社サービス開発」どうしの転職ではあるのですが、「入社当時 社員5名未満の超スタートアップ」から「上場後で社員300名超のミドルベンチャーの転職です(ただし某ブラックホールではありません、フロア違いです 笑)。
エンジニアの規模としては、3人から11人(インターン・アルバイト含む)に変わりました。
ですので、ついでに、今回の転職で変わったことを書いてみようと思います。

以前やっていたこと

上記の記事から抜粋。

現在やっていること

  • CSSレビュアー
  • Vue.js実装・レビュアー
  • Ruby on Rails実装(初期実装ほぼ全体)
  • インフラのお勉強

変わったこと

技術に集中できるようになった

現在の会社では「ディレクター」や「デザイナー」がいらっしゃるので、サービスの方針とか、デザインの方針とかを考えるのはお任せする感じになりました。もちろん、発言権がないわけではなく、意見があれば都度相談することもあります。
ですので以前よりは、技術のことを考える時間が増えました。あとは、コードレビューでレビュアーになることもグッと増えました。

プロジェクト内での役割について考えるようになった

以前はほとんど1人でフロントエンドもサーバーサイドも実装し、軽く上長に相談する程度で開発を進めていたのですが、上記に伴い、いろんな役割の人がいるので、たとえば
「これはディレクターにも相談するべきなんだっけ」
とか、
「ここはデザイナーに調整してもらった方がいいんだっけ」
とか、考えるようになりました。 なので、良くも悪くも「テキトーに、いい感じにやっとけばいいや」っていうふうに進められなくなったな、と思います。

専門家に相談できる強さがある

これも上記に関連するのですが、現在は、フロントエンドも、サーバーサイドも、インフラも、それぞれ専門家がいるので、相談相手に困らなくなりました。
なので、現在自分が関わっているプロジェクトには、ディレクターやデザイナー含め、たくさんの専門家がメンバーをサポートしてくださっています。ありがたい話です。

まとめ

やはり人数がいる強さってすごいな、に尽きます。
ただ、自分は現在ゼネラリストとしてやっているところがあるので、これ以上大きい組織になった時に、専門家でない自分が果たして役に立てるのかな...?なんて迷ったりすることもあります。
インフラお勉強中なので、ひと通り慣れてきたら、自分の道を決めるタイミングになるのかもしれません。
とはいえそれでも優秀なエンジニアさんは、自分の専門外の技術もある程度キャッチアップしていたりするので、そういうところを見習いながらやっていきたいな、と思っています。

PercelでVueを動かしてみた

職場で話題になったので動かしてみました。すごく簡単でした。
調べてみると、流行った頃の日本語の記事がいろいろ記事が出てきたんですが、Percel側もあれから開発が進んで、ますます簡単になっていたようです。

qiita.com

こんな感じなので、日本語の記事を参考にするより、公式ドキュメントを見た方がはるかに簡単でしたw

parceljs.org

ということで、早速やってみます。
まずは、プロジェクトルートのディレクトリを作ります。
今後はその中で作業していきます。

公式ドキュメントに書いてある通り、まずは Vueparcel-bundler をインストールします。

$ npm install --save vue
$ npm install --save-dev parcel-bundler

そして、 package.jsonscript を追加します。

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "parcel index.html"
  },

初期設定はこれだけ。
あとは、プロジェクトルートにビルドしたいファイルを追加していきます。

index.html

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>parcel-vue</title>
</head>

<body>
  <div id="app"></div>
  <script src="app.js"></script>
</body>
</html>

app.js

import Vue from 'vue/dist/vue.esm.js';
import Hello from './vue/hello.vue'

const app = new Vue(Hello);
app.$mount('#app');

vue/hello.vue

<template>
  <p>
    {{ message }}
  </p>  
</template>

<script>
  module.exports = {
    data: function() {
      return {
        message: 'Hello, Percel-Vue!!',
      }
    }
  }
</script>

<style scoped>
  p {
    font-size: 64px;
    text-align: center;
    color: #3eaf7c;
  }
</style>

ビルドしたいファイルを追加したら、先ほど package.json に追加した script で動かしてみましょう。

$ npm start index.html

Server running at http://localhost:1234 の文字が出たら、 http://localhost:1234 から確認できます。

f:id:c5meg1012:20180715143641p:plain

Webpackのconfigには自分も悩まされてきたので、Percelの今後に期待です。

RailsのModelで定義するlengthのvalidationについて、activemodelのコードを見てみる

今日はさっぱりめ。
Railsでmodelを書くときって、こんな感じに書くと思うんですが、

class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 30 }
  validates :email, presence: true
end

昨日お仕事で length のカスタムバリデーションを書きたくなり、このデフォルトの length の実装ってどうなってるんだろう? と思ったので、調べてみました。

該当箇所はこちら。

github.com

7/7 10時現在のソースは以下。

# frozen_string_literal: true

module ActiveModel
  module Validations
    class LengthValidator < EachValidator # :nodoc:
      MESSAGES  = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
      CHECKS    = { is: :==, minimum: :>=, maximum: :<= }.freeze

      RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long]

      def initialize(options)
        if range = (options.delete(:in) || options.delete(:within))
          raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
          options[:minimum], options[:maximum] = range.min, range.max
        end

        if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
          options[:minimum] = 1
        end

        super
      end

      def check_validity!
        keys = CHECKS.keys & options.keys

        if keys.empty?
          raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option."
        end

        keys.each do |key|
          value = options[key]

          unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) || value.is_a?(Proc)
            raise ArgumentError, ":#{key} must be a nonnegative Integer, Infinity, Symbol, or Proc"
          end
        end
      end

      def validate_each(record, attribute, value)
        value_length = value.respond_to?(:length) ? value.length : value.to_s.length
        errors_options = options.except(*RESERVED_OPTIONS)

        CHECKS.each do |key, validity_check|
          next unless check_value = options[key]

          if !value.nil? || skip_nil_check?(key)
            case check_value
            when Proc
              check_value = check_value.call(record)
            when Symbol
              check_value = record.send(check_value)
            end
            next if value_length.send(validity_check, check_value)
          end

          errors_options[:count] = check_value

          default_message = options[MESSAGES[key]]
          errors_options[:message] ||= default_message if default_message

          record.errors.add(attribute, MESSAGES[key], errors_options)
        end
      end

      private
        def skip_nil_check?(key)
          key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
        end
    end

    module HelperMethods
      # Validates that the specified attributes match the length restrictions
      # supplied. Only one constraint option can be used at a time apart from
      # +:minimum+ and +:maximum+ that can be combined together:
      #
      #   class Person < ActiveRecord::Base
      #     validates_length_of :first_name, maximum: 30
      #     validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
      #     validates_length_of :fax, in: 7..32, allow_nil: true
      #     validates_length_of :phone, in: 7..32, allow_blank: true
      #     validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
      #     validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
      #     validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
      #     validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.'
      #
      #     private
      #
      #     def words_in_essay
      #       essay.scan(/\w+/)
      #     end
      #   end
      #
      # Constraint options:
      #
      # * <tt>:minimum</tt> - The minimum size of the attribute.
      # * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
      #   default if not used with +:minimum+.
      # * <tt>:is</tt> - The exact size of the attribute.
      # * <tt>:within</tt> - A range specifying the minimum and maximum size of
      #   the attribute.
      # * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
      #
      # Other options:
      #
      # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
      # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
      # * <tt>:too_long</tt> - The error message if the attribute goes over the
      #   maximum (default is: "is too long (maximum is %{count} characters)").
      # * <tt>:too_short</tt> - The error message if the attribute goes under the
      #   minimum (default is: "is too short (minimum is %{count} characters)").
      # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
      #   method and the attribute is the wrong size (default is: "is the wrong
      #   length (should be %{count} characters)").
      # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
      #   <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
      #   <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
      #
      # There is also a list of default options supported by every validator:
      # +:if+, +:unless+, +:on+ and +:strict+.
      # See <tt>ActiveModel::Validations#validates</tt> for more information
      def validates_length_of(*attr_names)
        validates_with LengthValidator, _merge_attributes(attr_names)
      end

      alias_method :validates_size_of, :validates_length_of
    end
  end
end

パッと見「ハテ?」という気持ちにもなりますが(笑)、自分たちが普段Modelで定義しているのは、最初の方に書いてある以下の部分ですね。

MESSAGES  = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
CHECKS    = { is: :==, minimum: :>=, maximum: :<= }.freeze

この MESSAGESCHECKS が使われているところから読んでみたらわかりやすかったです。

あとは、 validate_each メソッドの中にある、以下の部分でデータの length を出しているんだな、とか。

value_length = value.respond_to?(:length) ? value.length : value.to_s.length

そしてこの後に続いて、値のチェックをしたり、エラーを出したりしてるんだなー、とか。

CHECKS.each do |key, validity_check|
  next unless check_value = options[key]

  if !value.nil? || skip_nil_check?(key)
    case check_value
    when Proc
      check_value = check_value.call(record)
    when Symbol
      check_value = record.send(check_value)
    end
    next if value_length.send(validity_check, check_value)
  end

  errors_options[:count] = check_value

  default_message = options[MESSAGES[key]]
  errors_options[:message] ||= default_message if default_message

  record.errors.add(attribute, MESSAGES[key], errors_options)
end

RailsにおけるModelのvalidationは日々当たり前に使っている部分ですが、より身近なものに感じることができました。

Pythonの素人がDjangoアプリケーションをlocalhostで立ち上げるまで

これまで全くPythonは触ったことなかったのですが、機械学習ブームだったり、Ruby25のイベントでMatzさんがPythonの話をしてたりしたので、すこし興味がありました。
RubyPHPJavaScriptと同じスクリプト言語だし、ちょっと見てみたいな、と思ったところで、こんな本が出ていました。

www.instagram.com

スラスラ読める Pythonふりがなプログラミング

「ふりがなプログラミング」シリーズは話題になっていたので個人的に欲しいなと思っていたのですが、せっかくなら未履修の言語をやってみたいと思ったので、JavaScriptではなくPythonの方を買ってみました。

内容はプログラミング未経験者向けなので、変数や関数、リストやループなどの基本的なしくみが、Pythonの文法も含めて、しっかり解説されていました。
このあたり、自分は学生時代にJavaの入門書で独学したのですが、ここまでやさしくは説明されていなかったので、結構大変だった記憶があります。いい時代になったなあ。

Pythonの文法は、SlimやSass(not Scss)、CoffeeScriptなどのように基本閉じなくてよくて、インデントやスペースで制御されている感じなのが楽でいいなーと思いました。
あとは前述の通りスクリプト言語なので、普段RubyJavaScriptをやっている自分でも、ハードルに感じた内容は特になかったように思います。解説もやさしかったし。

Djangoを使ってみる

さて、本題に戻ります。Djangoですね。
今のところ自分は機械学習に対してハードルを感じているので、RailsやLaravelみたいなアプリケーションなら触れそうだと思ったのが、Djangoを触ってみようと思ったきっかけです。

ただ、Ruby on RailsやVue CLI、Nuxt.jsみたいな爆速initに慣れてしまった身には、Djangoの公式ドキュメントが少しハードル高めに感じたので(笑)、最終的にはDjango Girls Tutorialを参考に進めていくのが良かったです。

並行して、日本語では以下の記事を参考にさせていただきました。

qiita.com


pythonのインストール

自分は別のプロジェクトの関係で direnvpyenv を使っていました。 なので、まずは pyenv を使って3系のPythonをインストールしました。

$ pyenv install 3.6.1

次に、プロジェクト用のディレクトリを作成します。

$ mkdir django-tutorial
$ cd django-tutorial

direnv を使うために、以下の記述のある .envrc を作成します。

pyenv local 3.6.1

あとは、以下のコマンドを使えば、このプロジェクトの中だけでpython3.6.1を動かすことができるようになります。

$ direnv allow

Python仮想環境のためのvirtualenv導入

ここでは以下の記事を参考にさせていただきました。

qiita.com

上記の通りに進めていきます。

$ sudo easy_install virtualenv

# すでにプロジェクト用のディレクトリ内にいるため、カレントディレクトリで実行
$ virtualenv --no-site-packages .
$ source bin/activate

Djangoプロジェクトの作成

Django Girls Tutorialにしたがって、Djangoをインストールします。
Django自体は、もっと新しいバージョンが出ているようですね。

$ pip install django~=1.11.0

Djangoのプロジェクトを作成します。

$ django-admin.py startproject mysite .

migrateします。

$ cd mysite
$ python manage.py migrate

ここまで進んだら、以下のコマンドでローカルサーバーを立ち上げることができます。 http://127.0.0.1:8000/ から確認してみましょう。

$ python manage.py runserver

ローカル環境を立ち上げるのはここまでですが、Django Girls Tutorialではmodelやviewの作成など、実際にPythonを用いて定義していく部分も含まれますので、Pythonを文法をサッと学んだ身としては、ここからが醍醐味だなあ、という感じです。

Electronのwebviewでブラウザ版Slackを見ようと挑戦して詰んだ話

f:id:c5meg1012:20180624123438p:plain

公式アプリがあるのに、どうして車輪の再発明なことをわざわざやろうと思ったのか?
それは、これがやりたかったから。

qiita.com

もちろん、会社Slackなどの重要事項が流れてくるTeamではやりませんが、
基本ROMってるTeamで、SNS疲れみたいな気持ちになりたくないなーというのがありまして。
あと、Electronが未履修なので一回触っておきたいな、と思いました。

そういうことで、まずはElectronを動かしてみます。

electronjs.org

上記の公式サイトでQuick Startをやってみます。

# Clone the Quick Start repository
$ git clone https://github.com/electron/electron-quick-start

# Go into the repository
$ cd electron-quick-start

# Install the dependencies and run
$ npm install && npm start

こんな感じで動きました。超簡単!

f:id:c5meg1012:20180624123605p:plain

続いて、webviewを試してみましょう。
以下は公式ドキュメントです。

electronjs.org

こちらにある以下の部分を、ルート直下にある index.html にコピーしてみます。

<webview id="foo" src="https://www.github.com/" style="display:inline-flex; width:640px; height:480px"></webview>

不要なところは削除して、こんな感じ。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <webview id="foo" src="https://www.github.com/" style="display:inline-flex; width:640px; height:480px"></webview>
    <script>
      // You can also require other files to run in this process
      require('./renderer.js')
    </script>
  </body>
</html>

再度 npm start で動かしてみます。

f:id:c5meg1012:20180624123720p:plain

おお、GitHubのページが見られましたね!
webviewの幅や高さが微妙なので、CSSを追加します。

html {
  width: 100%;
  height: 100%;
}
body {
  margin: 0;
  padding: 0
  height: 100%;
}

CSSを読み込むため、 index.html<link rel="stylesheet" href="style.css"> を追加します。
そして、webviewの src 属性を、 https://www.github.com/ から https://slack.com/signin に変更してあげます。

これで再度 npm start してみましょう。

f:id:c5meg1012:20180624123803p:plain

おお、なんかそれっぽくなってきました。
試しにログインしてみます。けれど、サインインした瞬間にフリーズしますね...😭

ひとまず表題の件については今回はここまで。
フリーズの原因がわかったら続きをやっていきたいと思います。笑