rubyonrails.org에서 더 보기:

다음은 https://guides.rubyonrails.org 에서 확인 가능한 가이드입니다.

Active Support Core Extensions

Active Support는 Ruby 언어 확장과 유틸리티를 제공하는 Ruby on Rails 컴포넌트입니다.

Rails 애플리케이션 개발과 Ruby on Rails 자체 개발을 대상으로 언어 차원에서 더 풍부한 기능을 제공합니다.

이 가이드를 읽은 후에는 다음을 알 수 있습니다:

  • Core Extensions가 무엇인지
  • 모든 확장을 로드하는 방법
  • 필요한 확장만 선택적으로 로드하는 방법
  • Active Support가 제공하는 확장들

1 Core Extensions를 로드하는 방법

1.1 독립형 Active Support

가능한 한 가장 작은 기본 풋프린트를 유지하기 위해, Active Support는 기본적으로 최소한의 의존성만을 로드합니다. 원하는 확장만 로드할 수 있도록 작은 조각으로 나뉘어 있습니다. 또한 관련된 확장들을 한 번에 로드하거나 모든 것을 로드할 수 있는 편리한 진입점들도 있습니다.

따라서 다음과 같은 간단한 require를 한 후:

require "active_support"

Active Support framework에서 필요한 extension만 로드됩니다.

1.1.1 원하는 Definition만 선택하기

이 예제는 Hash#with_indifferent_access를 로드하는 방법을 보여줍니다. 이 extension은 HashActiveSupport::HashWithIndifferentAccess로 변환할 수 있게 해주며, 이를 통해 string이나 symbol로 키에 접근할 수 있습니다.

{ a: 1 }.with_indifferent_access["a"] # => 1

코어 확장으로 정의된 모든 메서드에 대해 이 가이드는 해당 메서드가 정의된 위치를 알려주는 노트를 포함하고 있습니다. with_indifferent_access의 경우 다음과 같은 노트가 있습니다:

active_support/core_ext/hash/indifferent_access.rb에 정의되어 있습니다.

이는 다음과 같이 require할 수 있다는 의미입니다:

require "active_support"
require "active_support/core_ext/hash/indifferent_access" 

Active Support는 파일을 cherry-picking할 때 필요한 의존성만을 정확하게 로드하도록 신중하게 수정되었습니다.

1.1.2 그룹화된 Core Extensions 로드하기

다음 단계는 Hash에 대한 모든 확장을 단순히 로드하는 것입니다. 경험칙상 SomeClass에 대한 확장은 active_support/core_ext/some_class를 로드하여 한번에 사용할 수 있습니다.

따라서 Hash에 대한 모든 확장을 로드하려면 (with_indifferent_access 포함):

require "active_support"
require "active_support/core_ext/hash"

1.1.3 모든 Core Extension 로드하기

모든 core extension을 한번에 로드하길 원한다면, 이를 위한 파일이 있습니다:

require "active_support"
require "active_support/core_ext"

1.1.4 Active Support의 모든 기능 로드하기

마지막으로, Active Support의 모든 기능을 사용하고 싶다면 다음과 같이 입력하세요:

require "active_support/all"

이는 Active Support의 모든 기능을 요청하는 것입니다. Active Support를 선택적으로 로드하려면 필요한 각 기능들 대신에 이것을 사용할 수 있습니다.

이것은 실제로 모든 Active Support를 메모리에 미리 로드하지도 않습니다. 일부는 autoload를 통해 설정되어 있어서 사용될 때만 로드됩니다.

1.2 Ruby on Rails 애플리케이션 내의 Active Support

Ruby on Rails 애플리케이션은 config.active_support.bare가 true가 아닌 한 모든 Active Support를 로드합니다. true인 경우, 애플리케이션은 프레임워크 자체가 필요로 하는 것만 선택적으로 로드하며, 이전 섹션에서 설명한 것처럼 어떤 세부 수준에서도 선택적으로 로드할 수 있습니다.

2 모든 객체에 대한 확장

2.1 blank?present?

Rails 애플리케이션에서 다음 값들은 blank로 간주됩니다:

  • nilfalse,

  • 공백으로만 구성된 문자열(아래 참고사항 참조),

  • 빈 배열과 빈 해시, 그리고

  • empty?에 응답하고 비어있는 다른 모든 객체.

문자열에 대한 조건은 유니코드를 인식하는 문자 클래스 [:space:]를 사용하므로, 예를 들어 U+2029(단락 구분자)도 공백으로 간주됩니다.

숫자들은 언급되지 않았다는 점에 주의하세요. 특히, 0과 0.0은 blank가 아닙니다.

예를 들어, ActionController::HttpAuthentication::Token::ControllerMethods의 다음 메서드는 토큰의 존재 여부를 확인하기 위해 blank?를 사용합니다:

def authenticate(controller, &login_procedure)
  token, options = token_and_options(controller.request)
  unless token.blank?
    login_procedure.call(token, options)
  end
end

controller에 대해 authenticate를 수행하고 login_procedure를 실행합니다. controller request로부터 token과 options를 가져오고, token이 비어있지 않으면 token과 options를 인자로 login_procedure를 호출합니다.

메서드 present?!blank?와 동등합니다. 이 예시는 ActionDispatch::Http::Cache::Response에서 가져온 것입니다:

def set_conditional_cache_control!
  unless self["Cache-Control"].present?
    # ...
  end
end

active_support/core_ext/object/blank.rb에 정의되어 있습니다.

2.2 presence

presence 메서드는 present?가 true이면 해당 객체를 반환하고, 그렇지 않으면 nil을 반환합니다. 이는 다음과 같은 경우에 유용합니다:

host = config[:host].presence || "localhost"

host는 config[:host]가 존재하면 그 값을 사용하고, 없는 경우 "localhost"를 사용합니다.

active_support/core_ext/object/blank.rb에 정의되어 있습니다.

2.3 duplicable?

Ruby 2.5부터 대부분의 object들은 dup 또는 clone을 통해 복제할 수 있습니다:

"foo".dup           # => "foo"
"".dup              # => ""
Rational(1).dup     # => (1/1) 
Complex(0).dup      # => (0+0i)
1.method(:+).dup    # => TypeError (Method에 대해 allocator가 정의되지 않음)

Active Support는 객체에 대해 이러한 것을 조회하기 위한 duplicable?를 제공합니다:

"foo".duplicable?           # => true
"".duplicable?              # => true
Rational(1).duplicable?     # => true
Complex(1).duplicable?      # => true
1.method(:+).duplicable?    # => false

모든 클래스는 dupclone을 제거하거나 이들로부터 예외를 발생시켜 복제를 허용하지 않을 수 있습니다. 따라서 rescue만이 주어진 임의의 객체가 복제 가능한지 여부를 알려줄 수 있습니다. duplicable?은 위의 하드코딩된 리스트에 의존하지만, rescue보다 훨씬 빠릅니다. 사용 사례에서 하드코딩된 리스트로 충분하다고 확신할 때만 사용하세요.

active_support/core_ext/object/duplicable.rb에 정의되어 있습니다.

2.4 deep_dup

deep_dup 메서드는 주어진 객체의 깊은 복사본을 반환합니다. 일반적으로 다른 객체를 포함하는 객체를 dup할 때, Ruby는 그 안의 객체들을 dup하지 않으므로 객체의 얕은 복사본을 생성합니다. 예를 들어 문자열이 포함된 배열이 있다면 다음과 같이 동작합니다:

array     = ["string"]
duplicate = array.dup

duplicate.push "another-string" 

# 객체가 복제되었으므로 요소는 복제본에만 추가됨
array     # => ["string"]
duplicate # => ["string", "another-string"]

duplicate.first.gsub!("string", "foo")

# 첫번째 요소는 복제되지 않았으므로 두 배열 모두에서 변경됨
array     # => ["foo"]
duplicate # => ["foo, "another-string"]

보시다시피 Array 인스턴스를 복제한 후에는 다른 객체를 얻게 되므로, 이를 수정해도 원본 객체는 변하지 않은 상태로 유지됩니다. 하지만 이는 배열의 요소에는 해당되지 않습니다. dup는 깊은 복사를 수행하지 않기 때문에 배열 내의 문자열은 여전히 동일한 객체입니다.

객체의 깊은 복사가 필요한 경우 deep_dup를 사용해야 합니다. 예시는 다음과 같습니다:

array     = ["string"]
duplicate = array.deep_dup

duplicate.first.gsub!("string", "foo")

array     # => ["string"]
duplicate # => ["foo"]

객체가 복제 불가능한 경우, deep_dup는 그 객체를 그대로 반환합니다:

number = 1
duplicate = number.deep_dup 
number.object_id == duplicate.object_id   # => true

active_support/core_ext/object/deep_dup.rb에 정의되어 있습니다.

2.5 try

객체가 nil이 아닐 때만 메서드를 호출하고 싶을 때, 가장 간단한 방법은 조건문을 사용하는 것이지만 이는 불필요한 코드를 추가하게 됩니다. 대안으로 try를 사용할 수 있습니다. tryObject#public_send와 비슷하지만 nil에 대해 호출될 경우 nil을 반환한다는 점이 다릅니다.

예시입니다:

# try 없이 작성
unless @number.nil?
  @number.next
end

# try 사용 
@number.try(:next)

ActiveRecord::ConnectionAdapters::AbstractAdapter의 다음 코드는 @loggernil일 수 있는 또 다른 예시입니다. 이 코드에서는 try를 사용하여 불필요한 체크를 피하는 것을 볼 수 있습니다.

def log_info(sql, name, ms)
  if @logger.try(:debug?)
    name = "%s (%.1fms)" % [name || "SQL", ms]
    @logger.debug(format_log_entry(name, sql.squeeze(" "))) 
  end
end

SQL 문과 이름, 실행시간(ms)을 받아 debug 레벨로 로그를 남기는 메서드입니다. @logger가 존재하고 debug? 메서드가 true를 반환할 경우, SQL 실행에 대한 정보를 포맷팅하여 디버그 로그에 기록합니다. sql.squeeze(" ")는 연속된 공백을 하나로 압축합니다.

try는 인자 없이 블록만 사용할 수도 있습니다. 이 경우 블록은 객체가 nil이 아닐 때만 실행됩니다:

@person.try { |p| "#{p.first_name} #{p.last_name}" }

try는 메서드가 없는 에러를 무시하고 대신 nil을 반환합니다. 오타를 방지하고 싶다면 대신 try!를 사용하세요:

@number.try(:nest)  # => nil
@number.try!(:nest) # NoMethodError: 1:Integer에 대해 정의되지 않은 메서드 `nest'

active_support/core_ext/object/try.rb에 정의되어 있습니다.

2.6 class_eval(*args, &block)

class_eval을 사용하여 어떤 객체의 싱글톤 클래스 컨텍스트에서 코드를 평가할 수 있습니다:

class Proc
  def bind(object)
    # block과 현재 시간을 저장
    block, time = self, Time.current
    # 객체의 클래스에서 평가
    object.class_eval do
      # 메서드명을 생성 ("__bind_" + UNIX 타임스탬프 + 마이크로초)
      method_name = "__bind_#{time.to_i}_#{time.usec}"
      # 블록으로 메서드 정의
      define_method(method_name, &block)
      # 메서드를 인스턴스 메서드로 가져옴
      method = instance_method(method_name)
      # 메서드를 삭제 
      remove_method(method_name)
      method
    end.bind(object)
  end
end

active_support/core_ext/kernel/singleton_class.rb에 정의되어 있습니다.

2.7 acts_like?(duck)

acts_like? 메소드는 간단한 규칙을 기반으로 어떤 클래스가 다른 클래스처럼 동작하는지 확인하는 방법을 제공합니다: String과 동일한 interface를 제공하는 클래스를 정의합니다.

def acts_like_string?
end

String처럼 동작하는지 여부를 알려줍니다.

이는 단순한 표시일 뿐이며, 본문이나 반환값은 관련이 없습니다. 그런 다음, client 코드는 다음과 같은 방식으로 duck-type-safeness를 확인할 수 있습니다:

some_klass.acts_like?(:string)

Rails에는 DateTime처럼 동작하고 이 계약을 따르는 클래스들이 있습니다.

active_support/core_ext/object/acts_like.rb에 정의되어 있습니다.

2.8 to_param

Rails의 모든 객체는 to_param 메서드에 응답하도록 되어 있습니다. 이는 query string의 값이나 URL fragment로서 객체를 나타내는 것을 반환하기 위한 것입니다.

기본적으로 to_param은 단순히 to_s를 호출합니다:

7.to_param # => "7"

to_param의 반환값은 escape 처리하면 안됩니다:

"Tom & Jerry".to_param # => "Tom & Jerry"

Rails의 여러 클래스들이 이 메서드를 오버라이드합니다.

예를 들어 nil, true, false는 자기 자신을 반환합니다. Array#to_param은 각 요소에 대해 to_param을 호출하고 결과를 "/"로 연결합니다:

[0, true, String].to_param # => "0/true/String"

Rails 라우팅 시스템은 모델의 :id placeholder에 대한 값을 얻기 위해 to_param을 호출합니다. ActiveRecord::Base#to_param은 모델의 id를 반환하지만, 모델에서 이 메서드를 재정의할 수 있습니다. 예를 들어,

class User
  def to_param
    "#{id}-#{name.parameterize}"
  end
end

이 예제와 같이, object의 id와 함께 object 이름이 포함된 URL을 생성할 수 있습니다. parameterize는 공백을 대시(-)로 대체하고 비 ASCII 문자들을 제거합니다.

we get:

으로부터 다음을 얻습니다:

user_path(@user) # => "/users/357-john-smith"

Controller는 to_param의 재정의를 인지해야 합니다. "357-john-smith"와 같은 요청이 들어올 때 이것이 params[:id]의 값이 되기 때문입니다.

active_support/core_ext/object/to_param.rb에 정의되어 있습니다.

2.9 to_query

to_query 메서드는 주어진 keyto_param의 반환값을 연결하는 query string을 생성합니다. 예를 들어, 다음과 같은 to_param 정의가 있다면:

class User
  def to_param
    "#{id}-#{name.parameterize}"
  end
end

Here's a Rails app with no database at all:

# config/application.rb
module NoDatabase
  class Application < Rails::Application
    config.load_defaults 7.1
    config.autoloader = :zeitwerk

    # Prevent database connection
    config.active_record.database_selector = false
    config.active_record.database_resolver = false
    config.active_record.database_resolver_context = false
  end
end

Now we can request /articles?id=xxx&author_id=yyy and the table won't be queried.

With this change, you can run the application even if the database specified in config/database.yml doesn't exist.

current_user.to_query("user") # => "user=357-john-smith"

이 메소드는 key와 value 모두에 대해 필요한 escape 처리를 수행합니다:

account.to_query("company[name]")
# => "company%5Bname%5D=Johnson+%26+Johnson"

따라서 출력은 query string에서 사용할 수 있도록 준비됩니다.

Array는 각 요소에 key[]를 key로 하여 to_query를 적용한 결과를 반환하고 "&"로 결과를 결합합니다:

[3.4, -45.6].to_query("sample")
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"

Hash는 다른 시그니처로 to_query에 반응합니다. 인자가 전달되지 않은 경우 호출은 value에 대해 to_query(key)를 호출하여 정렬된 key/value 할당 시리즈를 생성합니다. 그런 다음 결과를 "&"로 결합합니다.

{ c: 3, b: 2, a: 1 }.to_query # => "a=1&b=2&c=3"

메서드 Hash#to_query는 key들을 위한 선택적 namespace를 허용합니다:

{ id: 89, name: "John Smith" }.to_query("user")
# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"

active_support/core_ext/object/to_query.rb에 정의되어 있습니다.

2.10 with_options

with_options 메서드는 일련의 메서드 호출에서 공통적인 옵션들을 분리할 수 있는 방법을 제공합니다.

기본 옵션 hash가 주어지면, with_options은 proxy 객체를 block에 전달합니다. block 내에서 proxy에 호출되는 메서드들은 그들의 옵션이 병합된 채로 receiver에 전달됩니다. 예를 들어, 다음과 같은 중복을 제거할 수 있습니다:

class Account < ApplicationRecord
  has_many :customers, dependent: :destroy
  has_many :products,  dependent: :destroy
  has_many :invoices,  dependent: :destroy
  has_many :expenses,  dependent: :destroy
end

"this way:" 를 다음과 같이 번역합니다:

이렇게:

class Account < ApplicationRecord
  # 동일한 dependent: :destroy 옵션을 공유하는 여러 관계를 정의할 때
  with_options dependent: :destroy do |assoc|
    assoc.has_many :customers
    assoc.has_many :products 
    assoc.has_many :invoices
    assoc.has_many :expenses
  end
end

이 관용구는 독자에게 grouping 의미도 전달할 수 있습니다. 예를 들어, 사용자의 언어에 따라 뉴스레터를 보내고 싶다고 가정해보겠습니다. mailer의 어딘가에서 locale에 종속적인 부분을 다음과 같이 그룹화할 수 있습니다:

