2023.04.18 - [Programming/Ruby On Rails] - [Ruby On Rails] Module Mixin
[Ruby On Rails] Module Mixin
Ruby는 다중 상속을 지원하지 않는다. 즉 Ruby 클래스는 하나의 부모 클래스(슈퍼 클래스)만 가질 수 있다는 뜻이다. Ruby에서는 다중 상속을 모듈의 Mixin을 통해 구현이 가능하다. Mixin에 대해 알아
garonnome.tistory.com
이전에 Module Mixin에 기본적인 개념을 살펴보았다.
모듈을 사용하다 보면, 모듈을 하나의 클래스에 동시에 extend와 include를 하고 싶은 경우가 생기기도 한다.
아래와 같이 클래스 내에서 두 번 Mixin 하는 것이 문법적으로는 가능하지만,
의도와는 다르게 모듈의 메서드들이 클래스 메서드가 되는 동시에 인스턴스 메서드가 되어버린다.
module MyModule
def included_method
"included"
end
def extended_method
"extended"
end
end
class MyClass
include MyModule
extend MyModule
end
이런 경우 모듈이 mixin 될 때 호출되는 hook을 이용하여 해결할 수 있다.
hook에는 included, prepended, extended 가 있다.
로그 모듈을 Record 클래스에 동시에 extend, include 하고자 한다.
module Loggable
def self.included(base)
base.extend ClassMethods
base.class_eval do
attr_accessor :enable_log
end
end
module ClassMethods
def loggable_list
all.select { |item| item.enable_log? }
end
end
def loggable?
enable_log == true
end
end
class Record < ApplicationRecord
include Loggable
end
Record 클래스가 Loggable을 include 함으로써 다음 세 가지가 가능해진다.
- Record.new.loggable?: 모듈이 include 되어 인스턴스 메서드가 확장된다.
- Record.loggable_list: Record 클래스가::ClassMethods를 extend하여 클래스 메서드가 확장된다.
- Record.new.enable_log: class_eval을 통해 Record 클래스의 컨텍스트에서 블록이 실행되고, Record 모델에 enable_log라는 인스턴스 변수에 대한 getter 및 setter 메서드를 정의한다.
Ruby는 클래스나 객체를 즉석에서 수정할 수 있는 기능을 제공하는데, 여기서 사용한 class_eval이 그 역할이다.
class_eval 블록 내부의 코드는 마치 base 클래스 내부에 작성된 것처럼 동작한다.
Rails 4.0부터는 위 코드와 같은 패턴을 ActiveSerport::Concern을 통해 간편하게 사용할 수 있다.
ActiveSerport::Concern 이란?
ActiveSerport::Concern는 모듈을 확장하는 모듈이다.
여러 클래스에 대한 공통 논리를 재사용 가능한 별도의 모듈로 분리할 수 있다.
Concern이라는 이름은 AOP(Aspect Oriented Programming)에서 유래했다.
AOP의 관심사는 "기능의 응집력 있는 영역"을 캡슐화 하는 것이다.
즉, ActiveSupport::Concern은 클래스 자체 및 클래스의 싱글톤 클래스의 ancestors(조상 목록)을 변경하고, included hook을 통해 대상 클래스를 직접 조작함으로써 대상 클래스의 동작을 확장할 수 있는 메카닉을 제공한다.
ActiveSerport::Concern을 extend한 모듈에서는 다음 두 블록을 사용할 수 있다.
- included
- self.included(base)를 대체한다.
- 이 블록 내부 코드를 해당 모듈을 include한 클래스에 자동으로 class_eval 해주기 때문에, before_action이나 has_many, scope 등 여러 유용한 hook이나 association을 재사용하기 쉬워진다.
- class_methods
- base.extend ClassMethods를 대체한다.
- 이 블록 안에서 정의된 메서드는 해당 모듈을 include한 클래스의 클래스 메서드로 확장된다.
module Loggable
extend ActiveSupport::Concern
included do
attr_accessor :enable_log
end
class_methods do
def loggable_list
all.select { |item| item.enable_log? }
end
end
def loggable?
enable_log == true
end
end
class Record < ApplicationRecord
include Loggable
end
ActiveSerport::Concern은 패턴을 간편하게 줄여주는 것 뿐만 아니라 모듈간 의존성 문제도 해결해준다.
ActiveSerport::Concern을 사용하지 않은 예시를 먼저 알아보자.
MyModule이 include 될 때 연결관계를 추가하려고 한다.
그런데 MyModule에 메소드를 더 추가하고 싶어 MyModule을 include한 ExtendedModule을 만들었다.
module MyModule
def self.included(base)
base.class_eval do
has_many :something
end
end
end
module ExtendedModule
include MyModule
def another_method
end
end
class MyClass
include ExtendedModule
end
MyClass에 ExtendedModule을 include 했을 때 정상적으로 동작할까?
# NoMethodError (undefined method `has_many' for MyModule:Module)
MyModule의 included hook에 들어온 base가 MyClass가 아닌 ExtendedModule이 되는데, ExtendedModule에는 has_many가 정의되어 있지 않기 때문에 에러가 발생한다.
ActiveSerport::Concern을 사용하면 모듈이 ActiveSerport::Concern가 포함되지 않은 클래스 및 모듈에 포함되기 전까지 hook을 지연 실행시켜 의존성 문제를 해결할 수 있다.
module MyModule
extend ActiveSupport::Concern
included do
has_many :something
end
end
module ExtendedModule
extend ActiveSupport::Concern
include MyModule
end
class MyClass < ApplicationRecord
include ExtendedModule
end
Concern을 통해 MyModule의 included 블록 내부 코드는 MyClass에 추가되어 의도했던 대로 실행이 가능해졌다.
<참고>
https://www.akshaykhot.com/how-rails-concerns-work-and-how-to-use-them/
https://engineering.appfolio.com/2013/06/17/ruby-mixins-activesupportconcern/
'Programming > Ruby On Rails' 카테고리의 다른 글
[Ruby On Rails] before_action 및 after_action 사용법과 실행 순서 (0) | 2023.06.17 |
---|---|
[Ruby On Rails] database migration 관련 명령어 모음 (0) | 2023.05.18 |
[Ruby On Rails] Module Mixin (1) - include, prepend, extend (0) | 2023.04.18 |
[Ruby On Rails] Eager loading으로 N+1 문제 해결하기 (0) | 2023.04.16 |
[Ruby On Rails] Elasticsearch 검색 템플릿(Search Template) 사용하기 (0) | 2023.03.28 |