valid,invalid

関心を持てる事柄について

Rubyでemojiとcodepointsの変換

emoji から codepoints に変換

"👍".unpack("U*")
=> [128077]

"👍".codepoints
=> [128077]

# Convert to hexadecimal
"👍".each_codepoint.map {|n| n.to_s(16) }
=> ["1f44d"]

codepoints から emoji に変換

[128077].pack("U*")
=> "👍"

0x1f44d.chr('UTF-8')
=> "👍"

"\u{1f44d}"
=> "👍"

環境

Ruby 3.0.0

rubocopのNaming/MethodParameterName, Naming/BlockParameterNameで警告の指摘箇所がずれているのを修正してみた

ActiveRecord Association Extensionとwith_optionsを併用するとrubocop-railsのRails/HasManyOrHasOneDependent警告が出るので修正してみた - valid,invalid でrubocopの中身に少し興味を持ったのでシュッと倒せそうなissueがないかを拙作の goofi で検索してみたところ、過去に遭遇したことあるバグを見かけたので対応してpull requestを送ってみた。

事象

issueはこれ Faulty calculation in UncommunicativeName · Issue #8229 · rubocop-hq/rubocop · GitHub

Naming/MethodParameterName, Naming/BlockParameterName copsで警告する対象に _*といったprefixが付いていると警告の表示位置がずれる…、実際の挙動を見たほうがわかりやすい。

以下のファイルがあるとする。

def test1(_a); end

def test2(__a); end

def test3(*a); end

def test4(**a); end

def test5(**__a); end

rubocopを実行すると、警告箇所を示すlocation ^ が引数名の全体にかかっていないことがわかる。

$ rubocop test.rb
Inspecting 1 file
C

Offenses:

test.rb:3:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test1(_a); end
          ^
test.rb:5:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test2(__a); end
          ^
test.rb:7:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test3(*a); end
          ^
test.rb:9:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test4(**a); end
          ^
test.rb:11:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test5(**__a); end
          ^

1 file inspected, 5 offenses detected

本来はこうあるべき。

$ rubocop test.rb
Inspecting 1 file
C

Offenses:

test.rb:3:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test1(_a); end
          ^^
test.rb:5:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test2(__a); end
          ^^^
test.rb:7:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test3(*a); end
          ^^
test.rb:9:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test4(**a); end
          ^^^
test.rb:11:11: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long.
def test5(**__a); end
          ^^^^^

1 file inspected, 5 offenses detected

修正

Naming/MethodParameterName, Naming/BlockParameterName copsでincludeしているUncommunicativeName moduleにて、引数名の長さの計算が誤っていたようだ。

def check(node, args)
  args.each do |arg|
    # Argument names might be "_" or prefixed with "_" to indicate they
    # are unused. Trim away this prefix and only analyse the basename.
    name_child = arg.children.first
    next if name_child.nil?
    full_name = name_child.to_s
    next if full_name == '_'
    name = full_name.gsub(/\A(_+)/, '')
    next if allowed_names.include?(name)

    # name.size が location の range を決めている
    # name は prefix を除いた文字列
    range = arg_range(arg, name.size)
    issue_offenses(node, range, name)
  end
end

lengthを愚直に再計算して渡すようにした。

def check(node, args)
  args.each do |arg|
    # Argument names might be "_" or prefixed with "_" to indicate they
    # are unused. Trim away this prefix and only analyse the basename.
    name_child = arg.children.first
    next if name_child.nil?
    full_name = name_child.to_s
    next if full_name == '_'
    name = full_name.gsub(/\A(_+)/, '')
    next if allowed_names.include?(name)

-   range = arg_range(arg, name.size)
+   length = full_name.size
+   length += 1 if arg.restarg_type?
+   length += 2 if arg.kwrestarg_type?

+   range = arg_range(arg, length)
    issue_offenses(node, range, name)
  end
end

rubocopへのpull requestは初めてだったがシュッとマージしてもらえて良かった。

環境

  • rubocop 1.8.0

This article is for ohbarye Advent Calendar 2020.

ActiveRecord Association Extensionとwith_optionsを併用するとrubocop-railsのRails/HasManyOrHasOneDependent警告が出るので修正してみた

ActiveRecord Association extensionsでメソッドを追加する - valid,invalid で書いたActiveRecord Association Extensionだが、with_optionsと併用するとrubocop-railsRails/HasManyOrHasOneDependent copに警告されることがわかった。

with_options dependent: :destroy do
  has_many :foo do
  end
end
with_options dependent: :destroy do
  has_many :foo do
  ^^^^^^^^ Specify a `:dependent` option.
  end
