かずおの開発ブログ(主にRuby)

日々の開発のことを色々書きます。

オブジェクト生成時にインスタンス変数の初期値を設定する

オブジェクト生成時にインスタンス変数の初期値を設定する Railsのmodelではmodel生成時に

user = User.new(name: "tarou", address: "東京都港区", phone: "08099999999"...)

このような形で初期値を設定することが出来ます。

でこれに慣れてるとRubyでこのようなクラスがあったとして、同じようにこんなふうにインスタンス変数設定したいなーと思って書いてもRubyのオブジェクトにそんな機能は備わってないので、Object#initializeを呼んでエラーになってしまいます。

class Bar
  attr_accessor :a, :b, :c
end
Bar.new(a: "A", b: "B", c: "C")
ArgumentError: wrong number of arguments (1 for 0)
from (pry):5:in `initialize'

よくあるやり方としてはinitializeで普通に引数を設定する方法があります。

class Foo
  attr_accessor :a, :b, :c
  def initialize(a, b, c)
    @a = a
    @b = b
    @c = c
  end
end
=> :initialize
Foo.new("A", "B", "C")
=> #<Foo:0x007fec0b61e690 @a="A", @b="B", @c="C">

ただしこのやりかただと変数の数がたくさん増えていった場合にいちいちinitializeの定義を確認してどの順番で引数を渡さないといけなかったかなーと確認しないといけなくなりとても不便です。

そこでもう少し便利な方法を考えます。

キーワード引数を使う

キーワード引数を使うことで引数を関数に与えられた順番ではなく、キーワードに紐付けて渡せるようになります。その結果上記のコードは以下のように書き直せます。これによってどの値にどの値をセットしているか がよくわかるようになるし、順番も気にする必要がなくなります。

class Bar
  attr_accessor :a, :b, :c
  def initialize(a:, b:, c:)
    @a = a
    @b = b
    @c = c
  end
end
Bar.new(a: "A", b: "B", c: "C") #keyword:値 なのでどの値にどの値をセットするかがわかりやすい
=> #<Bar:0x007fec06199c50 @a="A", @b="B", @c="C">
Bar.new(b: "B", a: "A", c: "C") #順番を入れ替えても問題ない
=> #<Bar:0x007fec0925c388 @a="A", @b="B", @c="C">

ただしこれでもたくさん変数があると

def initialize(a:, b:, c:, d:...)
  @a = a
  @b = b
  @c = c
  @d = d
...
end

となってやや冗長な感じがします。

sendを使う

そこで今度はsendを使って初期値の代入を行います。与えられた引数をまずhash形式に直したうえでsendを使って初期値をセットします。こうすることでいくら初期値がたくさんあってもこれ以上コードが長くなることはなくなりました。ただし、ぱっと見何をやってるかわかりづらいのでここまでやるかは微妙なところかと思います。

class Foo
  attr_accessor :a, :b, :c, :d, :e, :f
  def initialize(a:, b:, c:, d:, e:, f:)
    # 与えられた引数をhash形式で取得
    args_hash = self.method(__callee__).parameters.map do |arg_type,arg|
      [ arg, eval(arg.to_s) ]
    end.to_h
    # sendを使って初期値をセット
    args_hash.each do |prop_name, prop_value|
      self.send("#{prop_name}=",prop_value)
    end
  end
end
Foo.new(a: "A", b: "B", c: "C", d: "D", e: "E", f: "F")
=> #<Foo:0x007f8154748128 @a="A", @b="B", @c="C", @d="D", @e="E", @f="F">
Foo.new(a: "A", b: "B", c: "C")
ArgumentError: missing keywords: d, e, f #何が足りていないかもわかりやすい。ただし何も入れたくない場合は明示的にnilをセットする必要がある。

まだまだ初学者+エンジニア1人でやっているのでおかしいところがあれば是非ご指摘いただけると嬉しいです。 参考: http://qiita.com/hidechae/items/5416a996529d7663c182

LINUXでファイルのフルパスを表示させる

コマンドが無かったのでシェルスクリプトで書きました。 bashrcとかに以下を追記して

function lsfull() {
  current=`pwd`
  echo $current
  command ls -d ${current}/*
}

読みこみ

. path/to/bashrcとか

使ってみる

$ lsfull
/Users/user/desktop/foo/hoge
/Users/user/desktop/foo/hoge/bar

たくさん文字が表示されるのでgrepとかと組み合わせて使うと便利です

$ lsfull | grep bar
/Users/user/desktop/foo/hoge/bar

Rubyのメソッドについてあらためてしっかり理解する

今回はメソッドについてです。 Rubyに限らずほぼ全ての言語には

を定義可能です。 今回はこれらについて詳しく見ていきます。

パブリックと プライベートメソッドの違い

こちらはわかっている方が大多数かと思いますが、改めて確認です。 パブリックメソッドはクラス外から呼び出せる公的なメソッド、 プライベートメソッドはクラス内でしか呼び出せない私的なメソッドです。

class A
  def some_public_method
    puts "this is public instance method"
  end

  def call_private_method
    some_private_method
  end

  private
  def some_private_method
    puts "this is private instance method"
  end
end

# パブリックメソッド(呼び出せる)
A.new.some_public_method
# => this is public instance method

# プライベートメソッド(呼び出せない)
A.new.some_private_method
# => private method `private_instance_method' called for #<A:0x007fe0b204e300> (NoMethodError)

