# Model と画面上の form が1対1で一致しない場合、どのように実装するのが綺麗なのか？

**URL:** <https://discourse.clean-rails.org/t/model-form-1-1/14>\
**Category:** Uncategorized\
**Created:** [2018 年 3 月 19 日午後 2:24 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14 "2018-03-19T14:24:33Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2018 年 3 月 19 日午後 2:24 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/1 "2018-03-19T14:24:33Z")

</div>

[Railsのきれいなコードのお題案 · Issue #1 · clean-rails-ja/conversation](https://github.com/clean-rails-ja/conversation/issues/1#issuecomment-353373429) からの転載。

## 例

ブログにカテゴリーを複数つけて投稿する機能。  
[はてなブログ](http://hatenablog.com/)の投稿画面がイメージに近いです。

 ![blog_img](https://clean-rails.s3.dualstack.ap-northeast-1.amazonaws.com/original/1X/707b5e9dcdd576439674cc48d2c97c39403a2dd7.png)

おそらく、モデルは下記のように複数のモデルになると思います。

```rb
# app/models/article.rb
class Article < ApplicationRecord
  has_many :categories
end

# app/models/category.rb
class Category < ApplicationRecord
  belongs_to :article
end

```

## 実装方法の例

下記のような方法があると思いますが、それぞれメリット・デメリットがあると思う。

- [accepts\_nested\_attributes\_for](http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)
- Form オブジェクト
- Service クラス（Form オブジェクトとほぼ一緒？）

私が知らないだけで、他にも良い実装方法はあるかも。

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2018 年 3 月 19 日午後 2:29 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/2 "2018-03-19T14:29:10Z")

</div>

[[WIP]Model と画面上の form が1対1で一致しない場合、どのように実装するのが綺麗なのか？ · Issue #2 · clean-rails-ja/conversation](https://github.com/clean-rails-ja/conversation/issues/2#issuecomment-362769810)からの転載

この例、厳密に考えるとArticleとCategoryは多対多な気がするけど、面倒なのでいったん1対多で考えますね。

accepts\_nested\_attributes\_for の例

```ruby
class Article < ApplicationRecord
  has_many :categories
  accepts_nested_attributes_for :categories, allow_destroy: true, reject_if: :all_blank
end

class Category < ApplicationRecord
  belongs_to :article
end

class ArticlesController < ApplicationController
  # ...
  def create
    # ...
  end

  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to article_path(@article), notice: '更新しました'
    else
      render :edit      
    end
  end

  def article_params
    params.require(:article).permit(:title, :body, categories_attributes: [:id, :name, :_destroy])
  end
end

```

```auto
<%= form_with @article do |f| %>
    <%= f.text_field :title %>
    <%= f.text_area :body %>
    <%= f.field_for :categories |g| %>
      <%= g.text_field :name %>
      削除する <%= g.check_box :_destroy %>
    <% end %>
  <% f.submit %>
<% end %>

```

## メリット

- 全部自分で書くのと比べてコード量が格段に減る

## デメリット

- 覚えづらい、ハマりやすいインターフェース
  - `reject_if`と`_destroy`
  - `accepts_nested_attributes_for`を定義するとfields\_forの挙動が変わる
    - name属性がnested attributes仕様に変わる

- モデルの定義とビューの定義が一体化される
- レールから外れたいときに大変
  - 保存時にbelongs\_toのバリデーションが走ってN+1になってしまう、みたいなケースがあった気がする

---

<div class="post-metadata">

**Author:** ![elcondor](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/elcondor/32/905_2.png) [@elcondor](https://discourse.clean-rails.org/u/elcondor)\
**Post date:** [2018 年 3 月 22 日午前 2:22 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/3 "2018-03-22T02:22:46Z")

</div>

- ライフサイクル的に親に従属して単独更新もほぼない、というケースであればaccepts\_nested\_attributes\_for一択ですね。お題のはてなブログのカテゴリのケースだと、右側のカテゴリはAjaxか何かで単独更新にした上で、articles\_categoriesまでをaccepts\_nested\_attributes\_forにすると思います。
- 単に複数モデルの同時更新のようなものであれば、Formオブジェクトを作った上で、accepts\_nested\_attributes\_for互換のアクセサなど作る方向にすると思います。これをFormと呼ぶかServiceと呼ぶかは、一括更新するのが「ビジネスロジックとして整合性を担保する必然性がある」かどうかによるかなと。

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2018 年 3 月 22 日午後 2:57 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/4 "2018-03-22T14:57:00Z")

</div>

nested attributes嫌いな人の意見も聞いてみたいですね。僕はわかって使う分にはいいんじゃないのか派。

↓こういうバッドノウハウができてしまうのはちょっと微妙だなという気はするけども。

[nested attributes なレコードを、特定の属性が空の時に削除する - おもしろwebサービス開発日記](http://blog.willnet.in/entry/2015/07/25/174044)

---

<div class="post-metadata">

**Author:** ![elcondor](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/elcondor/32/905_2.png) [@elcondor](https://discourse.clean-rails.org/u/elcondor)\
**Post date:** [2018 年 3 月 22 日午後 4:18 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/5 "2018-03-22T16:18:01Z")

</div>

ちょっとトピックから外れますが、上記のような場合は\_destroy: 1を設定するのではなく、mark\_for\_destructionを使うのが好みであったりはします(起きる現象は同じですが\_desrtroy: 1は隠し属性的でよろしくないなーと)

---

<div class="post-metadata">

**Author:** ![oakbow](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/oakbow/32/894_2.png) [@oakbow](https://discourse.clean-rails.org/u/oakbow)\
**Post date:** [2018 年 4 月 15 日午後 6:28 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/6 "2018-04-15T18:28:50Z")

</div>

基本方針は@elcondorさんと同じかなあ。  
ただ、フォームオブジェクトを使う場合、私はサービスと併用したりしています。  
フォームオブジェクトにはあくまでユーザ入力の取り扱いのみ（具体的にはヴァリデーションとかその辺）をお願いし、その後のビジネスロジックはサービスで実装する、という形を取っていたり。

いつもそうするというわけではありませんし、フォームオブジェクトで通常のモデルでやるように振る舞いまで実装してしまっても構わないかなとは思います。  
同様の処理をフォームオブジェクトを経由せずに行う処理（例えばバッチ処理やAPI経由の処理）にしたい場合、フォームオブジェクトを共有するのは違和感があるので、ビジネスロジック（サービス）とビューに依存する処理（フォームオブジェクト）に分けてる感じです。  
この辺みんなどうしてるのかな？

---

<div class="post-metadata">

**Author:** ![tkawa](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/tkawa/32/900_2.png) [@tkawa](https://discourse.clean-rails.org/u/tkawa)\
**Post date:** [2018 年 4 月 16 日午前 4:12 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/7 "2018-04-16T04:12:34Z")

</div>

単純な複数選択の場合には、こういうのも好きです。

```ruby
# class Article
def category_names
  categories.map(&:name)
end
def category_names=(names)
  self.categories = names.select(&:present?).map { |name| Category.find_or_initialize_by(name: name) }
end

```

ビューはselect2みたいな感じで。`category_names[]` を複数送ればOK。

---

<div class="post-metadata">

**Author:** ![joker1007](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/joker1007/32/902_2.png) [@joker1007](https://discourse.clean-rails.org/u/joker1007)\
**Post date:** [2018 年 4 月 16 日午前 7:48 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/8 "2018-04-16T07:48:10Z")

</div>

accepts\_nested\_attributesは滅びるべきAPIだと思ってるので、自分なら絶対使いません。  
大きな理由は2点かな。

- あれはActiveRecordのAPIでPOROに使えないので、ARのカラム構造と現実が合わなくなった時や付随する処理でPOROが必要になった時に調整で苦労する可能性が高い
- ReactやVueの様なJSON構造と表示のbindingと相性が悪い(名前付けのルールとかdestroyフラグとか)
- この手の複数選択系のフォームはまともなUIで作るならJSでの項目の増減や個別の編集フォーム等はほぼ必須で、今時そういうUIをRailsのform helperをベースに作ることはほぼ無いので、記述削減のメリットが無い。

基本はFormオブジェクトで、シンプルに一要素だけでマッピング可能であるならtkawaさんのスタイルも使いそうです。

---

<div class="post-metadata">

**Author:** ![koheisg](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/koheisg/32/884_2.png) [@koheisg](https://discourse.clean-rails.org/u/koheisg)\
**Post date:** [2018 年 4 月 17 日午前 5:26 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/9 "2018-04-17T05:26:56Z")

</div>

本当にこういうAPIが出てきたら面白そうだなぁと思っています

> <https://gist.github.com/dhh/b0a106a00e8737280666>

---

<div class="post-metadata">

**Author:** ![hanachin](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/hanachin/32/897_2.png) [@hanachin](https://discourse.clean-rails.org/u/hanachin)\
**Post date:** [2018 年 4 月 17 日午後 5:07 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/10 "2018-04-17T17:07:42Z")

</div>

`accepts_nested_attributes_for`この辺のコメントを読む限り新規に開発する部分ではあまり使いたくないですね(バージョン上げる際の負債になりそうなので)

> dhh on 15 Nov 2016 Owner  
> I’d actually like to kill accepts\_nested\_attributes\_for in due time. Don’t think we should promote it for this new API. Rather, let’s just show how to do it by hand in the controller.  
> [https://github.com/rails/rails/pull/26976#discussion\_r87855694](https://github.com/rails/rails/pull/26976#discussion_r87855694)

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2018 年 4 月 22 日午後 3:36 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/11 "2018-04-22T15:36:03Z")

</div>

Formオブジェクトにするのは僕も賛成なのですが、お仕事だとFormオブジェクトの作り方を伝授する暇がなかったり、POROのFormオブジェクトだと記述量がそれなりに増えてしまうという理由で`accepts_nested_attributes_for`を許容する機会がわりとあります。

Formオブジェクトで実装する場合、POROで実装しますか？それともなんらかのライブラリを使います？

---

<div class="post-metadata">

**Author:** ![koheisg](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/koheisg/32/884_2.png) [@koheisg](https://discourse.clean-rails.org/u/koheisg)\
**Post date:** [2018 年 4 月 23 日午前 10:35 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/12 "2018-04-23T10:35:33Z")

</div>

> Formオブジェクトで実装する場合、POROで実装しますか？それともなんらかのライブラリを使います？

activemodelですかね。。

---

<div class="post-metadata">

**Author:** ![hshimoyama](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/hshimoyama/32/893_2.png) [@hshimoyama](https://discourse.clean-rails.org/u/hshimoyama)\
**Post date:** [2018 年 4 月 24 日午前 2:45 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/13 "2018-04-24T02:45:31Z")

</div>

同じく ActiveModel ですね…最近はこんなクラスを用意して使ってます。

```auto
class ApplicationModel
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::Validations
end

```

---

<div class="post-metadata">

**Author:** ![ttanimichi](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/ttanimichi/32/628_2.png) [@ttanimichi](https://discourse.clean-rails.org/u/ttanimichi)\
**Post date:** [2018 年 5 月 9 日午前 6:37 UTC](https://discourse.clean-rails.org/t/model-form-1-1/14/14 "2018-05-09T06:37:14Z")

</div>

同じく ActiveModel で、

> class ApplicationModel

これを個人的に「ApplicationModel パターン」と呼んでいますｗ

```auto
$ bin/rails g model HogeForm --parent ApplicationModel

```

ってするとモデルが生成できるので便利（Qiita か何かに書こうかな…）

あと、ActiveModel にすることで AR::Base なクラスとインターフェイスが揃うのが良いですね
