3月メモ・リンク集

3月に調べたことのメモです。

Angular関連

React関連

CircleCI関連

Angularで作成したアプリのCircleCI環境を2.0に移行した時のメモです。

2.0 Docs - CircleCI(オフィシャルドキュメント)

オフィシャルからは、これくらいの資料を確認。

Angular対応

It has 3 main components: a version, a list of jobs, and a list of workflows.

...

Common things that you can achieve within a job include: - Installing the tools you need to run/build/test your project - Executing bash commands - Storing or restoring items from the CircleCI cache

...

The key of the cache is generated using the checksum function, which will output a base64 encoded hash of the package.json file’s content.

...

We run the build command. Notice that we are going to use a multi-line command with each line running in the same shell, so we start the command with the pipe (|) character.

...

その他

Rails関連

Android関連

もしかしたらAndroidエンジニアになるかも、という状況があったので設計まわりについて調べてみた時の記事リンクです。Androidアーキテクチャも面白そう。

その他

Rails チュートリアル復習用メモ

以下を参考にした、Rails アプリを作成する手順の覚書です。

モデルとコントローラーの基本的な部分を知ることが目的だったので、モデルの応用的な部分や View 周りはできるだけ省いたアプリケーションに仕上がっています。具体的には 4, 11, 12, 14 章をスキップし、それ以外の各章もスタイルやインテグレーションテストについては省いています。(発展的なログイン機構、アカウント有効化のメール送信、パスワード再設定、ユーザーのフォローなどが機能として未実装。)

新規アプリ作成

rails _5.1.4_ new first_app
rails server
# => http://localhost:3000/

Gemfile 更新

feat: upadte Gemfile · ryotah/rails-tutorial@6336633

bundle install --without production

モデルを作成

User モデルと Micropost モデルを作成します。

User モデルを作成

rails generate model User name:string email:string password_digest:string
# => app/models/user.rb
# => db/migrate/xxx_create_users.rb
# => test/fixtures/user.yml
# => test/models/user_test.rb

rails db:migrate
# => db/schema.rb
# Rails 4以前
# bundle exec rake db:migrate

すでに存在するモデルにインデックスを追加。

rails generate migration add_index_to_users_email

生成されたdb/migrate/[timestamp]_add_index_to_users_email.rbを更新してから migrate を実行。

class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    # 以下の行を追加
    #
    # unique: true => 一意性を強制
    add_index :users, :email, unique: true
  end
end

User モデルに validates, has_secure_password, has_many :microposts, dependent: :destroy などを追加。

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum:  50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true

  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
end

(メモ)rails console からユーザーを追加

user = User.create(name: "Michael Hartl", email: "mhartl@example.com", password: "foobar", password_confirmation: "foobar")

user.authenticate("foobaz")
# => false
!!user.authenticate("foobar")
# => true

Micropost モデル

rails generate model Micropost content:text user:references

生成されたdb/migrate/[timestamp]_create_microposts.rbを更新。

class CreateMicroposts < ActiveRecord::Migration[5.1]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
    # 以下の行を追加
    #
    # インデックスを追加して、user_id に関連付けられたマイクロポストを
    # 作成時刻の逆順で取り出しやすくする
    add_index :microposts, [:user_id, :created_at]
  end
end

Micropost モデルに validates, default_scope を追加。(belongs_to :user は最初から用意されている。)

class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

テスト

# 全テストを実行
rails test

# modelsのみテストを実行
rails test:models

# 特定ファイルのテストを実行
rails test test/models/micropost_test.rb

コントローラー作成

必要なControllerを作成。

rails generate controller StaticPages home
rails generate controller Users new
rails generate controller Sessions new
rails generate controller Microposts

ルーティングを設定

Rails.application.routes.draw do
  # root_path
  root 'static_pages#home'

  # 名前付きルートを定義
  get '/signup', to: 'users#new'
  post '/signup', to: 'users#create'
  get '/login', to: 'sessions#new'
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'

  resources :users
  resources :microposts, only: [:create, :destroy]
end

セッションヘルパー

  • 以下 5 つを定義
    • ログイン
    • セッション情報から現在のユーザー(ログインユーザ)を取得
    • ログインユーザーか確認
    • ログイン済みか確認
    • ログアウト