# 内部から呼び出すことは可能
A.new.call_private_method
# => this is private instance method

プライベートメソッドが呼び出せないのはRubyの仕組みとして * privateメソッドにレシーバーをつけてはいけない * 自分(self)以外のオブジェクトのメソッドを呼び出すときはレシーバをつけなければいけない という2つのルールがコンフリクトしてしまうためです。 参考(url)

インスタンスメソッドとクラスメソッド

こちらも基本です。クラスメソッドはクラスから直接呼び出せるメソッドインスタンスメソッドはクラスからnewで作られたインスタンスから呼び出せるメソッドです。( Rubyではクラスもオブジェクトなのでクラスメソッドはクラスのインスタンスメソッドとも言えるかと思います 。)

class B
  def some_instance_method
    puts "this is instance method"
  end
  def self.some_class_method
    puts "this is class method"
  end
end

# インスタンスメソッドはクラスをnewしたインスタンスから呼び出せる
B.new.some_instance_method
# => this is instance method

# クラスメソッドはクラスから直接呼び出せる
B.some_class_method
# => this is class method

# インスタンスからクラスメソッドは呼び出せrない
B.new.some_class_method
# => undefined method `some_class_method' for #<B:0x007f8cc4113b60> (NoMethodError)

# クラスからインスタンスメソッドは呼び出せない
B.some_instance_method
# => undefined method `some_instance_method' for B:Class (NoMethodError)

パブリック・プライベートとクラス・インスタンスメソッドをそれぞれ組み合わせたもの

今度はそれぞれを組み合わせたものを試してみます

class C
  def some_public_instance_method
    puts "this is public instance method"
  end

  def self.some_public_class_method
    puts "this is public class method"
  end

  private
  def some_private_instance_method
    puts "this is private instance method"
  end

  def self.some_private_class_method
    puts "this is private class method"
  end
end

C.some_private_class_method
# => this is private class method

とは言っても今までで出てきていないのはプライベートなクラスメソッドのみです。ここで不思議なことが起きるのですが、プライベートなクラスメソッドはクラスの外から呼び出せてしまいます。つまりパブリックなクラスメソッドと同じ動きをしてしまいます。 ちょっとこれはかなり不思議な事態なのですが恐らく * privateメソッドにレシーバーをつけてはいけない =>(クラス側から見ればself.some_private_class_method=C.some_private_classmethodと解釈されてレシーバーはついていない) * 自分(self)以外のオブジェクトのメソッドを呼び出すときはレシーバをつけなければいけない =>(呼び出し側から見ればCがレシーバー) となって呼び出せてしまうのかなと思います。あくまでこれは僕の推測ですのでご注意ください。(もしご存知の方いらっしゃいましたら、コメントでご教示いただけると幸いです。)

本当にプライベートなクラスメソッドを定義するにはprivate_class_methodを使う必要があります。

