2023.04.18 - [Programming/Ruby On Rails] - [Ruby On Rails] Module Mixin
이전에 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 |