外に出るねくら

~ 外に出たって結局やることは自宅と同じ ~

Rails 5.2のCredential管理方法

背景

RailsからS3にファイルをアップロードするときに、認証情報を環境変数に持たせようとしていたが、どうやらそれをする必要はないということがわかった。
今回はそれをまとめます。

環境情報は以下。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.4 LTS"

$ ruby --version
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]

$ rails --version
Rails 5.2.3

何をしようとしていたのか

ActiveStorageを使ってアップロード機能を実装しようとしていましたが、storage.ymlの編集時に以下の記載がありました。

amazon:
 service: S3
 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
 secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
 region: ${region}
 bucket: ${your_own_bucket}

Rails.application.credentials.digとは。
AWS Access Keyとかはてっきり環境変数に保持して、ENV[AWS_ACCESS_KEY]で参照するつもりでいたので、プチ想定外。

もちろんもともとの想定で実装を進めることも可能ですが、せっかくなのでちゃんと調べてみることに。

結局何なのか

Rails 5.2から追加されたCredentialの管理方法。 普通にRails Guideのリリースノートを見た。
config/credentials.yml.encconfig/master.keyの2つが登場人物。
平文の認証情報をconfig/master.keyで暗号化し、その内容がconfig/credentials.yml.encに保存される。
Rails.application.credentials.digconfig/credentials.yml.encにアクセスする。

gitとかに上げるときは、config/credentials.yml.encを上げて、config/master.keyは上げない。

こうすることで一元的に管理することができる。
いちいち環境変数に保存したり、環境変数から参照する手間が省ける。

どうすればいいのか

コンソールで以下のコマンドを実行する。
EDITOR=vim bin/rails credentials:edit

secret_key_baseはそのままにして、awsの情報を追記。
${access_key_id}と${secret_access_key}には各々の情報を記載する。

aws:
  access_key_id: ${access_key_id}
  secret_access_key: ${secret_access_key}

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: ${secret_key_base}

config/credentials.yml.encの中を見てみると内容が変わっている。
以上

ファイルをS3にアップロードするときはサーバー?クライアント?

背景

クライアントサイドとサーバーサイドを分離して、JSONでデータのやり取りをするのが昨今のアプリケーションのトレンド。
クライアントサイドからファイルをアップロードするのがいいのか、サーバーサイドからファイルをアップロードするのがいいのか、どっちがいいのかとふと思いました。
最近はS3に画像データ保存することが多いので、S3にデータを保存するとしたらどうするかを考えます。

サーバーサイドからアップロードする場合

ファイル送信を行う際に取りうる選択肢

ファイル送信を行う際には、multipart/form-dataBase64 Encodeのどちらかが使われるのが一般的

multipart/form-data

HTTPリクエストのヘッダに付与されるContent-Type。
HTTPでファイル送信を行う際の王道。
大体のWebアプリケーションのフレームワークでは、この方法で送られてきたファイルをデコードする仕組みを持っている。
複合データ型であることを意味していて、1回のHTTP通信で複数の種類のデータ(text, file etc...)を扱うことができる。
バウンダリ文字列という境界を定義して、その境界を使ってリクエストボディを複数のパートに区切ってデータを送信する。

  • 良いところ
    バイナリを扱える
    王道

  • 良くないところ
    JSONベースの通信で使えない

Base64 Encode

Base64エンコードして文字列にし、JSONでサーバーへ送る方法。
Base64ってのは、全てのデータを英数字と記号(+/)で表現したもの。
なんでBase64なの?みたいな話は「base64ってなんぞ??理解のために実装してみた」って記事がいい感じでした。
multi-part/form-dataほど王道というわけではないが、GitHubTumblrなどのAPIで利用実績があり、それなりに安心して採用できる手法。

  • 良いところ
    文字列として扱えるのでJSONベースの通信と相性バッチリ
    それなりに安心して使える

  • 良くないところ
    データ容量が大きい
    エンコードとデコードのコストが高い(場合によっては自前で処理を書くかも)

取りうる選択肢

  1. multi-part/form-dataを使うなら、画像をアップロードするエンドポイントを切り出す。
    そもそもJSONで使えないし。
    その場合は、アップロードしたレスポンスでIDを返してもらって、そのIDを保存したいリソースに紐づけて再度サーバーにリクエスト投げる、って感じになるのかな。
  2. Base64 Encodeを使う。
    JSONで使えるけど、処理書くのめんどくさそう。
  3. そのリソースに関する部分だけそもそもJSONの通信やめちゃう?(← 割と一番実装楽そうだったりする)
    そのリソースに関する部分はmulti-part/form-dataでやり取りするということ。

