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은 Hash
를 ActiveSupport::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로 간주됩니다:
nil
과false
,공백으로만 구성된 문자열(아래 참고사항 참조),
빈 배열과 빈 해시, 그리고
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
모든 클래스는 dup
와 clone
을 제거하거나 이들로부터 예외를 발생시켜 복제를 허용하지 않을 수 있습니다. 따라서 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
를 사용할 수 있습니다. try
는 Object#public_send
와 비슷하지만 nil
에 대해 호출될 경우 nil
을 반환한다는 점이 다릅니다.
예시입니다:
# try 없이 작성
unless @number.nil?
@number.next
end
# try 사용
@number.try(:next)
ActiveRecord::ConnectionAdapters::AbstractAdapter
의 다음 코드는 @logger
가 nil
일 수 있는 또 다른 예시입니다. 이 코드에서는 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에는 Date
나 Time
처럼 동작하고 이 계약을 따르는 클래스들이 있습니다.
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
메서드는 주어진 key
와 to_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
구현을 제공합니다. 이는 Hash
와 Process::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_warnings
와 enable_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::Z
와 M
은 동일한 module을 참조하므로, 둘 다 같은 parent(X::Y
)를 가집니다.
모듈이 익명이거나 최상위에 속하는 경우 module_parent
는 Object
를 반환합니다.
이 경우 module_parent_name
은 nil
을 반환한다는 점에 유의하세요.
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_name
은 nil
을 반환합니다.
이 경우 module_parent
는 Object
를 반환한다는 점에 유의하세요.
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_reader
가 false
일 때, 인스턴스 술어는 리더 메서드와 마찬가지로 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_reader
를 false
로 설정하여 막을 수 있고, writer 인스턴스 메서드의 생성은 :instance_writer
를 false
로 설정하여 막을 수 있습니다. 두 메서드의 생성을 모두 막으려면 :instance_accessor
를 false
로 설정하면 됩니다. 모든 경우에서 값은 정확히 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_accessor
를 false
로 설정하는 것이 유용할 수 있습니다.
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!"인 경우, 앰퍼샌드는 "&"로 이스케이프되어야 하므로 출력이 제대로 형성되지 않습니다. 더욱이 애플리케이션에 따라서는 사용자가 악의적인 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 + "<" # => "<"
안전한 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: "…")
# => "Oh dear! Oh …"
특히 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: "…")
# => "Oh dear! Oh dear!…"
문자열을 자연스러운 끊김 지점에서 잘라내려면 :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와 같이 해당 관례를 따르는 언어에서 메서드 이름을 계산하는데 유용할 수 있습니다.
일반적으로 camelize
를 underscore
의 반대 개념으로 생각할 수 있지만, 예외도 있습니다: "SSLError".underscore.camelize
는 "SslError"
를 반환합니다. 이러한 경우를 지원하기 위해 Active Support에서는 config/initializers/inflections.rb
에서 약어를 지정할 수 있습니다:
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym "SSL"
end
"SSLError".underscore.camelize # => "SSLError"
camelize
는 camelcase
의 별칭입니다.
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]
에서 얻는 값입니다.
일반적으로 underscore
는 camelize
의 반대 역할을 한다고 생각할 수 있지만, 항상 그렇지는 않습니다. 예를 들어, "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"
titleize
는 titlecase
의 별칭입니다.
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
문자열이 알려진 상수로 평가되지 않거나 그 내용이 유효한 상수 이름조차 아닌 경우, constantize
는 NameError
를 발생시킵니다.
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_one
과 has_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"]
excluding
은 without
의 별칭입니다.
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_last
와 third_to_last
도 마찬가지입니다(first
와 last
는 내장 메서드입니다). 모두의 긍정적인 기여와 사회적 지혜 덕분에 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인 경우:root
로key
를 사용하여 재귀적으로 호출됩니다.value
가 배열인 경우:root
로key
를 사용하고 단수화된key
를:children
으로 사용하여 재귀적으로 호출됩니다.value
가 callable 객체인 경우 하나 또는 두 개의 인자를 받아야 합니다. arity에 따라, callable은 첫 번째 인자로:root
로key
를 가진options
hash를, 두 번째 인자로 단수화된key
를 받아 호출됩니다. 그 반환값이 새로운 노드가 됩니다.value
가to_xml
에 응답하는 경우:root
로key
를 사용하여 메서드가 호출됩니다.그 외의 경우,
key
를 태그로 하고value
의 문자열 표현을 텍스트 노드로 하는 노드가 생성됩니다.value
가nil
인 경우 "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_merge
와 reverse_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_keys
와 stringify_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_keys
와 deep_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_keys
와 symbolize_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_keys
와 deep_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_options
와 to_options!
to_options
와 to_options!
메서드는 각각 symbolize_keys
와 symbolize_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_values
와 deep_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).tomorrow
는 Date.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.today
가 Date.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_week
와 end_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_week
는 at_beginning_of_week
의 alias이고 end_of_week
는 at_end_of_week
의 alias입니다.
active_support/core_ext/date_and_time/calculations.rb
에 정의되어 있습니다.
15.1.2.2 monday
, sunday
monday
와 sunday
메서드는 각각 이전 월요일과 다음 일요일의 날짜를 반환합니다.
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_week
는 last_week
의 별칭입니다.
next_week
와 prev_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_month
와 end_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_month
는 at_beginning_of_month
의 별칭이고, end_of_month
는 at_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_quarter
와 end_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_quarter
는 at_beginning_of_quarter
의 별칭이고, end_of_quarter
는 at_end_of_quarter
의 별칭입니다.
active_support/core_ext/date_and_time/calculations.rb
에 정의되어 있습니다.
15.1.2.6 beginning_of_year
, end_of_year
beginning_of_year
와 end_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_year
는 at_beginning_of_year
의 별칭이며, end_of_year
는 at_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_ago
와 months_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_day
는 at_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_hour
는 at_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_minute
는 at_beginning_of_minute
의 별칭입니다.
beginning_of_hour
, end_of_hour
, beginning_of_minute
, 그리고 end_of_minute
는 Time
과 DateTime
에서 구현되어 있지만 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
를 로드할 필요가 없습니다:
한편, advance
와 change
도 정의되어 있으며 더 많은 옵션을 지원합니다. 이들은 아래에서 설명합니다.
다음 메서드들은 DateTime
인스턴스에서만 의미가 있기 때문에 active_support/core_ext/date_time/calculations.rb
에서만 구현됩니다:
16.1.1 Named Datetimes
16.1.1.1 DateTime.current
Active Support는 DateTime.current
를 Time.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
이것들은 since
나 advance
의 호출로 변환됩니다. 예를 들어, 여기서는 달력 개혁에서 올바른 점프를 얻을 수 있습니다:
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
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_date
가 Date.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_day
와 next_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_month
와 next_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_year
와 next_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_quarter
와 next_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_quarter
와 next_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_quarter
는 last_quarter
의 별칭입니다.
active_support/core_ext/date_and_time/calculations.rb
에 정의되어 있습니다.
17.2 Time 생성자
Active Support는 사용자 시간대가 정의되어 있다면 Time.current
를 Time.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는 NameError
에 missing_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는 LoadError
에 is_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
에 정의되어 있습니다.