end

原因調査

disableしてもよいのだがどのような原理で警告が出るのか気になったので調べて修正してみた。

github.com

対象のcopの実装を読みつつprint debugすると、Association Extensionのblockの存在有無によってS式の構造が変わることが原因のようだった。

まず、警告が出ないblockなしのケース。

with_options dependent: :destroy do
  has_many :foo
end
s(:block,                      # <= node.parent
  s(:send, nil, :with_options,
    s(:hash,
      s(:pair,
        s(:sym, :dependent),
        s(:sym, :destroy)))),
  s(:args),
  s(:send, nil, :has_many,     # <= node
    s(:sym, :foo))))

has_manyに対して:dependent optionが渡されるかどうかはnode.parentのnodeからwith_optionsを辿れば判断できる。

一方、blockありのケース。

with_options dependent: :destroy do
  has_many :foo do
  end
end

node.parentのnode下にはwith_options blockがない。

s(:block,                      # <= node.parent.parent
  s(:send, nil, :with_options,
    s(:hash,
      s(:pair,
        s(:sym, :dependent),
        s(:sym, :destroy)))),
  s(:args),
  s(:block,                    # <= node.parent
    s(:send, nil, :has_many,   # <= node
      s(:sym, :foo)),
    s(:args), nil)))

with_optionsの引数を参照するにはnode.parent.parentから辿らなければいけないのだが、node.parentが検査対象となってしまう。

# https://github.com/rubocop-hq/rubocop-rails/blob/9808efdb80078f1382da7cab8fe0b6a1917af047/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb#L64-L70
def valid_options_in_with_options_block?(node)
  return true unless node.parent

  n = node.parent.begin_type? ? node.parent.parent : node.parent

  contain_valid_options_in_with_options_block?(n)
end

修正

block付きの場合のS式にmatchしたらnode.parent.parentを参照して検証させるようにした。

+       def_node_matcher :association_extension_block?, <<~PATTERN
+         (block
+           (send nil? :has_many _)
+           (args) ...)
+       PATTERN

        def valid_options_in_with_options_block?(node)
          return true unless node.parent

-         n = node.parent.begin_type? ? node.parent.parent : node.parent
+         n = node.parent.begin_type? || association_extension_block?(node.parent) ? node.parent.parent : node.parent

          contain_valid_options_in_with_options_block?(n)
        end

rubocop-railsへの初pull requestであり雰囲気で書いているのでacceptされるかはわからない。

(2020-01-13 追記) mergeされた!

これまでrubocopの中身をちゃんと見たことがなかったので学びがあってよかった。

環境

  • rubocop 1.8.0
  • rubocop-rails 2.9.1

This article is for ohbarye Advent Calendar 2020.

GitHubのメール通知先をorganization単位で分ける

長年 GitHub の仕事関連の通知と個人の趣味でウォッチしている organization / repository / issue の通知を分けられないものかと思っていたが、あっさり解決した。

https://github.com/settings/notifications (Settings -> Notifications) の Custom routing から organization 単位で通知を分けられるようだ。

docs.github.com

個人的に実現したいことは以下の設定でなんとかなりそうだ。

  • Default notification email を個人のメールアドレスにする
  • 仕事関連の organization の通知先を Custom routing で社用メールアドレスにする

メール通知なので https://github.com/settings/emails (Settings -> Emails) を何度か見た気がするが、そこで設定できなかったのでできないと思いこんでいた〜

My favorite dancers in 2020

『ワンダンス』の影響もあり、YouTubeでよくダンスバトルの動画を見ている

あまり真剣なファンではないしシーンの現状もよくわかっていないのだけど、3年前に少しだけダンススクールに通っていたこともあり少しだけジャンルやテクニックの知識があるのも楽しめる要因かもしれない

あとバトルで使われるような"ビート"の強い音楽がシンプルに好きというのもある

Bouboo

フランス

HIP-HOPメインで滑らかでしなやかな動きとグラウンドの大胆な使い方が好き

Poppin'ぽいジャンルのときのキレもすごくて、たとえばこのバトルのムーヴ。2:20から銃声までの流れはいったいどうやったら狙えるんだ…

www.youtube.com

伝説の"七人抜き"は流れに完全にノッていて相手のいろんなジャンルをなぎ倒すところが見られる

www.youtube.com

この中国の番組、技名がダンスゲームやペルソナみたいにオシャレ表示されて面白い

www.youtube.com

Salah

フランス

フランスの英雄にして世界最強のストリートダンサー

とのこと。アニメーションやLOCK中心だが何でもできそう