クライアントサイドからアップロードする場合

処理は軽いだろうけど

やっぱりセキュリティのリスクあるよなぁ。
特にS3に保存するってなるとAWS Access Keyとかの取得処理をクライアントに持たないといけなくてかなりリスクあると思う。

と思ったらなんかいけそうなのある

ググったら色々出てきた。
Amazon S3 Browser Upload」とか「Vue.jsからS3に直接ファイルをアップロードする」とか。
Amazonが提供しているSDKの中に、S3への画像アップロードをサポートしてくれるやつがあるんだと。
ここからはちょっとRubyの話。
PresignedPostというのがあるらしい
AWSが提供しているSDKクラスリストを見てみるとS3にアクセスするためのキーを発行できそうな感じ。
Pre signedなので仮なんだろうなという感じはするが・・・
ただ、PresignedPostを直接使うんじゃなくて、Bucket::presigned_postを使った方がいいとも書かれている。
そちらを見てみるとBucket::presigned_postPresignedPostインスタンスを簡単に作ってくれるらしい。
expiresオプションをつけることで有効期限をつけることができるようで、要はワンタイムURLみたいなのを生成してくれるみたい。
これ使えばクライアントサイドからS3に直接アップロードでもさっきの懸念はなさそう。

クライアントサイドからアップロードする場合の処理のイメージ

サーバーでワンタイムURLを作り、クライアントに返す。
クライアントはそれを受け取って、Fileオブジェクトを乗せてS3にリクエスト。
S3からレスポンスとしてアップロード先のkeyなどの情報が入ってるので、保存したいリソースに紐づけて次はサーバーにリクエスト。
・・・
ってこれmulti-part/form-dataの処理と変わらない。
むしろS3との通信は成功したけど、サーバーとの通信失敗しましたってなったときにS3のデータとサーバーのデータで若干不整合が生じている気がする。
それが許されるならそれでいいけど。

結論

クライアントでアップロードするっていう選択肢はなさそう。
サーバーでアップロードする場合は

  1. multi-part/form-dataを使う。画像をアップロードするエンドポイントを切り出す。
    これはクライアントからアップロードする場合と同じ問題を孕んでいる気がする。っていうかそう。
    画像はアップロードできたけど、リソースの保存リクエスト失敗しましたって、S3がサーバーに変わっただけで構造的には同じ。
  2. Base64 Encodeを使う。
    最初はめんどくさいけどエンコード/デコードの処理は一度作れば使いまわせるはず。
    そしたら何も考えずJSONでデータのやり取りができるので幸せになれそう。
  3. そのリソースに関する部分だけそもそもJSONの通信やめちゃう。
    最初楽できそう。だけど同じあとで苦労しそう。

悩むなら2と3。
時間との相談な気がする。

なんか妙な結論になりましたが、本日はここまで。 それぞれのパターンで実装してみようかな。

同じモデルに対する外部参照キーを別名で複数持つ方法

同じモデルに対する外部参照キーを複数持つ方法

今週に入って、だいたい10度前後を推移していた(高い時は20度近かった)のに、この日の最高気温2度。
寒すぎ。
どうでもいいですね。
さて、今回は同じモデルに対する外部参照キーを複数持つ方法、そして外部参照キーの名前をデフォルトから変更する方法を一気にやります。

言葉だけだとわかりにくいのでイメージを説明します。

何がしたいのか

f:id:Kenny27:20190209090943p:plain
あるユーザは0以上のPostを持っている

あるユーザが複数のPostを持っているようなモデルを想像する。
誰から誰に投稿したのか、どのグループに投稿したのかを保持したくて

  • from_user_id
  • to_user_id
  • posted_group_id

を持たせた。

from_user_idto_user_id がともにUserを参照してほしい。
という感じです。

HOW TO

テーブル作成時にカラム名を変更しておく

外部参照をするときは、

class CreatePosts < ActiveRecord::Migration[5.1]
  def change
    create_table :posts do |t|
      t.string :text
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end

のように書くのがオーソドックス。

そして実際に作成されるテーブルには user_id でカラムが作成される。
ただ今回みたいに from_user_idto_user_id という別々のカラムを作成し、それぞれでUserを参照したい場合、同じカラムがPostsテーブル内に2つ存在することになるので困る。
なので user_id というカラムを作るのをやめてもらう。

class CreatePosts < ActiveRecord::Migration[5.1]
  def change
    create_table :posts do |t|
      t.string :text
      t.references :from_user, foreign_key: { to_table: :users }
      t.references :to_user, foreign_Key: { to_table: :users }

      t.timestamps
    end
  end
end