module SessionsHelper
  def log_in(user)
    session[:user_id] = user.id
  end

  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def current_user?(user)
    user == current_user
  end

  def logged_in?
    !current_user.nil?
  end

  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

ApplicationControllerincludeする。(各ビューとコントローラーで利用できるようになる)

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

サンプルデータを追加

https://github.com/ryotah/rails-tutorial/blob/b27827169748425dbff08188d97c366eb73ec1fb/db/seeds.rb

rails db:seed

ビューとコントローラーを調整

ヘッダー

(メモ)デバッグ

<%= debug(params) if Rails.env.development? %>

ユーザー一覧

views/users/index.html.erbを追加。

<h1>Users#index</h1>
<%= render @users %>

views/users/_user.html.erbを追加。(render @usersに対応するパーシャル)

<div><%= user.name %>, <%= user.email %></div>

コントローラーにindexを追加。

class UsersController < ApplicationController
  # ...
  def index
    @users = User.all
  end
  # ...
end

ログイン

views/sessions/new.html.erbにフォームを追加。

<%= form_for(:session, url: login_path) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email, class: 'form-control' %>
  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>
  <%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>

<p>New user? <%= link_to "Sign up now!", signup_path %></p>

コントローラーにcreateを追加。

def create
  user = User.find_by(email: params[:session][:email])
  if user && user.authenticate(params[:session][:password])
    log_in user
    redirect_to user
  else
    flash.now[:danger] = 'Invalid email/password combination'
    render 'new'
  end
end

(メモ)Flash

<% flash.each do |message_type, message| %>
  <div><%= message_type %> | <%= message %></div>
<% end %>

ユーザー作成

<%= form_for(@user, url: signup_path) do |f| %>
<%= render 'shared/error_messages', object: f>
# ...

ユーザー詳細

ホーム(マイクロポスト作成)

  • feat: post a micropost · ryotah/rails-tutorial@98a775c
    • ログイン済みユーザーかどうか確認するlogged_in_userApplicationControllerに追加
    • MicropostsControllercreateを実装
      • before_action :logged_in_user, only: :create
    • app/views/static_pages/home.html.erbにマイクロポスト用のフォームを追加
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>  <p>Find me in app/views/static_pages/home.html.erb</p>
<% if logged_in? %>
  <%= current_user.name %>, <%= current_user.email %>
  <%= form_for(@micropost) do |f| %>
    <%= render 'shared/error_messages', object: f.object %>
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    <%= f.submit "Post", class: "btn btn-primary" %>
  <% end %>
<% end %>

元記事のメモ/リンク

@user = users(:michael)
# このコードは慣習的に正しくない
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)

Rails ガイドより

users = User.all
user = User.first
david = User.find_by(name: 'David')

2月メモ・リンク集

2月に調べたことのメモです。

f:id:ryotah:20180303165052p:plain f:id:ryotah:20180303165054p:plain

リサイザー

@Directive({
  selector: '[grabber]',
})
export class GrabberDirective {
  // ...
  constructor(
    private elm: ElementRef,
    @Host() @SkipSelf() private resizable: ResizableDirective
  ) { }
  // ...
}

Show More/Less コンポーネント

function getLineHeight(element: HTMLElement): number {
  let lineHeight = parseInt(
    window.getComputedStyle(element, undefined).getPropertyValue('lineHeight'),
    10
  );
  if (isNaN(lineHeight)) {
    const clone = element.cloneNode() as HTMLElement;
    clone.innerHTML = '<br>';
    element.appendChild(clone);
    lineHeight = clone.clientHeight;
    element.removeChild(clone);
  }
  return lineHeight;
}

ドラッグアンドドロップdnd

実装の必要がなくなったので、参考にしようとしたリンクのみ。

Googleスプレッドシート的なスクロール

  • angular-scrollbar-like-spreadsheet - StackBlitz
    • コンテンツ(A)とスクロールエリア(B)を用意
    • A自体のスクロールバーは利用しない
    • Bの内部の要素をAの高さに合わせる
    • Bのスクロールに合わせてAを移動、A上発生したホイールイベントを利用してBを移動
