最近アプリばかり作っているので、冬休みの久しぶりにウェブサービスを作ることにした。5を使って目標管理ツールを作った。Herokuにデプロイした。遠回りで省略可能なステップもありますが、わかりやすさ重視のステップで書いていきます。Railsの基礎的な使い方に関しては省略。CableやAPIモードが追加された。今回はそこらへんは使わないが、一応最新版を使っておく。3間も無く4がリリースされるが、現行版は3なので3を使う。CLIをインストールしておくPostgresHerokuではSQLiteは使えず、Postgresを使うことになる。Herokuエディタ2016年現在、Railsの開発に使うエディタとしてはatomが良い感じ。従ってパッケージを入れると、大分快適な開発環境が整った。browser-syncすら不要。2016年12月31日現在、browser-plusはうまく動作しなかったので、代わりにplatformio-ide-terminalを使うようにした。postgresqlHeroku上で運用することを考えると、最初からPostgresを使う設定でプロジェクトを作っておいた方が良い。https://devcenter.heroku.com/articles/sqlite3プロジェクトを生成したら、atomを起動する。初回なので、developmentとtest用のPostgresのDBを作成する。atom上で、Ctrl+Shift+@でターミナルを起動したら、db:createを実行する。postgresなどで確認する。動作確認まだ何もない状態だが、DBの作成までできたところで一旦起動確認しておく。sコマンドでサーバーを起動する。Ctrl+Opt+Oで、ブラウザータブを開き、http://localhost:3000/を開く。右上の雷マークを押しておくと、ライブリロードが有効になる。(ファイルを更新すると自動的にページが再読み込みされる)gitレポジトリの作成Herokuにデプロイするタイミングでも必要になるので、プロジェクト用のgitレポジトリを作っておく。commit"Scaffoldで土台を作る一旦Ctrl+Cでrailsサーバーを終了してから、railsコマンドでListのscaffoldを作り、マイグレーションを走らせる。sでサーバーを再起動する。sブラウザタブでhttp://localhost:3000/listsを開き、Listのscaffoldが正しく動作することを確認する。自動リロードの確認http://localhost:3000/listsを開いた状態で、index.html.erbの中身を編集・保存すると、ブラウザの自動リロードが発生して画面に変更が反映されることを確認する。0.9"haml:erb2hamlコマンドを使うと、プロジェクト内のerbファイルをまとめてhamlファイルに変換できる。5の組み合わせでは、uglifierで問題が発生することがあるため、修正されているtherubyracerを使っておく。lessでプロジェクトにBootstrap関連のアセットを追加する。lessまた、bootstrap:layoutを実行すると、レイアウトテンプレートにbootstrapの雛形を適用できる。bootstrapの雛形が適用されるただし、生成されるレイアウトに含まれるnavbarとsidebarは今回は使わないので、消しておく。2017Herokuにデプロイする一旦ベースの構造ができたところで、Herokuにデプロイしてみる。作業中のファイルを、gitレポジトリにコミットしておく。createアプリ・レポジトリができたら、pushするとデプロイが始まる。master初回デプロイなので、DBのマイグレーションを実行する。db:migrateマイグレーションが終わったら、ブラウザでHerokuアプリケーションのURLを開いて、Listのscaffoldが正しく動くことを確認する。TZ=Asia/Tokyo機能実装ルートパスの設定サイトのルートにアクセスした時に、リストの一覧画面(lists#index)が表示されるようにしておく。:listsendリストの下に目標を追加できるようにする結論から言うと、ここで作るscaffoldの内容は、最終的にはほとんど不要になって削除してしまうことになる。完成形が見えている場合はscaffoldを使わなくても良いのだが、データ構造が明らかになってそれが正しく動く様子を確認してからUIに合わせて内容を書き換えていく方が分かりやすいと思うので、少し遠回りだがscaffoldを使って作っていく。db:migrateListが複数のAchievementオブジェクトを保持できるようなデータ構造にする。:achievementsendListが複数のAchievementを持つことをhas_manyで明示的に設定しておく。list:referencesの代わりに、list_id:integerとしても同じデータ構造になるが、referencesを使うとインデックスを作ってくれるので、必要性がなければreferencesを使った方が良い。目標の登録ができるようになるが、目標作成時に手動でリストを入力する、というのは使い勝手的によろしくない。URL構造を変える目標はリスト毎に管理・表示されるものなので、まずはURLの構造から変えていきたい。http://localhost:3000/lists/#{リストのID}/achievementsというURLでリスト毎の目標一覧画面を出すようにroutes.rbを書き換える。endendこれで、lists/#{リストID}/achievementsというURLでアチーブメント一覧画面にアクセスできるようになるが、実際にhttp://localhost:3000/lists/#{リストのID}/achievementsを開くとnew_achievement_pathの箇所でエラーになる。ルーティングの設定が変わったため、new_achievement_pathというパスが存在しなくなったためだ。routesで有効なパスの一覧を確認する。lists#destroynew_achievementの代わりにnew_list_achievementが登場しているため、これに置き換える。親となるListの取得ただし、この修正をするにあたっては、少し下準備をする必要がある。achievements_controllerはlistsの下にネストされた状態になった。今後、achievements_controllerは:list_idで指定されたListに紐づいて動くようにする必要がある。そこで、achievements_controllerのbefore_actionで各アクションの実行前にIDが:list_idとなるListを@listとして参照できるように改造する。...endここで、achievements/index.html.hamlを編集して、ページにリストの名前が表示されるようにしてみる。achievements/index.html.haml%h1=@list.name%table...加えて、新しいアチーブメントを追加するページ(achievements#new)へのリンク生成箇所も、新しいnew_list_achievement_pathを使う形に書き換える。new_list_achievement_path(@list)これで、目標一覧ページ(lists/#{リストのID}/achievements)を表示できるようになった。目標は一つも登録されていないので何も表示されないし、登録したとしてもリスト関係なく全ての目標が表示されてしまうが、それは後で直していく。次に、新しい目標の作成ページを修正する。新規作成画面(new.html.haml)は、内部的に_form.html.hamlを参照している。_form.html.hamlでは、目標の設定のためのフォームが定義されている。ここで使用しているform_forタグは、元々@achievementの登録(・更新)のためのURL(achievements/:id)を生成するようになっていたが、URL構造の変更により(lists/:list_id/achievements/:id)を生成するように書き換えなければいけない。|f|第一引数に@listと@achievementの両方を渡すことによって、正しいURLを生成できるようになる。次に、_form.html.hamlにはListを指定するための欄があるが、Listを手動で指定させることは無いし、変な値を入力されても困るので、この項目は削除してしまおう。#いらないまた、そもそも変な値を送れないようにコントローラの方でも制限しておこう。endもう一箇所、new.html.hamlでは、ページ下部に「Back」リンクがあり、そこで存在しないパス(achievements_path)が指定されているためエラーが発生している。achievements_pathこれは、list_achievements_path(@list)とすることで動作するようになる。最後に、フォームの値が送信されたらインスタンスを作る部分を修正する。@list.achievements.build(achievement_params)新しく保存されるAchievementのインスタンスは、@listの子要素として生成されるように@list.achievements.buildで生成する。することで、自動的にlist_idに@listのIDが入る。}URL構造の変更によって、@achievement(achievement_path)に該当するものはなくなっている。list_achievement_pathを表示するように直してもよいが、Nameしか表示項目がないものを表示するのは微妙なので、list_achievements_pathを表示するように変更しておく。}ここまでの修正で、新しい目標の作成まで動作するようになっているが、このままでは一覧表示画面が正しく表示されないので、修正する。endlists/:list_id/achievementsにおいて、全ての目標(Achievement.all)ではなく、@listにひもづく目標(@list.achievements)のみを表示させるようにする。}また、今までと同じ要領でパスの修正を行う。Destroyアクションに関連する各パス生成箇所も修正する。UIの改良lists#showとachievements#indexを統合するlists#showもachievements#indexも、リストに含まれる目標の一覧を表示する、という意味合いでは同じ目的のページとなるので、どちらか一つに統合してしまおう。どちらを残す形でも良いが、URLが短くなる方が嬉しいので、lists#showを残してachievements#indexを削除してしまおう。lists#showで目標の一覧を表示するachievements#indexで表示している目標一覧の実装をlists#showに持ってくる。同時に、それっぽくなるように一旦ページのレイアウトを少し調整しておく。btn-default"Nameしか項目を持たない個別の目標(Achievement)のshowやedit、updateはあまり意味がないので、消してしまうことにした。updateの各アクションに関するachievements_controller内の実装も消してしまおう。[:destroy]のonly:対象から削除するのも忘れずに。下記のビューファイルも不要なので消しちゃう。(newと_formだけが残る)views/achievements/show.html.hamlviews/achievements/show.json.jbuilderviews/achievements/edit.html.hamlviews/achievements/index.html.hamlviews/achievements/index.json.jbuilderviews/achievements/_achievements.json.jbuilderそして、achievements_controller内、目標の作成後などlist_achievements_pathにリダイレクトしていた箇所は、全て@listにリダイレクトするように書き換える。}ここまで作って、ようやくリストの作成〜目標の作成までが画面として繋がった。目標を達成したら達成済み(achieved=true)にできるようにする。アクションの追加lists/:list_id/achievements/:id/achieveで、目標を達成できるようにする。endendachievements_controllerに、achieveアクションを定義していく。アクションを定義する前に、新しく追加するachieveアクション内でも@achievementを参照できるように、set_achievementの対象に含めておく。:achieve]achieveアクションでは、@achievementのachievedフラグをtrueにして、リスト画面にリダイレクトする。目標一覧画面で、未達成の目標には「達成済みにする」リンクを表示するようにする。'達成済みにしますか?一度達成済みにすると、元に戻せません。btn-danger"目標登録時に「達成済み」にならないようにする最後に、新しい目標が作成されるタイミングでは「達成済み」にならないように対策をします。フォームから項目を消すフォームの「達成済み」チェックボックスは不要なので、消す。:achievedあわせて、コントローラ側でもachievedパラメータを受け付けないように対策する。:achievedを消したend以上で、目標の達成管理まで動くようになった。2017年の目標宣言では、レイアウトの調整に加えて、下記のような機能を実装しています。イベント管理Twitterへのシェア機能etc.が、篦棒に長くなってしまうわりに内容が細かいので説明は省略します。いつか気が向いたら、完全版を書き記すかも...では良い冬休みを。