このように書くことで、Postsテーブルには from_user_idto_user_id というカラムができる。

これでOK。マイグレートする。

Modelにアソシエーションを追加

もちろんこれだけではダメで、Modelにアソシエーションを記載しないといけない。

class Post < ApplicationRecord
  belongs_to :from_user, class_name: 'User', :foreign_key => 'from_user_id'
  belongs_to :to_user, class_name: 'User', :foreign_key => 'to_user_id'
  has_many :praise_replies
end  

アソシエーションを記述する際は、 belongs_to :${table_name} がオーソドックスだが、今回はUserを参照しているカラムが2つあるのでそれぞれに対してアソシエーションの定義が必要。

これで例えば

posts = Post.all.includes(:from_user)
posts.each do |post|
 puts post.from_user.name
 puts post.from_user.email
end

とするとPostに紐づくfrom_userのUser情報をワンクエリで取ってくることができる。

以上です。

いつもこれくらいサバサバした文章を書きたいな。。。
ありがとうございました。

Ruby on RailsのAPIモードでログイン機能の実装をする

概要

タイトルのとおりです。
Rails APIを使ってログイン機能の実装を行います。
今回はサーバーサイドオンリーです。
Google拡張機能でデータをPOSTして、テーブルに格納できていることを確認できればゴールとします。
そのうち、フロントサイドも含めた記事を書く予定ですが、その時の参照先となる記事です。

前提

すでにRailsのプロジェクトがある。

手順概要

実装するための手順の概要です。

やること

  1. devise_token_authのgemのインストール
  2. テーブルの作成・マイグレーション
  3. devise_token_authを使ったトークン認証の実装

devise_token_authのgemのインストール

では早速いきましょう。
まずはGemfileに追加です。

devise_token_authはTwitterFacebookでの認証にも対応することができ、omniauthというGemをインストールすることで使用可能になるのですが、今回はこれらの認証方法は使わないので、omniauthはインストールしません。
devise_token_authを利用する上でインストールすべきGemは devisedevise_token_auth なのですが、
ユーザのデータはJSONでやりとりしたいので jbuilder を、
またクロスドメイン対策のために rack-cors もインストールします。

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.5'
gem 'rake', '~>12.3.2'
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'          <- JSONでデータのやりとりを行う想定のためコメントアウトを外す
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'                    <- クロスドメイン対策のために利用するため、コメントアウトを外す

# This is for authentication based on token
gem 'devise'                                <- 追加
gem 'devise_token_auth'                     <- 追加

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Gemfileの編集が終わったらこれらのGemをインストールします。
$ bundle install --path ./vendor/bundle
(プロジェクトごとに管理するgemを変えたいのでローカル(./vendor/bundle)にインストールします。

ここまでエラーなく終了すれば、1st ステップ終了です。

テーブルの作成・マイグレーション

では次はユーザ認証のために使用するクラスや、それに対応するテーブルを作っていきます。

devise_token_authのコントローラ等の作成

$ bundle exec rails g devise_token_auth:install User auth
(bundle exec は必要に応じてつける)

フォーマットは
$ rails g devise_token_auth:install [USER_CLASS] [MOUNT_PATH]
です。
[USER_CLASS] はユーザ認証に使用するクラスの名前です。何も指定しなければUserとなります。
[MOUNT_PATH] は認証のルーティングをマウントするパスです。何も指定しなければauthとなります。
認証のルーティングをマウントとは、要はURLのことで、authだと例えばサインインのURLはGET、POSTともに /auth/sing_in のようになります。

$ bundle exec rails g devise_token_auth:install User auth
      create  config/initializers/devise_token_auth.rb
      create  db/migrate/xxxxxxxxxxxxxx_devise_token_auth_create_users.rb
      create  app/models/user.rb
      insert  app/controllers/application_controller.rb
        gsub  config/routes.rb

となればOK!!

user.rbとマイグレーションファイルの修正

そうすると、 user.rbマイグレーションファイルが作成されるのでそちらを修正していきます。

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  include DeviseTokenAuth::Concerns::User
end

もともとconfirmableやomniauthableがコメントアウトされていましたが、もしコメントアウトされていなければコメントアウトしておきましょう。
confirmableはメールを送信して確認する機能を実装するためのもの、omniauthableは先程のTwitterFacebookでの認証を指しています。
今回は使いません。

次はマイグレーションファイルを修正します。
confirmableがコメントアウトされていなかったので、コメントアウトします。
また、このコメントアウトに伴って confirmation_token に対して張られているindexの記述もコメントアウトします。
今回は、

  • 名前
  • ニックネーム
  • メールアドレス
  • 画像

がデフォルトで記述されていたので、それをそのまま使用します。

class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.1]
  def change
    
    create_table(:users) do |t|
      ## Required
      t.string :provider, :null => false, :default => "email"
      t.string :uid, :null => false, :default => ""

      ## Database authenticatable
      t.string :encrypted_password, :null => false, :default => ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at
      t.boolean  :allow_password_change, :default => false

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, :default => 0, :null => false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      # ## Confirmable                        ========= コメントアウトここから
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable 
                                              ========= コメントアウトここまで

      ## Lockable
      # t.integer  :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at

      ## User Info
      t.string :name
      t.string :nickname
      t.string :image
      t.string :email

      ## Tokens
      t.text :tokens

      t.timestamps
    end

    add_index :users, :email,                unique: true
    add_index :users, [:uid, :provider],     unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true      <- コメントアウト
    # add_index :users, :unlock_token,       unique: true
  end