// スクロール内部の高さを対象の高さに合わせる
const height = this.contentInner.nativeElement.clientHeight;
this.scrollInner.nativeElement.style.height = `${height}px`;

// スクロール時に
this.scroll.nativeElement.addEventListener('scroll', (e) => {      
  this.content.nativeElement.scrollTop = e.target.scrollTop;
});

// コンテンツエリアでマウホイールイベント発生時に
this.content.nativeElement.addEventListener('wheel', (e) => {
  const result = this.content.nativeElement.scrollTop + e.deltaY
  this.content.nativeElement.scrollTop = result;
  this.scroll.nativeElement.scrollTop = result;
});

AngularのDIに関して

Ruby, Rails関連

VS CodeでRubyを書く

.vscode/settings.json

"ruby.lint": {
  "rubocop": true,
  "ruby": {
      "unicode": true //Runs ruby -wc -Ku
  },
},

その他

1月メモ・リンク集

1月に調べたことのメモです。

Angular関連

Date系ライブラリとIANA

最近はLuxonを利用しています。Intlのブラウザ対応状況、他のライブラリのタイムゾーン関連のサポートについて気になったので、そのあたりを調べた時のメモです。

複数のプロジェクタで共有したいロジックとコンポーネントをパッケージ化した

必要な前提知識

最終的に上記環境を利用し、簡単にパッケージ作成ができるようになったのだが、実際には色々とつまづいた。原因は、前提となる基礎知識がかけていたから。
(基本的な公式ドキュメントはしっかり読みましょうという話)

ReactのHOCs

その他

AngularのCDK Tableのコードを読みながら、Viewの組み立て方について調べる

段階的に実装してみたサンプルコードを晒します。完成版とおまけを除くと8ステップにわかれています。

Viewの組み立て部分を参考にしたかったので、DataSourceやIterableDiffers, TrackByFunctionなどを利用したコードはありません。

CDK Tableのドキュメント

0: 完成版

f:id:ryotah:20180124234812p:plain

1: コンポーネントを動的にaddする

2: 対象のコンポーネント内にaddする

3: コンポーネントのaddからテンプレートを取得してaddする方式に変更

f:id:ryotah:20180124232835p:plain

4: table.ts, row.ts, cell.tsを用意

  • angular-cdk-datatable-04-celldefs - StackBlitz
    • 各セルの内容を定義するために、CellDefという考え方を導入
    • この段階では実態は export class CellDef { constructor(public template: TemplateRef<any>) {} } というDirective

5: 行定義を追加(RowDef)

app.component.html

<my-table [data]="data">
    <!-- cellを定義 -->
    <ng-container *cellDef="let data;">
        <span style="border: 1px solid cyan;">[cell-1] {{data}}</span>
    </ng-container>
    <ng-container *cellDef="let data;">
        <span style="border: 1px solid yello;">[cell-2] {{data}}</span>
    </ng-container>
    <ng-container *cellDef="let data;">
        <span style="border: 1px solid blue;">[cell-3] {{data}}</span>
    </ng-container>
  
</my-table>  

table.html

<!-- rowを定義 -->
<ng-container *rowDef>
    <div style="border: 1px solid red; background-color: rgba(255,0,0,.2)">[row]
        <ng-container cellOutlet></ng-container>
    </div>
</ng-container>

6: ヘッダー用の行定義を追加(HeaderRowDef)

f:id:ryotah:20180124234656p:plain

app.component.html

<my-table [columns]="columns" [data]="data">

  <!-- header cellを定義 -->
    <ng-container *headerCellDef="let data;">
        <span style="border: 1px solid cyan;">[ttl-1]{{data.title}}</span>
    </ng-container>
    <ng-container *headerCellDef="let data;">
        <span style="border: 1px solid green;">[ttl-2]{{data.title}}</span>
    </ng-container>
    <ng-container *headerCellDef="let data;">
        <span style="border: 1px solid blue;">[ttl-2]{{data.title}}</span>
    </ng-container>

    <!-- cellを定義 -->
    <ng-container *cellDef="let data;">
        <span style="border: 1px solid cyan;">[cell-1] {{data}}</span>
    </ng-container>
    <ng-container *cellDef="let data;">
        <span style="border: 1px solid green;">[cell-2] {{data}}</span>
    </ng-container>
    <ng-container *cellDef="let data;">
        <span style="border: 1px solid blue;">[cell-3] {{data}}</span>
    </ng-container>
  
