■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