【本文】
最近、Rails のソースコードを暇つぶしに読んでいたら、ふと private メソッドの枠組みで underscore の有無があることが気になりました。
参考 URL を読むと、Ruby では private と protected メソッドに使われることが多いそうです。
他のプログラミング言語でも( JavaScript, PHP, Phython etc...)同様のコーディング規約が存在します。これは、boolean を返すメソッドは末尾に?
を付けたり、破壊的なメソッドは!
を付けたり、getter メソッドに=
を付けることと同じ意味ですね。
- 参考URL
- Ruby - Rubyの1文字目がアンダーバー(_)のメソッド定義|teratail
- JavaScript - [Javascript]アンダースコアから始まる変数、関数の意味|teratail
- coding style - What's the deal with a leading underscore in PHP class methods? - Stack Overflow
- code quality - Should I always use prefix private methods with an underscore in Python? - Software Engineering Stack Exchange
【あとがき】
private メソッドでも underscore が付かないものもある?
underscore はコーディング規約の一種と考えても、Rails のソースコードでは、 underscore が付くものと付かないものが存在します。
Rails と言う巨大なフレームワークなら一定のコーディング規約があると思うのですが、なぜ付かないものがあるのでしょうか?
結論は違いがわかりませんでしたでしたが...
全ての unserscore 付きメソッドに言えることではありませんが、_assign_attributes
にしろ、_find_all
にしろ、呼び出し元のメソッドと強く依存しているケースが散見されます。
...
def assign_attributes(new_attributes)
unless new_attributes.respond_to?(:each_pair)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
end
return if new_attributes.empty?
_assign_attributes(sanitize_for_mass_assignment(new_attributes))
end
alias attributes= assign_attributes
private
def _assign_attributes(attributes)
attributes.each do |k, v|
_assign_attribute(k, v)
end
end
def _assign_attribute(k, v)
setter = :"#{k}="
if respond_to?(setter)
public_send(setter, v)
else
raise UnknownAttributeError.new(self, k.to_s)
end
end
...
...
def route_for(name, *args)
public_send(:"#{name}_url", *args)
end
protected
def optimize_routes_generation?
_routes.optimize_routes_generation? && default_url_options.empty?
end
private
def _with_routes(routes) # :doc:
old_routes, @_routes = @_routes, routes
yield
ensure
@_routes = old_routes
end
def _routes_context # :doc:
self
end
...
...
%w(<< concat push insert unshift).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(*args)
paths.#{method}(*typecast(args))
end
METHOD
end
...
def find_all(path, prefixes = [], *args)
_find_all path, prefixes, args
end
...
private
def _find_all(path, prefixes, args)
prefixes = [prefixes] if String === prefixes
prefixes.each do |prefix|
paths.each do |resolver|
templates = resolver.find_all(path, prefix, *args)
return templates unless templates.empty?
end
end
[]
end
def typecast(paths)
paths.map do |path|
case path
when Pathname, String
OptimizedFileSystemResolver.new path.to_s
else
path
end
end
end
sanitize とは一体?
全くの余談ですが、#assign_attributes
を見ると、入力値と仮定する new_attributes をsanitize_for_mass_assignment
を通していることがわかります。つまるところ、new_attributes は、比喩表現として消毒
される必要があることですよね。
メソッドを見てみると、Storong Parameter 等で許可されていない params が合ったら例外を出すようです。
def sanitize_for_mass_assignment(attributes)
if attributes.respond_to?(:permitted?)
raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
attributes.to_h
else
attributes
end
end
他にも activemodel の中を探検すると、dirty
(汚れた)と呼ばれる module が目につきます。この module は、DB 保存前の入力値を扱う機能であり、例え assign_attributes でインスタンス変数に入力値を割り当てても、元の DB 保存値を取り出せるメソッドが定義されています。
つまるところ、データベースにとって、ユーザの入力値は、粗く汚く悪意ある情報と見ており、それを考慮した上で対応する必要性があることが読み取れます。まぁ読めばわかることではあるのですが、オブジェクトやドメインに対する共通認識を理解することで、コードの理解度を深められるなと思ったところです。
@_
のようにインスタンス変数にも underscore が使われてる?
メモ化等で使用範囲を内部で留めるインスタンス変数に対して、使用されているようです。
今回の private に似た目的ですね。