Rails メモ


■Rails をはじめよう

・booksアプリケーションを作成する
kokaki@skynew:~/www/rails$ rails new books
kokaki@skynew:~/www/rails$ cd books

・Webサーバーを起動する
kokaki@skynew:~/www/rails/books$ rails server

・Railsで「Hello」と表示する
kokaki@skynew:~/www/rails/books$ vi config/routes.rb

  Rails.application.routes.draw do
    get "/books", to: "books#index"
  end
        
kokaki@skynew:~/www/rails/books$ rails generate controller Books index --skip-routes
kokaki@skynew:~/www/rails/books$ vi app/views/books/index.html.erb

  <h1>Hello, Rails!</h1>
        
kokaki@skynew:~/www/rails/books$ rails s

http://localhost:3000/books

・アプリケーションのHomeページを設定する
kokaki@skynew:~/www/rails/books$ vi config/routes.rb

  Rails.application.routes.draw do
    root "books#index"

    get "/books", to: "books#index"
  end
        
kokaki@skynew:~/www/rails/books$ rails s

http://localhost:3000/books

・モデルを生成する
kokaki@skynew:~/www/rails/books$ rails generate model Book title:string author:text outline:text image:string
- タイムスタンプのデフォルト値を設定
kokaki@skynew:~/www/rails/books$ vi db/migrate/20240201001623_create_books.rb

        :
        t.timestamps null: false, default: ->{ "CURRENT_TIMESTAMP" }
        :
        
kokaki@skynew:~/www/rails/books$ rails db:migrate

kokaki@skynew:~/www/rails/books$ sqlite3 db/development.sqlite3

  SQLite version 3.37.2 2022-01-06 13:25:41
  Enter ".help" for usage hints.
  sqlite> .schema books
  CREATE TABLE IF NOT EXISTS "books" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar, "author"
  text, "outline" text, "image" varchar, "created_at" datetime(6) DEFAULT CURRENT_TIMESTAMP NOT NULL, "updated_at"
  datetime(6) DEFAULT CURRENT_TIMESTAMP NOT NULL);
  sqlite> .q
        
