es2015の分割代入は、rubyのキーワード引数のイメージ
わかりづれー!ってなっていました。
{}
←これ何?って感じで意味不明に陥りやすかったです。
function foo({x, y}) { return x + y } console.log(foo({x: 1, y: 2})); =>3
x, yのvalueが代入されている。
これってrubyのキーワード引数と同じっぽく感じる。
def foo(x:, y:) x + y end foo(x: 1, y: 2) =>3
それだけです。
新しい記法は慣れるのに時間がかかる。
なおかつ、それをほとんど使用しない身としては、とても覚えづらいけど、似た様なものと関連付ければ覚えやすくもなる。
has_manyのhas_manyのcountで効率よくパフォーマンスを出す
counter_cacheはできるだけ使いたくないんですよ。
無駄なカラムを持ちたくないということで、頑張りました。
対策としては、最初で全てのcountを手にするのが一番いいですね。
countは重たいので、先に何とかして値を入手したい・・・。
今回のサンプルでは、Blogがあって、Commentをたくさん持っていて、さらにCommentに対して複数のReactionをつけれるようなものです。 それのgroup化したReactionの数が欲しいという状況でした。
サンプル
class Blog < ActiveRecord::Base has_many :comments, dependent: :destroy has_many :comment_reactions, through: :comments def comment_reactions_count_map comment_reactions.group(:comment_id, :reaction_id).count←これが今回の肝 => {[1, 1]=>7, [1, 315]=>1, [1, 332]=>1, [1, 708]=>1, [1, 825]=>1, [1, 864]=>1, [1, 1038]=>1, [1, 1044]=>1, [1, 1182]=>1, [1, 1229]=>1, [1, 1288]=>1, [2, 57]=>1, [2, 82]=>1, [2, 220]=>1, end end
class Comment < ActiveRecord::Base belongs_to :blog has_many :comment_reactions has_many :reactions, through: :comment_reactions end
class CommentReaction < ActiveRecord::Base belongs_to :comment belongs_to :reaction end
class Reaction < ActiveRecord::Base has_many :comment_reactions has_many :comments, through: :comment_reactions end
controller側
def show @blog = Blog.find(params[:id]) @count_map = @blog.comment_reactions_count_map←これでIDをセットする。 end
view側はこんな感じになりますね。
サンプルなんで、適当です。
show.html.slim
= render partial: "blog/comments", collection: @blog.comments.includes(:reactions), as: :comment
_blog/comments.html.slim
- if comment.reactions.present? - comment.reactions.group(:id).each do |reaction| = image_tag reaction.image.url = @count_map[[comment.id, reaction.id]]←これでcountの値をもらう
こんな感じでどうでしょうか。
パフォーマンスはexplainでコツコツ攻略するのも大事ですが、可能ならば、sqlのクエリを減らすのがいいですね。
ES2015のIteratorとGeneratorについて
最近はVue.jsを個人で使用しているので、javascriptについても学んでいます。
ES2015の機能を少しづつ勉強しています。
基礎能力がないと結局開発の仕方がわからないなってのがあるので、勉強がてらに記事を書きます。
Iteratorって何?
そもそもIterator
ってなんぞや?って感じですよね。
プログラミング言語的には、反復子
と呼ばれているようです。
反復するためのものの意味で反復子(はんぷくし)と訳される。繰返子(くりかえし)という一般的ではない訳語もある。
繰り返されるものに対して、呼ばれるようですね。
反復子(反復可能なオブジェクト)に対して、javascriptはfor..of
を使って処理ができるようです。
ということは・・・
配列
配列は反復可能なオブジェクト
になります。
const programming_languages = ["javascript", "ruby", "php"] for (let language of programming_languages) { console.log(language) } =>javascript =>ruby =>php
なるほど。
ただし、Iteratorオブジェクト
ではありません。
Iterator
はメソッドのnext
を使用することができます。
Iteratorのプロトコル
Iteratorであるためには、「Iteratorプロトコル」を実装する必要があります。
Symbol.iterator()
const programming_languages = ["javascript", "ruby", "php"] const it = programming_languages[Symbol.iterator]() console.log(it.next()) =>Object {value: "javascript", done: false} console.log(it.next()) =>Object {value: "ruby", done: false} console.log(it.next()) =>Object {value: "php", done: false} console.log(it.next()) =>Object {value: undefined, done: true}
done
の真偽値で次があるかどうかがわかるんですね。
独自のIteratorを作成する
Logクラスを作成して、試してみます。
class Log { constructor() { this.messages = [] } add(message) { const now = Date.now(); console.log(`ログ追加:${message}(${now})`) return this.messages.push({ message, time: now }) } [Symbol.iterator]() { let i = 0 const messages = this.messages return { next: () => i >= messages.length ? { value: undefined, done: true } : { value: messages[i++], done: false } } } } const log = new Log() log.add("foo") log.add("bar") log.add("baz") console.log(log) for (let l of log) { console.log(l) =>Object {message: "foo", time: 1487771847145} =>Object {message: "bar", time: 1487771847146} =>Object {message: "baz", time: 1487771847147} }
Iteratorを返すだけなら、既存のメソッドを使用した方が楽なので、既存のメソッドで書き換えます。
[Symbol.iterator]() { return this.messages[Symbol.iterator]() }
結果は同じになります。
Generatorについて
ここでGeneratorが出てきます。
Generatorってなんぞや?って感じですよね。
関数の一種と捉えることができますが、通常の関数とは違います。
- 関数は制御(と値)を任意の場所から呼び出し側に戻すことができる
- Generatorを呼び出すときにはすぐには実行されず、まずはIteratorが戻される。そのあとで、Iteratorのメソッドnextを呼び出す度に実行が進む。
意味がわからないって感じなので、実際のコードと動きを見ていきます。
定義方法
Generatorを使用するにはfunctionの後に*
を付けます。
呼び出し側はyield
を使用します。
rubyでもあるのに、ここでもあるのか!って思いました。
rubyの場合はブロックを返してくれるやつでしたね。
function* frame_work() { yield 'ruby on rails' yield 'vue' yield 'react' } const it = frame_work() console.log(it.next()) =>Object {value: "ruby on rails", done: false} console.log(it.next()) =>Object {value: "vue", done: false} console.log(it.next()) =>Object {value: "react", done: false} console.log(it.next()) =>Object {value: undefined, done: true}
確かにIteratorオブジェクトになっとる。
双方向コミュニケーション
yieldは式なので、評価の結果、何らかの値になります。
では、どんな値なのでしょうか。
next呼び出し時の引数の値になります。
どういうことだ?ってことで例をみます。
function* introduction() { const name = yield '名前は何ですか?' const hobby = yield '趣味は何ですか?' return `名前は${name}です。趣味は${hobby}です。` } const it = introduction() console.log(it.next()) =>Object {value: "名前は何ですか?", done: false} console.log(it.next('javascript')) =>Object {value: "趣味は何ですか?", done: false} console.log(it.next('プログラミング')) Object {value: "名前はjavascriptです。趣味はプログラミングです。", done: true}
一個前の値を保持していますね。
この辺の遅延評価を利用して、非同期処理を行えるようですね。
どうやるんじゃろうか。
以上です。
フックメソッドについて
特定のタイミングで実行されるものです。
意味不明だと思うので、解説します。
included
includeではありません、included
です。
なんとなく、見覚えがあったのですが、違いを全く理解していませんでした。
includedメソッドは、includeメソッドによってモジュールが他のモジュールやクラスにインクルードされたあとに呼び出されます。引数にはモジュールをインクルードするクラスやモジュールが入ります。
どういうことか、コードで説明します。
module Foo def self.included(klass) puts "#{klass}にincludeされました" end end class Bar include Foo end =>Barにincludeされました
includeしたタイミングで上のputsが反応しています。
これを利用して、特異メソッド化する手法がよく使われます。
module Foo def self.included(klass) puts "#{klass}にincludeされました" klass.extend Methods end module Methods def foo 'foo' end end end class Bar include Foo end Bar.foo => "foo"
extendでMethodsをBarのみに使用できるようにしています。 これでincludeした場合に特異メソッド化ができます。
included
は単なるフックメソッドとして、使用されており、inlucdeとは全く違うものでした。
ここの理解を全くしていなかったな。
特異メソッドでincludeする方法
何個か方法あるので、少しづつ紹介します。
クラス内でincludeする
module Foo def hello 'hello' end end class Bar class << self include Foo end end Bar.hello => "hello"
特定のオブジェクトだけincludeする
x = Bar.new class << x include Foo end x.hello => "hello"
Object#extend
これが一番一般的ですね。
y = Bar.new y.extend Foo y.hello => "hello"
方法としては、この3つあります。
意外にextendの用法を忘れてしまう・・・orz
クラス変数の注意点
クラス変数なんて、めったに使うことなんてないし、rails
で使うならclass_attribute
があるんで、普通は必要ないです。
しかし、よくクラス変数やめろ!なんて言われることが多いと思うので、ここでおさらいしておきます。
クラス変数とは
そもそもクラス変数ってなんぞや?
クラス間で共有出来る変数ですね。
例えば、下記のようなものです。
class Foo @@x = 1 def x @@x end def x=value @@x = value end end pry(main)> a = Foo.new => #<Foo:0x007f89982500e8> pry(main)> a.x = 3 => 3 pry(main)> b = Foo.new => #<Foo:0x007f8998343978> pry(main)> b.x = 5 => 5 pry(main)> a.x => 5
確かに共有されますね。
ただし、厄介なことがあります。
- サブクラスにも継承される
- クラス階層に属している
前者はわかりやすいと思うので、後者について説明します。
先程の続きです。
pry(main)> @@x = 6 (pry):17: warning: class variable access from toplevel => 6 pry(main)> b.x => 6
上記のように、mainクラスの時に変更したものも値が変更されてしまいます。
こういう罠が多いので、クラス変数は使うなってことになります。
けど、どうしてもクラス間で共有したいって時があります。
そういう場合はどうすればいいのかってことです。
あくまでもrails側の方法です。
class_attribute
railsではclass_attributeがあります。
これは、先程のサブクラスの継承を無しにして、なおかつトップレベルのものは参照されなくなります。
なので、railsでクラス間の値を共有したい場合は、class_attribute
を使いましょう。
以上です。
mysqlの予約語に気をつけた方がいい
やられました...orz
直接sqlをいじることがなかったのですが、いじった時に気付きました。
mysql> show tables; +-----------------------------------+ | Tables_in_foo | +-----------------------------------+ | categories | | comments | | rankings | | reads | | schema_migrations | | supplementals | | taggings | | tags | | users | +-----------------------------------+
こんな感じで表示されています。
ここでreads
テーブルのデータを取得しようとします。
mysql> select * from reads; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'reads' at line 1
なぜ、syntaxエラーになる???
おかしいだろ!ってなりました。
テーブルはきちんと作成されているのか?
reads.frm reads.ibd
二つともきちんとある。
rails側では値が取得できている。
[1] pry(main)> Read.count (0.3ms) SELECT COUNT(*) FROM `reads` => 0
わからん・・・。
試しにデータを投入しても、無事に投入できる。
ここで予約語っていう概念か?と思い調べました。
そうしたらREAD/READS
が見つかりました。
ああ、そういうことかーってなりました。
予約語を使用する場合はバッククォート
でエスケープすればいいです。
今思うと、上のrailsのsqlはきちんとバッククォート
でエスケープしています。
今まで気にしなかったですが、予約語対策をしていたんですね。
cpuの情報を見る方法(mac/linux)
lscpu
でいけるんじゃない?って思っていたのでしたが、macは違いました。
mac
system_profiler SPHardwareDataType
Hardware Overview: Model Name: MacBook Pro Model Identifier: MacBookPro11,1 Processor Name: Intel Core i5 Processor Speed: 2.4 GHz Number of Processors: 1 Total Number of Cores: 2 L2 Cache (per Core): 256 KB L3 Cache: 3 MB Memory: 8 GB ....
linux
lscpu
mikami@ik1-324-22232:~$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 2 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 45 Model name: Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz Stepping: 7 CPU MHz: 2499.998 BogoMIPS: 4999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32K L1i cache: 32K L2 cache: 4096K NUMA node0 CPU(s): 0,1 mikami@ik1-324-22232:~$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 2 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 45 Model name: Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz Stepping: 7 CPU MHz: 2499.998 BogoMIPS: 4999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32K L1i cache: 32K L2 cache: 4096K NUMA node0 CPU(s): 0,1
linuxの方がいっぱい情報あるんで、こっちの方がいいな・・・って思いました。
ちなみにsystem_profiler
は他の引数もあるので、それを渡すことで色々見れそうな感じがしました。
macのmysqlをupdateしたら、Library not loaded: /usr/local/opt/mysql56/lib/libmysqlclient.18.dylibって出るようになった
railsのmysqlでemojiを使う方法
emojiを使おうと思ったら、色々と問題があるようなので、まとめます。
文字コード問題
まず、emojiはmysqlのutf8では使用できません。 mysqlでemojiを使用するにはutf8mb4にする必要があります。
これは何をしようとしているのかいうと、emojiの文字コードは1文字に4バイト使用します。 通常のutf8では、1文字に対して3バイトまでしか使用できません。 なので、mysqlで保存するためには、文字コードを変更する必要があります。
collation問題
こちらは初期設定のutf8mb4の設定でいくと、寿司ビール(🍣🍺)問題になります。 🍣と🍺が同じものとして扱われます。 こちらを別々に検索できるようにするための設定が必要になります。
これに関連してハハパパ問題というのがあります。
utf8でもきちんと設定しておかないと、はまったりします。
ハハパパ問題(参考)
ハハパパ問題は、「ハハ」「パパ」が同一の値として扱われます。 そんなことがあるのか!って思いましたが、railsのmysqlではdefaultでこの設定になっています。 何も知らないと検索結果おかしい・・・みたいな罠にはまります。 では、どのようなcollationがあるかを見ていきます。
- utf8_bin
- utf8_general_ci
- utf8_unicode_ci
utf8_bin 文字コードが完全に一致するもののみ返す 一番厳しいですね
utf8_general_ci アルファベットの大文字・小文字は区別しない 他は全て区別する
utf8_unicode_ci 大文字小文字/全角半角を区別しない。
「あああ」は「アアア」「ァァァ」「ぁぁぁ」にマッチする。
railsはdefaultでutf8_unicode_ciになっています。 なので、この設定は変更しましょう。
show full columns from topics;
でcollationの確認をすることができます。
mysql> show full columns from topics; +------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+ | Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment | +------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+ | id | int(11) | NULL | NO | PRI | NULL | auto_increment | select,insert,update,references | | | title | varchar(255) | utf8_unicode_ci | NO | | NULL | | select,insert,update,references | | | body | text | utf8_unicode_ci | NO | | NULL | | select,insert,update,references | | | name | varchar(255) | utf8_unicode_ci | NO | | NULL | | select,insert,update,references | | | category_id | int(11) | NULL | NO | MUL | NULL | | select,insert,update,references | | | thumbnail_updated_at | datetime | NULL | YES | | NULL | | select,insert,update,references | | | thumbnail_file_size | int(11) | NULL | YES | | NULL | | select,insert,update,references | | | thumbnail_content_type | varchar(255) | utf8_unicode_ci | YES | | NULL | | select,insert,update,references | | | thumbnail_file_name | varchar(255) | utf8_unicode_ci | YES | | NULL | | select,insert,update,references | | | created_at | datetime | NULL | NO | | NULL | | select,insert,update,references | | | updated_at | datetime | NULL | NO | | NULL | | select,insert,update,references | | +------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+ 11 rows in set (0.00 sec)
何もしていないと、無事にutf8_unicode_ci
になって、ウワって結果になると思います。
default: &default adapter: mysql2 collation: utf8_general_ci
これは初期設定でやるべきことですね。
もし、運用フェーズで気づいたなら、mysql側で変更しましょう。
index問題
最大のインデックス長が757バイトになっています。 utf8では255文字で、utf8mb4では191文字になります。
解決方法としては、二つあります。
- インデックス長を増やす
- インデックス長を減らす
減らす方法はデフォルトの動きではならなくなるので、インデックス長を増やす方が良いです。
mysql側の設定
my.cnfを設定します。
[mysqld]
innodb_file_per_table
innodb_file_format=barracuda
innodb_large_prefix = 1
rails側の設定
database.ymlの設定
default: &default adapter: mysql2 charset: utf8mb4 encoding: utf8mb4 collation: utf8mb4_bin pool: 5 timeout: 5000
絵文字を区別したいのであれば、collationの設定をutf8mb4_bin
にしないといけません。
区別する必要がないのであれば、utf8mb4_general_ci
で大丈夫です。
ただし、このままだとmigrationの設定がうまくいかないので、挙動を変更するようにします。
config/initializers/ar_innodb_row_format.rb
ActiveSupport.on_load :active_record do module ActiveRecord::ConnectionAdapters class AbstractMysqlAdapter def create_table_with_innodb_row_format(table_name, options = {}) table_options = options.merge(:options => 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC') create_table_without_innodb_row_format(table_name, table_options) do |td| yield td if block_given? end end alias_method_chain :create_table, :innodb_row_format end end end
以上です。
参考