Laravel/Vue.js勉強会#7

こんにちは、SCOUTERでフロントエンドエンジニアのhirokiです。

LaraVue勉強会も皆様のおかげで第7回を迎えることが出来ました!いつもたくさんの方にお越しいただき運営をしている側としては嬉しい限りです!

今回株式会社オープンロジさんのご協力のもと池袋にある貸会議室で行いました!

発表

初めてのLaravelで学習を始めるまでのお話

弊社SCOUTERで10月からjoinしてくれたshowさんの発表です! 結局公式ドキュメントを信じようという話をしてくれました!

f:id:hiroki-nishizawa:20190201132942j:plain 自分でいろいろ勉強してわからなくなって調べて、その調べたものが分からないという話を、聞いていて僕も意味わかんなかったな。。。とか、今でもわからないな。。。とか後ろで見ながら思っていましたw とてもテンポの良くておもしろいLTでした!僕も頑張っていきます!

speakerdeck.com

コンシューマ向けVue SPAでプロダクトを開発して得た知見。

今日2/1から弊社SCOUTERにjoinしてくださるjiyuujinさんの発表です! f:id:hiroki-nishizawa:20190201130026j:plain

SPAでプロダクトを開発していたということで「にゃんこスタジオ」さんで得た知見をすべてではないですがお話してくれました! これから一緒に頑張りましょう!よろしくおねがいします!

master.d1xmp1dbc0142d.amplifyapp.com

実運用におけるLaravelとNuxtでのRepositoryのレイヤ分割の話

上記タイトルに有るように「実運用におけるLaravelとNuxtでのRepositoryのレイヤ分割」についてkon_shouさんが話してくれました!

f:id:hiroki-nishizawa:20190201124725j:plain

デメリットでも書いてある初見殺し。。。自分も思っていましたw ただメリットを見た時に責任分担を明確にできるというところはこれから人数増えてきた時にとてもいいと思いました!

speakerdeck.com

VueのUIフレームワークをまとめました2019

株式会社プラムザの中の人をやっているplumsaさんのLTです!

f:id:hiroki-nishizawa:20190201122157j:plain

VueのUIフレームワークgithubスターランキングや直近3ヶ月のnpmインストール数をランキング形式にして発表してくれました! 弊社ではelement-uiを使用しているのですが、レスポンシブ対応をしていないのを初めて知りました!@plumsaさんが使っているVuetify。。。使ったことがないので使ってみようかなと思います!

Testing on Laravel

弊社SCOUTERで業務委託で仕事をしてくれているnunulkさんのLTです! f:id:hiroki-nishizawa:20190201123941j:plain いろいろなテストのやり方について発表してくれました! 個人的にテストをガッツリ書いたことがないのでModel Factory + Fakerが気になりました!これからもどんどんテストを書いていきましょう!

speakerdeck.com

懇親会

発表後はちょっとしたフードとドリンクを用意したのでみんなでわいわい懇親会をしました! f:id:hiroki-nishizawa:20190201120434j:plain

まとめ

今回SCOUTERの人が3人も出てしまうという初めての勉強会でした!が、LTをやっていただいた5名それぞれいろいろな角度からLaravel,Vueについて話して、とても有意義な時間でした!

次回はの日程、場所ともに未定なので、次回以降会場提供してくださる方いましたら弊社CTO @kotamatまでご連絡お願いします!

最後に

SCOUTER社では一緒に頑張ってくれる方を募集しております。 デザイン、エンジニアの皆さん興味のある方はご応募お願いします!

www.wantedly.com

FlutterでWebエンジニアが1日でアプリを作った

こんにちは、株式会社SCOUTERの石岡 将明( @masaakikunsan )です。

2019年が始まり、一ヶ月が過ぎようとしていますが、皆さんはどうお過ごしでしょうか? 私は、プログラミングを始め2年が経ちWeb以外にも手をつけていこうかと考えている今日このごろです。

ということで、今回は弊社サービス SARDINEAPIと Flutter で求人が見れるアプリを作ったので Flutter について書いていこうと思います。

Flutter とは

flutter.io

AndroidiPhoneアプリ開発を行うためのフレームワークです。 Flutter の開発は、 Google によって開発された Dart というプログラミング言語を使用します。

Flutter の特徴

  • 同じコードを使用して、AndroidiPhoneアプリ開発ができる
  • Hot Reload により、リアルタイムで変更が確認できる
  • Material や Cupertino といった Widget を使用することでアプリっぽい見た目を簡単につくれる

なぜ Flutter を選んだのか

冒頭でも述べた通り、私は2019年はアプリにも手を出そうと考えていました。 アプリをやるなら、Swift や Kotlinなどいろいろ選択肢がある中で私は React が書けるので React Native に手を出そうとしていました。

そんな中とある日に、「JavaScript のような Java のようなプログラミング言語Dart で開発できる Flutter というフレームワークがあるよ」と天の声が聞こえ、お!と思い Twitter に やるぞの意思表示をしたところ 実質 React + TS と言われたので触らない理由はなかったです。

私は、 普段 JS で開発をしており、フリー時代には Javaフレームワークである Spring Boot でAPI開発もしたことがあり、React 開発経験者でもある為 Flutter に興味を持ち触ることにしました。

f:id:masaakikunsan:20190130120702p:plain

入門

それでは早速、Flutter の入門を解説していきます。

まずはじめに Flutter SDK をダウンロードします。下記 URL からダウンロードしましょう。

flutter.io

SDK のダウンロードが終わったら、好きな場所で解凍してください。

次に、Flutter の コマンドをどこからでも使えるように、PATH の登録を行います。 私は zsh を使っているので .zshrc に以下を追加しました。

export PATH=$PATH:$HOME/flutter/bin

