のんラボ

Z250でフォレストパーク神野山へツーリング!

2021/02/28 2021/03/01

Z250でフォレストパーク神野山へツーリング! こんにちは。のんです。 今回はバイク記事になります。 2月にしては温かい週末があったので、それを利用して フォレストパーク神野山 までツーリングしてきました! https://yamazoekanko.jp/spots/forestpark-konoyama https://yamazoekanko.jp/spots/forestpark-konoyamaを見る 友人が良いカメラを買ったので、それ使ってみよう 友人が買ったのはこれらしい。 カメラのことは詳しくないので全くわからないが、たしかにスマホの画像に比べるとだいぶ違いますね。 ちなみに、容量の都合上、このブログには劣化した画像を使用してます🥲 あとカメラマンである友人は写っていない... ちょっと悲しい;; まったり画像 なんかお昼寝したくなっちゃいますね😴 正直この日暖かったので、一緒に寝たろかとは思いましたww イエーイ✌午後ティーが目立つ テレワークのせいで、ちょっとした運動でも息が上がる...これは休憩中の写真です。 三脚で集合写真! うーむ...いい写真だ! それにしてもいい天気ですね。改めて見ても晴天だ。 最後に 久しぶりのツーリングでした〜 この日は暖かくて4月くらいの気持ちで走ってました。 ニット帽持っていきましたが正直いらなかったかも。 GoPro買ってYouTubeに垂れ流す計画が進行中です。ツーリング仲間とも色々楽しく動画や写真を作りたいと思ってますので、次の春にはコロナ落ち着いてくれるといいなぁ。 またその時は写真を投稿しますね。 そのときはよしなに。 .

NuxtでVue製の自作ライブラリを公開せずにインストールする方法と、そのときのエラー解決

2021/02/19 2021/02/19

NuxtでVue製の自作ライブラリをインポートしたときのエラーについて こんにちは。Nonです。 今回は前回に書いた記事 自分的Storybook + Vue環境の作成方法(CLIなし) | のんラボ 自分的Storybook + Vue環境の作成方法こんにちは。Nonです。今回は ... で作成したVueコンポーネントライブラリをNuxtへインストールする方法と、ハマったポイント、 Cannot use import statement outside a module が発生してしまう現象について書こうと思います。 パッケージ公開せずにインストール 前回の記事に書いたような内容は一般公開できないようなプロダクト専用のライブラリだと思います。 なので、リンク先の 外部でパッケージ利用できるように設定する でも書いたように、色々なパターンで利用されることを想定せずにVueコンポーネントそのものをビルドに時に自動インストールするか、コンポーネントをexportしています。 copied.import BaseButton from './src/components/atoms/Button/BaseButton'; const Components = { BaseButton, }; // ここで、Vueがinstallされようとするときに、自動的にinportしようとしている。 export function install(Vue) { if (install.installed) return; install.installed = true; Object.keys(Components).forEach((component) => { Vue.component(component, Components[component]); }); } const plugin = { install, }; let GlobalVue = null; if (typeof window !== 'undefined') { GlobalVue = window.Vue; } else if (typeof global !== 'undefined') { GlobalVue = global.Vue; } if (GlobalVue) { GlobalVue.use(plugin); } // ここでコンポーネントのexportをしている。 export default Components; これはlaravel-mixでのVue利用や、素のVueでimportされるときを想定しています。 インストール方法 GitHubやGitLabで作成したリポジトリをそのままインストールする方法は Githubにあげた個人のnpmパッケージをインストールする方法 - Qiita 始めにpublicには公開しないけど、他の案件でも使いますためにローカルで作成したnpmパッケージをインストールする方法について紹介します。CLIで動作するパッケージの作成方法はこちらの記事を参考にし... や nodejsで自作別リポジトリ(gitlab/github)をモジュール化して利用するには? - kei0425tan’s blog プログラムを作っていて、この機能をあのプログラムでも利用したいなぁ。。。といったことはよくあるかと思います。そういうときには、コピペしてしまうと整合性が取れなくなってあとで困ることになります。 なので... などの色々なブログで公開されているので、特に言及はしません。 package.json にこのように入力したり、 npm install するときにリポジトリ情報を指定することでインストール出来ます。このとき、しっかりとトークンを設定しておくことを推奨します。 copied."dependencies": { ... "smaregi-developers-ui": "git+https://{user_name}:{token}@example.com/path", ... } copied.npm i my-project-name インストールできたら node_modules の中に自作パッケージがインストールされているかと思います。 自作パッケージのインポート方法 Vueやlaravel-mixを利用したときには大丈夫だったのですが、Nuxtを利用したときにハマったポイントはこちらになります。 laravel-mixの場合 laravel-mixを利用してVueをインストールしている場合、 {project_root}/resource/js/app.js があると思いますので、そちらに自作コンポーネントをimportします。cssなどをjsで読み込む場合も同じです。 {project_root}/resource/js/app.js copied.import 'my-project-name'; // もともとあるコード 簡単ですね。自動インストールされるはずなので、特にコンポーネントを指定する必要もないです。 Nuxtの場合 外部パッケージ読み込み用のプラグインファイルを作成します。 ここではプロダクト専用のUIコンポーネントパッケージでしたので ui.js と名付けました。 ファイルの内容はこちら。 copied.import Vue from 'vue'; // storybookプロジェクトでexportしたコンポーネントをimport import Components from 'smaregi-app-market-ui'; // コンポーネント登録 Object.keys(Components).forEach((component) => { Vue.component(component, Components[component]); }); storybookプロジェクトでexportしたコンポーネントをimportするので、それをNuxtのVueに登録する必要があります。 次に、 {project_root}/nuxt.config.js に plugins フィールドがあるので、そちらに作成したファイルを指定します。 copied.// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins plugins: [ '@/plugins/ui', ], これで設定できたので、nuxtを起動されると... copied.npm run dev エラー発生 copied.Cannot use import statement outside a module 画面やログにはこのようなエラーが発生します。 エラー内容はES6関連のコードエラーのようです。 Trying ES6 style import gives 'Cannot use import statement outside a module' I am trying to write a javascript test in intellij for which I need to import some dependancies and ... Babel周りの設定がうまいことできていないような気がする。と思い色々調査するとNuxtではbabel用のトランスパイル設定があるようです。 build プロパティ Nuxt では、webpack の設定をカスタマイズして web アプリケーションを思いのままに構築することができます。 Cannot use import statement outside a module は import 構文を古いブラウザなどで使用できるようにするために、ES6未満の対応をするのですが、babelがその役割を担ってくれます。Nuxtはその辺も考慮されたFWなので、これを利用します。 plugins 情報を記載した、 {project_root}/nuxt.config.js の build フィールドに下記を追加します。 copied. // Build Configuration: https://go.nuxtjs.dev/config-build build: { transpile: ['my-project-name'] } 設定後、再度コマンドを実行。 copied.npm run dev エラーが発生せずにNuxtが起動すればOKです。 最後に Nuxtがどのような原理で外部パッケージのコンパイルなどを行っているのかはあまり理解出来ていませんが、どうやらパッケージ毎にコンパイルやトランスパイルの方法まで色々指定できるようですね。 この辺は少しずつ勉強して記事にしたいと思っています。 その時はよしなに。 .

