HadoopのMapReduceについて
ECサイトのリコメンド機能や、広告の最適化等、多くのWEBサービスで活用されているHadoopのMapReduce処理についてまとめてみた。
1. そもそもHadoopとは何か
Hadoopとは、ビッグデータの分析処理を、複数のマシンに分散させることで、高速化するミドルウェアである。Apache Software FoundationがOSSで開発・公開している。これまで、複数マシンへの処理の分散は、通信、監視、障害時の対応などの観点から、エンジニアにとって困難なものであったが、これを使うことでエンジニアが処理を分散することを意識しなくて良いほど容易に分散処理を行えるようになった。例えば、Hadoopを使うことで、米VISA社は、これまで1ヵ月かかっていたような重い処理(130億件のトランザクションデータの処理)を、13分に短縮した。
Hadoopは、大きく分けてMapReduceという処理モデルとHDFSというファイル管理システムで構成されている。
そして、分散処理の型としては、masterがslaveに対してタスクを振り分けるpush型のものと、slaveがmasterからタスクをとってくるpull型のものがあるが、push型のものに属する。Hadoopにおける、MapReduce処理のmasterはJob Tracker、slaveはTask Trackerと呼び、HDFSのmasterは、Name Node, (Secondary Name Node)、slaveはData Nodeと呼ぶ。
2. なぜHadoopは生まれたのか
なぜHadoopが生まれたのか。それは昨今、企業や政府、個人が扱えるデータが膨大なものとなったからである。そうしたビッグデータを分析することでお金や価値を生み出せるようになり、それに伴い、その分析処理の高速化が求められるようになった。ビッグデータに関しては5分でわかる!動画スライド「ビッグデータ利活用が注目される2つの背景」 | ビッグデータマガジンで、うまくまとまっている。
3. HadoopのMapReduce処理とは
今回は、Hadoopの中でもGoogleによって提唱されたプログラミングモデルである、MapReduceについて解説する。
HadoopのMapReduce処理は、大きく分けて「Map」「Shuffle」「Reduce」の3つの処理からなる。
(1)Map処理 : インプットしたビッグデータを構造化する処理。
具体的には、ビックデータをkeyとvalueを持つhashに変換する処理。
(2)Shuffle処理&Sort処理 : 構造化したデータを整列する処理。
具体的には、Map処理で生成されたhashを、keyでソートし、keyと同じkeyを持つvalueのhashを生成する。
この処理の後、master node が slave node にreduce処理を分散する際に、hashを同じサーバーにまとめてくれる。
(3)Reduce処理 : 整列されたデータに対する計算などの処理。
具体的には、arrayの要素数を数えたり、各hashのvalue数を数えたりと、整列されたデータを使った様々な計算処理。
この中で、Hadoopのユーザーはmap処理とreduce処理を入力するが、shuffle処理に関しては自動的にHadoopによって行われる。
また、map処理とreduce処理は複数マシンに処理が分散されるが、shuffle処理は一つのマシンで処理される。したがって、Hadoopは、MapReduce処理におけるmap処理とreduce処理が重いような処理に対して使うことで、その処理を高速化できる。
そして、MapReduce処理が使われる例として、テキストマイニングや、画像分析等が挙げられる。
以下に、テキストマイニングにおける、MapReduce処理の簡単な例を記述しておく。
#"IN"という英単語が文字列の中で何回使われているか調べる処理をしたいとする #以下のような文字列があったとき "A BIRD IN THE HAND IS WORTH TWO IN THE BUSH" #map処理で、以下のようなarrayを生成し [ {'A' => 1}, {'BIRD' => 1}, {'IN' => 1}, {'THE' => 1}, {'HAND' => 1}, {'IS' => 1}, {'WORTH' => 1}, {'TWO' => 1}, {'IN' => 1}, {'THE' => 1}, {'BUSH' => 1} ] #shuffle処理によって、以下のようなarrayに変換され [ {'A' => 1}, {'BIRD' => 1}, {'BUSH' => 1} , {'HAND' => 1}, {'IN' => 1}, {'IN' => 1}, {'IS' => 1}, {'THE' => 1}, {'THE' => 1}, {'TWO' => 1}, {'WORTH' => 1} ] ↓ [ {'A' => 1}, {'BIRD' => 1}, {'BUSH' => 1} , {'HAND' => 1}, {'IN' => [1, 1]}, {'IS' => 1}, {'THE' => [1, 1]}, {'TWO' => 1}, {'WORTH' => 1} ] #reduce処理で、今回は"IN"という英単語が文字列の中で何回使われているか調べたいから、'IN'がkeyのvalueの要素数を数える #=>2
参照URL
1.5分でわかる!動画スライド「ビッグデータ利活用が注目される2つの背景」 | ビッグデータマガジン
以上
Rubyのオブジェクトの同値性、同一性等の比較方法
Rubyでオブジェクト同士を比較する方法についてまとめる。
1.オブジェクト同士の同値性を確かめる
2.オブジェクト同士の同一性を確かめる
3.ハッシュのkeyとvalue両方の同値性を確かめる
4.正規表現と文字列がマッチしているかいないかを確かめる
5.ふたつのオブジェクトの大小や順番を比較する
1. オブジェクト同士の同値性を確かめる
オブジェクトの同値性とは、オブジェクトが同じでも、別々でもよく、ただ値が等しいことを意味する。この同値性を確かめるには、== を使う。
str1= str2 = String.new"hoge" str3 = String.new"hoge" str4= String.new"fuga" #== を使って比較する str1 == str2 #=> true(オブジェクトが等しい場合) str1 == str3 #=> true(値のみ等しい場合) str1 == str4 #=> false(値が異なる場合)
多くのクラスで同値性は==で確かめられるが、例外として、Objectクラスにおいて、==は、equal?と同義で使われ、オブジェクトの同一性を判定するのに用いられる。
他にも、異値性を確かめる演算子として、!=がある。!=は、==がtrueを返すとき、falseを返し、==がfalseを返すとき、trueを返す。
2.オブジェクト同士の同一性を確かめる
オブジェクト同士の同一性とは、すなわち、オブジェクトが等しいということである。
これには、equal?メソッドが用いられる。
str1= str2 = String.new"hoge" str3 = String.new"hoge" str4= String.new"fuga" #== を使って比較する str1.equal? str2 #=> true(オブジェクトが等しい場合) str1.equal? str3 #=> false(値のみが等しい場合) str1.equal? str4 #=> false(値が異なる場合)
equal?メソッドは、オブジェクトクラスのもので、オブジェクトの同一性を確かめるメソッドとして、いかなるサブクラスによってoverrideされるべきではないとされている。
同一性を確かめる手段として、他にも、object_idを比較する方法もある。
str1= str2 = String.new"hoge" str3 = String.new"hoge" str4= String.new"fuga" #== を使って比較する str1.object_id == str2.object_id #=> true(オブジェクトが等しい場合) str1.object_id == str3.object_id #=> false(値のみが等しい場合) str1.object_id == str4.object_id #=> false(値が異なる場合)
3. ハッシュのkeyとvalue両方の同値性を確かめる
これには、eql?メソッドを使う。
このメソッドはハッシュのkeyとvalue両方にeql?メソッドを用い、比較する。
hash1 = { :a => 1, :b => 2 } hash2 = { :a => 1.0, :b => 2 } hash1 == hash2 #=> true hash1.eql(hash2) #=>false #Numelicクラスに対して、eql?メソッドは、floatとintegerを比較する場合、同値性を認めない
4. 正規表現と文字列がマッチしているかいないかを確かめる
正規表現(Regexp)と文字列(String)のパータンマッチがあるか否かを確認するには、=~, !~を使う。!~ 演算子は =~ 演算子が偽 (nil または false) を返す場合は true を返し、それ以外の場合は false を返す。
/pencil/ =~ "I have a pencil." #=> true /eraser/ =~ "I have a pencil." #=> false /pencil/ !~ "I have a pencil." #=> false /eraser/ !~ "I have a pencil." #=> true
5.ふたつのオブジェクトの大小や順番を比較する
オブジェクトの大小を比較する際に用いるのは、Comparableのメソッドの<. >, <=, >=を用いる。
順に、<は左辺が小さい、>は左辺が大きい、<=は左辺が小さいもしくは同じ、>=は左辺が大きいもしくは同じである場合にtrueを返す。
1 < 2 #=>true 2 > 1 #=>true 1 <= 2 #=>true 2 >= 1 #=>true
さらに、比較するふたつのオブジェクトのうちどちらが大きいか、もしくは、同じかを確認したい場合は、<=>を用いる。
左辺が小さい場合、-1を返し、左辺が大きい場合、1を返し、両辺が同値の場合、nilを返す。
1 <=> 2 #=> -1 2 <=> 1 #=> 1 1 <=> 1 #=> nil
以上
Rubyのmap、reduce処理について
Rubyのmap処理が結構ややこしいのでまとめてみた。
最後にreduceメソッドについても触れる。
1.RubyのArray#map, Hash#mapメソッドについて
RubyのArrayクラス、Hashクラスのmapメソッドは、ブロックを引数にとり、要素の数だけループをまわし、その戻り値をarrayに格納する。mapメソッドは、別名collectメソッドともいう。
mapメソッドが使われる例として、以下のようなものがあげられる。
#1.arrayの要素に処理をしてarrayに格納する array = [ 1, 2, 3, 4, 5, 6 ] array.map{ |num| num.to_s } #=> [ "1", "2", "3", "4", "5", "6" ] #2.hashのvalueをarrayに格納する hash = { key1: 1, key2: 2, key3: } hash.map{ |k,v| v } #=>[ 1, 2, 3 ] #2については、以下の方法もある hash.values #=> [ 1, 2, 3 ]
2.Rubyのmap処理を短く記述する
RubyではActiveSupportというgemをrequireすることで、上記のmap処理をより簡単に記述することができる。
その記法について、説明する。
#1.arrayの要素に処理をしてarrayに格納する array = [ 1, 2, 3, 4, 5, 6 ] array.map(&:to_s) #=> [ "1", "2", "3", "4", "5", "6" ]
mapメソッドの引数の先頭についている「&」は、その引数がblockであることを表す。
Rubyでは、ほとんどのものがobjectだが、blockはobjectではない。そこで、「&」のあとにはblock内の手続きとコンテキストをオブジェクト化した、Procオブジェクトが期待される。
しかし、上記の例では「&」のあとにProcオブジェクトがきている訳ではない。なぜ、このような記法が許されるかというと、requireしたRubyのgemであるActiveSupportが「&」のあとにあるオブジェクトにto_procメソッドがある場合、to_procメソッドを呼び出してからブロックにすると定義しているからである。上記の例では、シンボルオブジェクトにto_proc メソッドが定義されていたから、そのメソッドが呼び出され、Procオブジェクトに変換されたものが、「&」によりブロックに変換され、map処理が正常に行われた。
3.Enumerable#reduceメソッドについて
Enumerable#reduceメソッドは、ArrayやHashの各値をブロックを使って繰り返し計算するのに使う。ブロックに順に、「Arrayやhashの要素1、Arrayやhashの要素2」、「一つ前のブロック処理の戻り値、Arrayやhashの要素3」、「一つ前のブロック処理の戻り値、Arrayやhashの要素4」、...を渡す。メソッドの戻り値は、ブロックが最後に返した値になる。reduceメソッドは、別名、injectメソッドともいう。
以下、reduceメソッドの使用例を紹介する。
numbers = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] numbers.reduce{ | sum, n | sum + n } #=> 55 #reduceの引数を指定すると、ブロックに順に、「引数、ArrayやHashの要素1」、「一つ前のブロックの戻り値、ArrayやHashの要素2」、「一つ前のブロックの戻り値、ArrayやHashの要素3」、...を渡す numbers.reduce(100){ | diff, n | diff - n } #=> 45
以上
Railsのリレーションのオプションでできること、まとめてみた。
Railsのモデル間のリレーションのメソッドには、多くのオプションがある。
今日は、それらを使ってできることをまとめていきたい。
リレーションのオプションでできること一覧
1. ポリモーフィック関連の定義
2. ポリモーフィック関連が定義されたモデルと他のモデルの関連の定義
3. 親モデルに子モデルが依存するようにする
4. 親モデルのプライマリーキーを設定する
5. 外部キーのカラム名を設定する
6. 紐づくモデルのインスタンスを取得するメソッドの名前を設定する
7. 紐づくモデルのobjectに対してvalidationを実行しない
8. 2階層以上離れているモデルのインスタンスを取得する
9. 親モデルのオブジェクトがsaveされたとしても、loadされている子モデルのオブジェクトの変更はsaveされないようにする
10. ひとつのモデル(A)から他のモデルを通して、もう一度モデル(A)のインスタンスを取得したとき、それが、もとのモデル(A)のインスタンスと等しいと設定する
1.ポリモーフィック関連の定義
これには、:as , :polymorphicオプションを使う。
ポリモーフィック関連とは、複数の上位モデルの下にひとつの下位モデルが紐づき、かつ、各上位モデルと下位モデルの関係が同じような場合のとき、それらの関係を抽象化して名前付けすることで、下位モデルから各上位モデルに共通のメソッドでアクセス出来るようにモデルを関連付けることである。
これを行うことで、2つの利点がある。それは、
1.下位モデルにおいて、上位モデルが増える度にbelongs_toを記述する必要がなくなる
2.下位モデルから複数の上位モデルに一括でアクセスできるようになる
である。
例えば、UserモデルとCompanyモデルがあり、UserとCompany両方の写真が保存されているImageモデルがあるとすると、ポリモーフィック関連は以下のように記述することができる。
#上位モデルでは、 :as オプションを記述する(imageableの部分は、その関係を分かりやすく表す名前をつけると良い) #User.rb has_many :images, :as => imageable #Company.rb has_many :images, :as => imageable #下位モデルでは、:polymorphic オプションを記述する #Image.rb belongs_to : imageable, :polymorphic =>true
ポリモーフィック関係における外部キーは、idとtypeの2種類設定される。Idには、上位モデルのidが入り、typeには、上位モデル名が入る。したがって、images table のカラムには、imageable_idとimageable_typeが追加される。
上記のリレーションにより、
Image.where(id:1).first.imageable
で、id = 1のImageモデルのインスタンスに紐づく、複数の上位モデルのインスタンスを一括で取得できる。
また、上位モデルから下位モデルのインスタンスを取得するには、
User.where(id: 1).first.imageable Company.where(id: 1).first.imageable
で、idが1のUser、Companyモデルに紐づくImageモデルのインスタンスを取得できる。
2.ポリモーフィック関連が定義されたモデルと他のモデルの関連の定義
これには、:sourceや:source_typeオプションを使う。
1.の例で用いた、Company, User, Imageモデルに加え、imagesを複数持つPhotographerモデルが存在する場合を例にとり説明する。
#User.rb has_many :images, :as => imageable #Company.rb has_many :images, :as => imageable #Image.rb belongs_to : imageable, :polymorphic =>true #Photographer.rb has_many :images
Photographerモデルに紐づく、Company, Userモデルのインスタンスを取得したい場合
、リレーションは以下のように記述する。
#Photographer.rb has_many :images has_many :companies, through: :images, :source :imageable, :source_type 'Comapany' has_many :users, through: :images, :source :imageable, :source_type 'User' #sourceで、ポリモーフィック関連で定義した関係の名前(:as オプションで指定したもの)を指定する #source_typeで、取得したいポリモーフィック関連の上位モデルの名前を指定する
これによって、
Photographer.where(id: 1).first.companies
で、idが1のPhotographerモデルに紐づくCompanyモデルのインスタンスを取得することができる。
捕捉として、:sourceオプションのみを使用する場合について説明する。
:source オプションのみを使用する場合は、以下の2例である。
1.他のモデルからポリモーフィック関連を定義した上位モデルに一括でのみアクセスする場合
2.他のモデルからポリモーフィック関連を定義した上位モデルがひとつしかない場合
3.親モデルに子モデルが依存するようにする
これには、dependentオプションを使う。
:dependentオプションによる、親モデルと子モデルの依存関係は5種類ある。
1.親objectが削除されたとき、それに紐づく子objectが同時にdestroyされる
=> dependent: :destroy
2.親objectが削除されたとき、それに紐づく子objectがdatabaseから直接削除される。よって、コールバックは発生しない。
=> dependent: :delete_all
3.親objectが削除されたとき、それに紐づく子objectの外部キーをnullにする
=> dependent: :nullify
4. 親objectが削除されたとき、それに紐づく子objectがある場合、例外を発生させる
=>dependent: :restrict_with_exception
5.親objectが削除されたとき、それに紐づく子objectがある場合、エラーを発生させる
=> dependent: :restrict_with_error
4.プライマリーキーを設定する
これには、:primary_keyオプションを使う。
カラム名を記述することで、プライマリーキーとなるカラムを設定できる。
例えば、Categoryモデルが複数のArticleモデルをもっている場合、
#Category.rb has_many :articles, :primary_key :name #Article.rb belongs_to :category
とすることで、CategoryモデルとArticleモデルの関係におけるCategoryモデルのプライマリーキーは、nameカラムとなる。
したがって、Articleモデルのcategory_idには、Categoryモデルのnameが入ることになる。
5.外部キーのカラム名を設定する
これには、:foreign_keyオプションを使う。
カラム名を記述することで、外部キーのカラムを設定できる。
3.のプライマリーキーの設定と共に用い、以下のように記述すると、
#Category.rb has_many :articles, :primary_key :name, :foreign_key :category_name #Article.rb belongs_to :category
Categoryモデルのプライマリーキーがname、外部キーがcategory_nameと設定することができる。よって、Articleモデルのcategory_nameにCategoryモデルのnameが入り、モデル間の関係わかりやすくすることができる。
6.紐づくモデルのインスタンスを取得するメソッドの名前を設定する
これには、class_nameオプションを使う。
ActiveRecordは、
belongs_to :model #defaultでbelongs_toの第一引数に、そのモデルに紐づくモデル名を全て小文字で単数系で設定 has_many :models #has_manyの第一引数に、そのモデルに紐づくモデル名を全て小文字で複数系で設定
以上のように設定すると、そのモデルに紐づくモデルのインスタンスを取得するメソッド名をそのモデル名で定義してくれる。このメソッド名を変更したい場合に、このオプションを用いる。具体的に、メソッド名を変更する場面として、Model名のaliasを設定する場面や、モデルの一部のデータに新しく名前をつけ意味づけする場面が挙げられる。
例えば、「1対多」の関係のCategoryとArticleモデルで、Categoryに紐づくArticleモデルのインスタンスを取得するメソッド名をfeedsにしたい場合、以下のように記述する。
#Category.rb has_many :feeds, class_name: 'Article' #第一引数でメソッド名、class_nameオプションで紐づくモデル名を記述する #Article.rb belongs_to :category
これにより、
Category.where(id: 1).first.feeds
で、idが1のCategoryモデルのインスタンスに紐づくArticleモデルのインスタンスを取得できる。
また、上記の例で、belongs_toにclass_nameを指定した場合、
#Category.rb has_many :articles #Article.rb belongs_to :type, class_name: 'Category' #第一引数でメソッド名、class_nameオプションで紐づくモデル名を記述する
と、設定することで、
Article.where(id: 1).first.type
で、idが1のArticleモデルのインスタンスに紐づくCategoryモデルのインスタンスを取得できる。
この際、articlesテーブルのtype_idカラムを外部キーとして、Categoryモデルのインスタンスを取得している。
7.紐づくモデルのオブジェクトに対してバリデーションを実行しない
これには、:validateオプションを使う。
:validateはdefaultでtrueになっているので、これをfalseにすると、モデルに紐づくモデルのオブジェクトに対してのバリデーションを実行しなくなる。
#Category.rb has_many :feeds, validate: :false #Article.rb belongs_to :category
8.2階層以上離れているモデルのインスタンスを取得する
これには、:throughオプションを使う。
Categoryモデルに紐づくArticleモデルにさらに紐づくCommentモデルがある場合、以下のように記述することで、Categoryモデルに紐づくCommentモデルのインスタンスを取得できる。
#Category.rb has_many :articles has_many :comments, through: :articles #Article.rb belongs_to :category has_many :comments #Comment.rb belongs_to :article
注意すべき点として、throughオプションに引き渡されるシンボルが単数か、複数かということがある。
これは、どちらの場合もあり、2階層以上離れているモデルまでの間で、すべての関係が「1対1」の場合が単数で、それ以外の場合が複数となる。
9. 親モデルのオブジェクトがsaveされたとしても、loadされている子モデルのオブジェクトの変更はsaveされないようにする
これには、:autosaveオプションを使う。
ActiveRecordでは、defaultで親モデルのオブジェクトがsaveされると、loadされている子モデルのオブジェクトの変更も保存される。
:autosaveの値をfalseにすることで、親モデルのオブジェクトがsaveされたとしても、loadされている子モデルのオブジェクトの変更がsaveされないようにすることができる。
has_many :models, autosave: false
10. ひとつのモデル(A)から他のモデルを通して、もう一度モデル(A)のインスタンスを取得したとき、それが、もとのモデル(A)のインスタンスと等しいと設定する
これには、inverse_ofオプションを使う。
「1対多」の関係にあるCategoryモデルとArticleモデルを例にとると、
#Category.rb has_many :articles, inverse_of: :category #inverse_ofの値は、この場合Categoryモデルのインスタンスはひとつであるから、単数形 #Article.rb belongs_to :category, inverse_of: :articles #inverse_ofの値は、この場合Articleモデルのインスタンスは複数ある可能性があるから、複数形
と、することによって、本来inverse_ofオプションを指定しないと、
Category.where(id: 1) == Category.where(id: 1).first.articles.category #=> false(異なるインスタンスと認識される)
と、なるのに対し、
Category.where(id: 1) == Category.where(id: 1).first.articles.category #=> true(同一インスタンスと認識される)
と、することができる。
それでは、inverse_ofオプションを設定することには、一体どのような意味があるのだろう。それには、以下の二つのメリットがある。
1. 同一オブジェクトと認識されるので、メモリの使用量を減らすことができる
2. クエリー数を減らし、処理速度を上げることができる
また、inverse_ofオプションを設定する際に注意するべき点があり、それは、
1.:throughオプションと同時に設定できない
2.:as,:polymorphicオプションと同時に設定できない
である。
参照サイト:Active Record Associations — Ruby on Rails Guides
以上
Railsのモデルのリレーションについて
Railsで何か新しいapplicationを作る際やモデルを追加する際に、あらかじめ定義したモデル同士の関係をどのように記述するのだろうか。
今日は、その記述、すなわち、モデル間のリレーションの仕方について解説する。
モデル間のリレーションの3つのタイプ
モデル間のリレーションには以下の3つのタイプがある。
1.1対1
2.1対多
3.多対多
以下、この3つのリレーションのタイプについて順に説明するが、まず説明を簡単にするため、想定するモデルとその関係について述べる。モデルは、Category・Article・Comment・Commenter・Profileで、その関係は、Categoryが複数のArticlesを持ち、ArticleがひとつのCategoryに紐付き、複数のCommentsを持ち、CommentがひとつのArticleとCommenterに紐付き、Commenterが複数のCommentsとひとつのProfileを持ち、ProfileがひとつのCommenterに紐づくものとする。
1.1対1
モデル間を「1対1」の関係で結びたいときに使うメソッドは、has_one/belongs_toである。
今回想定するモデルでは、CommenterとProfileの関係がそれにあたる。
この、モデル間のリレーションは以下のように記述する。
#Commenter.rb class Commenter < ActiveRecord::Base has_one :profile end #一つのprofileを下位に紐付けるということ #Profile.rb class Profile < ActiveRecord::Base belongs_to :user end #一つのcommenterを上位に紐付けるということ
これにより、
Commenter.where(id: 1).first.profile
とすることで、idが1のCommenterモデルのインスタンスに紐づく、Profileモデルのインスタンスを取得できる。
2.1対多
モデル間を「1対多」の関係で結びたいときに使うメソッドは、has_many/belongs_toである。
今回想定するモデルでは、CategoryとArticle、ArticleとComment、CommenterとCommentの関係がそれにあたる。
これらのモデル間のリレーションは、CategoryとArticle間の関係を例にとると、以下のように記述する。
#Category.rb class Category < ActiveRecord::Base has_many :articles end #複数のarticlesを下位に紐付けている #Article.rb class Article < ActiveRecord::Base belongs_to :Category end #ひとつのcategoryを上位に紐づけている
これにより、
Category.where(id: 1).first.articles
とすることで、idが1のCategoryモデルのインスタンスに紐づく、複数のArticleモデルのインスタンスを取得できる。
3.多対多
モデル間を「多対多」の関係で結びたいときに使うメソッドは、has_many/throughである。
今回想定するモデルでは、CategoryとCommenter、ArticleとCommenterの関係がそれにあたる。
これらのモデル間のリレーションは、CategoryとCommenterの関係を例にとると、以下のように記述できる。
#Category.rb class Category < ActiveRecord::Base has_many :articles has_many :comments, through: articles has_many :commenter, -> { uniq }, through: comments end #Commenter.rb Class Comment < ActiveRecord::Base has_many : comments end
ここで、注意すべき点がひとつある。それは、-> { uniq } というオプションをどのような時に付けるのかということである。
-> { uniq } オプションを付けるのは、今回の場合、Categoryモデルにおいて、Commentモデルの複数のインスタンスを通して、Commenterモデルのインスタンスを取得するときに、同じcommenterが、同じarticleに複数のcommentをした場合、複数回同じCommenterモデルのインスタンスを取得してしまう可能性があるからである。このように、重複するインスタンスを取得してしまう可能性がある場合にのみ、その重複を解除するために、 -> { uniq } オプションを付ける必要がある。
これにより、
Category.where(id: 1).first.commenters
とすることで、idが1のCategoryモデルのインスタンスに紐づく、複数のCommenterモデルのインスタンスを、重複なく取得できる。
また補足として、「多対多」の関係でモデルを結ぶ場合は、クロステーブルが必要である。
以上
Rubyのシンボルを詳しくまとめてみた。
Rubyのコードを読んでいると、
{ 'key1' => 'value1', 'key2' => 'value2' }
{ :key1 => 'value1', :key2 => 'value2' }
{ key1: 'value1', key2: 'value2' }
のように、同じような記述が書かれていて、混乱することはないだろうか。
そもそも、2行目のkeyの前に付いている、:って何だ?って思う人もいるのではないか。
: が先頭についてる、単語(2行目の :key 等)をRubyでは、シンボルという。
シンボルの特徴
1.ハッシュのキーに使われる
シンボルは、ハッシュのキーによく使われる。
**キーにシンボルが使われているハッシュ**
{ :key1 => 'value1', :key2 => 'key2'}
そして、「キーにシンボルが使われているハッシュ」には、より簡単な記法があり、コードをより読みやすくすることができる。
**「キーがシンボルのハッシュ」の簡略記法**
#「キーがシンボルのハッシュ」と意味は全く同じ。 { hoge: 'hoge', fuga: 'fuga' }
2.文字列と違い、名前に一意性をもたらす
シンボルは、その名前に一意性を持たせることができる。すなわち同じ名前の場合、生成されるインスタンスは同じになる。
#同じ名前の文字列同士とシンボル同士から生成されるインスタンスを比較してみる hoge1 = "hoge" hoge2 = "hoge" hoge3 = :hoge hoge4 = :hoge #==は等値を表す論理演算子 puts hoge1 == hoge2 #=>true puts hoge3 == hoge4 #=>true #equal?は生成されたインスタンスが同じ場合、真を返す puts hoge1.equal?(hoge2) #=>false puts hoge3.equal?(hoge4) #=>true #同じ名前のシンボルのインスタンスが等しいことが分かる
また、プログラム内部で文字列を比較する場合、文字列に含まれる文字1文字単位で比較することになるが、シンボルを比較する場合、シンボル単位で比較することができる。シンボル単位での比較は、シンボルクラスのオブジェクトとして、振り分けられたobject_idをもとに行われる。
#同じ名前の文字列同士とシンボル同士のobject_idを比較してみる puts 'hoge'.object_id #=> 70261881731340 puts 'hoge'.object_id #=> 70261881731280 puts :hoge.object_id #=> 405448 puts :hoge.object_id #=> 405448 #同じ名前のシンボル同士はobject_idが等しいことが分かる
これによって、文字列同士の比較に比べ、シンボル同士の比較は格段にスピードが上がる。なぜなら、上記のhogeで比較するとき、文字列では、'h','o','g','e'と、4文字について比較しなければならないのに対し、シンボルでは、object_id(405448)のみを比較すればいいからである。
それでは、本文字列とシンボルではどれほど処理速度が変わってくるのだろうか。
実際に、調べてみた。
#キーが文字列のハッシュの値ひとつを1万回取得する時間 hash = {'key' => 'value'} start_time = Time.now 10000.times do |i| hash['key'] end end_time = Time.now hash_string = end_time - start_time puts hash_string #=> 0.002935 #キーがシンボルのハッシュの値ひとつを1万回取得する時間 hash = {:key => 'value'} start_time = Time.now 10000.times do |i| hash[:key] end end_time = Time.now hash_symbol = end_time - start_time puts hash_symbol #=> 0.000874 #キーがシンボルの場合の方が、3倍以上取得時間が速いことがわかる
さらに処理するデータが大きくなれば、それだけ処理速度にも差が出てくるのではないかと思い、調べてみた。
#100万個の異なる文字列のkey,とvalueのハッシュを並列に内包するハッシュから、ひとつのkeyのvalueにアクセスする時間 hash = {} 1000000.times do |i| hash.store('imp' + i.to_s, 'value' + i.to_s) end hash_string = [] 10.times do start_time = Time.now 100000.times do hash['imp1'] end end_time = Time.now hash_string.push end_time - start_time end puts hash_string.reduce(:+)/10 #=>0.0346474 #100万個の異なるシンボルのkey,とvalueのハッシュを並列に内包するハッシュから、ひとつのkeyのvalueにアクセスする時間 hash = {} 1000000.times do |i| hash.store(('imp' + i.to_s).intern, 'value' + i.to_s) end hash_symbol = [] 10.times do start_time = Time.now 100000.times do hash[:imp1] end end_time = Time.now hash_symbol.push end_time - start_time end puts hash_symbol.reduce(:+)/10 #=>0.0102569
処理速度は文字列よりシンボルを使った方が3倍に上がった。したがって、大きなデータを扱う場合も、シンボルをキーとする方が、処理速度が速いといえるだろう。
以上