#はじめに
###注意書き
未経験初心者Rubyistがメモ同様の記事を書いているものなので間違っている部分やもっといい書き方の出来る部分があります(確信してます).
がっつりまさかり投げてください.勉強させていただきます.
###何故記事をかいたのか
ActiveInteractionを勧められて使ってみて良さをひしひしと実感している今日此の頃,ふとQiitaでActiveInteractionの記事を検索してみると全く無かったのでこれは勧める他ねえと啓示を頂いたため.
#ActiveInteractionとは
commandパターンを利用してコードをめちゃくちゃスッキリさせられる.すごい
名前空間を使えるのでcontroller/function
の形でファイルを管理できる.見やすい.すごい1
と,言うのをなんとなく自分で意訳しまくるとapp/controller/concerns
に入る予定だったmoduleを別のところ(app/interactions/
)に,よりキレイに置いておけるもの
#メリット
- コードの可読性がめちゃくちゃ上がる(個人的所感)
- interaction毎にテストが書けるのでテストが書きやすい
- 静的型付けになっているのでエラーを見つけやすい(後述)
#使ってみる
取り敢えず使ってみないことには(見せないことには)理解が曖昧なままだし何より自分で使ってみようという気持ちにならないので使ってみます.
###interactionを使わない状態
それっぽく書いた動かないコードなので注意
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を使う
####忙しい人の為の概要
- gemfileに
gem 'active_interaction', '~> 3.5', '>= 3.5.2'
を記述 bundle install
-
app/interactions/Models/FileName.rb
を新しく作る
module Hoges
class CreateData < ActiveInteraction::Base
string :url #静的型付けで引数のデータ型を定義
def execute
#ここにコードを書く
str = 'hello, world'
str
end
end
end
4 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
を継承させます.
module Hoges
class ScrapingData < ActiveInteraction::Base
def execute
end
end
end
#####Interactionに渡すデータを設定する
ActiveInteractionのファイルは静的型付けで引数を取ります.
これによってデータ型が違う時点でエラーが出せるのでバグを発見しやすくなります.
今回scrapingするのに渡したいデータはparams
の中にあるurl
です.
urlはstring型なので以下のように記述します.
module Hoges
class ScrapingData < ActiveInteraction::Base
string :url # ここを追加
def execute
end
end
end
#####executeの中に処理を書いていく
今回はcreateの中に書いていたこちらのコードを記述します.
scraped_title = params[:data][:url].scraping(title) # titleをscrapingする
execute
の中に入れるときは型付けをしたurl
を用います.
def execute
scraped_title = url.scraping(title)
scraped_title # 戻り値にscraped_titleが来るようにする
end
#####controllerでinteractionを使う
controller部分のscrapingの処理をinteractionに置き換えます.
NameSpace.run(data: hogehoge)
の形で呼び出しをします.
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をつける
validates :url_data, presence: true
# ---
> ::Hoge.run(url_data: '').errors.full_messages
#=> ["Url dataを入力してください"]
####引数に違う型を入れた時の挙動
もし間違えて入れてしまった時,どういう挙動になるのでしょうか.
不肖わたくしが少しだけ調査してみました.
::Recipes::CreateRecipe.run(recipe_url: params[:url])
こちらのInteractionはString型を取ります.
ここにintegerを入れてみましょう
> ::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が入っているので翻訳データがありませんというエラーになっていますが,要は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
READMEのDocumentationすごく読みやすかったのでお薦めです.