自分的Storybook + Vue環境の作成方法(CLIなし)

2021/02/10 2021/02/10

自分的Storybook + Vue環境の作成方法 こんにちは。Nonです。 今回は @storybook/addon-actionsを利用したときのTips | のんラボ @storybook/addon-actionsを利用したときのTipsこんにちは。Nonです。最近、フロントのこともガッツリと関わるようになってきました。デザインのセンスはあまりありませんが、デザイ... でTipsだけまとめているStorybookの環境構築や設定について自分なりに試行錯誤したものを書こうと思います。 弊社 株式会社スマレジ 企業サイト 株式会社スマレジは、インターネット社会においてデザインとテクノロジーを駆使して「いい未来をつくる」ことを理念としたテクノロジー企業です。レジ、自販機、券売機、eコマースなど、世界中に広がる販売データの... では、デザイナーとフロントの協業というのがまだまだ弱く、フロント開発におけるテンプレートといいますか、明確なフローというのが確立していません。私はバックエンドの人間なので、デザイナーとどのように仕事をしていくかなどもわからなかったので、色々調べていった結果Storybookというものを発見しました。 なのでここでの目標は デザイナーとコンポーネント単位のデザインレビューや、コードレビューを行うこと Nuxtのようなフレームワークに依存せず、コンポーネント単位での開発ができること 同様にNuxtのようなフレームワークに依存せず、コンポーネントをinstall or importできること としています。 Storybook導入 storybookを使用するので、当然インストールしなければなりません。他のサイトやブログなどではNuxt + storybook のような構成を取っていますが、Nuxtに依存したくないと思いましたので、このようなフレームワークの導入は検討していません。 しかし、Vue.jsには依存しています。 また、storybook-cliも利用していません。使った方が簡単ですが、最小構成にしたい and cliのinstallしたくない などの理由があります。そのうちCLI版の記事も書くかも。 storybook インストール copied.npm i @storybook/vue --save-dev コマンドでstorybookを起動できるようにpackage.jsonに下記を追加。 この時点ではstorybookの設定をしていないので起動できません。 copied."scripts": { "storybook": "start-storybook", "build-storybook": "build-storybook", }, Vue インストール copied.npm i vue --save-dev Vue loader インストール copied.npm i vue-loader vue-template-compiler --save-dev eslint インストール copied.npm i eslint eslint-loader eslint-plugin-vue --save-dev linterの設定はプロダクトに合わせて設定してください。 コマンドでlinterを起動できるように、package.jsonに下記を追加 copied."scripts": { "eslint": "eslint --ext .js,.vue ./", "eslint-fix": "eslint --fix --ext .js,.vue ./" }, Storybook設定 {project_root}/.storybook/main.js に下記を追加 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], }; Storybook用のjsの場所はプロジェクト毎に決めていいと思います。 コンポーネント追加 {project_root}/src/components/atoms/Button/BaseButton.vue を配置。 copied.<template> <button>button</button> </template> <script> export default { name: 'BaseButton' }; </script> <style scoped> </style> {project_root}/src/components/atoms/Button/Button.stories.js を配置。 copied.import {storiesOf} from '@storybook/vue'; import BaseButton from './BaseButton'; storiesOf('Button', module) .add('buttons', () => ({ components: {BaseButton}, template: '<div>' + '<base-button></base-button>' + '</div>', })); 外部でパッケージ利用できるように設定する npm i でパッケージインストールしたときに使用できるようにコンポーネントを登録するスクリプトを記載。 これに関しては色々なリリース方法があるので、用途に応じて変更して下さい。(CDNとかplane jsとか) 詳細はこちら。 Vue コンポーネントを npm パッケージ化する — Vue.js Vue.js - The Progressive JavaScript Framework copied.import BaseButton from './src/components/atoms/Button/BaseButton'; const Components = { BaseButton, }; export function install(Vue) { if (install.installed) return; install.installed = true; Object.keys(Components).forEach((component) => { Vue.component(component, Components[component]); }); } const plugin = { install, }; let GlobalVue = null; if (typeof window !== 'undefined') { GlobalVue = window.Vue; } else if (typeof global !== 'undefined') { GlobalVue = global.Vue; } if (GlobalVue) { GlobalVue.use(plugin); } export default Components; css / scss / sass 関連 2021/02現在、最新のwebpackでsass-loaderを動かすとバージョンの整合性チェックでエラーが発生するので、10系 の最新バージョンを指定してインストールする。 copied.npm i style-loader css-loader sass-loader@10.1.1 --save-dev {project_root}/.storybook/main.js に下記を追加 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', '@storybook/addon-viewport', 'storybook-readme', ], webpackFinal: (config) => { config.module.rules.push({ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }); return config; } }; copied.npm run storybook ※↓エラーが発生する場合あり。 copied.Node Sass does not yet support your current environment: OS X 64-bit with Unsupported runtime (88) For more information on which environments are supported please see: https://github.com/sass/node-sass/releases/tag/v4.13.1 storybookを起動しようとしてエラーが出た場合、node-sassをインストールしてビルドしておくこと。 copied.npm i node-sass --save-dev npm rebuild node-sass npm run storybook 試しにscssファイルなどを作成してimportしてみる。 VueのScoped CSSで試しても大丈夫です。 {project_root}/src/sass/atoms/_button.scss copied..btn { color: red; } {project_root}/src/sass/app.scss copied.// My atom's scss. @import 'atoms/button'; {project_root}/.storybook/preview.js でscssファイルをimportする copied.import {addDecorator, addParameters} from "@storybook/vue"; import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport"; import {addReadme} from 'storybook-readme/vue'; import '../src/sass/app.scss'; addParameters({ viewport: { viewports: INITIAL_VIEWPORTS } }); addDecorator(addReadme); アドオンを追加 storybookを使うにあたって便利なアドオンを追加します。これも各プロダクトによって導入するかどうかを決めて大丈夫です。 @storybook/addon-actions copied.npm i --save-dev @storybook/addon-actions {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', ] }; {project_root}/src/components/atoms/Button/BaseButton.vue にクリックイベント設置。 copied.<template> <button @click="$emit('click')"> button </button> </template> {project_root}/src/components/atoms/Button/Button.stories.js を書き換え。 copied.import {storiesOf} from '@storybook/vue'; import BaseButton from './BaseButton'; import {action} from '@storybook/addon-actions'; storiesOf('Button', module) .add('buttons', () => ({ components: {BaseButton}, template: '<div>' + '<base-button @click="action"></base-button>' + '</div>', methods: { action: action('clicked') } })); @storybook/addon-knobs copied.npm i @storybook/addon-knobs --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', ] }; {project_root}/src/components/atoms/Button/Button.stories.js を書き換え。 copied.import {storiesOf} from '@storybook/vue'; import BaseButton from './BaseButton'; import {action} from '@storybook/addon-actions'; import {text} from '@storybook/addon-knobs'; storiesOf('Button', module) .add('buttons', () => ({ components: {BaseButton}, props: { buttonLabel: { default: text('label', 'button') }, }, template: '<div>' + '<base-button @click="action">{{ buttonLabel }}</base-button>' + '</div>', methods: { action: action('clicked') } })); {project_root}/src/components/atoms/Button/BaseButton.vue を書き換え。 copied.<template> <button>button</button> </template> <script> export default { name: 'BaseButton' }; </script> <style scoped> </style> @storybook/addon-storysource copied.npm i @storybook/addon-storysource --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', ] }; @storybook/addon-viewport copied.npm i @storybook/addon-viewport --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', '@storybook/addon-viewport', ] }; {project_root}/.storybook/preview.js にviewport(iPhoneなどの画面情報)情報を追加。デフォルトでも使用可能。 copied.import {addParameters} from "@storybook/vue"; import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport"; addParameters({ viewport: { viewports: INITIAL_VIEWPORTS } }); storybook-readme copied.npm i storybook-readme --save-dev {project_root}/.storybook/main.js にアドオン登録 copied.module.exports = { stories: ['../src/**/*.stories.[tj]s'], addons: [ '@storybook/addon-actions', '@storybook/addon-knobs', '@storybook/addon-storysource', '@storybook/addon-viewport', 'storybook-readme', ] }; {project_root}/.storybook/preview.js に設定を追加。 copied.import {addDecorator, addParameters} from "@storybook/vue"; import {INITIAL_VIEWPORTS} from "@storybook/addon-viewport"; import {addReadme} from 'storybook-readme/vue'; addParameters({ viewport: { viewports: INITIAL_VIEWPORTS } }); addDecorator(addReadme); {project_root}/src/components/atoms/Button/Button.stories.js を書き換え。 copied.import {storiesOf} from '@storybook/vue'; import ButtonReadme from './README.md'; import BaseButton from './BaseButton'; import {action} from '@storybook/addon-actions'; import {text} from '@storybook/addon-knobs'; storiesOf('Button', module) .addParameters({ readme: { sidebar: ButtonReadme } }) .add('buttons', () => ({ components: {BaseButton}, props: { buttonLabel: { default: text('label', 'button') }, }, template: '<div>' + '<base-button @click="action">{{ buttonLabel }}</base-button>' + '</div>', methods: { action: action('clicked') } })); {project_root}/src/components/atoms/Button/README.md を追加。 copied.# Usage🛠 その他設定はご自由に @babel/core vue-svg-loader @fortawesome/fontawesome-free bootstrap のようなプロジェクトの要件などに関わるパッケージなどはご自由にインストールして下さい。 最後に ほぼ、私のメモの用に書いてみました。 「こうしたほうがいいよ」とか、「ここ間違ってるくない?」とかあるかもしれませんが、その辺は自身の環境に合わせて設定を変えていただければと思います。 特にコンポーネントのパッケージ化についてはインストールする側がVueを利用していることを前提にしているので、使いにくいところがあるかもしれません。 CDN化やpure jsの対応はそのうちしておきたいなとは思っています。 個人的にはフレームワーク依存したくない(Vue / ReactのようなFWを除く)ので、それ単体で動く環境をCLIなどなしで構築できるようになっているはずです。 みなさんの役に立てれば幸いです。 React記事書こうとしていましたが、Storybookのまとめしてなかったなぁと思ったのでこちらを優先しました。Nuxtの案件も近いのでついでに...といった感じ。 Nuxtについてもそのうち書きたい。 そのときはよしなに。 .

