フロントエンドでJavaScriptを採用してバックエンドをRubyで書いていると、snake_case🐍とcamelCase🐫を行ったり来たりして、混乱したりするのでRubyでcamelCase🐫で書けたりしないのか、ちょっと試してみたのでMEMO📝
記事内のコードはこちらにあげています🐫💎
やりたいこと
自分で生やすメソッドに関しては単純にcamelCase🐫で書けばいいだけなんですが、
class Book def initialize(name, publishedAt) @name = name @publishedAt = publishedAt end attr_reader :name, :publishedAt end book = Book.new('foo', Time.now) p book.name p book.publishedAt
ネックなのは組み込みライブラリのメソッドはsnake_case🐍なので、単純に書くと🐍と🐫が混在してしまうのが辛いところですね😓
class Book def initialize(name, publishedAt) @name = name @publishedAt = publishedAt end attr_reader :name, :publishedAt end book = Book.new('foo', Time.now) p book.name p book.publishedAt.to_s
標準ライブラリのメソッドも含めてcamelCase🐫で書けないかと思った次第です。
ちなみにrubocopでcamelCaseのstyleを矯正することもできます👮
GithubでcamelCaseのルールを採用しているコードが無いか、検索してみたところ殆どないけど、全く無いわけではないっぽい👀
https://github.com/search?l=YAML&q=EnforcedStyle%3A+camelCase&type=Code
method_missingを使って無理やり🐫で書いてみる
以下のようなmethod_missing
をオーバーライドして、method.to_s.underscore.to_sym
を実行して🐫→🐍してからpublic_send
してメソッドを実行するようなActiveSupport::Concern
をextendしたmodule CameRuby
を用意してみました。
require 'active_support/all' module CameRuby module MethodMissing extend ActiveSupport::Concern module ClassMethods def method_missing(method, *args) snaky_name = method.to_s.underscore.to_sym if public_methods.include?(snaky_name) public_send(snaky_name, *args) else super end end def respond_to_missing?(method, include_private) snaky_name = method.to_s.underscore.to_sym public_methods.include?(snaky_name) ? true : super end end def method_missing(method, *args) snaky_name = method.to_s.underscore.to_sym if public_methods.include?(snaky_name) self.public_send(snaky_name, *args) else super end end def respond_to_missing?(method, include_private) snaky_name = method.to_s.underscore.to_sym public_methods.include?(snaky_name) ? true : super end end end
これをObjectクラスにincludeして使うと以下のように🐫で書くことが出来ます。
require_relative './lib/cameRuby/methodMissing' class Object include CameRuby::MethodMissing end class Book def initialize(name, publishedAt) @name = name @publishedAt = publishedAt end attr_reader :name, :publishedAt end book = Book.new('foo', Time.now) p book.name #=> "foo" p book.publishedAt.toS #=> "2020-11-14 14:00:54 +0900"
alias_method
を使って🐫でも呼び出し可能にする
以下のようなObjectSpace.each_object(Class)
を使ってClassを継承している全Objectを取得し、method_name.to_s.camelize(:lower).to_sym
を実行して🐍→🐫をしてからalias_method
で🐫で呼び出し可能にするような処理をCameRuby::AliasMethod.call
で呼び出せるようにして、
# fornzen_string_literal: true require 'active_support/all' module CameRuby class AliasMethod def self.call ObjectSpace.each_object(Class) do |klass| klass.instance_methods.each do |method_name| camelized_name = method_name.to_s.camelize(:lower).to_sym next if camelized_name == method_name klass.instance_eval do alias_method(camelized_name, method_name) end rescue FrozenError => err next end end end end end
以下のように呼び出すと、🐫で書くことが出来ます。
require_relative './lib/cameRuby/aliasMethod' CameRuby::AliasMethod.call puts 'foo'.respond_to?(:toS, false) puts 'foo'.toS puts String.respond_to?(:tryConvert, false) puts String.tryConvert('str') class Book def initialize(name, publishedAt) @name = name @publishedAt = publishedAt end attr_reader :name, :publishedAt end book = Book.new('foo', Time.now) p book.name #=> "foo" p book.publishedAt.toS #=> "2020-11-14 14:23:24 +0900"
ベンチマーク
method_missingを使う方法
大分パフォーマンスへの影響が大きい😓
require_relative './lib/cameRuby/methodMissing' class Object include CameRuby::MethodMissing end n = 100000 Benchmark.bm(7, ">total:", ">avg:") do |x| x.report("to_s:") { "foo".to_s } x.report("toS:") { "foo".toS } end user system total real to_s: 0.000008 0.000007 0.000015 ( 0.000008) toS: 0.000131 0.000001 0.000132 ( 0.000132)
alias_methodを使う方法
意外と速い🤔(けど、メモリ上の全Classの各メソッドにalias_methodを実行するのでその時間は掛かる)
require_relative './lib/cameRuby/aliasMethod' CameRuby::AliasMethod.call n = 100000 Benchmark.bm(7, ">total:", ">avg:") do |x| x.report("to_s:") { "foo".to_s } x.report("toS:") { "foo".toS } end user system total real to_s: 0.000009 0.000006 0.000015 ( 0.000008) toS: 0.000003 0.000001 0.000004 ( 0.000002)
おわりに
軽く試してみた感じ🐫で書けそうな気配を感じましたが、大分パフォーマンスへの影響が大きく、method_missing
or instance_eval
,alias_method
で割と無理やりな感じになっているので、本番運用に耐えられそうにはないですし、使えたとしても趣味レベルな感じですね😅
あとはIDEのインテリセンスとかもかなり影響を受けそうなので、結果素直に🐍で書いたほうが良いということがわかった。。。