class D

  def self.some_private_class_method
    puts "hello"
  end
  private_class_method :some_private_class_method
end

D.some_private_class_method
# => private method `some_private_class_method' called for D:Class (NoMethodError)

これで意図した動作になりました。

== と ===の違いについて

今回はRubyにおける==と===の違いについて調べました。 ===メソッドはwhen case文の中で内部的に使われているので、違いをわかっていないと思わぬところではまります。

先に結論から言っておくと

  • ===メソッドが拡張されていないクラス

    • ==と同じ
  • ===メソッドが拡張されているクラス

    • それぞれ定義された挙動

ということです。常に違う動きをするわけではないというのが、初めわかってなかったんですが、

これを理解するとスッと理解できました。

===メソッドが拡張されていないクラス

まずは===メソッドが拡張されていないクラスについて考えます。これは上記の通り==メソッドと同じ動きをするので、 例えば拡張されていないStringクラスでは

"hoge" == "hoge"
=> true
"hoge" === "hoge"
=> true

といったように==と===で同じ挙動を示します。

=== メソッドが拡張されているクラス

===メソッドが拡張されているクラスには以下の4つがあります。

  • Range
  • Regexp
  • Proc
  • Module, Class

1つずつどのような挙動の違いがあるのか確認していきましょう。

Range

Rangeの===は引数が自身の範囲内に含まれるなら真を返します。

(1..5) == 3 #これは同値性を判定しているのでfalse
=> false
(1..5) === 3 #拡張された===が呼び出される
=> true

ここで注意しないといけないのは

3 === (1..5)
=> false

左右を入れ替えるとfalseになるということです。これは3(Numeric)の===を呼び出しているため、拡張されていない===メソッドが呼び出されて同値性を判定した結果falseになるということです。このように===拡張されたクラスを扱う時は左右を入れ替えるだけで真偽値が変わってしまうので注意が必要です。

ちなみにRangeの実装は以下のようになっています(注: C言語)

/*
 *  call-seq:
 *     rng === obj       ->  true or false
 *
 *  Returns <code>true</code> if +obj+ is an element of the range,
 *  <code>false</code> otherwise.  Conveniently, <code>===</code> is the
 *  comparison operator used by <code>case</code> statements.
 *
 *     case 79
 *     when 1..50   then   print "low\n"
 *     when 51..75  then   print "medium\n"
 *     when 76..100 then   print "high\n"
 *     end
 *
 *  <em>produces:</em>
 *
 *     high
 */

static VALUE
range_eqq(VALUE range, VALUE val)
{
    return rb_funcall(range, rb_intern("include?"), 1, val);
}

Regexp

Regexpは引数の文字列がマッチした場合は真を返します。

/k/ == 'kazuya'
=> false
/k/ === 'kazuya'
=> true

実装は以下のようになっています。(cは辛いですね。。。)

/*
 *  call-seq:
 *     rxp === str   -> true or false
 *
 *  Case Equality---Used in case statements.
 *
 *     a = "HELLO"
 *     case a
 *     when /^[a-z]*$/; print "Lower case\n"
 *     when /^[A-Z]*$/; print "Upper case\n"
 *     else;            print "Mixed case\n"
 *     end
 *     #=> "Upper case"
 *
 *  Following a regular expression literal with the #=== operator allows you to
 *  compare against a String.
 *
 * /^[a-z]*$/ === "HELLO" #=> false
 * /^[A-Z]*$/ === "HELLO" #=> true
 */

VALUE
rb_reg_eqq(VALUE re, VALUE str)
{
    long start;

    str = reg_operand(str, FALSE);
    if (NIL_P(str)) {
    rb_backref_set(Qnil);
    return Qfalse;
    }
    start = rb_reg_search(re, str, 0, 0);
    if (start < 0) {
    return Qfalse;
    }
    return Qtrue;
}

Proc

Procついては一番トリッキーです。なんとblockの呼び出しができます。 Proc#===(arg) -> () argにブロックの引数を入れることができます。

proc = Proc.new{|a, b| puts a; puts b}
proc.call(1,2)
1
2
=> nil
proc === [1, 2]
1
2
=> nil