ようやく今年の抱負メーカーをバージョンアップしたよ

2021/01/24 2021/01/24

ようやく今年の抱負メーカーをバージョンアップしたよ こんにちは。Nonです。 皆さんお待たせしました。 ようやく 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! をバージョンアップしました。 結構要望を受け取っていて、これを解決するのに、結構な時間が経過しちゃっていました... みんなごめんね。 今回はそのお知らせという感じで、更新内容をサクッと説明しつつ、バージョンアップのお知らせ記事にしていこうと思います。 前に書いた記事 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた | のんラボ 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた明けましておめでとうございます。本年もよろしくお願いいたします。早速ですが、新年早々アプリを作成しました。 ... 3行で入力できるようにしてほしい 今までのバージョンでは1行25文字までにして、プログラム側が定形処理に従って自動的に改行していましたが、それを3行までユーザーが指定できるようにしました。 こんな感じで入力できるようになりました これが結果になります。 改行位置が指定というか、固定されるようになったので、見た目の調整がしやすくなったと思います。 本当は、プレビューを見せてあげたいので、そのへんは後のバージョンでなんとか実装したいと思います。 色んな使い方① 箇条書きっぽく こんな感じで箇条書きにみたいに見せることもできます。 抱負がたくさんある方はこのような使い方をしてみてくださいね。 色んな使い方② これまで通り文章でも入力できるよ 文節毎に区切って入力するとわかりやすくていいかも。 最大21文字なので、そのへんも考慮して抱負の内容を入力すると、見栄えが良くなるかも! この要望がとても多かった... 正直、勉強用に作成していたので、ユーザービリティとか考慮してませんでした... 申し訳ない🙇 こんな感じでどうだろうか? 違う年の抱負を見たい 今までは総合PVでしたが、これからはその年毎のPVランキングにしました。 個人の抱負を見たい場合はTwitterからリンクたどって来てください。 見られたくない抱負の場合は、画像だけ作成して、ツイートしないようにしたら大丈夫です。 セレクトボックスを変更すれば、以前の抱負が見れます。 遅くなってすみません。 本当は、2020年内に対応しておきたかった まぁこのアプリって1月初旬にしか需要ありませんからねww それまでに間に合わなくて申し訳ない。 12月に予想外の仕事が入ってしまってこちらに取り組む余裕がありませんでした😅 本当はほとんどの機能をリプレースしたかった 画像のサイズがでかすぎて表示に時間がかかっているから、画像の軽量化処理を入れたかった。 プレビューを表示したかった。 DBの登録処理が複数回行われるので、画像を保存するかどうかを選択させてあげたかった。 他にも色々あるので、今年また時間があるときに頑張ろうと思います。 最後に 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! 今年の抱負メーカー | Twitterで今年の抱負をつぶやこう!筆書き風の画像をダウンロードして背景に設定したりしてください! の新バージョンの通知でした。 前に書いた記事 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた | のんラボ 【個人開発】新年早々、今年の抱負をつぶやくWEBアプリをつくってみた明けましておめでとうございます。本年もよろしくお願いいたします。早速ですが、新年早々アプリを作成しました。 今年の抱負メーカーア... 約2年前のコードでしたが、いやぁやばかったww これは設計レベルで少し見直す必要があるかもしれません。 少しずつ更新していくので、次のバージョンアップ時も通知記事を入れたいと思います。 その時はよしなに。 .