追加したら、反映するために下記コマンドを実行します。

$ source ~/.zshrc

これで Flutter が使える状態です。

開発をするための設定

今回は iOS の開発をする予定だったので XCode の設定をしました。 App Store か 公式サイトから XCode をインストールしましょう。

インストールが完了したら、ライセンスに同意しましょう。

$ sudo xcodebuild -license

これで Flutter 開発をするまでの準備が整いました。

プロジェクトの作成

プロジェクトを作成しきましょう。 flutter create <project_name> をターミナルで実行することでプロジェクトを作成できます。 作成したら起動してみましょう。

$ cd project_name
$ flutter run

そうすると Counter のアプリが開かれるかと思います。

デモコードの解説

ディレクトリを見ると lib フォルダがあります。 ここにアプリケーションのプログラムを書いていきます。

さっそく lib/main.dart のコードを見ていきましょう。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

コードを読めばだいたい想像つくと思うので細かい説明は省きます。

画面表示は Widget と呼ばれる部品によって作成されています。 Sample Codeでは StatefulWidget つまりステートを持てる Widget でボタンとテキストをもったClassがあり、それを MyApp class のHomeに設定しています。 MyApp classではtitleの設定や、themeの設定もしており、main関数を使ってアプリを起動しているといった感じです。

作成物

今回は、SARDINE API を使用し、ログインし求人が見れるアプリを作りました。 Navigator でページ遷移したり、API を叩いてログインや求人を取得したりしています。

昨日 Flutter の勉強を始め、1日で作ったのでもう少し詳しくなったタイミングでコードと一緒に紹介するブログを書かせてください。

まとめ

Flutter は Web エンジニアでもすぐにかじることができ良いフレームワークでした。 しかし、用意されている Widget を検索したり仕様を把握しないといけないのでちゃんと書けるレベルになるにはかなりドキュメントを読む必要があるなと感じました。 また、アプリでは Web と違い localStorage が使えなかったりと State 周りでかなり苦労しそうです。 次ちゃんと触るときは Redux が使えるようなので Redux で状態管理も考えながら作成しようかなと思います。

2月半ばにAPIの叩き方やページ内のレイアウト作り方などをちゃんと解説したブログを書こうと思うのでお楽しみに!

最後に

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

markdown-itの導入方法

こんにちは!
株式会社SCOUTER開発部フロントエンドエンジニアの佐藤(@r_sato1201)です

先日、業務でCMSでコンテンツを管理するLPをNuxtプロジェクトで作成しました。 その際に、Markdownをhtmlにパースする必要がありmarkdown-itを触ったので、 備忘録も兼ねてNuxtプロジェクトを使用する際のmarkdown-itについて書きたいと思います。

導入方法

@nuxtjs/markdownitのインストール

まずは、markdown-itのnpmパッケージをインストールします。

yarn add @nuxtjs/markdownit

nuxt.config.jsにmarkdownitの設定を記述

nuxt.config.jsにnuxtjs/markdownitのモジュール読み込みと主なオプションを記述します。 ※各オプションデフォルト値です

nuxt.config.js

modules: [
    '@nuxtjs/markdownit'
  ],
markdownit: {
    preset: 'default'
    injected: true, 
    breaks: true, 
    html: true, 
    linkify: true,
    typography: true, 
    xhtmlOut: true,
    langPrefix: 'language-',
    quotes: '“”‘’',
    highlight: function (/*str, lang*/) { return ''; },
  },

各オプションを説明します。

オプション名 説明
preset パースするファイルの規格(Markdown/CommonMark)を指定します (※詳細は後で記述します)
injected $mdを利用してMarkdownをhtmlにパースする
breaks Markdown内の改行コードを<br>に変換する
html HTMLタグを有効にする
linkify URLに似たテキストをリンクに自動変換する
typography 言語に依存していない引用符などを綺麗にする
xhtmlOut 単一のタグを閉じるには 「/」を使用する
langPrefix コードブロックのCSSクラス名の接頭辞に付加します。
quotes typographyが有効になっていて'"を同時に使用している際に自動で識別し変換してくれます。
highlight 第1引数で指定したタグ名に対して、第2引数で指定した言語名のシンタックスハイライトを行う処理を指定できます。返り値にシンタックスハイライト後の内容を返すよう指定できます。

CommonMarkとMarkdownについて

CommonMarkとはMarkdownで曖昧になっている細部の構文仕様を明確化したものです。 (CommonMarkについて詳しく知りたい方は以下を参照して下さい)

commonmark.org

CommonMarkとMarkdownでは細かい仕様の差異があります。 例えば、リストの見出しです。 Markdownでは、見出しを以下のように記述することが出来ます。

# Heading

CommonMarkでは、これはリスト内の見出しとして解釈されてしまうため、エスケープする必要があります。

\# Heading

このような差異があるため、前述したpresetで規格を指定することにより 適切にhtmlにパースすることが出来ます。

利用方法

$md を使ってパースする場合

オプションのinjected:trueに設定すると、$mdという変数が使えます。 sample.vue

<template>
  <div v-html="$md.render(model)"></div>
</template>

<script>
export default {
  data() {
    return {
      model: '# Hello World!'
    }
  }
}
</script>

CMSからMarkdown形式のデータが渡ってきている場合は $md.render(model) 上記のrenderの引数に、Markdown形式のデータを渡すことでパースされHTMLにレンダリングされます。

.mdファイルを使ってパースする場合

どこかのディレクトリに格納されているMarkdownファイルをimportしcomputedで返し、v-htmlでレンダリングすることで表示できます。
sample.md

# Hello World!!

sample.vue

<template>
  <div v-html="sample"></div>
</template>