I18n.with_options locale: user.locale, scope: "newsletter" do |i18n|
  subject i18n.t :subject 
  body    i18n.t :body, user_name: user.name
end

with_options는 호출을 receiver로 전달하기 때문에 중첩될 수 있습니다. 각 중첩 단계는 자신의 기본값과 함께 상속된 기본값을 병합합니다.

active_support/core_ext/object/with_options.rb에 정의되어 있습니다.

2.11 JSON 지원

Active Support는 Ruby 객체를 위해 json gem이 기본적으로 제공하는 것보다 더 나은 to_json 구현을 제공합니다. 이는 HashProcess::Status와 같은 일부 클래스들이 적절한 JSON 표현을 제공하기 위해 특별한 처리가 필요하기 때문입니다.

active_support/core_ext/object/json.rb에 정의되어 있습니다.

2.12 Instance Variables

Active Support는 instance variable에 접근하기 쉽게 해주는 몇 가지 메서드를 제공합니다.

2.12.1 instance_values

instance_values 메서드는 "@" 기호가 없는 instance variable 이름을 해당하는 값에 매핑하는 hash를 반환합니다. 키는 문자열입니다:

class C
  def initialize(x, y)
    @x, @y = x, y
  end
end

C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} 

active_support/core_ext/object/instance_variables.rb에 정의되어 있습니다.

2.12.2 instance_variable_names

instance_variable_names 메서드는 배열을 반환합니다. 각 이름은 "@" 기호를 포함합니다.

class C
  def initialize(x, y)
    @x, @y = x, y
  end
end

C.new(0, 1).instance_variable_names # => ["@x", "@y"] 

active_support/core_ext/object/instance_variables.rb에 정의되어 있습니다.

2.13 Warning과 Exception 무시하기

silence_warningsenable_warnings 메서드는 블록이 실행되는 동안 $VERBOSE 값을 적절하게 변경하고, 블록이 종료된 후에는 이전 값으로 복원합니다:

silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }

警告メッセージを抑制して { Object.const_set "RAILS_DEFAULT_LOGGER", logger } を実行

Exception을 무시하는 것은 suppress로도 가능합니다. 이 메소드는 임의의 수의 exception 클래스를 받습니다. 블록 실행 중에 exception이 발생하고 그것이 인자들 중 하나의 kind_of?인 경우, suppress는 이를 포착하고 조용히 리턴합니다. 그렇지 않으면 exception은 포착되지 않습니다.

# 사용자가 잠겨있는 경우 increment는 손실되지만, 큰 문제는 없습니다.
suppress(ActiveRecord::StaleObjectError) do
  current_user.increment! :visits
end

active_support/core_ext/kernel/reporting.rb에 정의되어 있습니다.

2.14 in?

