【Ruby on Rails】 愛犬管理アプリを作ってみたので作り方を解説してみる【初心者向け】

# はじめに
愛犬管理アプリを作りました。
きっかけは、DICというプログラミングスクール。
Ruby on Railsについて学んだので、そこで学んだ知識を総動員したアプリを作ってみたかったのです。

# 概要
以下の目次に従い、3記事ほどに分けて、記載していく予定です。

* 目次
    * [part1 : ペット管理機能](https://qiita.com/tak8_al/items/08274dd9228363642555)
    * [part2 : ペットに関するタスク機能](https://qiita.com/tak8_al/items/7be5fc885c779b384b0e)
    * [part3 : ペットの体調管理機能](https://qiita.com/tak8_al/items/4f099c2597122bebb8df)
    

* 実装したい機能

    * 個人単位ではなく、集団単位でのペット管理機能
    * ペットに関するタスク機能
    * ペットの体調管理機能


# 開発環境
MacBook Air (M1, 2020)
Rails 6.1.6.1
ruby 3.0.1
yarn 1.22.19

# 成果物
現在作成中です。
後ほど、写真をアップしますので、少々お待ちください。。。

# 本記事の内容
* 環境構築
* 集団単位でのペット管理機能



本記事では、家族であったり、兄弟であったりと、2人以上の集団単位でのペット管理機能の実装と、そのための環境構築までを記載します。


# 環境構築
①プロジェクトの作成
②データベースの作成

# ①プロジェクトの作成
```:terminal
$ rails _6.1.6_ new pet_health -d postgresql
```

[-d postgresql]： データベースとしてPostgreSQLを使用するオプション。プロジェクト生成時にVue.jsを同時にインストールするオプション。これを設定しなければ、標準のSQLiteが使用されます。
実行したら、作成したフォルダに移動します。


# ②データベースの作成
rails db:createを実行することによって、database.ymlの設定に従い、データベースが作成されます。

```:terminal
$ rails db:create
```


Webサーバを起動して、アクセスしてみます。

http://localhost:3000/

<img width="1000" alt="スクリーンショット 2022-09-07 午前11.39.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/7926cfff-f114-ed7d-5d19-2d612c654c1d.png">

上記のURLにアクセスし、上記の画面が出れば、環境構築は完了です。

# 集団単位でのペット管理機能

![スクリーンショット 2022-09-12 午前10.05.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/8ef5a12a-e4d5-715e-aea9-fe794b246f09.png)

集団単位でのペットの管理方法としては、すべてのユーザーが何らかのファミリーに属し、そのファミリーでペットを管理するという形にしたい。
しかし、ファミリーにユーザーが所属するという形にすれば、1ファミリーに1ユーザーしか所属することができません。
そのため、中間テーブルであるグループを設け、そこにファミリー情報を保存する形にします。
手順は4つです。
①ユーザーの作成
②ファミリーの作成
③中間テーブルの作成
④所属家族の情報を持った招待機能の作成
⑤ペットの作成

順に実装していきましょう。

# ①ユーザーの作成
ユーザーに関しては、deviseで作成します。
非常に長くなってしまうことを考え、別ページに記載しました。
詳しくは、[こちら](https://qiita.com/tak8_al/items/265b080674456398d56e)をクリックしてください。

# ②ファミリーの作成
![スクリーンショット 2022-09-12 午前9.58.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/5ea6183f-9cb5-efec-bd7d-ee0df6ec5c95.png)

ユーザーの所属先がわかるようにしたいため、ファミリーに必要なカラムは、nameのみです。
ファミリーの作成は、Scaffoldというジェネレータを利用します。
Scaffold機能を使うことで、アプリケーションに必要なMVCを自動で生成できます。
```:terminal
$ rails g scaffold Family name:string
```

# ③中間テーブルの作成
中間テーブルに関してしたいことは2つです。
* 中間テーブルの作成
* 中間テーブルにファミリーを作成した際、ユーザーとファミリーの情報を保存すること。
* ユーザーページにファミリー名の表示

## 中間テーブルの作成
![スクリーンショット 2022-09-12 午前9.59.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/e333ee0e-a2a2-3481-3dd4-d2ed812ec2b6.png)

中間テーブルはデータを保存するだけのものなので、モデルだけあれば良いです。
以下のコマンドを実行してください。
```:terminal
$ rails g model Group family:references user:references 
```
実行が完了したら、rails db:migrateをしましょう。

## 中間テーブルにファミリーを作成した際、ユーザーとファミリーの情報を保存すること。
やる工程は2つ。
* アソシエーションメソッドを定義
* コントローラーの変更

### アソシエーションメソッドを定義
中間テーブルにユーザーとファミリーの情報を保存できるようにするためには、アソシエーションの定義をする必要があります。
本来であれば、group.rb, family.rb, user.rbにおいて、アソシエーションを定義する必要がありました。
しかし、先程のコマンドによって、group.rbにおける定義は済んでいます。
残りは、user.rbとfamily.rbです。

user.rb, family.rbそれぞれのモデルに、以下を追記してください。
```ruby:app/models/user.rb
    has_many :groups, dependent: :destroy
    has_many :families, through: :groups, source: :family
```
```ruby:app/models/family.rb
    has_many :groups, dependent: :destroy
    has_many :users, through: :groups, source: :user
```
dependent:destroyは親モデルを削除する際に、その親モデルに紐づく「子モデル」も一緒に削除できるようにするものです。

これでアソシエーションの定義は完了です。

### コントローラーの変更
コントローラーを変更し、保存する際に中間テーブルを通じて保存するよう設定します。
コントローラーのcreateメソッドを下記に変更してください。
```ruby:app/controllers/families_controller.rb
  def create
    current_user.families.build(family_params)
    # アソシエーションの親要素を保存すると子要素も保存される。
    if current_user.save
      redirect_to user_path(current_user), notice: "作成しました"
    else
      render 'new'
    end
  end
```
これで、作成した家族名が、中間テーブルに保存されるようになりました。

# ④所属家族の情報を持った招待機能の作成
工程は4つです。
* 招待機能の導入
* 招待メールを送る際、ログインしているユーザーのファミリーのパラメーターを保持した状態で送信する実装
* 招待されたユーザーがアカウントを作る際、中間テーブルを通じて保存する実装
* 導入確認

## 招待機能の導入
簡潔にするため、別ページに記載しました。
[こちら](https://qiita.com/tak8_al/items/3b8a0bf506ce3b03576c)をご覧ください。

## 招待メールを送る際、ログインしているユーザーのファミリーのパラメーターを保持した状態で送信する実装
招待メールの送信のボタンを押した際、ファミリーパラメーターも送信できるようにしたいため、deviseのnew.html.erbファイルに追記する。

```erb:app/views/devise/new.html.erb
    <% resource.class.invite_key_fields.each do |field| -%>
      <div class="field">
        <%= f.label field %><br />
        <%= f.text_field field %>
       
        <%= f.hidden_field :family_id, :value => current_user.families.first.id %> #追記

      </div>
    <% end -%>
```
ストロングパラメーターにもfamily_idを追記し、DBに保存できるようにしてあげます。

```ruby:app/controllers/application_controller.rb
    def configure_permitted_parameters
        devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :family_id]) 
        devise_parameter_sanitizer.permit(:invite) { |u| u.permit(:email, :name, :family_id) }
        devise_parameter_sanitizer.permit(:accept_invitation) { |u| u.permit(:password, :password_confirmation, :invitation_token, :name, family_id) }
    end
```
このままだと、"unknown attribute 'family_id' for User."というエラーが出ます。
user.rbに下記を追記してください。
```ruby:app/models/user.rb
class User < ApplicationRecord
  devise  :invitable, :database_authenticatable, :registerable,
          :recoverable, :rememberable, :validatable

  has_many :groups, dependent: :destroy
  has_many :families, through: :groups, source: :family
  #以下を追記
  attr_accessor :family_id
end
```

## 招待されたユーザーがアカウントを作る際、中間テーブルを通じて保存する実装
usersフォルダを作成し、その直下にinvitations_controller.rbを作成、以下を追記します。
```ruby:app/models/user.rb
class Users::InvitationsController < Devise::InvitationsController
    def create
        super
        @group = Group.new
        #Groupテーブルに招待したいユーザーのidを入れる。
        @group.user_id = User.find_by(email: params[:user][:email]).id
        #paramsの構造として、ユーザーの中にfamily_idがあるので、このような形でかく。
        @group.family_id = params[:user][:family_id]
        @group.save
    end
end
```
今作成したコントローラーを参照するようにルーティングを設定します。
```config/routes.rb
Rails.application.routes.draw do
  resources :families
  resources :tasks

  root to: "tasks#index"
  #ここから追記
  devise_for :users, controllers: {
    invitations: 'users/invitations'
  }
  #ここまで追記
  resources :users, only: :show

end
```
この時、必ずresources :usersよりも上に追記してください。
下に追記してしまうとsign_inやsign_upをidだと認識してしまい、新規登録や、ログインができなくなってしまいます。

## 導入確認
導入確認のため、ユーザーページにファミリー名の表示させます。
show.html.erbに以下を追記してください。
```erb:app/users/show.html.erb
<h2>Users</h2>
<hr />

<p>
    <strong>ユーザー名</strong>
    <%= @user.name %>
</p>
<p>
    <strong>e-mail</strong>
    <%= @user.email %>
</p>
# ここから追記
#今ログインしているユーザーにすでに所属しているファミリーがいれば、その家族名を表示。
<% if current_user.families.first.present? %>
<p>
    <strong>家族名</strong>
    <%= current_user.families.first.name %>
</p>
#そうでなければ、ファミリー作成ページへと飛ばす
<% else %>
<%= link_to "ファミリーに所属する", new_family_path%><br>
<% end %>
# ここまで追記
```
これで工程はすべて完了です。
招待メールを送り、招待されたメールアドレスでユーザを作成してみてください。
ユーザーページに招待メールを送ったユーザーが所属している家族名が表示されていたら成功です！

# ペットの作成
ここで実現したいことは、集団で管理しているペットの情報の閲覧です。
実装手順は2つ。
①ペットのCRUD機能の実装
②アソシエーション定義
③集団で管理しているペットのみの表示

## ①ペットのCRUD機能の実装
ペットで作成するカラムは以下の3つです。
![スクリーンショット 2022-09-08 午後8.48.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/5ddcc690-3230-47c5-c2b2-1cadcf16e709.png)
下記のコマンドを実行してください。
```:terminal
$ rails g scaffold Pet name:string image:text content:text family:references 
```
生成が完了したらrails db:migrateを実行します。
実行が完了したら、CRUD機能の完成です。

試しに、ペットを登録してみましょう。
![スクリーンショット 2022-09-08 午後8.53.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973643/4f0880d6-97c8-e963-b546-ea6e3e5e0617.png)

imageが選択式ではなく、記述式になっています。
imageを選択し、所有する写真からアップできるようにしたいです。

また、familyの欄は必要ないので削除しましょう。

### 写真アップロード機能の実装
工程は7つです。
* ImageMagickのインストール
* CarrierWaveのインストール
* アップローダーの作成
* モデルに、carrierwave用の設定を行う
* フォームの形式の変更
* 画像表示に対応させる
* ストロングパラメータの設定しておく

#### ImageMagickのインストール
下記のコマンドを実行します。
```:terminal
$ brew install imagemagick
```

#### CarrierWaveのインストール
Gemfileに以下を追記します。
```:Gemfile
gem 'carrierwave'
```
bundle installを実行して、Gemをインストールしましょう。

#### アップローダーの作成
以下のコマンドを実行してください。
```:terminal
$ rails g uploader Image
```
#### モデルに、carrierwave用の設定を行う
画像のアップロード先のモデルに以下を追記します。
```ruby:app/models/pet.rb
class Pet < ApplicationRecord
  belongs_to :family
  mount_uploader :image, ImageUploader
end
```
#### フォームの形式の変更
_form.html.erbを開くと、以下のようになっています。
```erb:app/views/pets/_form.html.erb(変更前)
<div class="field">
  <%= form.label :image %>
  <%= form.text_area :image %>
</div>
```
imageカラム のフォームが text_area になっています。
これがさっきファイルを選択し、画像をアップロードできなかった原因です。

以下に変更しましょう。
```erb:app/views/pets/_form.html.erb
<div class="field">
  <%= form.label :image %>
  <%= form.file_field :image %>
</div>
```

#### 画像表示に対応させる
show.html.erbのファイルを見てみます。
```erb:app/views/pets/show.html.erb
<p>
  <strong>Image:</strong>
  <%= @pet.image.url %>
</p>
```
imageカラムのは画像のパスが表示させるため、このままでは画像のパスが表示されます。
表示させたいので画像自体ですので、以下のように変更しましょう。

```erb:app/views/pets/show.html.erb
<p>
  <strong>Image:</strong>
  <%= image_tag @pet.image.url %>
</p>
```

これで愛犬の画像をアップロードできるようになりました。

## ②アソシエーション定義
ペットを家族に所属させるため、アソシエーション定義をします。
工程は2つあります。
* アソシエーション定義
* ペット作成時、ファミリーのパラメーターの付与

### アソシエーション定義
ペットのモデルのアソシエーション定義は完了していますが、ファミリー側の定義がまだ完了していません。
以下のように追記しましょう。
```ruby:app/models/pet.rb
has_many :pets
```
### ペット作成時、ファミリーのパラメーターの付与
family_idのカラムに送るデータを付与してあげましょう。
ニューファイルに、以下のコードを追記してください。

```erb:app/views/pets/_form.html.erb
<%= form.hidden_field :family_id, :value => current_user.families.first.id %>
```
これで、ペットを作成時、家族の情報を持ったペットの情報が作成できます。

## ③集団で管理しているペットのみの表示
index.html.erbに以下の追記をしましょう。
```erb:app/views/pets/index.html.erb
    <% @pets.each do |pet| %>
      <tr>
        #ここから追記
        <% if pet.family.id == current_user.families.first.id %>
        <td><%= pet.name %></td>
        <% end %>
        #ここまで追記
      </tr>
    <% end %>
```
これで集団で管理しているペットのみ表示されるようになりました。

# 終わりに
お疲れ様です！
ここまで本当にお疲れ様でした！笑
part2では、ペットに関するタスク機能を実装します。
ぜひご覧あれ！