オブジェクト生成時にインスタンス変数の初期値を設定する
オブジェクト生成時にインスタンス変数の初期値を設定する 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
今いるブランチをクリップボードにコピーする
コマンドはなかったのでlsfullに引き続き自分で書きました bashrcとかに以下を追記
alias gbcopy='git rev-parse --abbrev-ref HEAD | pbcopy'
読み込み
. ~/.bashrcとか
使う
$ gbcopy
$ master #paste
結構便利です。
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]]