クエリ:Proc#=== | るりまサーチ なんでこんな実装になっているかというと、冒頭でも書きましたがwhen case文で使うためですね。 こんな感じでProcそのものの同値判定ではなくProcの戻り値で判定を行うことができます

proc = Proc.new{"hello"}
greeting = "hello"
case greeting
  when proc #ここでproc===が呼ばれる
    puts "this is hello"
end
=> "this is hello"

こちらも実装を確認してみましょう。 case文で使うための実装ですよということが書いてあります。

/*  Document-method: ===
 *
 *  call-seq:
 *     proc === obj   -> result_of_proc
 *
 *  Invokes the block with +obj+ as the proc's parameter like Proc#call.  It
 *  is to allow a proc object to be a target of +when+ clause in a case
 *  statement.
 */

/* CHECKME: are the argument checking semantics correct? */

/*
 *  call-seq:
 *     prc.call(params,...)   -> obj
 *     prc[params,...]        -> obj
 *     prc.(params,...)       -> obj
 *
 *  Invokes the block, setting the block's parameters to the values in
 *  <i>params</i> using something close to method calling semantics.
 *  Generates a warning if multiple values are passed to a proc that
 *  expects just one (previously this silently converted the parameters
 *  to an array).  Note that <code>prc.()</code> invokes
 *  <code>prc.call()</code> with the parameters given.  It's a syntax sugar to
 *  hide "call".
 *
 *  Returns the value of the last expression evaluated in the block. See
 *  also Proc#yield.
 *
 *     a_proc = Proc.new { |scalar, *values| values.collect { |value| value*scalar } }
 *     a_proc.call(9, 1, 2, 3)   #=> [9, 18, 27]
 *     a_proc[9, 1, 2, 3]        #=> [9, 18, 27]
 *     a_proc.(9, 1, 2, 3)       #=> [9, 18, 27]
 *
 *  For procs created using <code>lambda</code> or <code>->()</code> an error
 *  is generated if the wrong number of parameters are passed to a Proc with
 *  multiple parameters.  For procs created using <code>Proc.new</code> or
 *  <code>Kernel.proc</code>, extra parameters are silently discarded.
 *
 *     a_proc = lambda {|a,b| a}
 *     a_proc.call(1,2,3)
 *
 *  <em>produces:</em>
 *
 *     prog.rb:4:in `block in <main>': wrong number of arguments (given 3, expected 2) (ArgumentError)
 *         from prog.rb:5:in `call'
 *         from prog.rb:5:in `<main>'
 *
 */
#if 0
static VALUE
proc_call(int argc, VALUE *argv, VALUE procval)
{
    VALUE vret;
    const rb_block_t *blockptr = 0;
    const rb_iseq_t *iseq;
    rb_proc_t *proc;
    VALUE passed_procval;
    GetProcPtr(procval, proc);

    iseq = proc->block.iseq;
    if (RUBY_VM_IFUNC_P(iseq) || iseq->body->param.flags.has_block) {
   if (rb_block_given_p()) {
       rb_proc_t *passed_proc;
       passed_procval = rb_block_proc();
       GetProcPtr(passed_procval, passed_proc);
       blockptr = &passed_proc->block;
   }
    }

    vret = rb_vm_invoke_proc(GET_THREAD(), proc, argc, argv, blockptr);
    RB_GC_GUARD(procval);
    RB_GC_GUARD(passed_procval);
    return vret;
}
#endif

#if SIZEOF_LONG > SIZEOF_INT
static inline int
check_argc(long argc)
{
    if (argc > INT_MAX || argc < 0) {
    rb_raise(rb_eArgError, "too many arguments (%lu)",
         (unsigned long)argc);
    }
    return (int)argc;
}
#else
#define check_argc(argc) (argc)
#endif

VALUE
rb_proc_call(VALUE self, VALUE args)
{
    VALUE vret;
    rb_proc_t *proc;
    GetProcPtr(self, proc);
    vret = rb_vm_invoke_proc(GET_THREAD(), proc, check_argc(RARRAY_LEN(args)), RARRAY_CONST_PTR(args), 0);
    RB_GC_GUARD(self);
    RB_GC_GUARD(args);
    return vret;
}