[in?][Object#in?] 술어는 한 객체가 다른 객체에 포함되어 있는지를 테스트합니다. 전달된 인자가 include?에 응답하지 않는 경우 ArgumentError 예외가 발생합니다.

in? 예제:

1.in?([1, 2])        # => true
"lo".in?("hello")   # => true
25.in?(30..50)      # => false
1.in?(1)            # => ArgumentError

active_support/core_ext/object/inclusion.rb에 정의되어 있습니다.

3 Module에 대한 확장

3.1 Attributes

3.1.1 alias_attribute

모델 속성은 reader, writer, predicate를 가집니다. alias_attribute를 사용하여 해당하는 세 가지 메서드가 모두 정의된 모델 속성의 별칭을 지정할 수 있습니다. 다른 별칭 지정 메서드와 마찬가지로 첫 번째 인자는 새 이름이고 두 번째 인자는 이전 이름입니다(할당을 할 때와 같은 순서라고 기억하면 됩니다):

class User < ApplicationRecord
  # email 컬럼을 "login"으로 참조할 수 있습니다.
  # 이는 인증 코드에서 의미있게 사용될 수 있습니다.
  alias_attribute :login, :email
end

active_support/core_ext/module/aliasing.rb에 정의되어 있습니다.

3.1.2 Internal Attributes

상속될 예정인 클래스에서 attribute를 정의할 때는 이름 충돌의 위험이 있습니다. 이는 라이브러리에서 매우 중요한 사항입니다.

Active Support는 attr_internal_reader, attr_internal_writer, attr_internal_accessor 매크로들을 정의합니다. 이들은 Ruby 내장 attr_*와 동일하게 동작하지만, 인스턴스 변수의 이름을 충돌 가능성이 적은 방식으로 지정합니다.

attr_internal 매크로는 attr_internal_accessor의 동의어입니다:

# library
class ThirdPartyLibrary::Crawler
  attr_internal :log_level
end

# 클라이언트 코드
class MyCrawler < ThirdPartyLibrary::Crawler
  attr_accessor :log_level
end

앞의 예시에서 :log_level은 라이브러리의 public interface에 속하지 않고 개발 용도로만 사용되는 경우일 수 있습니다. 잠재적 충돌을 인식하지 못한 클라이언트 코드가 서브클래스를 만들고 자체적으로 :log_level을 정의할 수 있습니다. attr_internal 덕분에 충돌이 발생하지 않습니다.

기본적으로 내부 instance variable은 앞에 밑줄이 붙어 위 예시에서는 @_log_level과 같이 명명됩니다. 이는 Module.attr_internal_naming_format을 통해 설정 가능하며, 맨 앞에 @가 있고 이름이 들어갈 자리에 %s가 있는 sprintf 형식의 문자열을 전달할 수 있습니다. 기본값은 "@_%s"입니다.

Rails는 몇 군데에서 내부 속성을 사용하는데, 예를 들어 view에서 사용합니다:

module ActionView
  class Base
    # content_for에서 캡처된 내용을 저장
    attr_internal :captures
    # request 객체와 layout을 저장
    attr_internal :request, :layout
    # controller와 현재의 template을 저장
    attr_internal :controller, :template
  end
end

active_support/core_ext/module/attr_internal.rb에 정의되어 있습니다.

3.1.3 Module Attributes

mattr_reader, mattr_writer, mattr_accessor 매크로는 클래스에 정의된 cattr_* 매크로와 동일합니다. 사실, cattr_* 매크로는 mattr_* 매크로의 별칭입니다. Class Attributes를 확인하세요.

예를 들어, Active Storage의 logger API는 mattr_accessor로 생성됩니다:

module ActiveStorage
  mattr_accessor :logger
end

active_support/core_ext/module/attribute_accessors.rb에 정의되어 있습니다.

3.2 Parents

3.2.1 module_parent

중첩된 named module에서 module_parent 메서드는 해당 상수를 포함하는 모듈을 반환합니다:

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parent # => X::Y 
M.module_parent       # => X::Y

이 경우 X::Y::ZM은 동일한 module을 참조하므로, 둘 다 같은 parent(X::Y)를 가집니다.

모듈이 익명이거나 최상위에 속하는 경우 module_parentObject를 반환합니다.

이 경우 module_parent_namenil을 반환한다는 점에 유의하세요.

active_support/core_ext/module/introspection.rb에 정의되어 있습니다.

3.2.2 module_parent_name

중첩된 명명된 모듈에서 module_parent_name 메서드는 해당 상수를 포함하는 모듈의 정규화된 이름을 반환합니다:

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parent_name # => "X::Y" 
M.module_parent_name       # => "X::Y"

최상위 또는 익명 모듈의 경우 module_parent_namenil을 반환합니다.

이 경우 module_parentObject를 반환한다는 점에 유의하세요.

active_support/core_ext/module/introspection.rb에 정의되어 있습니다.

3.2.3 module_parents

module_parents 메서드는 수신자에 대해 module_parent를 호출하고 Object에 도달할 때까지 상위로 올라갑니다. 이 체인은 하위에서 상위 순으로 배열로 반환됩니다:

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parents # => [X::Y, X, Object]
M.module_parents       # => [X::Y, X, Object] 

active_support/core_ext/module/introspection.rb에 정의되어 있습니다.

3.3 익명

모듈은 이름을 가질 수도 있고 가지지 않을 수도 있습니다:

module M
end
M.name # => "M"

N = Module.new
N.name # => "N"

Module.new.name # => nil

module의 이름과 객체 ID를 확인할 수 있습니다. 상수로 할당된 module의 경우 그 이름을 반환하지만, 이름없는 module(익명 module)의 경우 nil을 반환합니다.

module이 name을 가지고 있는지 predicate anonymous?를 통해 확인할 수 있습니다:

module M
end
M.anonymous? # => false

Module.new.anonymous? # => true

module이 익명인지 검사합니다. module이 이름이 있으면 false를 반환하고 이름이 없으면 true를 반환합니다.

도달할 수 없다는 것이 반드시 익명이라는 것을 의미하지는 않습니다:

module M
end

m = Object.send(:remove_const, :M)

m.anonymous? # => false

이 모듈은 명시적으로 이름이 지정되었기 때문에 anonymous가 아닙니다. 상수 할당이 제거되었더라도 루비는 모듈의 원래 이름을 기억합니다.

정의상 익명 모듈은 접근할 수 없지만 이를 확인할 수 있습니다.

active_support/core_ext/module/anonymous.rb에 정의되어 있습니다.

3.4 Method Delegation

3.4.1 delegate

매크로 delegate는 메서드를 전달하는 쉬운 방법을 제공합니다.

어떤 애플리케이션에서 사용자들의 로그인 정보는 User 모델에 있지만 이름과 다른 데이터는 별도의 Profile 모델에 있다고 가정해보겠습니다:

class User < ApplicationRecord
  has_one :profile
end

이런 configuration으로 user.profile.name을 통해 user의 name에 접근할 수 있지만, 이러한 속성에 직접 접근할 수 있으면 편리할 수 있습니다:

class User < ApplicationRecord
  has_one :profile

  def name
    profile.name
  end
end

이것이 delegate가 당신을 위해 하는 일입니다:

class User < ApplicationRecord
  has_one :profile

  delegate :name, to: :profile
end

User model은 profile에 name method를 delegate합니다. 이를 통해 직접 user.profile.name을 호출하지 않고도 user.name을 호출할 수 있습니다.

더 짧고 의도가 더 명확합니다.

대상에서 메서드가 public이어야 합니다.

delegate 매크로는 여러 메서드를 받아들입니다:

delegate :name, :age, :address, :twitter, to: :profile

name, age, address, twitter와 같은 메소드 호출들을 profile 객체로 위임합니다.

문자열로 삽입될 때, :to 옵션은 메서드가 위임되는 객체를 평가하는 표현식이 되어야 합니다. 일반적으로 문자열이나 symbol입니다. 이러한 표현식은 receiver의 컨텍스트에서 평가됩니다.

# Rails 상수에 위임합니다
delegate :logger, to: :Rails

# 수신자의 클래스에 위임합니다  
delegate :table_name, to: :class

:prefix 옵션이 true인 경우 일반성이 떨어집니다. 아래를 참조하세요.

기본적으로 위임된 대상이 nil이고 delegation이 NoMethodError를 발생시키는 경우 예외가 전파됩니다. :allow_nil 옵션을 사용하면 대신 nil이 반환되도록 할 수 있습니다:

delegate :name, to: :profile, allow_nil: true

profile로 name을 delegate하고, nil이 허용됩니다.

:allow_nil 옵션을 사용하면 user에 profile이 없을 때 user.name 호출은 nil을 반환합니다.

:prefix 옵션은 생성된 메서드의 이름에 접두사를 추가합니다. 더 나은 이름을 사용하기 위해 이 옵션이 유용할 수 있습니다:

delegate :street, to: :address, prefix: true

이것은 address.street()address_street()로 호출할 수 있도록 위임 메서드를 생성합니다.

이전 예시는 street 대신 address_street를 생성합니다.

이 경우에는 생성된 메서드의 이름이 target object와 target 메서드 이름으로 구성되어 있기 때문에 :to 옵션은 반드시 메서드 이름이어야 합니다.

커스텀 prefix도 설정할 수 있습니다:

attachment로 size 메서드를 위임하며, 이때 avatar라는 prefix를 붙입니다

이전 예제에서 매크로는 size 대신 avatar_size를 생성합니다.

:private 옵션은 메서드의 스코프를 변경합니다:

delegate :date_of_birth, to: :profile, private: true

:delegate를 사용해 date_of_birth 메서드를 profile object에 위임하고, 이 메서드를 private으로 설정합니다.

delegate된 메서드들은 기본적으로 public입니다. private: true를 전달하면 이를 변경할 수 있습니다.

active_support/core_ext/module/delegation.rb에 정의되어 있습니다.

3.4.2 delegate_missing_to

User 객체에서 누락된 모든 것을 Profile 객체에 delegate하고 싶다고 가정해봅시다. delegate_missing_to 매크로를 사용하면 이를 손쉽게 구현할 수 있습니다:

class User < ApplicationRecord
  has_one :profile

  delegate_missing_to :profile
end

target은 객체 내의 인스턴스 변수, 메서드, 상수 등 호출 가능한 모든 것이 될 수 있습니다. target의 public 메서드만 위임됩니다.

active_support/core_ext/module/delegation.rb에 정의되어 있습니다.

3.5 메서드 재정의하기

define_method로 메서드를 정의해야 하지만 해당 이름의 메서드가 이미 존재하는지 알 수 없는 경우가 있습니다. 만약 이미 존재한다면, warning이 활성화되어 있을 때 경고가 발생합니다. 큰 문제는 아니지만 깔끔하지도 않습니다.

redefine_method 메서드는 필요한 경우 기존 메서드를 먼저 제거하여 이러한 잠재적 경고를 방지합니다.

대체 메서드를 직접 정의해야 하는 경우(예: delegate를 사용하는 경우)에는 silence_redefinition_of_method를 사용할 수 있습니다.

active_support/core_ext/module/redefine_method.rb에 정의되어 있습니다.

4 Extensions to Class

4.1 Class 속성

4.1.1 class_attribute

class_attribute 메서드는 계층 구조의 모든 레벨에서 재정의될 수 있는 하나 이상의 상속 가능한 class 속성을 선언합니다.

class A
  class_attribute :x
end

class B < A; end

class C < B; end

A.x = :a
B.x # => :a
C.x # => :a

B.x = :b
A.x # => :a  
C.x # => :b

C.x = :c
A.x # => :a
B.x # => :b

예를 들어 ActionMailer::Base는 다음을 정의합니다:

class_attribute :default_params
self.default_params = {
  mime_version: "1.0", 
  charset: "UTF-8",
  content_type: "text/plain",
  parts_order: [ "text/plain", "text/enriched", "text/html" ]
}.freeze

이들은 인스턴스 레벨에서도 접근하고 오버라이드할 수 있습니다.

A.x = 1

a1 = A.new 
a2 = A.new
a2.x = 2

a1.x # => 1, A로부터 가져옴
a2.x # => 2, a2에서 재정의됨

writer instance method의 생성은 :instance_writer 옵션을 false로 설정하여 막을 수 있습니다.

module ActiveRecord
  class Base
    class_attribute :table_name_prefix, instance_writer: false, default: "my"
  end
end

model은 mass-assignment가 속성을 설정하는 것을 방지하는 방법으로 이 옵션을 유용하게 사용할 수 있습니다.

:instance_reader 옵션을 false로 설정하면 reader instance method의 생성을 방지할 수 있습니다.

class A
  class_attribute :x, instance_reader: false
end

A.new.x = 1 
A.new.x # NoMethodError가 발생

편의를 위해 class_attribute는 인스턴스 리더가 반환하는 것의 이중 부정인 인스턴스 술어도 정의합니다. 위의 예시에서는 x?로 호출됩니다.

:instance_readerfalse일 때, 인스턴스 술어는 리더 메서드와 마찬가지로 NoMethodError를 반환합니다.

인스턴스 술어를 원하지 않는 경우, instance_predicate: false를 전달하면 정의되지 않습니다.

active_support/core_ext/class/attribute.rb에 정의되어 있습니다.

4.1.2 cattr_reader, cattr_writer, 그리고 cattr_accessor

cattr_reader, cattr_writer, cattr_accessor 매크로는 클래스를 위한 attr_* 대응물입니다. 이미 존재하지 않는 경우 클래스 변수를 nil로 초기화하고, 이에 접근하기 위한 해당 클래스 메서드들을 생성합니다:

class MysqlAdapter < AbstractAdapter
  # @@emulate_booleans에 접근하기 위한 class 메서드를 생성합니다.
  cattr_accessor :emulate_booleans
end

또한, cattr_*에 block을 전달하여 기본값으로 attribute를 설정할 수 있습니다:

class MysqlAdapter < AbstractAdapter
  # true를 기본값으로 하여 @@emulate_booleans에 접근하는 클래스 메서드들을 생성합니다.
  cattr_accessor :emulate_booleans, default: true
end

편의상 인스턴스 메서드도 생성되는데, 이는 단순히 클래스 속성에 대한 프록시입니다. 그래서 인스턴스는 클래스 속성을 변경할 수는 있지만, class_attribute의 경우처럼(위 참조) 오버라이드할 수는 없습니다. 예를 들어 다음과 같습니다

module ActionView
  class Base
    cattr_accessor :field_error_proc, default: Proc.new {
      # ...
    } 
end
end

views에서 field_error_proc에 접근할 수 있습니다.

reader 인스턴스 메서드의 생성은 :instance_readerfalse로 설정하여 막을 수 있고, writer 인스턴스 메서드의 생성은 :instance_writerfalse로 설정하여 막을 수 있습니다. 두 메서드의 생성을 모두 막으려면 :instance_accessorfalse로 설정하면 됩니다. 모든 경우에서 값은 정확히 false여야 하며 거짓 값이면 안 됩니다.

module A
  class B
    # first_name instance reader가 생성되지 않습니다.
    cattr_accessor :first_name, instance_reader: false
    # last_name= instance writer가 생성되지 않습니다. 
    cattr_accessor :last_name, instance_writer: false
    # surname instance reader나 surname= writer가 생성되지 않습니다.
    cattr_accessor :surname, instance_accessor: false
  end
end

model은 mass-assignment로 인한 속성 설정을 방지하기 위해 :instance_accessorfalse로 설정하는 것이 유용할 수 있습니다.

active_support/core_ext/module/attribute_accessors.rb에 정의되어 있습니다.

4.2 Subclasses와 Descendants

4.2.1 subclasses

subclasses 메서드는 수신자의 subclass들을 반환합니다:

class C; end
C.subclasses # => []

class B < C; end
C.subclasses # => [B]

class A < B; end
C.subclasses # => [B]

class D < C; end 
C.subclasses # => [B, D]

위 코드는 subclasses가 클래스 C의 직계 하위 클래스들을 반환한다는 것을 보여줍니다. 클래스 A는 C의 하위 클래스가 아닌 B의 하위 클래스이기 때문에 목록에 포함되지 않습니다.

이러한 클래스들이 반환되는 순서는 명시되지 않습니다.

active_support/core_ext/class/subclasses.rb에 정의되어 있습니다.

4.2.2 descendants

descendants 메서드는 receiver보다 <인 모든 클래스들을 반환합니다:

class C; end
C.descendants # => []

class B < C; end
C.descendants # => [B]

class A < B; end
C.descendants # => [B, A]

class D < C; end 
C.descendants # => [B, A, D]

descendants 메서드는 클래스의 모든 자식 클래스를 반환합니다.

이러한 클래스들이 반환되는 순서는 명시되지 않았습니다.

active_support/core_ext/class/subclasses.rb에 정의되어 있습니다.

5 String에 대한 확장

5.1 Output Safety

5.1.1 동기

HTML template에 데이터를 삽입할 때는 각별한 주의가 필요합니다. 예를 들어, @review.title을 HTML 페이지에 그대로 삽입할 수는 없습니다. 리뷰 제목이 "Flanagan & Matz rules!"인 경우, 앰퍼샌드는 "&amp;"로 이스케이프되어야 하므로 출력이 제대로 형성되지 않습니다. 더욱이 애플리케이션에 따라서는 사용자가 악의적인 HTML을 주입할 수 있는 커다란 보안 취약점이 될 수 있습니다. 이러한 위험에 대한 자세한 내용은 Security 가이드의 cross-site scripting 섹션을 참조하세요.

5.1.2 Safe Strings

Active Support는 (html) safe string이라는 개념을 가지고 있습니다. safe string은 HTML에 그대로 삽입할 수 있다고 표시된 문자열입니다. 이스케이프 여부와 관계없이 신뢰할 수 있습니다.

문자열은 기본적으로 unsafe 로 간주됩니다:

"".html_safe? # => false

주어진 문자열에서 html_safe 메서드를 사용하여 안전한 문자열을 얻을 수 있습니다:

s = "".html_safe
s.html_safe? # => true

html_safe는 어떠한 이스케이핑도 수행하지 않으며, 단지 표명(assertion)일 뿐이라는 점을 이해하는 것이 중요합니다.

s = "<script>...</script>".html_safe
s.html_safe? # => true
s            # => "<script>...</script>"

특정 문자열에 html_safe를 호출하는 것이 안전한지 확인하는 것은 당신의 책임입니다.

안전한 문자열에 concat/<<를 사용하여 직접 추가하거나, +를 사용하여 추가할 경우, 결과는 안전한 문자열이 됩니다. 안전하지 않은 인수는 이스케이프됩니다:

"".html_safe + "<" # => "&lt;"

안전한 arguments는 직접 이어 붙여집니다:

"".html_safe + "<".html_safe # => "<"

이러한 메소드들은 일반적인 view에서는 사용되면 안 됩니다. unsafe한 값들은 자동으로 escape 처리됩니다:

<%= @review.title %> <%# 괜찮음, 필요한 경우 이스케이프 처리됨 %>

변환없이 그대로 삽입하려면 html_safe를 호출하는 대신 raw helper를 사용하세요:

<%= raw @cms.current_template %> <%# @cms.current_template를 있는 그대로 삽입 %>

또는 동일하게 <%==를 사용할 수 있습니다:

<%== @cms.current_template %> <%# @cms.current_template를 있는 그대로 삽입 %>

raw 헬퍼는 여러분을 위해 html_safe를 호출합니다:

def raw(stringish)
  stringish.to_s.html_safe
end

입력값을 직접 HTML에 출력합니다. 당신이 정확히 어떤 것을 출력하는지 알고 있을 때에만 이 메소드를 사용하세요. 절대로 사용자가 제공한 데이터를 그대로 출력하지 마세요.

active_support/core_ext/string/output_safety.rb에 정의되어 있습니다.

5.1.3 Transformation

경험적 규칙으로, 위에서 설명한 연결을 제외하고는 문자열을 변경할 수 있는 모든 메서드는 안전하지 않은 문자열을 반환합니다. 여기에는 downcase, gsub, strip, chomp, underscore 등이 포함됩니다.

gsub!와 같은 직접 변경 변환의 경우, 수신자 자체가 안전하지 않게 됩니다.

변환이 실제로 무언가를 변경했는지 여부와 관계없이 안전성 비트는 항상 손실됩니다.

5.1.4 Conversion and Coercion

안전한 문자열에서 to_s를 호출하면 안전한 문자열이 반환되지만, to_str로 강제 변환하면 안전하지 않은 문자열이 반환됩니다.

5.1.5 Copying

안전한 문자열에서 dup 또는 clone을 호출하면 안전한 문자열이 생성됩니다.

5.2 remove

remove 메서드는 패턴의 모든 출현을 제거합니다:

"Hello World".remove(/Hello /) # => "World"

destructive 버전인 String#remove!도 있습니다.

active_support/core_ext/string/filters.rb에 정의되어 있습니다.

5.3 squish

squish 메서드는 문자열의 앞뒤 공백을 제거하고, 연속된 공백을 각각 하나의 공백으로 대체합니다:

" \n  foo\n\r \t bar \n".squish # => "foo bar"

파괴적인 버전인 String#squish!도 있습니다.

ASCII와 Unicode 공백 모두를 처리한다는 점을 유의하세요.

active_support/core_ext/string/filters.rb에 정의되어 있습니다.

5.4 truncate

truncate 메소드는 주어진 length 이후를 자른 receiver의 복사본을 반환합니다:

"Oh dear! Oh dear! I shall be late!".truncate(20)
# => "Oh dear! Oh dear!..."

ellipsis:omission 옵션으로 원하는 대로 수정할 수 있습니다:

"Oh dear! Oh dear! I shall be late!".truncate(20, omission: "&hellip;")
# => "Oh dear! Oh &hellip;"

특히 truncation은 생략 문자열의 길이를 고려한다는 점에 유의하세요.

:separator를 전달하여 문자열을 자연스러운 구분점에서 잘라낼 수 있습니다:

"Oh dear! Oh dear! I shall be late!".truncate(18)
# => "Oh dear! Oh dea..."
"Oh dear! Oh dear! I shall be late!".truncate(18, separator: " ")
# => "Oh dear! Oh..."

길이가 18자를 넘는 문자열에 대하여 첫 번째는 18자에서 자르고, 두 번째는 공백을 구분자로 하여 18자 이내에서 자릅니다.

option :separator는 regexp일 수 있습니다.

"Oh dear! Oh dear! I shall be late!".truncate(18, separator: /\s/) 
# => "Oh dear! Oh..."

이렇게 합니다! separator로 공백을 지정하면 단어가 중간에 잘리지 않습니다.

위 예시에서 "dear"가 먼저 잘리지만 :separator가 이를 방지합니다.

active_support/core_ext/string/filters.rb에 정의되어 있습니다.

5.5 truncate_bytes

truncate_bytes 메서드는 최대 bytesize 바이트로 잘린 수신자의 복사본을 반환합니다:

"👍👍👍👍".truncate_bytes(15)
# => "👍👍👍…"

생략 기호는 :omission 옵션을 사용해서 사용자 정의할 수 있습니다:

"👍👍👍👍".truncate_bytes(15, omission: "🖖")
# => "👍👍🖖"

active_support/core_ext/string/filters.rb에 정의되어 있습니다.

5.6 truncate_words

truncate_words 메서드는 지정된 단어 수만큼 잘라낸 receiver의 복사본을 반환합니다:

"Oh dear! Oh dear! I shall be late!".truncate_words(4)
# => "Oh dear! Oh dear!..."

생략 부호는 :omission 옵션으로 사용자 정의할 수 있습니다:

"Oh dear! Oh dear! I shall be late!".truncate_words(4, omission: "&hellip;")
# => "Oh dear! Oh dear!&hellip;"

문자열을 자연스러운 끊김 지점에서 잘라내려면 :separator를 전달하세요:

"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: "!")
# => "Oh dear! Oh dear! I shall be late..."

이것은 단어 3개로 잘라내고, "!" 구분자를 사용합니다.

:separator 옵션은 regexp가 될 수 있습니다:

"Oh dear! Oh dear! I shall be late!".truncate_words(4, separator: /\s/)
# => "Oh dear! Oh dear!..."

active_support/core_ext/string/filters.rb에 정의되어 있습니다.

5.7 inquiry

inquiry 메서드는 문자열을 StringInquirer 객체로 변환하여 동등성 검사를 더 보기좋게 해줍니다.

"production".inquiry.production? # => true
"active".inquiry.inactive?       # => false

active_support/core_ext/string/inquiry.rb에 정의되어 있습니다.

5.8 starts_with?ends_with?

Active Support는 String#start_with?String#end_with?의 3인칭 별칭을 정의합니다:

"foo".starts_with?("f") # => true  
"foo".ends_with?("o")   # => true

active_support/core_ext/string/starts_ends_with.rb에 정의되어 있습니다.

5.9 strip_heredoc

메서드 strip_heredoc는 heredocs의 들여쓰기를 제거합니다.

예를 들어

if options[:usage]
  puts <<-USAGE.strip_heredoc
    이 명령은 이런저런 작업을 수행합니다.

    지원되는 옵션들:
      -h         이 메시지
      ...
  USAGE
end

사용자는 사용법 메시지가 왼쪽 여백에 맞춰진 것을 볼 수 있습니다.

기술적으로는, 전체 문자열에서 들여쓰기가 가장 적은 줄을 찾아서 그만큼의 선행 공백을 제거합니다.

active_support/core_ext/string/strip.rb에 정의되어 있습니다.

5.10 indent

indent 메서드는 수신자의 각 줄을 들여씁니다:

<<EOS.indent(2)
def some_method 
  some_code
end
EOS
# => 
  def some_method
    some_code
  end

두 번째 인자인 indent_string은 사용할 들여쓰기 문자열을 지정합니다. 기본값은 nil이며, 이는 첫 번째로 들여쓰기가 된 라인을 보고 적절한 추측을 하되, 들여쓰기가 없는 경우 공백을 대신 사용하도록 메서드에 지시합니다.

"  foo".indent(2)        # => "    foo"            # 들여쓰기 공백 2개 추가 
"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar" # 각 줄마다 탭 2개씩 추가
"foo".indent(2, "\t")    # => "\t\tfoo"           # 탭 문자로 들여쓰기 2개 추가

indent_string은 일반적으로 공백이나 탭 하나이지만, 어떤 문자열이든 될 수 있습니다.

세 번째 인자인 indent_empty_lines는 빈 줄도 들여쓰기 할지를 결정하는 플래그입니다. 기본값은 false입니다.

"foo\n\nbar".indent(2)            # => "  foo\n\n  bar"
"foo\n\nbar".indent(2, nil, true) # => "  foo\n  \n  bar"

indent! 메서드는 들여쓰기를 현재 위치에서 수행합니다.

active_support/core_ext/string/indent.rb에 정의되어 있습니다.

5.11 Access

5.11.1 at(position)

at 메소드는 문자열에서 position 위치의 문자를 반환합니다:

"hello".at(0)  # => "h"
"hello".at(4)  # => "o"
"hello".at(-1) # => "o"
"hello".at(10) # => nil

active_support/core_ext/string/access.rb에 정의되어 있습니다.

5.11.2 from(position)

from 메서드는 position 위치에서 시작하는 문자열의 부분 문자열을 반환합니다:

"hello".from(0)  # => "hello"
"hello".from(2)  # => "llo" 
"hello".from(-2) # => "lo"
"hello".from(10) # => nil

active_support/core_ext/string/access.rb에 정의되어 있습니다.

5.11.3 to(position)

to 메서드는 문자열의 처음부터 position 위치까지의 부분 문자열을 반환합니다:

"hello".to(0)  # => "h"
"hello".to(2)  # => "hel" 
"hello".to(-2) # => "hell"
"hello".to(10) # => "hello"

active_support/core_ext/string/access.rb에 정의되어 있습니다.

5.11.4 first(limit = 1)

first 메서드는 문자열의 처음부터 limit 개수만큼의 문자를 포함하는 부분 문자열을 반환합니다.

str.first(n) 호출은 n > 0일 때 str.to(n-1)와 동일하며, n == 0일 때는 빈 문자열을 반환합니다.

active_support/core_ext/string/access.rb에 정의되어 있습니다.

5.11.5 last(limit = 1)

last 메서드는 문자열의 마지막부터 limit 개수만큼의 문자를 포함하는 부분 문자열을 반환합니다.

str.last(n) 호출은 n > 0일 때 str.from(-n)과 동일하며, n == 0일 때는 빈 문자열을 반환합니다.

active_support/core_ext/string/access.rb에 정의되어 있습니다.

5.12 Inflections

5.12.1 pluralize

pluralize 메서드는 인자로 받은 단어의 복수형을 반환합니다:

"table".pluralize     # => "tables" 
"ruby".pluralize      # => "rubies"
"equipment".pluralize # => "equipment" 

이전 예제에서 볼 수 있듯이, Active Support는 일부 불규칙 복수형과 불가산 명사를 알고 있습니다. 내장된 규칙은 config/initializers/inflections.rb에서 확장할 수 있습니다. 이 파일은 기본적으로 rails new 명령어에 의해 생성되며 주석에 설명이 포함되어 있습니다.

pluralize는 선택적으로 count 매개변수를 받을 수 있습니다. count == 1인 경우 단수형이 반환되고, count의 다른 모든 값에 대해서는 복수형이 반환됩니다:

"dude".pluralize(0) # => "dudes"
"dude".pluralize(1) # => "dude"
"dude".pluralize(2) # => "dudes"

Active Record는 모델에 대응하는 기본 테이블 이름을 계산하기 위해 이 메소드를 사용합니다.

# active_record/model_schema.rb
def undecorated_table_name(model_name)
  table_name = model_name.to_s.demodulize.underscore # 모듈 부분을 제거하고 _ 를 사용한 방식으로 변환
  pluralize_table_names ? table_name.pluralize : table_name # 설정에 따라 테이블 이름을 복수형으로 변환
end

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.2 singularize

singularize 메서드는 pluralize의 반대입니다:

"tables".singularize    # => "table"
"rubies".singularize    # => "ruby" 
"equipment".singularize # => "equipment"

Associations는 이 메서드를 사용하여 대응되는 기본 associated class의 이름을 계산합니다:

# active_record/reflection.rb 
def derive_class_name
  class_name = name.to_s.camelize
  class_name = class_name.singularize if collection? 
  class_name
end

// class_name을 도출합니다. name을 문자열로 변환하여 camelCase로 변경하고, collection인 경우 단수형으로 변환합니다. class_name을 반환합니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.3 camelize

camelize 메서드는 리시버를 camel case로 반환합니다:

"product".camelize    # => "Product"
"admin_user".camelize # => "AdminUser"

경험상 이 메서드는 슬래시로 namespace를 구분하는 경로를 Ruby 클래스나 모듈 이름으로 변환하는 메서드라고 생각하시면 됩니다:

"backoffice/session".camelize # => "Backoffice::Session"

예를 들어, Action Pack은 특정 session store를 제공하는 클래스를 로드하기 위해 이 메서드를 사용합니다:

# action_controller/metal/session_management.rb
def session_store=(store) 
  @@session_store = store.is_a?(Symbol) ?
    ActionDispatch::Session.const_get(store.to_s.camelize) :
    store
end

camelize는 선택적 인자를 받습니다. :upper(기본값) 또는 :lower가 될 수 있습니다. :lower를 사용하면 첫 글자가 소문자가 됩니다:

"visual_effect".camelize(:lower) # => "visualEffect"

JavaScript와 같이 해당 관례를 따르는 언어에서 메서드 이름을 계산하는데 유용할 수 있습니다.

일반적으로 camelizeunderscore의 반대 개념으로 생각할 수 있지만, 예외도 있습니다: "SSLError".underscore.camelize"SslError"를 반환합니다. 이러한 경우를 지원하기 위해 Active Support에서는 config/initializers/inflections.rb에서 약어를 지정할 수 있습니다:

ActiveSupport::Inflector.inflections do |inflect|
  inflect.acronym "SSL" 
end

"SSLError".underscore.camelize # => "SSLError"

camelizecamelcase의 별칭입니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.4 underscore

underscore 메서드는 camel case에서 경로로 변환하는 반대 방향으로 동작합니다:

"Product".underscore   # => "product"
"AdminUser".underscore # => "admin_user"

"::"를 "/"로 다시 변환합니다:

"Backoffice::Session".underscore # => "backoffice/session"

그리고 소문자로 시작하는 문자열을 이해합니다:

"visualEffect".underscore # => "visual_effect"

underscore는 인자를 받지 않습니다.

Rails는 controller 클래스의 소문자 이름을 가져오기 위해 underscore를 사용합니다:

# actionpack/lib/abstract_controller/base.rb
# controller_path는 controller 클래스 이름에서 "Controller" 접미사를 제거하고 
# 결과를 underscore case로 변환합니다.
def controller_path
  @controller_path ||= name.delete_suffix("Controller").underscore
end

예를 들어, params[:controller]에서 얻는 값입니다.

일반적으로 underscorecamelize의 반대 역할을 한다고 생각할 수 있지만, 항상 그렇지는 않습니다. 예를 들어, "SSLError".underscore.camelize"SslError"를 반환합니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.5 titleize

titleize 메서드는 수신자의 단어들을 대문자로 변환합니다:

"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize     # => "Fermat's Enigma"

titleizetitlecase의 별칭입니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.6 dasherize

dasherize 메서드는 수신자의 밑줄을 대시로 바꿉니다:

"name".dasherize         # => "name"
"contact_data".dasherize # => "contact-data"

모델의 XML serializer는 node 이름을 dasherize하기 위해 이 메서드를 사용합니다:

# active_model/serializers/xml.rb
def reformat_name(name)
  name = name.camelize if camelize?  # camelize? 가 true면 이름을 camelCase로 변환
  dasherize? ? name.dasherize : name # dasherize?가 true면 이름을 dash로 구분, 아니면 원래 이름 반환
end

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.7 demodulize

정규화된 상수명이 포함된 문자열이 주어졌을 때, demodulize은 가장 오른쪽에 있는 상수명 자체를 반환합니다:

"Product".demodulize                        # => "Product"
"Backoffice::UsersController".demodulize    # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"::Inflections".demodulize                  # => "Inflections" 
"".demodulize                               # => ""

주어진 문자열에서 namespace를 제거하고 마지막 요소를 반환합니다.

Active Record는 이 메서드를 counter cache 열의 이름을 계산하는 데 사용합니다:

# active_record/reflection.rb
def counter_cache_column
  if options[:counter_cache] == true
    "#{active_record.name.demodulize.underscore.pluralize}_count"
  elsif options[:counter_cache]
    options[:counter_cache] 
  end
end

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.8 deconstantize

정규화된 상수 참조 표현식이 있는 문자열이 주어지면, deconstantize는 가장 오른쪽 세그먼트를 제거하며, 일반적으로 상수가 포함된 컨테이너의 이름을 남깁니다:

"Product".deconstantize                        # => ""
"Backoffice::UsersController".deconstantize    # => "Backoffice" 
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.9 parameterize

parameterize 메서드는 예쁜 URL에서 사용할 수 있는 방식으로 receiver를 정규화합니다.

"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"

문자열의 대소문자를 유지하려면 preserve_case 인자를 true로 설정하세요. 기본적으로 preserve_case는 false로 설정되어 있습니다.

"John Smith".parameterize(preserve_case: true) # => "John-Smith" 
"Kurt Gödel".parameterize(preserve_case: true) # => "Kurt-Godel"

커스텀 separator를 사용하려면, separator 인자를 오버라이드하세요.

"John Smith".parameterize(separator: "_") # => "john_smith"
"Kurt Gödel".parameterize(separator: "_") # => "kurt_godel"

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.10 tableize

tableize 메서드는 underscore 다음에 pluralize를 적용한 것입니다.

"Person".tableize      # => "people"
"Invoice".tableize     # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"

일반적인 규칙으로, tableize는 간단한 경우에 주어진 모델에 대응하는 테이블 이름을 반환합니다. Active Record의 실제 구현은 단순한 tableize가 아닙니다. 클래스 이름을 demodulize하고 반환되는 문자열에 영향을 줄 수 있는 몇 가지 옵션을 확인하기 때문입니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.11 classify

classify 메서드는 tableize의 반대입니다. 테이블 이름에 대응하는 클래스 이름을 제공합니다:

"people".classify        # => "Person"
"invoices".classify      # => "Invoice" 
"invoice_lines".classify # => "InvoiceLine"

이 메서드는 qualified table name들을 인식합니다:

"highrise_production.companies".classify # => "Company"

classify는 클래스 이름을 문자열로 반환합니다. 여기에 constantize를 호출하면 실제 class object를 얻을 수 있습니다. 이는 다음에 설명됩니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.12 constantize

constantize 메서드는 receiver에서 constant reference expression을 해석합니다:

"Integer".constantize # => Integer

module M
  X = 1 
end
"M::X".constantize # => 1

문자열이 알려진 상수로 평가되지 않거나 그 내용이 유효한 상수 이름조차 아닌 경우, constantizeNameError를 발생시킵니다.

constantize에 의한 상수 이름 해석은 선행하는 "::"가 없더라도 항상 최상위 레벨의 Object에서 시작합니다.

X = :in_Object
module M
  X = :in_M

  X                 # => :in_M
  "::X".constantize # => :in_Object 
  "X".constantize   # => :in_Object (!) # 마지막 동작은 예상하지 못한 것일 수 있습니다
end

따라서, 일반적으로 실제 상수가 평가되는 동일한 지점에서 Ruby가 수행하는 것과 동일하지 않습니다.

Mailer 테스트 케이스는 constantize를 사용하여 테스트 클래스의 이름으로부터 테스트할 mailer를 가져옵니다:

# action_mailer/test_case.rb
def determine_default_mailer(name)
  name.delete_suffix("Test").constantize
rescue NameError => e
  raise NonInferrableMailerError.new(name)
end

기본 mailer를 결정하기 위한 메서드입니다. name 파라미터에서 "Test" 접미사를 제거하고 constantize 하려 시도합니다. 이 과정에서 NameError가 발생하면 NonInferrableMailerError를 발생시킵니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.13 humanize

humanize 메서드는 최종 사용자에게 표시하기 위해 속성 이름을 수정합니다.

구체적으로 다음과 같은 변환을 수행합니다:

  • 인자에 human inflection 규칙을 적용합니다.
  • 선행 밑줄이 있다면 삭제합니다.
  • "_id" 접미사가 있다면 제거합니다.
  • 밑줄이 있다면 공백으로 대체합니다.
  • 두문자어를 제외한 모든 단어를 소문자로 변환합니다.
  • 첫 단어를 대문자로 변환합니다.

첫 단어의 대문자 변환은 :capitalize 옵션을 false로 설정하여 비활성화할 수 있습니다(기본값은 true입니다).

"name".humanize                         # => "Name"
"author_id".humanize                    # => "Author"
"author_id".humanize(capitalize: false) # => "author"
"comments_count".humanize               # => "Comments count" 
"_id".humanize                          # => "Id"

SSL이 약어로 정의된 경우:

"ssl_error".humanize # => "SSL error"

helper 메서드인 full_messages는 attribute 이름을 포함하기 위한 대체 수단으로 humanize를 사용합니다:

def full_messages
  map { |attribute, message| full_message(attribute, message) }
end

def full_message
  # ...
  attr_name = attribute.to_s.tr(".", "_").humanize  
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
  # ...
end

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.14 foreign_key

foreign_key 메서드는 클래스 이름으로부터 foreign key 컬럼 이름을 반환합니다. 이를 위해 demodulize, underscore 처리를 하고 "_id"를 추가합니다:

"User".foreign_key           # => "user_id"  
"InvoiceLine".foreign_key    # => "invoice_line_id"
"Admin::Session".foreign_key # => "session_id"

"_id"의 밑줄을 원하지 않는 경우 false 인자를 전달하세요:

"User".foreign_key(false) # => "userid"

Association들은 이 메서드를 사용하여 foreign key를 추론합니다. 예를 들어 has_onehas_many는 다음과 같이 수행됩니다:

# active_record/associations.rb
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key

위 코드는 foreign_key를 options에서 가져오거나, reflection의 active_record 이름에서 foreign_key를 생성합니다.

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.12.15 upcase_first

upcase_first 메서드는 수신자의 첫 번째 글자를 대문자로 변환합니다:

"employee salary".upcase_first # => "Employee salary" 
"".upcase_first                # => ""

active_support/core_ext/string/inflections.rb에 정의됨.

5.12.16 downcase_first

downcase_first 메서드는 문자열 시작의 첫 글자를 소문자로 변환합니다:

"If I had read Alice in Wonderland".downcase_first # => "if I had read Alice in Wonderland" 
"".downcase_first                                  # => ""

active_support/core_ext/string/inflections.rb에 정의되어 있습니다.

5.13 변환

5.13.1 to_date, to_time, to_datetime

to_date, to_time, to_datetime 메서드들은 기본적으로 Date._parse를 감싸는 편의 래퍼입니다:

"2010-07-27".to_date              # => 화요일, 2010년 7월 27일
"2010-07-27 23:37:00".to_time     # => 2010-07-27 23:37:00 +0200
"2010-07-27 23:37:00".to_datetime # => 화요일, 2010년 7월 27일 23:37:00 +0000

to_time은 선택적 인자로 :utc 또는 :local을 받아서 어떤 timezone으로 시간을 변환할지 지정할 수 있습니다:

"2010-07-27 23:42:00".to_time(:utc)   # => 2010-07-27 23:42:00 UTC 
"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200

기본값은 :local입니다.

자세한 내용은 Date._parse 문서를 참고하세요.

세 메서드 모두 빈 수신자에 대해 nil을 반환합니다.

active_support/core_ext/string/conversions.rb에 정의되어 있습니다.

6 Symbol 확장

6.1 starts_with?ends_with?

Active Support는 Symbol#start_with?Symbol#end_with?의 3인칭 별칭을 정의합니다:

:foo.starts_with?("f") # => true
:foo.ends_with?("o")   # => true

active_support/core_ext/symbol/starts_ends_with.rb에 정의되어 있습니다.

7 Numeric에 대한 확장

7.1 Bytes

모든 숫자들은 다음 메소드에 응답합니다:

이들은 1024를 변환 계수로 사용하여 해당하는 bytes 값을 반환합니다:

2.kilobytes   # => 2048
3.megabytes   # => 3145728  
3.5.gigabytes # => 3758096384.0
-4.exabytes   # => -4611686018427387904

단수형은 다음과 같이 말할 수 있도록 별칭이 지정되어 있습니다:

1.megabyte # => 1048576

active_support/core_ext/numeric/bytes.rb에 정의되어 있습니다.

7.2 Time

다음의 메서드들:

45.minutes + 2.hours + 4.weeks와 같은 시간 선언과 계산을 가능하게 합니다. 이들의 반환값은 Time 객체에 더하거나 뺄 수도 있습니다.

이러한 메서드들은 정확한 날짜 계산을 위해 from_now, ago 등과 조합할 수 있습니다. 예를 들면:

# Time.current.advance(days: 1)와 동일
1.day.from_now

# Time.current.advance(weeks: 2)와 동일 
2.weeks.from_now

# Time.current.advance(days: 4, weeks: 5)와 동일
(4.days + 5.weeks).from_now

다른 기간에 대해서는 Integer의 time extensions를 참조하세요.

active_support/core_ext/numeric/time.rb에 정의되어 있습니다.

7.3 포맷팅

숫자를 다양한 방식으로 포맷팅할 수 있습니다.

전화번호 형식의 문자열로 숫자를 표현:

5551234.to_fs(:phone)
# => 555-1234
1235551234.to_fs(:phone) 
# => 123-555-1234
1235551234.to_fs(:phone, area_code: true)
# => (123) 555-1234
1235551234.to_fs(:phone, delimiter: " ") 
# => 123 555 1234
1235551234.to_fs(:phone, area_code: true, extension: 555)
# => (123) 555-1234 x 555
1235551234.to_fs(:phone, country_code: 1)
# => +1-123-555-1234

숫자를 화폐 형식의 문자열로 변환:

[NOTE] number_to_currency와 number_to_human은 Float와 BigDecimal 타입 간의 수치 불일치가 발생할 수 있습니다. 이는 부동소수점과 십진법 계산의 차이 때문에 발생합니다. currencies.yml 파일에서 Currency의 정확도를 변경하는 것이 도움될 수 있습니다.

1234567890.50.to_fs(:currency)                 # => $1,234,567,890.50
1234567890.506.to_fs(:currency)                # => $1,234,567,890.51 
1234567890.506.to_fs(:currency, precision: 3)  # => $1,234,567,890.506

숫자를 percentage(백분율) 문자열로 표현:

100.to_fs(:percentage)
# => 100.000%
100.to_fs(:percentage, precision: 0)
# => 100%
1000.to_fs(:percentage, delimiter: ".", separator: ",")
# => 1.000,000%
302.24398923423.to_fs(:percentage, precision: 5) 
# => 302.24399%

숫자를 구분된 형태의 문자열로 표현합니다:

12345678.to_fs(:delimited)                     # => 12,345,678
12345678.05.to_fs(:delimited)                  # => 12,345,678.05
12345678.to_fs(:delimited, delimiter: ".")     # => 12.345.678 
12345678.to_fs(:delimited, delimiter: ",")     # => 12,345,678
12345678.05.to_fs(:delimited, separator: " ")  # => 12,345,678 05

숫자를 정밀도에 맞춰 반올림하여 문자열로 표현합니다:

111.2345.to_fs(:rounded)                     # => 111.235
111.2345.to_fs(:rounded, precision: 2)       # => 111.23
13.to_fs(:rounded, precision: 5)             # => 13.00000
389.32314.to_fs(:rounded, precision: 0)      # => 389
111.2345.to_fs(:rounded, significant: true)  # => 111

숫자를 사람이 읽을 수 있는 byte 단위의 문자열로 표현합니다:

123.to_fs(:human_size)                  # => 123 Bytes
1234.to_fs(:human_size)                 # => 1.21 KB
12345.to_fs(:human_size)                # => 12.1 KB
1234567.to_fs(:human_size)              # => 1.18 MB  
1234567890.to_fs(:human_size)           # => 1.15 GB
1234567890123.to_fs(:human_size)        # => 1.12 TB
1234567890123456.to_fs(:human_size)     # => 1.1 PB
1234567890123456789.to_fs(:human_size)  # => 1.07 EB

숫자를 사람이 읽을 수 있는 단어로 된 문자열로 변환:

123.to_fs(:human)               # => "123" 
1234.to_fs(:human)              # => "1.23 천"
12345.to_fs(:human)             # => "12.3 천" 
1234567.to_fs(:human)           # => "1.23 백만"
1234567890.to_fs(:human)        # => "1.23 십억"
1234567890123.to_fs(:human)     # => "1.23 조"
1234567890123456.to_fs(:human)  # => "1.23 천조"

active_support/core_ext/numeric/conversions.rb에 정의되어 있습니다.

8 Integer 확장

8.1 multiple_of?

multiple_of? 메서드는 정수가 인수의 배수인지를 테스트합니다:

2.multiple_of?(1) # => true
1.multiple_of?(2) # => false

active_support/core_ext/integer/multiple.rb에 정의되어 있습니다.

8.2 ordinal

ordinal 메서드는 receiver integer에 해당하는 서수 접미사 문자열을 반환합니다:

1.ordinal    # => "st"
2.ordinal    # => "nd" 
53.ordinal   # => "rd"
2009.ordinal # => "th"
-21.ordinal  # => "st" 
-134.ordinal # => "th"

서수 접미사를 반환합니다.

active_support/core_ext/integer/inflections.rb에 정의되어 있습니다.

8.3 ordinalize

ordinalize 메서드는 정수를 서수로 나타내는 문자열을 반환합니다. 반면에 ordinal 메서드는 서수 접미사만 반환합니다.

1.ordinalize    # => "1st"
2.ordinalize    # => "2nd" 
53.ordinalize   # => "53rd"
2009.ordinalize # => "2009th"
-21.ordinalize  # => "-21st"
-134.ordinalize # => "-134th"

참고: ordinalize 메소드는 숫자를 서수 형태의 문자열로 변환합니다.

active_support/core_ext/integer/inflections.rb에 정의되어 있습니다.

8.4 Time

다음 메소드들:

4.months + 5.years와 같은 시간 선언과 계산을 가능하게 합니다. 이들의 반환값은 Time 객체에 더하거나 뺄 수도 있습니다.

이러한 메소드들은 정확한 날짜 계산을 위해 from_now, ago 등과 조합할 수 있습니다. 예를 들면:

# Time.current.advance(months: 1)와 동일
1.month.from_now

# Time.current.advance(years: 2)와 동일 
2.years.from_now

# Time.current.advance(months: 4, years: 5)와 동일
(4.months + 5.years).from_now

다른 duration에 대해서는 Numeric의 time extension을 참고하세요.

active_support/core_ext/integer/time.rb에 정의되어 있습니다.

9 Extensions to BigDecimal

9.1 to_s

메서드 to_s는 기본 지정자로 "F"를 제공합니다. 이는 단순히 to_s를 호출하면 공학적 표기법 대신 부동 소수점 표현이 반환된다는 것을 의미합니다:

BigDecimal(5.00, 6).to_s       # => "5.0"

공학적 표기법도 여전히 지원됩니다:

BigDecimal(5.00, 6).to_s("e")  # => "0.5E1"

10 Enumerable의 확장

10.1 index_by

index_by 메서드는 enumerable 요소들을 어떤 key에 의해 인덱싱된 hash를 생성합니다.

컬렉션을 순회하면서 각 요소를 블록에 전달합니다. 요소는 블록이 반환한 값으로 키가 지정됩니다:

invoices.index_by(&:number)
# => {"2009-032" => <Invoice ...>, "2009-008" => <Invoice ...>, ...}

WARN. key는 일반적으로 고유해야 합니다. 블록이 서로 다른 요소에 대해 동일한 값을 반환하는 경우 해당 key에 대한 collection이 생성되지 않습니다. 마지막 항목이 우선됩니다.

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

10.2 index_with

index_with 메서드는 열거형의 요소들을 key로 하는 hash를 생성합니다. value는 전달된 기본값이거나 블록에서 반환된 값입니다.

post = Post.new(title: "hey there", body: "what's up?")

%i( title body ).index_with { |attr_name| post.public_send(attr_name) }
# => { title: "hey there", body: "what's up?" }

# WEEKDAYS와 Interval.all_day를 매핑하여 각 요일에 하루 전체 시간 간격을 지정 
WEEKDAYS.index_with(Interval.all_day) 
# => { monday: [ 0, 1440 ], … }

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

10.3 many?

many? 메서드는 collection.size > 1의 축약형입니다:

<% if pages.many? %>
  <%= pagination_links %>
<% end %>

여러 페이지가 있는 경우 페이지네이션 링크를 표시합니다.

선택적 블록이 주어진 경우, many?는 true를 반환하는 요소만을 고려합니다:

@see_more = videos.many? { |video| video.category == params[:category] }

many?는 Collection이 2개 이상의 element를 가지고 있고 모든 element가 블록 조건을 만족하면 true를 반환합니다.

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

10.4 exclude?

exclude? 술어는 주어진 객체가 컬렉션에 속하지 않는지 테스트합니다. 이는 내장된 include?의 부정입니다:

visited가 node를 포함하지 않는 경우 to_visit에 node를 추가

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

10.5 including

including 메서드는 전달된 요소들을 포함하는 새로운 enumerable을 반환합니다:

[ 1, 2, 3 ].including(4, 5)                    # => [ 1, 2, 3, 4, 5 ]
["David", "Rafael"].including %w[ Aaron Todd ] # => ["David", "Rafael", "Aaron", "Todd"]

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

10.6 excluding

excluding 메서드는 지정된 요소들이 제거된 enumerable의 복사본을 반환합니다:

["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]

excludingwithout의 별칭입니다.

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

10.7 pluck

pluck 메서드는 각 요소에서 주어진 키를 추출합니다:

[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) # => ["David", "Rafael", "Aaron"]
[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) # => [[1, "David"], [2, "Rafael"]]

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

10.8 pick

[pick][Enumerable#pick] 메소드는 첫 번째 요소에서 주어진 키를 추출합니다:

[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name) # => "David" 
[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name) # => [1, "David"]

active_support/core_ext/enumerable.rb에 정의되어 있습니다.

11 Array에 대한 확장

11.1 접근하기

Active Support는 배열에 접근하는 특정 방법을 쉽게 만들기 위해 배열의 API를 보강합니다. 예를 들어, to는 전달된 인덱스의 요소까지의 하위 배열을 반환합니다:

%w(a b c d).to(2) # => ["a", "b", "c"] 
[].to(7)          # => [] 

비슷하게, from은 전달된 인덱스의 요소부터 끝까지의 나머지 부분을 반환합니다. 만약 인덱스가 배열의 길이보다 크다면, 빈 배열을 반환합니다.

%w(a b c d).from(2)  # => ["c", "d"]
%w(a b c d).from(10) # => []
[].from(0)           # => []

메서드 including은 전달된 요소들을 포함하는 새로운 배열을 반환합니다:

[ 1, 2, 3 ].including(4, 5)          # => [ 1, 2, 3, 4, 5 ]
[ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ]

메서드 excluding은 지정한 요소들을 제외한 Array의 복사본을 반환합니다. 이것은 성능상의 이유로 Array#reject 대신에 Array#-를 사용하는 Enumerable#excluding의 최적화 버전입니다.

["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]
[ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ])                  # => [ [ 0, 1 ] ]

second, third, fourth, fifth 메서드는 해당 위치의 요소를 반환하며, second_to_lastthird_to_last도 마찬가지입니다(firstlast는 내장 메서드입니다). 모두의 긍정적인 기여와 사회적 지혜 덕분에 forty_two도 사용할 수 있습니다.

%w(a b c d).third # => "c"
%w(a b c d).fifth # => nil

active_support/core_ext/array/access.rb에 정의되어 있습니다.

11.2 추출하기

extract! 메소드는 블록이 true를 반환하는 요소들을 제거하고 반환합니다. 블록이 주어지지 않으면 Enumerator를 반환합니다.

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]

active_support/core_ext/array/extract.rb에 정의되어 있습니다.

11.3 Options Extraction

메서드 호출의 마지막 인자가 해시일 때 (&block 인자는 제외하고), Ruby는 대괄호를 생략할 수 있게 해줍니다:

User.exists?(email: params[:email])

이런 문법적 단순화(syntactic sugar)는 Rails에서 너무 많은 위치 기반 인수를 피하기 위해 많이 사용되며, 대신 named parameter를 모방하는 인터페이스를 제공합니다. 특히 options를 위해 마지막에 hash를 사용하는 것이 매우 관용적인 방식입니다.

하지만 메서드가 가변 개수의 인수를 기대하고 선언부에 *를 사용하는 경우, 이러한 options hash는 인수 배열의 한 항목이 되어 본래의 역할을 잃게 됩니다.

이러한 경우에는 extract_options!를 사용하여 options hash를 특별하게 처리할 수 있습니다. 이 메서드는 배열의 마지막 항목의 타입을 확인합니다. 만약 해당 항목이 hash라면 이를 pop하여 반환하고, 그렇지 않으면 빈 hash를 반환합니다.

예를 들어 caches_action 컨트롤러 매크로의 정의를 살펴보겠습니다:

def caches_action(*actions)
  return unless cache_configured?
  options = actions.extract_options!
  # ...
end

이 메서드는 임의의 개수의 action 이름들과 마지막 인자로 옵션 hash를 선택적으로 받습니다. extract_options!를 호출하면 options hash를 얻고 actions에서 간단하고 명시적인 방법으로 제거할 수 있습니다.

active_support/core_ext/array/extract_options.rb에 정의되어 있습니다.

11.4 변환

11.4.1 to_sentence

to_sentence 메서드는 배열을 해당 항목들을 나열하는 문장을 포함한 문자열로 변환합니다:

%w().to_sentence                # => ""
%w(Earth).to_sentence           # => "Earth"
%w(Earth Wind).to_sentence      # => "Earth와 Wind"
%w(Earth Wind Fire).to_sentence # => "Earth, Wind, 그리고 Fire"

이 메서드는 세 가지 옵션을 받습니다:

  • :two_words_connector: 길이가 2인 배열에 사용됩니다. 기본값은 " and "입니다.
  • :words_connector: 3개 이상의 요소를 가진 배열에서 마지막 두 요소를 제외한 요소들을 연결할 때 사용됩니다. 기본값은 ", "입니다.
  • :last_word_connector: 3개 이상의 요소를 가진 배열에서 마지막 항목들을 연결할 때 사용됩니다. 기본값은 ", and "입니다.

이러한 옵션들의 기본값은 지역화할 수 있으며, 해당 키는 다음과 같습니다:

옵션 I18n 키
:two_words_connector support.array.two_words_connector
:words_connector support.array.words_connector
:last_word_connector support.array.last_word_connector

active_support/core_ext/array/conversions.rb에 정의되어 있습니다.

11.4.2 to_fs

to_fs 메서드는 기본적으로 to_s처럼 동작합니다.

하지만 배열이 id에 응답하는 항목들을 포함하고 있다면, :db 심볼을 인자로 전달할 수 있습니다. 이는 일반적으로 Active Record 객체의 컬렉션에서 사용됩니다. 반환되는 문자열은 다음과 같습니다:

[].to_fs(:db)            # => "null"
[user].to_fs(:db)        # => "8456" 
invoice.lines.to_fs(:db) # => "23,567,556,12"

위 예제의 integers는 각각의 id 호출에서 가져오는 것으로 가정됩니다.

active_support/core_ext/array/conversions.rb에 정의되어 있습니다.

11.4.3 to_xml

to_xml 메서드는 receiver를 XML로 표현한 문자열을 반환합니다:

Contributor.limit(2).order(:rank).to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors type="array">
#   <contributor>
#     <id type="integer">4356</id>
#     <name>Jeremy Kemper</name>
#     <rank type="integer">1</rank> 
#     <url-id>jeremy-kemper</url-id>
#   </contributor>
#   <contributor>
#     <id type="integer">4404</id>
#     <name>David Heinemeier Hansson</name>
#     <rank type="integer">2</rank>
#     <url-id>david-heinemeier-hansson</url-id>
#   </contributor>
# </contributors>

이를 위해 각각의 item에 차례대로 to_xml을 보내고 결과를 root node 아래에 수집합니다. 모든 item은 to_xml에 응답해야 하며, 그렇지 않으면 exception이 발생합니다.

기본적으로 root element의 이름은 첫 번째 item의 class 이름을 복수형으로 표기하고 underscore와 dash를 추가한 것입니다. 단, 나머지 element들이 해당 타입에 속하고(is_a?로 확인) hash가 아닌 경우에만 해당됩니다. 위의 예시에서는 "contributors"가 됩니다.

만약 첫 번째 element의 타입에 속하지 않는 element가 있다면 root node는 "objects"가 됩니다:

[Contributor.first, Commit.first].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
#   <object>
#     <id type="integer">4583</id>
#     <name>Aaron Batalion</name> 
#     <rank type="integer">53</rank>
#     <url-id>aaron-batalion</url-id>
#   </object>
#   <object>
#     <author>Joshua Peek</author>
#     <authored-timestamp type="datetime">2009-09-02T16:44:36Z</authored-timestamp>
#     <branch>origin/master</branch>
#     <committed-timestamp type="datetime">2009-09-02T16:44:36Z</committed-timestamp>
#     <committer>Joshua Peek</committer>
#     <git-show nil="true"></git-show>
#     <id type="integer">190316</id>
#     <imported-from-svn type="boolean">false</imported-from-svn>
#     <message>AMo가 ARes에서만 사용되었기 때문에 wrap_with_notifications를 관찰하는 것을 제거</message>
#     <sha1>723a47bfb3708f968821bc969a9a3fc873a3ed58</sha1>
#   </object>
# </objects>

수신자가 해시의 배열인 경우 루트 요소는 기본적으로 "objects"입니다:

[{ a: 1, b: 2 }, { c: 3 }].to_xml 
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
#   <object>
#     <b type="integer">2</b>
#     <a type="integer">1</a>
#   </object>
#   <object>
#     <c type="integer">3</c>
#   </object>
# </objects>

주의. 컬렉션이 비어있는 경우 루트 요소는 기본적으로 "nil-classes"입니다. 이것은 함정이 될 수 있습니다. 예를 들어 위의 contributors 목록이 비어있다면 루트 요소가 "contributors"가 아닌 "nil-classes"가 됩니다. :root 옵션을 사용하여 일관된 루트 요소를 보장할 수 있습니다.

자식 노드의 이름은 기본적으로 루트 노드 이름의 단수형입니다. 위의 예제에서는 "contributor"와 "object"를 보았습니다. :children 옵션을 사용하여 이러한 노드 이름을 설정할 수 있습니다.

기본 XML builder는 Builder::XmlMarkup의 새로운 인스턴스입니다. :builder 옵션을 통해 자신만의 builder를 구성할 수 있습니다. 이 메서드는 :dasherize와 같은 옵션들도 받으며, 이들은 builder로 전달됩니다:

Contributor.limit(2).order(:rank).to_xml(skip_types: true)
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors>
#   <contributor>
#     <id>4356</id>
#     <name>Jeremy Kemper</name>
#     <rank>1</rank> 
#     <url-id>jeremy-kemper</url-id>
#   </contributor>
#   <contributor>
#     <id>4404</id>
#     <name>David Heinemeier Hansson</name>
#     <rank>2</rank>
#     <url-id>david-heinemeier-hansson</url-id>
#   </contributor>
# </contributors>

active_support/core_ext/array/conversions.rb에 정의되어 있습니다.

11.5 Wrapping

Array.wrap 메서드는 인자가 이미 배열(또는 배열과 유사한 형태)이 아닌 경우 해당 인자를 배열로 감쌉니다.

구체적으로:

  • 인자가 nil이면 빈 배열이 반환됩니다.
  • 그렇지 않고, 인자가 to_ary에 응답하면 이를 호출하고, to_ary의 값이 nil이 아니라면 이를 반환합니다.
  • 그렇지 않다면, 해당 인자를 단일 요소로 가지는 배열이 반환됩니다.
Array.wrap(nil)       # => []
Array.wrap([1, 2, 3]) # => [1, 2, 3]  
Array.wrap(0)         # => [0]

이 메서드는 목적상 Kernel#Array와 유사하지만 몇 가지 차이점이 있습니다:

  • 인자가 to_ary에 응답하면 메서드가 호출됩니다. Kernel#Array는 반환된 값이 nil인 경우 to_a를 시도하지만, Array.wrap은 즉시 인자를 단일 요소로 하는 배열을 반환합니다.
  • to_ary에서 반환된 값이 nil도 아니고 Array 객체도 아닌 경우, Kernel#Array는 예외를 발생시키지만 Array.wrap은 그렇지 않고 단순히 해당 값을 반환합니다.
  • 인자가 to_ary에 응답하지 않는 경우 인자에 대해 to_a를 호출하지 않고, 인자를 단일 요소로 하는 배열을 반환합니다.

마지막 점은 특히 일부 enumerable에 대해 비교해볼 가치가 있습니다:

Array.wrap(foo: :bar) # => [{:foo=>:bar}]
Array(foo: :bar)      # => [[:foo, :bar]]

splat 연산자를 사용하는 관련 숙어도 있습니다:

[*object]

값이 nil인 경우 빈 Array를 반환하고, Array인 경우 복사본을 반환하며, 그 외의 경우 객체를 포함하는 새 Array를 반환합니다.

active_support/core_ext/array/wrap.rb에 정의되어 있습니다.

11.6 Duplicating

Array#deep_dup 메서드는 자신과 내부의 모든 객체들을 Active Support의 Object#deep_dup 메서드를 사용해 재귀적으로 복제합니다. 이는 Array#map처럼 작동하며, 내부의 각 객체에 deep_dup 메서드를 보냅니다.

array = [1, [2, 3]]
dup = array.deep_dup
dup[1][2] = 4
array[1][2] == nil   # => true

active_support/core_ext/object/deep_dup.rb에 정의되어 있습니다.

11.7 Grouping

11.7.1 in_groups_of(number, fill_with = nil)

in_groups_of 메서드는 배열을 특정 크기의 연속된 그룹으로 나눕니다. 그리고 그룹들로 이루어진 배열을 반환합니다:

[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]

또는 block이 전달된 경우 차례로 yield 합니다:

<% sample.in_groups_of(3) do |a, b, c| %>
  <tr>
    <td><%= a %></td>
    <td><%= b %></td>
    <td><%= c %></td>
  </tr>
<% end %>

첫 번째 예시는 in_groups_of가 마지막 그룹을 요청된 크기만큼 얼마나 많은 nil 요소로 채우는지를 보여줍니다. 두 번째 선택적 인자를 사용하여 이 패딩값을 변경할 수 있습니다:

[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]]

그리고 false를 전달하여 마지막 그룹을 채우지 않도록 메서드에 지시할 수 있습니다:

[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]]

