2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【コードリーディング】form.labelで2単語目以降小文字になる箇所がどこなのか

Posted at

はじめに

form.labelを使い、以下のようにキャメルケースの文字列のみ引数に渡したとき、表示された文字列が先頭のみ大文字で、2単語目以降小文字になっていました。

form.label('HogeFoo')

表示文字列:Hogefoo

Ruby on Railsのlabelのドキュメントを確認したところ、ラベル配下のコンテンツなしの場合は以下のように記載されていました。

f.label
ラベル配下のコンテンツなし

f.label :name
#<label for="page_name">Name</label>

1単語のみの場合最初の文字が大文字になるようですが、単語が複数続いた場合については記載がありませんでした。
そのため、コードのどこで小文字になっているのか調べました。

環境

Ruby: 3.0.0
Ruby on Rails: 6.1.2.1
コード確認日: 2020/03/03

コードを読む

それではRuby on Railsのコードを読んでいきます。
form.label('HogeFoo')を実行するとFormHelperlabelメソッドが呼び出されます。

form_helper.rb
def label(method, text = nil, options = {}, &block)
  @template.label(@object_name, method, text, objectify_options(options), &block)
end

labelメソッド呼び出し時、引数は第一引数のみなので、methodHogeFooがセットされ、それ以外はnil{}がセットされます。
@templateformの初期化時(form_for呼び出し時)にFormHelperがセットされます。そのため、同じFormHelperlabelメソッドが呼び出されます。
なお、formの初期化処理についてはここではスキップします。

form_helper.rb
def label(object_name, method, content_or_options = nil, options = nil, &block)
  Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end

このメソッドではTags::Labelクラスを初期化してrenderメソッドを呼び出しています。
まずTags::Labelクラスの初期化処理を見ていきます。

label.rb
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
  options ||= {}

  content_is_options = content_or_options.is_a?(Hash)
  if content_is_options
    options.merge! content_or_options
    @content = nil
  else
    @content = content_or_options
  end

  super(object_name, method_name, template_object, options)
end

各変数に値をセット後、親クラスの初期化処理を呼び出しています。そのため、Labelクラスの親クラスであるBaseを見ます。

base.rb
def initialize(object_name, method_name, template_object, options = {})
  @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
  @template_object = template_object

  @object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]")
  @object = retrieve_object(options.delete(:object))
  @skip_default_ids = options.delete(:skip_default_ids)
  @allow_method_names_outside_object = options.delete(:allow_method_names_outside_object)
  @options = options

  if Regexp.last_match
    @generate_indexed_names = true
    @auto_index = retrieve_autoindex(Regexp.last_match.pre_match)
  else
    @generate_indexed_names = false
    @auto_index = nil
  end
end

HogeFooが入ったmethod_nameは、to_sで文字列変換後、dupメソッドでコピーされ@method_nameにセットされます。
初期化メソッドでは各変数の初期化のみなので、次にrenderメソッドを見ていきます。

label.rb
def render(&block)
  options = @options.stringify_keys
  tag_value = options.delete("value")
  name_and_id = options.dup

  if name_and_id["for"]
    name_and_id["id"] = name_and_id["for"]
  else
    name_and_id.delete("id")
  end

  add_default_name_and_id_for_value(tag_value, name_and_id)
  options.delete("index")
  options.delete("namespace")
  options["for"] = name_and_id["id"] unless options.key?("for")

  builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)

  content = if block_given?
    @template_object.capture(builder, &block)
  elsif @content.present?
    @content.to_s
  else
    render_component(builder)
  end

  label_tag(name_and_id["id"], content, options)
end

このメソッドではlabelタグの準備と描画を行っています。このメソッド内で呼び出されているadd_default_name_and_id_for_valueメソッドに関しては、name_and_idのデフォルト値をセットするのみなので、この記事ではスキップします。
その後、LabelBuilderクラスの初期化時に@method_nameを渡しているので、その初期化処理を見てみます。

label.rb
class LabelBuilder # :nodoc:
  attr_reader :object

  def initialize(template_object, object_name, method_name, object, tag_value)
    @template_object = template_object
    @object_name = object_name
    @method_name = method_name
    @object = object
    @tag_value = tag_value
  end
・・・
end

この初期化処理ではLabelBuilderクラスが持つ@method_namemethod_nameをセットするのみとなっています。

さきほどのrenderメソッドに戻ると、LabelBuilderのインスタンスをrender_componentメソッドに渡しているのでその処理を見ていきます。

def render_component(builder)
  builder.translation
end

このメソッドでは変換メソッドを呼び出すのみなので、その呼び出し先を見てみます。

LabelBuidler.rb
def translation
  method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name

  content ||= Translator
    .new(object, @object_name, method_and_value, scope: "helpers.label")
    .translate
  content ||= @method_name.humanize

  content
end

@tag_valueはここまでの処理でnilが入っているので、method_and_value@method_nameがセットされます。このmethod_and_valueを使用してTranslatorクラスを初期化し、translateメソッドを呼び出しています。
そのため、ここまでと同様に、まずは初期化処理を見てみます。

translator.rb
class Translator # :nodoc:
  def initialize(object, object_name, method_and_value, scope:)
    @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
    @method_and_value = method_and_value
    @scope = scope
    @model = object.respond_to?(:to_model) ? object.to_model : nil
  end
・・・
end

とくに変わったことはしておらず、初期化の中でmethod_and_value@method_and_valueにセットしています。
次にtranslateメソッドを確認してみます。

translator.rb
  def translate
    translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence
    translated_attribute || human_attribute_name
  end

I18nで変換し、その後presenceメソッドで変換後の値が存在するかどうかを確認しています。
"#{object_name}.#{method_and_value}"object_namenilなので".HogeFoo"となります。もともとのform.label呼び出し時に他言語は用意していないので、デフォルト値がセットされます。デフォルト値はi18n_defaultメソッドで定義されているので、そのメソッドを見てみます。

translator.rb
def i18n_default
  if model
    key = model.model_name.i18n_key
    ["#{key}.#{method_and_value}".to_sym, ""]
  else
    ""
  end
end

modelnilとなっているので、空文字が返されます。そのためI18nの変換は空文字となり、preseceメソッドでnilが返され、translated_attributeにセットされます。

I18nの変換処理の後は、translated_attribute || human_attribute_nameとなっているので、human_attribute_nameメソッドを見てみます。

translator.rb
def human_attribute_name
  if model && model.class.respond_to?(:human_attribute_name)
    model.class.human_attribute_name(method_and_value)
  end
end

ActiveModelのhuman_attribute_nameメソッドを使用してI18nの変換を行うメソッドですが、ここもmodelnilなのでnilが返されます。そのため、TransLatorクラスではとくに何も行われずにnilを返していることがわかります。

LabeBuidlerクラスの処理に戻り、次に実行されるcontent ||= @method_name.humanizeを見てみます。TransLatorクラスからの戻り値contentnilだったので@method_name.humanizeが実行されます。
このhumanizeメソッドにより、最初の文字のみ大文字となり、2単語目以降は小文字になります。

ちなみに、その後のlabel_tagメソッド以降の処理では、実際にHTMLのlabelタグを作成し、そこに値をセットする処理となります。

おわりに

コードを追いかけて、どこで小文字になる処理が入っているか調べました。
コードを読んでいくことで、このメソッドが他言語への変換も行っていることを新しく発見できました。
この記事が誰かのお役に立てれば幸いです。

2
2
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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?