ナスカブログ

未来の自分へのドキュメント

Ruby Goldへの道 day4

以下のサイトでruby gold取得に向けて毎日一回Goldチャレンジを行い間違えた問題を簡単にまとめる。 rex.libertyfish.co.jp

4日目 得点 70点/100点中

惜しい。順調に得点を伸ばしてる。

では、今日も間違えた問題を簡単にまとめる。

module M
  def class_m # インスタンスメソッド
    "class_m"
  end
end

class C
  include M
end

p C.methods.include? :class_m #=> false
# Cの特異メソッドを表示
p C.new.methods.include? :class_m #=> true
  • selfはnewされたクラスのオブジェクトになる
class C
  def initialize
    p self.class
    # selfはnewされたオブジェクトになる
  end
end

class C2 < C
end

C2.new #=> C2
  • モジュールを読み込むにはusing モジュールが必要
class C
  def m1
    200
  end
end

module R
  refine C do
    def m1
      100
    end
  end
end

c = C.new
puts c.m1 #=>200
# Rモジュールは読み込めていない
# 読み込むにはusingRが必要
using R
c2 = C.new
puts c2.m1 #=> 100
  • loadは外部ライブラリを読み込む...
    モジュールの使用は include か using
module M
  def foo
    super
    puts "M#foo"
  end
end

class C2
  def foo
    puts "C2#foo"
  end
end

class C < C2
  def foo
    super
    puts "C#foo"
  end
  using M # or include M
  # ※using と include は挙動が違う
  # load M 
  # loadは外部ライブラリを読み込む
end

C.new.foo
  • method_missingはチェーンをたどった末にメソッドが見つからなかったら呼ばれる
module M
  def method_missing(id, *args)
    puts "M#method_missing"
  end
end
class A
  include M
  def method_missing(id, *args)
    puts "A#method_missing"
  end
end
class B < A
  def method_missing(id, *args)
    puts "B#method_missing"
  end
  # このmethod_missingメソッドをコメントアウトすると
  # Aクラスのmethod_missingが呼ばれる
end

obj = B.new
obj.dummy_method #=> B#method_missing
# 継承チェーンをたどった末に見つからなかったらオブジェクト(Bクラス)のmethod_missingが呼ばれる
# method_missingも継承チェーンをたどるのでBに定義しなかったらAのmethod_missingが呼ばれる
  • Objectクラスに*メソッドは定義されていない
p [1,2,3,4].map(&self.method(:*))
#=> undefined method `*' for class `#<Class:#<Object:0x00007ff4e18b9d58>>' (NameError)
# オブジェクトクラスに*メソッドが定義されていないためエラー
  • 定数は静的に探索が行われる
module A
  B = 42

  def f
    21
  end
end

A.module_eval(<<-CODE)
  def self.f
    p B
  end
CODE

B = 15

A.f #=> 42
  • 正規表現の[]は囲まれた文字1つ1つにマッチする
    出た。正規表現。少しずつ覚えていこう..
p "Matz is my tEacher"[/[J-P]\w+[^ ]/] #=> "Matz"
  • クラスメソッドの呼び出しはself.[メソッド]
class C
  class << C # 特異クラス
    def hoge # クラスメソッド
      'Hi'
    end
  end

  def hoge # selfをつけるとクラスメソッドになる
    'Goodbye'
  end
end

p C.hoge #=> "Hi"
# クラスメソッドを呼び出す
p C.new.hoge #=> "Goodbye"
  • Module.nestingはネストの状態を表示する
    浅いところから表示していく
module SuperMod
  module BaseMod
    p Module.nesting
  end
end

#=> [SuperMod::BaseMod, SuperMod]
# ネストの状態を浅いところから全て表示する
  • raiseの例外クラスを省略した場合は、RuntimeErrorを発生させる
begin
  raise "Err!"
  # raise 発生させたい例外クラス, "エラーメッセージ" 
rescue => e
  puts e.class #=> RuntimeError
  # 例外クラスの記述が省略されているので RuntimeError が発生
end
  • superと呼び出した場合は、現在のメソッドと同じ引数が引き継がれる
    super() と引数がないことを明示的に示す必要がある
class S
  def initialize
    puts "S#initialize"
  end
end

class C < S
  def initialize(*args)
    super
    # super()と記述するとエラーはなくなる
    puts "C#initialize"
  end
end

C.new(1,2,3,4,5) #=>  wrong number of arguments (given 5, expected 0) (ArgumentError)
  • モジュールにクラスメソッドを定義するには3つ方法がある
# モジュールにクラスメソッドを定義する方法
# ①
module M
  extend self
  def a
    100
  end
end

p M.a #=> 100

# ②
module M
  def a
    100
  end

  module_function :a
end

p M.a #=> 100

# ③
module M
  class << self
    def a
      100
    end
  end