<script>
  import sample from '../sample.md'

  export default {
    computed: {
      sample() {
        return sample
      }
    }
  }
</script>

パースしたhtmlにスタイルを当てる際の注意点

v-html によって作成された DOM コンテンツはの子要素には、data 属性がつかなく、 <style scoped>とスコープをつけCSSの影響範囲をコンポーネント内に収めている場合 data属性がついていない要素にはCSSが当たりません。
つまり、markdown-it で$md を使ってパースする場合、通常通りCSSを当ててもうまく効かないので ディープセレクタを使用して当てるようにしましょう。

※ディープセレクタを仕様する際はsass環境が必要となるのでnode-sass および sass-loaderのパッケージをインストールしてください

sample.vue

<template>
    <div class="sample-wrapper">
      <div v-html="$md.render(model)"></div>
    </div>
</template>

<script>
export default {
  data() {
    return {
      model: '# Hello World!'
    }
  }
}
</script>

<style  lang="scss" scoped>
.sample-wrapper /deep/ h1 {
  border-left: solid 2px #6ac5b6;
}
</style>

拡張方法

markdown-itには機能を拡張できるパッケージが複数あります。 まず、yarnなどでパッケージをインストールします。

yarn add markdown-it-<パッケージ名>

nuxt.config.jsのmarkdownitオプションに以下の記述でパッケージを読み込むよう設定することで markdown機能を拡張することができます。

nuxt.config.js

markdownit: {
    use: [
      'markdown-it-<パッケージ名>',
    ]
  },

いくつかの拡張パッケージを紹介します。

脚注

f:id:ryonnsui1201:20190123230953p:plain

ブログなどで使う脚注を使用することが出来ます。 使用する際は脚注を指定したい箇所に[^n]を記述し 脚注として表示したい内容は[^n]:を先頭に付けます。

sample_footnote.md

これは脚注[^1]のテストです

[^1]:これは1つ目の脚注の内容です

導入方法は以下となります。

yarn add markdown-it-footnote

nuxt.config.js

markdownit: {
    use: [
      'markdown-it-footnote',
    ]
  },

ハイライト

f:id:ryonnsui1201:20190123231001p:plain

指定箇所がmark要素で囲まれ、ハイライト表示をすることができます。 使用する際は適用させたい箇所の前後に==を記述します。 デフォルの背景色は黄色ですが、mark要素にcssを当てることで自由なスタイルで表示させることが出来ます。

sample_highlight.md

==ハイライト表示==のテストです

導入方法は以下となります。

yarn add markdown-it-mark

nuxt.config.js

markdownit: {
    use: [
      'markdown-it-mark',
    ]
  },

Youtube埋め込み

YouTubeの埋め込みをすることができます。 使用する際は以下のサンプルのように、[]内にyoutubeを指定し()内に表示させたい動画のIDを指定します。 動画のIDではなく、動画のURLでも可能です。
sample_video.md

[youtube](movie id)

導入方法は以下となります。

yarn add markdown-it-video

nuxt.config.js

markdownit: {
    use: [
      'markdown-it-video',
    ]
  },

さいごに

私のような経験が浅いエンジニアの方の力に少しでもなることができればと思い書きました。

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

normalizrの使い方

こんにちは株式会社SCOUTERでフロントエンドエンジニアをしているhirokinishizawaです。

弊社サービスである「人材紹介会社向けの業務管理システム」SARDINEで新しく無料業務管理ツールをリリースしました。 無料業務管理ツールを開発するにあたり設計段階で複雑にネストされるのがわかっていたのでnormalizrを導入することになりました。 今回このブログでは導入したnormalizrの使い方を書いていきます。

normalizrとは

normalizrはデータを正規化するためのライブラリです。 公開されているAPIでも、自社サービスで作成しているAPIでも複雑にネストされているデータを扱うことが多々あると思います。

複雑にネストされているデータを取り扱うのはなかなか大変ですが正規化する事によりEntity毎にidをキーとしたオブジェクトになるためidで辿ることでデータを取ってくることができます。

開発環境

nuxt: 2.2.0
normalizr: 3.3.0

インストール

yarn add normalizr
or
npm install normalizr

使い方の説明

やりたいこと

冒頭で正規化するとidをキーとしたオブジェクトになりidで辿ることでデータを取ってくることが出来るという話を少ししたと思いますが、実際にどのように使うのかを正規化していないデータをstore/module/denormalize.js、正規化したデータをstore/module/normalize.jsのstateに保管して比較しながら使い方の説明をして行きたいと思います。

実際に正規化していないデータと正規化したデータが以下のようになります。

正規化していないデータ

store/module/denormalize.js

// state.posts
  posts: [
    {
      id: 100,
      text: 'テスト1',
      user: {
        id: 100002,
        name: 'Michael',
      },
      comments: [
        {
          id: 200,
          text: 'コメント1',
          post_id: 100,
          user: {
            id: 100000,
            name: 'Jone',
          },
        },
        {
          id: 201,
          text: 'コメント2',
          post_id: 100,
          user: {
            id: 100001,
            name: 'Mary',
          },
        },
      ],
    },
    {
      id: 101,
      text: 'テスト2',
      user: {
        id: 100001,
        name: 'Mary',
      },
      comments: [
        {
          id: 202,
          text: 'コメント3',
          post_id: 101,
          user: {
            id: 100000,
            name: 'Jone',
          },
        },
        {
          id: 203,
          text: 'コメント4',
          post_id: 101,
          user: {
            id: 100000,
            name: 'Jone',
          },
        },
      ],
    },
  ],

正規化したデータ

store/module/normalize.js

// state.posts
  posts: {
    100: {
      id: 100,
      text: 'テスト1',
      user: 100002,
      comments: [200, 201],
    },
    101: {
      id: 101,
      text: 'テスト2',
      user: 100001,
      comments: [202, 203],
    },
  },
