{"id":1366,"date":"2025-03-24T08:52:17","date_gmt":"2025-03-23T23:52:17","guid":{"rendered":"https:\/\/dexall.co.jp\/articles\/?p=1366"},"modified":"2025-03-24T08:52:17","modified_gmt":"2025-03-23T23:52:17","slug":"%e3%80%902024%e5%b9%b4%e4%bf%9d%e5%ad%98%e7%89%88%e3%80%91ruby-on-rails%e3%81%a7%e3%83%81%e3%82%a7%e3%83%83%e3%82%af%e3%83%9c%e3%83%83%e3%82%af%e3%82%b9%e3%82%92%e5%ae%8c%e5%85%a8%e5%88%b6%e8%a6%87","status":"publish","type":"post","link":"https:\/\/dexall.co.jp\/articles\/?p=1366","title":{"rendered":"\u30102024\u5e74\u4fdd\u5b58\u7248\u3011Ruby on Rails\u3067\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u5b8c\u5168\u5236\u8987\uff01\u5b9f\u88c5\u304b\u3089\u5fdc\u7528\u307e\u30677\u3064\u306e\u5fc5\u9808\u30c6\u30af\u30cb\u30c3\u30af"},"content":{"rendered":"\n<div class=\"toc\"><br \/>\n<b>Warning<\/b>:  Undefined array key \"is_admin\" in <b>\/home\/xs392991\/dexall.co.jp\/public_html\/articles\/wp-content\/themes\/sango-theme\/library\/gutenberg\/dist\/classes\/Toc.php<\/b> on line <b>116<\/b><br \/>\n<br \/>\n<b>Warning<\/b>:  Undefined array key \"is_category_top\" in <b>\/home\/xs392991\/dexall.co.jp\/public_html\/articles\/wp-content\/themes\/sango-theme\/library\/gutenberg\/dist\/classes\/Toc.php<\/b> on line <b>121<\/b><br \/>\n<br \/>\n<b>Warning<\/b>:  Undefined array key \"is_top\" in <b>\/home\/xs392991\/dexall.co.jp\/public_html\/articles\/wp-content\/themes\/sango-theme\/library\/gutenberg\/dist\/classes\/Toc.php<\/b> on line <b>128<\/b><br \/>\n    <div id=\"toc_container\" class=\"sgb-toc--bullets js-smooth-scroll\" data-dialog-title=\"\u76ee\u6b21\">\n      <p class=\"toc_title\">\u76ee\u6b21 <\/p>\n      <ul class=\"toc_list\">  <li class=\"first\">    <a href=\"#i-0\">Ruby on Rails\u3067\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u5b9f\u88c5\u3059\u308b\u57fa\u790e\u77e5\u8b58<\/a>    <ul class=\"menu_level_1\">      <li class=\"first\">        <a href=\"#i-1\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u57fa\u672c\u7684\u306a\u5b9f\u88c5\u65b9\u6cd5\u3068\u30d5\u30a9\u30fc\u30e0\u30d8\u30eb\u30d1\u30fc\u306e\u4f7f\u3044\u65b9<\/a>      <\/li>      <li class=\"last\">        <a href=\"#i-4\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5024\u304c\u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u30fc\u306b\u6e21\u308b\u4ed5\u7d44\u307f<\/a>      <\/li>    <\/ul>  <\/li>  <li>    <a href=\"#i-8\">\u5b9f\u8df5\u7684\u306a\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u5b9f\u88c5\u30c6\u30af\u30cb\u30c3\u30af<\/a>    <ul class=\"menu_level_1\">      <li class=\"first\">        <a href=\"#i-9\">\u8907\u6570\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u4e00\u62ec\u3067\u6271\u3046\u65b9\u6cd5<\/a>      <\/li>      <li>        <a href=\"#i-10\">Turbo\/Hotwire\u3092\u6d3b\u7528\u3057\u305f\u975e\u540c\u671f\u66f4\u65b0\u306e\u5b9f\u88c5<\/a>      <\/li>      <li class=\"last\">        <a href=\"#i-11\">JavaScript\u3092\u4f7f\u7528\u3057\u305f\u52d5\u7684\u306a\u72b6\u614b\u7ba1\u7406<\/a>      <\/li>    <\/ul>  <\/li>  <li>    <a href=\"#i-12\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\u3068\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3<\/a>    <ul class=\"menu_level_1\">      <li class=\"first\">        <a href=\"#i-13\">Strong Parameters\u3067\u306e\u5b89\u5168\u306a\u5024\u306e\u53d7\u3051\u53d6\u308a\u65b9<\/a>      <\/li>      <li class=\"last\">        <a href=\"#i-14\">\u30ab\u30b9\u30bf\u30e0\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\u306e\u5b9f\u88c5\u65b9\u6cd5<\/a>      <\/li>    <\/ul>  <\/li>  <li>    <a href=\"#i-15\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5fdc\u7528\u5b9f\u88c5\u30d1\u30bf\u30fc\u30f3<\/a>    <ul class=\"menu_level_1\">      <li class=\"first\">        <a href=\"#i-16\">\u5168\u9078\u629e\u30fb\u5168\u89e3\u9664\u6a5f\u80fd\u306e\u5b9f\u88c5\u65b9\u6cd5<\/a>      <\/li>      <li class=\"last\">        <a href=\"#i-17\">\u30cd\u30b9\u30c8\u3057\u305f\u5c5e\u6027\u3067\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u7ba1\u7406<\/a>      <\/li>    <\/ul>  <\/li>  <li>    <a href=\"#i-18\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30c6\u30b9\u30c8\u5b9f\u88c5<\/a>    <ul class=\"menu_level_1\">      <li class=\"first\">        <a href=\"#i-19\">System Spec\u3067\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u30c6\u30b9\u30c8\u65b9\u6cd5<\/a>      <\/li>      <li class=\"last\">        <a href=\"#i-20\">JavaScript\u3092\u542b\u3080\u30c6\u30b9\u30c8\u306e\u66f8\u304d\u65b9<\/a>      <\/li>    <\/ul>  <\/li>  <li>    <a href=\"#i-21\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5b9f\u88c5\u3067\u3088\u304f\u3042\u308b\u30c8\u30e9\u30d6\u30eb\u5bfe\u51e6\u6cd5<\/a>    <ul class=\"menu_level_1\">      <li class=\"first\">        <a href=\"#i-22\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5024\u304c\u6b63\u3057\u304f\u9001\u4fe1\u3055\u308c\u306a\u3044\u5834\u5408\u306e\u5bfe\u51e6<\/a>      <\/li>      <li class=\"last\">        <a href=\"#i-23\">\u8907\u6570\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3067\u7279\u5b9a\u306e\u5024\u3060\u3051\u9001\u4fe1\u3055\u308c\u306a\u3044\u554f\u984c\u306e\u89e3\u6c7a<\/a>      <\/li>    <\/ul>  <\/li>  <li class=\"last\">    <a href=\"#i-24\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u6700\u9069\u5316<\/a>    <ul class=\"menu_level_1\">      <li class=\"first\">        <a href=\"#i-25\">\u5927\u91cf\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u6271\u3046\u969b\u306e\u6700\u9069\u5316\u30c6\u30af\u30cb\u30c3\u30af<\/a>      <\/li>      <li class=\"last\">        <a href=\"#i-26\">N+1\u554f\u984c\u306e\u56de\u907f\u65b9\u6cd5<\/a>      <\/li>    <\/ul>  <\/li><\/ul>\n      <a href=\"#\" class=\"sgb-toc-button js-toc-button\" rel=\"nofollow\" data-open-dialog=\"true\"><i class=\"fa fa-list\"><\/i><span class=\"sgb-toc-button__text\">\u76ee\u6b21\u3078<\/span><\/a>\n    <\/div><\/div><h2 class=\"wp-block-heading\" id=\"i-0\">Ruby on Rails\u3067\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u5b9f\u88c5\u3059\u308b\u57fa\u790e\u77e5\u8b58<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-1\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u57fa\u672c\u7684\u306a\u5b9f\u88c5\u65b9\u6cd5\u3068\u30d5\u30a9\u30fc\u30e0\u30d8\u30eb\u30d1\u30fc\u306e\u4f7f\u3044\u65b9<\/h3>\n\n\n\n<p>Rails \u306e\u30d5\u30a9\u30fc\u30e0\u30d8\u30eb\u30d1\u30fc\u3092\u4f7f\u7528\u3059\u308b\u3053\u3068\u3067\u3001HTML\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u7c21\u5358\u304b\u3064\u5b89\u5168\u306b\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002\u57fa\u672c\u7684\u306a\u5b9f\u88c5\u65b9\u6cd5\u3092\u898b\u3066\u3044\u304d\u307e\u3057\u3087\u3046\u3002<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"i-2\">1. \u5358\u4e00\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u5b9f\u88c5\u3059\u308b<\/h4>\n\n\n\n<p>\u6700\u3082\u57fa\u672c\u7684\u306a\u5b9f\u88c5\u65b9\u6cd5\u306f\u3001<code>check_box<\/code>\u30d8\u30eb\u30d1\u30fc\u30e1\u30bd\u30c3\u30c9\u3092\u4f7f\u7528\u3059\u308b\u3053\u3068\u3067\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/views\/users\/_form.html.erb\n&lt;%= form_with(model: @user) do |f| %&gt;\n  &lt;%= f.check_box :newsletter_subscription %&gt;\n  &lt;%= f.label :newsletter_subscription, \"\u30cb\u30e5\u30fc\u30b9\u30ec\u30bf\u30fc\u3092\u8cfc\u8aad\u3059\u308b\" %&gt;\n&lt;% end %&gt;<\/pre>\n\n\n\n<p>\u3053\u306e\u30b3\u30fc\u30c9\u3067\u751f\u6210\u3055\u308c\u308bHTML:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;input type=\"hidden\" value=\"0\" name=\"user[newsletter_subscription]\"&gt;\n&lt;input type=\"checkbox\" value=\"1\" name=\"user[newsletter_subscription]\" id=\"user_newsletter_subscription\"&gt;\n&lt;label for=\"user_newsletter_subscription\"&gt;\u30cb\u30e5\u30fc\u30b9\u30ec\u30bf\u30fc\u3092\u8cfc\u8aad\u3059\u308b&lt;\/label&gt;<\/pre>\n\n\n\n<p>\u91cd\u8981\u306a\u30dd\u30a4\u30f3\u30c8\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>hidden\u30d5\u30a3\u30fc\u30eb\u30c9\u304c\u81ea\u52d5\u7684\u306b\u751f\u6210\u3055\u308c\u308b\uff08\u30c1\u30a7\u30c3\u30af\u304c\u5916\u308c\u3066\u3044\u308b\u5834\u5408\u306e\u5024\u3068\u3057\u3066\u201d0\u2033\u304c\u9001\u4fe1\u3055\u308c\u308b\uff09<\/li>\n\n\n\n<li>\u30c1\u30a7\u30c3\u30af\u3055\u308c\u305f\u5834\u5408\u306f\u201d1\u2033\u304c\u9001\u4fe1\u3055\u308c\u308b<\/li>\n\n\n\n<li><code>id<\/code>\u5c5e\u6027\u304c\u81ea\u52d5\u7684\u306b\u751f\u6210\u3055\u308c\u3001label\u3068\u306e\u95a2\u9023\u4ed8\u3051\u304c\u884c\u308f\u308c\u308b<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"i-3\">2. \u30ab\u30b9\u30bf\u30e0\u5024\u3092\u6301\u3064\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9<\/h4>\n\n\n\n<p>\u30c7\u30d5\u30a9\u30eb\u30c8\u306e1\/0\u4ee5\u5916\u306e\u5024\u3092\u4f7f\u7528\u3057\u305f\u3044\u5834\u5408\u306f\u3001\u4ee5\u4e0b\u306e\u3088\u3046\u306b\u5b9f\u88c5\u3057\u307e\u3059\uff1a<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/views\/articles\/_form.html.erb\n&lt;%= form_with(model: @article) do |f| %&gt;\n  &lt;%= f.check_box :status, {}, \"published\", \"draft\" %&gt;\n  &lt;%= f.label :status, \"\u8a18\u4e8b\u3092\u516c\u958b\u3059\u308b\" %&gt;\n&lt;% end %&gt;<\/pre>\n\n\n\n<p>\u3053\u306e\u30b1\u30fc\u30b9\u3067\u306f\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u30c1\u30a7\u30c3\u30af\u3055\u308c\u305f\u5834\u5408: \u201cpublished\u201d \u304c\u9001\u4fe1\u3055\u308c\u308b<\/li>\n\n\n\n<li>\u30c1\u30a7\u30c3\u30af\u304c\u5916\u308c\u3066\u3044\u308b\u5834\u5408: \u201cdraft\u201d \u304c\u9001\u4fe1\u3055\u308c\u308b<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-4\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5024\u304c\u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u30fc\u306b\u6e21\u308b\u4ed5\u7d44\u307f<\/h3>\n\n\n\n<p>\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5024\u306f\u3001\u30d5\u30a9\u30fc\u30e0\u9001\u4fe1\u6642\u306b\u3069\u306e\u3088\u3046\u306b\u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u30fc\u306b\u6e21\u3055\u308c\u308b\u306e\u304b\u3001\u305d\u306e\u4ed5\u7d44\u307f\u3092\u7406\u89e3\u3059\u308b\u3053\u3068\u304c\u91cd\u8981\u3067\u3059\u3002<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"i-5\">1. \u30d1\u30e9\u30e1\u30fc\u30bf\u306e\u5f62\u5f0f<\/h4>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/controllers\/users_controller.rb\nclass UsersController &lt; ApplicationController\n  def create\n    # params[:user] \u306e\u4e2d\u306b newsletter_subscription \u306e\u5024\u304c\u542b\u307e\u308c\u308b\n    @user = User.new(user_params)\n\n    if @user.save\n      redirect_to @user, notice: '\u30e6\u30fc\u30b6\u30fc\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u3002'\n    else\n      render :new\n    end\n  end\n\n  private\n\n  def user_params\n    # Strong Parameters \u3067\u306e\u8a31\u53ef\u304c\u5fc5\u8981\n    params.require(:user).permit(:newsletter_subscription)\n  end\nend<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"i-6\">2. \u5024\u306e\u81ea\u52d5\u5909\u63db<\/h4>\n\n\n\n<p>Rails\u306f\u9001\u4fe1\u3055\u308c\u305f\u5024\u3092\u81ea\u52d5\u7684\u306b\u9069\u5207\u306a\u578b\u306b\u5909\u63db\u3057\u307e\u3059\uff1a<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/user.rb\nclass User &lt; ApplicationRecord\n  # \u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30ab\u30e9\u30e0\u578b\u306b\u5fdc\u3058\u3066\u81ea\u52d5\u5909\u63db\u3055\u308c\u308b\n  # - boolean\u578b\u30ab\u30e9\u30e0\u306e\u5834\u5408:\n  #   \"1\", \"true\", \"on\" \u2192 true\n  #   \"0\", \"false\", \"off\" \u2192 false\nend<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"i-7\">\u5b9f\u88c5\u6642\u306e\u6ce8\u610f\u70b9<\/h4>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>hidden_field\u3068\u306e\u4f75\u7528<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Rails \u306e <code>check_box<\/code> \u30d8\u30eb\u30d1\u30fc\u306f\u81ea\u52d5\u7684\u306bhidden_field\u3092\u751f\u6210\u3057\u307e\u3059<\/li>\n\n\n\n<li>\u624b\u52d5\u3067hidden_field\u3092\u8ffd\u52a0\u3059\u308b\u5fc5\u8981\u306f\u3042\u308a\u307e\u305b\u3093<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u274c \u4e0d\u8981\u306a\u5b9f\u88c5\n&lt;%= hidden_field_tag 'user[newsletter_subscription]', '0' %&gt;\n&lt;%= check_box_tag 'user[newsletter_subscription]', '1' %&gt;\n\n# \u2b55\ufe0f \u6b63\u3057\u3044\u5b9f\u88c5\n&lt;%= f.check_box :newsletter_subscription %&gt;<\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\u8003\u616e<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5fc5\u9808\u30c1\u30a7\u30c3\u30af\u306a\u3069\u306f\u3001\u30e2\u30c7\u30eb\u3067\u8a2d\u5b9a\u3057\u307e\u3059<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/user.rb\nclass User &lt; ApplicationRecord\n  validates :terms_of_service, acceptance: true\nend<\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li><strong>\u30e9\u30d9\u30eb\u306e\u9069\u5207\u306a\u914d\u7f6e<\/strong><\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u30a2\u30af\u30bb\u30b7\u30d3\u30ea\u30c6\u30a3\u306e\u305f\u3081\u3001\u5fc5\u305a\u30e9\u30d9\u30eb\u3092\u4ed8\u4e0e\u3057\u307e\u3059<\/li>\n\n\n\n<li>\u30e9\u30d9\u30eb\u3068\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u9069\u5207\u306b\u95a2\u9023\u4ed8\u3051\u307e\u3059<\/li>\n<\/ul>\n\n\n\n<p>\u4ee5\u4e0a\u304c\u3001Ruby on Rails\u3067\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u5b9f\u88c5\u306e\u57fa\u790e\u77e5\u8b58\u3067\u3059\u3002\u3053\u308c\u3089\u306e\u57fa\u672c\u3092\u62bc\u3055\u3048\u305f\u4e0a\u3067\u3001\u6b21\u306e\u30bb\u30af\u30b7\u30e7\u30f3\u3067\u5b9f\u8df5\u7684\u306a\u30c6\u30af\u30cb\u30c3\u30af\u3092\u898b\u3066\u3044\u304d\u307e\u3057\u3087\u3046\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-8\">\u5b9f\u8df5\u7684\u306a\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u5b9f\u88c5\u30c6\u30af\u30cb\u30c3\u30af<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-9\">\u8907\u6570\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u4e00\u62ec\u3067\u6271\u3046\u65b9\u6cd5<\/h3>\n\n\n\n<p>\u8907\u6570\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u6271\u3046\u5834\u5408\u306e\u5b9f\u88c5\u65b9\u6cd5\u3092\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/user.rb\nclass User &lt; ApplicationRecord\n  has_many :user_interests\n  has_many :interests, through: :user_interests\nend\n\n# app\/models\/interest.rb\nclass Interest &lt; ApplicationRecord\n  has_many :user_interests\n  has_many :users, through: :user_interests\nend\n\n# app\/controllers\/users_controller.rb\nclass UsersController &lt; ApplicationController\n  def edit\n    @user = User.find(params[:id])\n    @interests = Interest.all\n  end\n\n  def update\n    @user = User.find(params[:id])\n    if @user.update(user_params)\n      redirect_to @user, notice: 'Interests updated successfully.'\n    else\n      @interests = Interest.all\n      render :edit\n    end\n  end\n\n  private\n\n  def user_params\n    params.require(:user).permit(interest_ids: [])\n  end\nend<\/pre>\n\n\n\n<p>\u30d3\u30e5\u30fc\u306e\u5b9f\u88c5:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!-- app\/views\/users\/_form.html.erb --&gt;\n&lt;%= form_with(model: @user) do |form| %&gt;\n  &lt;div class=\"interests-selection\"&gt;\n    &lt;h3&gt;Select your interests:&lt;\/h3&gt;\n    &lt;%= form.collection_check_boxes :interest_ids, @interests, :id, :name do |b| %&gt;\n      &lt;div class=\"interest-option\"&gt;\n        &lt;%= b.check_box %&gt;\n        &lt;%= b.label %&gt;\n      &lt;\/div&gt;\n    &lt;% end %&gt;\n  &lt;\/div&gt;\n  &lt;%= form.submit %&gt;\n&lt;% end %&gt;<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-10\">Turbo\/Hotwire\u3092\u6d3b\u7528\u3057\u305f\u975e\u540c\u671f\u66f4\u65b0\u306e\u5b9f\u88c5<\/h3>\n\n\n\n<p>Turbo\/Hotwire\u3092\u4f7f\u7528\u3057\u3066\u3001\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u72b6\u614b\u3092\u975e\u540c\u671f\u3067\u66f4\u65b0\u3059\u308b\u5b9f\u88c5\u3092\u793a\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/task.rb\nclass Task &lt; ApplicationRecord\n  broadcasts_to -&gt;(task) { \"tasks\" }\nend\n\n# app\/controllers\/tasks_controller.rb\nclass TasksController &lt; ApplicationController\n  def update\n    @task = Task.find(params[:id])\n    @task.update(task_params)\n  end\n\n  private\n\n  def task_params\n    params.require(:task).permit(:completed)\n  end\nend<\/pre>\n\n\n\n<p>\u30d3\u30e5\u30fc\u306e\u5b9f\u88c5:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!-- app\/views\/tasks\/_task.html.erb --&gt;\n&lt;%= turbo_frame_tag dom_id(task) do %&gt;\n  &lt;div class=\"task-item\"&gt;\n    &lt;%= form_with(model: task, class: \"task-form\") do |form| %&gt;\n      &lt;%= form.check_box :completed, \n          data: { controller: \"task-status\", action: \"change-&gt;task-status#toggle\" },\n          class: \"task-checkbox\" %&gt;\n      &lt;%= form.label :completed, task.title %&gt;\n    &lt;% end %&gt;\n  &lt;\/div&gt;\n&lt;% end %&gt;\n\n&lt;!-- app\/views\/tasks\/index.html.erb --&gt;\n&lt;%= turbo_stream_from \"tasks\" %&gt;\n&lt;div id=\"tasks\"&gt;\n  &lt;%= render @tasks %&gt;\n&lt;\/div&gt;<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-11\">JavaScript\u3092\u4f7f\u7528\u3057\u305f\u52d5\u7684\u306a\u72b6\u614b\u7ba1\u7406<\/h3>\n\n\n\n<p>JavaScript\u3092\u4f7f\u7528\u3057\u3066\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u72b6\u614b\u3092\u52d5\u7684\u306b\u7ba1\u7406\u3059\u308b\u5b9f\u88c5\u4f8b\u3067\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ app\/javascript\/controllers\/task_status_controller.js\nimport { Controller } from \"@hotwired\/stimulus\"\n\nexport default class extends Controller {\n  toggle(event) {\n    const form = event.target.closest('form')\n    \/\/ \u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u72b6\u614b\u304c\u5909\u66f4\u3055\u308c\u305f\u3089\u81ea\u52d5\u7684\u306b\u30d5\u30a9\u30fc\u30e0\u3092\u9001\u4fe1\n    form.requestSubmit()\n\n    \/\/ \u30aa\u30d7\u30b7\u30e7\u30f3: \u8996\u899a\u7684\u30d5\u30a3\u30fc\u30c9\u30d0\u30c3\u30af\u3092\u8ffd\u52a0\n    const taskItem = event.target.closest('.task-item')\n    if (event.target.checked) {\n      taskItem.classList.add('completed')\n    } else {\n      taskItem.classList.remove('completed')\n    }\n  }\n}<\/pre>\n\n\n\n<p>\u95a2\u9023\u3059\u308bCSS\u30b9\u30bf\u30a4\u30eb:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/* app\/assets\/stylesheets\/tasks.css *\/\n.task-item {\n  transition: background-color 0.3s ease;\n  padding: 10px;\n  margin: 5px 0;\n}\n\n.task-item.completed {\n  background-color: #f0f0f0;\n  text-decoration: line-through;\n}\n\n.task-checkbox {\n  margin-right: 10px;\n}<\/pre>\n\n\n\n<p>\u3053\u306e\u3088\u3046\u306b\u5b9f\u88c5\u3059\u308b\u3053\u3068\u3067\u3001\u4ee5\u4e0b\u306e\u6a5f\u80fd\u304c\u5b9f\u73fe\u3067\u304d\u307e\u3059\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u8907\u6570\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u52b9\u7387\u7684\u306a\u7ba1\u7406<\/li>\n\n\n\n<li>\u30ea\u30a2\u30eb\u30bf\u30a4\u30e0\u306a\u72b6\u614b\u66f4\u65b0<\/li>\n\n\n\n<li>\u30e6\u30fc\u30b6\u30fc\u30d5\u30ec\u30f3\u30c9\u30ea\u30fc\u306aUI\/UX<\/li>\n\n\n\n<li>\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u306e\u6700\u9069\u5316<\/li>\n<\/ol>\n\n\n\n<p>\u3055\u3089\u306b\u3001\u3053\u308c\u3089\u306e\u5b9f\u88c5\u306f\u30e2\u30d0\u30a4\u30eb\u30c7\u30d0\u30a4\u30b9\u3067\u3082\u9069\u5207\u306b\u52d5\u4f5c\u3057\u3001\u30a2\u30af\u30bb\u30b7\u30d3\u30ea\u30c6\u30a3\u306b\u3082\u914d\u616e\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-12\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\u3068\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-13\">Strong Parameters\u3067\u306e\u5b89\u5168\u306a\u5024\u306e\u53d7\u3051\u53d6\u308a\u65b9<\/h3>\n\n\n\n<p>\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5024\u3092\u5b89\u5168\u306b\u51e6\u7406\u3059\u308b\u305f\u3081\u306eStrong Parameters\u306e\u5b9f\u88c5\u65b9\u6cd5\u3092\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/controllers\/settings_controller.rb\nclass SettingsController &lt; ApplicationController\n  def update\n    @settings = current_user.settings\n    if @settings.update(settings_params)\n      redirect_to settings_path, notice: '\u8a2d\u5b9a\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f'\n    else\n      render :edit\n    end\n  end\n\n  private\n\n  def settings_params\n    # \u914d\u5217\u306e\u5834\u5408\n    params.require(:settings).permit(\n      :receive_notifications,  # \u5358\u4e00\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\n      notification_types: [],  # \u8907\u6570\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\n      preferences: [:email_digest, :sms_alerts]  # \u30cd\u30b9\u30c8\u3055\u308c\u305f\u5c5e\u6027\n    )\n  end\nend<\/pre>\n\n\n\n<p>\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e\u30d9\u30b9\u30c8\u30d7\u30e9\u30af\u30c6\u30a3\u30b9:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/settings.rb\nclass Settings &lt; ApplicationRecord\n  # \u8a31\u53ef\u3055\u308c\u308b\u5024\u306e\u5236\u9650\n  validates :notification_types, inclusion: { \n    in: %w(email sms push), \n    message: \"\u306f\u8a31\u53ef\u3055\u308c\u3066\u3044\u306a\u3044\u5024\u3067\u3059\" \n  }\n\n  # boolean\u5024\u306e\u578b\u30c1\u30a7\u30c3\u30af\n  validates :receive_notifications, inclusion: { \n    in: [true, false], \n    message: \"\u306f\u771f\u507d\u5024\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\" \n  }\n\n  # \u30ab\u30b9\u30bf\u30e0\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\n  validate :notification_types_limit\n\n  private\n\n  def notification_types_limit\n    if notification_types.present? &amp;&amp; notification_types.length &gt; 3\n      errors.add(:notification_types, \"\u306f3\u3064\u307e\u3067\u3057\u304b\u9078\u629e\u3067\u304d\u307e\u305b\u3093\")\n    end\n  end\nend<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-14\">\u30ab\u30b9\u30bf\u30e0\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\u306e\u5b9f\u88c5\u65b9\u6cd5<\/h3>\n\n\n\n<p>\u3088\u308a\u8907\u96d1\u306a\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\u30eb\u30fc\u30eb\u3092\u5b9f\u88c5\u3059\u308b\u4f8b\u3092\u793a\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/survey_response.rb\nclass SurveyResponse &lt; ApplicationRecord\n  # \u5fc5\u9808\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\n  validates :terms_accepted, acceptance: { \n    message: '\u306b\u540c\u610f\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059',\n    accept: true \n  }\n\n  # \u30ab\u30b9\u30bf\u30e0\u30d0\u30ea\u30c7\u30fc\u30bf\u306e\u4f5c\u6210\n  class MinimumSelectionsValidator &lt; ActiveModel::Validator\n    def validate(record)\n      selections = record.category_selections || []\n      if selections.count &lt; options[:minimum]\n        record.errors.add :base, \"\u5c11\u306a\u304f\u3068\u3082#{options[:minimum]}\u500b\u306e\u30ab\u30c6\u30b4\u30ea\u30fc\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\"\n      end\n    end\n  end\n\n  # \u30ab\u30b9\u30bf\u30e0\u30d0\u30ea\u30c7\u30fc\u30bf\u306e\u9069\u7528\n  validates_with MinimumSelectionsValidator, minimum: 2\n\n  # \u6761\u4ef6\u4ed8\u304d\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\n  with_options if: :advanced_options_enabled? do |survey|\n    survey.validate :validate_option_combinations\n  end\n\n  private\n\n  def validate_option_combinations\n    selected_options = options.select { |opt| opt[:selected] }\n\n    # \u7279\u5b9a\u306e\u7d44\u307f\u5408\u308f\u305b\u3092\u30c1\u30a7\u30c3\u30af\n    if selected_options.map { |opt| opt[:category] }.uniq.length &lt; 2\n      errors.add(:options, \"\u306f\u7570\u306a\u308b\u30ab\u30c6\u30b4\u30ea\u30fc\u304b\u3089\u9078\u629e\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\")\n    end\n\n    # \u77db\u76fe\u3059\u308b\u9078\u629e\u3092\u30c1\u30a7\u30c3\u30af\n    if has_conflicting_selections?(selected_options)\n      errors.add(:options, \"\u306b\u77db\u76fe\u3059\u308b\u9078\u629e\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059\")\n    end\n  end\n\n  def has_conflicting_selections?(selections)\n    # \u77db\u76fe\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u7d44\u307f\u5408\u308f\u305b\u3092\u30c1\u30a7\u30c3\u30af\u3059\u308b\u30ed\u30b8\u30c3\u30af\n    conflicting_pairs = [\n      [:option_a, :option_b],\n      [:option_c, :option_d]\n    ]\n\n    conflicting_pairs.any? do |pair|\n      selections.map { |s| s[:name] }.intersection(pair).length == 2\n    end\n  end\nend<\/pre>\n\n\n\n<p>\u3053\u308c\u3089\u306e\u5b9f\u88c5\u306b\u3088\u308a\u3001\u4ee5\u4e0b\u306e\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u5bfe\u7b56\u304c\u5b9f\u73fe\u3067\u304d\u307e\u3059\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u4e0d\u6b63\u306a\u5024\u306e\u9001\u4fe1\u9632\u6b62<\/li>\n\n\n\n<li>\u30c7\u30fc\u30bf\u306e\u6574\u5408\u6027\u78ba\u4fdd<\/li>\n\n\n\n<li>\u30d3\u30b8\u30cd\u30b9\u30eb\u30fc\u30eb\u306e\u5f37\u5236<\/li>\n\n\n\n<li>\u30e6\u30fc\u30b6\u30fc\u5165\u529b\u306e\u9069\u5207\u306a\u691c\u8a3c<\/li>\n<\/ol>\n\n\n\n<p>\u307e\u305f\u3001\u30d0\u30ea\u30c7\u30fc\u30b7\u30e7\u30f3\u30a8\u30e9\u30fc\u306e\u5834\u5408\u306f\u9069\u5207\u306a\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u30e6\u30fc\u30b6\u30fc\u306b\u8868\u793a\u3057\u3001UX\u3092\u640d\u306a\u308f\u306a\u3044\u3088\u3046\u914d\u616e\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-15\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5fdc\u7528\u5b9f\u88c5\u30d1\u30bf\u30fc\u30f3<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-16\">\u5168\u9078\u629e\u30fb\u5168\u89e3\u9664\u6a5f\u80fd\u306e\u5b9f\u88c5\u65b9\u6cd5<\/h3>\n\n\n\n<p>\u5168\u9078\u629e\u30fb\u5168\u89e3\u9664\u6a5f\u80fd\u3092\u52b9\u7387\u7684\u306b\u5b9f\u88c5\u3059\u308b\u65b9\u6cd5\u3092\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/controllers\/categories_controller.rb\nclass CategoriesController &lt; ApplicationController\n  def index\n    @categories = Category.all\n  end\nend<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!-- app\/views\/categories\/index.html.erb --&gt;\n&lt;%= form_with(model: @user, class: \"categories-form\") do |form| %&gt;\n  &lt;div class=\"select-all-wrapper\"&gt;\n    &lt;%= check_box_tag 'select_all', \n                      '1', \n                      false, \n                      data: { \n                        controller: \"select-all\",\n                        action: \"change-&gt;select-all#toggle\"\n                      } %&gt;\n    &lt;%= label_tag 'select_all', '\u5168\u3066\u9078\u629e\/\u89e3\u9664' %&gt;\n  &lt;\/div&gt;\n\n  &lt;div class=\"categories-list\" data-select-all-target=\"checkboxes\"&gt;\n    &lt;%= form.collection_check_boxes :category_ids, \n                                  @categories, \n                                  :id, \n                                  :name,\n                                  data: { select_all_target: \"checkbox\" } do |b| %&gt;\n      &lt;div class=\"category-item\"&gt;\n        &lt;%= b.check_box %&gt;\n        &lt;%= b.label %&gt;\n      &lt;\/div&gt;\n    &lt;% end %&gt;\n  &lt;\/div&gt;\n&lt;% end %&gt;<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ app\/javascript\/controllers\/select_all_controller.js\nimport { Controller } from \"@hotwired\/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\"checkbox\"]\n\n  toggle(event) {\n    const checked = event.target.checked\n    this.checkboxTargets.forEach(checkbox =&gt; {\n      checkbox.checked = checked\n      \/\/ \u30ab\u30b9\u30bf\u30e0\u30a4\u30d9\u30f3\u30c8\u306e\u767a\u706b\n      checkbox.dispatchEvent(new Event('change', { bubbles: true }))\n    })\n  }\n\n  \/\/ \u500b\u5225\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u72b6\u614b\u5909\u66f4\u3092\u76e3\u8996\n  checkboxTargetConnected(element) {\n    element.addEventListener('change', () =&gt; this.updateSelectAllState())\n  }\n\n  updateSelectAllState() {\n    const selectAllCheckbox = this.element.querySelector('#select_all')\n    const allChecked = this.checkboxTargets.every(checkbox =&gt; checkbox.checked)\n    const someChecked = this.checkboxTargets.some(checkbox =&gt; checkbox.checked)\n\n    selectAllCheckbox.checked = allChecked\n    selectAllCheckbox.indeterminate = someChecked &amp;&amp; !allChecked\n  }\n}<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-17\">\u30cd\u30b9\u30c8\u3057\u305f\u5c5e\u6027\u3067\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u7ba1\u7406<\/h3>\n\n\n\n<p>\u968e\u5c64\u69cb\u9020\u3092\u6301\u3064\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5b9f\u88c5\u65b9\u6cd5\u3092\u793a\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/department.rb\nclass Department &lt; ApplicationRecord\n  has_many :sub_departments\n  has_many :permissions\nend\n\n# app\/models\/sub_department.rb\nclass SubDepartment &lt; ApplicationRecord\n  belongs_to :department\n  has_many :permissions\nend\n\n# app\/controllers\/permissions_controller.rb\nclass PermissionsController &lt; ApplicationController\n  def edit\n    @departments = Department.includes(:sub_departments).all\n  end\n\n  def update\n    if @user.update(permission_params)\n      redirect_to permissions_path, notice: '\u6a29\u9650\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f'\n    else\n      render :edit\n    end\n  end\n\n  private\n\n  def permission_params\n    params.require(:user).permit(\n      department_permissions: [],\n      sub_department_permissions: []\n    )\n  end\nend<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!-- app\/views\/permissions\/_nested_checkboxes.html.erb --&gt;\n&lt;div class=\"permissions-tree\" data-controller=\"nested-permissions\"&gt;\n  &lt;% @departments.each do |department| %&gt;\n    &lt;div class=\"department-group\"&gt;\n      &lt;%= check_box_tag \"department_#{department.id}\",\n                        department.id,\n                        @user.department_permissions.include?(department.id),\n                        data: {\n                          action: \"nested-permissions#toggleParent\",\n                          department_id: department.id\n                        } %&gt;\n      &lt;%= label_tag \"department_#{department.id}\", department.name %&gt;\n\n      &lt;div class=\"sub-departments\" data-parent-id=\"&lt;%= department.id %&gt;\"&gt;\n        &lt;% department.sub_departments.each do |sub_dept| %&gt;\n          &lt;div class=\"sub-department-item\"&gt;\n            &lt;%= check_box_tag \"sub_department_#{sub_dept.id}\",\n                            sub_dept.id,\n                            @user.sub_department_permissions.include?(sub_dept.id),\n                            data: {\n                              action: \"nested-permissions#toggleChild\",\n                              parent_id: department.id\n                            } %&gt;\n            &lt;%= label_tag \"sub_department_#{sub_dept.id}\", sub_dept.name %&gt;\n          &lt;\/div&gt;\n        &lt;% end %&gt;\n      &lt;\/div&gt;\n    &lt;\/div&gt;\n  &lt;% end %&gt;\n&lt;\/div&gt;<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ app\/javascript\/controllers\/nested_permissions_controller.js\nimport { Controller } from \"@hotwired\/stimulus\"\n\nexport default class extends Controller {\n  toggleParent(event) {\n    const departmentId = event.target.dataset.departmentId\n    const checked = event.target.checked\n    const childCheckboxes = this.element.querySelectorAll(\n      `[data-parent-id=\"${departmentId}\"] input[type=\"checkbox\"]`\n    )\n\n    childCheckboxes.forEach(checkbox =&gt; {\n      checkbox.checked = checked\n      checkbox.dispatchEvent(new Event('change', { bubbles: true }))\n    })\n  }\n\n  toggleChild(event) {\n    const parentId = event.target.dataset.parentId\n    const parentCheckbox = this.element.querySelector(\n      `[data-department-id=\"${parentId}\"]`\n    )\n    const siblings = this.element.querySelectorAll(\n      `[data-parent-id=\"${parentId}\"] input[type=\"checkbox\"]`\n    )\n\n    const allChecked = Array.from(siblings).every(checkbox =&gt; checkbox.checked)\n    const someChecked = Array.from(siblings).some(checkbox =&gt; checkbox.checked)\n\n    parentCheckbox.checked = allChecked\n    parentCheckbox.indeterminate = someChecked &amp;&amp; !allChecked\n  }\n}<\/pre>\n\n\n\n<p>\u3053\u308c\u3089\u306e\u5b9f\u88c5\u306b\u3088\u308a\u3001\u4ee5\u4e0b\u306e\u6a5f\u80fd\u304c\u5b9f\u73fe\u3067\u304d\u307e\u3059\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u76f4\u611f\u7684\u306a\u5168\u9078\u629e\u30fb\u5168\u89e3\u9664\u64cd\u4f5c<\/li>\n\n\n\n<li>\u968e\u5c64\u69cb\u9020\u3092\u6301\u3064\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u9023\u52d5<\/li>\n\n\n\n<li>\u4e2d\u9593\u72b6\u614b\uff08indeterminate\uff09\u306e\u9069\u5207\u306a\u7ba1\u7406<\/li>\n\n\n\n<li>\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u3092\u8003\u616e\u3057\u305f\u5b9f\u88c5<\/li>\n<\/ol>\n\n\n\n<p>\u307e\u305f\u3001\u30a2\u30af\u30bb\u30b7\u30d3\u30ea\u30c6\u30a3\u306b\u3082\u914d\u616e\u3057\u3001\u30ad\u30fc\u30dc\u30fc\u30c9\u64cd\u4f5c\u3084\u30b9\u30af\u30ea\u30fc\u30f3\u30ea\u30fc\u30c0\u30fc\u3067\u3082\u9069\u5207\u306b\u64cd\u4f5c\u3067\u304d\u308b\u3088\u3046\u306b\u5b9f\u88c5\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-18\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30c6\u30b9\u30c8\u5b9f\u88c5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-19\">System Spec\u3067\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u30c6\u30b9\u30c8\u65b9\u6cd5<\/h3>\n\n\n\n<p>RSpec\u3068Capybara\u3092\u4f7f\u7528\u3057\u305f\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30c6\u30b9\u30c8\u5b9f\u88c5\u306b\u3064\u3044\u3066\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># spec\/system\/tasks_spec.rb\nrequire 'rails_helper'\n\nRSpec.describe 'Tasks', type: :system do\n  let!(:task) { create(:task, completed: false) }\n\n  before do\n    driven_by(:selenium_chrome_headless)\n  end\n\n  describe '\u30bf\u30b9\u30af\u5b8c\u4e86\u72b6\u614b\u306e\u5207\u308a\u66ff\u3048' do\n    it '\u5358\u4e00\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u5207\u308a\u66ff\u3048\u3089\u308c\u308b' do\n      visit tasks_path\n\n      # \u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u30af\u30ea\u30c3\u30af\n      find(\"#task_#{task.id}_completed\").click\n\n      # \u975e\u540c\u671f\u66f4\u65b0\u306e\u5b8c\u4e86\u3092\u5f85\u6a5f\n      expect(page).to have_css('.task-completed')\n\n      # DB\u306e\u72b6\u614b\u3092\u78ba\u8a8d\n      expect(task.reload.completed).to be true\n    end\n  end\n\n  describe '\u8907\u6570\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u64cd\u4f5c' do\n    let!(:tasks) { create_list(:task, 3, completed: false) }\n\n    it '\u5168\u9078\u629e\u30dc\u30bf\u30f3\u3067\u5168\u3066\u306e\u30bf\u30b9\u30af\u3092\u5b8c\u4e86\u72b6\u614b\u306b\u3067\u304d\u308b' do\n      visit tasks_path\n\n      # \u5168\u9078\u629e\u30dc\u30bf\u30f3\u3092\u30af\u30ea\u30c3\u30af\n      find('#select_all_tasks').click\n\n      # \u5168\u3066\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u304c\u30c1\u30a7\u30c3\u30af\u3055\u308c\u3066\u3044\u308b\u3053\u3068\u3092\u78ba\u8a8d\n      all('.task-checkbox').each do |checkbox|\n        expect(checkbox).to be_checked\n      end\n\n      # DB\u306e\u72b6\u614b\u3092\u78ba\u8a8d\n      expect(Task.where(completed: true).count).to eq 3\n    end\n  end\nend<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-20\">JavaScript\u3092\u542b\u3080\u30c6\u30b9\u30c8\u306e\u66f8\u304d\u65b9<\/h3>\n\n\n\n<p>JavaScript\u306e\u52d5\u4f5c\u3092\u542b\u3080\u3088\u308a\u8907\u96d1\u306a\u30c6\u30b9\u30c8\u30b1\u30fc\u30b9\u306e\u5b9f\u88c5\u4f8b\u3067\u3059\u3002<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># spec\/system\/categories_spec.rb\nRSpec.describe 'Categories', type: :system do\n  let!(:department) { create(:department) }\n  let!(:sub_departments) { create_list(:sub_department, 3, department: department) }\n\n  describe '\u30cd\u30b9\u30c8\u3055\u308c\u305f\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u64cd\u4f5c', js: true do\n    before { visit edit_permissions_path }\n\n    it '\u89aa\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u9078\u629e\u3067\u5b50\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u304c\u5168\u3066\u9078\u629e\u3055\u308c\u308b' do\n      # \u89aa\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u30af\u30ea\u30c3\u30af\n      find(\"#department_#{department.id}\").click\n\n      # Stimulus\u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u30fc\u306e\u51e6\u7406\u5b8c\u4e86\u3092\u5f85\u6a5f\n      sleep 0.1\n\n      # \u5168\u3066\u306e\u5b50\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u308b\u3053\u3068\u3092\u78ba\u8a8d\n      within(\"[data-parent-id='#{department.id}']\") do\n        all('input[type=\"checkbox\"]').each do |checkbox|\n          expect(checkbox).to be_checked\n        end\n      end\n    end\n\n    it '\u4e00\u90e8\u306e\u5b50\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u9078\u629e\u3067\u89aa\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u304c\u4e2d\u9593\u72b6\u614b\u306b\u306a\u308b' do\n      # \u6700\u521d\u306e\u5b50\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u307f\u9078\u629e\n      first(\"[data-parent-id='#{department.id}'] input[type='checkbox']\").click\n\n      # \u89aa\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u72b6\u614b\u3092\u78ba\u8a8d\n      parent_checkbox = find(\"#department_#{department.id}\")\n      expect(parent_checkbox).not_to be_checked\n      expect(parent_checkbox['indeterminate']).to eq 'true'\n    end\n  end\nend\n\n# spec\/support\/capybara.rb\nRSpec.configure do |config|\n  config.before(:each, type: :system) do\n    driven_by :selenium_chrome_headless\n  end\n\n  config.before(:each, type: :system, js: true) do\n    # JavaScript\u30c6\u30b9\u30c8\u7528\u306e\u8a2d\u5b9a\n    Capybara.default_max_wait_time = 5\n  end\nend<\/pre>\n\n\n\n<p>\u30c6\u30b9\u30c6\u30a3\u30f3\u30b0\u306e\u30d9\u30b9\u30c8\u30d7\u30e9\u30af\u30c6\u30a3\u30b9:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30c6\u30b9\u30c8\u306e\u6e96\u5099<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># spec\/rails_helper.rb\nRSpec.configure do |config|\n  # FactoryBot\u306e\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\n  config.include FactoryBot::Syntax::Methods\n\n  # \u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30af\u30ea\u30fc\u30ca\u30fc\u306e\u8a2d\u5b9a\n  config.before(:suite) do\n    DatabaseCleaner.clean_with(:truncation)\n  end\n\n  config.before(:each) do\n    DatabaseCleaner.strategy = :transaction\n  end\n\n  config.before(:each, js: true) do\n    DatabaseCleaner.strategy = :truncation\n  end\nend<\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>\u30c6\u30b9\u30c8\u7528\u30d8\u30eb\u30d1\u30fc\u30e1\u30bd\u30c3\u30c9\u306e\u4f5c\u6210<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># spec\/support\/checkbox_helpers.rb\nmodule CheckboxHelpers\n  def check_all_visible_checkboxes\n    all('input[type=\"checkbox\"]').each(&amp;:check)\n  end\n\n  def uncheck_all_visible_checkboxes\n    all('input[type=\"checkbox\"]').each(&amp;:uncheck)\n  end\nend\n\nRSpec.configure do |config|\n  config.include CheckboxHelpers, type: :system\nend<\/pre>\n\n\n\n<p>\u3053\u308c\u3089\u306e\u30c6\u30b9\u30c8\u5b9f\u88c5\u306b\u3088\u308a\u3001\u4ee5\u4e0b\u306e\u70b9\u304c\u4fdd\u8a3c\u3055\u308c\u307e\u3059\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u57fa\u672c\u7684\u306a\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u6a5f\u80fd<\/li>\n\n\n\n<li>JavaScript\u3092\u4f7f\u7528\u3057\u305f\u52d5\u7684\u306a\u632f\u308b\u821e\u3044<\/li>\n\n\n\n<li>\u975e\u540c\u671f\u51e6\u7406\u306e\u6b63\u5e38\u52d5\u4f5c<\/li>\n\n\n\n<li>\u30a8\u30c3\u30b8\u30b1\u30fc\u30b9\u306e\u51e6\u7406<\/li>\n<\/ol>\n\n\n\n<p>\u307e\u305f\u3001\u30c6\u30b9\u30c8\u306e\u4fdd\u5b88\u6027\u3068\u53ef\u8aad\u6027\u3092\u9ad8\u3081\u308b\u305f\u3081\u3001\u9069\u5207\u306a\u62bd\u8c61\u5316\u3068\u30d8\u30eb\u30d1\u30fc\u30e1\u30bd\u30c3\u30c9\u306e\u4f7f\u7528\u3092\u5fc3\u304c\u3051\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-21\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5b9f\u88c5\u3067\u3088\u304f\u3042\u308b\u30c8\u30e9\u30d6\u30eb\u5bfe\u51e6\u6cd5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-22\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5024\u304c\u6b63\u3057\u304f\u9001\u4fe1\u3055\u308c\u306a\u3044\u5834\u5408\u306e\u5bfe\u51e6<\/h3>\n\n\n\n<p>\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u5024\u304c\u6b63\u3057\u304f\u9001\u4fe1\u3055\u308c\u306a\u3044\u4e00\u822c\u7684\u306a\u554f\u984c\u3068\u305d\u306e\u89e3\u6c7a\u65b9\u6cd5\u3092\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>hidden_field\u304c\u91cd\u8907\u3057\u3066\u3044\u308b\u5834\u5408\u306e\u554f\u984c<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># \u554f\u984c\u306e\u3042\u308b\u30b3\u30fc\u30c9\n&lt;%= form_with(model: @task) do |f| %&gt;\n  &lt;%= hidden_field_tag 'task[completed]', '0' %&gt;  # \u4e0d\u8981\u306ahidden_field\n  &lt;%= f.check_box :completed %&gt;  # form_with\u304c\u81ea\u52d5\u7684\u306bhidden_field\u3092\u751f\u6210\n&lt;% end %&gt;\n\n# \u6b63\u3057\u3044\u5b9f\u88c5\n&lt;%= form_with(model: @task) do |f| %&gt;\n  &lt;%= f.check_box :completed %&gt;  # \u3053\u308c\u3060\u3051\u3067\u5341\u5206\n&lt;% end %&gt;<\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>JavaScript\u30a4\u30d9\u30f3\u30c8\u306e\u4f1d\u64ad\u554f\u984c<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ \u554f\u984c\u306e\u3042\u308b\u30b3\u30fc\u30c9\ndocument.querySelector('.checkbox-wrapper').addEventListener('click', (e) =&gt; {\n  e.preventDefault()  \/\/ \u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30af\u30ea\u30c3\u30af\u30a4\u30d9\u30f3\u30c8\u3082\u6b62\u3081\u3066\u3057\u307e\u3046\n  \/\/ \u51e6\u7406\n})\n\n\/\/ \u6b63\u3057\u3044\u5b9f\u88c5\ndocument.querySelector('.checkbox-wrapper').addEventListener('click', (e) =&gt; {\n  if (e.target.type !== 'checkbox') {\n    e.preventDefault()\n  }\n  \/\/ \u51e6\u7406\n})<\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li>Turbo\u306b\u3088\u308b\u554f\u984c\u306e\u89e3\u6c7a<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/controllers\/tasks_controller.rb\nclass TasksController &lt; ApplicationController\n  def update\n    @task = Task.find(params[:id])\n\n    respond_to do |format|\n      if @task.update(task_params)\n        format.turbo_stream {\n          render turbo_stream: turbo_stream.replace(\n            @task,\n            partial: 'tasks\/task',\n            locals: { task: @task }\n          )\n        }\n        format.html { redirect_to @task }\n      else\n        format.html { render :edit }\n      end\n    end\n  end\nend<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-23\">\u8907\u6570\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3067\u7279\u5b9a\u306e\u5024\u3060\u3051\u9001\u4fe1\u3055\u308c\u306a\u3044\u554f\u984c\u306e\u89e3\u6c7a<\/h3>\n\n\n\n<p>\u8907\u6570\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3067\u3088\u304f\u767a\u751f\u3059\u308b\u554f\u984c\u3068\u305d\u306e\u89e3\u6c7a\u65b9\u6cd5\u3067\u3059\u3002<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Strong Parameters\u306e\u8a2d\u5b9a\u30df\u30b9<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/controllers\/users_controller.rb\nclass UsersController &lt; ApplicationController\n  private\n\n  # \u554f\u984c\u306e\u3042\u308b\u30b3\u30fc\u30c9\n  def user_params\n    params.require(:user).permit(:name, :email, preference_ids: [])\n  end\n\n  # \u6b63\u3057\u3044\u5b9f\u88c5\n  def user_params\n    params.require(:user).permit(:name, :email, { preference_ids: [] })\n  end\nend<\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>\u30d5\u30a9\u30fc\u30e0\u306e\u5b9f\u88c5\u30df\u30b9<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!-- \u554f\u984c\u306e\u3042\u308b\u30b3\u30fc\u30c9 --&gt;\n&lt;%= form_with(model: @user) do |f| %&gt;\n  &lt;% @preferences.each do |preference| %&gt;\n    &lt;%= check_box_tag \"preferences[]\", preference.id %&gt;  &lt;!-- \u540d\u524d\u306e\u6307\u5b9a\u304c\u4e0d\u9069\u5207 --&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;\n\n&lt;!-- \u6b63\u3057\u3044\u5b9f\u88c5 --&gt;\n&lt;%= form_with(model: @user) do |f| %&gt;\n  &lt;% @preferences.each do |preference| %&gt;\n    &lt;%= f.check_box :preference_ids, \n                    { multiple: true }, \n                    preference.id, \n                    nil %&gt;\n  &lt;% end %&gt;\n&lt;% end %&gt;<\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li>\u914d\u5217\u30d1\u30e9\u30e1\u30fc\u30bf\u306e\u51e6\u7406<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/user.rb\nclass User &lt; ApplicationRecord\n  # \u554f\u984c\u306e\u3042\u308b\u5b9f\u88c5\n  def preference_ids=(ids)\n    super(ids.reject(&amp;:blank?))  # blank\u306a\u5024\u3082\u542b\u3081\u3066\u51e6\u7406\u3059\u3079\u304d\n  end\n\n  # \u6b63\u3057\u3044\u5b9f\u88c5\n  def preference_ids=(ids)\n    super(Array(ids).reject(&amp;:nil?))  # nil\u306e\u307f\u9664\u5916\n  end\nend<\/pre>\n\n\n\n<p>\u3088\u304f\u3042\u308b\u30c8\u30e9\u30d6\u30eb\u306e\u56de\u907f\u7b56\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30c7\u30d0\u30c3\u30b0\u6642\u306e\u30c1\u30a7\u30c3\u30af\u30dd\u30a4\u30f3\u30c8<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># config\/environments\/development.rb\nRails.application.configure do\n  # \u30d1\u30e9\u30e1\u30fc\u30bf\u306e\u30ed\u30b0\u51fa\u529b\u3092\u8a73\u7d30\u306b\u3059\u308b\n  config.filter_parameters = []\nend\n\n# app\/controllers\/application_controller.rb\nclass ApplicationController &lt; ActionController::Base\n  before_action :debug_parameters, if: -&gt; { Rails.env.development? }\n\n  private\n\n  def debug_parameters\n    puts \"Parameters: #{params.inspect}\"\n  end\nend<\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u72b6\u614b\u78ba\u8a8d<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ development\u74b0\u5883\u3067\u306e\u554f\u984c\u7279\u5b9a\u7528\ndocument.addEventListener('change', (e) =&gt; {\n  if (e.target.type === 'checkbox') {\n    console.log({\n      name: e.target.name,\n      value: e.target.value,\n      checked: e.target.checked,\n      form: e.target.form.serialize()\n    })\n  }\n})<\/pre>\n\n\n\n<p>\u3053\u308c\u3089\u306e\u5bfe\u51e6\u6cd5\u306b\u3088\u308a\u3001\u4ee5\u4e0b\u306e\u554f\u984c\u304c\u89e3\u6c7a\u3067\u304d\u307e\u3059\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30d1\u30e9\u30e1\u30fc\u30bf\u306e\u9001\u4fe1\u6f0f\u308c<\/li>\n\n\n\n<li>\u5024\u306e\u578b\u5909\u63db\u306e\u554f\u984c<\/li>\n\n\n\n<li>JavaScript\u306e\u7af6\u5408<\/li>\n\n\n\n<li>Turbo\u306b\u3088\u308b\u975e\u540c\u671f\u66f4\u65b0\u306e\u554f\u984c<\/li>\n<\/ol>\n\n\n\n<p>\u307e\u305f\u3001\u958b\u767a\u74b0\u5883\u3067\u306e\u554f\u984c\u7279\u5b9a\u3092\u5bb9\u6613\u306b\u3059\u308b\u305f\u3081\u306e\u30c7\u30d0\u30c3\u30b0\u624b\u6cd5\u3082\u5b9f\u88c5\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"i-24\">\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u306e\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u6700\u9069\u5316<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-25\">\u5927\u91cf\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u6271\u3046\u969b\u306e\u6700\u9069\u5316\u30c6\u30af\u30cb\u30c3\u30af<\/h3>\n\n\n\n<p>\u5927\u91cf\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u52b9\u7387\u7684\u306b\u51e6\u7406\u3059\u308b\u305f\u3081\u306e\u5b9f\u88c5\u65b9\u6cd5\u3092\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u6bb5\u968e\u7684\u8aad\u307f\u8fbc\u307f\u306e\u5b9f\u88c5<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/controllers\/categories_controller.rb\nclass CategoriesController &lt; ApplicationController\n  def index\n    @categories = Category.includes(:subcategories)\n                         .order(:name)\n                         .page(params[:page])\n                         .per(50)\n  end\n\n  def load_more\n    @categories = Category.includes(:subcategories)\n                         .order(:name)\n                         .page(params[:page])\n                         .per(50)\n\n    render partial: 'category_checkboxes', locals: { categories: @categories }\n  end\nend<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;!-- app\/views\/categories\/_category_checkboxes.html.erb --&gt;\n&lt;div class=\"categories-container\" \n     data-controller=\"infinite-scroll\"\n     data-infinite-scroll-url-value=\"&lt;%= load_more_categories_path %&gt;\"\n     data-infinite-scroll-page-value=\"&lt;%= @categories.current_page %&gt;\"&gt;\n\n  &lt;div class=\"checkboxes-grid\" data-infinite-scroll-target=\"container\"&gt;\n    &lt;%= render partial: 'category', collection: @categories %&gt;\n  &lt;\/div&gt;\n\n  &lt;div data-infinite-scroll-target=\"loading\" class=\"hidden\"&gt;\n    Loading more...\n  &lt;\/div&gt;\n&lt;\/div&gt;<\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>\u30e1\u30e2\u30ea\u4f7f\u7528\u91cf\u306e\u6700\u9069\u5316<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/models\/category.rb\nclass Category &lt; ApplicationRecord\n  # \u30d0\u30c3\u30c1\u51e6\u7406\u3067\u306e\u6700\u9069\u5316\n  def self.update_selected_status(category_ids)\n    transaction do\n      where(id: category_ids).find_each(batch_size: 1000) do |category|\n        category.update_column(:selected, true)\n      end\n      where.not(id: category_ids).find_each(batch_size: 1000) do |category|\n        category.update_column(:selected, false)\n      end\n    end\n  end\nend<\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li>\u30d5\u30ed\u30f3\u30c8\u30a8\u30f3\u30c9\u306e\u6700\u9069\u5316<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/ app\/javascript\/controllers\/infinite_scroll_controller.js\nimport { Controller } from \"@hotwired\/stimulus\"\nimport { debounce } from \"lodash\"\n\nexport default class extends Controller {\n  static targets = [\"container\", \"loading\"]\n  static values = {\n    url: String,\n    page: Number,\n    loading: Boolean\n  }\n\n  initialize() {\n    this.scroll = debounce(this.scroll.bind(this), 200)\n  }\n\n  connect() {\n    window.addEventListener('scroll', this.scroll)\n  }\n\n  disconnect() {\n    window.removeEventListener('scroll', this.scroll)\n  }\n\n  async scroll() {\n    if (this.loadingValue) return\n\n    const bottom = this.element.getBoundingClientRect().bottom\n    const windowHeight = window.innerHeight\n\n    if (bottom &lt;= windowHeight + 100) {\n      this.loadingValue = true\n      this.loadingTarget.classList.remove('hidden')\n\n      try {\n        const response = await fetch(`${this.urlValue}?page=${this.pageValue + 1}`)\n        const html = await response.text()\n\n        if (html.trim()) {\n          this.containerTarget.insertAdjacentHTML('beforeend', html)\n          this.pageValue++\n        }\n      } finally {\n        this.loadingValue = false\n        this.loadingTarget.classList.add('hidden')\n      }\n    }\n  }\n}<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"i-26\">N+1\u554f\u984c\u306e\u56de\u907f\u65b9\u6cd5<\/h3>\n\n\n\n<p>N+1\u554f\u984c\u3092\u56de\u907f\u3059\u308b\u305f\u3081\u306e\u6700\u9069\u5316\u624b\u6cd5\u3092\u89e3\u8aac\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u9069\u5207\u306a\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u8a2d\u5b9a<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># db\/migrate\/20240404000000_add_indexes_to_categories.rb\nclass AddIndexesToCategories &lt; ActiveRecord::Migration[7.1]\n  def change\n    add_index :categories, :parent_id\n    add_index :categories, :selected\n    add_index :category_selections, [:user_id, :category_id], unique: true\n  end\nend<\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li>\u30af\u30a8\u30ea\u306e\u6700\u9069\u5316<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/controllers\/categories_controller.rb\nclass CategoriesController &lt; ApplicationController\n  def index\n    @categories = Category.includes(:subcategories, :permissions)\n                         .where(parent_id: nil)\n                         .order(:name)\n\n    @selected_categories = current_user.categories.pluck(:id)\n  end\nend\n\n# app\/models\/category.rb\nclass Category &lt; ApplicationRecord\n  scope :with_selection_status, -&gt;(user) {\n    joins(\"LEFT JOIN category_selections ON categories.id = category_selections.category_id AND category_selections.user_id = #{user.id}\")\n    .select(\"categories.*, CASE WHEN category_selections.id IS NOT NULL THEN true ELSE false END as selected\")\n  }\nend<\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li>\u30ad\u30e3\u30c3\u30b7\u30e5\u306e\u6d3b\u7528<\/li>\n<\/ol>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># app\/views\/categories\/_category.html.erb\n&lt;% cache [category, current_user.updated_at] do %&gt;\n  &lt;div class=\"category-item\"&gt;\n    &lt;%= check_box_tag \"category_ids[]\",\n                      category.id,\n                      @selected_categories.include?(category.id),\n                      data: { \n                        action: \"change-&gt;category#updateSelection\",\n                        category_id: category.id\n                      } %&gt;\n    &lt;%= label_tag \"category_#{category.id}\", category.name %&gt;\n  &lt;\/div&gt;\n&lt;% end %&gt;<\/pre>\n\n\n\n<p>\u30d1\u30d5\u30a9\u30fc\u30de\u30f3\u30b9\u6700\u9069\u5316\u306e\u30d9\u30b9\u30c8\u30d7\u30e9\u30af\u30c6\u30a3\u30b9\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u6700\u9069\u5316<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u9069\u5207\u306a\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u8a2d\u5b9a<\/li>\n\n\n\n<li>\u5fc5\u8981\u306a\u30ab\u30e9\u30e0\u306e\u307f\u306e\u53d6\u5f97<\/li>\n\n\n\n<li>\u30d0\u30c3\u30c1\u51e6\u7406\u306e\u6d3b\u7528<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30ec\u30d9\u30eb\u306e\u6700\u9069\u5316<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>N+1\u30af\u30a8\u30ea\u306e\u56de\u907f<\/li>\n\n\n\n<li>\u30ad\u30e3\u30c3\u30b7\u30e5\u306e\u9069\u5207\u306a\u5229\u7528<\/li>\n\n\n\n<li>\u30e1\u30e2\u30ea\u4f7f\u7528\u91cf\u306e\u5236\u5fa1<\/li>\n<\/ul>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u30d5\u30ed\u30f3\u30c8\u30a8\u30f3\u30c9\u306e\u6700\u9069\u5316<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u9045\u5ef6\u8aad\u307f\u8fbc\u307f\u306e\u5b9f\u88c5<\/li>\n\n\n\n<li>\u30a4\u30d9\u30f3\u30c8\u306e\u6700\u9069\u5316\uff08\u30c7\u30d0\u30a6\u30f3\u30b9\u51e6\u7406\uff09<\/li>\n\n\n\n<li>DOM\u64cd\u4f5c\u306e\u6700\u5c0f\u5316<\/li>\n<\/ul>\n\n\n\n<p>\u3053\u308c\u3089\u306e\u6700\u9069\u5316\u306b\u3088\u308a\u3001\u5927\u91cf\u306e\u30c1\u30a7\u30c3\u30af\u30dc\u30c3\u30af\u30b9\u3092\u6271\u3046\u5834\u5408\u3067\u3082\u30b9\u30e0\u30fc\u30ba\u306a\u64cd\u4f5c\u304c\u53ef\u80fd\u306b\u306a\u308a\u307e\u3059\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Warning: Undefined array key &#8220;is_admin&#8221; in \/home\/xs392991\/dexall.co.jp\/public_html\/articles\/wp-content\/themes\/ &#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":{"0":"post-1366","1":"post","2":"type-post","3":"status-publish","4":"format-standard","6":"category-ruby","7":"nothumb"},"_links":{"self":[{"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=\/wp\/v2\/posts\/1366","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1366"}],"version-history":[{"count":2,"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=\/wp\/v2\/posts\/1366\/revisions"}],"predecessor-version":[{"id":1399,"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=\/wp\/v2\/posts\/1366\/revisions\/1399"}],"wp:attachment":[{"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1366"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1366"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dexall.co.jp\/articles\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1366"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}