티스토리 뷰

Ruby

[RoR] Rails ActiveRecord

강씨아저씨 2019. 10. 3. 12:26

 나름 바쁘다는 핑계로 안 쓰다가 오랜만에 포스팅이다.

오늘의 포스팅 내용은 ActiveRecord의 구조를 간략히 알아보자 이다.

포스팅의 내용은 아래의 책을 읽고 내용들을 정리한 것이다.

https://books.google.co.kr/books/about/Metaprogramming_Ruby_2.html?id=V0iToAEACAAJ&source=kp_cover&redir_esc=y

 

ActiveRecord 란?

 ActiveRecord는 Ruby의 객체와 Database를 맵핑해주는 라이브러리이다. 

이는 ORM 이라 불리며 이를 이용해 RDB 와 OOP 간의 변화를 자연스럽게 사용할 수 있다. 

 

 오늘은 ActiveRecord의 소스코드 구조에 대해서 확인하고, 이들이 어떻게 조합되는지에 대해서

알아볼 예정이다. 우선 ActiveRecord의 간략한 예제를 확인해보자. 

class Fog < ActiveRecord::Base
  validate do
    errors.add(:base, "Illegal fog name.") unless name[0] == 'F'
  end
end

 다음과 같이 ActiveRecord를 상속받은 Fog라는 클래스가 있다. 그리고 Rails에서는 관례상 Fog라는 클래스와 fogs라는 테이블이 매칭 되며 테이블이 존재한다면, 우리는 Fog 클래스를 이용해서 Database의 fogs 테이블에 데이터 CRUD 가 가능하다. 

my_fog = Fog.new #<Fog id: nil, name: nil, created_at: nil, updated_at: nil>
my_fog.name= "Fog_NAME" # "Fog_NAME"
my_fog.valid? # true
my_fog.save! # true

Rails 의 my_fog 객체의 정보가 DB에 추가되었다 :)

 Fog 클래스에 대해서 조금 더 살펴보자면 상속받은 ActiveRecord::Base는 ORM을 위한 여러 가지 기능들을 포함하고 있는 ActiveRecord에서 가장 중요한 클래스이고, Fog 클래스의 유효성 검사를 위해서 정의된 validate이전에 소개한 Class Macro 이며 유효성판단을 위해 block 내부 로직을 사용하고 있다. 우리는 validate 내부에서 어떤 작업을 하는지 모두 알고 있을 필요는 없으며 단지 block 의 로직대로 name의 첫 글자가 'F' 로 시작하지 않으면 save! 시점에 예외가 발생한다는 것만 알고 있으면 된다. 그리고 Fog 클래스에는 name이라는 attribute 를 정의하지 않았지만 my_fog 객체의 name 에 "Fog_NAME" 을 할당하기 위해  Ghost Method 를 이용했다.

 이처럼 Rails 에서 사용하는 기능들은 이전에 알아본 Ruby의 Metaprogramming 을 응용해서 구현되었다고 이해하면 도움이 될 것 같다.

 

ActiveRecord::Base 

 위의 설명에서 ActiveRecord::Base 는 여러 가지 기능을 포함하고 있기 때문에 ActiveRecord 의 가장 중요한 클래스라고 설명했다. ActiveRecord 는 데이터 저장, 삭제, 검색, 유효성 검사 등의 복잡하고 많은 기능들을 제공하고 있기 때문에 

ActiveRecord::Base 또한 수천 라인의 코드로 구성되어있는 클래스라고 생각할 수도 있다. 

 하지만 실제로 ActiveRecord::Base 를 확인해보면 내부에는 하나의 함수도 존재하지 않는다. ActiveRecord::Base 는 ORM을 위한 여러 가지 Module들을 조합으로 갖고 있을 뿐이다. 

module ActiveRecord
  class Base
    extend ActiveModel::Naming
    extend ActiveSupport::Benchmarkable
    extend ActiveSupport::DescendantsTracker
    extend ConnectionHandling
    extend QueryCache::ClassMethods
    extend Querying
    extend Translation
    # ...
    include Core
    include Persistence
    include NoTouching
    include ReadonlyAttributes
    # ... 
    include Validations
    # ...
  end
  ActiveSupport.run_load_hooks(:active_record, Base)