결과적으로 false는 패딩 값으로 사용될 수 없습니다.

active_support/core_ext/array/grouping.rb에 정의되어 있습니다.

11.7.2 in_groups(number, fill_with = nil)

in_groups 메서드는 배열을 지정된 수의 그룹으로 분할합니다. 이 메서드는 그룹들을 포함한 배열을 반환합니다:

%w(1 2 3 4 5 6 7).in_groups(3)
# => ["1", "2", "3"]은 첫 번째 그룹, ["4", "5", nil]은 두 번째 그룹, ["6", "7", nil]은 세 번째 그룹으로 나눕니다.

또는 블록이 전달된 경우 차례로 yield합니다:

%w(1 2 3 4 5 6 7).in_groups(3) { |group| p group }
["1", "2", "3"]
["4", "5", nil] 
["6", "7", nil]

첫 번째 배열이 다른 배열들보다 하나 더 많은 요소를 가지도록 하여 7개의 요소를 3개의 그룹으로 나눕니다. 나머지 두 그룹은 첫 번째 그룹과 크기를 맞추기 위해 nil로 채워집니다.

위 예제들은 in_groups가 필요에 따라 일부 그룹을 후행 nil 요소로 채운다는 것을 보여줍니다. 각 그룹은 이러한 추가 요소를 최대 하나만 가질 수 있으며, 있다면 가장 오른쪽에 위치합니다. 그리고 이러한 요소를 가진 그룹은 항상 마지막 그룹들입니다.