// state.comments
  comments: {
    200: {
      id: 200,
      text: 'コメント1',
      post_id: 100,
      user: 100000,
    },
    201: {
      id: 201,
      text: 'コメント2',
      post_id: 101,
      user: 100001,
    },
    202: {
      id: 202,
      text: 'コメント3',
      post_id: 102,
      user: 100000,
    },
    203: {
      id: 203,
      text: 'コメント4',
      post_id: 103,
      user: 100000,
    },
  },
// state.users
  users: {
    100000: {
      id: 100000,
      name: 'Jone',
    },
    100001: {
      id: 100001,
      name: 'Mary',
    },
    100002: {
      id: 100002,
      name: 'Michael',
    },
  },

このようなデータになるまでの説明をしていきたいと思います。

normalizrを実行してstateに保管する

始めにSchema情報を定義してnormalizrを実行するコードを書きます。


import { normalize, schema } from 'normalizr'

// userを表すスキーマ定義
const user = new schema.Entity('users');

// commentを表すスキーマ定義
// commentの中には `user` というデータがありそこに先ほど定義した`user`をセットします。
const comment = new schema.Entity('comments', {
 user: user
});

// postを表すスキーマを定義します
// postにも`user`というデータがあり先ほどと同じように先ほど定義した`user`をセットします。
// postには`comments`というデータがあり`user`と同じように先ほど定義した`comment`をセットします。
const post = new schema.Entity('posts', { 
  user: user,
  comments: [ comment ]
});

// 引数(data)には正規化していないデータを渡します。
export const normalizePost = (data) => {
  return normalize(data, post)
}

引数のdataには先程の正規化する前のデータを入れることにより以下のような正規化されたデータが生成されます。

results: [100, 101],
entities: {
  posts: {
    100: {
      id: 100,
      text: 'テスト1',
      user: 100002,
      comments: [200, 201],
    },
    101: {
      id: 101,
      text: 'テスト2',
      user: 100001,
      comments: [202, 203],
    },
  },
  comments: {
    200: {
      id: 200,
      text: 'コメント1',
      post_id: 100,
      user: 100000,
    },
    201: {
      id: 201,
      text: 'コメント2',
      post_id: 100,
      user: 100001,
    },
    202: {
      id: 202,
      text: 'コメント3',
      post_id: 101,
      user: 100000,
    },
    203: {
      id: 203,
      text: 'コメント4',
      post_id: 101,
      user: 100000,
    },
  },
  users: {
    100000: {
      id: 100000,
      name: 'Jone',
    },
    100001: {
      id: 100001,
      name: 'Mary',
    },
    100002: {
      id: 100002,
      name: 'Michael',
    },
  },
}

上記のデータの各項目ごとにstateを保管すればデータの保管は完了です。

正規化したデータの使い方

次に先ほどstateにいれた正規化されたデータの使い方を正規化していないデータと比較しながら書いていきます。

正規化していない場合

store/module/denormalize.js

const getters = {
  getPostById: (state) => (postId) => state.posts.filter((post) => {
    return post.id === postId
  })
}

index.vue

<template>
  <div>
// 投稿した内容
    {{post.text}}
// 投稿したユーザー名
    {{post.user.name}}
  </div>
  <div v-for="(comment, key) in post.comments" :key="key">
// コメントテキスト
    {{comment.text}}
// コメントしたユーザー名
    {{comment.user.name}}
  </div>
</template>

<script>
  computed: {
    ...mapGetters({
      getPostById: 'post/getPostById'
    }),
    post() {
      return this.getPostById(100)
    }
  }
</script>

this.getPostById(100)のデータ

    {
      id: 100,
      text: 'テスト1',
      user: {
        id: 100002,
        name: 'Michael',
      },
      comments: [
        {
          id: 200,
          text: 'コメント1',
          post_id: 100,
          user: {
            id: 100000,
            name: 'Jone',
          },
        },
        {
          id: 201,
          text: 'コメント2',
          post_id: 100,
          user: {
            id: 100001,
            name: 'Mary',
          },
        },
      ],
    },

正規化した場合

store/module/normalize.js

const getters = {
  getPostById: (state) => (postId) => state.posts[postId],
  getCommentById: (state) => (commentId) => state.comments[commentId]
  getUserById: (state) => (userId) => state.users[userId]
},

index.vue

<template>
  <div>
// 投稿した内容
    {{post.text}}
// 投稿したユーザー名
    {{contributor.name}}
  </div>
  <div v-for="(comment, key) in post.comments" :key="key">
// コメントテキスト
    {{getCommentById(comment).text}}
// コメントしたユーザー名
    {{getUserById(getCommentById(comment).user).name}}
  </div>
</template>

<script>
  computed: {
    ...mapGetters('store', [
      'getPostById',
      'getCommentById',
      'getUserById',
    ]),
    post() {
      return this.getPostById(100)
    },
    contributor() {
      return this.getUserById(this.post.user)
    }
  }
</script>

this.getPostById(100)のデータ

    {
      100: {
        id: 100,
        text: 'テスト1',
        user: 100002,
        comments: [200, 201],
      },
    },

this.getUserById(this.post.user)のデータ

  100002: {
      id: 100002,
      name: 'Michael',
    },
  },

正規化することによりcommentやuserのように少し複雑にネストされていてもidで取り扱うことがてきるようになります。

まとめ

