RoR on MacOSでの業務用メモ
Set up
Ruby Env
$ xcode-select --install
xcode-select: note: install requested for command line developer tools
$ brew install rbenv ruby-build
Ruby
$ rbenv install --list # インスコできるRubyのリスト
$ rbenv install 2.3.5 # 2.3.5をインスコ
$ rbenv versions # インスコしたものをリスト
system
* 2.3.5 (set by /Users/os10/apps/picknbuy24/.ruby-version)
$ rbenv global 2.3.5 # 2.3.5を使用する
$ ruby -v # 変わっていなかった
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin16]
$ which ruby # PATHが間違っている。~/.rbenv/shims/ruby にする必要がある
/usr/bin/ruby
$ rbenv init
# Load rbenv automatically by appending
# the following to ~/.zshrc:
eval "$(rbenv init -)"
$ vim ~/.bash_profile # eval "$(rbenv init -)" を追加
$ source ~/.bash_profile
$ which ruby
/Users/os10/.rbenv/shims/ruby
$ ruby -v
ruby 2.3.5p376 (2017-09-14 revision 59905) [x86_64-darwin16]
$
Gem
- PerlでいうCPAN
$ gem list # パッケージリスト
*** LOCAL GEMS ***
activesupport (4.2.6)
...
$
Bundler
- Gemfile: パッケージリスト(NPMで言う所のpackage.json)
$ gem install bundler
$ # bundlerの使い方
$ bundle install # Gemパッケージの一括インスコ
$ bundle # bundle installへのエイリアス
Rails
$ gem install rails -v 5.1.4
Rails
Components
-
Webインターフェイス:Rack
-
ビルド・タスク実行:Rake
-
コントローラー
- 抽象化レイヤ: AbstractController
- 実現レイヤ: ActionController(Web用)、ActionMailer(メール用)
-
モデル
- 抽象化レイヤ: ActiveModel
- 実現レイヤ: ActiveRecord
-
ビュー層: ActionView
-
ルーティング: ActionDispatch
- ルーティング
- リクエストをControllerに渡す
- config/routes.rb を読む人
-
ユーティリティ: ActiveSupport
- Rubyのコア機能拡張
-
Webサービス: ActiveResource
-
国際化: I18n
-
バッチ系: ActiveJob
-
ツール系
- 自動テスト: test/unit
- gemライブラリ管理: Bundler
new
$ rails new app -d mysql -B # -B(--skip-bundle)オプションをつける事によってパッケージのインスコをスキップする
# -dオプションでDBを指定する(デフォはsqlite3)
remove config/initializers/cors.rb
remove config/initializers/new_framework_defaults_5_1.rb
$ cd app
$ bundle # Gemパッケージのインスコ
Server
$ rails server # サーバー起動 sでもOK
$ # -p ポート(デフォは3000)
$ # -b バインドするIP(デフォは0.0.0.0)
$ # -d デーモンとするか
$ # -e 環境を指定(test、development、production)
Rack
- rackupコマンド
- config.ru: Rackのサーバ起動コマンドrackupの設定ファイル
- ミドルウェア、アプリケーションを組み合わせてWebサーバを作るためのインターフェイス
Model
- Rails5からはModelはActiveRecord::Baseからではなく、ApplicationRecordから継承する
$ rails g model Task title:string -f # fはファイルが存在する場合は上書き
invoke active_record
create db/migrate/20180107143924_create_tasks.rb
create app/models/task.rb
invoke test_unit
create test/models/task_test.rb
create test/fixtures/tasks.yml
- 型一覧
:primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp,
:time, :date, :binary, :boolean, :references
Attributes
- attr_accessor – セッタとゲッタを共に定義する
- attr_reader – ゲッタのみを定義する
- attr_writer – セッタのみを定義する
# 下記の2つのHogeクラスはどちらも同じ意味
class Moge
end
class Hoge
attr_accessor :name
end
class Hoge
def name=(val) # セッター
@name = val
end
def name # ゲッター
@name
end
end
# irb
h = Hoge.new
=> #<Hoge:0x007fb7f9bbd2b8>
h.name = "aaa"
=> aaa
m = Moge.new
=> #<Moge:0x007fb7f9bbd2b9>
m.name = "aaa"
NoMethodError # nameフィールドはないのでエラー
Scope
- 共通的に使うクエリをスコープを定義する
# 定義方法1: scopeメソッドで定義
class Post < ActiveRecord::Base
scope :published, -> { where(published: true) }
end
# 定義方法2: クラスメソッドのように定義
class Post < ActiveRecord::Base
def self.published
where(published: true)
end
end
# 使用方法
Post.published # => publishedカラムが"true"のPost達を取得
- 引数を取るパターン
# 引数を取るパターン
class Post < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
# 使用方法
Post.created_before(Time.local(2011)) #=> 2011年より前に作成されたPostレコードを取得
- デフォルトスコープ
class Customer < ActiveRecord::Base
# 退会した顧客では、 removed_atカラムは "削除した日付"
# 退会していない顧客では、removed_atカラムは "NULL"
default_scope { where("removed_at IS NULL") }
end
# 使用方法
# 全てのクエリにデフォルトスコープで指定した条件がつけられる
Customer.all
# => SELECT "customers".* FROM "customers" WHERE (removed_at IS NULL)
Association
- テーブル間リレーションシップをモデル上の関係として扱う仕組み
- おおよそ 1:1, 1:多, 多:多
- 外部キーは、belongs_toを追加した方のモデルのテーブルに追加される
- 関連付けは、通常双方向で行なう
- 2つのモデル両方に関連を定義する必要がある
- オプション
- :primary_key 参照元(自分)のテーブルの外部キーのカラム名
- :foreign_key 参照先のテーブルの外部キーのカラム名
- 注意!
- belongs_to 時は第一引数は単数形(:customer)
- has_many の時第一引数は複数形は(:customers)
belongs_to
- Authorがマスタ
- Bookはauthor_id(foreign key)を持っている
class Author < ActiveRecord::Base
end
class Book < ActiveRecord::Base
belongs_to :author
end
# HTML
<h1><%= @author.book.title %><h2>
<p><%= @author.name %></p>
has_one
- 1:1の関係
- この場合、ユーザーがマスタで、アカウントが従属している
class User < ActiveRecord::Base
has_one :account
end
class Account < ActiveRecord::Base
belongs_to :user
end
# マイグレ
create_table :accounts do |t|
t.belongs_to :user, index: { unique: true }, foreign_key: true
# ...
end
has_many
- 1:多の関係を表す
class Author < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :author
end
# HTML
<ul>
<% @author.books.each do |b| %>
<li><%= b.title %></li>
<% end %>
</ul>
# マイグレ
class CreateAuthors < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.belongs_to :author, index: true
t.datetime :published_at
t.timestamps
end
end
end
has_many through
- 別テーブルを通しての多:多の関係を表すアソシエーション
- throughの関連付けは、2つのモデルの間に第3のモデル(別のモデル)が存在する
- 医師(Physician)と患者(Patient)はどちらも同格で、診断予約の(Appointment)を通して関係を持つ
- なお、多:多用の中間テーブルを仕様する時はRails的にはHABTMを推奨
- 例えばtag機能を作る時のToxi法みたいなときの場合
- ただ、HABTMはBadらしいのでhas_many throughで作ってもOK
class Physician < ApplicationRecord # 医師
has_many :appointments
has_many :patients, through: :appointments # 医者は予約を通して患者を知っている
end
class Appointment < ApplicationRecord # 診断予約
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord # 患者
has_many :appointments
has_many :physicians, through: :appointments
end
# マイグレ
class CreateAppointments < ActiveRecord::Migration[5.0]
def change
create_table :physicians do |t|
t.string :name
t.timestamps
end
create_table :patients do |t|
t.string :name
t.timestamps
end
create_table :appointments do |t|
t.belongs_to :physician, index: true
t.belongs_to :patient, index: true
t.datetime :appointment_date
t.timestamps
end
end
end
has_one :through
- 別テーブルを通しての1対1のつながりを設定する
- この関連付けは、2つのモデルの間に第3のモデル(別のモデル)が存在する
- この場合供給者(Supplier)は勘定(Account)を通して勘定履歴(AccountHistory)を一つ所有する
class Supplier < ApplicationRecord
has_one :account
has_one :account_history, through: :account
end
class Account < ApplicationRecord
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ApplicationRecord
belongs_to :account
end
has_and_belongs_to_many
- 多:多のテーブル用のメソッド
- 例えば完成品(Assembly)と部品(Part)はそれぞれ同格でどちらも多:多の対応している
- ただ、どちらのテーブルにもhas_manyとblongs_toを追加すると次の問題がある
- 一つの更新で完成品とパーツのどちらも更新しなければならない
- よって、中間テーブル(assemblies_parts)を用意する必要がある
- ただ、この機能はbadらしいので、HMTが推奨されている?
多:多と中間テーブルについて
- 基本的にRDBは、多:多を2つのテーブルでは表現できない
- なぜなら二次元だから
- そこで、便宜上形式的に中間テーブルを用意する
- すなわち、中間テーブルはエンティティとしてリレーション以外の意味を持たない
- なので、HABTMでも中間テーブルのModelをソースコード的に宣言する必要がない(only in RDB)
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
# マイグレ
class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
def change
create_table :assemblies do |t|
t.string :name
t.timestamps
end
create_table :parts do |t|
t.string :part_number
t.timestamps
end
create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly, index: true
t.belongs_to :part, index: true
end
end
end
Enum
class Article < ActiveRecord::Base
# enumの定義(キーと数字のハッシュを渡す。数字がDBカラムに設定される)
enum status: { draft: 0, published: 1 }
end
# マイグレ
class CreateArticles < ActiveRecord::Migration
def change
create_table :articles do |t|
t.integer :status, default: 0, null: false, limit: 1
...
end
end
article = Article.new
# => #<Article id: nil, status: 0, created_at: nil, updated_at: nil>
article.status #=> "draft"
article.draft? #=> true
article.published? #=> false
# statusをpublishedに設定
article.published!
# => INSERT INTO "articles" ("status", "created_at", "updated_at") VALUES (?, ?, ?) [["status", 1], ["created_at", "2015-07-09 ..."], ["updated_at", "2015-07-09 ..."]]
article.status #=> "published"
# enumの属性名の複数形でハッシュを取得できる
Article.statuses #=> {"draft"=>0, "published"=>1}
# ハッシュとしてアクセスできる
Article.statuses[:draft] #=> 0
# キー値で設定できる
article = Article.new(status: :published)
article.published? #=> true
# Enumのキーをフィールドとして使用することが可能
Article.published.where('created_at > ?', 3.days.ago)
# => SELECT "articles".* FROM "articles" WHERE "articles"."status" = ? AND (created_at > '2015-07-06 ...') [["status", 1]]
Article.where("status <> ?", Article.statuses[:published])
# なお、配列でもOK
class Article < ActiveRecord::Base
enum status: [:draft, :published]
end
article = Article.create
article.status #=> draft
# DB値は 0
SQL methods
Testdata
joinsのためのテストデータ
class User < ApplicationRecord # User name:string
has_many :posts
end
class Post < ApplicationRecord # Post user_id:integer title:string month:integer
belongs_to :user
end
User.create(name:"山田花子")
User.create(name:"長瀬来")
User.create(name:"安田一郎")
User.create(name:"細田俊介")
User.create(name:"安原庄之助")
User.create(name:"中村拓郎")
Post.create(user_id:1,title:"楽しい休日の過ごし方" ,month:8)
Post.create(user_id:1,title:"先日の旅行での話" ,month:2)
Post.create(user_id:2,title:"昨日の出来事" ,month:12)
Post.create(user_id:3,title:"山登りに行きました" ,month:8)
Post.create(user_id:5,title:"友人が結婚しました" ,month:4)
Post.create(user_id:8,title:"最近少し気になったこと" ,month:1)
Post.create(user_id:13,title:"ランニングのコツ" ,month:9)
Joins
- 内部結合(INNER JOIN)するメソッド
- 内部結合なので、両方の表に存在するものだけが抽出される
- 2つのテーブルの一致したものの論理積
- Associationの関連付けは、通常双方向で行なう必要がある
User.joins(:posts)
> SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
+----+------------+---------------------------+---------------------------+
| id | name | created_at | updated_at |
+----+------------+---------------------------+---------------------------+
| 1 | 山田花子 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 1 | 山田花子 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 2 | 長瀬来 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 3 | 安田一郎 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 5 | 安原庄之助 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
+----+------------+---------------------------+---------------------------+
5 rows in set
User.joins(:posts).select("users.name") # selectメソッドで特定のカラムを取得できる
User Load (0.2ms) SELECT users.name FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
+----+------------+
| id | name |
+----+------------+
| | 山田花子 |
| | 山田花子 |
| | 長瀬来 |
| | 安田一郎 |
| | 安原庄之助 |
+----+——————+
User.joins(:posts).where(posts: { month: 8 }) # whereメソッドで特定のローを取得できる
User Load (0.2ms) SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "posts"."month" = ? [["month", 8]]
+----+----------+---------------------------+---------------------------+
| id | name | created_at | updated_at |
+----+----------+---------------------------+---------------------------+
| 1 | 山田花子 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 3 | 安田一郎 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
+----+----------+---------------------------+---------------------------+
2 rows in set
left_outer_joins
- 左外部結合(LEFT OUTER JOIN)をするメソッド
- 外部結合なので、論理積 + 左の一致していないレコード
User.left_outer_joins(:posts)
User Load (2.1ms) SELECT "users".* FROM "users" LEFT OUTER JOIN posts ON users.id = posts.user_id
+----+------------+---------------------------+---------------------------+
| id | name | created_at | updated_at |
+----+------------+---------------------------+---------------------------+
| 1 | 山田花子 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 1 | 山田花子 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 2 | 長瀬来 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 3 | 安田一郎 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 4 | 細田俊介 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 5 | 安原庄之助 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
| 6 | 中村拓郎 | 2017-07-04 12:03:54 +0900 | 2017-07-04 12:03:54 +0900 |
+----+------------+---------------------------+---------------------------+
7 rows in set
# 優先テーブルはUserなので、Userテーブルのカラムが優先して表示されている
Includes
TODO
Where
- Whereは一つのモデルのコレクションを返すのに使用する
- 一般的なSQLと同じimi
find/find_by
- find/find_byは一つのモデルを返すのに使用する
- findはprimary_key(id)で検索し、find_byはアトリビュートで検索できる
- エンティティが見つからない場合、findはExeptionを返すが、find_byはnilを返す
Others
Clinet.find([1, 10]) # SELECT * FROM clients WHERE (clients.id IN (1,10))
Client.where(first_name: 'Lifo')
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
Client.exists?(name: ['John', 'Sergei'])
User.where(id: 1).joins(:articles).explain # SQL文と結果のテーブルの確認
# batch_sizeオプションは、(ブロックに個別に渡される前に) 1回のバッチで取り出すレコード数を指定します。
# たとえば、1回に5000件ずつ処理したい場合は以下のように指定
User.find_each(batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
# 範囲条件
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
# SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')
# SQLのIn句
Client.where(orders_count: [1,3,5])
# Sort
Client.order(:created_at)
Client.order("created_at")
Client.order(orders_count: :asc, created_at: :desc)
# Distinct
Client.select(:name).distinct
# Limit と Offset
find_by_sql
find_by_sqlを推奨。型が違うので、SQLの実行結果を更に絞り込むことが出来ないため
# ActiveRecode::Relationが戻り値の型
u = User.find_by_sql("SELECT * FROM users")
puts(u.attributes["new_column"])
# ActiveRecode::Resultが戻り値の型
ActiveRecord::Base.connection.select_all(<<-SQL.squish)
SELECT * FROM users
SQL
refs https://stackoverflow.com/questions/2272846/rails-setting-column-alias-attribute-with-find-by-sql
Validation
以下の用な感じ
project = Project.find 118
project.assign_attributes(featured: true)
project.valid?
project.errors.full_messages
- 日本語でメッセージを作る場合
class Product < ActiveRecord::Base
validate :add_error_sample
def add_error_sample
if name.empty?
errors.add(:name, "に関係するエラーを追加")
errors[:base] << "モデル全体に関係するエラーを追加"
end
end
end
ここを見る
https://qiita.com/shunhikita/items/772b81a1cc066e67930e
Transaction
- ロールバックの発火条件は例外のキャッチ
- !マークが後ろについているメソッドは例外を発生するのでそれをなるべく使う
- #saveだとerrのbooleanが変えるが、save!だと、errの時は例外が発生する
- 当然だが、複数のDBにまたがる時は、それぞれをネストしてトランザクションの処理をしなければならない
- つまり、複数のテーブルのrollbackは一片にできない
begin
ApplicationRecord.transaction do
# 更新処理
end
rescue ActiveRecord::ActiveRecordError => e
end
Controller
$ rails generate controller user # -s で既存ファイルはスキップ
$ # --assets アセットを生成するか(デフォルトはtrue)
$ # -t テストフレームワーク
$ # --helper ヘルパー生成するか(デフォはtrue)
create app/controllers/user_controller.rb
invoke erb
create app/views/user
invoke test_unit
create test/controllers/user_controller_test.rb
invoke helper
create app/helpers/user_helper.rb
invoke test_unit
invoke assets
invoke coffee
create app/assets/javascripts/user.coffee
invoke scss
create app/assets/stylesheets/user.scss
- 例
class UserController < ApplicationController
def index # アクションメソッド
render plain: "Hello World" # plain テキストを返す
end
def index
@hello = "Hello world" # テンプレート変数
render "user/index"
end
end
Filter
- フィルタは、アクションの直前 (before)、直後 (after)、あるいはその両方 (around) に実行されるメソッド
class CatsController < ApplicationController
before_action :set_cat, only: [:show, :edit, :update, :destroy]
# showなどのメソッドの前にset_catメソッドが走る
end
Params
- Paramsとはリクエスト情報をひとまとめにして、params[:パラメータ名]という形式で取得できるハッシュ
- bodyやquery string
- Strong Parameters
- mass assignment 脆弱性の対策に導入された
- 具体的にはrequireとpermitのメソッドのこと
- #require
- form名(form_forの第一引数か、asの名前を指定する)
- #permit
- 引数に設定したkeyの値だけを取得する
From Link
リンクからGETパラムを受け取る例
<%= link_to 'ユーザ名', controller: 'users', action: 'show',
id: 1, name: '太郎' %>
def show
id = params[:id] #=> id = 1
name = params[:name] #=> name = '太郎'
end
Flash
ログイン時のメッセージや、登録や更新時の成功通知などの簡易な通知のためのもの
def index
flash[:notice] = "ようこそ。本日は#{Date.today}です。"
end
なお、リダイレクト時にflashはよく使うので、redirect_toメソッドのオプションとしてnoticeとalertが用意されている
# flash[:notice]にメッセージを設定しながら、リダイレクトをしている
redirect_to @user, notice: 'ログインに成功しました'
# flash[:alert]にメッセージを設定しながら、リダイレクトをしている
redirect_to login_url, alert: 'ログインできませんでした'
ビュー
<% flash.each do |key, value| %>
<%= content_tag(:div, value, class: "#{key}") %>
<% end %>
## View
- ERB: Embedded Rubyの略
- もとのレイアウトは application.html.erbで、yieldに埋め込まれている
```user/index.html.erb
<% @users.each do |user| %> # usersはテンプレート変数
<tr>
<td><%= user.name %></td>
<td><%= user.no %></td>
</tr>
<% end %>
Form
- form_forメソッドはモデルに紐付いたフォームを作成するのに利用する
- 一方、form_tagの場合は特定のモデルを気にする必要がないので、モデルとは切り離した汎用的なフォーム(検索フォームなど)を作成する場合には有用
- submitを押した時の挙動は@userが新規作成された時はuser#createへ@userが既存であった場合はuser#updateへ自動でアクションを振り分けられる
- コールバックのアクションを変えたい場合はurlオプションを使う
- パラメーター
- 第一引数
- モデル変数(コントローラーのインスタンス変数を入れる)
- as: 'hoge'
- paramsでの参照用の名前
- url: :hoge
- formのコールバックメソッド(hoge)の指定ができる
- html: {method: :get}
- HTMLのフォームの指定
- 第一引数
<%= form_for(@user) do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
なお、Rails 5.1ではform_tagとform_forという二つのヘルパーメソッドがform_with
に統合された
from_for
フォームからGETやPOSTなどのメソッドから受け取る例
<% form_for @user do |f| %>
名前:<%= f.text_field :name %>
年齢:<%= f.text_field :age %>
職業:<%= f.text_field :job %>
<% end %>
params[:user][:name] #=> 太郎
params[:user][:age] #=> 年齢
params[:user][:job] #=> 学生
form_with
- NOte: form_withはform_tag/form_forと違い、remote:trueがデフォ
- local: trueを入れると例えばvalidationエラーなどを表示できる
<%= form_with model: @user, local: true do |f| %>
<% end %>
collection_select
- form_forの場合、既に対象のオブジェクト名が明確であるため、そのオブジェクト名を都度指定しなくてもいい
- 逆に下の例では第一引数にオブジェクト名(:item)を指定する必要がある
<%= form_for(@item) do |f| %>
<%= f.collection_select :name, @users, :id, :name %>
<% end %>
<% # form_forがない場合 %>
<%= collection_select(:item, :name, @users, :id, :name, selected: @current_user.try(:id)) %>
# 戻り値
# 第一引数はobject = item
# 第二引数はobject field name = name
# オプション
# : include_blank: boolean # :include_blank => true
<select selectd=@current_user.try(:id) >
<option value=id>:name<option>
</select>
respond_to
- respond_toメソッドは、リクエストで指定されたフォーマット(HTML,JSON,XML)に合わせて結果を返すメソッドと
class UsersController < Application
def show
@user = { 'name' => 'Yamada', 'old' => '20' }
respond_to do |format|
format.html # 拡張子がHTMLでアクセスしてきたときの処理
format.json { render :json => @user } # JSONの時
format.xml { render :xml => @user } # XMLの時
end
end
end
Router
$ rails routes # 現在のプロジェクトで有効なルート設定一覧を表示する
Prefix Verb URI Pattern Controller#Action
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
get
Rails.application.routes.draw do
get "user/index", to: "user#index"
# 名前が一緒の場合は、次でもOK
# get "user/index"
end
resources
- データベース上での特定のCRUD (Create/Read/Update/Delete) 操作に対応付けられるルールになっています。
Rails.application.routes.draw do
resources :photos # photos_controller.rbのactionが紐づく
end
namespace
namespaceは、URLもcontroller格納フォルダも、指定のパスになる
namespace :admin do
resources :users
end
# rake routes
Prefix Verb URI Pattern Controller#Action
admin_users GET /admin/users(.:format) admin/users#index
POST /admin/users(.:format) admin/users#create
new_admin_user GET /admin/users/new(.:format) admin/users#new
dit_admin_user GET /admin/users/:id/edit(.:format) admin/users#edit
admin_user GET /admin/users/:id(.:format) admin/users#show
PATCH /admin/users/:id(.:format) admin/users#update
PUT /admin/users/:id(.:format) admin/users#update
DELETE /admin/users/:id(.:format) admin/users#destroy
module
moduleは、controllerの格納フォルダだけ、指定パスになる
scope module: :admin do
resources :users
end
# rake routes
Prefix Verb URI Pattern Controller#Action
users GET /users(.:format) admin/users#index
POST /users(.:format) admin/users#create
new_user GET /users/new(.:format) admin/users#new
edit_user GET /users/:id/edit(.:format) admin/users#edit
user GET /users/:id(.:format) admin/users#show
PATCH /users/:id(.:format) admin/users#update
PUT /users/:id(.:format) admin/users#update
DELETE /users/:id(.:format) admin/users#destroy
scope
scopeのみの場合は、URLだけ、指定のパスになる
scope '/admin' do
resources :users
end
# rake routes
Prefix Verb URI Pattern Controller#Action
users GET /admin/users(.:format) users#index
POST /admin/users(.:format) users#create
new_user GET /admin/users/new(.:format) users#new
edit_user GET /admin/users/:id/edit(.:format) users#edit
user GET /admin/users/:id(.:format) users#show
PATCH /admin/users/:id(.:format) users#update
PUT /admin/users/:id(.:format) users#update
DELETE /admin/users/:id(.:format) users#destroy
Scaffold
- モデル、コントローラ、ビューをまとめて作成する
$ rails g scaffold User name:string no:integer # nameとnoのフィールドを持つUserテーブルのプログラムの作成
$ rails d scaffold User name:string no:integer # gの取り消しコマンド
$ rails db:migrate # db/migrate/フォルダにあるマイグレーションスクリプトが実行される
$ rails db:migrate:reset # DBを一旦破棄してからマイグレーションをする
Console
$ rails console
> User.all
User Load (0.1ms) SELECT "users".* FROM "users" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "hoge", no: 12, created_at: "2018-01-07 12:57:34", updated_at: "2018-01-07 12:57:34">]>
Destroy
$ rails destroy ファイルの種類
DB
Console
- config/database.ymlで定義した接続情報に従って、データベースクライアントを起動する
$ rails dbconsole [環境(develop, text, production)]
Version
- railsはVCSのようにDBをバージョンで管理している
- migration fileのファイル名の頭に14桁の数字がversionです。
rails db:version
Rollback
dbの状態を一つ前に戻す
rails db:rollback
Fixture
- テストデータの読み込み
$ touch test/fixtures/hoges.yaml
$ rake db:fixtures:load FIXTURES=hoges,ages
- db/seeds.rb はアプリケーション動作に必須なデータを記述するファイル
- test/fixtures/* は動作確認用のデータを入れるファイル
Seed
- 初期データの投入
- マスタデータなど
$ vim ./db/seed.rb # 追加する
User.create(name:'hoge', is_deleted: false)
$ rails db:seed
Migration
参考文献
- http://ruby-rails.hatenadiary.com/entry/20140810/1407634200#migration-change-column-default
- https://qiita.com/dawn_628/items/13fa64dc6d600e921ce3
$ rails db:migrate
$ rails db:migrate RAILS_ENV=test # ※ -e ではない
status
rails db:migrate:status
Init
$ rake db:drop db:create db:migrate
Test
- Minitest
- Rubyに標準搭載されているテストFW
- RSpec
- TDD、BDDのためのテストFW
Gem
Gemのインストール
- rspec-rails: RSpecのラッパーライブラリ
- factory_girl_rails: テストデータ用
$ vim Gemfile
group :development, :test do
gem "rspec-rails"
gem "factory_girl_rails"
end
Prepare
$ rails db:test:prepare
RSpec
- specファイルにプログラムに期待する振る舞いを記述する
- it 〜 endのなかに一つのテストケースを書く
- describe {Model}
- it
- example
- it
- describe {Model}
- JSのMochaと同じ
- フォルダはspec
describe "テスト対象" do
it "期待するプログラムの振る舞い"
# ...
end
context "条件" do
it "contextの条件下で期待する振る舞い" do
# ...
end
end
end
- 初期化
$ rails g rspec:install
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
- 実行方法
$ rspec spec/models/borrowing_spec.rb
$ # -f d = ドキュメント形式で出力する。(--format documentation)
- matcher : 期待値と実際の値を比較して、一致した(もしくは一致しなかった)という結果を返すオブジェクト
- eq(x):xと等しいか
- not_eq(x):xと等しくないか
- be [true/false]:trueかfalseか
- be < x:xより小さいか
- be_between(x, y).inclusive:数値がxとyの範囲内か(xとyも範囲に含む)
- respond_to(:{メソッド名}):そのメソッドが存在しているか
let
beforeを使う処理をletに置き換えられる
require 'rails_helper'
describe "Account" do
context "Funciton" do
before do
@account = FactoryBot.build(:account)
@account.save()
@account_id = @account.id
end
it "Find active account from DB" do
current_account = Account.active.find(@account.id)
expect(current_account).to eq @account
end
end
end
require 'rails_helper'
describe "Account" do
context "Funciton" do
let(:account) { create(:account) }
it "Find active account from DB" do
current_account = Account.active.find(account.id)
expect(current_account).to eq account
end
end
end
ActiveSupport
- Railsの汎用的なライブラリ
Ruby
Block
- do~end(もしくは{~})で囲われた、引数となるためのカタマリ。
- 複数行の場合はdo~endで1行の場合は{~}が使われがち
- do~endの場合はパイプで引数を取り、{~}の場合は丸括弧で引数を取る
- ブロックは所謂callback関数
- 性質上、関数の最後の引数となる
- メソッドの内部では、Kernel#block_given?()メソッドを使ってブロックの有無を確認できる
ブロックの例
def hoge
yield
end
hoge do
p "Hello, block!" # ちなみに、#block_given? でブロックの存在をチェックできる
end
=> "Hello, block!" #give_me_block内で、yieldによって呼び出された。
ブロックの例(Procを使った場合)
- &を付けることで、関数にブロックが渡ってきたら、Procオブジェクトに変換している
- ブロック引数は、仮引数の中で一番最後に定義されていなければならない
def hoge( &block ) # blockを引数として受け取っている
block.call # yieldのかわり
end
hoge do
p "Hello, block!"
end
=> "Hello, block!"
Procedure
- Procはおそらくprocedureの略
- ブロックをオブジェクト化したクラス(Proc)
proc1 = Proc.new do
p "hoge"
end
# 実行
proc1.call # => "hoge" # proc1オブジェクトのブロックを呼び出した
Labmda
- lambda と procedure は、どちらも Proc オブジェクト
- lamdaとprocedureの動作の違い
- メソッドの引数
- lamdaは引数全必要
- lamdaは関数っぽそうだが、引数の部分適用はできない
- procedureはnot
- lamdaは引数全必要
- returnの挙動
- lamdaは関数がevalされるだけ
- procedureは呼び出し元のスコープもreturnする
- なぜなら、procedureは呼び出し元のスコープの処理なので
- メソッドの引数
構文
proc_obj = Proc.new {|x, y| [x, y] } #=> #<Proc:0x007ff96a8c8fc8@(irb):1>
lambda_obj = lambda {|x, y| [x, y] } #=> #<Proc:0x007ff96a8d0d18@(irb):2 (lambda)>
挙動の違い
# トップレベルで実行した場合
proc { return 1 }.call #=> LocalJumpError: unexpected return
# メソッド内で実行した場合
def foo
proc { return 1 }.call
return 2
end
foo #=> 1
lambda { return 1 }.call #=> 1
Bundle InstallとUpdateの違い
Install
bundle installを実行すると、railsは、gemfile.lockを元にgemのインストールを行います。この時、gemfile.lockに記述されていない、且つgemfileに記述されているgemがある場合、そのgemとそのgemに関連するgemをインストール後、gemfile.lockを更新します。
Update
bundle updateを実行すると、Bundlerは、gemfileを元にgemのインストールを行います。その後、gemfile.lockを更新します。
タスクランナーのち外
rails runner
- スクリプトとしてバッチを書く
rake task - ビルドタスクとしてバッチを書く
sidekiq - 非同期処理としてバッチを書く
参考文献
https://qiita.com/issobero/items/e0443b79da117ed48294
https://h5y1m1411.gitbooks.io/rails4-with-google-map-api/
https://qiita.com/scivola/items/17470c52641d3ffa1650
https://stackoverflow.com/questions/4384284/ruby-on-rails-generates-model-fieldtype-what-are-the-options-for-fieldtype
https://stackoverflow.com/questions/29138077/rails-fixtures-vs-seeds
https://www.buildinsider.net/web/rubyonrails4/0202
https://qiita.com/ShuntaShirai/items/465ddfc2c0f29ed93523
http://tech.gmo-media.jp/post/45955244694/rails%E3%81%AE%E8%87%AA%E5%8B%95%E3%83%86%E3%82%B9%E3%83%88rspec%E3%81%A7model%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E7%B7%A8
http://www.atmarkit.co.jp/ait/articles/1103/09/news112.html
https://qiita.com/shizuma/items/c7b8d7b91e8f325f8ad9
http://ruby-rails.hatenadiary.com/entry/20141129/1417223453
https://railsguides.jp/action_controller_overview.html
https://www.xmisao.com/2014/02/10/ruby-attr-accessor-attr-reader-attr-writer.html
https://www.sejuku.net/blog/13163#form_for
http://ruby-rails.hatenadiary.com/entry/20140814/1407994568
http://techblog.kyamanak.com/entry/2017/08/29/012909
https://qiita.com/To_BB/items/47d2c7b1bc3513025d7b
https://tech.a-listers.jp/2012/01/31/the-evil-unnecessary-has_and_belongs_to_many/
http://gomocool.net/gomokulog/?p=820
https://www.sejuku.net/blog/26522
http://ruby-rails.hatenadiary.com/entry/20150710/1436461745
https://railsguides.jp/active_record_querying.html#%E9%85%8D%E5%88%97%E3%81%A7%E8%A1%A8%E3%81%95%E3%82%8C%E3%81%9F%E6%9D%A1%E4%BB%B6
http://techblog.kayac.com/2016/12/01/000000
http://morizyun.github.io/ruby/active-record-belongs-to-has-one-has-many.html
https://teratail.com/questions/90663
https://qiita.com/blueplanet/items/522cc8364f6cf189ecad
https://stackoverflow.com/questions/19187820/view-cause-of-rollback-error-in-rails-console
https://www.tamurasouko.com/?p=783
https://ja.stackoverflow.com/questions/39554/migration%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%A7%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92%E8%BF%BD%E5%8A%A0-%E5%89%8A%E9%99%A4%E3%81%97%E3%81%A6%E3%82%82schema-rb%E3%81%AB%E5%8F%8D%E6%98%A0%E3%81%97%E3%81%AA%E3%81%84%E3%81%AE%E3%81%AF%E3%81%AA%E3%81%9C%E3%81%8B
http://ruby-rails.hatenadiary.com/entry/20141127/1417086075
https://stackoverflow.com/questions/21556733/rails-select-subquery-without-finder-sql-if-possible