日本語の説明はここをクリックして展開
- これは、Ruby on Railsで実装された、日々の食事記録を投稿できるSNSアプリである。
- このレポジトリではPR運用をしており、各機能の詳細な設計や開発のログはそのPRページに記入してある。
- CSS, JavaScriptのアセット管理はwebpackを用いている。
- モデル層に関してはrspecによるテストを実装している。
- パスワード認証/Facebook認証の2種類のサインアップ/ログイン機能
- 食事ポスト(最低1つ以上の食品目で構成される)の投稿機能/投稿削除機能
- 他人の食事ポストへの投票(いいね/悪いね)機能
- ユーザー間のフォロー機能
- 自分がフォローしているユーザの投稿を表示するフィード機能
https://diet-app-2020.herokuapp.com
以下のemailとパスワードでログインして動作確認が可能です。
password | |
---|---|
tester@test.com |
testtest |
- このレポジトリをローカルにクローンする。
- PosgreSQLサーバーを起動し、データベースのマイグレーションを行う。
$ pg_ctl -D /usr/local/var/postgres start
$ bundle exec rails db:create
$ bundle exec rails db:migrate
- Railsサーバーを起動する。
$ bundle exec rails serve
- ブラウザから
localhost:3000
にアクセス。
- Ruby on Rails (Ruby=2.7.0, Rails=6.0.2)
- HTML(erb), SCSS, JavaScript
- PostgreSQL
- Heroku
ライブラリ名 | 使用用途 |
---|---|
devise | 認証機能のために使用 |
omniauth | Facebook認証のためにdeviseとともに使用 |
cocoon | cocoon-js(JSライブラリ)とともに、ネストされたモデルのフォーム作成のために使用 |
counter_culture | あるレコードに関連するレコードの個数や値の集計(カウンターキャッシュ)機能のために使用 |
rubocop | ローカル環境でのコードの静的解析のために使用 |
rspec | モデル層のテストのために使用 |
factory_bot_rails | rspecでダミーのモデルのインスタンスを作成するために使用 |
ライブラリ名 | 使用用途 |
---|---|
Bootstrap | デザインテンプレートとして使用 |
ライブラリ名 | 使用用途 |
---|---|
jquery | Ajax通信のためなどに使用 |
flatpickr | カレンダーから日時を選択するUIのために使用 |
cocoon-js | cocoon(gem)とともに、ネストされたモデルのフォーム作成のために使用 |
usersテーブル
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('users_id_seq'::regclass) |
character varying | not null | ||
account_id | character varying | not null | |
name | character varying | not null | |
encrypted_password | character varying | ''::character varying | |
is_male | boolean | ||
height | double precision | ||
weight | double precision | ||
comment | text | ||
reset_password_token | character varying | ||
reset_password_sent_at | timestamp without time zone | ||
remember_created_at | timestamp without time zone | ||
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null | |
provider | character varying | ||
uid | character varying |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"index_users_on_account_id" UNIQUE, btree (account_id)
"index_users_on_email" UNIQUE, btree (email)
"index_users_on_reset_password_token" UNIQUE, btree (reset_password_token)
Referenced by:
TABLE "meal_posts" CONSTRAINT "fk_rails_07c05f4a8d" FOREIGN KEY (user_id) REFERENCES users(id)
TABLE "votes" CONSTRAINT "fk_rails_c9b3bef597" FOREIGN KEY (user_id) REFERENCES users(id)
relationshipsテーブル
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('relationships_id_seq'::regclass) |
follower_id | integer | ||
followed_id | integer | ||
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null |
Indexes:
"relationships_pkey" PRIMARY KEY, btree (id)
"index_relationships_on_follower_id_and_followed_id" UNIQUE, btree (follower_id, followed_id)
"index_relationships_on_followed_id" btree (followed_id)
"index_relationships_on_follower_id" btree (follower_id)
meal_postsテーブル
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('meal_posts_id_seq'::regclass) |
content | text | ||
time | timestamp without time zone | ||
user_id | bigint | ||
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null | |
total_calories | integer | ||
food_items_count | integer | not null | 0 |
food_items_with_calories_count | integer | not null | 0 |
Indexes:
"meal_posts_pkey" PRIMARY KEY, btree (id)
"index_meal_posts_on_user_id" btree (user_id)
Foreign-key constraints:
"fk_rails_07c05f4a8d" FOREIGN KEY (user_id) REFERENCES users(id)
Referenced by:
TABLE "food_items" CONSTRAINT "fk_rails_333bcce849" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
TABLE "votes" CONSTRAINT "fk_rails_bbb5af58df" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
food_itemsテーブル
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('food_items_id_seq'::regclass) |
name | character varying | not null | |
amount | character varying | ||
calory | bigint | ||
meal_post_id | bigint | not null |
Indexes:
"food_items_pkey" PRIMARY KEY, btree (id)
"index_food_items_on_meal_post_id" btree (meal_post_id)
Foreign-key constraints:
"fk_rails_333bcce849" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
votesテーブル
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('votes_id_seq'::regclass) |
user_id | bigint | not null | |
meal_post_id | bigint | not null | |
is_upvote | boolean | not null | |
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null |
Indexes:
"votes_pkey" PRIMARY KEY, btree (id)
"index_votes_on_user_id_and_meal_post_id" UNIQUE, btree (user_id, meal_post_id)
"index_votes_on_meal_post_id" btree (meal_post_id)
"index_votes_on_user_id" btree (user_id)
Foreign-key constraints:
"fk_rails_bbb5af58df" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
"fk_rails_c9b3bef597" FOREIGN KEY (user_id) REFERENCES users(id)
ログイン機構、User 間の Follow 機能、MealPost の投稿機能の追加
- PR#1で実装。
- 最初の実装。この PR 時点では、機能的にはほぼ Rails チュートリアルに近かった。
- 認証には devise を用いた。メールアドレスとパスワードでログイン。
- asset pipeline を使わず、webpack を導入した。
- datetimepicker でカレンダーを表示するのに苦戦した
Voting(投票機能)の追加
Facebook 認証によるサインインとログイン機能の追加
-
従来の devise を用いたメールアドレスとパスワードでのログイン形式に加え、ominiauth による Facebook 認証を加えた。
-
本システムでは以下の認証システムをとる
FacebookでLogin
ボタンを押下- Facebook にリダイレクトされ、そこでログインかつ許可ボタンを押下。
- アプリにリダレクトされる。この時点で、認可されて得られる(provider, uid)に対応する User が存在すれば自動的にその User としてログインされ、ホームへ飛ばされる。User が存在しない場合は Login されずにホームへ飛ばされる。
-
このために
OmniauthCallbacksController
とFacebookUsersController
の 2 つのコントローラを実装した- User テーブルに
provider
とuid
の2カラムを付け足した(provider
にはfacebook
という文字列が収納される。これは後に Google などの他の外部認証を加えることを想定して作ったカラムである)
-
この PR 時点では、 Facecbook 認証では Facebook の認可サーバから取ってきた
provider
とuid
に合致する列が users テーブルにが存在すれば認証完了、としていた。 -
この PR 時点では Facecbook 認証したユーザは password 設定ができない。故に profile や password 更新ができない。これらは以下の PR で直された。
Facebook 認証で登録した User が後からパスワードを設定できるようにする
MealPost から[Up|Down]Voters 一覧を、User から[|Un]favorite MealPosts の一覧を見れるようにする
-
PR#11で実装。
-
あるユーザーが[いいね|悪いね]した投稿一覧、ある投稿を[いいね|悪いね]したユーザ一覧をかえすエンドポイントを実装した。肝はこことここ。
class MealPost < ApplicationRecord has_many :votes, dependent: :destroy has_many :votes, dependent: :destroy has_many :upvotes, -> { where(is_upvote: true) }, class_name: 'Vote' has_many :upvoters, through: :upvotes, source: :user ...... end
MealPost のサブ項目として FoodItem を追加する
- PR13で実装。仕様設計はここ、時間かかったのはこことここ。
- MealPost の has_nested_attributes_for で FoodItem モデルを実装した
- cocoon gem を使用することで、FoodItems のフォームの数を柔軟に加減できる UI を構築した。最初にページをロードした時は FoodItem のフォームは 3 つ表示されているが、、(➕) ボタンを押すことでフォームの数を増やしたり、(✖︎) ボタンを押すことでフォームの数を減らしたりできるようにした
- FoodItem はそれぞれカロリー値を入力できる(必須ではない)。MealPost ではそれに属する FoodItems のカロリーの総和を
15kcal+
といった形式で表示する。(+
はその MealPost に属する全ての FoodItem にカロリーが入力されている場合のみ省略される)これを実現するため、counter_culture gem を導入し、 meal_posts テーブルにtotal_calories
food_items_count
food_items_with_calories_count
という 3 つのカラムを付け加えた。
機能の追加
- Google, Twitter認証でのサインイン、ログイン機能
- フォロワー/フォロイング数の表示
- プロフィールに画像を追加
- ポストへのコメント機能の追加
- DM チャットやビデオ通話機能の追加
開発環境、デプロイ環境の改善
- Github Action による CI/CD の導入
- Docker の導入
- AWS 上にデプロイ
- UI の洗練
- This is a SNS app where you can post a report of each meal.
- Each feature was implemented in one PR. You can find the details of specification, system design of each feature there.
- You can sign up with your email or with your facebook account.
- CSS, JavaScript files are bundled by webpack.
- Rspec test cases are implemented for model layer.
- Password authentication and facebook authentication
- Creating/deleting a post of each meal (which consists of one or more food items)
- Like/dislike posts by other users
- Follow/unfollow other users
- Feed where posts from your followings are listed
https://diet-app-2020.herokuapp.com
Try my app with this email and password. (Please don't change the password.)
password | |
---|---|
tester@test.com |
testtest |
- Clone this repository on your local.
- Boost PosgreSQL server and migrate database schema.
$ pg_ctl -D /usr/local/var/postgres start
$ bundle exec rails db:create
$ bundle exec rails db:migrate
- Boost Rails server.
$ bundle exec rails serve
- Open
localhost:3000
in your browser.
- Ruby on Rails (Ruby=2.7.0, Rails=6.0.2)
- HTML(erb), SCSS, JavaScript
- PostgreSQL
- Heroku
Library | What it is for |
---|---|
devise | For authentication |
omniauth | For facebook authentication (combined with devise) |
cocoon | For input forms of nested models (combined with cocoon-js ) |
counter_culture | For counter cache (number/statistics of records associated with a certain record) |
rubocop | For static analysis in the local environment |
rspec | For testing of model layer |
factory_bot_rails | For creating dummy model instances used for rspec |
Library | What it is for |
---|---|
Bootstrap | For design template |
Library | What it is for |
---|---|
jquery | For Ajax request and for DOM manipulations |
flatpickr | For UI to pick a date from a calendar |
cocoon-js | For input forms of nested models (combined with cocoon) |
users table
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('users_id_seq'::regclass) |
character varying | not null | ||
account_id | character varying | not null | |
name | character varying | not null | |
encrypted_password | character varying | ''::character varying | |
is_male | boolean | ||
height | double precision | ||
weight | double precision | ||
comment | text | ||
reset_password_token | character varying | ||
reset_password_sent_at | timestamp without time zone | ||
remember_created_at | timestamp without time zone | ||
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null | |
provider | character varying | ||
uid | character varying |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"index_users_on_account_id" UNIQUE, btree (account_id)
"index_users_on_email" UNIQUE, btree (email)
"index_users_on_reset_password_token" UNIQUE, btree (reset_password_token)
Referenced by:
TABLE "meal_posts" CONSTRAINT "fk_rails_07c05f4a8d" FOREIGN KEY (user_id) REFERENCES users(id)
TABLE "votes" CONSTRAINT "fk_rails_c9b3bef597" FOREIGN KEY (user_id) REFERENCES users(id)
relationships table
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('relationships_id_seq'::regclass) |
follower_id | integer | ||
followed_id | integer | ||
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null |
Indexes:
"relationships_pkey" PRIMARY KEY, btree (id)
"index_relationships_on_follower_id_and_followed_id" UNIQUE, btree (follower_id, followed_id)
"index_relationships_on_followed_id" btree (followed_id)
"index_relationships_on_follower_id" btree (follower_id)
meal_posts table
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('meal_posts_id_seq'::regclass) |
content | text | ||
time | timestamp without time zone | ||
user_id | bigint | ||
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null | |
total_calories | integer | ||
food_items_count | integer | not null | 0 |
food_items_with_calories_count | integer | not null | 0 |
Indexes:
"meal_posts_pkey" PRIMARY KEY, btree (id)
"index_meal_posts_on_user_id" btree (user_id)
Foreign-key constraints:
"fk_rails_07c05f4a8d" FOREIGN KEY (user_id) REFERENCES users(id)
Referenced by:
TABLE "food_items" CONSTRAINT "fk_rails_333bcce849" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
TABLE "votes" CONSTRAINT "fk_rails_bbb5af58df" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
food_items table
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('food_items_id_seq'::regclass) |
name | character varying | not null | |
amount | character varying | ||
calory | bigint | ||
meal_post_id | bigint | not null |
Indexes:
"food_items_pkey" PRIMARY KEY, btree (id)
"index_food_items_on_meal_post_id" btree (meal_post_id)
Foreign-key constraints:
"fk_rails_333bcce849" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
votes table
Column | Type | Nullable | Default |
---|---|---|---|
id | bigint | not null | nextval('votes_id_seq'::regclass) |
user_id | bigint | not null | |
meal_post_id | bigint | not null | |
is_upvote | boolean | not null | |
created_at | timestamp(6) without time zone | not null | |
updated_at | timestamp(6) without time zone | not null |
Indexes:
"votes_pkey" PRIMARY KEY, btree (id)
"index_votes_on_user_id_and_meal_post_id" UNIQUE, btree (user_id, meal_post_id)
"index_votes_on_meal_post_id" btree (meal_post_id)
"index_votes_on_user_id" btree (user_id)
Foreign-key constraints:
"fk_rails_bbb5af58df" FOREIGN KEY (meal_post_id) REFERENCES meal_posts(id)
"fk_rails_c9b3bef597" FOREIGN KEY (user_id) REFERENCES users(id)
ログイン機構、User 間の Follow 機能、MealPost の投稿機能の追加
- PR#1で実装。
- 最初の実装。この PR 時点では、機能的にはほぼ Rails チュートリアルに近かった。
- 認証には devise を用いた。メールアドレスとパスワードでログイン。
- asset pipeline を使わず、webpack を導入した。
- datetimepicker でカレンダーを表示するのに苦戦した
Voting(投票機能)の追加
Facebook authentication for signing up and logging in
-
In addition to an password authentication implemented with gem 'devise', Facebook authentication with gem 'ominiauth' is implemented.
-
Specification of user experience flow is:
- Click
Login in Facebook
button. - Redirected to Facebook. Click
Login
button and then clickAdmit
button in the next page. - Redirected back to the app. At this point, if a user record with (provider, facebook_uid) retrieved from Facebook exists, the user automatically logs in , and then is taken to the root the root page. If such a user record does not exist, a new user record is created and then the user is taken to the root page.
- Click
-
For this, in terms of deign,
- Two controllers including
OmniauthCallbacksController
andFacebookUsersController
are implemented. - Add two columns (
provider
,uid
) are added. (provider
にはfacebook
という文字列が収納される。これは後に Google などの他の外部認証を加えることを想定して作ったカラムである)
- この PR 時点では、 Facecbook 認証では Facebook の認可サーバから取ってきた
provider
とuid
に合致する列が users テーブルにが存在すれば認証完了、としていた。 - この PR 時点では Facecbook 認証したユーザは password 設定ができない。故に profile や password 更新ができない。これらは以下の PR で直された。
Facebook 認証で登録した User が後からパスワードを設定できるようにする
MealPost から[Up|Down]Voters 一覧を、User から[|Un]favorite MealPosts の一覧を見れるようにする
-
PR#11で実装。
-
あるユーザーが[いいね|悪いね]した投稿一覧、ある投稿を[いいね|悪いね]したユーザ一覧をかえすエンドポイントを実装した。肝はこことここ。
class MealPost < ApplicationRecord has_many :votes, dependent: :destroy has_many :votes, dependent: :destroy has_many :upvotes, -> { where(is_upvote: true) }, class_name: 'Vote' has_many :upvoters, through: :upvotes, source: :user ...... end
MealPost のサブ項目として FoodItem を追加する
- PR13で実装。仕様設計はここ、時間かかったのはこことここ。
- MealPost の has_nested_attributes_for で FoodItem モデルを実装した
- cocoon gem を使用することで、FoodItems のフォームの数を柔軟に加減できる UI を構築した。最初にページをロードした時は FoodItem のフォームは 3 つ表示されているが、、(➕) ボタンを押すことでフォームの数を増やしたり、(✖︎) ボタンを押すことでフォームの数を減らしたりできるようにした
- FoodItem はそれぞれカロリー値を入力できる(必須ではない)。MealPost ではそれに属する FoodItems のカロリーの総和を
15kcal+
といった形式で表示する。(+
はその MealPost に属する全ての FoodItem にカロリーが入力されている場合のみ省略される)これを実現するため、counter_culture gem を導入し、 meal_posts テーブルにtotal_calories
food_items_count
food_items_with_calories_count
という 3 つのカラムを付け加えた。
Add features
- Google authentication, Twitter authentication
- Display number of your followings/followers
- Uploading a profile image
- Posting reply comments on a post
- Direct messaging
Improve development environment
- Introduction of CI/CD by Github Actions
- Introduction of Docker
- Deployment on AWS (not Heroku)
- Sophisticate UI