두 번째 선택적 인자를 사용하여 이 패딩 값을 변경할 수 있습니다:

%w(1 2 3 4 5 6 7).in_groups(3, "0") 
# => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]]

그리고 false를 전달하여 더 작은 그룹을 채우지 않도록 메서드에 지시할 수 있습니다:

%w(1 2 3 4 5 6 7).in_groups(3, false)
# => [["1", "2", "3"], ["4", "5"], ["6", "7"]] 

결과적으로 false는 padding 값으로 사용할 수 없습니다.

active_support/core_ext/array/grouping.rb에 정의되어 있습니다.

11.7.3 split(value = nil)

split 메서드는 separator로 배열을 나누고 그 결과 청크들을 반환합니다.

블록이 전달되면 separator는 블록이 true를 반환하는 배열의 요소들입니다:

(-5..5).to_a.split { |i| i.multiple_of?(4) }
# => [[-5], [-3, -2, -1], [1, 2, 3], [5]]

그렇지 않으면 인자로 받은 값이 separator가 되며, 기본값은 nil입니다:

[0, 1, -5, 1, 1, "foo", "bar"].split(1)
# => [[0], [-5], [], ["foo", "bar"]]

이전 예제에서 연속된 구분자는 빈 배열을 생성한다는 점을 주목하세요.