end

p M.a #=> 100
  • freezeは破壊的な変更を禁止する
    どこにfreezeがあるかを確認する必要がある
array = ["a", "b", "c"].map(&:freeze) #=> 配列の要素に対して破壊的な変更を禁止する
array = array.freeze

array.each do |chr|
  chr.upcase!
end

p array #=> can't modify frozen String: "a" (FrozenError)
p Class.method_defined? :new #=> true
p String.method_defined? :new #=> false
p Class.singleton_class.method_defined? :new #=> true
p String.singleton_class.method_defined? :new #=> true

Ruby Goldへの道 day3

以下のサイトでruby gold取得に向けて毎日一回Goldチャレンジを行い間違えた問題を簡単にまとめる。 rex.libertyfish.co.jp

3日目 得点 54点/100点中

半分超えた〜少しずつメソッドを覚えてきた と言うより問題の内容を覚えてきた(良くない。。)

  • takeはEnumerable オブジェクトの先頭から n 要素を配列として返す
p (1..10).lazy.map{|num|
  num * 2
}.take(3).inject(0, &:+)
# [1*2, 2*2, 3*2]を返す
#=> 12
  • 値を取り出すには、Enumerator::Lazy#forceまたはEnumerator::Lazy#firstを呼び出す必要がある
p (1..100).each.lazy.chunk(&:even?).first(5)
#=> [[false, [1]], [true, [2]], [false, [3]], [true, [4]], [false, [5]]]

p (1..100).each.lazy.chunk(&:even?).take(5).force
#=> [[false, [1]], [true, [2]], [false, [3]], [true, [4]], [false, [5]]]
  • extendは引数に指定したモジュールのメソッドを特異メソッドとして追加する
module M
  def class_m
    "class_m"
  end
end

class C
  extend M
  # Mモジュールのclass_mメソッドを特異メソッドとして追加
end

p C.methods.include? :class_m #=> true
  • Refinementは有効化したスコープのみに影響を与えることが出来る
    オープンクラスについての理解が必要そう...
class C
  def m1
    400
  end
end

module M
  refine C do
    def m1
      100
    end
  end
end

class C
  using M
  # スコープ外のため無効
end

puts C.new.m1 #=> 400
  • initializeの可視性はprivateに設定されている
class C
  private
  def initialize
  end
end
  
p C.new.public_methods.include? :initialize #=> false
p C.new.private_methods.include? :initialize #=> true
  • usingはメソッドの中で呼び出すことは出来まず、呼び出した場合はRuntimeErrorが発生する
class C
end

module M
  refine C do
    def m1
      100
    end
  end
end

class C
  def m1
    400
  end

  def self.using_m
    using M
    # メソッドの中で呼び出すことができない
  end
end

C.using_m

puts C.new.m1 #=> Module#using is not permitted in methods (RuntimeError)
  • loadはrequire同様外部ライブラリを呼び出す
    モジュールは呼び出せない
    loadはrequire loadはrequire loadはreq...
module M
  def foo
    super
    puts "M#foo"
  end
end

class C2
  def foo
    puts "C2#foo"
  end
end

class C < C2
  def foo
    super
    puts "C#foo"
  end
  load M
  # モジュールは呼び出せない
end

C.new.foo #=> no implicit conversion of Module into String (TypeError)
  • 定数の定義はメモリ上にあるテーブルに管理される
    別々に書いてもテーブル上の値を参照する
module M
  CONST = "Hello, world"
end

module M
  def self.say
    CONST
  end
end

p M::say #=> "Hello, world"
  • instance_evalの引数に文字列を指定するとネストの状態はモジュールMの特異クラスになる
module M
  CONST = "Hello, world"
end

M.instance_eval(<<-CODE)
  def say
    CONST
  end
CODE

p M::say #=> uninitialized constant #<Class:M>::CONST (NameError)
# CONSTはモジュールMにのみあるので、例外が発生する
  • module_evalの引数に文字列を指定するとネストの状態はモジュールMになる
module M
  CONST = "Hello, world"
end

M.module_eval(<<-CODE) # モジュールMになる
  def self.say
    CONST
  end
CODE

p M::say #=> "Hello, world"
  • Refinementで再定義したメソッドの探索はprependより優先して行われる
class C
  def m1
    200
  end
end

module R
  refine C do # 優先して探索が行われる
    def m1
      300
    end
  end
end

using R

class C
  def m1
    100
  end
end

puts C.new.m1 #=> 300
class Base # 参照
  CONST = "Hello, world"
end

class C < Base # 参照
end

module P
  CONST = "Good, night"
end

class Base
  prepend P
end

module M
  class C
    CONST = "Good, evening"
  end
end

module M
  class ::C # トップレベルの探索
    def greet
      CONST
    end
  end
end