end

完了したらマイグレーションファイルを適用しましょう。

$bundle exec rake db:migrate
== 20190127155844 DeviseTokenAuthCreateUsers: migrating =======================
-- create_table(:users)
   -> 0.0580s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0552s
-- add_index(:users, [:uid, :provider], {:unique=>true})
   -> 0.0501s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0500s
== 20190127155844 DeviseTokenAuthCreateUsers: migrated (0.2139s) ==============

となっていればOKです!!

ここまででテーブルの作成・マイグレーションが完了しました。

devise_token_authを使ったトークン認証の実装

さぁ、いよいよ実装に入ります。
コードって書いてる時間より紙上とかエクセルとかで考えてる時間のほうが長いですよね。
どうでもいいですけど。

ちなみに今の状態で bundle exec rake routes をすると、

new_user_registration GET /auth/sign_up(.:format) devise_token_auth/registrations#new

のようなパスが表示されています。

コントローラの作成

さて、 bundle exec rails g controller api/v1/auth/registrations を実行します。

$bundle exec rails g controller api/v1/auth/registrations
Running via Spring preloader in process 6670
      create  app/controllers/api/v1/auth/registrations_controller.rb
      invoke  test_unit
      create    test/controllers/api/v1/auth/registrations_controller_test.rb

と表示されればOK。

作成されたコントローラを編集して以下のようにします。

# registrations_controller.rb
module Api
  module V1
    module Auth
      class RegistrationsController < DeviseTokenAuth::RegistrationsController
        private
        def sign_up_params
          params.permit(:name, :nickname, :email, :img, :password, :password_confirmation)
        end

        def account_update_params
          params.permit(:name, :nickname, :email, :img)
        end
      end
    end
  end
end

ルートの編集

次はルートを編集します。デフォルトのコントローラではなく、DeviseTokenAuth::RegistrationsControllerを継承したコントローラを使うようにします。

# routes.rb
Rails.application.routes.draw do
  namespace :api do
    scope :v1 do
      mount_devise_token_auth_for 'User', at: 'auth', controllers: {
          registrations: 'api/v1/auth/registrations'
      }
    end
  end
end

さて、ルートが確認しましょう。 $ bundle exec rake routes を実行します。

new_api_user_registration GET /api/v1/auth/sign_up(.:format) api/v1/auth/registrations#new

URIがちゃんと変わってますね!
実装としては以上です。

Initializerの編集

もろもろの設定を触ります。

config.change_headers_on_each_request = true にするとリクエストごとにトークンを新しくする必要があります。
なのでコメントアウトを外してfalseにしたいです。
そして、ずっと同じトークンを使い続けるわけにもいかないので config.token_lifespan = 2.weeksコメントアウトも外します。

以上で、サーバー側の実装完了です。
試しに適当にデータを投入してテストしてみましょう。

Restlet Clientを使ってテスト

まずは rails srailsをローカルで起動します。

ユーザ作成
http://localhost:3000/api/v1/auth に対して、POSTメソッドでリクエストを投げます。

Request Body

{
  "name": "kenny takimura",
  "nickname": "kenny",
  "email": "kennytakimura@example.com",
  "password": "password",
  "password_confirmation": "password"
}

f:id:Kenny27:20190129013915p:plain

200ステータスが返ってきてますね。
Response見てみましょう。

Response
**idは人によって違うかもしれないです。
僕はすでにいくつかユーザ登録していたので新規登録でidが4になっています。

{
 "status": "success",
 "data":{
  "id": 4,
  "provider": "email",
  "uid": "kennytakimura@example.com",
  "allow_password_change": false,
  "name": "kenny takimura",
  "nickname": "kenny",
  "image": null,
  "email": "kennytakimura@example.com",
  "created_at": "2019-01-28T16:00:20.000Z",
  "updated_at": "2019-01-28T16:00:21.000Z"
 }
}