active_support/core_ext/array/grouping.rb에 정의되어 있습니다.

12 Hash에 대한 확장

12.1 Conversions

12.1.1 to_xml

to_xml 메서드는 receiver를 XML 형식으로 나타낸 문자열을 반환합니다:

{ foo: 1, bar: 2 }.to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <hash>
#   <foo type="integer">1</foo>
#   <bar type="integer">2</bar>
# </hash>

이를 위해 이 메서드는 pairs를 순회하면서 값들에 의존하는 노드들을 생성합니다. key, value 쌍이 주어졌을 때:

  • value가 hash인 경우 :rootkey를 사용하여 재귀적으로 호출됩니다.

  • value가 배열인 경우 :rootkey를 사용하고 단수화된 key:children으로 사용하여 재귀적으로 호출됩니다.

  • value가 callable 객체인 경우 하나 또는 두 개의 인자를 받아야 합니다. arity에 따라, callable은 첫 번째 인자로 :rootkey를 가진 options hash를, 두 번째 인자로 단수화된 key를 받아 호출됩니다. 그 반환값이 새로운 노드가 됩니다.

  • valueto_xml에 응답하는 경우 :rootkey를 사용하여 메서드가 호출됩니다.

  • 그 외의 경우, key를 태그로 하고 value의 문자열 표현을 텍스트 노드로 하는 노드가 생성됩니다. valuenil인 경우 "nil" 속성이 "true"로 설정됩니다. :skip_types 옵션이 존재하고 true가 아닌 한, "type" 속성도 다음 매핑에 따라 추가됩니다:

XML_TYPE_NAMES = {
  "Symbol"     => "symbol",
  "Integer"    => "integer", 
  "BigDecimal" => "decimal",
  "Float"      => "float",
  "TrueClass"  => "boolean",
  "FalseClass" => "boolean",
  "Date"       => "date",
  "DateTime"   => "datetime",
  "Time"       => "datetime"
}

기본적으로 root 노드는 "hash"이지만, :root 옵션을 통해 구성할 수 있습니다.

기본 XML builder는 Builder::XmlMarkup의 새로운 인스턴스입니다. :builder 옵션을 통해 자신만의 builder를 구성할 수 있습니다. 이 메서드는 :dasherize와 같은 옵션들도 받으며, 이들은 builder에 전달됩니다.

active_support/core_ext/hash/conversions.rb에 정의되어 있습니다.

12.2 병합

Ruby는 두 개의 hash를 병합하는 내장 메서드 Hash#merge를 가지고 있습니다:

{ a: 1, b: 1 }.merge(a: 0, c: 2)
# => {:a=>0, :b=>1, :c=>2}

Active Support는 편리하게 hash를 병합하는 몇 가지 추가적인 방법을 정의합니다.

12.2.1 reverse_mergereverse_merge!

충돌이 발생할 경우 merge에서는 인자의 hash에 있는 key가 우선합니다. 이 관용구를 사용하면 기본값을 가진 옵션 hash를 간단한 방식으로 지원할 수 있습니다:

options = { length: 30, omission: "..." }.merge(options)