VALUE
rb_proc_call_with_block(VALUE self, int argc, const VALUE *argv, VALUE pass_procval)
{
    VALUE vret;
    rb_proc_t *proc;
    rb_block_t *block = 0;
    GetProcPtr(self, proc);

    if (!NIL_P(pass_procval)) {
    rb_proc_t *pass_proc;
    GetProcPtr(pass_procval, pass_proc);
    block = &pass_proc->block;
    }

    vret = rb_vm_invoke_proc(GET_THREAD(), proc, argc, argv, block);
    RB_GC_GUARD(self);
    RB_GC_GUARD(pass_procval);
    return vret;
}

Module, Class

Module, Classは引数が自身または自身のサブクラスのインスタンスであれば真を返します

"hello" === String
 => false
String === "hello"
 => true

こちらも実装を確認しておきます

/*
 *  call-seq:
 *     mod === obj    -> true or false
 *
 *  Case Equality---Returns <code>true</code> if <i>obj</i> is an
 *  instance of <i>mod</i> or and instance of one of <i>mod</i>'s descendants.
 *  Of limited use for modules, but can be used in <code>case</code> statements
 *  to classify objects by class.
 */

static VALUE
rb_mod_eqq(VALUE mod, VALUE arg)
{
    return rb_obj_is_kind_of(arg, mod);
}

...

VALUE
rb_obj_is_kind_of(VALUE obj, VALUE c)
{
    VALUE cl = CLASS_OF(obj);

    c = class_or_module_required(c);
    return class_search_ancestor(cl, RCLASS_ORIGIN(c)) ? Qtrue : Qfalse;
}

って感じです。when caseの内部でしか基本使われないとはいえ理解しておいて損はないかと思います!

チェックボックスやラジオボタンの値に応じてフォームの活性非活性を切り替える

Railsでフォームの開発をしていて、チェックボックスラジオボタンの値によってフォームの活性/非活性を切り替えたいなってことがよくあります。 その度に実装方法を思い出して、実装するっていうのは時間の無駄+馬鹿らしいのである程度汎用的なメソッドにしてしまうことにしました。 で作ったのがこちら。checkboxの値が定数なのでわざわざ引数にする必要がなかったり、複数のフォームに対応していなかったりまだ不十分なところはありますが、とりあえずこれをv0.1として適宜改修していこうと思います。

sample.html.slim(要 layoutファイル)

= check_box_tag 'checkbox', true, false, {}

= text_field_tag 'text_field'
= radio_button_tag 'gender', 'male'
| 男
= radio_button_tag 'gender', 'female', style: "display=block"
| 女

set_activate_connection.js.coffee

$ ->
  # checkboxのとき
  setActivateConnection("#checkbox", "true", undefined, "#text_field", "")
  # radiobuttonのとき
  setActivateConnection("input[name='gender']", "male", "female", "#text_field", "foo")
  return

setActivateConnection = (triggerSelector, activeValue, disabledValue, targetSelector, targetInitialVal) ->
  $(triggerSelector).on 'change', ->
    switch this.type
      when "checkbox"
        checkedVal = $(triggerSelector + ":checked").val()
        if checkedVal is activeValue
          $(targetSelector).removeAttr('disabled')
        else if checkedVal is disabledValue
          $(targetSelector).val(targetInitialVal).attr('disabled','disabled')
      when "radio"
        if this.value is activeValue
          $(targetSelector).removeAttr('disabled')
        else if this.value is disabledValue
          $(targetSelector).val(targetInitialVal).attr('disabled','disabled')
  return

配列、ハッシュを半分に分ける

Rubyを書いていて配列やハッシュを綺麗に半分に分けたいなってときありますよね?少なくともぼくはあったし、今この記事を見ているあなたもそう思ってるのかもしれません。 イメージとしてはこんな感じです。

[1, 2, 3, 4] => [[1, 2], [3, 4]]
["Yamada" => 34, "Katou" => 28, "Endou" => 18, "Matsumoto" => 25] 
=> ["first_half" => ["Yamada" => 34, "Katou" => 28],  "second_half"  =>["Endou" => 18, "Matsumoto" => 25]]

