Rails4系でごく単純な　1対多の実装を行ったが、留意点がいっぱい

フレームワークというものは、芋づる式に設定がでてくるものだ・・・
それ言ってよー。といった気分になる内容が多数存在したが、

# やりたいこと


- 表示させたい対象が存在する
- 対象には、その内容を説明するタグが０〜複数存在する
- 対象の作成、編集画面で編集できる

# 実現方法

最低限これだけできればいい

- とりあえず、対象１に対しタグが多数存在するモデル構造
- タグ名は直接記入する
- とりあえず今は、対象１つに対して、最大５個のタグをつけられること

# 実装の前に

## 環境

- Rails 4.2.5 (Bundle Installしたもの）
- ruby 2.2.3 （rbenv で選択中）

## 注意事項

- タグをつける対象のモデルは page という名前であり、すでに作成されています。
- bundle exec rails ~ とかありますが、このRailsプロジェクト用に用意したgem使っているという意味です。


# まずはタグ用のモデルを用意する

```bash:terminal
$ bundle exec rails g model extag page:references label:string
```

ここでは、page:references というところに注目。
参照用の設定記述が作成されます。

## マイグレーションファイルはこんな感じに

```ruby:migrate
class CreateExtags < ActiveRecord::Migration
  def change
    create_table :extags do |t|
      t.references :page, index: true, foreign_key: true
      t.string :label

      t.timestamps null: false
    end
  end
end
```

t.references :page, index: true, foreign_key: true

とか、ちょっと見慣れない記述が出てきました。

## マイグレーションを実施し、タグ用テーブルを生成すると・・・

データベースを更新します。

```bash:terminal
$ bundle exec rake db:migrate
```

とかやると

```bash:terminal
|id|page_id|label|created_at|updated_at|
```
とかいうカラムを持ったテーブルが作成されます。

日付部分の説明は省略しますが

- id 　　　　　このテーブル内のデータに割りふられる固有のID
- page_id 　　関連するモデルpageの項目に紐づく
- label　　　 タグの名前

といった内容。注目すべきはpage_id 

```bash:terminal
|id|page_id|label|created_at|updated_at|
|1 |      3|hoge | （省略）   | （省略）  |
```

みたいな場合、モデルpageのid=3に保存されているレコードに　タグ名"hoge" が
関連付けられていることになります。

# モデルの改修

## page（１対多の１の方）

下記のように記述を追加します。


```ruby:page.rb
#extag とも関連付けあり
	has_many :extags, dependent: :destroy
	#reject_ifで登録しない条件をつける必要あり
	accepts_nested_attributes_for :extags, allow_destroy: true , reject_if: :chk_reject

	def chk_reject(attributed)
		attributed['label'].blank?
	end
```

accepts_nested_attributes_for　に関しては
pageと関連し　ettag（タグ）を操作する。というの意味合い。

検索するか、ここでも参考に
http://blog.livedoor.jp/sasata299/archives/51931176.html

### reject_if とは何者か？

ネット上の記述を見ると、reject_if以下のメソッドなどで指定した条件が真の時、そのレコードを追加しないという意味になる。

上記の例では、chk_rejectメソッドの返す値が真になる、つまり、chk_rejectメソッドの判定がtrueになる、labelの値が空白の時には登録しないということになる。



参考；ここの付随する機能あたり。
http://o.inchiki.jp/obbr/81

## extag （１対多の多の方）

rails generate が空気・・・いや命令を読んで、 belongs_to page
（このモデルは pageモデルに所属する（多対１の関連になる）と最初から記述しています。

```
class Extag < ActiveRecord::Base
  belongs_to :page
end

```

# コントローラの改修

修正が必要になるところだけ抜粋。
具体的には

- new（新規にデータを作成）
- edit（既存データを修正）
- page_params（StrongParameter：DBに書き込む前などのチェック）

に修正を加える。　


## まずはnewの方から

```ruby:pages_controller.rb
  # GET /pages/new
  def new
    @page = Page.new
   #追記　５つ分だけ関連付けが用意される。
    5.times{
      @page.extags.build
    }
  end
```

まんま、５つ分だけextagの要素が作成されます。

## edit について（すでに登録されているタグ数に応じて項目追加を行う）

```ruby:pages_controller.rb
  def edit
    # 各ページについて、インスタンス変数を生成する。
    @page = set_page
    # 登録数をカウントする。
    tag_count = Extag.where(page_id: params[:id] ).count
    
    # 登録されているタグが５つ未満なら、不足分だけ要素を追加する。
    if tag_count < 5 
      (5 - tag_count ).times{
        @page.extags.build
      }
    end
  end
```


## params（ StrongParameter ) の箇所を修正

```ruby:pages_controller.rb
# Never trust parameters from the scary internet, only allow the white list through.
    def page_params
     #extagsのlabel項目も許可するようにする
     #params.require(:page).permit(:name, :view, :desc)
      params.require(:page).permit(:name, :view, :desc, extags_attributes: [:label])
    end

```

ここでの記述は
(belong_toするほうのモデル）_attributes: [:(項目１）,:（項目２）・・・]
ということになる。


## ビューの改修（正確には　_formの部分だけど）

今回は編集処理をするときのフォームを改修する。
なお、関係あるところだけ抜粋します。
ここに関しては失敗版と同じ。

```rails:_form.html.erb
（前略）
  <div class="field">
    <%= f.label :desc %><br>
    <%= f.text_field :desc %>
  </div>
  <div class="field">
    <%= f.label :extags , "直接タグ名（最大５個）" %><br>
      <%= f.fields_for :extags do |et| %>
        <%= et.text_field :label %>
      <% end %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
（以下略）
```

単に、fileds_for でテキストタイプのフォームを生成しています。

Railsドキュメントによる説明はこちら
http://railsdoc.com/references/fields_for

＃ （追加）参考
20141208
Railsでaccepts_nested_attributes_forとfields_forを使ってhas_many関連の子レコードを作成/更新するフォームを作成

http://ruby-rails.hatenadiary.com/entry/20141208/1418018874

### reject_if の処理内容を新規登録で解説

実際に登録した時のログをもとに解説する。
このケースではextags_attributes の中に入れ子になっているハッシュのlabel内の中身が空かどうかを判定している。

```bash:terminal
"page"=>{"name"=>"５", "view"=>"1", "desc"=>"５", "extags_attributes"=>{"0"=>{"label"=>"tag1"}, "1"=>{"label"=>"tag2"}, "2"=>{"label"=>""}, "3"=>{"label"=>""}, "4"=>{"label"=>""}}}, "commit"=>"Create Page"}
```

ここでは、"0"と"1"のみlabelの値が入っているので
それぞれ新規登録されることになる。

```bash:terminal
  SQL (0.3ms)  INSERT INTO "extags" ("label", "page_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["label", "tag1"], ["page_id", 5], ["created_at", "2016-01-03 13:49:28.024254"], ["updated_at", "2016-01-03 13:49:28.024254"]]
  SQL (0.1ms)  INSERT INTO "extags" ("label", "page_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["label", "tag2"], ["page_id", 5], ["created_at", "2016-01-03 13:49:28.026350"], ["updated_at", "2016-01-03 13:49:28.026350"]]
```