#
だとroot権限での実行とまぎらわしいので、 //
で記載。bundle install --path vendor/bundle --binstubs=vendor/bin の実行方法
$ bundle install --path vendor/bundle --binstubs=vendor/bin
$ bundle config --local path 'vendor/bundle'
$ bundle binstubs --path=vendor/bin
// $ bundle binstubs --path=bin としてしまうと、rails app:update:bin で生成されるものとぶつかるので注意。
// 確認
$ bundle config
$ bundle config <name>
// 設定(global)
$ bundle config <name> <value>
$ bundle config --global <name> <value>
// 設定(local)
$ bundle config --local <name> <value>
// 削除
$ bundle config --delete
// --skip-test-unit・・・RSpec等を利用する場合に、Test::Unitの設定をスキップ
// --skip-bundle・・・gemのinstallをスキップ(デフォルトのGemfileを変更してからinstallしたい場合。)
$ rails new $APP_NAME$ -d postgresql --skip-test-unit
$ rails new $APP_NAME$ -d mysql --skip-test-unit
$ vim Gemfile
$ ./bin/bundle
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "2.6.4"
gem "rails", "~> 6.0.3", ">= 6.0.3.2"
gem "pg", ">= 0.18", "< 2.0"
gem "puma", "~> 4.1"
gem "sass-rails", ">= 6"
gem "webpacker", "~> 4.0"
gem "turbolinks", "~> 5"
gem "jbuilder", "~> 2.7"
gem "bootsnap", ">= 1.4.2", require: false
### ===additional utility===start
gem "bcrypt" # for password encryption
gem "rails-i18n" # for multilingualization
gem "kaminari" # for pagination
gem "date_validator" # for date validation
gem "valid_email2" # for email validation
gem "nokogiri" # xml/html parser/generator
### ===additional utility===end
group :development, :test do
gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem "web-console", ">= 3.3.0"
gem "listen", "~> 3.2"
gem "spring"
gem "spring-watcher-listen", "~> 2.0.0"
end
group :test do
gem "capybara", ">= 2.15"
gem "selenium-webdriver"
gem "webdrivers"
### for rspec
gem "rspec-rails"
gem "factory_bot_rails"
end
$ yarn
// インストール済みのファイルが削除されていないか確認する場合は、
// yarn install --check-files
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: db
username: postgres
password: ""
./bin/rails db:create
class Application < Rails::Application
config.load_defaults 6.0
+ config.time_zone = "Tokyo"
+ # "{ROOT}/config/locales/**/*.{rb,yml}"
+ config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}").to_s]
+ config.i18n.default_local = :ja
end
class Application < Rails::Application
config.load_defaults 6.0
config.time_zone = "Tokyo"
# "{ROOT}/config/locales/**/*.{rb,yml}"
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}").to_s]
config.i18n.default_locale = :ja
+ config.generators do |g|
+ g.skip_routes true
+ g.helper false
+ g.assets false
+ g.test_framework :rspec # mini_test -> RSpec
+ g.controller_specs false
+ g.view_specs false
+ end
end
config/initializers/blocked_hosts.rb
を新規作成するRails.application.configure do
config.hosts << "example.com"
config.hosts << "hoge.example.com"
end
// rails側のコードでは、次のように開発環境においてはlocalhostがデフォルトで許可されている。
@hosts = Array(([".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")] if Rails.env.development?))
Rails.application.configure do
config.hosts = nil
end
Cannot render console from xx.xx.xx.xx
とでる。config/environments/development.rb
を次のように編集することでIP範囲を広げる config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+
+ # allow from 172.16.0.0 to 172.31.255.255
+ config.web_console.whitelisted_ips = ["172.16.0.0/12"]
end
./bin/rails s -b 0.0.0.0
./bin/rails g rspec:install
> create .rspec
> create spec
> create spec/spec_helper.rb
> create spec/rails_helper.rb
spec/models
以下spec/requests
以下spec/sample/string_spec.rb
require 'spec_helper'
describe 'String' do
before do
# Do nothing
end
after do
# Do nothing
end
describe '#<<' do
example '文字の追加' do
s = "ABC"
s << "DEF"
expect(s.size).to eq(6)
expect(s).to eq("ABCDEF")
end
example 'nilの追加(pending)' do
pending "pendingなので、failureにカウントされない。"
s = "ABC"
s << nil
expect(s.size).to eq(3)
expect(s).to eq("ABC")
end
xexample 'nilの追加(スキップ)' do
s = "ABC"
s << nil
expect(s.size).to eq(3)
expect(s).to eq("ABC")
end
end
end
$ bundle exec rspec spec/sample/string_spec.rb
// bundle exec rspec と同じ
$ bundle exec rspec spec
// 行番号指定で、特定のテストだけ実行
$ bundle exec rspec spec/sample/string_spec.rb:25
$ bundle exec rspec --tag=tag1
Rails.application.routes.draw do
namespace :staff do
# /staff
# Staff::TopController
root "top#index"
end
namespace :admin do
# /admin
# Admin::TopController
root "top#index"
end
namespace :customer do
# /customer
# Customer::TopController
root "top#index"
end
end
$ ./bin/rails g controller staff/top
$ ./bin/rails g controller admin/top
$ ./bin/rails g controller customer/top
> create app/controllers/staff/top_controller.rb
> invoke erb
> create app/views/staff/top
> invoke rspec
> create spec/requests/staff/top_request_spec.rb
app/controllers/staff/top_controller.rb
class Staff::TopController < ApplicationController
# レスポンスを返すメソッドを呼ばない場合、
# デフォルトでアクションに対応するERBテンプレートが使用されるので、
# 下記でも同じ
# def index
# end
#
def index
render action: "index"
end
end
app/views/staff/top/index.html.erb
<% @title = "職員トップページ" %>
<h1><%= @title %></h1>
<% >
<%= >
<%= @str.html_safe %>
<%= raw(@str) %>
app/helpers/application_helper.rb
にApplicationHelper
モジュールのメソッドとして定義する。./bin/rails assets:precompile
abc-XXXXX.css
のようにXXXXXに32桁のフィンガープリントが入る(値はファイルの中身のMD5)。ファイル更新時に違うファイルがブラウザにキャッシュされ続けないようにするための対策。*=
require_tree .
・・・ app/assets/stylesheets以下のファイルをすべてアセットパイプラインの処理範囲とする。
require_self
自身もアセットパイプラインの処理対象とする。require_tree ./staff
/ require_tree ./admin
config/initializers/assets.rb
にコンパイル設定を追加
Rails.application.config.assets.precompile += %w( staff.css admin.css )
app/views/layouts
にapplication.html.erb
相当のものを作る。(application.html.erb
は不要なら削除)
staff.html.erb
/ admin.html.erb
<%= stylesheet_link_tag 'staff', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_link_tag 'admin', media: 'all', 'data-turbolinks-track': 'reload' %>
app/controllers/application_controller.rb
でのerbの呼び出し先を変更// app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
layout :set_layout
private def set_layout
if params[:controller].match(%r{\A(staff|admin|customer)/})
Regexp.last_match[1]
else
"customer"
end
end
end
@import
によって他のSCSSファイルに読み込まれる。config/environments/production.rb
にて、config.cache_classes=false
とすればリロードするようになる。export RAILS_SERVE_STATIC_FILES=1
の様にRAILS_SERVE_STATIC_FILES
に何らかの値を設定すればRailsアプリケーションから返却するようになる。./bin/rails s
はデフォルトのdevelopmentモードになる。RAILS_ENV=production
をつけると、productionモードになる。
./bin/rails db:create RAILS_ENV=production
./bin/rails s -e production -b 0.0.0.0
でもproductionモードになる。Couldn't decrypt config/credentials.yml.enc. Perhaps you passed the wrong key?
と出た場合は、組み合わせが正しくないので、2ファイルとも消して作り直す必要がある。EDITOR=vim ./bin/rails credentials:edit
// RuntimeErrorをraise
raise
//
raise ArgumentError, "the first argument must be a string"
begin
A
rescue E1 => e1
C1
rescue E2 => e2
C2
ensure
Z
end
class ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :rescue403
private def rescue403(e)
@exception = e
# パスに / が含まれているとコントローラーの階層とは別のところにviewがあると判断される。
# (Rails 2.2以前では、template: オプションが必須だった)
render template: "errors/forbidden", status: 403
end
end
log/production.log
// マイグレーションを実行(ここで、db/schema.rbが自動更新される。)
$ ./bin/rails db:migrate
// データベースを削除した上で、新たにデータベースし直して、migrateを実行
$ ./bin/rails db:migrate:reset
// すべてのテーブルをdropし、db/schema.rbの内容でテーブルを作り直し、seedデータを投入(db:schema:load -> db:seed)
// seedデータを投入し直したい場合は、このコマンド
$ ./bin/rails db:reset
// テーブルがどんなカラムを持っているかを調べる (./bin/rails runner)
$ ./bin/rails r "ModelName.columns.each {|c| p [ c.name, c.type ] }"
class ModelName < ActiveRecord::Base
self.table_name = "TableName"
alias_attribute :email, :OriginalColumnName
end
// model.email or model.OriginalColumnName でアクセス可
$ ./bin/rails db:seed
// ./bin/rails r "puts Model.count" 等で確認。
helper_method :method_name_xxx
のように指定すれば、ApplicationHelperのメソッドとしても定義される。Action名 | 対応するHTTPメソッド |
---|---|
index | GET |
show | GET |
new | GET |
edit | GET |
create | POST |
update | PATCH |
destroy | DELETE |
<%= f.fields_for :bar_baz, f.object.foo.bar_baz do |ff| %>
[:bar_baz]
の部分は、レコード名として指定した値が入る。config/initializers/action_view.rb
を下記のようにする。Rails.application.configure do
config.action_view.form_with_generates_remote_forms = false
end
my_user[email]
で、取得する時は、params[:my_user][:email]
となる、このmy_user
がprefix。config/initializers/action_controller.rb
を作り、下記のようにする.Rails.application.configure do
config.action_controller.permit_all_parameters = true
end
<%= form_with model: @user, url: :registration do |f| %>
<%= f.label :name, "名前" %>
<%= f.text_field :email %>
<%= f.submit "送信" %>
<% end %>
include ActiveModel::Model
によって、form_withのmodel引数に渡すことができるようになる。class Staff::LoginForm
include ActiveModel::Model
attr_accessor :email, :password
end
staff_login_form: {
email: "aaaaa@example.com",
password: "foo"
}
ビジネスロジック実行用・集約用の場所
class Staff::Authenticator
def initialize(staff_member)
@staff_member = staff_member
end
def authenticate(raw_password)
@staff_member &&
!@staff_member.suspended? &&
@staff_member.hashed_password &&
@staff_member.start_date <= Date.today &&
(@staff_member.end_date.nil? || @staff_member.end_date > Date.today) &&
BCrypt::Password.new(@staff_member.hashed_password) == raw_password
end
end
Staff::Authenticator.new(staff_member).authenticate(@form.password)
extend ActiveSupport::Concern
の記述をする。
module ErrorHandlers
extend ActiveSupport::Concern
included do
rescue_from StandardError, with: :rescue500
rescue_from ApplicationController::Forbidden, with: :rescue403
rescue_from ApplicationController::IpAddressRejected, with: :rescue403
rescue_from ActiveRecord::RecordNotFound, with: :rescue404
private def rescue403(e)
@exception = e
render "errors/forbidden", status: 403
end
private def rescue404(e)
render "errors/not_found", status: 404
end
private def rescue500(e)
render "errors/internal_server_error", status: 500
end
end
end
config.include FactoryBot::Syntax::Methods
FactoryBot.define do
factory :staff_member do
sequence(:email) { |n| "member#{n}@example.com" }
family_name { "山田" }
given_name { "太郎" }
family_name_kana { "ヤマダ" }
given_name_kana { "タロウ" }
password { "pw" }
start_date { Date.yesterday }
end_date { nil }
suspended { false }
end
end
require 'rails_helper'
describe Staff::Authenticator do
describe "#authenticate" do
example "正しいパスワードならtrueを返す" do
m = build(:staff_member)
expect(Staff::Authenticator.new(m).authenticate("pw")).to be_truthy
end
example "誤ったパスワードならfalseを返す" do
m = build(:staff_member)
expect(Staff::Authenticator.new(m).authenticate("xy")).to be_falsey
end
example "パスワード未設定ならfalseを返す" do
m = build(:staff_member, password: nil)
expect(Staff::Authenticator.new(m).authenticate(nil)).to be_falsey
end
example "停止フラグが立っていればfalseを返す" do
m = build(:staff_member, suspended: true)
expect(Staff::Authenticator.new(m).authenticate("pw")).to be_falsey
end
example "開始前ならfalseを返す" do
m = build(:staff_member, start_date: Date.tomorrow)
expect(Staff::Authenticator.new(m).authenticate("pw")).to be_falsey
end
example "終了後ならfalseを返す" do
m = build(:staff_member, end_date: Date.today)
expect(Staff::Authenticator.new(m).authenticate("pw")).to be_falsey
end
end
end
// flash.notice
renderでは消えないが、redirectをするとアクションを経由するので、表示後に消える。
// flash.now.notice
render後に消える。
// flash.keep.notice
redirectを2回経由したら、表示後に消える。
Rails.application.routes.draw do
get "blog/:year/:month/:mday" => "articles#show",
constraints: {
year:/20\d\d/,month:/\d\d/,mday:/\d\d/
}
end
Rails.application.routes.draw do
get "staff/login" => "staff/sessions#new", as: :staff_login
end
<%= link_to "ログイン", :staff_login %>
(<%= link_to "ログイン", "/staff/login" %>)
Rails.application.routes.draw do
get "login" => "sessions#new", as: :login_form
post "login" => "sessions#create", as: :authentication
end
<%= link_to "ログイン", :login_form %>
(<%= link_to "ログイン", "/login" %>)
<%= link_to "ログイン", :authentication %>
(<%= link_to "ログイン", "/login" %>)
<%= link_to "ログイン", :login %>
(<%= link_to "ログイン", "/login" %>)
<%= link_to "ログイン", :login_path %>
(<%= link_to "ログイン", "/login" %>)
<%= link_to "ログイン", :login_path(tracking: "001") %>
(<%= link_to "ログイン", "/login?stacking=001" %>)
// /magazines/:magazine_id/ads/:id というroutingがあり、
// MagazineモデルとAdモデルのインスタンスがあるとする
// @magazine のidが2, @ad のidが3であるとする
@magazine, @ad
// /magazines/2/ads/3 を示す。
<%= link_to 'Ad details', magazine_ad_path(2, 3) %>
// オブジェクトのidを利用させることもできる
// /magazines/2/ads/3 を示す。
<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>
// url_forは、配列・setから適切なurlを生成します。
// この場合は、MagazineモデルとAdモデルなので、
// Magazine_Ad_path をlowcaseにした。magazine_ad_path(@magazine, @ad)を呼び出します。
// もし、StaffMemberモデルの場合は、staff_member_path() です。
<%= link_to 'Ad details', url_for([@magazine, @ad]) %>
// url_forに、配列・setを渡すかわりに、url_forを省略して配列を渡しても同じです。
<%= link_to 'Ad details', [@magazine, @ad] %>
// オブジェクトだけを渡した場合は、
// magazine_path(@magazine) と同じです。
<%= link_to 'Ad details', @magazine %>
// 次の配列は、:edit, :foo を追加で渡しているので、
// magazine_ad_path(@magazine, @ad) に:edit, :fooをつけた
// edit_foo_magazine_ad_path(@magazine, @ad) が呼び出されることになります。
<%= link_to 'Edit Ad', [:edit, :foo, @magazine, @ad] %>
Rails.application.routes.draw do
get "articles/:year/:number" => "articles#show", as: :article
end
<%= link_to "読む", article_path(year: "2019", number: "12") %>
<%= link_to_unless( ........ ) do |name| ...... end %>
Rails.application.routes.draw do
namespace :staff do
......
end
do
// オプション適用時
Rails.application.routes.draw do
namespace :staff, path: "bar", module: "bar", as: "bar" do
......
end
do
内容 | HTTPメソッド | URLパターン | アクション名 |
---|---|---|---|
リスト画面表示 | GET | /foos | index |
詳細画面表示 | GET | /foos/:id | show |
登録画面表示 | GET | /foos/new | new |
編集画面表示 | GET | /foos/:id/edit | edit |
追加 | POST | /foos | create |
更新 | PATCH | /foos/:id | update |
削除 | DELETE | /foos/:id | destroy |
URLパターン | ルーティング名 |
---|---|
/foo/bar_hoges | :foo_bar_hoges |
/foo/bar_hoges/:id | :foo_bar_hoge |
/foo/bar_hoges/new | :new_foo_bar_hoge |
/foo/bar_hoges/:id/edit | :edit_foo_bar_hoge |
resources :foo_bars, except: [:index, :new, :create]
resources :foo_bars, except: [:index, :new, :create]
resources :foo_bars, controller: "hoo_bars"
resources :foo_bars, path: "hoo_bars"
内容 | HTTPメソッド | URLパターン | アクション名 |
---|---|---|---|
詳細画面表示 | GET | /foo | show |
登録画面表示 | GET | /foo/new | new |
編集画面表示 | GET | /foo/edit | edit |
追加 | POST | /foo | create |
更新 | PATCH | /foo | update |
削除 | DELETE | /foo | destroy |
resource :account
としても、コントローラー名は accounts
となる。URLパターン | ルーティング名 |
---|---|
/foo | :foo |
/foo/new | :new_foo |
/foo/edit | :edit_foo |
Rails.application.configure do
config.foo = {
bar: {hoge: "foobar"}
}
end
Rails.application.routes.draw do
config = Rails.application.config.foo
constraints host: "foo.example.com" do
namespace :foo do
end
end
constraints host: config[:bar][:hoge] do
namespace :bar do
end
end
end
// パラメータを受け取って保存
@staff_member = StaffMember.new(params[:staff_member])
@staff_member.save // true or false
// 値を一括で設定
@staff_member.assign_attributes(params[:staff_member])
// assign_attributes と同じ (attributes= は、assign_attributesのエイリアス)
@staff_member.attributes = params[:staff_member]
<div class="legend">
<span class="mark">*</span> 印の付いた項目は入力必須です。
</div>
<div>
<%= f.label :email, "メールアドレス", class: "required" %>
<%= f.email_field :email, size: 32, required: true %>
</div>
<% if f.object.new_record? %>
<div>
<%= f.label :password, "パスワード", class: "required" %>
<%= f.password_field :password, size: 32, required: true %>
</div>
<% end %>
<div>
<%= f.label :family_name, "氏名", class: "required" %>
<%= f.text_field :family_name, required: true %>
<%= f.text_field :given_name, required: true %>
</div>
<div>
<%= f.label :start_date, "入社日", class: "required" %>
<%= f.date_field :start_date, required: true %>
</div>
<div class="check-boxes">
<%= f.check_box :suspended %>
<%= f.label :suspended, "アカウント停止" %>
</div>
form {
div {
label.required:after {
content: "*";
padding-left: $narrow;
color: $red;
}
}
}
<%= render partial: "event", collection: @events %>
と書いた場合、@eventsの要素数分だけ繰り返し部分テンプレートが呼び出される。 そのときに渡される変数名は、event。
params#require#permit
とフィルターを通していないものを、モデルオブジェクトのassign_attributesで渡すと、ActiveModel::ForbiddenAttributesError
がraiseされる。params.require(:hoge)
params.permit(:email)
let(:foo) { bar()}
マッチャー・・・expect().to hoge のhogeの部分
test.host
というホスト名のURLが生成され、このURLでテストされる。// spec/supportを自動で読み込むようにするコード
require 'rspec/rails'
+ Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
describe "管理者による職員管理", "ログイン前" do
describe "新規登録" do
example "職員一覧ページにリダイレクト" do
end
example "例外 ActionController::ParameterMissingが発生" do
end
end
end
は、以下と同じ
describe "管理者による職員管理" do
context "ログイン前" do
describe "新規登録" do
example "職員一覧ページにリダイレクト" do
end
example "例外 ActionController::ParameterMissingが発生" do
end
end
end
end
ActiveSupport::Testing::TimeHelpers
を読み込む。 config.include ActiveSupport::Testing::TimeHelpers
end
save,create,update | save!,create!,update! | |
---|---|---|
ActiveRecord::RecordInvalid(validation系) | return false | 例外 |
ActiveRecord::RecordNotSaved(callback系) | return false | 例外 |
ActiveRecord::RecordNotUnique,ActiveRecord::StatementInvalidなど | 例外 | 例外 |
class CreateStaffEvents < ActiveRecord::Migration[6.0]
def change
create_table :staff_events do |t|
t.references :staff_member, null: false, index: false, foreign_key: true
t.string :type, null: false
t.datetime :created_at, null: false
end
add_index :staff_events, :created_at
add_index :staff_events, [ :staff_member_id, :created_at ]
end
end
class StaffMember < ApplicationRecord
has_many :events, class_name: "StaffEvent", dependent: :destroy
# 次でも同じ
# has_many :staff_events, dependent: :destroy
# foreign_keyについては、モデル名 StaffMember から staff_member_id と推測される。
end
class StaffEvent < ApplicationRecord
self.inheritance_column = nil
# t.string :type は特別な意味を持つが、self.inheritance_column = nil とすることで無効化できる。
belongs_to :member, class_name: "StaffMember", foreign_key: "staff_member_id"
alias_attribute :occurred_at, :created_at
end
// 以下は同じ呼び出し
// staff_member.events.create!(type:"rejected")
// StaffEvent.create!(member: staff_member, type: "rejected")
./bin/rails r "puts ApplicationRecord.ancestors"
./bin/rails r "puts StaffMember.new.events.class.ancestors"
1. 祖先を調べる
./bin/rails r "puts StaffMember.new.events.class.ancestors"
StaffEvent::ActiveRecord_Associations_CollectionProxy
StaffEvent::GeneratedRelationMethods
ActiveRecord::Delegation::ClassSpecificRelation
ActiveRecord::Associations::CollectionProxy
ActiveRecord::Relation
ActiveRecord::FinderMethods
ActiveRecord::Calculations
ActiveRecord::SpawnMethods
ActiveRecord::QueryMethods
ActiveModel::ForbiddenAttributesProtection
ActiveRecord::Batches
ActiveRecord::Explain
ActiveRecord::Delegation
Enumerable
ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency
ActiveSupport::ToJsonWithActiveSupportEncoder
Object
JSON::Ext::Generator::GeneratorMethods::Object
ActiveSupport::Tryable
ActiveSupport::Dependencies::Loadable
Kernel
BasicObject
2. 祖先を調べる ```api.rubyonrails.org```で検索
https://api.rubyonrails.org/ を開き上から順番に検索
上から4つ目の、 ActiveRecord::Associations::CollectionProxy でヒット
3. ドキュメントを見る。
create, create! というメソッドが使えることがわかる。
resources :staff_members do
# このネストされたリソースが示すパスは
# /staff_members/:staff_member_id/staff_events
# ルーティング名は、
# :staff_member_staff_events
resources :staff_events, only: [:index]
end
./bin/rails g kaminari:config
create config/initializers/kaminari_config.rb
./bin/rails g kaminari:views default
create app/views/kaminari/_gap.html.erb
create app/views/kaminari/_next_page.html.erb
create app/views/kaminari/_last_page.html.erb
create app/views/kaminari/_page.html.erb
create app/views/kaminari/_prev_page.html.erb
create app/views/kaminari/_paginator.html.erb
create app/views/kaminari/_first_page.html.erb
# frozen_string_literal: true
Kaminari.configure do |config|
# config.default_per_page = 25
# config.max_per_page = nil
# config.window = 4
# config.outer_window = 0
# config.left = 0
# config.right = 0
# config.page_method_name = :page
# config.param_name = :page
# config.max_pages = nil
# config.params_on_first_page = false
end
# frozen_string_literal: true
Kaminari.configure do |config|
config.default_per_page = 25
# config.max_per_page = nil
# config.window = 4
# config.outer_window = 0
# config.left = 0
# config.right = 0
# config.page_method_name = :page
# config.param_name = :page
# config.max_pages = nil
# config.params_on_first_page = false
end
mkdir -p config/locales/views
touch config/locales/views/paginate.ja.yml
ja:
views:
pagination:
first: "先頭"
last: "末尾"
previous: "前"
next: "次"
truncate: "..."
@events = Event.order(created_at: :desc)
@events = @events.page(params[:page])
<%= paginate @events %>
app/views/kaminari/
以下のerbファイルを編集する。.includes()
@events = @events.includes(:member)
\A
\z
^
$
\p{katakana}
\p{han}
\p{hiragana}
A-Za-z
/\A[\u{30fc}]+\z/黒田 努. Ruby on Rails 6 実践ガイド (impress top gearシリーズ) (Japanese Edition) (Kindle の位置No.8103). Kindle 版.
- 文字コードで1文字を指定(例: 長音符)
- ```\u{30fc}```
multiline: true
が必要uniqueness: true
uniqueness: {case_sensitive: false}
validate do
unless foo_bar_validate?
errors.add(:foo_bar, :wrong)
end
end
require 'nkf'
-WwZ1
のように連結して利用も可。-W
: 入力の文字コードをUTF8と仮定する-w
: UTF8で出力する-Z1
: 全角の英数字、記号、全角スペースを半角に変換する--katakana
: ひらがなをカタカナに変換するrequire 'nkf'
module StringNormalizer
extend ActiveSupport::Concern
def normalize_as_name(text)
NKF.nkf("-W -w -Z1", text).strip if text
end
def normalize_as_furigana(text)
NKF.nkf("-W -w -Z1 --katakana", text).strip if text
end
end
-> (obj) {}
)で渡す必要がある。
before: 1.year.from_now.to_date
としてしまうと、「起動時の時刻から1年後」と固定されてしまう。 validates :start_date, presence: true, date: {
after_or_equal_to: Date.new(2000, 1, 1),
before: -> (obj) { 1.year.from_now.to_date },
allow_blank: true
}
validates :end_date, date: {
after: :start_date,
before: -> (obj) { 1.year.from_now.to_date },
allow_blank: true
}
validates :email, presence: true, "valid_email_2/email": true
mkdir -p app/presenters
touch app/presenters/model_presenter.rb
class ModelPresenter
delegate :raw, to: :view_context
attr_reader :object, :view_context
def initialize(object, view_context)
@object = object
@view_context = view_context
end
end
class StaffMemberPresenter < ModelPresenter
delegate :suspended?, to: :object
def suspended_mark
suspended? ? raw("☑") : raw("☐")
end
end
<% @staff_members.each do |m| %>
<tr>
<td><%= m.family_name %> <%= m.given_name %></td>
<td><%= m.family_name_kana %> <%= m.given_name_kana %></td>
<td class="email"><%= m.email %></td>
<td class="date"><%= m.start_date.strftime("%Y/%m/%d") %></td>
<td class="date"><%= m.end_date.try(:strftime, "%Y/%m/%d") %></td>
<td class="boolean"><%= m.suspended? ? raw("☑") : raw("☐")%></td>
<td class="actions">
<%= link_to "編集", [ :edit, :admin, m] %> |
<%= link_to "Events", [:admin, m, :staff_events] %> |
<%= link_to "削除", [ :admin, m], method: :delete, data: { confirm: "本当に削除しますか?"} %>
</td>
</tr>
<% end %>
<% @staff_members.each do |m| %>
<% p = StaffMemberPresenter.new(m, self) %>
<tr>
<td><%= m.family_name %> <%= m.given_name %></td>
<td><%= m.family_name_kana %> <%= m.given_name_kana %></td>
<td class="email"><%= m.email %></td>
<td class="date"><%= m.start_date.strftime("%Y/%m/%d") %></td>
<td class="date"><%= m.end_date.try(:strftime, "%Y/%m/%d") %></td>
<td class="boolean"><%= p.suspended_mark %></td>
<td class="actions">
<%= link_to "編集", [ :edit, :admin, m] %> |
<%= link_to "Events", [:admin, m, :staff_events] %> |
<%= link_to "削除", [ :admin, m], method: :delete, data: { confirm: "本当に削除しますか?"} %>
</td>
</tr>
<% end %>
&
&:foo
は、{|e| e.foo }
と同じ、つまり、map(&:foo)
とmap {|e| e.foo }
は同じ。class Foo
delegate :raw, :low to: bar
def example
# この呼び出しは、delegateにより、bar.raw という呼び出しとなる
raw
# この呼び出しは、delegateにより、bar.low という呼び出しとなる
low
end
end
m << p.foo(size: 32, required: true)
p.with_options(required: true) do |q|
m << q.foo(size: 32)
end
m << p.foo(size: 32, required: false)
p.with_options(required: true) do |q|
m << q.foo(size: 32, required: false)
end
mkdir -p app/lib
module HtmlBuilder
def markup(tag_name = nil, options = {})
root = Nokogiri::HTML::DocumentFragment.parse("")
Nokogiri::HTML::Builder.with(root) do |doc|
if tag_name
# 内部的にmethod_missingを使って実装されているため、次の2つは同じ
# - doc.span "*", class: "mark"
# - doc.method_missing("span", "*", class: "mark")
doc.method_missing(tag_name, options) do
yield(doc)
end
else
yield(doc)
end
end
root.to_html.html_safe
end
end
<spanclass="mark">*</span>印の付いた項目は入力必須です。
markup do |m|
m.span "*", class: "mark"
m.text "印の付いた項目は入力必須です。"
end
<divclass="notes"><spanclass="mark">*</span>印の付いた項目は入力必須です。</div>
markup do |m|
m.div(class:"notes") do
m.span "*", class: "mark"
m.text "印の付いた項目は入力必須です。"
end
end
markup(:div,class:"notes") do |m|
m.span "*", class: "mark"
m.text "印の付いた項目は入力必須です。"
end
markup do |m|
m.div(id:"message") do
m.div(class:"box") do
m.span"まもなくシステムが停止します。", class: "warning"
end
end
end
markup do |m|
m << "<spanclass='mark'>*</span>"
m.text "印の付いた項目は入力必須です。"
end
class StaffEventPresenter < ModelPresenter
delegate :member, :description, :occurred_at, to: :object
def table_row
markup(:tr) do |m|
unless view_context.instance_variable_get(:@staff_member)
m.td do
m << link_to(member.family_name + member.given_name,
[ :admin, member, :staff_events ])
end
end
m.td description
m.td(:class => "date") do
m.text occurred_at.strftime("%Y/%m/%d %H:%M:%S")
end
end
end
end
<% @events.each do |event| %>
<%= StaffEventPresenter.new(event, self).table_row %>
<% end %>
class FormPresenter
include HtmlBuilder
attr_reader :form_builder, :view_context
delegate :label, :text_field, :date_field, :password_field,
:check_box, :radio_button, :text_area, :object, to: :form_builder
def initialize(form_builder, view_context)
@form_builder = form_builder
@view_context = view_context
end
def notes
markup(:div, class: "notes") do |m|
m.span "*", class: "mark"
m.text "印の付いた項目は入力必須です。"
end
end
def text_field_block(name, label_text, options = {})
markup(:div, class: "input-block") do |m|
m << decorated_label(name, label_text, options)
m << text_field(name, options)
m << error_messages_for(name)
end
end
def password_field_block(name, label_text, options = {})
markup(:div, class: "input-block") do |m|
m << decorated_label(name, label_text, options)
m << password_field(name, options)
m << error_messages_for(name)
end
end
def date_field_block(name, label_text, options = {})
markup(:div, class: "input-block") do |m|
m << decorated_label(name, label_text, options)
m << date_field(name, options)
m << error_messages_for(name)
end
end
def error_messages_for(name)
markup do |m|
object.errors.full_messages_for(name).each do |message|
m.div(class: "error-message") do |m|
m.text message
end
end
end
end
private def decorated_label(name, label_text, options = {})
label(name, label_text, class: options[:required] ? "required" : nil)
end
end
class StaffMemberFormPresenter < FormPresenter
def password_field_block(name, label_text, options = {})
if object.new_record?
super(name, label_text, options)
end
end
def full_name_block(name1, name2, label_text, options = {})
markup(:div, class: "input-block") do |m|
m << decorated_label(name1, label_text, options)
m << text_field(name1, options)
m << text_field(name2, options)
m << error_messages_for(name1)
m << error_messages_for(name2)
end
end
def suspended_check_box
markup(:div, class: "check-boxes") do |m|
m << check_box(:suspended)
m << label(:suspended, "アカウント停止")
end
end
end
<% p = StaffMemberFormPresenter.new(f, self) %>
<%= p.notes %>
<%= p.text_field_block(:email, "メールアドレス", size: 32, required: true) %>
<%= p.password_field_block(:password, "パスワード", size: 32, required: true) %>
<%= p.full_name_block(:family_name, :given_name, "氏名", required: true) %>
<%= p.full_name_block(:family_name_kana, :given_name_kana, "フリガナ", required: true) %>
<%= p.date_field_block(:start_date, "入社日", required: true) %>
<%= p.date_field_block(:end_date, "退職日") %>
<%= p.suspended_check_box %>
モデル
テーブル
モデル
テーブル
class CreateCustomers < ActiveRecord::Migration[6.0]
def change
create_table :customers do |t|
t.string :email, null: false # メールアドレス
t.string :family_name, null: false # 姓
t.string :given_name, null: false # 名
t.string :family_name_kana, null: false # 姓(セイ)
t.string :given_name_kana, null: false # 名(メイ)
t.string :gender # 性別
t.date :birthday # 誕生日
t.string :hashed_password # パスワード
t.timestamps
end
add_index :customers, "LOWER(email)", unique: true
add_index :customers, [ :family_name_kana, :given_name_kana ]
end
end
class CreateAddresses < ActiveRecord::Migration[6.0]
def change
create_table :addresses do |t|
t.references :customer, null: false # 顧客への外部キー
t.string :type, null: false # 継承カラム
t.string :postal_code, null: false # 郵便番号
t.string :prefecture, null: false # 都道府県
t.string :city, null: false # 市区町村
t.string :address1, null: false # 町域、番地等
t.string :address2, null: false # 建物名、部屋番号等
t.string :company_name, null: false, default: "" # 会社名
t.string :division_name, null: false, default: "" # 部署名
t.timestamps
end
add_index :addresses, [ :type, :customer_id ], unique: true
add_foreign_key :addresses, :customers
end
end
class Customer < ApplicationRecord
has_one :home_address, dependent: :destroy
has_one :work_address, dependent: :destroy
def password=(raw_password)
if raw_password.kind_of?(String)
self.hashed_password = BCrypt::Password.create(raw_password)
elsif raw_password.nil?
self.hashed_password = nil
end
end
end
class Address < ApplicationRecord
belongs_to :customer
PREFECTURE_NAMES = %w(
北海道
青森県 岩手県 宮城県 秋田県 山形県 福島県
茨城県 栃木県 群馬県 埼玉県 千葉県 東京都 神奈川県
新潟県 富山県 石川県 福井県 山梨県 長野県 岐阜県 静岡県 愛知県
三重県 滋賀県 京都府 大阪府 兵庫県 奈良県 和歌山県
鳥取県 島根県 岡山県 広島県 山口県
徳島県 香川県 愛媛県 高知県
福岡県 佐賀県 長崎県 熊本県 大分県 宮崎県 鹿児島県
沖縄県
日本国外
)
end
class HomeAddress < Address
end
class WorkAddress< Address
end
hasone :foo_bar
であれば、object.build_foo_bar で初期状態のインスタンスを作成して紐付ける事ができる。(DBには保存されない。)Capybaraがデフォルトで使用するホスト名は、 www.example.com
のため、変更する場合は、Capybara.app_host
を使う。
module FeaturesSpecHelper
def switch_namespace(namespace)
config = Rails.application.config.myapp
Capybara.app_host = "http://" + config[namespace][:host]
end
def login_as_staff_member(staff_member, password = "pw")
visit staff_login_path
within("#login-form") do
# fill_inには、「ラベル文字列」か「input要素のid属性かname属性の値」が指定できる。
fill_in "メールアドレス", with: staff_member.email
fill_in "パスワード", with: password
# click_buttonには、「ラベル文字列」か「input要素かbutton要素のid属性の値」が指定できる。
click_button "ログイン"
end
end
end