p C.new.greet #=> "Hello, world"
  • クラス変数はレキシカルに決定される
class C
  @@val = 10
end

module B
  @@val = 30
end

module M
  include B
  @@val = 20

  class << C
    p @@val #=> 20
  end
end
  • includeはモジュールのメソッドをインスタンスメソッドとして追加する
    メソッド探索順はselfの後に追加される
module M
  def foo
    super
    puts "M#foo"
  end
end

class C2
  def foo
    puts "C2#foo"
  end
end

class C < C2
  def foo
    super
    puts "C#foo"
  end
  include M # モジュールMのfooメソッドを追加する self.fooの後に呼ばれる
end

C.new.foo
#=> C2#foo
#=> M#foo
#=> C#foo
  • const_defined?メソッドは第2引数に継承関係を探索するか指定出来る
    デフォルトではtrue
mod = Module.new

mod.module_eval do
  EVAL_CONST = 100
end

puts "EVAL_CONST is defined? #{mod.const_defined?(:EVAL_CONST)}"
#=> EVAL_CONST is defined? true

puts "EVAL_CONST is defined? #{Object.const_defined?(:EVAL_CONST)}"
#=> EVAL_CONST is defined? true

# 第2引数にfalseを指定すると継承関係まで探索しない
puts mod.const_defined? :EVAL_CONST, false # false
  • ブロック引数は他の引数より後ろに記述する
def bar(&block)
  block.yield
end

bar do
  puts "hello, world"
end

def bar(&block)
  block.call
end

bar do
  puts "hello, world"
end

def bar(n, &block)
  block.call
end

bar(5) {
  puts "hello, world"
}

# エラー
def bar(&block, n)
  block.call
end

bar(5) {
  puts "hello, world"
}

-__END__をファイルとして扱い、このファイルへのアクセスはDATAを用いる

while not DATA.eof?
  print DATA.read 1
end

__END__
1
2
3
4

#=> 1
#=> 2
#=> 3
#=> 4
  • superを実行した場合にもRefinementが影響する
class C
end

module M
  refine C do
    def m1(value)
      super value - 100 #=> ②
    end
  end
end

class C
  def m1(value)
    value - 100 #=> ③
  end
end

using M

class K < C
  def m1(value)
    super value - 100 #=> ①
  end
end

puts K.new.m1 400 #=> 100
  • レキシカルスコープにある定数を探索する
module K
  CONST = "Good, night"
  class P
  end
end

module K::P::M
  class C
    CONST = "Good, evening"
  end
end

module M
  class C
    CONST = "Hello, world"
  end
end

class K::P
  class M::C
    # K::P::M::CのCONSTを参照する
    p CONST #=> "Good, evening"
  end
end
  • チェーンを行う場合はEnumeratorオブジェクトを作成する必要がある
    作成に必要なメソッド enum_forto_enum
class Array
  def succ_each(step = 1)
    return to_enum(:succ_each) unless block_given?

    each do |int|
      yield int + step
    end
  end
end

p [98, 99, 100].succ_each(2).map {|succ_chr| succ_chr.chr}

[101, 102, 103].succ_each(5) do |succ_chr|
  p succ_chr.chr
end
  • オブジェクトの破壊的な変更は禁止するが配列の要素の破壊的な変更は禁止しない
array = ["a", "b", "c"].freeze

array.each do |chr|
  chr.upcase!
end

p array #=> ["A", "B", "C"]
  • Kernel#Array、Kernel#Hash、Kernel#StringはKernelのモジュール関数として定義されている

Ruby Goldへの道 day2

以下のサイトでruby gold取得に向けて毎日一回Goldチャレンジを行い間違えた問題を簡単にまとめる。 rex.libertyfish.co.jp

2日目 得点 46点/100点中

class C
  @val = 3 # クラスインスタンス変数
  attr_accessor :val # 特異クラスのクラスインスタンス変数
  class << self
    @val = 10
  end
  def initialize
    @val *= 2 if val
  end
end

c = C.new
c.val += 10

p c.val #=> undefined method `+' for nil:NilClass (NoMethodError)
  • -tと-fはオプションがない 昨日も同じとこ間違えた〜 forceとtailはないで覚えておこう。

  • 複数回includeされた場合は、後に宣言された方からメソッド探索される

module M1
end

module M2
end

class C
  include M1
  include M2
end

p C.ancestors #=> [C, M2, M1, Object, Kernel, BasicObject]
  • クラスCの特異クラスにあるCONSTもコンテキストが異なる
class Object
  CONST = "100"
end

class C
  CONST = "010"
  class << self
    CONST = "001"
  end
end

p C::CONST #=> "010"
  • FixnumとRationalの演算はRationalになる
  • 1/2rはRationalのインスタンスが作成される
    rが未定義でエラーかと思った。