end

이렇게 ActiveRecord::Base 에 포함된 Module 들 또한 내부의 여러 가지 Module 들을 포함하고 있고 있기 때문에

실제로 ActiveRecord::Base 는 보는 것보다 더 많은 Module 들을 포함하고 있다. 

ActiveRecord::Base 에서 특정 기능을 담당하는 Module 이 어떤 것인지를 예상하는 것은 그리 어려운 일이 아니다. 

예를 들어 저장, 삭제에 대한 기능이 구현되어 있는 Module 은 Persistence 인 것을 예상해볼 수 있다.

module ActiveRecord
  module Persistence
    # ...
    def save(*args, &block)
    def save!(*args, &block)
    def delete
    # ...
  end
end

그렇다면 이번에는 valid?validate 같은 유효성 검사를 담당하는 Module 에 대해서도 알아보자.

 

ActiveRecord::Validations

다음은 ActiveRecord::Validations 코드의 내부이다.

module ActiveRecord
  module Validations
    include ActiveModel::Validations
    # ...
    def valid?(context = nil) 
    # ...
  end
end

해당 코드를 보면 valid? 함수가 ActiveRecord::Validations 에 정의되어 있다는 것을 확인할 수 있다. 그리고 ActiveRecord::Base 는 ActiveRecord::Validations 를 include 했기 때문에 ActiveRecord::Base 를 상속받은 Fog 클래스는 instance_method 로 valid? 를 호출할 수 있다는 것도 알 수 있다. 

my_fog.valid? # true

 그런데 validate 라는 Class Macro 를 사용하기 위해서는 class method 가 필요한데 validate 라는 함수는 어디에 정의되어 있고 어떻게 사용할 수 있었을까?

 우선 우리가 찾고 있는 validate 라는 함수는 ActiveRecord::Validations 에 include 된 ActiveModel::Validations 에 정의되어 있다. 하지만 아직 해결되지 않은 의문점이 있다. Ruby에서 일반적으로 Module 을 include 를 이용해서 instance method 를 얻을 수 있다는 것은 알고 있다. 그런데 validate 는 class method 이다. 어떻게 include 를 했는데 instance method 뿐만 아니라 class method 도 얻을 수 있었을까? 이에 대한 비밀은 ActiveSupport::Concern Module 에서 확인할 수 있지만 다음에 설명하기로 하고 일단은 넘어가겠다.

 또 다른 의문점은 ActiveRecord::Validations 와 ActiveModel::Validations 는 유사한 기능인데 왜 두 개나 필요한것일까? 이다. 책의 설명에 따르면 실제로 예전버전(1.2.6 이하)에서는 ActiveModel::Validations 가 존재하지 않았고 validate 또한 ActiveRecord::Validations 에 포함되어 있었다.

 그러나 시간이 갈수록 ActiveRecord 는 계속해서 발전해나갔고 이에 따라 Validations 또한 2가지로 분리하게 되었다. Database 와 연관된 작업에 대한 유효성 검사 작업은 ActiveRecord::Validations 에서 담당하고, Database 와 상관없이 객체에 대한 유효성 검사를 담당하는 작업은 ActiveModel::Validations 에 할당하게 했다. 이렇게 2가지로 분리하면서 ActiveModel::Validations 가 생기게 되었다.

 

우리는 그 외에 다른 Module 들에 대해서도 알아볼 수 있지만 오늘은 여기까지만 알아보려 한다. 

 

오늘 알아본 내용을 요약하면

1. ActiveRecord 는 Rails 에서 ORM을 위한 라이브러리이다.

2. ActiveRecord::Base 는 Module 의 집합이다. 

3. ActiveRecord 에 적용된 기능들은 Ruby의 Metaprogramming 을 응용해서 구현되어 있다.

4. ActiveRecord 의 유효성 검사는 2가지로 분리되고, 하나는 ActiveRecord::Validations 이고, 다른 하나는 ActiveModel::Validations 이다.

 

누군가에게는 도움이 되었기를 바라면서 오늘의 포스팅 끝~!

댓글