サンプルデータではあまり複雑にネストされていないので、normalizrの実行するのに少し書くコード量を増やせばidをキーとしたオブジェクトに変換してくれるぐらいと思った人もいるかと思います。ですが普段みなさんが扱っているデータは上記なんかよりもっと複雑にネストされているかと思います。複雑にネストされていてもnormalizrを実行することでデータの階層がなくなりEntity毎にidで辿れるということでロジックがシンプルになり、総合的にみたらコード量も減るというとこが魅力的だなと感じました。

最後に

サービスの成長と共にこれから一緒に切磋琢磨していけるメンバーを増やしていきたいと思っています。

興味のある方は下記からご応募いただくか、hirokinishizawaにご連絡ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

SCOUTER社でデータ可視化が定着するまで

こんにちは、株式会社SCOUTERの開発責任者の小平(@ryotakodaira )です。 業務では、SARDINEという人材紹介会社向けの業務管理システムを開発しています。

自分の担当している事業では、metabaseというBIツールを使用して数値管理・可視化をしています。 そこで今回は事業全体にBIツールを用いた数値管理を定着させるまでの話を書いていきます。

BIツールとは簡単にいうとサービスのDBなどに蓄積されているデータを可視化するためのツールです。

BIツールの「BI」とは、「ビジネス・インテリジェンス(Business Intelligence)」を意味します。ビジネス・インテリジェンスとは、企業が日々蓄積されていく膨大なデータを分析し、その分析結果を経営意思決定に活用することをいいます。 このBIを助けるシステムを総称したものを「BIツール」と呼びます。 *1

metabase導入前

導入前は、サービス内の主要KPIを確認するものはほぼ存在せず、どうしようもない状態でした。。。

そんな状態でも当然、事業部のメンバーから数値を出力する依頼が来る訳ですが、その都度SQLを書いて出力結果のCSVを渡していたり、クレンジングされたデータが無かったので毎回クレンジング用のクエリを追加していましたw

なぜmetabaseを選択したのか

BIツールはかなり多くのサービスがあり、OSSのものからサブスクリプションモデルで高価なものまで幅広いのが現状となっています。 OSSのBIツールもいくつかありますがその中でもmetabaseを選択した理由は主に以下の3点となります。

親しみやすさ

f:id:ryotakodaira:20190117114813p:plain

上の画像を見ていただければわかると思いますが、metabaseのユーザーインターフェイスはかなりシンプルでわかりやすく作られています。

データが可視化できれば見た目はどうでもいいだろと思うかもしれませんが、「親しみやすさ」はかなり重要だったと考えています。

エンジニアのみが使うものなら見た目を度外視していてもある程度は我慢できる?かと思いますが、触る時間が長いのは常に数値確認を行う事業部のビジネス職のメンバーだからです。

実際、数値が可視化されていてもそれが見にくい場合は時間が経つに連れて見られにくくなってしまうため、選定時に「親しみやすさ」(見た目)は重要視して比較していました。

扱いやすさ

metabaseにはダッシュボードという複数のグラフを1画面に並べて表示すること出来ますが、画像のようにセルの範囲内でグラフの大きさを自由に変更することが出来ます。

ダッシュボードの中でも大きく表示したい重要なグラフや参考程度の情報のグラフが混在することがありますが、グラフの大きさで分類する事ができます。

f:id:ryotakodaira:20190117114708p:plain

また、クエリビルダーのような機能が搭載されており、SQLを書かずにグラフを表示することが出来ます。

複雑な条件には対応出来ないものの1テーブルの中のデータを可視化するくらいであれば一瞬で作ることが出来ます。

これならエンジニア以外のメンバーでもグラフを作成することができるためSQLに触れるきっかけを作ることもでき、多方向で効果を発揮します。

f:id:ryotakodaira:20190117114745p:plain

導入コスト

metabaseはOSSのプロジェクトとして開発されているため無料で使うことが出来ます。

当初からサクッと環境を作って運用まで持っていきたいという思いがあったため、OSSのプロジェクトであるmetabaseは適していました。

また、dockerイメージが用意されているためサーバー上にmetabaseを構築するのは普段インフラを触っていないエンジニアでも簡単にできるようになっています。 なので短期間で導入までたどり着くことができました。

最近では大体のOSSのBIツールはdockerイメージが用意されている印象です。

導入後に気をつけたこと

BIツールを導入したのはいいけど、使われるのは最初の1ヶ月で直ぐに忘れ去られてしまうという事態は以外と多いのではないかと思います。

しかし、BIツールは事業にとって非常に重要なものであり、無いと意思決定を間違いかねません。

僕も以前にそのような失敗をしていますが、今回は同じ失敗を繰り返さずメンバーに親しまれるツールにするために実行したことを上げます。

事業目標数値を常に可視化する

metabaseを使って数値やグラフを見るタイミングは大きく2つあると考えています。

  1. 標数値/中間指標の確認
  2. 実績の原因分析

特に 1 目標数値/中間指標の確認 に関してはほぼ毎日行います。そのため、metabaseで可視化することによって強制的にmetabaseに触れる機会を増やしています。

追加して、metabaseのグラフを全員が見ることができるため "AさんとBさんの集計結果が違った" などの事態の発生を防ぐこともできるようになっています。

クレンジング済みのデータを用意する

データクレンジングとは、データベースに保存されているデータの中から、重複や誤記、表記の揺れなどを探し出し、削除や修正、正規化などを行い、データの品質を高めること。 *2

グラフの作成依頼は直ぐにそのグラフを確認したい場合に飛んで来ることがほとんどです。素早くそれらの依頼に対応するために、よく使われるデータについては常にクレンジング済みのものを用意するようにしました。

依頼者からすると見たいときにデータを見れないことが一番のストレスとなるため、そうならないようクレンジング後のデータの整備にはかなりの力を割きました。

クレンジングすることによってデータが扱いやすくなっているため、素早く求められているデータの出力が可能になったり元々のデータ構造を完全に理解していなくてもクエリを書くことができるようになりました。