val = 1 + 1/2r
puts val.class #=> Rational
  • class << self; endで定義されたメソッドは、特異クラスのインスタンスメソッドになる
module M
  def method_missing(id, *args)
    puts "M#method_missing"
  end
end
class A
  include M
  def method_missing(id, *args)
    puts "A#method_missing"
  end
end
class B < A
  class << self # 特異クラス
    def method_missing(id, *args)
      puts "B.method_missing"
    end
  end
end

B.new.dummy_method #=> A#method_missing
  • const_getは、selfに定義された定数を探索する
class Human
  NAME = "Unknown"

  def self.name
    const_get(:NAME) # Fukuzawaクラスのインスタンス
  end
end

class Fukuzawa < Human
  NAME = "Yukichi"
end

puts Fukuzawa.name #=> Yukichi
  • キーワード引数へHashオブジェクトを渡すことができる
def foo(arg1:100, arg2:200)
  puts arg1
  puts arg2
end

option = {arg2: 900}

foo arg1: 200, **option
#=> 200
#=> 900
#foo(arg1: 200, arg2: 900)になる
  • キーワード引数は省略できない これも昨日やったような、、、
def foo(arg:)
  puts arg
end

foo 100 #=> wrong number of arguments 

# 下記のように書き換える
def foo(arg:)
  puts arg
end

foo arg: 100 # <= arg: は省略できない
  • Enumerator::Yielderを評価するには、<<を呼び出す
enum_char = Enumerator.new do |yielder|
  "apple".each_char do |chr|
    yielder << chr # Enumerator::Yielderの評価
  end
end

array = enum_char.map do |chr|
  chr.ord
end

p array #=> [97, 112, 112, 108, 101]
  • succ ≒ next selfの次の文字列を返す
class Object
  CONST = "1"
  def const_succ
    CONST.succ! # 1,2,...
  end
end

class Child1
  const_succ
  class << self
    const_succ
  end
end

class Child2
  const_succ
  def initialize
    const_succ
  end
end

Child1.new
Child2.new

p Object::CONST #=> "5"
  • Module.nestingはネストの状態を表示する
    モジュールがネストされた場合全ての状態を表示する
module SuperMod
end

module SuperMod::BaseMod
  p Module.nesting #=> [SuperMod::BaseMod]
end
class Ca
  CONST = "001"
end

class Cb
  CONST = "010"
end

class Cc
  CONST = "011"
end

class Cd
  CONST = "100"
end

module M1
  class C0 < Ca
    class C1 < Cc
      class C2 < Cd
        p CONST #=> "100"
        # C2のスーパークラスCdを探索

        class C2 < Cb
        end
      end
    end
  end
end
  • ブロック変数に*argsを渡すことができる
def hoge(*args, &block)
  block.call(args)
end

hoge(1,2,3,4) do |*args| # [[1, 2, 3, 4]]が渡される
  p args.length < 0 ? "hello" : args #=> [[1, 2, 3, 4]]
end
  • sort!は破壊的メソッド
    大きい順に並び替える
class Company
  attr_reader :id
  attr_accessor :name
  def initialize id, name
    @id = id
    @name = name
  end
  def to_s
    "#{id}:#{name}"
  end
  def <=> other
    other.id <=> self.id
  end
end

companies = []
companies << Company.new(2, 'Liberyfish')
companies << Company.new(3, 'Freefish')
companies << Company.new(1, 'Freedomfish')

companies.sort! # 大きい順に並び替える

companies.each do |e|
  puts e
end
# 3:Freefish
# 2:Liberyfish
# 1:Freedomfish
  • freezeはオブジェクトの破壊的な変更を禁止する
array = ["a", "b", "c"].freeze
array = array.map!{|content| content.succ}

p array #=> can't modify frozen Array: ["a", "b", "c"] (FrozenError)
  • method_missingは、継承チェーンを辿った末にメソッドが見つからなかった場合に呼び出される
class Class
  def method_missing(id, *args)
    puts "Class#method_missing"
  end
end
class A
  def method_missing(id, *args)
    puts "A#method_missing"
  end
end
class B < A
  def method_missing(id, *args)
    puts "B#method_missing"
  end
end

p B.class # Class ???
B.dummy_method #=> Class#method_missing
# Classクラスのインスタンスメソッドが呼ばれる
  • raiseの例外クラスを省略した場合は、RuntimeErrorを発生させる
    RuntimeErrorはstanderdErrorのサブクラス
begin
  raise "Err!"
rescue => e
  puts e.class #=> RuntimeError
end
  • ブロックにあるローカル変数valはトップレベルにあるものと同じ
val = 100

def method(val)
  yield(15 + val)
end

_proc = Proc.new{|arg| val + arg }

p method(val, &_proc) #=> 215
  • const_defined? 指定された名前の定数が定義されていたら真を返す
