自己紹介
会社紹介
今回のお話
技術選定のために
まずは1人でサービスを作る
Rails 以外を触ってみる
ポジとネガ
ネガ
- Rails + jQuery は「振る舞い」と「表示」をセットにするのが難しい
- FLOCSS は粒度が粗い
- Capistrano の読み書きがめんどくさい
- Elixir はググれないので時間がかかる
- React + Redux は記述量がめちゃめちゃ増える
ポジ
- Rails はやっぱり早い
- Service 層をリッチにするのはテストを書きやす
- atomic design の設計は可変性高い
- React + Redux は「振る舞い」を含めたコンポーネント化がコードに量を減らす
- GCP が使いやすい気がする
君に決めたっっ!!
- 最初は Rails で開発
- コンポーネントが見えてきたら Rails + React に変更
- 初期の頃から atomic design の atoms はちゃんと作る
- Controller への記述は控えて積極的に Service に切り出す
- Service に対してのみテストを書く
- Heroku から初めて docker を使って GCP に移行する
まずは1人で view の設計から
Sketch でコンポーネント設計
Symbol を丁寧に作りました
一旦 Rails で実装
sign_up.rb
section.section
.container
= render 'shared/atoms/heading', title: 'アカウント登録', margin: 25
= render 'shared/molecules/messages/default',
title: '必要書類',
content: '登録には会社代表者の運転免許証情報が必要となります。<br>お手元にご用意の上、登録を進めて下さい。',
margin: 25
= form_for resource, as: resource_name, url: registration_path(resource_name) do |f|
= render 'shared/molecules/inputs/text',
form: f,
name: :name,
label: '名前',
placeholder: '山田 太郎',
margin: 25
= render 'shared/atoms/buttons/default', form: f, text: '登録', margin:25
= render 'shared/atoms/link', path: unauthenticated_root_path, text: 'アカウントを登録済みの方こちら', margin: 40
- Symbol をパーシャルに切り出す
- (CSSModule っぽく)パーシャルに一対一になるように CSS を書く
- ロジック部分は helper に書き出しておく
Heroku へ
- view ができた段階で一旦 Heroku に上げました
- ユーザーインタビュー/ユーザービリティテストに使われます
- 関係各所へのデモに使われます
フィードバックを反映
- ユーザーに必要な情報とインタラクションが明らかになる
- コンポーネントの「振る舞い」の設計ができるようになる
- DB の設計ができるようになる
裏側実装に移っていきます
複数開発に備えて実装を気をつける
before_action
の乱用を控える
-
before_action
でredirect_to
しまくってるコードは最悪 - 「インスタンス変数のセット」と「認証・認可」だけに使う
bad.rb
before_action :redirect_to_done_before_admission_passed
:
def redirect_to_done_before_admission_passed
redirect_to done_sub_sub_dealers_path unless @current_sub_dealer.admission_passed?
end
good.rb
before_action :authenticate_account!
before_action :set_sub_dealer
accept_nested_attributes_for
は止めておく
- 魔法が多すぎる
-
model
のリレーションまで読みにいかきゃいけないのでわかりにくい
Service に対しては必ずテスト書く
- Service 名とテストを見れば何やっているかだいたい分かるようにする
service.rb
class Companies
class SelfConfirm < ApplicationService
object :account
object :company
validates :account, :company, presence: true
def execute
# ココにアプリケーション層のロジックを入れる
# メールの配信や状態変化など
company
rescue ActiveRecord::RecordInvalid
errors.add(:state, 'が不正です。')
end
end
end
service_spec.rb
RSpec.describe Companies::SelfConfirm, type: :service do
let!(:company) { create(:company, :with_account) }
let!(:account) { company.accounts.first }
subject do
described_class.run(
account: account,
company: company
)
end
it do
expect { subject }
.to change { company.hoge.count }.by(1)
.and change { company.fuga.count }.by(1)
.and change { company.reload.current_state }.from('old').to('new')
end
end
3人の開発チームになる
docker の準備ができておらず手こずる
ぼくの仕事
- github にひたすら issue を積んで、コードをレビュー
- Rails の API 化も進めてる(issue 書いただけ)
- graphql も考えてます(issue 書いただけ
- JS でコンポーネントに「振る舞い」の実装
- atomic, molecules の追加
- サービス内の文言、ページ遷移、フィードバック、マージンの修正
JS のコンポーネント設計
グラデーションをつけて React に移行
-
rails_script
でロジックが散らからない用に整理 - atomic design の molecules に「振る舞い」が結びつくようにパーシャルを修正
sample.js
import Base from '../Base'
import autoFocus from 'common/utilities/autoFocus'
export default class AccountsConfirmations extends Base {
show() {
autoFocus()
}
confirm() {
autoFocus()
}
}
sample.slim
- margin ||= 0
.m-inputs_passcode class="u-mt#{margin}"
.field.m-inputs_passcode__field.u-mb0
.control
= telephone_field_tag "#{form.object.class.name.underscore}[passcodes][]",
nil,
maxlength: 1,
id: 'payment_request_passcodes_1',
class: 'input m-inputs_passcode__input js-inputs_passcode__input'
.field.m-inputs_passcode__field.u-mb0
.control
= telephone_field_tag "#{form.object.class.name.underscore}[passcodes][]",
nil,
maxlength: 1,
id: 'payment_request_passcodes_2',
class: 'input m-inputs_passcode__input js-inputs_passcode__input'
.field.m-inputs_passcode__field.u-mb0
.control
= telephone_field_tag "#{form.object.class.name.underscore}[passcodes][]",
nil,
maxlength: 1,
id: 'payment_request_passcodes_3',
class: 'input m-inputs_passcode__input js-inputs_passcode__input'
.field.m-inputs_passcode__field.u-mb0
.control
= telephone_field_tag "#{form.object.class.name.underscore}[passcodes][]",
nil,
maxlength: 1,
id: 'payment_request_passcodes_4',
class: 'input m-inputs_passcode__input js-inputs_passcode__input'
来週からもうひとり Join
インフラ系の準備を進める
- docker file の作成
- Heroku の無料プランだと限界が見えてきたので GCP に移行準備中
- BigQuery を使ってみたい
- 良いユーザー体験は、良い分析からだと思う
おまけ
終わり
APPENDIX
やっと React
sample.jsx
export const IconPresenter = ({
iconName,
height = 20,
width = 20,
...props,
}) => (
<img
src={ iconName }
alt=''
height={ height }
width={ width }
{ ...props }
/>
);
export const IconContainer = ({
presenter,
className = '',
onClick,
...props,
}) => {
if (onClick) className += ` ${ styles.clickable }`;
return presenter({ onClick, className, ...props });
};
- もう作るものは大体わかっている
- Rails のパーシャル単位でコンポーネント作成
- CSS はコピーしてくる
- Container Component に Rails の helper に書き出したロジックを書くようにする
いい感じ
でも、よくわからないこともある
midleware がどんどん大きくなる
action.js
import api from '../api';
import { reset } from 'redux-form';
import { Presence } from 'phoenix';
const syncPresentUsers = (dispatch, presences) => {
const presentUsers = [];
Presence.list(presences, (id, { metas: [first] }) => first.user)
.map(user => presentUsers.push(user));
dispatch({ type: 'ROOM_PRESENCE_UPDATE', presentUsers });
};
export function connectToChannel(socket, roomId) {
return (dispatch) => {
if (!socket) { return false; }
const channel = socket.channel(`rooms:${roomId}`);
let presences = {};
channel.on('presence_state', (state) => {
presences = Presence.syncState(presences, state);
syncPresentUsers(dispatch, presences);
});
channel.on('presence_diff', (diff) => {
presences = Presence.syncDiff(presences, diff);
syncPresentUsers(dispatch, presences);
});
channel.on('message_created', (message) => {
dispatch({ type: 'MESSAGE_CREATED', message });
});
channel.join()
.receive('ok', (response) => {
dispatch({ type: 'ROOM_CONNECTED_TO_CHANNEL', response, channel });
})
.receive("error", resp => { console.log("Unable to join", resp) })
return false;
};
}
export function leaveChannel(channel) {
return (dispatch) => {
if (channel) {
channel.leave();
}
dispatch({ type: 'USER_LEFT_ROOM' });
};
}
export function createMessage(channel, data) {
return dispatch => new Promise((resolve, reject) => {
channel.push('new_message', data)
.receive('ok', () => resolve(
dispatch(reset('newMessage'))
))
.receive('error', () => reject());
});
}
export function loadOlderMessages(roomId, params) {
return (dispatch) => {
dispatch({ type: 'FETCH_MESSAGES_REQUEST' });
return api.fetch(`/rooms/${roomId}/messages`, params)
.then((response) => {
dispatch({ type: 'FETCH_MESSAGES_SUCCESS', response });
})
.catch(() => {
dispatch({ type: 'FETCH_MESSAGES_FAILURE' });
});
};
}
- 非同期の通信が多いから、midleware の部分がどんどん太っていく
テストってどこまで書くの
- まだ、テストぜんぜん書いてません。。。笑
- React はテストライブラリーがたくさんある
- ストラクチュラル・リグレッション・テスト
- ビジュアル・リグレッション・テスト
- インタラクション・テスト
- レイアウト・テスト
- パフォーマンス・テスト
- クロスプラットフォーム・テスト
- アクセシビリティ・テスト
- etc
テスト好きだけど、こんなに書くのはしんどい。。。
コンポーネント管理
- 実装中に「これ molecules だなー」っていうコンポーネントもある
- 今はまだ僕だけので、Storybook で管理
どんどん、Sketch と Storybook が乖離していく。。。
それでも、やっぱりいい感じ
- 非同期でヌルヌル動く体験はユーザーにわかりやすいと思う
- React Native もあるので、ネイティブアプリ化も捗る(もともと、それが目的)
- 懇親会でいろいろ教えてくださいー!笑