Rails 4.2 발표에는 곧 출시될 Rails 5에 대한 흥미로운 소식이 있었습니다. 아마도 Ruby 2.2가 필요할 것입니다. Ruby 2의 모든 장점을 활용하는 최초의 Rails 버전이 됩니다.
게시물은 가비지 수집 기호 및 키워드 인수를 언급했습니다. 하지만 나에게 가장 흥미로운 Ruby 2 기능 중 하나는 Module#prepend입니다.
alias_method_chain
의 좋은 점과 나쁜 점
내가 Rails를 처음 배웠을 때 alias_method_chain
내 마음을 완전히 날려 버렸어. Ruby가 얼마나 유연한지 보여주었습니다.
단 한 줄의 코드로 메소드 작동 방식을 완전히 바꿀 수 있습니다. 더 이상 원하는 코드를 추가하기 위해 라이브러리를 해킹할 필요가 없습니다. 바로 추가할 수 있습니다. alias_method_chain
gem에 대한 첫 번째 패치로 이어졌고, 이는 첫 번째 pull 요청으로 이어졌고, 이는 첫 번째 오픈 소스 기여로 이어졌습니다.
하지만 원숭이 패치와 마찬가지로 alias_method_chain
과도하게 사용되었고 문제가 명백해지기 시작했습니다.
- 생성하는 메서드 이름은 혼란스러워 오류를 찾고 디버그하기가 어렵습니다. 예:
class Person
def greeting
"Hello"
end
end
module GreetingWithExcitement
def self.included(base)
base.class_eval do
alias_method_chain :greeting, :excitement
end
end
def greeting_with_excitement
"#{greeting_without_excitement}!!!!!"
end
end
Person.send(:include, GreetingWithExcitement)
Person#greeting
에 오류가 있는 경우 , 역추적은 Person#greeting_without_excitement
에서 실제로 오류가 발생했음을 알려줍니다. . 그러나 그 방법은 어디에 정의되어 있습니까? 어디에서도 볼 수 없습니다. 어떤 greeting
인지 어떻게 알 수 있나요? 메서드에 버그가 있습니까? 그리고 메서드 이름은 연결할수록 더 혼란스러워집니다.
-
alias_method_chain
을 호출하는 경우 같은 클래스에서 같은 매개변수로 두 번 사용하면 스택 오버플로가 발생할 수 있습니다. (이유를 알 수 있습니까?)require
가 있는 한 일반적으로 발생하지 않습니다. 문은 사용하는 경로에 대해 일관성이 있습니다. 하지만 Rails 콘솔에 코드를 자주 붙여넣는 것은 매우 성가신 일입니다. -
그리고 나머지는 Yehuda Katz의 블로그 게시물에 설명되어 있습니다. 이 게시물은 많은 Rails 개발자들이
alias_method_chain
을 포기하도록 설득했습니다. 모듈 상속에 찬성합니다.
그렇다면 여전히 사용되는 이유는 무엇입니까?
대부분의 alias_method_chain
을 대체할 수 있습니다. 모듈에서 해당 메서드를 재정의하고 해당 모듈을 자식 클래스에 포함합니다. 그러나 이는 클래스 자체가 아닌 수퍼 클래스를 재정의하려는 경우에만 작동합니다. 즉:
class ParentClass
def log
puts "In parent"
end
end
class ChildClass < ParentClass
def log
puts "In child"
super
end
def log_with_extra_message
puts "In child, with extra message"
log_without_extra_message
end
alias_method_chain :log, :extra_message
end
ChildClass.new.log
를 실행한 경우 , 다음과 같이 표시됩니다.
In child, with extra message
In child
In parent
alias_method_chain
대신 모듈을 사용하려고 시도한 경우 , 출력을 다음과 같이 얻을 수 있습니다.
In child
In child, with extra message
In parent
하지만 할 수 없습니다. log
를 변경하지 않고 원래 출력과 일치 ChildClass
의 메소드 . Ruby 상속은 그런 식으로 작동하지 않습니다. 그렇지 않았습니다.
Ruby 2.0에서 변경된 사항은 무엇입니까?
Ruby 2.0 이전에는 아래에 코드를 추가할 방법이 없었습니다. 클래스, 그 위에만 있습니다. 하지만 prepend
, 모듈의 메서드로 클래스의 메서드를 재정의할 수 있으며 super
를 사용하여 클래스 구현에 계속 액세스할 수 있습니다. . 따라서 마지막 예제를 사용하여 다음을 사용하여 원래 출력을 얻을 수 있습니다.
class ParentClass
def log
puts "In parent"
end
end
module ExtraMessageLogging
def log
puts "In child, with extra message"
super
end
end
class ChildClass < ParentClass
prepend ExtraMessageLogging
def log
puts "In child"
super
end
end
In child, with extra message
In child
In parent
완벽합니다.
prepend
여전히 머리를 감는 것이 어렵습니다. 다음과 같이 생각하십시오.
class NewChildClass < ChildClass
include ExtraMessageLogging
end
ChildClass = NewChildClass
클래스 이름을 엉망으로 만들지 않고 이미 존재하는 개체에 영향을 미친다는 점을 제외하고.
(예, Ruby에서 클래스 이름을 다시 할당할 수 있습니다. 아니요, 아마도 좋은 생각이 아닐 것입니다.)
이것이 Rails에 의미하는 바는 무엇입니까?
그래서 alias_method_chain
을 사용한 마지막 변명은 Ruby 2.0에서 사라졌습니다. alias_method_chain
의 남은 몇 가지 예 중 하나를 들 수 있습니다. 레일스:
require 'active_support/core_ext/module/aliasing'
class Range #:nodoc:
def each_with_time_with_zone(&block)
ensure_iteration_allowed
each_without_time_with_zone(&block)
end
alias_method_chain :each, :time_with_zone
def step_with_time_with_zone(n = 1, &block)
ensure_iteration_allowed
step_without_time_with_zone(n, &block)
end
alias_method_chain :step, :time_with_zone
private
def ensure_iteration_allowed
if first.is_a?(Time)
raise TypeError, "can't iterate from #{first.class}"
end
end
end
대신 모듈로 교체하십시오.
require 'active_support/core_ext/module/aliasing'
module RangeWithTimeWithZoneSupport #:nodoc:
def each(&block)
ensure_iteration_allowed
super(&block)
end
def step(n = 1, &block)
ensure_iteration_allowed
super(n, &block)
end
private
def ensure_iteration_allowed
if first.is_a?(Time)
raise TypeError, "can't iterate from #{first.class}"
end
end
end
Range.send(:prepend, RangeSupportingTimeWithZone)
Range#each
가 더 깔끔합니다. 이름이 바뀌지 않고 ensure_iteration_allowed
원숭이 패치가 적용되지 않았습니다.
패치가 아닌 상속 사용
Ruby는 엄청난 유연성을 제공하며 이것이 제가 좋아하는 이유 중 하나입니다. 그러나 강력한 개체 모델도 있습니다. 따라서 자신의 코드를 삽입하려면 해킹하기 전에 모듈과 상속에 기대어 보세요. 코드를 훨씬 더 쉽게 이해하고 디버그할 수 있으며 alias_method_chain
과 같이 감지하기 어려운 부작용을 피할 수 있습니다. .
alias_method_chain
내가 Rails에서 소개된 가장 멋진 방법 중 하나였습니다. 그러나 그 날은 셀 수 있습니다. 우리는 그것을 능가했습니다. 그리고 그것이 사라지면 나는 그것을 놓치지 않을 것입니다.