テーブルも確認しましょう。
f:id:Kenny27:20190129014447p:plain
ちゃんとデータが入ってますね。

ログイン
ついでにログインも確認しておきましょう。
ログインするためには http://localhost:300/api/v1/auth/sign_in に対してPOSTメソッドでリクエストを投げます。

Request Body

{
  "email": "kennytakimura@example.com",
  "password": "password"
}

f:id:Kenny27:20190129013941p:plain

こちらも200ステータスが返ってきてます。
Responseは

Response

{
 "data":{
  "id": 4,
  "email": "kennytakimura@example.com",
  "provider": "email",
  "uid": "kennytakimura@example.com",
  "allow_password_change": false,
  "name": "kenny takimura",
  "nickname": "kenny",
  "image": null
 }
}

先ほど登録したデータが返ってきます。
idも4です。

いい感じですね。

おわりに

想像してたより簡単に実装できました。
他にもいろんなサイトで実装方法が紹介されているので、見てみるといいかもしれないです。
私は基本的には公式サイト
Installation - devise-token-auth
をベースに、
web帳 | Rails5 + devise token authで作る 認証API

[Rails5] devise_token_auth でAPIを作成する / 新規登録・ログイン | tackeyy.com
を参考に実装していきました。

次はフロントとのつなぎも書いていこうと思います。

それでは!

4日でポートフォリオサイトを作った話

ポートフォリオサイトを作りました

最近全然アウトプットできていなかったんですが、久しぶりの投稿です。
今まで学んできたVue.jsを使ってポートフォリサイトを作りました。

Kenta Takimura's Profile site

f:id:Kenny27:20190121000009p:plain

動機

細かいものを出していくといろいろあるんですが、大きいところでいうと、

  • 自分が持っているスキルとかやっていることとかをまとめたかった
  • このサイトを見てもらうことで、ちょっとでも興味持ってくれた人と繋がれたりしたら嬉しいなー

とかですかね。

使用言語etc

HTMLは当然、Vue.jsも最初に述べた通り。
Buefyは今回初めて使った、Vue用に作られたコンポーネント群。

Buefy?

今回初めてなので少しだけBuefyとはについて書こうと思います。
使った感想とかはまた後で。

