FlutterエンジニアがRuby on Railsを勉強してみた

7 min read読了の目安(約6800字

ひょんなことからRailsを勉強し始めたのでメモ。

現場で使える Ruby on Rails5 速習実践ガイドで勉強しています。

Rubyの基本

.class

オブジェクトの型を調べる。

> "氏名".class
=> String

> 1.class
=> Integer

.object_id

オブジェクト毎に持っている固有の番号を調べる。

> "氏名".object_id
=> 70291392970800

> 1.class
=> 3

再び実行すると"氏名"の番号は変化したが1は変化しない。

> "氏名".object_id
=> 70202805146000

> 1.class
=> 3

"氏名"はRubyコードを実行する度に別のオブジェクトが作られるが1という数値オブジェクトは何回Rubyコードを実行しても同じ数値オブジェクトが提供される。

ゲッターやセッターを簡単に定義する

通常のゲッター・セッター

class User
  def name=(name)
    @name = name
  end
  
  def name
    @name
  end

class User
  attr_accessor :name
end

attr_accessorの部分をattr_readerにするとゲッターだけ、attr_writerにするとセッターだけを定義できる。

メソッドからメソッドを使う

class User
  attr_accessor :name, :address, :email
  
  def profile
    "#{name}(#{address})" # name, addressメソッドを使用している
  end
end

nil

オブジェクトがnilかどうか調べることができる。

> value = nil
> value.nil?
=> true

真偽

nil?を使う場合

return if value.nil? # valueがnilなら以降の処理をしない

偽かどうかを調べている例

return if !value # valueがnilもしくはfalseなら以降の処理をしない

条件分岐

当てはまらない場合に分岐するunless

age = 16
unless age >= 20
  puts "20歳未満"
end

↑と同義

age = 16
if age < 20
  puts "20歳未満"
end

elseを使う場合はifを使用した方が読みやすい。

後置if

# 出力される
puts "Hello." if true

# 出力されない
puts "Hello." if false

配列

繰り返し処理はeachが多く使用される。

a = [1,2,3]
a.each do |element|
  puts element
end

=> 1
=> 2
=> 3

要素を追加

a = [1,2,3]
a << 4

privateメソッド

class Person
  def initialize(money)
    @money = money
  end
  
  # 億万長者かどうかを返す
  def billionaire?
    money >= 1000000000
  end
  
  private # この行以降に定義されたメソッドはprivateになる
  
  def money
    @money
  end

引数にデフォルト値を指定する

def name(full = true, with_age = true)
  n = if full
      "#{family_name} #{given_name}"
    else
      given_name
    end
  n << "(#{age})" if with_name
  n
end

person.name
=> "浦島太郎(100)"

キーワード引数

def name(full: true, with_age: true)
  # do something
end

モジュールによる共通化

# おしゃべり能力
module Chatting
  def chat
    "Hello"
  end
end

class Dog
  include Chatting
end

> pochi = Dog.new
> pochi.chat
=> "Hello"

複数のモジュールを取り込むことができる。

例外捕捉

begin
  # 例外が発生するかもしれないコード
  # Dartのtryに相当
rescue
  # 例外に対応するコード
  # Dartのcatchに相当
ensure
  # 例外が発生してもしなくても実行したいコード
  # Dartのfinalyに相当
end

メソッド内の処理全体に対して例外処理を行いたい場合はbeginを省略できる。

def method
  # メソッドのコード
rescue
  # 例外に対応するコード
ensure
  # 例外が発生してもしなくても実行したいコード
end

例外の内容を出力する

begin
  do_something
rescue => e
  puts e
end

nilガード

Dartの??に相当

number ||= 10
number || (number = 10)

よくある利用法

def children
  @children ||= []
end

ぼっち演算子 &.

nilを許容する。Dartの?.に相当

> object = nil
> object&.nil
=> nil

%記法

全ての要素が文字列である配列は%Wというキーワードを使って書くことができる。

> ary = %w(apple banana orange)
> puts ary
=> ["apple", "banana", "orange"]

全ての要素がシンボルである配列は%iというキーワードを使って書くことができる。

> ary = %i(apple banana orange)
> puts ary
=> [:apple, :banana, :orange]

配列の各要素から特定の属性だけを取り出す

Dartのusers.map((user) =>user.name).toList();に相当

user1 = User(name: "田中")
user2 = User(name: "鈴木")
user3 = User(name: "山田")
users = [user1, user2, user3]

names = users.map(&:name)
=> ["田中", "鈴木", "山田"]

Railsの基本

アプリを新規作成

$ rails new sample_app -d postgresql

DBを作成

$ rails db:create

サーバ起動

$ rails s

ユーザー管理機能の雛形を作る

$ rails g scaffold user name:string address:string age:integer

Route確認

routes.rbで定義された全てのルーティングを確認できる。

$ rails routes

Railsのエラーメッセージなどを日本語で出せるようにする

ファイルをダウンロード。

$ wget https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml -P config/locales/

アプリケーションの設定。config/initializers/locale.rbに以下を追記。

Rails.application.config.i18n.default_locale = :ja

モデルを作成

$ rails g model [モデル名] [属性名:データ型 属性名:データ型 ...] [オプション]

# 例
$ rails g model Task name:string description:text

コントローラを作成

$ rails g controller コントローラ名 [アクション名 アクション名 ...] [オプション]

#例
$rails g controller tasks index show new edit

必須かどうかの検証をする

app/model/task.rb

class Task < ApplicationRecord
  validates :name, presence: true
end
> task = Task.new
> task.save
=> false

テスト

RSpec

書き方

describe [使用を記述する対象(テスト対象)], type: [Specの種類] do
  context [ある状況・状態] do
    before do
      [事前準備]
    end
    
    it [仕様の内容(期待の概要)] do
      [期待する動作]
    end
  end
end

FactoryBotでテストデータを作成

spec/factories/users.rbUserのファクトリを追加

Factory.define do
  factory :user do
    name {'テストユーザー'}
    email {'test@example.com}
    password {'password'}
  end
end

System Spec

特定のURLにアクセスする。

visit login_path

Text Fieldに値を入力する。

fill_in 'メールアドレス', with: 'a@example.com'

ボタンを押す。

click_action 'ログインする'

テスト実行。

$ bundle exec rspec spec/system/{テストファイル名}

Railsの全体像を理解する

ルーティング

config/routes.rb

get '/login', to: 'sessions#new'

上記の意味

GETメソッドで'/login'というURLに対してリクエストが来たら、SessionsControllernewというアクションを呼び出してほしい。また、'/login'というURLをlogin_pathというヘルパーメソッドで生成できるようにする。

resourcesでCRUDのルート一式を定義する

config/routes.rbresources :tasksのように定義しておくとCRUD全てのルートが生成される。

Rails.application.routes.draw do
  resources :tasks
end

特定のルートだけ生成することも可能。

# indexのみ生成
resources :tasks, only: [:index]

# destroy, edit, update以外を生成
resources :tasks, except: [:delete, :edit, :update]

日時の扱い方

現在有効なタイムゾーンを表示。

$ Time.zone

現在時刻。

$ Time.zone.now
$ Time.current

日時の扱い方に関する設定。

config/application.rb

# どのタイムゾーンの表現で日時をDBに保存し、読み出し時にどう解釈するか(:utc or :local)
# デフォルトは:utc
# :localに設定するとRubyの動作している環境のシステム時刻を使用
config.active_record.default_timezone

# デフォルトのタイムゾーンを日本時間にする
config.time_zone = 'Asia/Tokyo'

エラー処理のカスタマイズ

デバッグ用 / 本番用のエラー画面の切り分け

config/environments/development.rb

# デバッグ用
config.consider_all_requests_local = true

# 本番用
config.consider_all_requests_local = false

development環境、test環境ではデフォルトでtrueになっている。

アプリケーション固有のエラー処理の追加

controllerファイル

rescue_form MyCustomError, with: show_custom_error_page

private
def show_custom_error_page(error)
  @error = error
  render :custom_error
end

※注意点

  • config.consider_all_requests_localの値にかかわらず常に発動するのでデバッグ時に不便
  • この仕組みはコントローラで実行されるためその外側(ルーティングなど)で発生した例外に対してはこの方法でエラー処理を行うことはできない

ログの利用方法

環境毎の出力先

log/production.log
log/development.log
log/test.log

出力方法

logger.debug 'デバッグ'
=> 'デバッグ'

ロガーの設定

config/environments/{環境}.rb

# warn以上のログのみ出力される
config.log_level = :warn() # 設定したいログレベル