ためすう
Ruby で const_get を使ってみる
2019-07-23やったこと
Ruby で const_get を使ってみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
test.rb
class Sample
ABC = 'abc'
end
p Object.const_get('Sample')
p Class.const_get('Sample')
p Object.const_get('Sample::ABC')
p Class.const_get('Sample::ABC')
p Sample.const_get('ABC')
p Object.const_get('Sample::ABCD')
実行結果
$ ruby test.rb
Sample
Sample
"abc"
"abc"
"abc"
Traceback (most recent call last):
test.rb:11:in `<main>': uninitialized constant Sample::ABCD (NameError)
存在しない値を参照しようとすると、例外が発生します。
参考
アジャイルソフトウェア開発の奥義 第8章 単一責任の原則 (SRP)
2019-07-20「アジャイルソフトウェア開発の奥義」の読書メモです。
時々、個人の見解入りです。
単一責任の原則
SRP: The Single Responsibility Princple
クラスを変更する理由は1つ以上存在してはならない。
クラスが複数の役割を背負っているような場合、それらの役割は結合してしまう。その結果、ある役割が変更を受けると、そのクラスが担っている他の役割も 影響を受け、不具合が生じる可能性がある。
例
このクラスは下記2つの役割を持っています。
- 接続の管理
- データ通信
「接続の管理」「データ通信」の2つの役割を持つ
test1.rb
module Modem
# 接続の管理
def dial(pno)
raise '実装してください'
end
def hangup()
raise '実装してください'
end
# データ通信
def send(c)
raise '実装してください'
end
def recv()
raise '実装してください'
end
end
class ModemImplementation
include Modem
def hangup
p "#{self.class} #{__method__}"
end
end
ModemImplementation.new.hangup
仮に、「接続を管理」する関数が影響を受けるような変更がされるなら、分離するべきです。
2つの役割が必ず同時に変更されるなら、分離してしまうと不必要に複雑になってしまいます。
「接続の管理」「データ通信」の2つの役割を分離する
test2.rb
module DataChannel
def send(c)
raise '実装してください'
end
def recv()
raise '実装してください'
end
end
module Connection
# 接続の管理
def dial(pno)
raise '実装してください'
end
def hangup()
raise '実装してください'
end
end
class ModemImplementation2
include DataChannel
include Connection
def hangup
p "#{self.class} #{__method__}"
end
end
ModemImplementation2.new.hangup
Ruby で class_eval を使ってみる
2019-07-20やったこと
Ruby で class_eval を使ってみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
test.rb
class Sample
end
Sample.class_eval %Q{
def hoge
p "Sample.class_eval"
p "self.class: #{self.class}"
p "__method__: #{__method__}"
end
}
Sample.new.hoge
実行結果
$ ruby test.rb
"Sample.class_eval"
"self.class: Object"
"__method__: "
method は空になりました。
参考
やったこと
Rails の ActiveRecord で update を実行するときに、 validate が呼び出されるのかを検証してみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
$ rails --version
Rails 5.2.3
調査
validate が呼び出されているかを調べる
app/models/task.rb
class Task < ApplicationRecord
validate :hoge
def hoge
logger.debug("custom validate: #{__method__}")
end
end
実行結果
$ rails c
Running via Spring preloader in process 29317
Loading development environment (Rails 5.2.3)
irb(main):001:0> Task.all
Task Load (1.9ms) SELECT "tasks".* FROM "tasks" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Task id: 4, created_at: "2019-06-12 12:50:53", updated_at: "2019-06-12 12:50:53", name: "name1">, #<Task id: 5, created_at: "2019-06-12 12:52:31", updated_at: "2019-06-12 12:52:31", name: "name1">]>
irb(main):002:0> t = Task.find(4)
Task Load (0.2ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]]
=> #<Task id: 4, created_at: "2019-06-12 12:50:53", updated_at: "2019-06-12 12:50:53", name: "name1">
irb(main):003:0> t.update(name: "name22")
(0.1ms) begin transaction
custom validate: hoge
Task Update (0.5ms) UPDATE "tasks" SET "updated_at" = ?, "name" = ? WHERE "tasks"."id" = ? [["updated_at", "2019-07-14 15:51:29.933901"], ["name", "name22"], ["id", 4]]
(3.5ms) commit transaction
=> true
update が実行されると validate も実行されていることが分かります。
validate の unique 制約が働くのか調べる
app/models/task.rb
class Task < ApplicationRecord
validates :name , uniqueness: true, presence: true
validate :hoge
def hoge
logger.debug("custom validate: #{__method__}")
end
end
実行結果
$ rails c
Running via Spring preloader in process 30205
Loading development environment (Rails 5.2.3)
irb(main):001:0> Task.all
Task Load (1.4ms) SELECT "tasks".* FROM "tasks" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Task id: 4, created_at: "2019-06-12 12:50:53", updated_at: "2019-07-14 16:01:58", name: "name2">, #<Task id: 5, created_at: "2019-06-12 12:52:31", updated_at: "2019-06-12 12:52:31", name: "name1">]>
irb(main):002:0> task = Task.find(4)
Task Load (0.2ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]]
=> #<Task id: 4, created_at: "2019-06-12 12:50:53", updated_at: "2019-07-14 16:01:58", name: "name2">
irb(main):003:0> task.update(name: "name1")
(0.1ms) begin transaction
Task Exists (0.2ms) SELECT 1 AS one FROM "tasks" WHERE "tasks"."name" = ? AND "tasks"."id" != ? LIMIT ? [["name", "name1"], ["id", 4], ["LIMIT", 1]]
custom validate: hoge
(0.1ms) rollback transaction
=> false
unique 制約のバリデーションが効いていることが分かります。
参考
Ruby で etc モジュールを使ってみる
2019-07-17やったこと
Ruby で etc モジュールを使ってみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
etc モジュールとは
/etc に存在するデータベースから情報を得るためのモジュールです。
検証
$ irb
irb(main):001:0> require 'etc'
=> true
irb(main):002:0> Etc.uname
=> {:sysname=>"Darwin", :nodename=>"hogehoge-no-MacBook-Pro.local", :release=>"17.7.0", :version=>"Darwin Kernel Version 17.7.0: Wed Apr 24 21:17:24 PDT 2019; root:xnu-4570.71.45~1/RELEASE_X86_64", :machine=>"x86_64"}
irb(main):003:0> Etc.getlogin
=> "hogehoge"
※ ユーザー名を hogehoge に変更しています。
参考
Ruby で initialize が呼ばれる順番を調べる
2019-07-16やったこと
Ruby で initialize が呼び出される順番を調べます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
module と 親クラスがある場合
test.rb
module SampleModule
def initialize
p "1: #{self.class} #{__method__}"
end
end
class Parent
def initialize
p "2: #{self.class} #{__method__}"
end
end
class Child < Parent
include SampleModule
def initialize
super
p "3: #{self.class} #{__method__}"
end
end
出力結果
$ ruby test.rb
"1: Child initialize"
"3: Child initialize"
親クラスがある場合
test.rb
class Parent
def initialize
p "2: #{self.class} #{__method__}"
end
end
class Child < Parent
def initialize
super
p "3: #{self.class} #{__method__}"
end
end
出力結果
$ ruby test.rb
"2: Child initialize"
"3: Child initialize"
module と親クラスが両方ある場合、super で呼び出されるのは module のメソッドであることが分かりました。
Ruby で private_class_method を使ってみる
2019-07-16やったこと
Ruby で private_class_method を使ってみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
ruby-private-class-method.rb
class Sample
def self.hoge
p __method__
end
def self.hoge2
p __method__
end
def hoge3
p __method__
end
private_class_method :hoge
end
検証
$ irb
...
irb(main):007:0> require './ruby-private-class-method'
=> true
irb(main):008:0> Sample.hoge
Traceback (most recent call last):
4: from /Users/hogehoge/.rbenv/versions/2.6.3/bin/irb:23:in `<main>'
3: from /Users/hogehoge/.rbenv/versions/2.6.3/bin/irb:23:in `load'
2: from /Users/hogehoge/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
1: from (irb):8
NoMethodError (private method `hoge' called for Sample:Class)
Did you mean? hoge2
irb(main):009:0> Sample.hoge2
:hoge2
=> :hoge2
irb(main):010:0> Sample.new.hoge3
:hoge3
=> :hoge3
参考
Ruby で uniq を使ってみる
2019-07-15やったこと
Ruby で uniq を使ってみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
$ irb
irb(main):001:0> p [1, 1, 1].uniq
[1]
=> [1]
irb(main):002:0> p [1, 2, 3, '1'].uniq
[1, 2, 3, "1"]
=> [1, 2, 3, "1"]
irb(main):003:0> p [1, 2, 3, '1'].uniq {|n| n.to_s}
[1, 2, 3]
=> [1, 2, 3]
参考
Ruby で insert を使ってみる
2019-07-15やったこと
Ruby で insert を使ってみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
test.rb
ary = [
{a: 1},
{a: 2},
{a: 3},
]
p ary
ary.insert(1, {a: 999})
p ary
ary.insert(10, {a: 10})
p ary
出力結果
$ ruby test.rb
[{:a=>1}, {:a=>2}, {:a=>3}]
[{:a=>1}, {:a=>999}, {:a=>2}, {:a=>3}]
[{:a=>1}, {:a=>999}, {:a=>2}, {:a=>3}, nil, nil, nil, nil, nil, nil, {:a=>10}]
参考
Ruby で tap を使ってみる
2019-07-14やったこと
Ruby で tap を使ってみます。
確認環境
$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]
調査
メソッドチェインの途中で直ちに操作結果を表示するために メソッドチェインに “入り込む” ことが、このメソッドの主目的です。
$ irb
irb(main):001:0> (1..10).tap {|x| puts "original: #{x}" }.
irb(main):002:0* to_a.tap {|x| puts "array: #{x}"}.
irb(main):003:0* select {|x| x.even? }.tap {|x| puts "evens: #{x}"}
original: 1..10
array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens: [2, 4, 6, 8, 10]
=> [2, 4, 6, 8, 10]