(今さら)2021年の抱負とこれまでの振り返り

2021/01/09 2021/01/09

(今さら)2021年の抱負とこれまでの振り返り 明けましておめでとうございます。 本年も何卒よろしくお願い申し上げます。 はい、こんにちは。Nonです。 昨年の振り返りと本年の目標 | のんラボ 昨年の振り返りと本年の目標こんにちは。Nonです。少し遅れてしまいましたが、去年の振り返りと、本年の目標を書いていこうと思います。2019年はどんな年だったか?職場実は2018年に転職をしておりまして... を書いてから早1年が経過しました。(去年の記事のタイトルが悪い) 今回はこの記事を見ながらできていること、できていないこと、をまとめながら今年はどのようにエンジニアとして活動していこうかを書いていこうと思います。 そう言えば家庭教師はじめました 無事、私の生徒さんが自社開発会社に内定をもらって現在勤めています。 現場レベルの人間が毎週2時間くらいみっちり教えてますので、かなりいい授業のはずです。 しかも、サイトの課題ポチポチとか、基本の構文の写経とかじゃなくて、作りたいアプリの仕様を二人で考えて、OJT方式にプルリクを出して私と生徒でレビューしていく方式なので、かなり現場に近いかも。 私もプルリク出すので、私のコードで勉強できるというメリット付き。 いくらでしょう...?内緒です。 (でも、かなり厳しい授業です。ちょっと悪いことをしてしまったかも。反省) 個人開発 一昨年(2019)は個人開発を中心に頑張っていた年でしたね。去年の記事にあるように色々なクソアプリを作成してはクローズしてきました。Trelloのパクリなので、公表はしていませんがtodogってアプリは現役で私が使っていまして、案外使いやすいので結局丸一年使ってしました。 https://todog.nozomi.bike/ https://todog.nozomi.bike/を見る リンク貼ったはいいけど登録はできないようになってます(パクリの上に個人で使っているツールなので)。フロントとバックが完全に別々になっているので、フロント重視にできたはず。 今年の抱負メーカー | ツイッターで今年の抱負をつぶやこう! 今年の抱負メーカー | ツイッターで今年の抱負をつぶやこう! は2021年の新年にみんなにもう一度使ってもらうとバージョンアップを画策していたのですが、12月は諸事情(横からのヘルプに追われていた)があったので達成ならず...口惜しい... ということもあり、2020年は個人開発あまりできませんでした。 といったところで話したいことも終わったので振り返り なにか1つのアプリをじっくりと開発する できませんでした... 2020年は個人開発に割くリソースがなく、できたものといえば、 スマレジアプリコンテスト 第2回 スマレジアプリコンテスト開催中!大賞賞金1,000万円、優秀賞100万円!スマレジと連携して利用する店舗向けアプリを開発して、ご応募ください。お店のいい未来をつくるアプリ募集。 用のアプリが一つだけ、しかも機能不足で公開できるレベルでは無いものです。 のんラボ のんラボ の調整や https://todog.nozomi.bike/ https://todog.nozomi.bike/を見る の調整などは何回か行ってきていましたし、社内向けのサンプルコードやOSSは作成していたのですが、これは個人開発とは言えないかな〜と自分の中で決着しています。 でも https://github.com/nonz250/simple-rss-maker-php は作った RSS生成アプリをPHPで作りました(なんで令和にこんなものをww) 詳細記事は TDDでRSSを生成するライブラリを作成した話 | のんラボ TDDでRSSを生成するライブラリを作成した話こんにちは。Nonです。前回に引き続き、TDDでRSSを作成するライブラリについて話をしてみたいと思います。完成したもの ... に書いています。 問題なく使用できていますが、OSS用のライブラリにたいしてDDDやりすぎた感とかその辺勉強できたのは大きかったな。 自作アプリを紹介するHP作成 こちらは今年の目標に棚上げかも。 アプリ充実してないから仕方ないね。 その他 技術などの勉強はかなりできた 個人開発ができなかったのにはわけがあって、何故か開発作業をせずにひたすら勉強していた年になりました。 TDDとかもともと知っていたのですが、昨年の初めにDDDの読書会から始まった社内勉強会をしていたのもあり、勉強にかなりの時間を割いていました。 あと、幸か不幸かコロナということもあって勉強会がオンラインになっていたというのもあります。 ちょっとずつOSSに貢献できるようになっていきたい。(英語力...) 貯金 まぁバイク一台を余裕で買える程度には頑張った...予想外の収入もあったので。 しかしバイク買うかは迷い中 おしゃれに気を使う がんばれている...よね? コロナが全ての努力を無に帰したかも。 2021年の抱負 自作アプリを紹介するHP作成(去年に引き続き) OSS貢献(私の中で必要なものは揃ったはず。もちろんOSS開発も。) 今年の抱負メーカーなど、ユーザーが居る or 可能性のある アプリの強化 ですかね。 ブログ更新 は言わずもがなですねw ここに書いたこと以外は我慢しない こちらは去年に引き続きです!気楽に行きましょう! 最後に こんなものでしょうか? なんか去年と似たりよったりの部分もありますが、停滞してしまった今年の状況を見ると結構うまく行っているような... ブログの更新頻度下がっているので、こちらも頑張りましょう。(やめちゃわない程度に!) そのときはよしなに。 .

Z250で高山ダムへラーツー!

2020/12/06 2020/12/06

