Rails7.1 + Ruby3.3サーバー構築


環境構築にあたっての前提条件

(1)バージョン

ruby
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]

rails
Rails 7.1.3.2

その他の環境構築方法については、Rails7 + Ruby3.1 + Bootstrap5 サーバー構築を参照してください


Rails7へのBootstrapの導入方法(2024/5/14時点)

(1)ネットにはRails newするときに、bootstrapを指定するとよいとありますが、これはNode.jsのインストールが必要で、2024/5/9時点でうまく動きません。

$ rails new myapp --css=bootstrap

(2)以下の方法でうまくいきました

①Gemfileに以下を追加

gem 'bootstrap-sass'
gem "sassc-rails"

②app\assets\stylesheetsのapplication.cssをapplication.scssにリネームして、内容をすべて削除して以下を追加

@import 'bootstrap';

③プリコンパイル

$ rails assets:precompile

注意)2024/5/14追記 上記でのインストールだとBootstrapはバージョン3が入ってしまいます

(3)バージョン5対応

①Gemfileに以下を追加

gem 'bootstrap', '~> 5.1'
gem "sassc-rails"

②app\assets\stylesheetsのapplication.cssをapplication.scssにリネームして、内容をすべて削除して以下を追加

@import 'bootstrap';

③プリコンパイル

$ rails assets:precompile

Rails7へのJQueryの導入方法(2024/5/17時点)

JQueryのRails7への導入方法は色々あるようですが、今回はRails7で標準のimportmapを使用します。この方法だとNode.jsをインストールしなくてすみます。

(1)JQueryの導入

①JavaScriptの準備を以下のコマンドを入力して行います。importmapでJavaScriptを使用するのに必要なファイル等が作成されます。

$ rails importmap:install

実行結果

apply  /root/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/importmap-rails-2.0.1/lib/install/install.rb
Add Importmap include tags in application layout
    insert    app/views/layouts/application.html.erb
Create application.js module as entrypoint
    create    app/javascript/application.js
Use vendor/javascript for downloaded pins
    create    vendor/javascript
    create    vendor/javascript/.keep
Ensure JavaScript files are in the Sprocket manifest
    append    app/assets/config/manifest.js
Configure importmap paths in config/importmap.rb
    create    config/importmap.rb
Copying binstub
    create    bin/importmap
       run  bundle install

②以下のコマンドでconfig/importmap.rbにJQueryを指定します。

$ bin/importmap pin jquery

なお今回はRocky9(Linux)で試しましたが、Https通信がエラーになっているようなので、手で以下の内容を入力しました。

エラー内容