Active Support에서는 다른 표기법을 선호하는 경우를 위해 [reverse_merge][Hash#reverse_merge]를 정의합니다:

options = options.reverse_merge(length: 30, omission: "...")

options를 length: 30, omission: "..." 기본값을 포함하여 merge 합니다.

그리고 병합을 즉시 수행하는 bang 버전 [reverse_merge!][Hash#reverse_merge!]가 있습니다:

options.reverse_merge!(length: 30, omission: "...")
```를 통해 length 기본값 30과 omission 기본값 "..."을 merged합니다.

WARNING. `reverse_merge!`가 호출자의 hash를 변경할 수 있다는 점을 고려하세요. 이는 상황에 따라 좋을 수도 있고 나쁠 수도 있습니다.

NOTE: `active_support/core_ext/hash/reverse_merge.rb`에 정의되어 있습니다.

#### `reverse_update`

[`reverse_update`][Hash#reverse_update] 메서드는 위에서 설명한 `reverse_merge!`의 alias입니다.

WARNING. `reverse_update`는 bang(!)이 없다는 점에 유의하세요.

NOTE: `active_support/core_ext/hash/reverse_merge.rb`에 정의되어 있습니다.

#### `deep_merge`와 `deep_merge!`

이전 예제에서 볼 수 있듯이 두 hash에서 동일한 key가 발견되면 인자로 전달된 hash의 value가 우선합니다.

Active Support는 [`Hash#deep_merge`][Hash#deep_merge]를 정의합니다. deep merge에서는 두 hash에서 동일한 key를 찾았을 때 그 value들이 hash인 경우, 그들의 _merge_ 결과가 최종 hash의 value가 됩니다:

```ruby
{ a: { b: 1 } }.deep_merge(a: { c: 2 })
# => {:a=>{:b=>1, :c=>2}}

deep_merge! 메서드는 내부에서 깊은 병합을 수행합니다.

active_support/core_ext/hash/deep_merge.rb에 정의되어 있습니다.

12.3 Deep Duplicating

Hash#deep_dup 메서드는 자신과 내부의 모든 key와 value를 Active Support의 Object#deep_dup 메서드를 사용하여 재귀적으로 복제합니다. 이는 내부의 각 쌍에 deep_dup 메서드를 전달하는 Enumerator#each_with_object처럼 작동합니다.

hash = { a: 1, b: { c: 2, d: [3, 4] } }

dup = hash.deep_dup
dup[:b][:e] = 5 
dup[:b][:d] << 5

hash[:b][:e] == nil      # => true - 원본 hash는 영향받지 않습니다
hash[:b][:d] == [3, 4]   # => true - 원본 hash는 영향받지 않습니다

active_support/core_ext/object/deep_dup.rb에 정의되어 있습니다.

12.4 Keys로 작업하기

12.4.1 except!

except! 메서드는 내장된 except 메서드와 동일하지만 keys를 직접 제거하고 self를 반환합니다.

{ a: 1, b: 2 }.except!(:a) # => {:b=>2}
{ a: 1, b: 2 }.except!(:c) # => {:a=>1, :b=>2}

만약 receiver가 convert_key에 응답하면, 각각의 argument에서 method가 호출됩니다. 이를 통해 except!(와 except)가 예를 들어 indifferent access를 가진 hash와 잘 동작할 수 있습니다:

{ a: 1 }.with_indifferent_access.except!(:a)  # => {}
{ a: 1 }.with_indifferent_access.except!("a") # => {}

active_support/core_ext/hash/except.rb에 정의되어 있습니다.

12.4.2 stringify_keysstringify_keys!

stringify_keys 메서드는 receiver의 키를 문자열화한 버전을 가진 hash를 반환합니다. 이는 키에 to_s를 호출하여 수행됩니다:

{ nil => nil, 1 => 1, a: :a }.stringify_keys 
# => {"" => nil, "1" => 1, "a" => :a}

키 충돌이 발생하는 경우, hash에 가장 최근에 삽입된 값이 사용됩니다:

{ "a" => 1, a: 2 }.stringify_keys
# 결과는 다음과 같습니다 
# => {"a"=>2}

이 메서드는 예를 들어 symbol과 string을 모두 option으로 쉽게 받아들이는 데 유용할 수 있습니다. 예를 들어 ActionView::Helpers::FormHelper는 다음과 같이 정의합니다:

def to_checkbox_tag(options = {}, checked_value = "1", unchecked_value = "0")
  options = options.stringify_keys
  options["type"] = "checkbox"
  # ...
end

checkbox 태그로 변환합니다. options는 해시 형태로 제공되며 체크되었을 때와 체크되지 않았을 때의 값을 지정할 수 있습니다.

두 번째 줄은 안전하게 "type" 키에 접근할 수 있으며, 사용자가 :type 또는 "type" 중 어느 것이든 전달할 수 있습니다.

키를 문자열로 직접 변환하는 bang 변형 stringify_keys!도 있습니다.

또한 deep_stringify_keysdeep_stringify_keys!를 사용하여 주어진 hash와 그 안에 중첩된 모든 hash의 모든 키를 문자열로 변환할 수 있습니다. 결과의 예시는 다음과 같습니다:

{ nil => nil, 1 => 1, nested: { a: 3, 5 => 5 } }.deep_stringify_keys
# => {"" => nil, "1" => 1, "nested" => {"a" => 3, "5" => 5}}

active_support/core_ext/hash/keys.rb에 정의되어 있습니다.

12.4.3 symbolize_keyssymbolize_keys!

symbolize_keys 메서드는 가능한 경우 receiver의 key들을 symbol화한 hash를 반환합니다. 이는 key들에 to_sym을 전송하여 수행됩니다:

{ nil => nil, 1 => 1, "a" => "a" }.symbolize_keys 
# => {nil=>nil, 1=>1, :a=>"a"}

이전 예제에서는 오직 하나의 key만 symbol화되었다는 점을 주의하세요.

key 충돌이 발생하는 경우, 값은 hash에 가장 최근에 삽입된 것이 됩니다:

{ "a" => 1, a: 2 }.symbolize_keys
# => {:a=>2}

이 메소드는 예를 들어 symbol과 string 둘 다 옵션으로 쉽게 받을 수 있어 유용할 수 있습니다. 예를 들어 ActionText::TagHelper는 다음과 같이 정의합니다

def rich_textarea_tag(name, value = nil, options = {})
  options = options.symbolize_keys

  options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}" 
  # ...
end

세 번째 줄에서는 :input 키에 안전하게 접근할 수 있으며, 사용자가 :input 또는 "input"를 전달할 수 있습니다.

키를 제자리에서 심볼로 변환하는 bang 변형인 symbolize_keys!도 있습니다.

또한, deep_symbolize_keysdeep_symbolize_keys!을 사용하여 주어진 해시의 모든 키와 그 안에 중첩된 모든 해시의 키를 심볼로 변환할 수 있습니다. 결과의 예시는 다음과 같습니다:

{ nil => nil, 1 => 1, "nested" => { "a" => 3, 5 => 5 } }.deep_symbolize_keys
# => {nil=>nil, 1=>1, nested:{a:3, 5=>5}}

active_support/core_ext/hash/keys.rb에 정의되어 있습니다.

12.4.4 to_optionsto_options!

to_optionsto_options! 메서드는 각각 symbolize_keyssymbolize_keys!의 별칭입니다.

active_support/core_ext/hash/keys.rb에 정의되어 있습니다.

12.4.5 assert_valid_keys

assert_valid_keys 메서드는 임의의 개수의 인자를 받고, receiver가 해당 목록 외의 key를 가지고 있는지 확인합니다. 만약 그렇다면 ArgumentError가 발생합니다.

{ a: 1 }.assert_valid_keys(:a)  # 통과
{ a: 1 }.assert_valid_keys("a") # ArgumentError

Active Record는 association을 생성할 때 알 수 없는 옵션을 허용하지 않습니다. 이러한 제어는 assert_valid_keys를 통해 구현됩니다.

active_support/core_ext/hash/keys.rb에 정의되어 있습니다.

12.5 값 다루기

12.5.1 deep_transform_valuesdeep_transform_values!

deep_transform_values 메서드는 모든 값들을 블록 연산으로 변환한 새로운 해시를 반환합니다. 이는 루트 해시의 값들과 모든 중첩된 해시와 배열의 값들을 포함합니다.

hash = { person: { name: "Rob", age: "28" } }

hash.deep_transform_values { |value| value.to_s.upcase }
# => {person: {name: "ROB", age: "28"}}

또한 블록 연산을 사용하여 모든 값을 파괴적으로 변환하는 bang 변형인 deep_transform_values!도 있습니다.

active_support/core_ext/hash/deep_transform_values.rb에 정의되어 있습니다.

12.6 Slicing

slice! 메소드는 주어진 키들만 남긴 해시로 대체하고, 제거된 키/값 쌍들을 포함한 해시를 반환합니다.

hash = { a: 1, b: 2 }
rest = hash.slice!(:a) # => {:b=>2} 
hash                   # => {:a=>1}

active_support/core_ext/hash/slice.rb에 정의되어 있습니다.

12.7 Extracting

extract! 메서드는 주어진 key에 매칭되는 key/value 쌍을 제거하고 반환합니다.

hash = { a: 1, b: 2 }
rest = hash.extract!(:a) # => {:a=>1} 
hash                     # => {:b=>2}

특정 key를 추출하여 새로운 hash를 만들고 원본 hash에서는 추출한 key를 제거합니다.

extract! 메소드는 수신자와 동일한 Hash의 하위 클래스를 반환합니다.

hash = { a: 1, b: 2 }.with_indifferent_access
rest = hash.extract!(:a).class
# => ActiveSupport::HashWithIndifferentAccess

active_support/core_ext/hash/slice.rb에 정의되어 있습니다.

12.8 Indifferent Access

with_indifferent_access 메서드는 수신자로부터 ActiveSupport::HashWithIndifferentAccess를 반환합니다:

{ a: 1 }.with_indifferent_access["a"] # => 1

active_support/core_ext/hash/indifferent_access.rb에 정의되어 있습니다.

13 Regexp에 대한 확장

13.1 multiline?

[multiline?][Regexp#multiline?] 메서드는 정규표현식에 /m 플래그가 설정되어 있는지, 즉 점(dot)이 줄바꿈 문자와 매치되는지 여부를 알려줍니다.

%r{.}.multiline?  # => false 
%r{.}m.multiline? # => true

Regexp.new(".").multiline?                    # => false
Regexp.new(".", Regexp::MULTILINE).multiline? # => true

multiline 모드인지 확인하는 방법입니다.

Rails는 라우팅 코드에서 단 한 곳에서만 이 메서드를 사용합니다. 라우트 requirements에서는 여러 줄의 정규식이 허용되지 않으며, 이 flag는 그러한 제약을 강제하는 것을 쉽게 만들어 줍니다.

def verify_regexp_requirements(requirements)
  # ...
  if requirement.multiline?
    raise ArgumentError, "routing requirements에서는 Regexp multiline 옵션이 허용되지 않습니다: #{requirement.inspect}"
  end
  # ...
end

active_support/core_ext/regexp.rb에 정의됨.

14 Range에 대한 확장

14.1 to_fs

Active Support는 기본값이 아닌 format 인자를 이해할 수 있는 to_s의 대안으로 Range#to_fs를 정의합니다. 이 글을 작성하는 시점에서 지원되는 유일한 기본값이 아닌 format은 :db입니다:

(Date.today..Date.tomorrow).to_fs
# => "2009-10-25..2009-10-26"

(Date.today..Date.tomorrow).to_fs(:db)
# => "BETWEEN '2009-10-25' AND '2009-10-26'" 

예시에서 보듯이, :db 포맷은 BETWEEN SQL 구문을 생성합니다. 이는 Active Record가 조건문에서 range 값을 지원하는데 사용됩니다.

active_support/core_ext/range/conversions.rb에 정의되어 있습니다.

14.2 ===include?

Range#===Range#include? 메소드는 주어진 인스턴스의 양 끝점 사이에 어떤 값이 있는지를 알려줍니다:

(2..3).include?(Math::E) # => true

Active Support는 이러한 메서드들을 확장하여 인자로 또 다른 range를 받을 수 있게 합니다. 이 경우 인자로 받은 range의 양 끝점이 수신자(receiver)에 속하는지를 테스트합니다:

(1..10) === (3..7)  # => true (1..10이 3..7을 완전히 포함)
(1..10) === (0..7)  # => false (0이 1..10의 범위를 벗어남)
(1..10) === (3..11) # => false (11이 1..10의 범위를 벗어남) 
(1...9) === (3..9)  # => false (1...9는 9를 제외, 3..9는 9를 포함)

(1..10).include?(3..7)  # => true (1..10이 3..7을 완전히 포함)
(1..10).include?(0..7)  # => false (0이 1..10의 범위를 벗어남)
(1..10).include?(3..11) # => false (11이 1..10의 범위를 벗어남)
(1...9).include?(3..9)  # => false (1...9는 9를 제외, 3..9는 9를 포함)

active_support/core_ext/range/compare_range.rb에 정의됨.

14.3 overlap?

Range#overlap? 메서드는 어떤 두 개의 range가 비어있지 않은 교집합을 가지고 있는지를 알려줍니다:

(1..10).overlap?(7..11)  # => true
(1..10).overlap?(0..7)   # => true 
(1..10).overlap?(11..27) # => false

active_support/core_ext/range/overlap.rb에 정의되어 있습니다.

15 Date에 대한 확장

15.1 Calculations

다음 계산 메서드들은 1582년 10월에 5일에서 14일까지 존재하지 않는 특이한 경우가 있습니다. 이 가이드에서는 간단히 하기 위해 해당 날짜들에 대한 동작을 문서화하지 않았지만, 예상하는 대로 동작한다고 말하면 충분할 것 같습니다. 예를 들어, Date.new(1582, 10, 4).tomorrowDate.new(1582, 10, 15)를 반환하는 식입니다. 예상되는 동작에 대해서는 Active Support 테스트 스위트의 test/core_ext/date_ext_test.rb를 확인해주세요.

15.1.1 Date.current

Active Support는 [Date.current][Date.current]를 현재 time zone의 오늘로 정의합니다. 이는 Date.today와 비슷하지만, 정의된 경우 사용자의 time zone을 반영한다는 점이 다릅니다. 또한 [Date.yesterday][Date.yesterday]와 [Date.tomorrow][Date.tomorrow], 그리고 인스턴스 predicates인 [past?][DateAndTime::Calculations#past?], today?, tomorrow?, next_day?, yesterday?, prev_day?, [future?][DateAndTime::Calculations#future?], [on_weekday?][DateAndTime::Calculations#on_weekday?], [on_weekend?][DateAndTime::Calculations#on_weekend?]도 정의합니다. 이 모든 메서드들은 Date.current를 기준으로 합니다.

사용자 time zone을 반영하는 메서드를 사용하여 Date 비교를 할 때는 Date.today 대신 Date.current를 사용해야 합니다. 사용자 time zone이 시스템 time zone(기본적으로 Date.today가 사용하는)보다 미래인 경우가 있을 수 있습니다. 이는 Date.todayDate.yesterday와 같을 수 있다는 것을 의미합니다.

active_support/core_ext/date/calculations.rb에 정의되어 있습니다.

15.1.2 Named Dates

15.1.2.1 beginning_of_week, end_of_week

beginning_of_weekend_of_week 메서드는 각각 주의 시작과 끝 날짜를 반환합니다. 주는 기본적으로 월요일에 시작하는 것으로 가정하지만, 인자를 전달하거나 thread local Date.beginning_of_week 또는 config.beginning_of_week를 설정하여 변경할 수 있습니다.

d = Date.new(2010, 5, 8)     # => 2010년 5월 8일 토요일
d.beginning_of_week          # => 2010년 5월 3일 월요일 
d.beginning_of_week(:sunday) # => 2010년 5월 2일 일요일
d.end_of_week                # => 2010년 5월 9일 일요일
d.end_of_week(:sunday)       # => 2010년 5월 8일 토요일

beginning_of_weekat_beginning_of_week의 alias이고 end_of_weekat_end_of_week의 alias입니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.2.2 monday, sunday

mondaysunday 메서드는 각각 이전 월요일과 다음 일요일의 날짜를 반환합니다.

d = Date.new(2010, 5, 8)     # => 토, 08 5월 2010 
d.monday                     # => 월, 03 5월 2010
d.sunday                     # => 일, 09 5월 2010

d = Date.new(2012, 9, 10)    # => 월, 10 9월 2012
d.monday                     # => 월, 10 9월 2012

d = Date.new(2012, 9, 16)    # => 일, 16 9월 2012 
d.sunday                     # => 일, 16 9월 2012

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.2.3 prev_week, next_week

next_week 메서드는 영어로 된 요일 이름의 symbol을 받습니다(기본값은 thread local Date.beginning_of_week, 또는 config.beginning_of_week, 또는 :monday입니다). 그리고 해당 요일에 대응하는 날짜를 반환합니다.

d = Date.new(2010, 5, 9) # => 일, 2010년 5월 9일
d.next_week              # => 월, 2010년 5월 10일
d.next_week(:saturday)   # => 토, 2010년 5월 15일

메서드 prev_week는 이와 유사합니다:

d.prev_week              # => 2010년 4월 26일 월요일 
d.prev_week(:saturday)   # => 2010년 5월 1일 토요일
d.prev_week(:friday)     # => 2010년 4월 30일 금요일

prev_weeklast_week의 별칭입니다.

next_weekprev_week 모두 Date.beginning_of_week 또는 config.beginning_of_week가 설정되어 있을 때 예상대로 작동합니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.2.4 beginning_of_month, end_of_month

beginning_of_monthend_of_month 메서드는 해당 월의 시작일과 마지막 일을 반환합니다:

d = Date.new(2010, 5, 9) # => 일, 09 5월 2010
d.beginning_of_month     # => 토, 01 5월 2010
d.end_of_month           # => 월, 31 5월 2010

beginning_of_monthat_beginning_of_month의 별칭이고, end_of_monthat_end_of_month의 별칭입니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.2.5 quarter, beginning_of_quarter, end_of_quarter

quarter 메서드는 해당 날짜가 속한 연도의 분기를 반환합니다:

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.quarter                # => 2 분기 

beginning_of_quarterend_of_quarter 메서드는 수신자의 달력 연도에서 분기의 시작일과 종료일을 반환합니다:

d = Date.new(2010, 5, 9) # => 2010년 5월 9일 일요일 
d.beginning_of_quarter   # => 2010년 4월 1일 목요일 
d.end_of_quarter         # => 2010년 6월 30일 수요일

beginning_of_quarterat_beginning_of_quarter의 별칭이고, end_of_quarterat_end_of_quarter의 별칭입니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.2.6 beginning_of_year, end_of_year

beginning_of_yearend_of_year 메서드는 해당 연도의 시작일과 종료일을 반환합니다:

d = Date.new(2010, 5, 9) # => 2010년 5월 9일 일요일
d.beginning_of_year      # => 2010년 1월 1일 금요일  
d.end_of_year           # => 2010년 12월 31일 금요일

beginning_of_yearat_beginning_of_year의 별칭이며, end_of_yearat_end_of_year의 별칭입니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.3 다른 날짜 계산

15.1.3.1 years_ago, years_since

years_ago 메서드는 연도 수를 인자로 받아 해당 연도만큼 이전의 같은 날짜를 반환합니다:

date = Date.new(2010, 6, 7)
date.years_ago(10) # => 수, 07 6월 2000

years_since은 시간을 앞으로 이동시킵니다:

date = Date.new(2010, 6, 7)
date.years_since(10) # => 2020년 6월 7일 일요일

해당하는 날짜가 존재하지 않는 경우, 해당 월의 마지막 날짜가 반환됩니다:

Date.new(2012, 2, 29).years_ago(3)     # => 2009년 2월 28일 토요일 
Date.new(2012, 2, 29).years_since(3)   # => 2015년 2월 28일 토요일

last_year#years_ago(1)의 축약형입니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.3.2 months_ago, months_since

months_agomonths_since 메서드는 월 단위로 유사하게 동작합니다:

Date.new(2010, 4, 30).months_ago(2)   # => 2010년 2월 28일 일요일
Date.new(2010, 4, 30).months_since(2) # => 2010년 6월 30일 수요일

해당하는 날짜가 존재하지 않는 경우, 해당 월의 마지막 날짜가 반환됩니다:

Date.new(2010, 4, 30).months_ago(2)    # => 2010년 2월 28일 일요일  
Date.new(2009, 12, 31).months_since(2) # => 2010년 2월 28일 일요일

last_month#months_ago(1)의 축약형입니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.3.3 weeks_ago, weeks_since

weeks_ago와 [weeks_since][DateAndTime::Calculations#week_since] 메서드는 주 단위로 유사하게 동작합니다:

Date.new(2010, 5, 24).weeks_ago(1)   # => 2010년 5월 17일 월요일
Date.new(2010, 5, 24).weeks_since(2) # => 2010년 6월 7일 월요일

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

15.1.3.4 advance

다른 날짜로 이동하는 가장 일반적인 방법은 advance입니다. 이 메서드는 :years, :months, :weeks, :days를 key로 가지는 hash를 받아서 현재 key가 지정하는 만큼 더해진 날짜를 반환합니다:

date = Date.new(2010, 6, 6)
date.advance(years: 1, weeks: 2)  # => 2011년 6월 20일 월요일
date.advance(months: 2, days: -2) # => 2010년 8월 4일 수요일

이전 예제에서 증분이 음수가 될 수 있다는 점에 주의하세요.

active_support/core_ext/date/calculations.rb에 정의되어 있습니다.

15.1.4 Components 변경하기

change 메서드를 사용하면 지정된 year, month, day만 변경된 새로운 date를 얻을 수 있습니다:

Date.new(2010, 12, 23).change(year: 2011, month: 11) 
# => 2011년 11월 23일 수요일

이 메서드는 존재하지 않는 날짜에 대해 허용되지 않으며, 변경이 유효하지 않은 경우 ArgumentError가 발생합니다:

Date.new(2010, 1, 31).change(month: 2)
# => ArgumentError: 유효하지 않은 날짜

active_support/core_ext/date/calculations.rb에 정의되어 있습니다.

15.1.5 Duration

Duration 객체는 날짜에 더하거나 뺄 수 있습니다:

d = Date.current
# => 월요일, 09 Aug 2010
d + 1.year
# => 화요일, 09 Aug 2011
d - 3.hours
# => 일요일, 08 Aug 2010 21:00:00 UTC +00:00

이는 since 또는 advance를 호출하도록 변환됩니다. 예를 들어 여기서는 달력 개혁에서 올바른 시점 이동을 얻게 됩니다:

Date.new(1582, 10, 4) + 1.day  
# => 1582년 10월 15일 금요일

15.1.6 Timestamps

다음 메서드들은 가능한 경우 Time 객체를, 그렇지 않은 경우 DateTime 객체를 반환합니다. 설정되어 있다면 사용자의 타임존을 준수합니다.

15.1.6.1 beginning_of_day, end_of_day

beginning_of_day 메서드는 하루의 시작 시간(00:00:00)의 timestamp를 반환합니다:

date = Date.new(2010, 6, 7)
date.beginning_of_day # => 2010년 6월 7일 월요일 00:00:00 +0200

end_of_day 메서드는 하루의 마지막 시각(23:59:59)의 timestamp를 반환합니다:

date = Date.new(2010, 6, 7)
date.end_of_day # => 2010년 6월 7일 월요일 23시 59분 59초 +0200

beginning_of_dayat_beginning_of_day, midnight, at_midnight의 별칭입니다.

active_support/core_ext/date/calculations.rb에 정의되어 있습니다.

15.1.6.2 beginning_of_hour, end_of_hour

beginning_of_hour 메서드는 시간의 시작(hh:00:00)을 나타내는 timestamp를 반환합니다:

date = DateTime.new(2010, 6, 7, 19, 55, 25) 
date.beginning_of_hour # => 2010년 6월 7일 월요일 19:00:00 +0200

end_of_hour 메서드는 해당 시간의 마지막 순간(hh:59:59)의 timestamp를 반환합니다:

date = DateTime.new(2010, 6, 7, 19, 55, 25) 
date.end_of_hour # => 2010년 6월 7일 월요일 19시 59분 59초 +0200

beginning_of_hourat_beginning_of_hour의 별칭입니다.

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

15.1.6.3 beginning_of_minute, end_of_minute

beginning_of_minute 메서드는 분의 시작 시점(hh:mm:00)의 타임스탬프를 반환합니다:

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.beginning_of_minute # => 2010년 6월 7일 월요일 19:55:00 +0200

메소드 end_of_minute는 해당 분의 마지막 시점(hh:mm:59)의 timestamp를 반환합니다:

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.end_of_minute # => 2010년 6월 7일 월요일 19:55:59 +0200

beginning_of_minuteat_beginning_of_minute의 별칭입니다.

beginning_of_hour, end_of_hour, beginning_of_minute, 그리고 end_of_minuteTimeDateTime에서 구현되어 있지만 Date에서는 구현되어 있지 않습니다. Date 인스턴스에서 시간이나 분의 시작과 끝을 요청하는 것은 의미가 없기 때문입니다.

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

15.1.6.4 ago, since

ago 메서드는 초 단위의 숫자를 인자로 받아 자정으로부터 해당 초만큼 이전의 타임스탬프를 반환합니다:

date = Date.current # => Fri, 11 Jun 2010 
date.ago(1)         # => Thu, 10 Jun 2010 23:59:59 EDT -04:00

비슷하게도, since는 앞으로 이동합니다:

date = Date.current # => Fri, 11 Jun 2010  
date.since(1)       # => Fri, 11 Jun 2010 00:00:01 EDT -04:00

active_support/core_ext/date/calculations.rb에 정의되어 있습니다.

16 DateTime 확장

DateTime는 DST(일광절약시간) 규칙을 인식하지 못하므로, DST 변경이 일어나는 동안에는 이러한 메서드들 중 일부가 예외적인 경우를 발생시킬 수 있습니다. 예를 들어 seconds_since_midnight는 그러한 날에 실제 양을 반환하지 않을 수 있습니다.

16.1 Calculations

DateTime 클래스는 Date의 서브클래스이므로 active_support/core_ext/date/calculations.rb를 로드하면 이러한 메서드와 별칭들을 상속받습니다. 단, 이들은 항상 datetime을 반환한다는 점이 다릅니다.

다음 메서드들은 재구현되었기 때문에 이들을 사용하기 위해 active_support/core_ext/date/calculations.rb를 로드할 필요가 없습니다:

한편, advancechange도 정의되어 있으며 더 많은 옵션을 지원합니다. 이들은 아래에서 설명합니다.

다음 메서드들은 DateTime 인스턴스에서만 의미가 있기 때문에 active_support/core_ext/date_time/calculations.rb에서만 구현됩니다:

16.1.1 Named Datetimes

16.1.1.1 DateTime.current

Active Support는 DateTime.currentTime.now.to_datetime와 유사하게 정의하지만, 사용자 시간대가 정의된 경우 이를 반영합니다. 인스턴스 프레디케이트 [past?][DateAndTime::Calculations#past?]와 [future?][DateAndTime::Calculations#future?]는 DateTime.current를 기준으로 정의됩니다.

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

16.1.2 Other Extensions

16.1.2.1 seconds_since_midnight

seconds_since_midnight 메서드는 자정 이후 경과된 초 수를 반환합니다:

now = DateTime.current     # => Mon, 07 Jun 2010 20:26:36 +0000
now.seconds_since_midnight # => 73596

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

16.1.2.2 utc

utc 메서드는 수신자가 표현하는 동일한 날짜와 시간을 UTC로 반환합니다.

now = DateTime.current # => 월, 07 6월 2010 19:27:52 -0400
now.utc                # => 월, 07 6월 2010 23:27:52 +0000

이 메서드는 getutc로도 사용할 수 있습니다.

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

16.1.2.3 utc?

utc? predicate는 수신자의 시간대가 UTC인지 여부를 알려줍니다:

now = DateTime.now # => Mon, 07 Jun 2010 19:30:47 -0400
now.utc?           # => false
now.utc.utc?       # => true

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

16.1.2.4 advance

다른 datetime으로 이동하는 가장 일반적인 방법은 advance입니다. 이 메서드는 :years, :months, :weeks, :days, :hours, :minutes, :seconds를 키로 갖는 hash를 받아서, 주어진 키들이 나타내는 만큼 datetime을 앞으로 진행한 결과를 반환합니다.

d = DateTime.current
# => 목, 05 8월 2010 11:33:31 +0000
d.advance(years: 1, months: 1, days: 1, hours: 1, minutes: 1, seconds: 1)
# => 화, 06 9월 2011 12:34:32 +0000

이 메서드는 먼저 위에서 설명한 Date#advance:years, :months, :weeks, :days를 전달하여 목적지 날짜를 계산합니다. 그 후에, 전진할 초 수와 함께 since를 호출하여 시간을 조정합니다. 이 순서가 중요한데, 다른 순서로 하면 일부 edge-case에서 다른 날짜/시간이 나올 수 있습니다. Date#advance의 예시가 적용되며, 시간 비트와 관련된 순서의 중요성을 보여주기 위해 이를 확장할 수 있습니다.

먼저 날짜 비트(이전에 설명했듯이 처리 순서가 상대적으로 정해져 있음)를 이동한 다음 시간 비트를 이동하면 예를 들어 다음과 같은 계산이 됩니다:

d = DateTime.new(2010, 2, 28, 23, 59, 59)
# => Sun, 28 Feb 2010 23:59:59 +0000
d.advance(months: 1, seconds: 1) 
# => Mon, 29 Mar 2010 00:00:00 +0000

하지만 반대 방향으로 계산한다면, 결과는 달라질 것입니다:

d.advance(seconds: 1).advance(months: 1)
# => 2010년 4월 1일 목요일 00:00:00 +0000

DateTime은 DST를 인식하지 못하기 때문에 어떠한 경고나 에러도 없이 존재하지 않는 시점에 도달할 수 있습니다.

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

16.1.3 컴포넌트 변경하기

change 메서드를 사용하면 수신자와 동일하지만 주어진 옵션이 다른 새로운 datetime을 얻을 수 있습니다. 옵션에는 :year, :month, :day, :hour, :min, :sec, :offset, :start가 포함될 수 있습니다:

now = DateTime.current
# => Tue, 08 Jun 2010 01:56:22 +0000
now.change(year: 2011, offset: Rational(-6, 24))
# => Wed, 08 Jun 2011 01:56:22 -0600

hours가 0이면 minutes와 seconds도 0이 됩니다(주어진 값이 없는 한):

now.change(hour: 0)
# => Tue, 08 Jun 2010 00:00:00 +0000

마찬가지로, minutes가 0이 되면 seconds도 0이 됩니다(값이 지정되지 않은 경우):

now.change(min: 0)
# => Tue, 08 Jun 2010 01:00:00 +0000

이 메서드는 존재하지 않는 날짜에 대해 허용하지 않으며, 변경이 유효하지 않을 경우 ArgumentError가 발생합니다:

DateTime.current.change(month: 2, day: 30)
# => ArgumentError: 유효하지 않은 날짜

active_support/core_ext/date_time/calculations.rb에 정의되어 있습니다.

16.1.4 Duration

Duration 객체는 datetime에 더하거나 뺄 수 있습니다:

now = DateTime.current
# => Mon, 09 Aug 2010 23:15:17 +0000
now + 1.year
# => Tue, 09 Aug 2011 23:15:17 +0000
now - 1.week
# => Mon, 02 Aug 2010 23:15:17 +0000

이것들은 sinceadvance의 호출로 변환됩니다. 예를 들어, 여기서는 달력 개혁에서 올바른 점프를 얻을 수 있습니다:

DateTime.new(1582, 10, 4, 23) + 1.hour
# => 1582년 10월 15일 금요일 00:00:00 +0000

17 Time 클래스의 확장

17.1 Calculations

이들은 유사합니다. 위의 문서를 참조하되 다음과 같은 차이점을 고려하세요:

  • change는 추가적으로 :usec 옵션을 받습니다.
  • Time은 DST를 이해하므로, 다음과 같이 올바른 DST 계산을 얻을 수 있습니다
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>

# 바르셀로나에서는 DST로 인해 2010/03/28 02:00 +0100이 2010/03/28 03:00 +0200가 됩니다.
t = Time.local(2010, 3, 28, 1, 59, 59) 
# => Sun Mar 28 01:59:59 +0100 2010
t.advance(seconds: 1)
# => Sun Mar 28 03:00:00 +0200 2010
  • since 또는 agoTime으로 표현할 수 없는 시간으로 점프하면 대신 DateTime 객체가 반환됩니다.

17.1.1 Time.current

Active Support는 현재 timezone의 오늘을 나타내는 Time.current를 정의합니다. 이는 Time.now와 비슷하지만 설정된 경우 사용자의 timezone을 따릅니다. 또한 [past?][DateAndTime::Calculations#past?], today?, tomorrow?, next_day?, yesterday?, prev_day?, [future?][DateAndTime::Calculations#future?] 인스턴스 predicate를 정의하며, 이들은 모두 Time.current를 기준으로 합니다.

사용자 timezone을 따르는 메서드를 사용해 시간을 비교할 때는 Time.now 대신 Time.current를 사용하도록 하세요. 사용자 timezone이 Time.now가 기본적으로 사용하는 시스템 timezone보다 미래인 경우가 있을 수 있습니다. 이는 Time.now.to_dateDate.yesterday와 같을 수 있다는 것을 의미합니다.

active_support/core_ext/time/calculations.rb에 정의되어 있습니다.

17.1.2 all_day, all_week, all_month, all_quarter, 그리고 all_year

all_day 메서드는 현재 시간의 하루 전체를 나타내는 범위를 반환합니다.

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now.all_day
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Mon, 09 Aug 2010 23:59:59 UTC +00:00

비슷한 방식으로, all_week, all_month, all_quarter 그리고 all_year 모두 시간 범위를 생성하는 용도로 사용됩니다.

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now.all_week 
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00
now.all_week(:sunday)
# => Sun, 16 Sep 2012 00:00:00 UTC +00:00..Sat, 22 Sep 2012 23:59:59 UTC +00:00
now.all_month
# => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00
now.all_quarter
# => Thu, 01 Jul 2010 00:00:00 UTC +00:00..Thu, 30 Sep 2010 23:59:59 UTC +00:00
now.all_year
# => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

17.1.3 prev_day, next_day

prev_daynext_day는 이전 날짜 또는 다음 날짜의 시간을 반환합니다:

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_day               # => 2010-05-07 00:00:00 +0900
t.next_day               # => 2010-05-09 00:00:00 +0900

active_support/core_ext/time/calculations.rb에 정의되어 있습니다.

17.1.4 prev_month, next_month

prev_monthnext_month는 이전 달이나 다음 달의 같은 날짜에 해당하는 time을 반환합니다:

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_month             # => 2010-04-08 00:00:00 +0900  
t.next_month             # => 2010-06-08 00:00:00 +0900

해당 날짜가 존재하지 않는 경우, 해당 월의 마지막 날이 반환됩니다:

Time.new(2000, 5, 31).prev_month # => 2000-04-30 00:00:00 +0900
Time.new(2000, 3, 31).prev_month # => 2000-02-29 00:00:00 +0900
Time.new(2000, 5, 31).next_month # => 2000-06-30 00:00:00 +0900
Time.new(2000, 1, 31).next_month # => 2000-02-29 00:00:00 +0900

active_support/core_ext/time/calculations.rb에 정의되어 있습니다.

17.1.5 prev_year, next_year

prev_yearnext_year는 지난해나 다음해의 같은 날짜/달의 time을 반환합니다:

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_year              # => 2009-05-08 00:00:00 +0900 
t.next_year              # => 2011-05-08 00:00:00 +0900

만약 날짜가 윤년의 2월 29일이라면, 28일을 얻게 됩니다:

t = Time.new(2000, 2, 29) # => 2000-02-29 00:00:00 +0900
t.prev_year               # => 1999-02-28 00:00:00 +0900 
t.next_year               # => 2001-02-28 00:00:00 +0900

active_support/core_ext/time/calculations.rb에 정의되어 있습니다.

17.1.6 prev_quarter, next_quarter

prev_quarternext_quarter는 이전 또는 다음 분기의 같은 날짜를 반환합니다:

t = Time.local(2010, 5, 8) # => 2010-05-08 00:00:00 +0300
t.prev_quarter             # => 2010-02-08 00:00:00 +0200
t.next_quarter             # => 2010-08-08 00:00:00 +0300

해당 날짜가 존재하지 않는 경우, 해당 월의 마지막 날짜가 반환됩니다:

각 분기별 날짜로부터 이전 분기의 마지막 날이나 다음 분기의 첫 날을 계산하기 위해서는 prev_quarternext_quarter를 사용하세요.

Time.local(2000, 7, 31).prev_quarter  # => 2000-04-30 00:00:00 +0300
Time.local(2000, 5, 31).prev_quarter  # => 2000-02-29 00:00:00 +0200
Time.local(2000, 10, 31).prev_quarter # => 2000-07-31 00:00:00 +0300
Time.local(2000, 11, 31).next_quarter # => 2001-03-01 00:00:00 +0200

prev_quarterlast_quarter의 별칭입니다.

active_support/core_ext/date_and_time/calculations.rb에 정의되어 있습니다.

17.2 Time 생성자

Active Support는 사용자 시간대가 정의되어 있다면 Time.currentTime.zone.now로 정의하며, 그렇지 않다면 Time.now로 fallback 합니다:

Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
Time.current  
# => 금, 2010년 8월 6일 17:11:58 CEST +02:00

마찬가지로 DateTime과 같이, [past?][DateAndTime::Calculations#past?]와 [future?][DateAndTime::Calculations#future?] 술어는 Time.current를 기준으로 합니다.

생성할 시간이 런타임 플랫폼의 Time이 지원하는 범위를 벗어나는 경우, usecs는 폐기되고 대신 DateTime 객체가 반환됩니다.

17.2.1 Durations

Duration 객체는 시간 객체에 더하거나 뺄 수 있습니다:

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now + 1.year  # 1년 더하기
# => Tue, 09 Aug 2011 23:21:11 UTC +00:00
now - 1.week  # 1주일 빼기 
# => Mon, 02 Aug 2010 23:21:11 UTC +00:00

이것들은 since 또는 advance를 호출하도록 변환됩니다. 예를 들어 여기서는 달력 변경시의 올바른 이동을 확인할 수 있습니다:

Time.utc(1582, 10, 3) + 5.days
# => Mon Oct 18 00:00:00 UTC 1582

18 File 확장

18.1 atomic_write

클래스 메서드 File.atomic_write를 사용하면 리더가 절반만 작성된 내용을 보지 않도록 하는 방식으로 파일에 쓸 수 있습니다.

파일의 이름이 인자로 전달되며, 이 메서드는 쓰기를 위해 열린 파일 핸들을 yield합니다. 블록이 완료되면 atomic_write는 파일 핸들을 닫고 작업을 완료합니다.

예를 들어 Action Pack은 이 메서드를 사용하여 all.css와 같은 asset cache 파일을 작성합니다:

File.atomic_write(joined_asset_path) do |cache|
  cache.write(join_asset_file_contents(asset_paths)) 
end

atomic_write는 이를 수행하기 위해 임시 파일을 생성합니다. 블록의 코드가 실제로 쓰는 파일이 바로 이 파일입니다. 완료되면 임시 파일의 이름이 변경되는데, 이는 POSIX 시스템에서 atomic 연산입니다. 대상 파일이 존재하는 경우 atomic_write는 해당 파일을 덮어쓰고 소유자와 권한을 유지합니다. 하지만 atomic_write가 파일 소유권이나 권한을 변경할 수 없는 몇 가지 경우가 있는데, 이 오류는 포착되어 무시되며 필요한 프로세스가 파일에 접근할 수 있도록 사용자/파일시스템을 신뢰합니다.

chmod 연산을 수행하는 atomic_write로 인해, 대상 파일에 ACL이 설정되어 있는 경우 이 ACL은 재계산/수정됩니다.

atomic_write로는 추가(append)할 수 없다는 점에 주의하세요.

보조 파일은 임시 파일을 위한 표준 디렉토리에 작성되지만, 두 번째 인수로 원하는 디렉토리를 전달할 수 있습니다.

active_support/core_ext/file/atomic.rb에 정의되어 있습니다.

19 NameError에 대한 확장

Active Support는 NameErrormissing_name?를 추가합니다. 이는 예외가 인수로 전달된 이름 때문에 발생했는지 테스트합니다.

이름은 symbol이나 string으로 지정할 수 있습니다. symbol은 순수한 상수 이름과 대조되고, string은 정규화된 상수 이름과 대조됩니다.

symbol은 :"ActiveRecord::Base"처럼 정규화된 상수 이름을 나타낼 수 있으므로, symbol에 대한 동작은 기술적으로 그래야 하기 때문이 아니라 편의를 위해 정의됩니다.

예를 들어, ArticlesController의 액션이 호출될 때 Rails는 낙관적으로 ArticlesHelper를 사용하려고 시도합니다. 헬퍼 모듈이 존재하지 않는 것은 괜찮으므로, 해당 상수 이름에 대한 예외가 발생하면 무시되어야 합니다. 하지만 articles_helper.rb가 실제 알 수 없는 상수로 인해 NameError를 발생시킬 수 있습니다. 이는 다시 발생시켜야 합니다. missing_name? 메서드는 이 두 가지 경우를 구분하는 방법을 제공합니다:

def default_helper_module!
  module_name = name.delete_suffix("Controller") 
  module_path = module_name.underscore
  helper module_path
rescue LoadError => e
  raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
  raise e unless e.missing_name? "#{module_name}Helper"
end

Controller 접미사를 삭제하고 module helper를 찾을 때 사용할 module 이름을 생성합니다. 예를 들어 MyWidget::CoolGadgetsController에서 모듈 이름은 my_widget/cool_gadgets가 되고 이는 MyWidget::CoolGadgetsHelper를 찾는데 사용됩니다.

active_support/core_ext/name_error.rb에 정의되어 있습니다.

20 LoadError의 확장

Active Support는 LoadErroris_missing?를 추가합니다.

경로 이름이 주어지면 is_missing?는 해당 파일로 인해 예외가 발생했는지 테스트합니다(".rb" 확장자는 제외될 수 있음).

예를 들어, ArticlesController의 action이 호출될 때 Rails는 articles_helper.rb를 로드하려고 시도하지만, 해당 파일이 존재하지 않을 수 있습니다. helper 모듈은 필수가 아니므로 Rails는 load error를 무시합니다. 하지만 helper 모듈이 존재하고 그 모듈이 존재하지 않는 다른 라이브러리를 require하는 경우가 있을 수 있습니다. 이 경우 Rails는 예외를 다시 발생시켜야 합니다. is_missing? 메서드는 이 두 경우를 구분하는 방법을 제공합니다:

def default_helper_module!
  module_name = name.delete_suffix("Controller") # Controller를 제거한 이름
  module_path = module_name.underscore # 모듈 경로로 변환
  helper module_path # 헬퍼로 모듈 추가 
rescue LoadError => e
  raise e unless e.is_missing? "helpers/#{module_path}_helper" # helpers/xxx_helper가 없는 경우가 아니면 예외를 발생시킴
rescue NameError => e
  raise e unless e.missing_name? "#{module_name}Helper" # xxxHelper가 없는 경우가 아니면 예외를 발생시킴
end

active_support/core_ext/load_error.rb에 정의되어 있습니다.

21 Pathname에 대한 확장

21.1 existence

existence 메서드는 지정된 파일이 존재하면 receiver를 반환하고, 존재하지 않으면 nil을 반환합니다. 다음과 같은 관용구에서 유용합니다:

content = Pathname.new("file").existence&.read

안전 내비게이션 연산자 &.를 사용하면 nil인 객체에 대해 메서드를 호출해도 NoMethodError 예외가 발생하지 않습니다. 대신 nil이 반환됩니다.

active_support/core_ext/pathname/existence.rb에 정의되어 있습니다.



맨 위로