配列

まず配列についてですが、もっといいやり方がある気もするのですが、単純な方法で実装しています。...と..

今回は要素が偶数でちょうど半分に割ることを想定しているので、奇数の場合はfirst_halfの要素が1つ多くなってしまいますが、そこはオプションを持たしたりということはしていません。

class HalfArray
  def self.half_array(array)
    first_half   = array.slice(0...array.size/2)
    second_half  = array.slice((array.size/2)..array.size)
    halfed_array = [first_half, second_half]
  end
end
sample_array = [1, 2, 3, 4]
HalfArray.half_array(sample_array) => [[1, 2], [3, 4]]

でせっかくなのでArrayクラスを拡張した形に変えてみます。

class Array
  def half
    first_half   = self.slice(0...self.size/2)
    second_half  = self.slice((self.size/2)..self.size)
    halfed_array = [first_half, second_half]
  end
end

sample = [1,2,3,4]
sample.half => [[1, 2], [3, 4]]

うまくいきましたね!

ハッシュ

続いてハッシュです。これについては一旦Arrayにして半分に割りもう一度ハッシュに戻すという、配列以上にもっといいやり方がありそうな気がするやり方をしてます笑

class HalfHash
  def self.half_hash(hash, first_key, second_key)
    first_half   = Hash[hash.to_a.slice(0...(hash.size/2))]
    second_half  = Hash[hash.to_a.slice((hash.size/2)..hash.size)]
    halfed_hash = {first_key => first_half, second_key => second_half}
  end
end

sample_hash = {"Yamada" => 34, "Katou" => 28, "Endou" => 18, "Matsumoto" => 25}
result = HalfHash.half_hash(sample_hash, "sample_first", "second_first")
=> {"sample_first"=>{"Yamada"=>34, "Katou"=>28}, "second_first"=>{"Endou"=>18, "Matsumoto"=>25}}

配列と同様Hashクラスを拡張します

class Hash
  def half (first_key, second_key)
    first_half   = Hash[self.to_a[0..(self.size/2 - 1)]]
    second_half  = Hash[self.to_a[(self.size/2)..self.size]]
    halfed_hash  = {first_key => first_half, second_key => second_half}
  end
end

sample_hash = {"Yamada" => 34, "Katou" => 28, "Endou" => 18, "Matsumoto" => 25}
sample_hash.half("first_half", "second_half")
=> {"first_half"=>{"Yamada"=>34, "Katou"=>28}, "second_half"=>{"Endou"=>18, "Matsumoto"=>25}}

配列とハッシュの処理をまとめる

で最後に配列とハッシュの処理がかなり重複しているのでDRYな感じにしてしましましょう。

class Array
  def half
    first_half   = self.slice(0...self.size/2)
    second_half  = self.slice((self.size/2)..self.size)
    halfed_array = [first_half, second_half]
  end
end

class Hash
  def half (first_key, second_key)
    first_half  = Hash[self.to_a.half[0]] #ここでArrayのhalfを使う
    second_half  = Hash[self.to_a.half[1]] #ここも
    halfed_hash  = {first_key => first_half, second_key => second_half}
  end
end

sample = [1,2,3,4]
sample.half => [[1, 2], [3, 4]]

sample_hash = {"Yamada" => 34, "Katou" => 28, "Endou" => 18, "Matsumoto" => 25}
sample_hash.half("first_half", "second_half")
=> {"first_half"=>{"Yamada"=>34, "Katou"=>28}, "second_half"=>{"Endou"=>18, "Matsumoto"=>25}}

追記

Railsではin_groups_ofというメソッドがあるので、それを使うのが良いです。

sample_even = [1, 2, 3, 4]
sample.in_groups_of(sample.size/2) 
=> [[1, 2], [3, 4]]
sample_odd  =  [1, 2, 3, 4, 5, 6, 7]

# 奇数の場合はオプションにfalseをつけないと足りない分の要素1つがnilで埋まります。
sample.in_groups_of(sample.size/2 + 1, false)
=> [[1, 2, 3, 4], [5, 6, 7]]