mod = Module.new

mod.module_eval do
  EVAL_CONST = 100
end

puts "EVAL_CONST is defined? #{mod.const_defined?(:EVAL_CONST, false)}"
#=> EVAL_CONST is defined? false
puts "EVAL_CONST is defined? #{Object.const_defined?(:EVAL_CONST, false)}"
#=> EVAL_CONST is defined? true
  • Fiberは軽量スレッドを提供する
f = Fiber.new do
  Fiber.yield 15
  5
end

f.resume
f.resume
# f.resume 三回以上呼び出すとエラーになる
  • requireはライブラリのロード、loadは設定ファイルの読み込みに用いる。
# lib.rb
module Lib
  $num += 1
end
##

# 実行ファイル
$num = 0
1..10.times do |n|
  require './lib.rb'
end
puts $num #=> 1
  • JSON.loadまたは、JSON.parseは引数にJSON形式の文字列を指定するとHashオブジェクトに変換する
require 'json'

json = <<JSON
{
  "price":100,
  "order_code":200,
  "order_date":"2018/09/20",
  "tax":0.8
}
JSON

using_parse = JSON.parse json
p using_parse #=> {"price"=>100, "order_code"=>200, "order_date"=>"2018/09/20", "tax"=>0.8}

using_load = JSON.load json
p using_parse #=> {"price"=>100, "order_code"=>200, "order_date"=>"2018/09/20", "tax"=>0.8}
  • 破壊的な変更できない また出た
CONST_LIST_A = ['001', '002', '003']
begin
  CONST_LIST_A.map{|id| id << 'hoge'} # 値変更する
rescue
end

CONST_LIST_B = ['001', '002', '003'].freeze
begin
  CONST_LIST_B.map{|id| id << 'hoge'} # 値変更する
rescue
end

CONST_LIST_C = ['001', '002', '003'].freeze
begin
  CONST_LIST_C.map!{|id| id << 'hoge'} # 値変更しない(破壊的メソッド)
rescue
end

CONST_LIST_D = ['001', '002', '003'].freeze
begin
  CONST_LIST_D.push('add') # 値変更しない
rescue
end

p CONST_LIST_A #=> ["001hoge", "002hoge", "003hoge"]
p CONST_LIST_B #=> ["001hoge", "002hoge", "003hoge"]
p CONST_LIST_C #=> ["001", "002", "003"]
p CONST_LIST_D #=> ["001", "002", "003"]

Ruby Goldへの道 day1

以下のサイトでruby gold取得に向けて毎日一回Goldチャレンジを行い間違えた問題を簡単にまとめる。 rex.libertyfish.co.jp

簡単な自己紹介

  • 2019年10月から某プログラミングスクールでプログラミング学習開始
  • 2020年4月から都内のRuby受託会社に入社
  • 実務経験半年(内2ヶ月社内研修)
  • Ruby silverは取得済み

1日目 得点 32点/100点中

50問中16問正解。逆に言うと34問間違い。。。
間違えた問題について復習する

  • selfはnewされたオブジェクトのクラス
class Fuga
  def initialize
    p self.class
  end
end

class Hoge < Fuga
end

Hoge.new #=> Hoge
  • 特異クラス内で宣言されたメソッドは特異メソッド
class Hoge
  class << Hoge
    def fuga
      '特異メソッドだよん'
    end
  end

  def fuga
    'インスタンスメソッドだよん'
  end
end

p Hoge.new.fuga #=> "インスタンスメソッドだよん"
p Hoge.fuga #=> "特異メソッドだよん"
  • -t, -fオプションはない

  • オープンクラスによる影響をローカルにとどめる為にRefinementがある
    RefinementはModule#refineで呼び出すことができModule#usingで定義したRefinementを有効化できる

class Hoge
  def fuga
    200
  end
end

module R
  refine Hoge do # Refinementを呼び出す
    def fuga
      100
    end
  end
end


puts Hoge.new.fuga #=> 200

using R # Refinementを有効か

puts Hoge.new.fuga #=> 100
  • Procはcallの際に引数の数を省略され不足の引数にはnilが代入される
local = 0

p1 = Proc.new { |arg1, arg2|
  arg1, arg2 = arg1.to_i, arg2.to_i
  local += [arg1, arg2].max
}

p1.call("1", "2") #=> 2
p1.call("7", "5") #=> 2 + 7 = 9
p1.call("9") #=> 9 + 9 = 18

p local # => 18
  • 定数はレキシカルに決定される
    レキシカル...静的??
module M1
  class C1
    CONST = "class_C1"
  end

  class C2 < C1
    CONST = "class_C2"

    module M2
      CONST = "module_M2"

      class Ca
        CONST = "class_Ca"
      end

      class Cb < Ca
        p CONST #=> "module_M2"
      end
    end
  end