/root/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/importmap-rails-2.0.1/lib/importmap/packager.rb:69:in `rescue in post_json': 
Unexpected transport error (Net::OpenTimeout: Failed to open TCP connection to api.jspm.io:443 (execution expired)) (Importmap::Packager::HTTPError)

config/importmap.rb

pin "application", preload: true
pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.7.1/dist/jquery.js"

③JSファイルで使用できるようにapp/javascript/application.jsに以下を指定します。

import jquery from "jquery"
window.$ = jquery

(2)自作のJQueryを使用する

①/app/javascript配下にcustomフォルダを作成します(名前は任意)

②/app/javascript/customフォルダに自作のJSファイルを作成します

例)test.js

// フォームロード時の処理
$(window).on('load', function () {
	alert("フォームロード");
}); // フォームロード時の処理終了

③/app/views/layouts/application.html.erbに以下の行を追加します(:jsという名前は任意)

<!DOCTYPE html>
<html>
  <head>
    <title>TestPg4
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
<!-- 追加開始 -->
	<%= yield (:js) %>
<!-- 追加終了 -->
  </head>
  <body>
    <%= yield %>
  </body>
</html>

④上記jsを導入したいViewに以下のように追加します

<p style="color: green"><%= notice %></p>
<h1>Webmemos</h1>
<div id="webmemos">
  <% @webmemos.each do |webmemo| %>
    <%= render webmemo %>
    <p>
      <%= link_to "Show this webmemo", webmemo %>
    </p>
  <% end %>
</div>
<%= link_to "New webmemo", new_webmemo_path %>

<!-- 追加開始 -->
<% content_for :js do %>
   <%= javascript_import_module_tag "custom/test" %>
 <% end %>
<!-- 追加終了 -->

Rails7へのVue.jsの導入方法

①config/importmap.rbに以下の2行を追加します

# 最終行に次の2行を追加 
pin "vue", to: "https://ga.jspm.io/npm:vue@3.2.45/dist/vue.esm-browser.js", preload: true 
pin_all_from "app/javascript/components", under: "components"

②app/javascript/componentsにvue.jsのファイルを配置します(template部分はRailsのscaffoldで作成したコードを使用し、修正)

例)work_msts_index.js

import * as Vue from "vue"
const WorkMstsIndex = {
  template: `
   <div class="card border-info table-responsive">
      <table class="table table-striped table-bordered table-hover table-sm">
         <thead>
         <tr class="bg-primary text-white">
            <td width="100px" class="bg-primary text-white"><label>作業コード</label></td>
            <td width="100px" class="bg-primary text-white"><label>作業名</label></td>
            <td width="100px" class="bg-primary text-white"><label>備考</label></td>
            <td width="50px" class="bg-primary text-white"></td>
            <td width="50px" class="bg-primary text-white"></td>
            <td width="50px" class="bg-primary text-white"></td>
         </tr>
         </thead>
      <!-- 表本体   -->
         <tbody>
         <tr v-for="data in dataes">
            <td>{{data.work_cd}}</td>
            <td>{{data.name}}</td>
            <td>{{data.note}}</td>
            <td class="fcenter">
               <a class="btn btn-primary btn-sm text-white" :href="'/work_msts/' + data.id">
                  <i class="bi bi-info-circle-fill" aria-hidden="true"></i> 詳細
               </a>
            </td>
            <td class="fcenter">
               <form data-turbo-confirm="本当に削除してよろしいですか?" class="button_to" method="post" :action="'/work_msts/' + data.id">
                  <input type="hidden" name="_method" value="delete" autocomplete="off" />
                  <button class="btn btn-danger btn-sm" type="submit">
                     <i class="bi bi-trash" aria-hidden="true"></i> 削除
                  </button>
                  <input type="hidden" name="authenticity_token" value="iPZlKdNww4uxtyXxF4KXHx8_ZyxA3ZLlekhzv_STHoyvOpibSi645D15p9KL60pi6MbL35Tm_ac_MSK6zjugtA" autocomplete="off" />
               </form>
            </td>
            <td class="fcenter">
               <a class="btn btn-primary btn-sm" :href="'/work_msts/' + data.id +  '/edit'">
                  <i class="bi bi-pencil-square" aria-hidden="true"></i> 編集
               </a>
            </td>
         </tr>
         </tbody>
      </table>
   </div>
  `,
  data(){
    return {
      dataes: [],
      selected:      0,
      data: {}
    }
  },
  created: function(){
    this.getData();
  },
  methods: {
    getData() {
      fetch('/work_msts.json')
        .then(response => response.json())
        .then(data => {
          this.dataes = data,
          console.log(data)
        })
    },
  }
};
const app = Vue.createApp({
  components: {
    'work_msts_index': WorkMstsIndex,
  },
});
app.mount('#work_msts_index');

③app/javascript/application.jsに上記で作成したjsファイルを指定します

import "./components/work_msts_index"

④app/viewsのviewファイルにvue.jsがtemplateを展開するコードを記述します

例)app/views/work_msts/index.html.erb(比較のため、前半部分はRailsのscaffoldで作成したコードを記述しています)

fetch('/work_msts.json')はviews/work_msts/index.json.jbuilderでデータを取得しています。その取得したデータをjs内のdataesに格納して、そのあとtemplateでhtmlに展開しています

<!-- RailsのScaffoldで作成開始 -->
<div class="container">
   <div class="content-title">
      <h3><span class="title-style"><%= label :work_mst, :table_name   %><label>一覧</label></span></h3>
   </div>
   <!-- メッセージ領域 -->
   <%= render partial: 'shared/message' %>
   <!-- ボタン領域 -->
   <div class="align-right padding-bottom5">
       <%= link_to new_work_mst_path, class: "btn btn-primary" do %>
         <i class="bi bi-plus-lg" aria-hidden="true"></i> 新規
       <% end %>
       <%= link_to menu_index_path, class: "btn btn-success" do %>
         <i class="bi bi-x-lg" aria-hidden="true"></i> 戻る
       <% end %>
   </div>
   <!-- 表見出し   -->
   <div class="card border-info table-responsive">
      <table class="table table-striped table-bordered table-hover table-sm">
         <thead>
         <tr class="bg-primary text-white">
            <td width="100px" class="bg-primary text-white"><%= label :work_mst, :work_cd %></td>
            <td width="100px" class="bg-primary text-white"><%= label :work_mst, :name %></td>
            <td width="100px" class="bg-primary text-white"><%= label :work_mst, :note %></td>
            <td width="50px" class="bg-primary text-white"></td>
            <td width="50px" class="bg-primary text-white"></td>
            <td width="50px" class="bg-primary text-white"></td>
         </tr>
         </thead>
      <!-- 表本体   -->
      <% @work_msts.each do |work_mst| %>
         <tr>
            <td><%= work_mst.work_cd %></td>
            <td><%= work_mst.name %></td>
            <td><%= work_mst.note %></td>
            <td class="fcenter">
               <%= link_to work_mst, class: "btn btn-primary btn-sm" do %>
                  <i class="bi bi-info-circle-fill" aria-hidden="true"></i> 詳細
               <% end %>
            </td>
            <td class="fcenter">
               <%= button_to work_mst, {method: :delete, form: {data: {turbo_confirm: '本当に削除してよろしいですか?' }},class: "btn btn-danger btn-sm"} do %>
                  <i class="bi bi-trash" aria-hidden="true"></i> 削除
               <% end %>
            </td>
            <td class="fcenter">
               <%= link_to edit_work_mst_path(work_mst), class: "btn btn-primary btn-sm" do %>
                  <i class="bi bi-pencil-square" aria-hidden="true"></i> 編集
               <% end %>
            </td>
         </tr>
      <% end %>
      </table>
   </div>
   <!--ページネート-->
   <div class="pagenate">
      <ul class="pagenation justify-content-center">
         <%= will_paginate(@work_msts, previous_label: '前へ', next_label: '次へ', renderer: WillPaginate::ActionView::BootstrapLinkRenderer) %>
      </ul>
   </div>
<!-- RailsのScaffoldで作成終了 -->

<!-- Vue.js追加開始 -->
  <div id="work_msts_index">
    <work_msts_index></work_msts_index>
  </div>
<!-- Vue.js追加終了 -->
</div>

⑤実行結果

上の表示が従来のRailsで表示したもので、下の表示がvue.jsで表示したものになります