CINXE.COM

Rails で has_many(one) な親要素から作成・削除されたか、子要素自身が作成・削除されたかを判断する | Basicinc Enjoy Hacking!

<!DOCTYPE html><html lang="ja" data-scope-path="layouts/application"><head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#"><link crossorigin="" href="https://www.google-analytics.com/analytics.js" rel="preconnect" /><link rel="preload" href="/fonts/noto-sans-jp-v24-japanese-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous"><link rel="preload" href="/fonts/noto-sans-jp-v24-japanese-700.woff2" as="font" type="font/woff2" crossorigin="anonymous"><!-- Global site tag (gtag.js) - Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id=UA-25666851-3"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-25666851-3'); </script> <link rel="stylesheet" media="all" href="/assets/ress-d1ba3a251927ab1f2bcc841c9a810109355bcb4260a522a0f76452fdc2fe6095.css" /><link rel="stylesheet" media="all" href="/assets/scopes/layouts/application-dda4ec688c139c8f29c39a2a795bcf4af04c4326643549a37f5d8843fffaebab.css" /><link rel="stylesheet" media="all" href="/assets/scopes/articles/show-37905516c06229c8fde10714e660fc48bc7b6ce7881755707db8b0327ceb5bf1.css" /><link rel="stylesheet" media="all" href="/assets/scopes/partials/_viewer-3a78a10250bb498047a06b71271b62ea949e304d25b9781c4450cf998aa2640c.css" /><link rel="stylesheet" media="all" href="/assets/scopes/layouts/_header-db87b54171cc020758ab7d1494677f8f6c2e9c93b453e896af82e4814eaacc7c.css" /><link rel="stylesheet" media="all" href="/assets/scopes/layouts/_breadcrumbs-744a099ab0b9eb5c4bd45215617e267c7d7c404ddddce8810eead5335512e637.css" /><link rel="stylesheet" media="all" href="/assets/scopes/layouts/_footer-a06961717440c6d0183c582c7f21768011f334d368f51b614eabc87bb17b3bbf.css" /><script src="/packs/application-02365dbc4dc28919eee1.js" defer="defer"></script><link href="/icon.png" rel="icon" type="image/png" /><link href="/apple-touch-icon.png" rel="apple-touch-icon" type="image/png" /><link href="/feed" rel="alternate" title="RSS2.0" type="application/rss+xml" /><title>Rails で has_many(one) な親要素から作成・削除されたか、子要素自身が作成・削除されたかを判断する | Basicinc Enjoy Hacking!</title><meta name="csrf-param" content="authenticity_token" /> <meta name="csrf-token" content="J2Llh04vwJk8ifGctyPczmFai1uFU/N8uAypowD56Yg0kbAt3Pn9xmn/hgF02XhfMg9vfvTCrqE3KgPEWN5lWw==" /><meta content="width=device-width,initial-scale=1" name="viewport" /><meta content="Rails で has_many(one) な親要素から作成・削除されたか、子要素自身が作成・削除されたかを判断する | Basicinc Enjoy Hacking!" property="og:title" /><meta content="article" property="og:type" /><meta content="2021-08-27T22:11:01+09:00" property="article:published_time" /><meta content="2021-08-27T22:19:28+09:00" property="article:modified_time" /><meta content="zaru" property="article:author" /><meta content="Technology" property="article:section" /><meta content="https://tech.basicinc.jp/articles/223" property="og:url" /><meta content="https://tech.basicinc.jp/assets/default_eyecatch-9e0d94e7defd5e124532d0b790a8f8cccfdffa0aed4581afa5f932cfec0b39a2.png" property="og:image" /><meta content="image/png" property="og:image:type" /><meta content="1200" property="og:image:width" /><meta content="630" property="og:image:height" /><meta content="ややこしいタイトルだけど、Ruby on Rails のモデルコールバックで迷った箇所があったのでメモ。 A has_many(has_one) B な関係で、B モデルの callback 実行を以下のパターンで制御したい。 親要素の A モデルと一緒に B モデルのデー..." property="og:description" /><meta content="ja_JP" property="og:locale" /><meta content="summary" name="twitter:card" /><meta content="@zaru" name="twitter:creator" /></head><body><header data-scope-path="layouts/_header"><div class="header-contents"><a class="root-link" href="/"><svg class="site-logo"><use xlink:href="/assets/icons-5bbe775037b55281ef0bc50d30a51f69c7c3914d399c7107e37f0702d98ae8a2.svg#logo"></use></svg><div class="site-title">Enjoy Hacking!</div></a><div class="site-desc">Basicinc エンジニアブログ</div></div></header><nav data-scope-path="layouts/_breadcrumbs"><ul class="breadcrumbs" itemscope="itemscope" itemtype="https://schema.org/BreadcrumbList"><span class="pretext">% cat </span> <li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem"><a itemprop="item" href="/"><span itemprop="name">~</span></a><meta itemprop="position" content="1" /></li>/<li class="current" itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem"><span itemprop="name">Railsでhas_many(one)な親要素から作成・削除されたか、子要素自身が作成・削除されたかを判断する.md</span><meta itemprop="item" content="https://tech.basicinc.jp/articles/223" /><meta itemprop="position" content="2" /></li></ul></nav><div data-scope-path="articles/show"><article class="article"><h1 class="article-title">Rails で has_many(one) な親要素から作成・削除されたか、子要素自身が作成・削除されたかを判断する</h1><div class="article-desc"><a class="article-author" href="/authors/1"><img class="article-author-avatar" src="https://tech.basicinc.jp/uploads/e868e5fb1e34b970ae7d8e04c454ff28.png" /><span class="article-author-name">zaru</span></a><div class="article-date-info"><time class="article-posted-date with-separator">Posted 2021-08-27</time><time class="article-revised-date">Revised 2021-08-27</time></div></div><div class="article-body"><div data-scope-path="partials/_viewer"><p>ややこしいタイトルだけど、Ruby on Rails のモデルコールバックで迷った箇所があったのでメモ。</p> <p>A has_many(has_one) B な関係で、B モデルの callback 実行を以下のパターンで制御したい。</p> <ul> <li>親要素の A モデルと一緒に B モデルのデータが作成された時だけ callback を実行</li> <li>親要素の A モデルの <code>dependent: :destroy</code> で削除された時だけ callback を実行</li> <li>子要素の B モデルが単独で作成・更新された時だけ callback を実行</li> <li>子要素の B モデルが単独で削除された時だけ callback を実行</li> </ul> <p>まずはモデルのシンプルな関係を示すコード。Article has_many Comment という感じ。</p> <pre lang="ruby" class="highlight highlight-ruby"><span class="k">class</span> <span class="nc">Article</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span> <span class="n">has_many</span> <span class="ss">:comments</span><span class="p">,</span> <span class="ss">dependent: :destroy</span> <span class="k">end</span> <span class="k">class</span> <span class="nc">Comment</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span> <span class="n">belongs_to</span> <span class="ss">:article</span> <span class="k">end</span> </pre> <p>これを上記の4つのパターン別に callback を登録してみるとこんな感じ。</p> <pre lang="ruby" class="highlight highlight-ruby"><span class="k">class</span> <span class="nc">Comment</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span> <span class="n">belongs_to</span> <span class="ss">:article</span> <span class="n">after_save_commit</span> <span class="ss">:created_by_association_only</span><span class="p">,</span> <span class="ss">if: :created_by_association?</span> <span class="n">after_save_commit</span> <span class="ss">:created_by_self_only</span><span class="p">,</span> <span class="ss">unless: :created_by_association?</span> <span class="n">after_destroy_commit</span> <span class="ss">:destroyed_by_association_only</span><span class="p">,</span> <span class="ss">if: :destroyed_by_association?</span> <span class="n">after_destroy_commit</span> <span class="ss">:destroyed_by_self_only</span><span class="p">,</span> <span class="ss">unless: :destroyed_by_association?</span> <span class="kp">private</span> <span class="k">def</span> <span class="nf">created_by_association?</span> <span class="n">article</span><span class="p">.</span><span class="nf">saved_change_to_updated_at?</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">destroyed_by_association?</span> <span class="n">destroyed_by_association</span><span class="p">.</span><span class="nf">present?</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">created_by_association_only</span> <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s1">'created_by_association_only'</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">created_by_self_only</span> <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s1">'created_by_self_only'</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">destroyed_by_association_only</span> <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s1">'destroyed_by_association_only'</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">destroyed_by_self_only</span> <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s1">'destroyed_by_self_only'</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> </pre> <h2>親要素から作られたか?</h2> <p>これを判定するメソッドが Rails には見当たらなかったので、親要素のオブジェクトが更新されているかどうかを確認して代替とすることにした。確認する方法は <code>model.saved_change_to_カラム名?</code>のマジックメソッドを使って、更新日が更新されているかどうかで判定している。</p> <p>親要素が更新されていれば、親と一緒に作成されているし、親要素が更新されていなければ子要素が単独で作成・更新されている…という風に判断している。果たしてこれで問題がないのかは若干の不安があるが、基本的には問題がなさそう。</p> <h2>親要素と一緒に削除されたか?</h2> <p>削除に関しては <code>destroyed_by_association</code> という便利なメソッドが生えている。これは <code>dependent: :destroy</code> 指定で一緒に削除される際に、削除元となったモデル情報が格納されている。子要素が単独で削除された場合は <code>nil</code> になる。これを使えば、一緒に削除されたのか単独なのかが判断できる。</p> <p>あとは callback の <code>if</code> オプションで条件を指定してあげれば4パターンそれぞれで callback を使い分けることができる。</p></div></div><div class="article-sns-share-buttons"><a rel="nofollow" target="_blank" class="pocket-share sns-share-button" href="http://getpocket.com/edit?title=Rails+%E3%81%A7+has_many%28one%29+%E3%81%AA%E8%A6%AA%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E4%BD%9C%E6%88%90%E3%83%BB%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%8B%E3%80%81%E5%AD%90%E8%A6%81%E7%B4%A0%E8%87%AA%E8%BA%AB%E3%81%8C%E4%BD%9C%E6%88%90%E3%83%BB%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%8B%E3%82%92%E5%88%A4%E6%96%AD%E3%81%99%E3%82%8B+by+zaru+%7C+Basicinc+Enjoy+Hacking%21&amp;url=https%3A%2F%2Ftech.basicinc.jp%2Farticles%2F223"><svg class="sns-icon pocket-icon"><use xlink:href="/assets/icons-5bbe775037b55281ef0bc50d30a51f69c7c3914d399c7107e37f0702d98ae8a2.svg#pocket"></use></svg></a><a rel="nofollow" target="_blank" class="hatena-share sns-share-button" href="http://b.hatena.ne.jp/add?mode=confirm&amp;title=Rails+%E3%81%A7+has_many%28one%29+%E3%81%AA%E8%A6%AA%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E4%BD%9C%E6%88%90%E3%83%BB%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%8B%E3%80%81%E5%AD%90%E8%A6%81%E7%B4%A0%E8%87%AA%E8%BA%AB%E3%81%8C%E4%BD%9C%E6%88%90%E3%83%BB%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%8B%E3%82%92%E5%88%A4%E6%96%AD%E3%81%99%E3%82%8B+by+zaru+%7C+Basicinc+Enjoy+Hacking%21&amp;url=https%3A%2F%2Ftech.basicinc.jp%2Farticles%2F223"><svg class="sns-icon hatena-icon"><use xlink:href="/assets/icons-5bbe775037b55281ef0bc50d30a51f69c7c3914d399c7107e37f0702d98ae8a2.svg#hatena"></use></svg></a><a rel="nofollow" target="_blank" class="twitter-share sns-share-button" href="https://twitter.com/share?text=Rails+%E3%81%A7+has_many%28one%29+%E3%81%AA%E8%A6%AA%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E4%BD%9C%E6%88%90%E3%83%BB%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%8B%E3%80%81%E5%AD%90%E8%A6%81%E7%B4%A0%E8%87%AA%E8%BA%AB%E3%81%8C%E4%BD%9C%E6%88%90%E3%83%BB%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%8B%E3%82%92%E5%88%A4%E6%96%AD%E3%81%99%E3%82%8B+by+zaru+%7C+Basicinc+Enjoy+Hacking%21&amp;url=https%3A%2F%2Ftech.basicinc.jp%2Farticles%2F223"><svg class="sns-icon twitter-icon"><use xlink:href="/assets/icons-5bbe775037b55281ef0bc50d30a51f69c7c3914d399c7107e37f0702d98ae8a2.svg#twitter"></use></svg></a><a rel="nofollow" target="_blank" class="facebook-share sns-share-button" href="http://www.facebook.com/share.php?u=https%3A%2F%2Ftech.basicinc.jp%2Farticles%2F223"><svg class="sns-icon facebook-icon"><use xlink:href="/assets/icons-5bbe775037b55281ef0bc50d30a51f69c7c3914d399c7107e37f0702d98ae8a2.svg#facebook"></use></svg></a></div></article></div><footer data-scope-path="layouts/_footer"><div class="footer-contents"><a class="root-link" href="/"><svg class="site-logo"><use xlink:href="/assets/icons-5bbe775037b55281ef0bc50d30a51f69c7c3914d399c7107e37f0702d98ae8a2.svg#logo"></use></svg><div class="site-title">Enjoy Hacking!</div></a><ul class="link-list"><li class="link-list-item"><a class="link" href="https://basicinc.jp/" rel="noopener" target="_blank">株式会社ベーシック</a></li><li class="link-list-item"><a class="link" href="https://basicinc.jp/enjoy-hacking" rel="noopener" target="_blank">エンジニア募集</a></li><li class="link-list-item"><a class="link" href="https://qiita.com/organizations/basicinc" rel="noopener" target="_blank">Qiita</a></li><li class="link-list-item"><a class="link" href="https://www.wantedly.com/companies/basicinc/projects" rel="noopener" target="_blank">Wantedly</a></li></ul></div></footer></body></html>

Pages: 1 2 3 4 5 6 7 8 9 10