弊社では、Laravelのデイリーバッチ処理でクレンジング後のデータをMySQLに格納する形で運用されています。

データ量を考慮しても今のところは前述の運用で間に合っていますが、今後さらなるスケールをした場合は違った運用方法に乗り換えるべきなのではないかという所感です。

まとめ

このようにして、BIツール(metabase)で数値を確認し、数値振り返りを行うことができるところまで作りあげることが出来ました。 今まで数字を見ていなかったメンバーや開発チームも数値で振り返ることができるようになったことが今回の一番の収穫だろうと感じています。

これからBIツールを導入するという方はぜひ試してもらえたらと思います!

今回はデータソースとしてMySQLのみに対応させていましたが、社内ではGoogleAnalyticsなども使っているため今後は他のデータソースの可視化も出来たらと考えています。

さいごに

事業・サービスが成長していくにあたって、これからもメンバーを増やしていきたいと思っています。

興味のある方は下記からご応募いただくか、@ryotakodairaにご連絡ください!!

www.wantedly.com

www.wantedly.com

www.wantedly.com

Laradockを使ったLaravel開発環境構築のやさしい解説

こんにちは!

今月からSCOUTER開発部でフロントエンドエンジニアをしている匠平@show60です。

弊社開発部では主にVue.jsとLaravelを使って開発をしています。

私も日頃はVue.jsを使ったフロントエンドの開発を担当させてもらっていますが、あわせてLaravelの学習も始めました。

さっそく開発環境の構築を始めたのですが、知らないことだらけのなかなか大変な作業で、新年から涙しそうでした。

これからLaravelを始めようという方が悲しみに暮れないよう、Laravel開発環境構築をやさしくやさしく解説したいと思います。

サマリ

  • マイグレートしてデータベースを作るまでを解説
  • 起きるかもしれないエラーにも言及

開発環境について

  • MacBook Air
  • Git
  • Laradock

Laradockとは

公式ウェブサイトには、Laravel(PHP)のプロジェクトをDocker上で動作させるためのワンダフルな環境とあります。

A full PHP development environment for Docker. Includes pre-packaged Docker Images, all pre-configured to provide a wonderful PHP development environment. Laradock is well known in the Laravel community, as the project started with single focus on running Laravel projects on Docker. [Laradock official] (https://laradock.io)

Laravelプロジェクトの開発環境はいくつもありますが、今回はこのワンダフルなLaradockを使って環境構築を行っていきます。

Laradockを使う上で、最低限知っておきたい基礎知識

きちんとした解説はもっと詳しい方へ譲るとして、開発環境を整える上で最低限理解しておきたいこと、また理解しておけばよかったと思ったことです。

なぜ仮想環境が必要なのか

Laravelの実行にはいくつものプログラムやライブラリが必要になります。例えばPHP, nginx, MySQLやミドルウェアなどです。

ある特定の環境でしか動かないプログラムなどに対応するために、1つのPC上に複数の環境を用意する必要がありますが、1つのOS上には1つの環境しか用意できないため、仮想のOS環境を用意する必要があります。

Docker、コンテナとは

Dockerはコンテナ型と呼ばれる仮想環境の1つです。仮想環境は他にホスト型、ハイパーバイザー型があります。

Dockerはコンテナという区画を複数提供することで、ある区画ではPHPアプリケーションを、別の区画ではMySQLを実行するというように隔離された実行環境を実現します。 隔離されている各区画(コンテナ)同士は、TCPという通信プロトコルで接続されます。

Laradockの導入

Laradockのダウンロード

ホームディレクトリにLaradockをダウンロードするために、ディレクトリを作成します。名前は任意で、ここでは「laravel_study」という名前にしました。

ダウンロードが完了したら、laradockに移動して.envファイルを作成します。

// laravel_studyディレクトリに移動
$ cd laravel_study

// GitからLaradockをダウンロード
laravel_study $ git clone https://github.com/Laradock/laradock.git

// ダウンロードができたらlaradockに移動
$ cd laradock

// env-exampleをコピペして.envファイルを作成
laradock $ cp env-example .env

このあとにMySQLをダウンロードしますが、先にバージョンを指定してあげます。

指定しない場合、latest(最新)バージョンをダウンロードします。投稿時点で最新バージョンである8系からはユーザー認証方式が違っており、私はここでかなり詰まってしまいました。

よんどころない理由がなければ、5.7を指定しましょう。

作成した.envファイルをテイストエディタで開いて、MYSQL_VERSIONを修正し保存します。

// "MYSQL_VERSION=latest" を以下のように変更
MYSQL_VERSION=5.7

コンテナの初期化

下記のように、nginx, MySQL, workspaceやphpMyAdminなど開発に必要なプログラムを指定してダウンロードします。これらが各コンテナで実行されることになります。 初めてダウンロードする際には20分ほど時間がかかります。

$ docker-compose up -d nginx mysql workspace phpmyadmin

各プログラム名の横にある進捗が"... done"となれば完了です。

http://localhostにアクセスすると404 Not Foundと表示されます。一見失敗に見えますが、ページにはnginxと表示されており、すでにコンテナ上でプログラムが実行されていることが分かります。

「docker-compose up ...」というコマンドは、これらのプログラムを実行する際に入力します。初回のみダウンロードに時間を要しますが、次回以降はほとんど待たずに実行します。

Laravelアプリケーションファイルの準備

Laravelプロジェクトは先ほどダウンロードしたworkspaceコンテナで実行されます。

workspaceにログインし、ここでは「sample」という名前でLaravelアプリケーションファイルを作成しました。その際にLaravelのバージョン5.5系を指定しています。なお、Laravelアプリケーションファイル名を「Laravel」にしてしまうとエラーが出るようなのでご注意ください。 Laravelのダウンロードには5〜10分ほど時間がかかります。

// wordspaceコンテナにログイン
$ docker-compose exec --user=laradock workspace bash

// ログインすると"/var/www$" というディレクトリに入ります
// workspace上でLaravelアプリケーションファイルを作成します
$ composer create-project laravel/laravel sample --prefer-dist "5.5.*"

// "Application key [base64:****] set successfully." となれば完了
// workspaceコンテナからログアウト
$ exit

workspaceコンテナで新たに「sample」という名前のアプリケーションファイルを作成できました。ここにLaradockがアクセスできるよう、Laradockディレクトリの.envファイルを修正します。先ほど.env-exampleをコピペして作ったものです。 Pathsの項目にあるAPP_CODE_PATH_HOST=../sampleと追記します。

### Paths #################################################

# Point to the path of your applications code on your host
APP_CODE_PATH_HOST=../sample

dockerを再起動します。.envファイルを修正した際には再起動が必要です。

# dockerを停止
$ docker-compose stop

# dokcerを再起動
# ここではnginx と MySQLだけ指定しています
$ docker-compose up -d nginx mysql

Laravelのアプリケーションファイルとそのパスを設定できたのでLaravelのページを開いてみましょう。 再度ブラウザでhttp://localhostにアクセスすると、無事Laravelのアプリケーションが起動していることが確認できます。

マイグレーションしてテーブルを作成する

MySQLにユーザー情報を登録できるテーブルを作成します。マイグレーションという機能を使うことで、テーブルの新規作成や実行を簡単に行うことができます。 マイグレーションに必要なファイルはすでに用意されているのでそちらを使います。

sample/
  └ database/
    ├ factories/
    ├ migrations/
    │  ├ 2014_10_12_000000_create_users_table.php
    │  └ 2014_10_12_000000_create_password_resets.php
    └ seeds/

マイグレーションファイルはテーブルの設計図のようなものです。今回は編集は加えずに、そのままマイグレーションを実行します。

// sampleディレクトリに移動
$ cd
$ cd laravel_study/sample

// マイグレーションの実行
$ php artisan migrate
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table
Migrating: 2014_10_12_000000_create_password_resets_table
Migrated: 2014_10_12_000000_create_password_resets_table

// マイグレーション完了

これでMySQLに新しいテーブルが作成されました。

マイグレーションの際に出たエラー

マイグレーションの段階で何度もエラーに遭遇し、その度に悲しい思いをしてきました。

一部ですが、実際に出たエラーとその対処法を紹介します。

SQLSTATE[HY000] [1045] Access denied for user 'ユーザー名'@'ホスト名' (using password: [YES/NO])

MySQLにログインできないよ、というエラーです。

using passwordYESの場合はユーザー設定の間違い、NOの場合はログインのパスワードの間違いが原因です。

私の場合はYESとなっており、ユーザー名が違っていました。Laradocksampleのそれぞれのディレクトリの.envファイル内のDB_USERNAMEのパラメータを合わせる必要があります。sampleディレクトリ側の.envファイルを編集、保存し、dockerを再起動することで解消しました。

"SQLSTATE[HY000] [2002] Connection refused

MySQLが起動していないよ、というエラー。

docker-compose up -d nginx mysqlで立ち上げましょう。

ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)

  • MySQLに接続する通信プロトコルは、TCP通信とソケット通信の2種類ある。
  • localhostがsocket使いたがるので、TCPに変更(127.0.0.1)する必要あり