kokaki@skynew:~/www/rails/books$ rails console

  Loading development environment (Rails 7.0.8)

  irb(main):001> book = Book.new(title: "水滸伝 一 曙光の章", author: "北方謙三", outline:
  "十二世紀の中国、北宋末期。重税と暴政のために国は乱れ、民は困窮していた。その腐敗した政府を倒そうと、立ち上がった者たちがいた――。
  世直しへの強い志を胸に、漢(おとこ)たちは圧倒的な官軍に挑んでいく。地位を捨て、愛する者を失い、そして自らの命を懸けて闘う。
  彼らの熱き生きざまを刻む壮大な物語が、いま幕を開ける。第九回司馬遼太郎賞を受賞した世紀の傑作、待望の電子書籍版配信開始。",
  image: "suikoden_01.jpg")

  irb(main):002> book.save
  TRANSACTION (0.1ms) begin transaction
  Book Create (0.6ms) INSERT INTO "books" ("title", "author", "outline", "image", "created_at", "updated_at")
  VALUES (?, ?, ?, ?, ?, ?) [["title", "水滸伝 一 曙光の章"], ["author", "北方謙三"], ["outline", "十二世紀の中国、北宋末期。
  重税と暴政のために国は乱れ、民は困窮していた。その腐敗した政府を倒そうと、立ち上がった者たちがいた――。世直しへの強い志を胸に、
  漢(おとこ)たちは圧倒的な官軍に挑んでいく。地位を捨て、愛する者を失い、そして自らの命を懸けて闘う。彼らの熱き生きざまを刻む壮大な物語が、いま幕を開ける。
  第九回司馬遼太郎賞を受賞した世紀の傑作、待望の電子書籍版配信開始。"],
  ["image", "suikoden_01.jpg"], ["created_at", "2024-02-01 00:27:51.452471"], ["updated_at", "2024-02-01
  00:27:51.452471"]]
  TRANSACTION (8.4ms) commit transaction
  => true

  irb(main):003> Book.find(1)
  Book Load (0.2ms) SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
  =>
  #<Book:0x00007f8d1b7b36d0
  id: 1,
  title: "水滸伝 一 曙光の章",
  author: "北方謙三",
  outline:
  "十二世紀の中国、北宋末期。重税と暴政のために国は乱れ、民は困窮していた。その腐敗した政府を倒そうと、立ち上がった者たちがいた――。
  世直しへの強い志を胸に、漢(おとこ)たちは圧倒的な官軍に挑んでいく。地位を捨て、愛する者を失い、そして自らの命を懸けて闘う。
  彼らの熱き生きざまを刻む壮大な物語が、いま幕を開ける。第九回司馬遼太郎賞を受賞した世紀の傑作、待望の電子書籍版配信開始。",
  image: "suikoden_01.jpg",
  created_at: Thu, 01 Feb 2024 00:27:51.452471000 UTC +00:00,
  updated_at: Thu, 01 Feb 2024 00:27:51.452471000 UTC +00:00>

  irb(main):004> Book.all
  Book Load (0.2ms) SELECT "books".* FROM "books"
  =>
  [#<Book:0x00007f8d1a831c48
  id: 1,
  title: "水滸伝 一 曙光の章",
  author: "北方謙三",
  outline:
  "十二世紀の中国、北宋末期。重税と暴政のために国は乱れ、民は困窮していた。その腐敗した政府を倒そうと、立ち上がった者たちがいた――。
  世直しへの強い志を胸に、漢(おとこ)たちは圧倒的な官軍に挑んでいく。地位を捨て、愛する者を失い、そして自らの命を懸けて闘う。
  彼らの熱き生きざまを刻む壮大な物語が、いま幕を開ける。第九回司馬遼太郎賞を受賞した世紀の傑作、待望の電子書籍版配信開始。",
  image: "suikoden_01.jpg",
  created_at: Thu, 01 Feb 2024 00:27:51.452471000 UTC +00:00,
  updated_at: Thu, 01 Feb 2024 00:27:51.452471000 UTC +00:00>]
  irb(main):005> quit
        
・記事のリストを表示する
kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  class BooksController < ApplicationController
    def index
      @books = Book.all
    end
  end
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/index.html.erb

  <h1>Books</h1>

  <ul>
    <% @books.each do |book| %>
      <li>
        <%= book.title %>
      </li>
    <% end %>
  </ul>
        
kokaki@skynew:~/www/rails/books$ rails s

http://localhost:3000/

・記事を1件表示する
kokaki@skynew:~/www/rails/books$ vi config/routes.rb

  Rails.application.routes.draw do
    root "books#index"

    get "/books", to: "books#index"
    get "/books/:id", to: "books#show"
  end
        
kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  class BooksController < ApplicationController
    def index
      @books = Book.all
    end

    def show
      @book = Book.find(params[:id])
    end
  end
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/show.html.erb

  <h1><%= @book.title %></h1>

  <p><%= @book.author %></p>

  <p><%= @book.outline %></p>
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/index.html.erb

  <h1>Books</h1>

  <ul>
    <% @books.each do |book| %>
      <li>
        <a href="/books/<%= book.id %>">
          <%= book.title %>
        </a>
      </li>
    <% end %>
  </ul>
        
・リソースフルルーティング
kokaki@skynew:~/www/rails/books$ vi config/routes.rb

  Rails.application.routes.draw do
    root "books#index"

    resources :books
  end
        
kokaki@skynew:~/www/rails/books$ rails routes

          Prefix Verb   URI Pattern
          Controller#Action
                    root GET    /
          books#index
                   books GET    /books(.:format)
          books#index
                         POST   /books(.:format)
          books#create
                new_book GET    /books/new(.:format)
          books#new
               edit_book GET    /books/:id/edit(.:format)
          books#edit
                    book GET    /books/:id(.:format)
          books#show
                         PATCH  /books/:id(.:format)
          books#update
                         PUT    /books/:id(.:format)
          books#update
                         DELETE /books/:id(.:format)
          books#destroy
turbo_recede_historical_location GET    /recede_historical_location(.:format)
          turbo/native/navigation#recede
turbo_resume_historical_location GET    /resume_historical_location(.:format)
        :
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/index.html.erb

  <h1>Books</h1>

  <ul>
    <% @books.each do |book| %>
      <li>
        <%= link_to book.title, book %> [<%= book.author %>]
      </li>
    <% end %>
  </ul>
        

・記事を1件作成する

kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  class BooksController < ApplicationController
    def index
      @books = Book.all
    end

    def show
      @book = Book.find(params[:id])
    end

    def new
      @book = Book.new
    end

    def create
      @book = Book.new(book_params)

      if @book.save
        redirect_to @book
      else
        render :new, status: :unprocessable_entity
      end
    end

    private
      def book_params
        params.require(:book).permit(:title, :author, :outline, :image)
      end
  end
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/new.html.erb

  <h1>New Book</h1>

  <%= form_with model: @book do |form| %>
    <div>
      <%= form.label :title %><br>
      <%= form.text_field :title %>
    </div>

    <div>
      <%= form.label :author %><br>
      <%= form.text_field :author %>
    </div>

    <div>
      <%= form.label :outline %><br>
      <%= form.text_area :outline %>
    </div>

    <div>
      <%= form.label :image %><br>
      <%= form.text_field :image %>
    </div>

    <div>
      <%= form.submit %>
    </div>
  <% end %>
        
・バリデーションとエラーメッセージの表示
kokaki@skynew:~/www/rails/books$ vi app/models/book.rb

  class Book < ApplicationRecord
    validates :title, presence: true
    validates :author, presence: true #, length: { minimum: 10 }
  end
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/new.html.erb

  <h1>New Book</h1>

  <%= form_with model: @book do |form| %>
    <div>
      <%= form.label :title %><br>
      <%= form.text_field :title %>
      <% @book.errors.full_messages_for(:title).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.label :author %><br>
      <%= form.text_field :author %>
      <% @book.errors.full_messages_for(:author).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.label :outline %><br>
      <%= form.text_area :outline %>
      <% @book.errors.full_messages_for(:outline).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.label :image %><br>
      <%= form.text_field :image %>
      <% @book.errors.full_messages_for(:image).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.submit %>
    </div>
  <% end %>
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/index.html.erb

  <h1>Books</h1>

  <ul>
    <% @books.each do |book| %>
      <li>
        <%= link_to book.title, book %> [<%= book.author %>]
      </li>
    <% end %>
  </ul>

  <%= link_to "New Book", new_book_path %>
        
・記事を更新する
kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  class BooksController < ApplicationController
    def index
      @books = Book.all
    end

    def show
      @book = Book.find(params[:id])
    end

    def new
      @book = Book.new
    end

    def create
      @book = Book.new(book_params)

      if @book.save
        redirect_to @book
      else
        render :new, status: :unprocessable_entity
      end
    end

    def edit
      @book = Book.find(params[:id])
    end

    def update
      @book = Book.find(params[:id])

      if @book.update(book_params)
        redirect_to @article
      else
        render :edit, status: :unprocessable_entity
      end
    end

    private
      def book_params
        params.require(:book).permit(:title, :author, :outline, :image)
      end
  end
        
・ビューのコードをパーシャルで共有する
kokaki@skynew:~/www/rails/books$ vi app/views/books/_form.html.erb

  <%= form_with model: book do |form| %>
    <div>
      <%= form.label :title %><br>
      <%= form.text_field :title %>
      <% book.errors.full_messages_for(:title).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.label :author %><br>
      <%= form.text_field :author %><br>
      <% book.errors.full_messages_for(:author).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.label :outline %><br>
      <%= form.text_area :outline %><br>
      <% book.errors.full_messages_for(:outline).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.label :image %><br>
      <%= form.text_field :image %><br>
      <% book.errors.full_messages_for(:image).each do |message| %>
        <div><%= message %></div>
      <% end %>
    </div>

    <div>
      <%= form.submit %>
    </div>
  <% end %>
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/new.html.erb

  <h1>New Book</h1>

  <%= render "form", book: @book %>
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/edit.html.erb

  <h1>Edit Book</h1>

  <%= render "form", book: @book %>
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/show.html.erb

  <h1><%= @book.title %></h1>

  <p><%= @book.author %></p>

  <p><%= @book.outline %></p>

  <ul>
    <li><%= link_to "Edit", edit_book_path(@book) %></li>
  </ul>
        
・記事を削除する
kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  class BooksController < ApplicationController
    def index
      @books = Book.all
    end

    def show
      @book = Book.find(params[:id])
    end

    def new
      @book = Book.new
    end

    def create
      @book = Book.new(book_params)

      if @book.save
        redirect_to @book
      else
        render :new, status: :unprocessable_entity
      end
    end

    def edit
      @book = Book.find(params[:id])
    end

    def update
      @book = Book.find(params[:id])

      if @book.update(book_params)
        redirect_to @article
      else
        render :edit, status: :unprocessable_entity
      end
    end

    def destroy
      @book = Book.find(params[:id])
      @book.destroy

      redirect_to root_path, status: :see_other
    end

    private
      def book_params
        params.require(:book).permit(:title, :author, :outline, :image)
      end
  end
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/show.html.erb

  <h1><%= @book.title %></h1>

  <p><%= @book.author %></p>

  <p><%= @book.outline %></p>

  <ul>
    <li><%= link_to "Edit", edit_book_path(@book) %></li>
    <li><%= link_to "Destroy", book_path(@book), data: {
      turbo_method: :delete,
      turbo_confirm: "Are you sure?"
      } %></li>
  </ul>
        
・BASIC認証
kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  class BooksController < ApplicationController
  http_basic_authenticate_with name: "kokaki", password: "secret", except: [:index, :show]

  def index
  @books = Book.all
  end

  #(以下省略)
  end
        

■本番環境の指定

kokaki@skynew:~/www/rails/books$ rails db:migrate RAILS_ENV=production
kokaki@skynew:~/www/rails/books$ bundle exec rails assets:precompile RAILS_ENV=production

■sqlite3データの取り扱い

・データのエクスポート
$ sqlite3 db/development.sqlite3

  SQLite version 3.37.2 2022-01-06 13:25:41
  Enter ".help" for usage hints.
  sqlite> .mode csv
  sqlite> .once books_dev.csv
  sqlite> select title, author, outline, image from books;
  sqlite> .q
        
・データのインポート
kokaki@skynew:~/www/rails/books$ sqlite3 db/development.sqlite3

  SQLite version 3.37.2 2022-01-06 13:25:41
  Enter ".help" for usage hints.
  sqlite> create table books_tmp(title string, author string, outline text, image string);
  sqlite> .mode csv
  sqlite> .import Book2.csv books_tmp
  sqlite> select count(*) from books_tmp;
  51
  sqlite> insert into books (title, author, outline, image) select * from books_tmp;
  sqlite> select count(*) from books;
  53
  sqlite> .q
        

■【Rails 7】Bootstrapのインストール

kokaki@skynew:~/www/rails/books$ vi Gemfile

  gem 'bootstrap', '~> 5.3.0'
  gem 'jquery-rails'

  gem "sassc-rails" #コメントアウトをなくす
        
kokaki@skynew:~/www/rails/books$ bundle install
kokaki@skynew:~/www/rails/books$ mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss
kokaki@skynew:~/www/rails/books$ vi app/assets/stylesheets/application.scss
追記

  @import "bootstrap";
        
kokaki@skynew:~/www/rails/books$ vi config/importmap.rb 追記

  pin "bootstrap", to: "bootstrap.min.js", preload: true
  pin "@popperjs/core", to: "popper.js", preload: true
        
kokaki@skynew:~/www/rails/books$ vi config/initializers/assets.rb

  Rails.application.config.assets.precompile += %w(bootstrap.min.js popper.js)

  # a)で"bootstap.min.js"ではなく、"bootsrap.js"と書いた場合は、こちらも同じに直す
        
kokaki@skynew:~/www/rails/books$ vi app/javascript/application.js
import "@hotwired/turbo-rails"の上に追記

  //= require jquery3
  //= require popper
  //= require bootstrap
        

■[Rails]画像アップロード 9/20

kokaki@skynew:~/www/rails/books$ vi Gemfile

  gem 'carrierwave', '>= 3.0.0.rc', '< 4.0'
  gem "mini_magick"
        
kokaki@skynew:~/www/rails/books$ bundle install
kokaki@skynew:~/www/rails/books$ rails generate uploader Image
kokaki@skynew:~/www/rails/books$ vi app/models/book.rb
追記

  mount_uploader :image, ImageUploader
        
kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  private
    def book_params
      params.require(:book).permit(:title, :author, :outline, :image, :image_cache)
  end
        
kokaki@skynew:~/www/rails/books$ vi app/views/books/_form.html.erb

  <div>
    <%= form.label :image %><br>
    <%= form.text_field :image %><br>
    <% book.errors.full_messages_for(:image).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>
  ↓
  <div class="form-group mb-3"">
    <%= form.label :image %>
    <%= form.file_field :image, class:'form-control', accept: 'image/*'%>
    <%= form.hidden_field :article_image_cache %>
  </div>
        
kokaki@skynew:~/www/rails/books$ vi config/locales/activerecord/ja.yml

  ja:
    activerecord:
      attributes:
        article:
          image: サムネール
        
kokaki@skynew:~/www/rails/books$ vi app/uploaders/image_uploader.rb

  def default_url(*args)
    # # For Rails 3.1+ asset pipeline compatibility:
    # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
    #
    # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
    "/images/NoImage.png"
  end
        


  ・画像表示
  <%= image_tag book.image.url %>
  
  ・画像保存先
  /uploads/book/image/1/suikoden_01.jpg
  
  ・画像表示コード
  <div class="row">
    <% @books.each do |book| %>
      <div class="col-md-3">
        <div class="card">
          <%= image_tag book.image.url, class: 'card-img-top' %>
          <%= link_to book.title, book %>
          <%= book.outline %>
        </div>
      </div>
    <% end %>
  </div>
      
■【Rails】kaminariとboostrap4を使ってページネーションをする
kokaki@skynew:~/www/rails/books$ vi Gemfile

  gem 'kaminari'
  gem 'bootstrap4-kaminari-views'

kokaki@skynew:~/www/rails/books$ bundle install

kokaki@skynew:~/www/rails/books$ rails g kaminari:config

kokaki@skynew:~/www/rails/books$ vi config/application.rb

  config.i18n.default_locale = :ja

kokaki@skynew:~/www/rails/books$ vi app/controllers/books_controller.rb

  class BooksController > ApplicationController
    http_basic_authenticate_with name: "kokaki", password: "secret", except: [:index, :show]
    def index
      @books = Book.order(:id).page(params[:page]) ★
    end

kokaki@skynew:~/www/rails/books$ vi app/views/books/index.html.erb

  <div class="d-flex">
    <div class="flex-fill">
      <%= link_to "New Book", new_book_path, class: "btn btn-primary" %>
    </div>
    <div class="mr-auto">
      <%= paginate @books, theme: 'twitter-bootstrap-4' %> ★
    </div>
  </div>

kokaki@skynew:~/www/rails/books$ vi app/models/book.rb

  class Book > ApplicationRecord
    mount_uploader :image, ImageUploader
    paginates_per 10 ★

    validates :title, presence: true
    validates :author, presence: true #, length: { minimum: 10 }
  end

kokaki@skynew:~/www/rails/books$ vi config/locales/ya.yml

  ja:
    views:
      pagination:
        first: "« 最初"
        last: "最後 »"
        previous: "‹ 前"
        next: "次 ›"
        truncate: "..."
    helpers:
      page_entries_info:
        one_page:
          display_entries:
            zero: ""
            one: "<strong>1-1</strong>/1件中"
            other: "<strong>1-%{count}</strong>/%{count}件中"
        more_pages:
          display_entries: "<strong>%{first}-%{last}</strong>/%{total}件中"

kokaki@skynew:~/www/rails/books$ sudo systemctl restart apache2