</my-table>  

table.html

<!-- header rowを定義 -->
<ng-container *headerRowDef>
    <div style="border: 1px solid magenta; background-color: rgba(255,0,255,.2)">[row]
        <ng-container cellOutlet></ng-container>
    </div>
</ng-container>

<!-- rowを定義 -->
<ng-container *rowDef>
    <div style="border: 1px solid red; background-color: rgba(255,0,0,.2)">[row]
        <ng-container cellOutlet></ng-container>
    </div>
</ng-container>

7: accessorを追加

8: header cell, cellの定義をcolumnの定義でまとめる

f:id:ryotah:20180124235744p:plain

app.component.html

<my-table [data]="data">

    <!-- [name] columnを定義 -->
    <ng-container columnDef="name">
        <ng-container *headerCellDef>
            <span style="border: 1px solid cyan;">Name</span>
        </ng-container>
        <ng-container *cellDef="let row;">
            <span style="border: 1px solid cyan;">{{row.name}}</span>
        </ng-container>
    </ng-container>
    <!-- [age] columnを定義 -->
    <ng-container columnDef="age">
        <ng-container *headerCellDef>
            <span style="border: 1px solid green;">Age</span>
        </ng-container>
        <ng-container *cellDef="let row;">
            <span style="border: 1px solid green;">{{row.age}}</span>
        </ng-container>
    </ng-container>
    <!-- [id] columnを定義 -->
    <ng-container columnDef="id">
        <ng-container *headerCellDef>
            <span style="border: 1px solid blue;">Id</span>
        </ng-container>
        <ng-container *cellDef="let row;">
            <span style="border: 1px solid blue;">{{row.id}}</span>
        </ng-container>
    </ng-container>

</my-table>

9: おまけ

12月メモ・リンク集

12月に調べたことのメモです。

Angular関連

View作成や再利用について

f:id:ryotah:20180107131908p:plain f:id:ryotah:20180107131911p:plain f:id:ryotah:20180107131913p:plain
<div [myItem]="item1" #dir="myItem">
  {{dir.item.header}}: {{dir.item.content}} | <button (click)="dir.onRemove()">remove</button>
</div>

Form関連

f:id:ryotah:20180107131717p:plain
private markFormGroupTouched(formGroup: FormGroup) {
  (<any>Object).values(formGroup.controls).forEach(control => {
    control.markAsTouched();
    if (control.controls) {
      control.controls.forEach(c => this.markFormGroupTouched(c));
    }
  });
}

Dateライブラリ

VSCode

コードフォーマット

{
  "singleQuote": true,
  "trailingComma": "es5"
}
  • tslint --fix
    • 今まで利用してなかったけど、便利すぎた
    • TypeScript Hero のSort and organize your imports (sort and remove unused)をわざわざする必要なくなった
"tslint.autoFixOnSave": true,
"no-unused-variable": true,
"ordered-imports": true,

Go環境用意

  • インストール
    • brew install go
export GOPATH=$HOME/foo/bar
export PATH=$GOPATH/bin:$PATH
  • GOPATH以下に開発環境を構築する
    • go get などのコマンドを実行するとGOPATHフォルダにダウンロードされる
  • PATHを設定しておくとコマンドうつのが楽

Reactチュートリアル

Tutorial

  • Tutorial: Intro To React - React
  • in React apps to use on names for the attributes and handle for the handler methods.

  • Why Immutability Is Important
    • Easier Undo/Redo and Time Travel
    • Tracking Changes
    • Determining When to Re-render in React
  • It’s strongly recommended that you assign proper keys whenever you build dynamic lists.

  • react-tutorial-tic-tac-toe - StackBlitz
    • 自分でやったサンプル

その他

Google Analytics

コンポーネントを設計するときに