Z250で高山ダムへラーツー! こんにちは。のんです。 今回はまたまたバイクでツーリングへ行ったことを書こうと思います。 最高に良い一枚 時期は12月初旬頃です。 高山ダムへラーツー。ラーツーってなに? ラーツーって言うのは「ラーメンツーリング」のことですね。 ラーメンの材料と、材料を調理するコンロとかを持ってツーリングして、昼食などにラーメンを食べるツーリングのことです。 調理するって言っても大抵インスタントラーメンとかになるんですが、外で食べるラーメンはうまいから別にヨシ! 高山ダムへ到着するも火気厳禁らしい 高山ダムへ到着したけど火気厳禁だった...かなしい。 ちなみにちょうど放水時間でした。 この量確かに決壊したら下流の街は大変なことになりそう😅 ダムの近くの自然公園はすべて火気厳禁だったので、この川の下流でデイキャンすることになりました。 ちょうどいい感じのところを発見。地名は知らん。 ということで早速ラーメンを作って食べよう これが こうなる ただのインスタントラーメンなので、水を沸かしてスープとチャーシュー入れておしまい。 やっぱ外で食うご飯は美味しい。この日は意外と寒くなくて、ラーメン食べたらむしろ暑いくらいでした。 飯テロ この写真最高に良くないか??🤔 あと、椅子持って来たらよかったかも。 食後のコーヒーとプリン 川で遊びだす自分 ススキがいい味出してる。 最後に コロナ第3波+寒くなってきて走り納め感ある。 でも寒い中温かい思いをするのは何故かやめられないんだよなぁ... 呪術廻戦おもろいぞ ちょっとポーズ違くないか?🤔 最後に...バイクはいいぞ!! .

Z250で職場の仲間と美山かやぶきの里「雨」ツーリング!

2020/11/29 2020/12/03

職場の同僚と美山かやぶきの里「雨」ツーリング! こんにちは。Nonです。 今回は久しぶりにバイクでツーリングに行ったことを記事にしたいと思います。 何度目かの美山かやぶきの里へ行きました もう何回行ったかわからない美山かやぶきの里へツーリングしました。 ちょうど2時間くらいで行けるので、かる~くツーリングするには道も所要時間もお手頃な目的地なんですよね。 天気予報「曇りのち晴れ」だったよな...? この日は午前中天気が良くていいツーリングになると思っていました。 同僚が最近 レブル250 を購入して、せっかくなのでツーリング行こう!とも思い誘ってみたところ...って感じです。 レブルかっちょいいいいいいいいいい😆😆😆 ストファイ系が好きな私でも、これは欲しくなりました。座り心地も良さそう。250なので取り回しも楽だろうし乗ってて楽しいバイクかも。 で、肝心の天気ですが 案の定降られた。 なんで?かなり楽しみしていたのに、割と思いっきり振られました。 山の天気は変わりやすいとか言うけど変わりやすすぎやろ... バイクびっしょびしょ。 相方には悪いことをしました。 寒いし濡れるし、新車だし、で😇 でもなんだかんだ楽しかった かやぶきの里を観光はできなかった(滞在時間は昼飯食べただけ)けど、温かいそば美味しかったし、帰りは比較的天気安定してたしでまぁまぁ楽しかった。(もちろん晴天だったら最高だった) また行きたいですね。 最後に バイクはいいぞ! .

TDDでRSSを生成するライブラリを作成した話

2020/11/27 2020/11/27

TDDでRSSを生成するライブラリを作成した話 こんにちは。Nonです。 前回に引き続き、TDDでRSSを作成するライブラリについて話をしてみたいと思います。 完成したもの GitHub - nonz250/simple-rss-maker-php Contribute to nonz250/simple-rss-maker-php development by creating an account on GitHub. https://packagist.org/packages/nonz250/simple-rss-maker-php https://packagist.org/packages/nonz250/simple-rss-maker-phpを見る 構造 RSS1.0に対応する余地を残しているので、SimpleRssMakerクラスの構造が少しおかしいかも。 ユースケース層も作ったけど、これいらなかったかなー。でもファイルを作成する処理をここに書きたかったので、まぁ良しとしましょう。 RSS1.0に対応していないですが、一応composerからはinstallできるようにしてる バージョンとしては1.0.0として作成しました。 https://packagist.org/packages/nonz250/simple-rss-maker-php https://packagist.org/packages/nonz250/simple-rss-maker-phpを見る しかし、RSS1.0にはまだ対応してないです。(RSS2.0の利用のほうが多いみたいだし別にいいよね😅) 構造的に悩んだところ ChannelをEntityにしたけどよく考えたらValueObejctなのかな?もっとシンプルな構造にすべきだったかも。DDDに固執しすぎた感ある。うーん。配列は作成したくなかったので、クラスにするのはあってるけどEntityとして定義するのは間違えた気がする。 SimpleXMLElementをつかってRSSを作成したのは、余計なstringとかで書かなくて済んだので良かったと思う。 RSS2エンティティの振る舞いだけで十分だったかなと思いました。 使い方 Packagistにも書いてありますが、 copied.$simpleRssMaker = new SimpleRssMaker(); $xml = $simpleRssMaker // Channel settings. ->setChannel('title', 'link', 'description', 'language', 'copyright', 'category', 'pubDate') // Image settings. ->setImage('title', 'link', 'url') // Item settings. ->addItem('title', 'link', 'description', 'author', 'category', 'datetime') ->addItem('title', 'link') // Generate RSS2.0 string. ->rss2(); // If you have more than one article. $simpleRssMaker = $simpleRssMaker // Channel settings. ->setChannel('title', 'link', 'description', 'language', 'copyright', 'category', 'pubDate') // Image settings. ->setImage('title', 'link', 'url'); foreach($items as $item) { $simpleRssMaker = $simpleRssMaker ->addItem('title', 'link', 'description', 'author', 'category', 'datetime'); } // Generate RSS2.0 string. $xml = $simpleRssMaker->rss2(); こんな感じ。SimpleRssMakerだけで全部設定できるように作成しました。 最初の頃はChannelFactoryとか作ってChannelクラスをユーザーに使用してもらおうと思ってたけど、使いにくいかなと思ってやめました。 最後に フィード系のライブラリは他にも結構いいのがあったけど、総じてPHP5.3とかのライブラリだったので、差別化は出来てるかなと思っています。(PHP7.4以上が対象です。) RSS1.0に対応はしたいけどAtomは別にいいかな... ということで、しょぼいけどOSS作れてよかったと思いました。 そろそろ私も色々な人に貢献していきたいなとおもいますので、他のパッケージも作って行きたいと思います。 そのときはよしなに。 .

PHPでTDDをしながら簡単なRSS生成アプリを作ってみた

2020/11/13 2020/11/15

