【超かんたん】Active Hashで投稿ページにプルダウンメニューを作成しよう！

Active Hashを利用して投稿ページにプルダウンメニューを作成します。
超初心者向けにレシピ投稿アプリを例に作成していきます。
#####完成イメージ
![7c1aa72d8269b4942f50794dc40e4b85.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/a691cff2-2720-8a99-1046-0b2e96b42d5b.gif)


## Active Hashとは
Active Hashとは、「基本的に変更されないデータ」をモデルファイルに直接記述し取り扱うことができるGem。[公式ドキュメント](https://github.com/zilkey/active_hash)

https://github.com/zilkey/active_hash

##Active Hashの導入
####Active Hashのインストール
Gemfileに下記を記述しbundle installします。

```:Gemfile
gem 'active_hash'
```

####Recipeモデルの作成
今回はレシピ投稿アプリなのでRecipeモデルを作成します。

```:ターミナル
rails g model recipe
```
マイグレーションファイルを編集。
今回、Active Hashを利用してカテゴリー(categoty)と所要時間(time_required)を保存するので、**integer型**の**:モデル名_id**という形で記述します。
このあと作成するCategoryモデルとTimeRequiredモデルのidを外部キーとして管理するためです。

```ruby:db/migrate/20XXXXXXXXXXXX_create_recipes.rb
class CreateRecipes < ActiveRecord::Migration[6.0]
  def change
    create_table :recipes do |t|
    #ここから
      t.string :title,          null: false 
      t.text :text,             null: false 
      t.integer :category_id,      null:false 
      t.integer :time_required_id, null: false 
    #ここまで
      t.timestamps
    end
  end
end
```
```:ターミナル
rails db:migrate
```

先ほど、integer型で指定したカテゴリーと所要時間の中身を作成していきましょう。

####Category、TimeRequiredモデルの作成
モデルファイルの作成

```:ターミナル
touch app/models/category.rb
```

Active Hashを用いて作成するモデルは**ActiveHash::Baseクラスを継承**します。
モデルファイルに以下のような形でプルダウンメニューに表示させたいデータをハッシュの中に記述していきましょう。

```ruby:app/models/category.rb
class Category < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: 'すし・魚料理'},
    {id: 2 , name: '丼もの・揚げ物'},
    {id: 3 , name: 'ラーメン・麺類'},
    {id: 4 , name: '中華'},
    {id: 5 , name: '焼きもの・粉もの'},
    {id: 6 , name: '洋食・西洋料理'},
    {id: 7 , name: 'イタリアン'},
    {id: 8 , name: 'フレンチ'},
    {id: 9 , name: 'アジア・エスニック'},
    {id: 10 , name: 'お菓子・スイーツ'}
  ]
end
```

同じ要領でTimeRequiredモデルも作成していきます。

```:ターミナル
touch app/models/time_required.rb
```
※モデル名はアッパーキャメルケースで記述します。

```ruby:app/models/time_required.rb
class TimeRequired < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: '10分以内'},
    {id: 2 , name: '10分〜20分'},
    {id: 3 , name: '20分〜30分'},
    {id: 4 , name: '30分〜45分'},
    {id: 5 , name: '45分〜60分'},
    {id: 6 , name: '60分以上'}
  ]
end
```

##アソシエーションの設定

####Recipeモデルのアソシエーションの設定
投稿するレシピ(Recipe)はひとつのカテゴリー(Category)と所要時間(TimeRequired)に紐づくので**belongs_to**を設定します。
また、Active Hashを用いて、belongs_toを設定するには、**extend ActiveHash::Associations::ActiveRecordExtensions**と記述してモジュールを取り込みます。

```ruby:app/models/recipe.rb
class Recipe < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :category
  belongs_to :time_required
end
```

####Category、TimeRequiredモデルのアソシエーションの設定
カテゴリー(Category)と所要時間(TimeRequired)はたくさんのレシピ(Recipe)に紐付いているので**has_many**を設定します。
また、Active Hashを用いて、has_manyを設定するには、**include ActiveHash::Associations**と記述してモジュールを取り込みます。

```ruby:app/models/categoty.rb
class Category < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: 'すし・魚料理'},
    {id: 2 , name: '丼もの・揚げ物'},
    {id: 3 , name: 'ラーメン・麺類'},
    {id: 4 , name: '中華'},
    {id: 5 , name: '焼きもの・粉もの'},
    {id: 6 , name: '洋食・西洋料理'},
    {id: 7 , name: 'イタリアン'},
    {id: 8 , name: 'フレンチ'},
    {id: 9 , name: 'アジア・エスニック'},
    {id: 10 , name: 'お菓子・スイーツ'}
  ]
#以下追記
  include ActiveHash::Associations 
  has_many :recipes
end
```

```ruby:app/models/time_required.rb
class TimeRequired < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: '10分以内'},
    {id: 2 , name: '10分〜20分'},
    {id: 3 , name: '20分〜30分'},
    {id: 4 , name: '30分〜45分'},
    {id: 5 , name: '45分〜60分'},
    {id: 6 , name: '60分以上'}
  ]
#以下追記
  include ActiveHash::Associations 
  has_many :recipes
end
```

##バリデーションの設定

**presence: true**　　空データは登録できない
**numericality**　　　数値のみを許可する
**{ other_than: 0 }**　numericalityのオプション、0以外を保存
先ほど、Active Hashを用いて作成したモデルのid: 0には'---'とデータが入っていないので0以外を保存するということになります。

```ruby:app/models/recipe.rb
class Recipe < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :category
  belongs_to :time_required

#以下追記
  validates :title, presence: true
  validates :text, presence: true
  validates :category_id, numericality: { other_than: 0 }
  validates :time_required_id, numericality: { other_than: 0 }
end
```

##ビューの作成
####コントローラー、ビューファイルの作成

```:ターミナル
rails g controller recipes new
```
コントローラーに以下を記述。

```ruby:app/controllers/recipes_controller.rb
class RecipesController < ApplicationController

  def index
  end

  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.new(recipe_params)
    if @recipe.save
      redirect_to root_path
    else
      render :new
    end
  end

  private
  def recipe_params
    params.require(:recipe).permit(:title, :text, :category_id, :time_required_id)
  end
end
```

####ルーティングの設定

```ruby:config/routes.rb
Rails.application.routes.draw do
  root to: 'recipes#index'
  resources :recipes, only: [:index, :new, :create]
end
```

####ビューファイルの編集
一部、Bootstrapを使用しております。
Bootstapについての記事も投稿いているので[こちら](https://qiita.com/mitaninjin/items/93553535483919d17a81)を参照してくだい

https://qiita.com/mitaninjin/items/93553535483919d17a81

Active Hashで作成したデータを表示させるには**[collection_select](https://railsdoc.com/page/collection_select)**というメソッドを使用します。
collection_selectは、下記のような順番で記述します。

```erb:例
<%= form.collection_select(保存するカラム名, オブジェクトの配列, カラムに保存する項目, 選択肢に表示するカラム名, オプション, htmlオプション) %>
```

先ほど作成したCategoryモデルだと下記のような記述になります。

```erb:例
<%= f.collection_select(:category_id, Category.all, :id, :name, {}, {class:"category"}) %>
```

第５引数のオプションは先頭に値のない選択肢を表示するinclude_blankなどがあります。
今回はid: 0 に'---'を指定しているので空にしてあります。
第５引数、第６引数については[Railsドキュメント](https://railsdoc.com/page/collection_select)を参照してください。
<br>

それではビューファイルに記述していきましょう。

```erb:app/views/recipes/new.html.erb
<div class="recipe-form ">
  <h1 class="text-center">レシピを投稿する</h1>

  <%= form_with model: @recipe, local: true do |f| %>
    <div class="form-group">
      <label class="text-secondary">料理名</label><br />
      <%= f.text_field :title, class: "form-control"%>
    </div>
    <div class="form-group">
      <label class="text-secondary">カテゴリー</label><br />
      <%= f.collection_select(:category_id, Category.all, :id, :name, {}, {class:"category"}) %>
    </div>
    <div class="form-group">
      <label class="text-secondary">所要時間</label><br />
      <%= f.collection_select(:time_required_id, TimeRequired.all, :id, :name, {}, {class:"time"}) %>
    </div>
    <div class="form-group">
      <label class="text-secondary">作り方</label><br />
      <%= f.text_area :text, class: "form-control"%>
    </div>
    <div class="actions">
      <%= f.submit "投稿", class: "btn btn-primary" %>
    </div>
  <% end %>
</div>
```
最後にCSSを整えます。

```scss
.recipe-form{
  width: 500px;
  margin: 0 auto;
  margin-top: 40px;
}
```

これで完成になります。
では、実際に投稿できるか確認してみましょう。

![7c1aa72d8269b4942f50794dc40e4b85.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/a691cff2-2720-8a99-1046-0b2e96b42d5b.gif)

しっかり保存されいています。
![b7198fb1f8a26d99aea09fb5063a1e60.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140333/b5a498be-6cb9-1ad5-cc0a-e3c91f448360.png)