[ruby: note] Ruby - A programmer's best friend. #ruby
日本人 ( Matz まつもとゆきひろ氏 ) の開発した純粋オブジェクト指向・インタプリタ・動的型付けなプログラミング言語。スタートアップ系の WEB アプリ開発で採用例が多く Ruby on Rails という MVC フレームワークがあまりにも有名。
書き方独特だけど、書いてて楽しい。いわゆる「Ruby っぽさ」は Rubyのリファクタリングでイケてないコードを美しいオブジェクト指向設計のコードへ改良するための方法 を読むのがわかりやすい。
ruby 言語ランタイムのバージョン管理ツール。nodenv みたいに global では 2.6 を、特定プロジェクトでは 2.4 を ... みたいにバージョンの切り替えができる。
local (特定プロジェクトディレクトリ) では .ruby-version というファイルに ruby バージョンを記載して他開発者と共有するつくり。
追記: 最近言語ランタイムのバージョン管理は anyenv 経由で rbenv を入れて環境構築する方向に統一した
gem - パッケージ管理
rubygems.org
Bundler - bundler.io
Bundlerの使い方
bundler、bundle execについて
GemfileでインストールしたGemを削除する
インストールしたgem なのに、requireしないと使えない。 - require についてはこの辺
gem は Ruby のパッケージ管理ツール。端末グローバルに個別に Ruby パッケージをインストールできる。但しこの場合プロジェクト間で依存を引き継いでしまう、これを解決し「アプリケーション毎に gem の依存関係を分離して管理」するために用いる gem が Bundler と呼ばれるパッケージバンドルツール。
Bundler はアプリケーション毎に Gemfile と Gemfile.lock により gem パッケージ群依存を解決・管理する。一般的に端末グローバルには Bundler 以外の gem はインストールせずに、各アプリケーションで必要な gem は全て Bundler 経由でローカル vendor/bundle に導入するのが望ましい。
# gem による個別インストール ( to Global )
$ gem install bundler
# バージョン指定
$ gem install sqlite3 -v "1.3.8"
# Bundler 経由で Gemfile の内容をアプリルートにインストール
# インストール状態のハッシュマップを Gemfile.lock へ反映
$ vi Gemfile
> source 'https://rubygems.org'
> git_source(:github) { |repo| "https://github.com/#{repo}.git" }
>
> ruby '2.5.1'
>
> gem 'gemname'
> .....
$ bundle install --path vendor/bundle
# bundler 経由で gem をアンインストール
$ bundle exec gem uninstall gemname
# Gemfile から当該パッケージを削除
# Gemfile.lock からも削除
rbenv と組み合わせて ruby バージョンをファイルから読み取るのもよく見る。
# Gemfile
ruby File.read(File.join(File.dirname(__FILE__), '.ruby-version')).strip
group で指定された gem は当該 env 以外では「ファイルはインストールされるが require されていない状態になる」
require: faise と同等の状態?require 'hoge' の名前が gem 名と異なる gem は自動で require されないため Gemfile で require: 'hoge-gem' とかしないといけないObject#nil? で全インスタンスは「 NULL 判定メソッド」を持ってたりする
false または nil だけが偽 で それ以外は 0 や '' も含め全て真 と評価される
return を省略した場合、最後に評価された値が返るreturn を記載した場合は他言語と同様に以後処理をスキップし return した値を返すreturn 'hoge', 'fuga', 'piyo' とすることで配列を返せる# coding: utf-8 で指定true
0 や "" は truefalse
false または nil は falsenil 一般的な言語の null のこと.object.object_idmain
puts メソッドは main オブジェクトのメソッドを self を省略するカタチで呼ばれてたりするvariable$global_variable@variable
@@variable
FOO = 'FOO'['hoge', 'fuga', 'piyo']array[key]['key' => 'value']hash['key']
hash が nil のとき hash['key'] でアクセスすると undefined method[]' for nil:NilClass` 例外となるhash['not-exists'] # nillhash.fetch('key') # valuehash.fetch('not-exists') # KeyError 例外1..3 で 1, 2, 3 みたいな Immutable かつ Enumerable なオブジェクト作れる('a'..'z') で a, b, c, ... z とかも:hoge のようにして「完全に一意なオブジェクトを指す参照値」を定義可能{} は使わず基本的に do ... end で表現sprintf() みたいなフォーマッタ
puts %w(Alice Bob Chrlie) # ['Alice', 'Bob', 'Chrlie']"variable is #{variable}"visible?split!require で外部ファイルを読み込みできる'0001'.to_i # 数値 1 として評価
12345.to_s # 文字列 '12345' として評価
.to_i で hogehoge012 とかのとき死んでほしければ Integer() を利用# クラス
class Foo < Super
# いわゆるメンバ変数
@instance_prop = 0
# いわゆる定数、クラス定数として FOO::MY_CONSTANT でアクセス
MY_CONSTANT = 'Foo constant'
# メンバ変数はアクセサ ( 後述 ) を明示しないと外から読み書きできない
attr_accessor :instance_prop
# クラス変数
# 当該クラスや子孫、インスタンスで共有されるグローバル変数的な
@@class_prop = 'hello'
def instance_method
# いわゆるメンバメソッド
end
def self.class_method
# 特異メソッドと呼ばれるいわゆるクラスメソッド、他言語では static メソッドとか
# Foo::class_method で呼べるし、ややこしいが Foo.class_method でも OK
end
end
# 特異クラス - 特定のオブジェクトにメソッドやインスタンス変数を定義・追加できる
obj = Object.new
class << obj
def instance_method
:
end
:
end
# モジュール
module Mod
Version = '2.3.0'
def hello
puts 'hello'
end
module_function :hello
end
Mod::Version # 2.3.0
Mod.hello # hello
class Klass
include Mod # モジュール読み込み → インスタンスが Mod モジュールの機能を持つ
# extend Mod でクラスが ( Static に ) Mod モジュールの機能を持つことも可能
# また、prepend で mixin すると module 側の定数やメソッドを受け入れ側クラスで上書きできる
end
いつも忘れるRubyの include, prepend, extend の意味。
Rails の ActiveSupport::Concern の存在理由
# Base クラス + ActiveRecord モジュールを継承
class ApplicationRecord < ActiveRecord::Base
end
モジュールは PHP でいうところの Trait っぽいやつで、Ruby では mix-in と呼ぶみたい。
module Bark
def initialize
@cry = 'woo'
end
def bark
puts "#{@cry.capitalize}!! #{@cry.capitalize}!!"
end
end
class Animal
include Bark
end
class Cat
include Bark
def initialize
@cry = 'meow'
end
end
class Dog
include Bark
def initialize
@cry = 'woof'
end
end
animal = Animal.new
animal.bark # Woo!! Woo!!
cat = Cat.new
cat.bark # Meow!! Meow!!
dog = Dog.new
dog.bark # Woof!! Woof!!
Ruby のプロパティは Getter / Setter メソッドを定義しないと外部からは参照できないが、アクセスメソッドにプロパティ名を食わせることで簡単にアクセサを生やすことができる。
attr_accessor :variable .variable でアクセス可能な getter / setter メソッドを自動定義attr_reader :variable 上記の getter のみ版attr_writer :variable 上記の setter のみ版レシーバーは特に Ruby の用語というわけではなく、オブジェクト指向一般に使われる用語です。意味は「 . の左側」で間違いありません。オブジェクト指向では処理はメッセージのやり取りによって行うという考え方があります。(
car.run()のとき ) car の run メソッドを呼び出すというのをメッセージの観点からいうと car に run のメッセージを送る となります。このため、car はメッセージの受け取り側なので、「レシーバー」と呼ばれています。Rubyは変数の代入とセッターメソッドの違いをselfを使うことで区別するようです。要はセッターメソッドの時にのみselfを付ければ良いわけですね。
Ruby は self など、他言語でいう疑似変数として表現されるレシーバは極力省略する傾向にある。Rubocop に怒られないようにするためには、セッターにはレシーバを明示し ( メンバ変数へのセットとローカル変数へのセットの違いがあるため ) 、メソッドコール ( アトリビュートのゲッター含む ) では省略するのがよろしいみたい。
class Guitar
attr_accessor :color
def initialize
self.color = '赤'
end
def say_color
"このギターは #{self.color} やで!" # Redundant self detected. で Rubocop 激おこ
"このギターは #{color} やで!" # Rubocop もにっこり
end
end
Ruby の private と protected 。歴史と使い分け
Rubyのprivate修飾子からアクセス修飾子を考えるpublic に設定されたメソッドは制限なしに呼び出せます。 private に設定されたメソッドは関数形式 ( レシーバ省略形式 ) でしか呼び出せません。 protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せます。
基本は上記 public / protected / private の 3 パターン。但し Ruby のアクセス修飾子は Access Control として作られている ( = 外部から呼べる呼べないの制御をする ) わけではない ことに注意。あくまでも秘匿している、外部からのコールを想定してないことの「明示 + ちょっと外部コールに制限ある ( レシーバつきで呼べない ) 」程度のもの。Ruby 開発当時は C++ や Java でよくある「外から呼べない private 」がまだ浸透しておらず、Smalltalk の Private Category ( 使わないでねってカテゴリの明示 ) から着想を得て実装したのでこうなったらしい。
class Klass
def public_method
puts 'public'
end
protected # 以降は全て protected メソッド
def protected_method
puts 'protected'
end
end
class Parent
def public_method
# 'hello ' + self.private_method # レシーバ付きのため通らない
'hello ' + private_method # 関数呼び出しで通る ( 期待通り )
end
private
def private_method
self.inspect
end
end
class Child < Parent
def initialize
# puts self.private_method # レシーバ付きなので通らない
# puts private_method # 関数呼び出しなので期待に反しこれは通る
puts public_method
end
end
Child.new
class Klass
# 主流じゃないけど最近は個別に宣言もできる
private def my_private_method
puts 'private'
end
end
また、クラスメソッド ( = 特異メソッド def self.hoge ) を private にする際は private_class_method モジュールで定義が必要。
class Example
def self.public_foo # クラスメソッド ( 特異メソッド )
puts 'public class method'
end
def self.private_bar
puts 'private class method'
end
private_class_method :private_bar # Module#private_class_method による定義
end
class Klass
def foo
puts 'foo'
end
alias :f :foo # エイリアス - f() で foo() 呼べるよ
undef foo # 定義の削除
defined? foo # 定義の確認 - 真偽値で定義済みかどうか返却
end
メソッドを ( クラス継承やモジュールのインクルードなどを介さず ) 柔軟に他のクラスに移譲する機能。
class Base
def method1
puts "method1"
end
def method2
puts "method2"
end
def method3
puts "method3"
end
end
require 'forwardable'
class Delegation
extend Forwardable
def_delegators :@base, :method2, :method3
def initialize(base)
@base = base
end
def method1
print 'Hello '
@base.method1
end
end
=begin
↑ は以下のような
各メソッドの後続処理を Base に移譲した Delegation クラスを手実装した例
と等価になる
class Delegation
def initialize(base)
@base = base
end
def method1
print 'Hello '
@base.method1
end
def method2
@base.method2
end
def method3
@base.method3
end
end
=end
d = Delegation.new(Base.new)
d.method1 # => Hello method1
d.method2 # => method2
d.method3 # => method3
上記した Delegation クラスは以下のように send と method_missing を利用しても実現可能。
class Delegation
def initialize(base)
@base = base
end
def method1
print 'Hello '
@base.method1
end
# 自インスタンスにコールされたメソッドが存在しない場合に
# コールされるマジックメソッド、第一引数にコールした
# メソッド名、第二引数に配列で指定された引数が入る
#
def method_missing(name, *args)
#
# send は第一引数で呼び出すメソッド名を指定し
# 以降可変長引数でメソッドに渡す引数を指定する
# これでインスンタンスのメソッドを動的にコールすることができる
#
@base.send name, *args
end
end
# || 演算子
hash_a = {}
hash_b = { hi: nil }
p hash_a[:hi] || "Hello" # => "Hello"
p hash_b[:hi] || "Hello" # => "Hello"
p hash_a.fetch(:hi, "Hello") # => "Hello"
p hash_b.fetch(:hi, "Hello") # => nil
a ||= 1 # a が偽か未定義ならば1を代入。初期化時のイディオムの一種
# << 演算子 / concat と同じみたい
str = 'abc' << 'def'
puts str # abcdef
Null 条件演算子 - Wikipedia
ぼっち演算子 - Safe Navigation Operator
Ruby2.3のSafe Navigation Operatorこれは object が nil でないときにメソッド foo を呼び出します。 Active Support の try! と似ていますが、メソッド名は文法的に必須であるという点が異なります。
str = nil
puts str&.upcase # 評価なし => nil 出力
puts str.upcase # NilClass の upcase は NoMethodError として評価
# 引数はメソッドが呼び出された場合のみ評価
obj.try!(:foo, bar()) # bar() は常に評価 ( Rails の try )
obj&.foo(bar()) # bar() は条件付きで評価
# 属性値の代入
obj&.attr += 1
# &.each で nil セーフなイテレート
records = [{no: 1}, {no: 2}, {no: 3}, {no: 4}, {no: 5}]
records.each do |record|
puts record[:no]
end
records = nil
records&.each do |record| # ご利用は計画的に
puts record[:no]
end
# 一行コメント
=begin
複数行コメント
=end
__END__
ここから下全部コメント
# print は純粋な標準出力
name = bob
print "My name is #{name}" #=> My name is bob
# puts は改行コードつき + 文字列展開出力
array = [1, 2, 3]
puts array #=> 1
2
3
# p はデバッグ用
my_hash = { name: 'John', age: 20 } #=> {:name=>'John', :age=>20}
PHP の require と同じく「ファイル」を読込/実行するやつ。
require 'uri'
p 'valid@example.com'.match(URI::MailTo::EMAIL_REGEXP).is_a?(MatchData) #=> true
p 'invalid#example.com'.match(URI::MailTo::EMAIL_REGEXP).is_a?(MatchData) #=> false
多重代入とも。
# こんなの
y, m, d = '2019.12.31'.split('.')
a = 1, 2
a #=> [1, 2]
a, b = 1, 2
a #=> 1
b #=> 2
a, b = 1
a #=> 1
b #=> nil
a, b = 1, 2, 3
a #=> 1
b #=> 2 ( 3 以降はあっても無視される
a, *b = 1, 2, 3
a #=> 1
b #=> [2, 3] * で unpack され配列化
a, (b, c) = 1, [2, 3] #=> [1, [2, 3]]
a #=> 1
b #=> 2 () でネスト構造もうけとれる
c #=> 3
# if
if time < 12
puts 'AM'
elsif time == 12
puts 'NOON'
else
puts 'PM'
end
# unless
unless is_boolean then
puts 'is_boolean is false'
else
puts 'is_boolean is true'
end
# case - then は省略可能、case 内で後置 if すると Linter が迷子になるので注意
case variable
when 'hoge' then
# do something for hoge.
when 'fuga' then
# do something for fuga.
else
# do something for else.
end
Ruby は他スクリプト言語と違って 0 や '' や [] が if で true 扱いになることに注意。意味的に「空よね?」って聞きたい場合は ''.empty? とする Ruby の流儀があるからなのかしら。
string = ''
if string
puts 'true' # '' は true 扱い
else
puts 'false'
end
if string.empty?
puts 'empty' # 空かどうかはちゃんとこれで聞けってこと?
else
puts 'not empty'
end
integer = 0
if integer
puts 'true' # 0 も true 扱い
else
puts 'false'
end
if integer.nil?
puts 'nil'
else
puts 'not nil' # 数値/真偽値には empty? がないので nil 判定しかできない
end
array = []
if array
puts 'true' # 空配列も true 扱い
else
puts 'false'
end
if array.empty?
puts 'empty' # 配列もこれで空かどうか聞けよってことか
else
puts 'not empty'
end
# for in
for i in object do
puts i
end
# while
while count <= 10 do
count += 1
end
# each
object.each do |value|
puts value
end
# each_with_index
['cat', 'doc', 'fox', 'snake'].each_with_index do |animal, count|
p animal # cat, dog, fox ...
p count # 0, 1, 2 ...
end
each メソッド自体の返り値は「レシーバ自身 = each したオブジェクトそのもの」。なので「n件処理しました」とか返却したければ count みたいな変数つくらずともこんな感じにできる。
def send_mail
emails = ['foo@example.com', 'bar@example.com']
emails.each do |email|
Mailer.send_notification_to_user(email)
end
end
iterated = send_mail_to_users
puts "メールを #{iterated.length} 通送信しました!"
他の言語と大きく違うのは、ブロック内で return すると 親メソッドも終了 してしまう点。Ruby では「ループかどうか」に限らず、ブロックスコープ内で処理を中断する場合は next を使う。
break # 中断してループ抜ける
next # スキップして次のループへ ( continue )
redo # 同じ条件でループをやり直す
【Ruby】例外処理
そもそもRailsでカスタム例外を定義すべき機会は少ない
【Rubyアンチパターン】ensure節でreturnしてはならない
Ruby / Rails では「システムエラー」は例外で制御するが、いわゆる「業務エラー/ロジックエラー」は返り値で制御する文化がある。例えば ... do_something if Object.new こんな。このため、システム的なエラーは極力 begin rescue による例外のハンドリングに、業務ロジックのエラーはメソッドの戻り値による制御構造のハンドリングに寄せるのが良さそう。
# いわゆる try catch
begin
raise 'Invalid error occurred.' # いわゆる throw new RuntimeException
rescue => error
puts error.message # 例外を error に代入せず特殊変数 $! でもよい
p error.backtrace # 例外を error に代入せず特殊変数 $@ でもよい
# ここで retry を行うと begin 再挑戦 ( 永久ループ化注意 )
ensure
# ここがいわゆる finally
end
# 複数例外の受付
begin
io = File.open(unverified_file)
rescue Errno::ENOENT, Errno::EACCES # 存在しない・権限ないを拾う
io = File.open(safe_file)
end
# raise のパターン
raise 'OH MY OH!!' # RuntimeError Exception
raise SyntaxError # SyntaxError Exception
raise SyntaxError.new 'OH MY OH!!' # message つき
begin
# ...
rescue
raise # rescue 内では最後の Exception を再投げ
end
# 修飾子として rescue 使ってこんなこともできちゃう
default_num = Integer(val) rescue 0
5.times do |time|
begin
if time === 2
raise 'Something wrong occurred!'
end
puts time
rescue => e
puts e.message
next # Skip this, then go to next loop.
end
end
__END__
# Results.
0
1
Something wrong occurred!
3
4
def hoge
# do something...
rescue => e
e.message
e.full_message
e.backtrace
end
いわゆるクロージャ。
do end もしくは {} で囲まれた命令文のかたまり
self などはブロックごとに文脈が変化users.any? { |user| user.name == 'John' }my_proc = Proc.new { puts 'hello' }
proc{} でも可& を利用することで「Proc を受け取る引数である」ことを明示可能
def my_method(&proc_as_arg) proc_as_arg.call endlambda{} や ->{} で生成可能Proc.new や proc{} した場合と 以下の点でふるまいが異なる
return などで処理を抜けず最後まで実行する# Lambda
lambda_proc1 = lambda { |arg| puts arg } # lambda{ } による方法
lambda_proc2 = ->(word) { puts word } # ->{ } による方法
lambda_proc3 = -> { puts "proc" } # ->{ } による方法
標準クラス/モジュールの詳細は別メモ参照。
# 絶対値を取得
(-1).abs # 1
# 浮動小数値の丸め
(2.58926).round(2) # 2.59
1.234567.ceil(2) # 1.24
# 文字列置換
str.gsub(/at/, '@') # gsubは該当する全ての要素を置換する ( atatat なら @@@ に変換 )
str.sub(/at/, '@') # subは最初の要素だけ置換する ( atatat なら @atat に変換 )
# 末尾改行文字を除去
str.chomp
# 大文字、小文字変換
'abcde'.upcase # ABCDE
'ABCDE'.downcase # abcde
# 文字列 → 配列
'Alice,Bob,Charlie'.split(',') # ['Alice', 'Bob', 'Charlie']
# 指定した文字列を含んでいるか確認
'abcde'.include?('bc') # true
'abcde'.include?('zy') # false
# 指定した文字列を含んでいれば、その開始位置を返す、存在しなければnilを返す
'abcde'.index('a') # 0
'abcde'.index('c') # 2
'abcde'.index('z') # nil
# 文字列の一部抽出
'abcde'[0] # a
'abcde'[0,3] # abc
# Length
[1,2,3].length # 3
# To string
ary.to_s
# Max / Min
[1,3,7,2,5].max # 7
[1,3,7,2,5].min # 1
# shift / pop
ary = [1,3,7,2,5]
ary.shift # 1
ary.pop # 5
p ary # [3,7,2]
# push / unshift
[1,2,3].push(4) # [1,2,3,4]
[1,2,3].unshift(0) # [0,1,2,3]
# empty?
[].empty? # true
# include?
[1,2,3].include?(1) # true
[1,2,3].include?(10) # false
# ランダム取得
[1,3,7,2,5].sample # 5 ※結果は毎回ランダムで選ばれる。
# 配列の複製
tmp = self.dup
# 指定したインデックスの要素の削除 ※破壊的メソッド
ary = [0,1,2,3]
ary.delete_at(2)
p ary # [0,1,3]
hash_1 = { 'name' => 'John', 'age' => 20 }
hash_2 = { :name => 'John', :age => 20 } # シンボルをキーに
hash_3 = { name: 'John' , age: 20 } # hash_2 の省略形
# methods
p hash_1.keys #=> ['name', 'age']
p hash_2.values #=> ['Jane', 19]
p hash_3.to_a #=> [[:name, 'Jack'], [:age, 28]]
# new
new_hash = Hash.new('default')
p new_hash['undefined_key'] #=> default
another_hash = Hash.new do |hash, key|
hash[key] = key.upcase
end
p another_hash['undefined_key'] #=> UNDEFINED_KEY
# store
new_hash.store('new_key', 'new_value')
p new_hash #=> {'new_key' => 'new_value'}
# fetch
p new_hash['new_key'] #=> new_value
p new_hash.fetch('new_key') #=> new_value
h = Hash.new
# p h.fetch('N') #=> Exception(IndexError)
p h.fetch('N', 'Nothing Value') #=> 'Nothing Value'
p h.fetch('N') { String.new } #=> ''
# delete / delete_if
new_hash.delete('new_key')
p new_hash.empty? #=> true
profile = { name: 'Bob', age: 18, height: 170, weight: 58 }
profile.delete_if { |key, value| key == :weight }
p profile #=> {:name=>'Bob', :age=>18, :height=>170}
p profile.reject! { |key, value| key == :nick_name } #=> 該当がないとき戻り値が nil に
p profile #=> {:name=>'Bob', :age=>18, :height=>170}
profile.clear
p profile #=> {}