MySQLから作成したテーブルを確認

先ほど作成したテーブルを確認できたら完了です。

// MySQLに接続
// DB_DATABASEは.envファイル内の名前と同じ
$ mysql --host=localhost --user=[MySQLのユーザー名] --password=secret [DB_DATABASE]

// テーブルを表示
$mysql> show tables from [DB_DATABASE]

最後に

私と同じように初めてLaravelに触れる方に、少しでもこの記事がお役に立てれば幸いです。

また、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

理想とする技術組織

こんにちは、kotamatです。 開発メンバーも15人を超えてきて、開発ラインも複数走っている状況となってきました。 それぞれの開発ラインにおいて、上長となる開発責任者やマネージャーが要件やスケジュールを決めていくため、それぞれの開発ラインが独立駆動で走っていってしまうということが発生してきました。 そういった中で、SCOUTERという会社に所属している開発メンバーとして立ち戻る物が必要となってきました。

SCOUTERでは、会社全体のミッション・バリューがありますが、開発者としてのまとまりを作るためには、抽象度を落としてより具体的にする必要があるため、開発組織として目指す像を定義しました。

今回は各定義の紹介と、内在する意図を紹介します。

f:id:kotamat:20190105195228p:plain
理想の開発組織

開発組織として目指す像

パートナー意識を持った相互依存型自律組織

シンプルに言うと「頼り頼られる組織」「背中を任せられる仲間」

エンジニアやデザイナー、PO、PMと、開発に関わる人は一人ではプロダクトを作ることができません。 それぞれ専門性を持った人が集まり、それぞれの特性を活かしながら開発を行うのがプロダクト開発です。 誰が何が得意なのか、苦手なのかを相互に認識した上で、自主的に穴埋めできるような高度なやり取りがプロダクト開発には求められており、そういう体制を構築できればスケールできる強い技術組織になると思っています。 また、開発に用いられる専門性は日進月歩でもあるため現状に満足せず、得意な領域に関してもより高度なレベルを常に目指す組織を目指していきます。

開発組織のバリュー

バリューに関しては、SCOUTERのバリューに照らし合わせています。