公式サイト(https://buefy.github.io/)を見ると、

Lightweight UI components for Vue.js based on Bulma

と書いています。
Bulmaをベースに作られたVue.jsのための軽量なコンポーネント群ってことですね。

で、Bulmaって何よ?となるわけで、さらにBulmaの公式ページ(https://bulma.io/)ヘ...

Bulma is a free, open source CSS framework based on Flexbox (以下略)

と記載されていて、要はFlexboxベースで作られたOSSCSSフレームワークで、Bootstrapみたいなものかなぁと理解します。

なので、BuefyはこのBulmaというCSSフレームワークを使って作られたコンポーネント群ですね。

作る前に考えてたこと

  • コンテンツ
  • UI

考えてたことは正直この2つだけです。
どういうコンテンツにするかを決めないと作れないし、ダサいUIは嫌だし・・・
(結局UIは個人的には好きな感じだけど、そこはほんとに人によりそう 笑)

コンテンツ

まず初めに考えた。
あまり情報が多すぎでも見る気失せるし、少なくても自分のことわかってもらえないし。
自分のテクニカルな側面を知ってもらうために、過不足ない情報はどんなもんなんだろうというところから考えた。

逆に、自分は人のことを知りたい時、何がわかればいいか。

結果、コンテンツは

  • Top
    Top画面。ベタに。
    だけどこのサイトを見てもらえるかどうかが決まる画面とも思ったのでかなり真剣に考えた。

  • Me
    自分の基本的な情報を書く画面。

  • Skills
    自分のスキルセットを書く画面。
    たぶんこのサイトを訪れた人が一番気になること。

  • Outputs
    今までの成果物を挙げる画面。
    Skillsページよりこっちの方が気になる人もいてるか。

  • Contacts
    僕への連絡手段を掲載する画面。
    おまけみたいな感じ。

で構成することにした。

UI

とりあえずExcelで大雑把なイメージを書き出してみた。

f:id:Kenny27:20190120224355p:plain
大雑把なイメージ。こういうの考えるときはできるだけ英語で考えるようにしている。

これを上の各コンテンツに対して行い、自分の中でのイメージを書き出してより明確なものにした。
ちなみに、自分の考えていることを書き出すということはよくやっている。
頭の中が整理されてちょっとすっきりするんですよね。

実装へ

実装するにあたって要件として以下を考えていました。

Vue.js使うならSPAは大前提ですが、前提でも要件は要件なので一応考えてた。
レスポンシブもWebでアプリ作るなら必須だと思うので要件にしています。

GitHub pagesは実は今回初めて知ったんですよね。
GitHubリポジトリから直接Webサイトをホスティングできるサービスです。
簡単にリリースできるとのことなので、友人からの勧めもあり使うことにしました。

ハマったポイント

ここではポイントの紹介だけ。

コーディング自体はまぁまぁスムーズにできたんですよね。
Vuexとかも使う必要なかったですし、ルーティングをネストさせることもなかったので、
そもそもハマるポイントがそれほどなかったです。

GitHub pagesに上げるところで一番時間使ってましたw
成果物をビルドして、専用のリポジトリにpushしたんですが、URLにアクセスしても真っ白でした。
このポイントはまた別の機会に書こうと思います。

感想

Buefyを今回初めて使ったんですが、かなり良かったです。
こういうTileを使ったUIとか

f:id:Kenny27:20190120233232p:plain
Tileコンポーネントを使って作成

NavbarとHamburgerの切り替えとか

f:id:Kenny27:20190120233424p:plain
Navbar

f:id:Kenny27:20190120233447p:plain
Hamburger(右上の三本線)

を一瞬で実装できた。

実際はTileはCSSクラスを利用することで実装できるのだが、それらをラップしてTileコンポーネントにして、
ラベルと値組み合わせ(誕生日: YYYY/MM/DD)を渡すようにし、再利用できるようにするとともに、
項目が追加された時も簡単に追加できるようにした。

f:id:Kenny27:20190120234053p:plain
Tileコンポーネントを呼び出している。labelやvalue、textなのかurlなのかを表すtypeなどの配列をcontentsに渡すことで、Meページを実現している。

にしてもやっぱりUI考えるのって難しいですね。

今回は割とスピード感持って完成させましたが、本音を言うならもっとゆっくり時間使ってやりたいです。
(これからも当然ブラッシュアップしていくんですが)

今後もう少しUI考えてできればもっと改良したいのと、
あとは多言語対応もしたいです。

i18n使えば実装自体はすぐできるんですが、翻訳をする時間が無い。というか優先度低い。

今回このようにまとめたことで、自分のスキルセットを整理して、今後どうしようかとか考えるいい機会になりました。
みなさんもぜひ自分でサイト作りましょうよ、めちゃくちゃ楽しいですよ。

最後まで読んでいただきありがとうございました!

Udemy修了しました

早いものでもう11月
スターバックスなんかは店員さんが赤と緑のスタイルですっかりクリスマスモード
なんか年々1年が短くなっているような気がする・・・

さて、タイトルのとおり、前回の投稿で伝えていたUdemyのカリキュラムを修了しました。
分量が多かったかつ平日仕事だったのでなかなか時間かかりました。

実施コンテンツ(前回投稿以降)

内容的には以前からの復習が割と多かった
それぞれ紹介したいんだが、長くなりそう・・・なので省略

実践演習:Bitcoin価格表示アプリの開発 - API連携

やっぱりGETメソッドしか実装しなかった。
証券コードみたいなのを入力すれば該当のBitcoinの価格を表示させる・・・みたいなことができると尚良かったかも。
まあでも、POSTメソッドとかまでやってしまうとaxiosの話にもなるし、入門編という趣旨からも外れるんだろうな。
自分でやった方が勉強にもなりそうだし、ここは追々自分でやっていこうかと思う。
とはいえエラーハンドリングの仕方とかローディング中の表示とか基本的なことではあるが、ちゃんと学べたのは良かった。

テンプレート構文

種類はいろいろある。

  • v-once
  • v-pre
  • v-html
  • v-cloak
  • v-text

などかな。
v-preディレクティブなんかはその要素とその要素の子要素のコンパイルを全てスキップするから、XSS(クロスサイトスクリプティング)対策になるらしい。
コンパイルされないということは悪意のあるJavaScriptも実行されないということだから、まぁそういうことなんだろうな。

v-cloakなんかは、インスタンスが生成されるまでは生きているが、インスタンスが生成されると取り除かれるディレクティブ。
なので、vueのインスタンスを生成する過程で見える可能性のあるマスタッシュ構文とかをユーザーに見せないようにすることができる。
かゆいところに手が届くディレクティブ。

算出プロパティ

computedプロパティを使う。
methodsプロパティを使った場合と比較されがちだが、
computedは算出結果をキャッシュしてくれるので、同じ処理を繰り返す場合はcomputedを使用する方が効率的。
また、computedにはgetterとsetterという概念がある一方でmethodsにはgetterしかないので、computedの方がよりリッチ。

監視プロパティ(ウォッチャ)

watchプロパティを使う。
computedプロパティと比較されがちなんだが、
どっちでも同じことができるなら、computedを使うことを推奨する。
watchに比べて、記述が簡単でコードが短くなるから。
watchプロパティを使えば、例えばテキストボックス内に金額を入力する必要があったとして、
onFocusで『¥1,200,000』->『1200000』に、onBlurで『1200000』->『¥1,200,000』にいった具合に切り替えることが容易になりそう。
あとはwatchプロパティのオプションとして、ネストされたオブジェクトまで監視するdeepオプションや、初期表示時にも処理を実行するimmediateオプションがあったりもする。

実践演習:APIを用いたリアルタイムサーチ - 監視プロパティの理解を深める

QiitaのAPIを使って記事を取得する演習。
watchを使うメリットはあまり感じなかった・・・v-on:keyupディレクティブでいいのでは・・・?という疑問が今なおある 笑
watchを使う練習だから別にいいんだけど。

クラスとスタイルのバインディング

v-modelやv-bindを使うことでvalue属性へのデータバインディングができるのと同様に、クラス属性やスタイル属性にもバインティングできるんだということを伝えるためのセクション。
v-bind:classを使うことでclassに、v-bind:styleを使うことでstyleにバインディングを行うことが可能だった。
プレーンなJavaScriptでDOM操作するよりはるかに簡単。
ここでも、「やっぱすごいな」と思っていた。
プレーンなJavaScriptで同じことをやろうとすると、

  • 1 要素を取得して、
  • 2 その要素が自分が追加したい属性を持っているかどうかを確認して、
  • 3 その要素に属性を追加して・・・

と割と手順が多い。

条件付きレンダリング

v-if、v-else-if、v-elseやv-showがあるよ。
v-ifは切り替えコスト高い一方でv-showは初期描画コストが高いよという話で、
内容的には前回投稿のv-ifとはv-showとはという話と重複してた。

イベントハンドリング

イベントハンドリングもv-onを使うという話で、基本的には復習。
ただし、イベントの修飾子があるよということで以下が紹介されていた。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

それぞれの内容を全ては覚えていないけど、.once修飾子はv-onceと似てる。
イベントに対して一度しか処理を実行しないとするのか(.once)、データに対して一度しかレンダリングしないとするのか(v-once)の違い。
.prevent修飾子は詳しくレクチャーされなかったけど、おそらくいわゆるprevenDefaultと同じ挙動になるのではないかと。
なので非同期で通信するときに使うかもしれない。

フォーム入力バインディング

これも以前からの復習で、v-modelを使って演習する感じ。
v-modelを使用するとvalue属性は無効化されるとか、textareaではマスタッシュ記法が使えないからv-model使いましょうとか。
あとはラジオボタンやセレクトボックスのときにどのようにデータバインディングするかを、選択肢が単体・複数に応じて勉強した。

コンポーネント

Vue.componentを使うことでグローバルにコンポーネントを定義できて、
ローカル変数に対してコンポーネントを宣言して代入し、ローカルのvueインスタンスにtemplateの記述をすることで、ローカルでしか適用されないコンポーネントを定義できるという話。
グローバルなコンポーネントは、ヘッダとかサイドバーとかで使えそうだなぁって考えながら聞いてた。
スコープが広がると影響範囲も大きくなるので基本的にはローカルな方がいいんでしょうけどね。

トランジション

最後がトランジションで、要するにアニメーションをvueを使ってどうやるかという話。
transtionタグで囲むと、アニメーションであることが認識されて、
非表示->表示に遷移する際にはv-enter-active, v-enter、
表示->非表示に遷移する際にはv-enter-leave, v-leave-to
というクラスが一時的に割り当てられ、そのクラスに対してCSSを記述していくことでフェードインさせたりフェードアウトさせたりできる。

まとめ

結局全てのセクションに対してコメントしてしまった・・・。
こんな自己満足な投稿をここまで読んでいただきありがとうございます。

個人的な感想ですが、やっぱり最初は体系的に、こういう形で動画とかで勉強するほうが良かったです。
多少お金を払ってでもこういう勉強の仕方をした方が、今後何かわからないことや詰まったことが出てきた時に、
この話は全体の中のどこに位置づけられるのかといったことが意識しやすくなる。

全量を全く知らず、付け焼き刃の状態では『わかる』ことはできても、『できる』ようにはならない。
『できる』ようにならないと正しい実戦経験が積めないし、結果として『気付き』も得にくいんじゃないのかな。

今後

ペンディングしていたRuby on Railsのプロジェクトを再開させようと思います。
並行して、来年4月に応用情報技術者試験も受けたいので、直近はその勉強ですかね。(実は3週間くらい前から着手してる)
わからないこととか、詰まったことがあれば今後もツラツラと書いて投稿すると思います。

Udemy始めてみました

背景

前回の記事でRuby(Ruby on Rails)を利用したプロジェクトにVue.jsを導入したという話を書きました。
で、実際にVue.jsを使い始めたわけですが、ところがどっこい凄まじく手詰まり・・・
Componentて?ルーティングどうなってんのこれ・・・笑?もはやどうやってサーバーと繋げればいいのか・・・?
あらゆることに疑問が出てきて、基本的・体系的な知識が不足していると感じたので、友人に勧められたこともあり先週くらいからUdemyでVue.jsのレクチャーを受講することにした。
通常7,200円が1,500円だったので、お買い得

www.udemy.com

『Vue JS入門決定版!jQueryを使わないWeb開発 - 導入からアプリケーション開発まで体系的に動画で学ぶ』(上に張ったリンク)を使ったが、
ある程度進んだので、ここで少しだけ自分にfeedbackしようかなぁと。

内容をざっくり

  • 速習編
    基礎を叩き込む

  • todoアプリ作成編
    叩き込んだ基礎でtodoアプリを作る

  • ビットコイン価格取得&表示
    axiosを使ってBitCoinの価格を取得するするAPIを叩き、JSON形式のレスポンスを画面に描画する

  • より深く
    テンプレート構文や算出プロパティなど、より深く知識を得て使いこなすためのレクチャー

やったこと

速習編・todoアプリの作成編の学習
期間は1週間くらい

感想を一言で

レガシーなjsに比べて、こんなに楽だと思ってなかった。

学んだこと

  • ディレクティブ
    簡単に言うなら、HTML側である指示を行うためのプロパティ。

    自分が学んだディレクティブと簡単な説明

  • v-if
    ""(ダブルクウォテーション)で書いた変数の値がtrueの時に、DOMを表示する。falseの時はDOMごと消されるので、処理的には比較的重い。

  • v-show
    基本的な処理はv-ifと同じ。ただし、falseのときにはDOMそのものが削除されるわけではなく、display:noneになるだけなので、v-ifに比べて処理が軽い。

  • v-on
    イベントリスナーとして使う。v-on:click="doSomething" みたいに使うと、ある要素がclickされたときにjsで定義しているdoSomethingという処理が呼ばれる感じ。

  • v-bind
    単方向のデータバインディング。Vue->HTMLに対してデータバインドするので、HTML側での入力はVueに影響することはない。

  • v-model
    双方向のデータバインディング。v-bindと違ってHTML側での入力がVueにも影響する。

  • v-for
    繰り返し処理。配列やオブジェクトをループしてくれる。前だとjsの中にテンプレート書いて、ループ回してappendしながらHTML作って、ようやく描画してたのに対して、HTML側で1行で済むので非常に楽。

こんなもんかな?もしかしたらヌケモレあるかも。

  • DOM操作
    上で述べたディレクティブはこれに強く関係している。 todoアプリ作成の中でちゃんと学べたと思う。
    各ディレクティブでDOMがどのように操作されるのか、使い所はどこかを学んだ。
    v-onを起点に、v-bind使って表示非表示を切り替えたりするのなんかめちゃくちゃ楽。

中でも強く印象に残っているのが、双方向のデータバインディングがこんなに簡単にできるのかということ。
todoアプリは、inputフォームを使ってタスクをリストに追加、doneになったら打ち消し線を表示する、削除するとリストからも消える。くらいの基本的な仕様だったが、JQuery使ってたときよりはるかに簡単に実装できる。
dataプロパティの中に、任意のプロパティを定義して、v-bindでそのプロパティを指定するだけであら不思議、データバインドできましたー
くらいの感覚。(実際はもう少しやることある(CSSとかとか)。要は簡単って話。)

感じたこと・感じていること

基礎って大事。
体系的に学ぶって大事。
どんどんいろんな技術が出てきて、進化してるな。
取り残されたくないな。
勉強し続けないと、(エンジニア市場で見た時に)ただでさえ高くない市場価値が下がる一方だな。

これから

ビットコイン価格取得&表示を進めていく。
API使ってレスポンスをどうやって描画するかを学習できるといいかな。
イメージGETメソッドしか使わなそうだけど、POSTメソッドも使う内容なんだろうか。

より深くの部分もやります。 おそらく次の投稿は全てやりきったあとなのではないかと。

外に出るねくらの成長を楽しみしててもらえるとありがたい。