f:id:ryotah:20180107131350p:plainf:id:ryotah:20180107131359p:plain
  • 「飛行機のパーツ」とみるか、「2×4のブロック」とみるか
  • 細かくつくる
  • 汎用化と抽象化
  • Viewにどこまで関係するのか(テンプレートにどこまで関係するのか)
    • それは「ロジック」ではないか
    • それは「振る舞い」ではない

moment -> Luxon

Momentと比較してみて

使ってみて1週間ほど経た感想です。v0.2.9を利用。

気に入ったところ

  • Immutability
    • momentのようにcloneメソッドが不要
      • const bar = foo.clone().add(1, 'month')
  • 月を1から12で計算できる
    • momentはネイティブのDateと同様に0スタート
  • ネイティブのIntl APIを利用している
    • moment-timezoneが不要
      • 各localeファイルが不要
      • 柔軟なフォーマットで表示できる(曜日も追加したい、など)
  • オブジェクト作成時に元データ(元フォーマット)に応じて複数のメソッドが用意されている
  • 厳格なパース
  • 単純な値の取得はgetterで統一
    • dateTime.year()に対してdateTime.year
  • 出力フォーマットの拡充

なるほどと思ったところ

  • setterを集約
    • dateTime.year(2016).month(4)からdateTime.set({year: 2016, month: 4})

その他のmomentとの違い

準備

TypeScriptの場合。

型定義ファイル

まだ用意されていないので、このあたりからUse Flow or Typescript · Issue #73 · moment/luxon · GitHub拾ってくる。

npm i @types/luxon

インポート

import { DateTime } from 'luxon';

基本的な書き方

作成

// 現在時刻
const now = DateTime.local();

// 特定の時刻
const dt = DateTime.local(2017, 5, 15, 8, 30);

// 特定フォーマットから作成
DateTime.fromISO('2017-05-15')
DateTime.fromMillis('1494774000000')
// など

https://moment.github.io/luxon/docs/manual/tour.html#creating-a-datetime
https://moment.github.io/luxon/docs/manual/parsing.html

変更

DateTime.fromISO('2017-05-15').plus({months: 2, days: 6}).toISODate();
//=> '2017-07-21'

https://moment.github.io/luxon/docs/manual/tour.html#transforming-your-datetime
https://moment.github.io/luxon/docs/manual/math.html

出力

dt.toISODate();
//=> '2017-04-20'

dt.valueOf();
//=> 1494774000000

dt.toJSDate();
//=> Native Date Object

dt.toLocaleString(DateTime.DATETIME_FULL);
//=> 'April 20, 2017, 11:32 AM EDT'

https://moment.github.io/luxon/docs/manual/formatting.html

日本語にする

import { DateTime, Settings } from 'luxon';

DateTime.fromMillis(1494774000000, { locale: 'ja' }).toLocaleString(DateTime.DATETIME_HUGE);
// -> 2017年5月15日月曜日 0:00 日本標準時

// デフォルトを変更
Settings.defaultLocale = 'ja';

https://moment.github.io/luxon/docs/manual/intl.html

タイムゾーンの変更

import { DateTime, Settings } from 'luxon';

const tz = 'America/Los_Angeles';

DateTime.fromMillis(1494774000000, { locale: 'ja', zone: tz }).toLocaleString(DateTime.DATETIME_HUGE);
// -> 2017年5月14日日曜日 8:00 アメリカ太平洋夏時間

// デフォルトを変更
Settings.defaultZoneName = tz;

// 作成後の変更
const dt = DateTime.local();
const rezoned = local.setZone('America/Los_Angeles');

// ローカルタイムゾーン、デフォルトタイムゾーンに戻す
// https://github.com/moment/luxon/blob/f70e17c5134f221b2534b8fb035374a50e220398/src/impl/util.js#L183
const rezonedDefault = rezoned.setZone();
const rezonedLocal = rezoned.setZone('local');

https://moment.github.io/luxon/docs/manual/zones.html

今後の懸念

  • Intl APIのポリフィルがちょっと不安。

デモ

https://stackblitz.com/edit/angular-date-pipe-and-luxon

Refs

Refs

Remove all ads