8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RubyとRailsの備忘録

Last updated at Posted at 2018-01-22

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
user_controller.rb
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のフォームの指定
.html.erb
<%= 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)を指定する必要がある
selectbox.html.erb
<%= 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
selectbox.html
<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

routes.rb
Rails.application.routes.draw do
  get "user/index", to: "user#index" 
  # 名前が一緒の場合は、次でもOK
  # get "user/index"
end

resources

  • データベース上での特定のCRUD (Create/Read/Update/Delete) 操作に対応付けられるルールになっています。
routes.rb
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

参考文献

$ 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
  • JSのMochaと同じ
  • フォルダはspec
spec/models/class_spec.rb
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の動作の違い
    1. メソッドの引数
      • lamdaは引数全必要
        • lamdaは関数っぽそうだが、引数の部分適用はできない
      • procedureはnot
    2. 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

8
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?