ためすう

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 が実行されるかを見てみる

2019-07-17

やったこと

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]

参考