PHPでTDDをしながら簡単なRSS生成アプリを作ってみた こんにちは。Nonです。 今回は事情によりRSS用のXMLを作成するライブラリをTDDしながら作成したみた話をしたいと思います。 GitHub - nonz250/simple-rss-maker-php Contribute to nonz250/simple-rss-maker-php development by creating an account on GitHub. TDDについて 以前関連する記事をかいていました。これをより実践的にやってみようという話になりますね。 以前の記事はこちら。 テスト駆動はテストのための手法ではない | のんラボ テスト駆動開発(TDD)は死んだって記事正直、開発手法などの記事については上辺をすくっただけなので、よくわかりませんが、TDDは時代遅れの開発手法ということなのでしょうか?しかし、 @t_wada さ... TDDしながらコードを書いてみる TDDはRED→Green→Refactoringが基本ですので、まずはテストコードを失敗させるところからスタートですね。 copied.<?php declare(strict_types=1); namespace Tests; use SimpleRssMaker\SimpleRssMaker; use PHPUnit\Framework\TestCase; class SimpleRssMakerTest extends TestCase { public function test__construct() { $simpleRssMaker = new SimpleRssMaker(); $this->assertInstanceOf(SimpleRssMaker::class, $simpleRssMaker); return $simpleRssMaker; } /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", $rss2); } } copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { return ""; } } 空文字を返す処理と理想的な<?xml version="1.0" encoding="UTF-8"?>\nを確認するテストを用意してテストを実行します。 copied..F 2 / 2 (100%) 結果はもちろん失敗です。 こちらを成功させるためにリファクタリングしましょう。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; } } 空文字を返すのではなく、理想的な<?xml version="1.0" encoding="UTF-8"?>\nを返すようにリファクタリングしました。 copied... 2 / 2 (100%) 結果はもちろん成功です。しかしこれでは、XMLタグを作成したとは言えないですね。思いっきりマジックナンバーになってしまっています。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $xml = $dom->saveXml(); return (string)$xml; } } XMLタグを動的に生成してくれるように変更しました。 copied... 2 / 2 (100%) うまくいきましたね。 しかしこれではRSSを作成したとは言えないので、引き続きテストを修正します。 copied. /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"/> XML; $this->assertEquals((string)$expected, (string)$rss2); } RSSに必要な<rss>タグを期待するテストへ変更しました。 ついでに、ヒアドキュメントを利用し、ファイル入力文字としてで期待するように変数を修正しました。 copied..F 2 / 2 (100%) 当然テスト失敗します。 次はこのテストをクリアするようにコードを修正しましょう。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $rss = $dom->createElement('rss'); $dom->appendChild($rss); $xml = $dom->saveXml(); $rss = new \SimpleXmlElement($xml); $rss->addAttribute('version', '2.0'); $xml = $rss->asXML(); return (string)$xml; } } PHPのDOMDocumentクラスとSimpleXmlElementを利用して、XMLタグとRSSタグを作成するコードを作成しました。 copied... 2 / 2 (100%) テストもクリアしたので、問題なさそうです。 次は<rss>タグの中身がなければRSSとして機能しませんので、期待値を変更します。 copied. /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <language>ja</language> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } RSS2.0の必須項目である<channnel>タグを用意し、その中に<language>タグを期待するテストへ変更しました。 copied..F 2 / 2 (100%) テストはもちろん失敗です。 copied. public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $rss = $dom->createElement('rss'); $dom->appendChild($rss); $xml = $dom->saveXML(); $rss = new \SimpleXMLElement($xml); $rss->addAttribute('version', '2.0'); $channel = $rss->addChild('channel'); $channel->addChild('language', 'ja'); $dom = dom_import_simplexml($rss)->ownerDocument; $dom->formatOutput = true; $xml = $dom->saveXML(); return (string)$xml; } 必要タグとその値をXMLに追加する処理を追加し、テストします。 copied... 2 / 2 (100%) クリアできたので、コードに問題はありませんね。 copied. /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expectedDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2020-01-01 00:00:00', new \DateTimeZone('UTC')); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>タイトル</title> <link>https://example.com</link> <description>説明</description> <language>ja</language> <copyright>コピーライト</copyright> <pubDate>{$expectedDate->format(\DateTimeInterface::RFC822)}</pubDate> <category>カテゴリ</category> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } では本格的にRSSの期待値を変更します。 copied..F 2 / 2 (100%) copied. public function rss2(): string { $dom = new \DOMDocument('1.0', 'UTF-8'); $rss = $dom->createElement('rss'); $dom->appendChild($rss); $xml = $dom->saveXML(); $rss = new \SimpleXMLElement($xml); $rss->addAttribute('version', '2.0'); $channel = $rss->addChild('channel'); $channel->addChild('title', 'タイトル'); $channel->addChild('link', 'https://example.com'); $channel->addChild('description', '説明'); $channel->addChild('language', 'ja'); $channel->addChild('copyright', 'コピーライト'); $channel->addChild('pubDate', 'Wed, 01 Jan 20 00:00:00 +0000'); $channel->addChild('category', 'カテゴリ'); $dom = dom_import_simplexml($rss)->ownerDocument; $dom->formatOutput = true; $xml = $dom->saveXML(); return (string)$xml; } copied... 2 / 2 (100%) かなり直列なコードですが、テストがクリアしたのでOKです。 しかしこれでは、動的に作成することができませんので、更に修正します。 copied.<?php declare(strict_types=1); namespace Tests; use SimpleRssMaker\SimpleRssMaker; use PHPUnit\Framework\TestCase; class SimpleRssMakerTest extends TestCase { public function test__construct() { $simpleRssMaker = new SimpleRssMaker(); $this->assertInstanceOf(SimpleRssMaker::class, $simpleRssMaker); return $simpleRssMaker; } /** * @depends test__construct * @param SimpleRssMaker $simpleRssMaker */ public function testRss2(SimpleRssMaker $simpleRssMaker) { $rss2 = $simpleRssMaker->rss2(); $expectedDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2020-01-01 00:00:00', new \DateTimeZone('UTC')); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>title</title> <link>https://example.com</link> <description>description</description> <language>ja</language> <copyright>copyright</copyright> <pubDate>Wed, 01 Jan 20 00:00:00 +0000</pubDate> <category>category</category> <image> <url>https://example.com/sample.png</url> <title>title</title> <link>https://example.com</link> </image> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } } 期待値を微調整し、欲しいタグを少しだけ追加しました。 copied..F 2 / 2 (100%) テストは失敗しましたね、先程、動的に値を取得作成していきたいと言ったのでそろそろコードも本格的にしていきましょう。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker; use SimpleRssMaker\Rss2\Command\UseCases\CreateRss2; use SimpleRssMaker\Rss2\Command\UseCases\CreateRss2Input; use SimpleRssMaker\Rss2\Command\UseCases\CreateRss2Output; use SimpleRssMaker\Rss2\Models\Entities\Rss2; class SimpleRssMaker implements SimpleRssMakerInterface { public function rss2(): string { $input = new CreateRss2Input(); $output = new CreateRss2Output(); $useCase = new CreateRss2(); $useCase->process($input, $output); $rss2dom = $output->rss2(); return (string)$rss2dom; } } final class CreateRss2Input implements CreateRss2InputPort { } final class CreateRss2Output implements CreateRss2OutputPort { private Rss2 $rss2; public function output(Rss2 $rss2): void { $this->rss2 = $rss2; } public function rss2(): Rss2 { return $this->rss2; } } final class CreateRss2 implements CreateRss2Interface { public function process(CreateRss2InputPort $inputPort, CreateRss2OutputPort $outputPort): void { $outputPort->output(new Rss2()); } } copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\Entities; use DOMDocument; use SimpleRssMaker\Shared\Models\ValueObjects\XmlEncoding; use SimpleRssMaker\Shared\Models\ValueObjects\XmlVersion; use SimpleXMLElement; final class Rss2 { public function createXMLDOM(): DOMDocument { $dom = new DOMDocument((string)$this->xmlVersion, (string)$this->xmlEncoding); $rss = $dom->createElement('rss'); $dom->appendChild($rss); return $dom; } public function createSimpleXmlElement(): SimpleXMLElement { $xml = $this->createXMLDOM()->saveXML(); $rss = new SimpleXMLElement($xml); $rss->addAttribute('version', '2.0'); $channel = $rss->addChild('channel'); $channel->addChild('title', 'title'); $channel->addChild('link', 'https://example.com'); $channel->addChild('description', 'description'); $channel->addChild('language', 'ja'); $channel->addChild('copyright', 'copyright'); $channel->addChild('pubDate', 'Wed, 01 Jan 20 00:00:00 +0000'); $channel->addChild('category', 'category'); $image = $channel->addChild('image'); $image->addChild('url', 'https://example.com/sample.png'); $image->addChild('title', 'title'); $image->addChild('link', 'https://example.com'); return $rss; } public function __toString(): string { $dom = dom_import_simplexml($this->createSimpleXmlElement())->ownerDocument; $dom->formatOutput = true; return $dom->saveXML(); } } とりあえずユースケースクラスとエンティティを作成し、その中で固定のXMLを返す処理にしてみました。 先程までSimpleRssMakerに書いていた処理をそのままエンティティに書き直しただけですね。 copied... 2 / 2 (100%) テストはクリアしたので、構造に問題はなさそうです。 では次に値を動的に生成できるようにしていきましょう。 結構かっちり作成したいので、ValueObejctを作成します。 ValueObejctを作成するからにはこの子のテストを書かなければなりませんね。 最初と同様に期待するテストと失敗する処理を書いてからのスタートです。 copied.<?php declare(strict_types=1); namespace Tests\Rss2\Models\ValueObjects; use SimpleRssMaker\Rss2\Models\ValueObjects\Link; use PHPUnit\Framework\TestCase; use Tests\TestHelper\StrTestHelper; class LinkTest extends TestCase { public function test__construct() { $expected = StrTestHelper::createRandomUrl(Link::MAX_LENGTH); $link = new Link($expected); $this->assertInstanceOf(Link::class, $link); $this->assertEquals($expected, (string)$link); return $link; } public function testFormatException() { $this->expectException(LinkFormatException::class); $expected = StrTestHelper::createRandomStr(); new Link($expected); } public function testLengthException() { $this->expectException(StrLengthException::class); $expected = StrTestHelper::createRandomUrl(Link::MAX_LENGTH + 1); new Link($expected); } } 期待するテストを書きました。 実装したコードはこちら。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\ValueObjects; final class Link { private string $link; public function __construct(string $link) { $this->link = $link; } public function __toString(): string { return (string)$this->link; } } copied.There were 2 failures: 1) Tests\Rss2\Models\ValueObjects\LinkTest::testFormatException Failed asserting that exception of type "SimpleRssMaker\Shared\Exceptions\LinkFormatException" is thrown. 2) Tests\Rss2\Models\ValueObjects\LinkTest::testLengthException Failed asserting that exception of type "SimpleRssMaker\Shared\Exceptions\StrLengthException" is thrown. クラスのインスタンス型のテストはクリアしていますが、バリデーション関連のテストで失敗していますね。 処理を書き直します。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\ValueObjects; use SimpleRssMaker\Foundation\Helpers\Rules\UriRule; use SimpleRssMaker\Shared\Exceptions\LinkFormatException; use SimpleRssMaker\Shared\Exceptions\StrLengthException; final class Link { /** * Maximum character length of URLs when using IE */ public const MAX_LENGTH = 2083; private string $link; public function __construct(string $link) { if (!UriRule::isValidHttp($link)) { throw new LinkFormatException(sprintf('%s must start at https:// or http://', get_class())); } $this->link = $link; } public function __toString(): string { return (string)$this->link; } } httpかhttps://から始まるリンクでないと検証失敗するコードを追加しました。 copied.There was 1 failure: 1) Tests\Rss2\Models\ValueObjects\LinkTest::testLengthException Failed asserting that exception of type "SimpleRssMaker\Shared\Exceptions\StrLengthException" is thrown. テストのエラーが一つ減ったのでうまく行ったようです。 copied.<?php declare(strict_types=1); namespace SimpleRssMaker\Rss2\Models\ValueObjects; use SimpleRssMaker\Foundation\Helpers\Rules\UriRule; use SimpleRssMaker\Shared\Exceptions\LinkFormatException; use SimpleRssMaker\Shared\Exceptions\StrLengthException; final class Link { /** * Maximum character length of URLs when using IE */ public const MAX_LENGTH = 2083; private string $link; public function __construct(string $link) { if (!UriRule::isValidHttp($link)) { throw new LinkFormatException(sprintf('%s must start at https:// or http://', get_class())); } if (mb_strlen($link) > self::MAX_LENGTH) { throw new StrLengthException(sprintf('%s must be less than %s chars.', get_class(), self::MAX_LENGTH)); } $this->link = $link; } public function __toString(): string { return (string)$this->link; } } 続いて、最大文字長の検証コードを追加しました。 copied... 2 / 2 (100%) これでLinkのValueObejctは感性しましたね。 copied.public function test__construct() { $expected = StrTestHelper::createRandomUrl(Link::MAX_LENGTH); $link = new Link($expected); $this->assertInstanceOf(Link::class, $link); $this->assertEquals($expected, (string)$link); return $link; } このテストでValueObjectとしての機能をテストして、他のExceptionテストで検証のテストがクリアしています。 こんな感じでTDDは進めるといいかも TDDの強み このように、画面でデバッグを進めるのではなく、テストで開発を進めるのがTDDです。 テストは当然プログラムで動作しているので、プログラム上のエラーも拾ってくれるので、非常に進めやすいです。また、自分の期待する値をあらかじめテストに記載してやれば、正常に動作したときの動作確認もできて一石二鳥だということがわかるかと思います。 最終的にはこんな感じ 最終的なメインクラスのテストコードはこんな感じになりました。 (仕様的にはまだ満たしていなけどひとまず雰囲気だけでも感じてください) copied.<?php declare(strict_types=1); namespace Tests; use DateTime; use DateTimeZone; use Exception; use SimpleRssMaker\Rss2\Models\Entities\Channel; use SimpleRssMaker\Shared\Exceptions\ChannelNotExistException; use SimpleRssMaker\SimpleRssMaker; use PHPUnit\Framework\TestCase; use SimpleRssMaker\SimpleRssMakerInterface; use Tests\TestHelper\StrTestHelper; class SimpleRssMakerTest extends TestCase { public function test__construct() { $simpleRSSMaker = new SimpleRssMaker(); $this->assertInstanceOf(SimpleRssMaker::class, $simpleRSSMaker); return $simpleRSSMaker; } /** * @depends test__construct * @param SimpleRssMakerInterface $simpleRSSMaker */ public function testException(SimpleRssMakerInterface $simpleRSSMaker) { $this->expectException(ChannelNotExistException::class); $simpleRSSMaker->rss2(); } /** * @depends test__construct * @param SimpleRssMakerInterface $simpleRSSMaker */ public function testChannelFactory(SimpleRssMakerInterface $simpleRSSMaker) { $channel = $simpleRSSMaker->channelFactory( StrTestHelper::createRandomStr(), StrTestHelper::createRandomUrl(), StrTestHelper::createRandomStr(), ); $this->assertInstanceOf(Channel::class, $channel); $simpleRSSMaker->setChannel($channel); $this->assertIsString($simpleRSSMaker->rss2()); } /** * @depends test__construct * @param SimpleRssMakerInterface $simpleRSSMaker * @throws Exception */ public function testRss2(SimpleRssMakerInterface $simpleRSSMaker) { $title = StrTestHelper::createRandomStr(); $link = StrTestHelper::createRandomUrl(); $description = StrTestHelper::createRandomStr(); $copyright = StrTestHelper::createRandomStr(); $pubDate = new DateTime('now', new DateTimeZone('UTC')); $category = StrTestHelper::createRandomStr(); $channel = $simpleRSSMaker ->channelFactory($title, $link, $description); $channel->setCopyright($copyright); $channel->setPubDate($pubDate); $channel->setCategory($category); $rss2 = $simpleRSSMaker ->setChannel($channel) ->rss2(); $expected = <<<XML <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>{$title}</title> <link>{$link}</link> <description>{$description}</description> <language>ja</language> <copyright>{$copyright}</copyright> <pubDate>{$pubDate->format(DateTime::RFC822)}</pubDate> <category>{$category}</category> </channel> </rss> XML; $this->assertEquals((string)$expected, (string)$rss2); } } 最後に こちらがGithubのコードです。 GitHub - nonz250/simple-rss-maker-php Contribute to nonz250/simple-rss-maker-php development by creating an account on GitHub. 機能的にはまだまだ途中なので、引き続き経過を記事にしたいと思います。 その時はよしなに。 .