end
  • ブロック引数は仮引数の中で最後に記述する
def hoge(&block, *args)
  block.call(*args)
end

hoge(1,2,3,4) do |*args|
  p args.length > 0 ? "hello" : args
end
  • initializeはpublicなどでアクセス修飾子をつけたとしても、privateから変わることはない。
class Hoge
  public
  def initialize
  end
end
  
p Hoge.new.private_methods.include? :initialize #=> true
  • loadはrequire同様に外部ライブラリを読み込む
    外部ライブラリではなくモジュールを読み込むとエラーになる
module M
  def foo
    super
    puts "M#foo"
  end
end

class C2
  def foo
    puts "C2#foo"
  end
end

class C < C2
  def foo
    super
    puts "C#foo"
  end
  load M
end

C.new.foo #=> no implicit conversion of Module into String (TypeError)
  • method_missingは継承チェーンをたどった末にメソッド が見つからなかったら呼び出される
module M
  def method_missing(id, *args)
    puts "M#method_missing"
  end
end
class A
  include M
  def method_missing(id, *args)
    puts "A#method_missing"
  end
end
class B < A
  def method_missing(id, *args)
    puts "B#method_missing"
  end
end

obj = B.new
obj.dummy_method #=> B#method_missing
  • クラス/モジュールに定義されているクラス変数 name の値を返す
class S
  @@val = 0
  def initialize
    @@val += 1
  end
end

class C < S
  class << C
    @@val += 1
  end

  def initialize
    @@val += 1
    super
  end
end

C.new
C.new
S.new
S.new

p C.class_variable_get(:@@val) #=> 7
  • キーワード引数は省略することができない
def foo(arg:)
  puts arg
end

foo 100 #=> wrong number of arguments (given 1, expected 0; required keyword: arg) (ArgumentError)
  • クラス変数が更新されるタイミング
    クラスメソッドが定義された
    C.newが呼び出された
    superからCのinitializeを呼び出された
    S.newが呼び出された
class S
  @@val = 0
  def initialize
    @@val += 1
  end
end

class C < S
  class << C
    @@val += 1
  end
end

C.new
C.new
S.new
S.new

p C.class_variable_get(:@@val) #=> 5
  • Dateクラス同士の減算はRationalになる
require 'date'
d = Date.today - Date.new(2015,10,1)
p d.class #=> Rational
  • Procオブジェクトをメソッドで実行するにはブロックに変換する必要がある
val = 100

def method(val)
  yield(15 + val)
end

_proc = Proc.new{|arg| val + arg }

p method(val, &_proc.to_proc) #=> 215

# &_proc.to_procと&_procはブロックに変換できる
  • ブロック変数に可変長引数はそのまま受け取る
def hoge(*args, &block)
  block.call(args)
end

hoge(1,2,3,4) do |*args|
  p args.length < 0 ? "hello" : args #=> [[1, 2, 3, 4]]
end
  • each_charとto_enum(:each_char)は同じ結果が返ってくる
enum = "apple".to_enum(:each_char)
# enum = "apple".each_char

p enum.next #=> "a"
p enum.next #=> "p"
p enum.next #=> "p"
p enum.next #=> "l"
p enum.next #=> "e"
  • 特異クラス定義の中でクラス変数を定義してもレキシカルに決定される
class C
  @@val = 10
end

module B
  @@val = 30
end

module M
  include B
  @@val = 20

  class << C
    p @@val #=> 20
  end
end
  • :fishはSymbolクラスのオブジェクト
begin
  print "liberty" + :fish.to_s
rescue TypeError
  print "TypeError."
rescue
  print "Error."
else
  print "Else."
ensure
  print "Ensure."
end

#=> libertyfishElse.Ensure.
  • andは左辺が真であれば、右辺の結果を返します。左辺が偽であれば、左辺の結果を返す
    優先順位が低いのでputs v2が評価される
p v1 = 1 / 2 == 0 #=> true
p v2 = !!v1 or raise RuntimeError #=> true
puts v2 and false #=> true
  • __method__はメソッドの中で呼び出すとそのメソッドに名になる
def awesome_method
  __method__
end

p awesome_method #=> :awesome_method
  • 引数名に&を付与することでブロック引数になる
def bar(&block)
  block.yield
end

bar do
  puts "hello, world" #=> hello, world
end
  • Module.nestingはネストの状態を表示する
module SuperMod
  module BaseMod
    p Module.nesting #=> [SuperMod::BaseMod, SuperMod]
  end
end
  • superを実行した場合にもRefinementが影響する
class C
end

module M
  refine C do
    def m1(value)
      super value - 100 # 300 - 100
    end
  end
end

class C
  def m1(value)
    value - 100 # 200 - 100
  end
end

using M