ダンサーよりも"パフォーマー"としてのセンスが爆発しているバトル。KANNONの動けるデブ感もすごい好きなんだけど完全に"喰"ってる

www.youtube.com

5:00のムーヴ、完全にLes Twinsと"会話"してるんだよな〜

www.youtube.com

ちなみにLes TwinsはBoubooと3人でCriminalz Crewというチームをやっている

UKAY

UKAYだけどドイツ

パワームーヴもあり、シンプルなステップでも軽やかさと動きのキレがあり、なんか忍者ぽい。

www.youtube.com

2:10からのムーヴで湧きすぎて観客がカメラを遮ってしまい、ダンスが何も見えなくなっているのウケる

www.youtube.com

Poppin Jun & Phoenix lil'Mini

Poppin'

センスも表現力もあるキッズが大人とガチでやりあってるのめちゃ良いな

www.youtube.com

超ハイレベルなキッズ同士でやりあうのも良いな

www.youtube.com


全体的にめっちゃ"密"、ゆえに盛り上がる感じがCOVID-19以前の風景というもののあわれがして切ない。

こんなんだがohbarye Advent Calendar 2020の記事とする

VSCodeのimport-cost extensionでimportするライブラリのサイズをチェックする

import / require文で読み込む3rd party libraryのコストを表示してくれるVSCode extension。3年も前からあったのにまったく知らなかったな〜

marketplace.visualstudio.com

完璧な分析ツールを目指しているわけではなく「開発中に明らかな問題に気づくためのものが必要(webpack-bundle-analyzerなどは素晴らしいけど見落とされがちなので)」というモチベーション。

asset bundleを分析するならもっと良いツールを使ってくれとのこと。

Vim pluginもあるらしい。

GitHub - yardnsm/vim-import-cost: 🏋️‍♂️ Display the import size of the JavaScript packages in Vim!

仕組み

どうやって計算しているのかなと思ったら作者によって解説されていた。

citw.medium.com

  • ファイルの変更を検知する
  • Typescript / Babylon AST parserで有効なimport/require文のリストを得る
  • 3rd partyライブラリに関するimport or require の行をtmp fileとして書き出す
  • このtmp fileをentrypointとしてWebpackを走らせる
  • compile結果のstatsからサイズを取得して表示する

コードはこのへん

面白い!

High CPU Load問題

僕はまだ遭遇していないのだけど、依存しているbabel-minify-webpack-pluginのためにCPU使用率が高くなる問題が報告されているのはちょっと気になる。このplugin自体はすでにdeprecatedなのでそのうちterser-webpack-pluginに移行されるかもしれないが、放置され気味なのでどうだろう…。


This article corresponds to the 8th day of ohbarye Advent Calendar 2020.

ActiveRecord Association extensionsでメソッドを追加する

has_many*1にblockを渡すとassociationにメソッドを追加することができる。

class User < ActiveRecord::Base
  has_many :posts do
    def stats
      group(:status).count
      # このcontextで`self`は`ActiveRecord::Associations::CollectionProxy`
      # `proxy_association.owner`で`user.posts`の`user`にアクセスすることもできる
    end
  end
end

user.posts.stats
# => {'draft'=>3,'published'=>42}

内部的にはblockがModule.new(&block)されextend optionに渡される。なので始めからextend optionとして渡してやってもよい。

module StatsCounter
  def stats
    group(:status).count
  end
end

class User < ActiveRecord::Base
  has_many :posts, extend: StatsCounter
  # has_many :posts, extend: [StatsCounter] でもよい
end

user.posts.stats
# => {'draft'=>3,'published'=>42}

ちなみにblockを渡した際にnewされたModuleActiveRecord::Associations::Builder.define_extensionsによってUser classにUser::PostsAssociationExtensionのような名前でconst_setされる*2

使いどころを考える

子テーブルの絞り込みを行うfinderを書くなら子テーブルのほうのモデルのscopeでよいし、何を書いても子テーブル側の詳細や知識が親テーブル側のモデルに漏れている感じがする。

親テーブルから辿ったときにしか使わせたくない、つまり対象となるassociationの一部としてのみ扱いたいロジックを書きたいときが使いどころといえそう。

公式ドキュメントではファクトリーメソッドのような例が挙げられている。

class Account < ActiveRecord::Base
  has_many :people do
    def find_or_create_by_name(name)
      first_name, last_name = name.split(" ", 2)
      find_or_create_by(first_name: first_name, last_name: last_name)
    end
  end
end

person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name  # => "Heinemeier Hansson"

うーん、上記の例でもPeople classに書いたほうが良さそうな気もする


This article corresponds to the 7th day of ohbarye Advent Calendar 2020.