AIを使っておすすめ商品を表示するスマレコ

2020/10/30 2020/10/30

AIを使っておすすめ商品を表示するスマレコ こんにちは。のんです。 今回はスマレジでスマレジ4のアプリコンテストが開かれましたでも言っていた、スマレコというアプリについて記事にしようと思います。 前から使ってみたかった AIが技術として普及し始めて結構経ちました。当時からAIを使って大きなデータを分析したいと思っていたので、スマレコ作成しました。 AIの作り方はそのうち別記事にしようかなと思います。 今回は紹介記事?的なものになるかと。 スマレジ4APIを利用した同期処理 元になるデータはスマレジ4APIを利用しています。しかしAIの処理には大量の元データがいるので、たくさんAPIをコールしなければなりません。(1000とかその程度の件数では無い。) この辺取引一覧で取得できるのかと思ったら、仕様上一覧で取得できるのはHeadの情報のみで、明細(商品情報など)は1件ずつ取得するしかないらしい。 ちょっとつらみですね。 Webhookで取引が登録されるたびにその情報を教えてもらって同期を取る手段も考えましたが、ちょっと訳あって保留中です。 ともかく、自前のDBにAPIで取得した情報をコピーしたあとで、分析を開始するという手段を取っています。(その方が速度的にも早い) スマレコの使い方 導線 スマレジでログインする 会員情報を同期する 分析ボタンを押す のみです。本当は分析終了時にメールで通知する処理を作成したかったのですが、スマレジ4のユーザー情報取得にメールアドレス取得はまだ実装していないので、この辺はスマレジのバージョンアップを待って、後日バージョンアップします。 課題 機能を持たない(例えばお会計の補助アプリとか、注文の補助アプリとか)ではなく、データをこねくるアプリなので、使い所が難しいです。店舗のデータ分析に熱心な方くらいで、普通の店長さんはこんなの考えないと思いますし。 相関値とか急に言われても、専門的な勉強をしてきた人しかわからない。見せ方に工夫しないとダメですね。ランキングのように変更して、多少の誤解は恐れず、一般化した方がいいかも知れません。 AIサーバーについては特に問題ないと思いますが、なにせ計算に時間がかかるので、同時に複数の処理を要求されたら流石に不安...ちょっと検証していかないとダメかも。 直近で抱えてる課題はこんなもんですかね? 安定稼働するまでは確実に持っていきたい。どれくらいの大きさのデータなら安定するかというのを探らなきゃいけないのも、こういうAI系アプリの面白いところかも。 予定 通知機能 これがなきゃ使おうと思わない 他の演算を利用した分析機能 AI演算処理の安定化、安全化 上記3点がクリアされれば、一段落といったところでしょうか。もちろん、現状の機能でも使用できるようにしていますが、より良くするための機能ですね。 最後に 今回はスマレコの紹介記事になりました。 本当は動画とかも用意したかったのですが、このブログまだ動画に対応していないので、その辺勉強してから出直してきます。 スマレジ4APIを利用したアプリは軽くてシンプルで業務の助けになるアプリがいいのかもと使ってみてわかりました。いろいろな会社のAPIを分析中心に使用してきた私の価値観が変わったかも。 次回はまだ何書くか決まってませんが、そろそろ技術的なアウトプットしたい。VueやDDDを習得してからというもの新しいインプットがないから焦ってます;; また記事にします。 その時はよしなに。 .