class K < C
  def m1(value)
    super value - 100 # 400 - 100
    # Refinementが有効なのでsuperはモジュールMにあるm1を参照する
  end
end

puts K.new.m1 400 #=> 100
  • String#scanはマッチした部分文字列を配列で返す
p "Matz is my tEacher".scan(/[is|my]/).length #=> 4
  • attr_accessorはattr_readerとattr_writerを同時に定義したもの
class C
  attr_accessor :v # ここ

  # 同じ
  def v=(other)
    @v = other
  end
  def v
    @v
  end

  # 同じ
  attr_reader :v
  attr_writer :v
end

c = C.new
c.v = 100
p c.v

class C
  
end
class Human
  NAME = "Unknown"

  def name
    NAME
  end
end

class Noguchi < Human
  NAME = "Hideyo"
end

puts Noguchi.new.name #=> Unknown
  • between?で値を比較するためには、Comparableをincludeする必要がある
# falsetrueになるように
class Company
  include Comparable # XXXX
  attr_reader :id
  attr_accessor :name
  def initialize id, name
    @id = id
    @name = name
  end
  def to_s
    "#{id}:#{name}"
  end
  def <=> other # YYYY
    self.id <=> other.id
  end
end

c1 = Company.new(3, 'Liberyfish')
c2 = Company.new(2, 'Freefish')
c3 = Company.new(1, 'Freedomfish')

print c1.between?(c2, c3)
print c2.between?(c3, c1)
  • チェーンを行う場合はEnumeratorオブジェクトを作成する必要がある
    作成に必要なメソッドはenum_forとto_enum
class Array
  def succ_each(step = 1)
    return enum_for(:succ_each, step) unless block_given?

    each do |int|
      yield int + step
    end
  end
end

p [98, 99, 100].succ_each(2).map {|succ_chr| succ_chr.chr} # ["d", "e", "f"]

[101, 102, 103].succ_each(5) do |succ_chr|
  p succ_chr.chr
  # "j"
  # "k"
  # "l"
end
  • freezeはオブジェクトの破壊的な変更を禁止する
    配列の破壊的な変更を禁止するが、配列の要素の破壊的な変更は禁止しない
array = ["a", "b", "c"].freeze

array.each do |chr|
  chr.upcase!
end

p array #=> ["A", "B", "C"]
  • JSON.loadまたは、JSON.parseは引数にJSON形式の文字列を指定するとHashオブジェクトに変換する
require 'json'

json = <<JSON
{
  "price":100,
  "order_code":200,
  "order_date":"2018/09/20",
  "tax":0.8
}
JSON

hash = JSON.load json

p hash #=> { "price"=>100, "order_code"=>200, "order_date"=>"2018/09/20", "tax"=>0.8 }
p Class.method_defined? :new #=> true
p String.method_defined? :new #=> false
p Class.singleton_class.method_defined? :new #=> true
p String.singleton_class.method_defined? :new #=> true
  • 定数の特徴
  • 代入を行うと警告が発生するが、値は変更される
  • 中身を直接変更した場合は値が変わる。ただし、警告は発生しない
CONST_LIST_A = ['001', '002', '003']
begin
  CONST_LIST_A.map{|id| id << 'hoge'}
rescue
end

CONST_LIST_B = ['001', '002', '003'].freeze
begin
  CONST_LIST_B.map{|id| id << 'hoge'}
rescue
end

CONST_LIST_C = ['001', '002', '003'].freeze
begin
  CONST_LIST_C.map!{|id| id << 'hoge'}
rescue
end

CONST_LIST_D = ['001', '002', '003'].freeze
begin
  CONST_LIST_D.push('add')
rescue
end

p CONST_LIST_A #=> ["001hoge", "002hoge", "003hoge"] 
p CONST_LIST_B #=> ["001hoge", "002hoge", "003hoge"] 
p CONST_LIST_C #=> ["001", "002", "003"]
p CONST_LIST_D #=> ["001", "002", "003"]

Rails ルーティング Tips

  • resources
  • resource
  • collection
  • member
  • namespace

ルーティングファイルの配置場所 config/routes.rb

Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  ## 以下に記述していく
end

resources

いくつかの書き方が存在する

## 1. 基本のアクション(index, new, create, show, edit, update, destroy)のルーティングが生成される
resources :posts

## 2. index, new, create, showのルーティングが生成される。
resources :posts, only: %i[index new create show]

## 3. destroy以外のアクションが生成される。
resources :posts, except: %i[destroy]

実務では2の書き方をよく見る。 必要なルーティングのみ生成する。

resourcesとresourceの違い

まず、生成されるルーティングの違いについてみていく

Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  resources :posts
  resource :post
end
  • 生成されるルーティングを確認する方法
    • ターミナルで rails routesコマンドを実行
