RailsでActive Interactionを使う

#はじめに
###注意書き
未経験初心者Rubyistがメモ同様の記事を書いているものなので間違っている部分やもっといい書き方の出来る部分があります（~~確信してます~~）．
がっつりまさかり投げてください．勉強させていただきます．

###何故記事をかいたのか
ActiveInteractionを勧められて使ってみて良さをひしひしと実感している今日此の頃，ふとQiitaでActiveInteractionの記事を検索してみると全く無かったのでこれは勧める他ねえと啓示を頂いたため．

#ActiveInteractionとは

[Github](https://github.com/AaronLasseigne/active_interaction)
[RubyGems](https://rubygems.org/gems/active_interaction/versions/3.5.2)

commandパターンを利用してコードをめちゃくちゃスッキリさせられる．すごい
名前空間を使えるので`controller/function` の形でファイルを管理できる．見やすい．すごい[^1]

と，言うのをなんとなく自分で意訳しまくると`app/controller/concerns` に入る予定だったmoduleを別のところ（`app/interactions/`）に，よりキレイに置いておけるもの

#メリット

- コードの可読性がめちゃくちゃ上がる(個人的所感)
- interaction毎にテストが書けるのでテストが書きやすい
- 静的型付けになっているのでエラーを見つけやすい（後述）

#使ってみる
取り敢えず使ってみないことには（見せないことには）理解が曖昧なままだし何より自分で使ってみようという気持ちにならないので使ってみます．

###interactionを使わない状態
それっぽく書いた動かないコードなので注意

```ruby:controller/hoge_controller.rb

def create
  data = Data.new(data_params)
  scraped_title = params[:data][:url].scraping(title) # titleをscrapingする
  if params[:data][:title].nil?
    data.title = scraped_title # titleが空ならscrapingしたtitleを使う
  end
  if data.save
    redirect_to root_path
    flash[:notice] = '登録しました！'
  end
end
```

`create`methodの中にscrapingするコードが入っているので機能を複数（scrapingする，登録する)動かすことになってしまいます．

このscrapingの部分，createじゃないのになって思いませんか？分割したい気持ちになりませんか？
`ActiveInteraction`の為になってください.
使いたい気持ちになったのでActiveInteractionで実際に綺麗に整えようと思います．

### ActiveInteractionを使う

####忙しい人の為の概要

1. gemfileに`gem 'active_interaction', '~> 3.5', '>= 3.5.2'`を記述
2. `bundle install`
3. `app/interactions/Models/FileName.rb`を新しく作る

[静的型付けする時の型一覧](https://github.com/AaronLasseigne/active_interaction#filters)

```ruby:app/interactions/Hoges/CreateData.rb
module Hoges
  class CreateData < ActiveInteraction::Base
    string :url #静的型付けで引数のデータ型を定義

    def execute
      #ここにコードを書く
      str = 'hello, world'
      str
    end
  end
end
```
4 `app/controllers/hoges_controller.rb`に記述

```ruby:app/controllers/hoges_controller.rb
def create
  greet = ::Hoges::CreateData.run(url: params[:data][:url])
  greet.result # => 'hello, world'
  # 戻り値はresultに収納されている
  # modelのようにgreet.valid? が使える -> interactionにvalidateを記述したときはerrorsに格納される
end
```

####コードをいい感じにActiveInteractionでまとめる(初心者向け)
初心者向けなのでどういう風に考えていくか流れを踏まえて使ってみます．

#####前準備
gemfileに`gem 'active_interaction', '~> 3.5', '>= 3.5.2'`を記述します．[^2]

`bundle install`を走らせます．

`/app/interactions`フォルダを追加しておきます．ここにinteractionのコードを記述していきます．

#####Interaction用のファイルを作る

まずどこからどこまでを区切るのか，それはどういう機能なのかを考えてその機能を記述するファイルを作りましょう．

今回はscrapingを行う機能を分割したいので`scraping_data.rb`とします．

`/app/interactions`以下に`/Hoges/scraping_data.rb` または `/scraping_data.rb` を追加します．どちらでも大丈夫ですが今後modelが増えることも考えて`Hoges/`以下にファイルを作ることにします．

#####Interactionの雛形を記述する
Interactionを使うためにclassには`ActiveInteraction::Base`を継承させます．

```ruby:app/interactions/Hoges/ScrapingData.rb
module Hoges
  class ScrapingData < ActiveInteraction::Base

    def execute
    end
  end
end
```

#####Interactionに渡すデータを設定する
ActiveInteractionのファイルは静的型付けで引数を取ります．
これによってデータ型が違う時点でエラーが出せるのでバグを発見しやすくなります．

今回scrapingするのに渡したいデータは`params`の中にある`url`です．

urlはstring型なので以下のように記述します．

```ruby:app/interactions/Hoges/ScrapingData.rb
module Hoges
  class ScrapingData < ActiveInteraction::Base
    string :url # ここを追加

    def execute
    end
  end
end
```

#####executeの中に処理を書いていく
今回はcreateの中に書いていたこちらのコードを記述します．

```ruby
  scraped_title = params[:data][:url].scraping(title) # titleをscrapingする
```

`execute`の中に入れるときは型付けをした`url`を用います．

```ruby
def execute
	  scraped_title = url.scraping(title)
	  scraped_title # 戻り値にscraped_titleが来るようにする
end
```

#####controllerでinteractionを使う
controller部分のscrapingの処理をinteractionに置き換えます．
`NameSpace.run(data: hogehoge)`の形で呼び出しをします．

```ruby:controllers/hoges_controller.rb
def create
  scraped_data = ::Hoges::ScrapingData.run(url: params[:data][:url])
  if params[:data][:title].nil?
    data.title = scraped_data.result # resultで戻り値を取得
  end
  if data.save
    redirect_to root_path
    flash[:notice] = '登録しました！'
  end
end
```

###小話

####interaction classとresultメソッド
Interactionを利用したときに帰ってくる戻り値はInteraction特有のclassです．
`scraped_data.class #=> Hoges::ScrapingData`
と返ってきます．

この状態から指定した戻り値（今回なら`scraped_title`）は出力出来ないので，`result`methodを用います．
`scraped_data.result`とすることで`scraped_title`を取得できます．

また，resultを付けていない状態からは

`scraped_data.valid?`でvalidateチェックが出来，

`scraped_data.errors.full_messages`でエラーメッセージを取得できます．

エラーのあるときは`result`はnilになっているので意識しておくといいかもしれません．

####interaction内でvalidateを確認する
interactionの中で先にmodel内に記述したvalidateを確認して動かす事は出来ません．

というか，saveの段階でvalidateを確認しているのでsave前でのvalidateチェックは自動で行われないです．（そのはず）

でもvalidateのチェックは行いたい，そんな場合にはinteraction内にmodelと同じ書き方でvalidatesを記述出来ます．

その際のエラーメッセージは型付けした引数がattributesの中に入るので注意してください

例：url_dataという引数にpresence: trueのvalidatesをつける

```ruby
validates :url_data, presence: true

# ---
>  ::Hoge.run(url_data: '').errors.full_messages
#=> ["Url dataを入力してください"]

```

####引数に違う型を入れた時の挙動
もし間違えて入れてしまった時，どういう挙動になるのでしょうか．
不肖わたくしが少しだけ調査してみました．

`::Recipes::CreateRecipe.run(recipe_url: params[:url])`

こちらのInteractionはString型を取ります．
ここにintegerを入れてみましょう

```ruby
> ::Recipes::CreateRecipe.run(recipe_url: 00)
> @details={:recipe_url=>[{:error=>:invalid_type, :type=>"translation missing: ja.active_interaction.types.string"}]}, @messages={:recipe_url=>["translation missing: ja.active_interaction.errors.models.recipes/create_recipe.attributes.recipe_url.invalid_type"]}>
```

長いですがおわかりいただけるでしょうか．部分でいうと`errors`の部分です
[i18n](https://rubygems.org/gems/i18n/versions/0.7.0)が入っているので翻訳データがありませんというエラーになっていますが，要は`typeがstringであるはずだ`というエラーが表示される予定でした．

もちろん`valid?`はfalseになります．
今回は無理やりintegerを入れたので型エラーが起こりましたが，paramsは基本stringで返ってくるのでparamsを使っているうちはそうそうintegerが入ってエラーになった，という状況は起こりにくいかと思われます．

ですが万が一の事を考えてエラー処理を記述しておくのも手だと思います（今回ならi18nのlocaleを追加で記述しておくだけで十分でしょう）


####引数にちゃんと値を入れているのにデータが上手く扱われない
私がこの事案に引っかかり，少し詰まりました．
ActiveInteractionは名前からデータを推測します．
`record :user`と書けばUser modelだと推測し，`record :tag`と書けばtag modelだと推測します．

この時に`record :user`と型付けをしてrun(user: Tag.first)と入れようとしてもエラーになってしまいます．

modelと同じ名前や他のmodelと被らない名前を付けたほうがこういったバグには引っかからずに済むかと思います．

まだしっかりと検証は出来ていない（後日追記予定）ですが，validatesで追加されたerrorもcontrollerの方で`errors.full_messages`を用いて表示させる必要があるかと思われます．

####デバッグをする
rails consoleにそのまま`NameSpace.run()`で動かすことが出来ます．これがすごい助かる．楽ちん
今回は可読性の為に変数に入れてからresultとかしてますが，別に`run().result`でも全然大丈夫なのでパッと戻り値確認したい時とかにはお薦めです．

#まとめ
ActiveInteraction はすごい

あまりActiveInteractionの記事見かけないので是非こういう書き方しました，とかこういう使いみちもありました，みたいなの皆出してほしいです……🙏
訂正や追記もお待ちしてます．

Rubygemsを見ると分かる通りrails依存ではないのでRubyだけでも使えます！是非気軽に使ってみてください！

#参考
[ActiveInteraction のGithub](https://github.com/AaronLasseigne/active_interaction)
READMEのDocumentationすごく読みやすかったのでお薦めです．

[^1]: commandパターンとnamespaceに関しては用法，理解が違う可能性があります．
[^2]: コードはRubyGemsから拝借