LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

Rails アプリを個人開発からチーム開発に成長させる

Last updated at Posted at 2018-11-19
1 / 41

自己紹介

image.png


会社紹介


今回のお話


:doughnut: 技術選定のために :doughnut:


まずは1人でサービスを作る

image.png


Rails 以外を触ってみる

image.png

eliact (1).gif


:laughing: ポジとネガ :expressionless:


ネガ

  • Rails + jQuery は「振る舞い」と「表示」をセットにするのが難しい
  • FLOCSS は粒度が粗い
  • Capistrano の読み書きがめんどくさい
  • Elixir はググれないので時間がかかる
  • React + Redux は記述量がめちゃめちゃ増える

ポジ

  • Rails はやっぱり早い
  • Service 層をリッチにするのはテストを書きやす
  • atomic design の設計は可変性高い
  • React + Redux は「振る舞い」を含めたコンポーネント化がコードに量を減らす
  • GCP が使いやすい気がする

:tada: 君に決めたっっ!! :tada:

  • 最初は Rails で開発
  • コンポーネントが見えてきたら Rails + React に変更
  • 初期の頃から atomic design の atoms はちゃんと作る
  • Controller への記述は控えて積極的に Service に切り出す
  • Service に対してのみテストを書く
  • Heroku から初めて docker を使って GCP に移行する

:cookie: まずは1人で view の設計から :cookie:


Sketch でコンポーネント設計 :pencil2:

image.png


Symbol を丁寧に作りました :sunglasses:

image.png


一旦 Rails で実装 :full_moon_with_face:

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 へ :rocket:

  • view ができた段階で一旦 Heroku に上げました
    • ユーザーインタビュー/ユーザービリティテストに使われます
    • 関係各所へのデモに使われます

フィードバックを反映

  • ユーザーに必要な情報とインタラクションが明らかになる
  • コンポーネントの「振る舞い」の設計ができるようになる
  • DB の設計ができるようになる

:lollipop: 裏側実装に移っていきます :lollipop:


複数開発に備えて実装を気をつける


before_action の乱用を控える

  • before_actionredirect_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

:tada: 3人の開発チームになる :tada:


docker の準備ができておらず手こずる :sweat_smile:


ぼくの仕事

  • github にひたすら issue を積んで、コードをレビュー
  • Rails の API 化も進めてる(issue 書いただけ)
    • graphql も考えてます(issue 書いただけ
  • JS でコンポーネントに「振る舞い」の実装
  • atomic, molecules の追加
  • サービス内の文言、ページ遷移、フィードバック、マージンの修正

:candy: JS のコンポーネント設計 :candy:


グラデーションをつけて 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'

:tada: 来週からもうひとり Join :tada:


インフラ系の準備を進める

  • docker file の作成
  • Heroku の無料プランだと限界が見えてきたので GCP に移行準備中
    • BigQuery を使ってみたい
    • 良いユーザー体験は、良い分析からだと思う

おまけ :smile:


image.png


終わり :beer:


APPENDIX


やっと React :sun_with_face:

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 に書き出したロジックを書くようにする

いい感じ :laughing:


でも、よくわからないこともある :sweat_smile:


midleware がどんどん大きくなる :pizza:

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 の部分がどんどん太っていく

テストってどこまで書くの :green_heart:

  • まだ、テストぜんぜん書いてません。。。笑
  • React はテストライブラリーがたくさんある
    • ストラクチュラル・リグレッション・テスト
    • ビジュアル・リグレッション・テスト
    • インタラクション・テスト
    • レイアウト・テスト
    • パフォーマンス・テスト
    • クロスプラットフォーム・テスト
    • アクセシビリティ・テスト
    • etc

テスト好きだけど、こんなに書くのはしんどい。。。


コンポーネント管理 :wrench:

  • 実装中に「これ molecules だなー」っていうコンポーネントもある
  • 今はまだ僕だけので、Storybook で管理

どんどん、Sketch と Storybook が乖離していく。。。


それでも、やっぱりいい感じ :laughing:

  • 非同期でヌルヌル動く体験はユーザーにわかりやすいと思う
  • React Native もあるので、ネイティブアプリ化も捗る(もともと、それが目的)
  • 懇親会でいろいろ教えてくださいー!笑

おわり :beer:

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up