% rails routes
      Prefix Verb   URI Pattern                  Controller#Action
    ########  resources :posts で生成されたルーティング ############
         posts GET    /posts(.:format)              posts#index
               POST   /posts(.:format)              posts#create
      new_post GET    /posts/new(.:format)          posts#new
     edit_post GET    /posts/:id/edit(.:format)     posts#edit
          post GET    /posts/:id(.:format)          posts#show
               PATCH  /posts/:id(.:format)          posts#update
               PUT    /posts/:id(.:format)          posts#update
               DELETE /posts/:id(.:format)          posts#destroy
    ########  resource :post で生成されたルーティング ############
               GET    /post/new(.:format)           posts#new
               GET    /post/edit(.:format)          posts#edit
               GET    /post(.:format)               posts#show
               PATCH  /post(.:format)               posts#update
               PUT    /post(.:format)               posts#update
               DELETE /post(.:format)               posts#destroy
               POST   /post(.:format)               posts#create

違いをいくつかまとめる

  • resources :posts
    • :idが存在する
  • resource :post
    • URI Patternの始めが/postで始まる
    • Controller#Actionにposts#indexがない

両者を使い分ける方法としては
特定のレコードを取得する際にidが必要かどうかで判断するといい

少し具体例を出して説明する

例えば ユーザーの基本的な情報(名前、メールアドレス)を保存するUserモデルが存在し
さらに詳しい情報(身長、体重、生年月日)を保存するProfileモデルが存在したとする

  • UserとProfileはhas_oneの関係になる(1対1)
  • Profileモデルは必ずUserのidに紐づく(Profileモデルがuser_idを持つ)

ある特定のユーザーのデータを取得する際はidが必要(idがないとレコードを特定できない)
しかしProfileのレコードは必ずユーザーに紐づくので特定のユーザーの情報さえ取得できればそれに紐づくProfileのレコードは特定できる
したがってProfileは特定のレコードを取得する際にidは不要

生成するルーティングとしてはUserはresources, Profileはresourceになる

Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  resources :users do
    resource :profile, only: %i[show edit update]
  end
end
% rails routes
                  Prefix Verb   URI Pattern          Controller#Action
 new_user_profile GET    /users/:user_id/profile/new(.:format)   profiles#new
edit_user_profile GET    /users/:user_id/profile/edit(.:format)  profiles#edit
     user_profile GET    /users/:user_id/profile(.:format)       profiles#show
                  PATCH  /users/:user_id/profile(.:format)       profiles#update
                  PUT    /users/:user_id/profile(.:format)       profiles#update
                  POST   /users/:user_id/profile(.:format)       profiles#create
            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

profileのdestroyアクションはおそらくUserのdestroyメソッドを使用した時にprofileも同時に削除されるような実装をすると思うので外した。

collection

## 1.一つだけルーティングを生成する場合
get 'search', on: :collection

## 2.複数ルーティングを生成する場合
collection do
  get 'search'
  get 'download_file'
  :
end

さっきのユーザーのルーティングに新たに検索機能(searchアクション)を追加する場合

resources :users do
  resource :profile, only: %i[new create show edit update]
  collection do
    get 'search'
  end
end

生成されるルーティング

% rails routes
      Prefix Verb   URI Pattern               Controller#Action
## 省略
search_users GET    /users/search(.:format)   users#search
## 省略

collectionとmemberの違い

生成されるルーティングの違い

resources :users do
  resource :profile, only: %i[new create show edit update]
  get 'search', on: :collection
  get 'search', on: :member
end
% rails routes
      Prefix Verb   URI Pattern                    Controller#Action
## 省略
search_users GET    /users/search(.:format)        users#search
 search_user GET    /users/:id/search(.:format)    users#search
## 省略
  • memberの方はidがつく

Ruby on Rails [単一モデル] CRUD

複数回に渡って単体モデルのCRUDから多対多のモデルのCRUDまでを記事にしてまとめます。

アプリが完成する頃には下記のような機能が実装できます。

  • ユーザーはログインできる
  • ユーザーは投稿できる
  • ユーザーは他のユーザーの投稿にいいねができる  

またER図は以下のようになっています。

ER図
ER図

 

前提条件としてrailsの環境構築はできているものとします。(rails s コマンドでlocalhost:3000にアクセスでき下記画像のように表示される)

f:id:nasuka7:20201006083806p:plain
localhost:3000アクセス時

下記環境で進めていきます。

% ruby -v

ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19]

% rails -v
Rails 6.0.3.2

% postgres --version
postgres (PostgreSQL) 9.6.18

では早速まいりましょう。

まず誰でも投稿できるようにPostモデルを作成します。

% rails g model post

rails gはrails generateの略で以下のように使用します。

rails g model モデル名(単数)
例) rails g model post

rails g controller コントローラー名(複数)
例) rails g controller pos