なぜ会社のバリューに相対させるものを作成したのかというと、

  • 開発組織としてのバリューを当該対象者の評価に組み込まないと運用されない未来が待っている
  • 一人の非評価者に一人の評価者という構造で評価を行っている
  • 既存の評価では、バリューの達成度合いを評価している

という背景があり、現行の評価にスムーズに組み込むためには既存の評価に対応させる必要があったためです。

副次的な効果として、相対させることによって、営業やCSのメンバーにとっても、開発組織が行っている行動がどのように会社の規範に紐付いているのかというところが理解可能な形になるため、全社的に同じ方向に向かっているということが感じ取れるようになります。

バリューの設定方法は以上です。 これから具体的にそれぞれのバリューを紹介させてもらいます。

1. 個々のプレゼンスが組織のプレゼンスとなる

対応するバリュー

f:id:kotamat:20190105195306p:plain
ROCK

正解は探すものではなく、創り出すもの 立場や結果を恐れず、自らの正しさを証明するまで闘い続けよう

コアとなる考え

  • 自分の得意を社内外問わず発信し続けることで個のプレゼンスを上げていく
  • 苦手なところは自責意識を持ちながらも他の人に認識してもらい助けてもらう
  • 得意なところは互いに高め合う

解説

前述の通り、開発に関わるメンバーの専門性の相互作用によってプロダクトは作られていきます。 また、同じ職種でも人によって得意な領域や不得意な領域は別れており、プロダクトのフェーズや施策開発のフェーズによって求められるレベル感も異なっていきます。

そういった中でそれぞれのメンバーが自分が得意な領域を他者に認識させ、発揮することによってその人の存在意義を発揮でき、その領域を任せられるようになります。

また、現在の開発のエコシステムに置いて、社外への発信は非常に重要視されてきています。 各個人の得意を向上する過程を社外に発信することによって、その過程そのものが本人のブランディング戦略の一つの要素となり、唯一無二となる個々のプレゼンスが形成されます。

そうしていくと、様々なシチュエーションにSCOUTERのメンバーが露出するようになり、会社としてのプレゼンスが上がっていくと考えています。

得意な領域が個々にあるということは、不得意な領域もあるということになります。 不得意な領域も認識し合うことによって、その領域は誰か他の人のヘルプを得ることもでき、背中を預けられる関係となると考えています。

2. 自分史上最高のプルリクを出す

対応するバリュー

f:id:kotamat:20190105195337p:plain
JAZZ

絶えず時間が流れ、状況は変わり続けている 型に嵌らない、その瞬間の最高パフォーマンスを追い求めよう

コアとなる考え

  • プルリクというのは、自分の成果物を他人に評価、受け入れてもらうためのものとする。
  • プロダクトやその他環境に応じて求められる評価は変わってくるが、その場における自己最高のものを作ることによってパフォーマンスを最大化していく

解説

キーワードは「比較対象が自分」というところではありながら「その環境におけるベストをアウトプットする」という点です。

スクラム開発ではよくもちいられている「トレードオフスライダー」や「Doneの定義」にもありますが、事業のフェーズやステークホルダーの関係性など様々な要因によって、求められるアウトプットにおける重要視されるポイントが変わってきます

自分が置かれている環境下でベストはなにかを認識し、過去の自分の経験も融合して一番いいものをアウトプットとするということを行っていけば、不確実性の高い領域においても、次第に良いものになっていく環境となり、強いプロダクトが作られていくと考えています。

3. 好奇心を持って体系化する

対応するバリュー

f:id:kotamat:20190105195401p:plain
PROGRESSIVE

誰もやっていないことに、誰も気付いていない価値がある 誰も実現したことのないことへの挑戦を喜び讃えよう

コアとなる考え

  • 日進月歩のWeb業界において、常に新しい技術や手法を取り入れていくことが求められている。
  • 新しいことを取り入れるだけではなく、それを体系化し再現性のある形に昇華させるまでをチャレンジとし、唯一無二の価値を創造する。

解説

日進月歩のWeb開発業界において、専門性の向上は常に行う必要があります。 そういった中で、常に最新の情報や技術に対してアンテナを張り続ける事によって、今どういうのがモダンなのかというのを判断できるようにする必要があります。

ここで注意しなければならないのは、新しいものを取り入れるというのはとてもいいのですが、よくあるのがとりいれたままにすることです。 新規プロダクト、新規施策においては、新しい概念を入れるのはたやすいですが、それだけではそれほど価値はありません。それを体系化しノウハウ化して他のプロダクトや社外の人もつかえるようにしていくことによって他者にとって価値のあるチャレンジとなります。

また、体系化を行うプロセスは唯一無二となるため、それを社外に発信し、個のプレゼンスとしていくことで、チャレンジをすることの対価を何倍も得られるようになります。

まとめ

SCOUTERの開発組織の目指す像とバリューについて紹介させていただきました。

バリューを設定する際には、バリューに沿う行動を行うことが

  • 当該メンバーのスキルアップに繋がり
  • 強い組織を作る要素となり
  • 会社の価値観に沿う

ようにすることで、組織と個人がWinWinの関係になるようなものとなるかと思います。

今回設定したものはまだベータ版であり、今後運用していく中でブラッシュアップされていくかと思いますが、明文化することでクリアになったので、10-20名規模になってきたら導入を検討されるといいのかなと思います。

最後に

SCOUTERでは、Laravue勉強会やNuxtMeetup、本技術ブログなど社外に発信する場があり、上記バリューを発揮する環境は整っているかと思います。

こういったバリューに共感する方、開発者として圧倒的に成長していきたい方は、一度お話させていただければと思います。 下記リンクから応募していただいてもいいですし、kotamatにDMを送っていただいても構いません。

www.wantedly.com

www.wantedly.com

www.wantedly.com