LoginSignup
2
0

More than 3 years have passed since last update.

【Tips】Rails の private メソッドになぜ underscore が付くのか

Last updated at Posted at 2020-03-29

【本文】

最近、Rails のソースコードを暇つぶしに読んでいたら、ふと private メソッドの枠組みで underscore の有無があることが気になりました。

参考 URL を読むと、Ruby では private と protected メソッドに使われることが多いそうです。

他のプログラミング言語でも( JavaScript, PHP, Phython etc...)同様のコーディング規約が存在します。これは、boolean を返すメソッドは末尾に?を付けたり、破壊的なメソッドは!を付けたり、getter メソッドに=を付けることと同じ意味ですね。

【あとがき】

private メソッドでも underscore が付かないものもある?

underscore はコーディング規約の一種と考えても、Rails のソースコードでは、 underscore が付くものと付かないものが存在します。

Rails と言う巨大なフレームワークなら一定のコーディング規約があると思うのですが、なぜ付かないものがあるのでしょうか?
結論は違いがわかりませんでしたでしたが...

全ての unserscore 付きメソッドに言えることではありませんが、_assign_attributesにしろ、_find_allにしろ、呼び出し元のメソッドと強く依存しているケースが散見されます。

rails/activemodel/lib/active_model/attribute_assignment.rb
...
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
    ...
rails/actionpack/lib/action_dispatch/routing/url_for.rb
...
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
...
rails/actionview/lib/action_view/path_set.rb
...
%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 が合ったら例外を出すようです。

rails/activemodel/lib/active_model/forbidden_attributes_protection.rb
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 に似た目的ですね。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0