01. 액티브레코드
액티브레코드
액티브레코드(ActiveRecord)는 애플리케이션과 데이터베이스 간의 상호운용성(interoperability)을 담당하는 객체-관계 맵핑 계층이자 데이터 추상화를 담당하기도 한다. (출처: 위키피디어)
sum 메서드
sum 메서드 표현식
이제 예를 들면 sum 같이, 계산을 처리하는 액티브레코드 메서드에서 식(expression)을 사용할 수 있게 되었다.
- Person.sum("2 * age")
sum method의 디폴트 반환값 변경
이전 버전에서는, 만약 우리가 액티브레코드의 sum 메서드를 사용하여 테이블의 모든 행의 합을 계산하는데 있어, 메서드 호출 시에 준 조건에 부합하는 행이 아무 것도 없을 경우, 디폴트 반환값은 nil이었다.
레일스 2.1에서는, 디폴트 반환값(즉, 아무 행도 발견되지 않은 경우)은 0이다. 예제를 보자:
- Account.sum(:balance, :conditions => '1 = 2') #=> 0
Has_one
through 옵션 지원
has_one 메서드에 through 옵션이 생겼다. 이것은 마치 has_many :through 처럼 동작하지만, 하나의 액티브레코드 객체에 대한 연관을 표현한다는 점이 다르다.
- class Magazine < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :magazine
belongs_to :user
end
class User < ActiveRecord::Base
has_many :subscriptions
has_one :magazine, :through => : subscriptions,
:conditions => ['subscriptions.active = ?', true]
end
Has_one이 :source_type을 가짐
앞서 언급한 has_one :through 메서드는 또한 :source_type을 취할 수도 있다. 이 부분은 예제를 가지고 설명하겠다. 다음과 같은 두 개의 클래스로 시작하자.
- class Client < ActiveRecord::Base
has_many :contact_cards
has_many :contacts, :through => :contact_cards
end
여기 보인 것은 Client 클래스가 여러 종류의 컨택(contacts)을 가지는(has_many) 경우인데, 왜냐면 ContactCard 클래스가 다형적 연관관계(polymorphic relationship)를 갖기 때문이다.
다음 단계로는, ContactCard를 표현하는 두 개의 클래스를 만들자.
- class Person < ActiveRecord::Base
has_many :contact_cards, :as => :contact
end
class Business < ActiveRecord::Base
has_many :contact_cards, :as => :contact
end
Person과 Business는 ContactCard 테이블을 통해(through) 내 Client 클래스와 연결된다. 다시 말하면, 나는 두 종류의 컨택, 즉 개인적인 것(personal)과 업무적인 것(business)을 가진다는 말이다.
그렇지만 이건 작동하지 않을 것이다. 내가 컨택을 추출하려하면 어떤 일이 일어나는지 보자.
- >> Client.find(:first).contacts
# ArgumentError: /…/active_support/core_ext/hash/keys.rb:48:
# in `assert_valid_keys’: Unknown key(s): polymorphic
이게 작동하게 하려면 :source_type을 사용해야 한다. 이제 Client 클래스를 변경하자.
- class Client < ActiveRecord::Base
has_many :people_contacts,
:through => :contact_cards,
:source => :contacts,
:source_type => :person
has_many :business_contacts,
:through => :contact_cards,
:source => :contacts,
:source_type => :business
end
이제 우리는 컨택을 추출하는 두 가지 다른 방법이 생겼다. 어떻게 그럴 수 있는지 유심히 보자. 이제 우리는 어느 컨택의 :source_type을 원하는지 말할 수가 있다.
- Client.find(:first).people_contacts
Client.find(:first).business_contacts
Named_scope
has_finder 젬이 named_scope라는 다른 이름으로 레일스에 추가되었다.
이 추가가 레일스에 어떤 것을 가져오는지 완전히 이해하기 위해 다음 예제를 살펴 보자.
- class Article < ActiveRecord::Base
named_scope :published, :conditions => {:published => true}
named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%’"
end
Article.published.paginate(:page => 1)
Article.published.containing_the_letter_a.count
Article.containing_the_letter_a.find(:first)
Article.containing_the_letter_a.find(:all, :conditions => {…})
모든 게시된(published) 포스트를 반환하기 위해 published라는 새로운 메서드를 생성하는 대신, 여기서는 이를 위해 named_scope 을 사용하고 있다. 그렇지만 이게 다가 아니다. 어떻게 사용될 수 있는지를 보이는 다른 예제를 하나 보자.
- named_scope :written_before, lambda { |time|
{ :conditions => ['written_on < ?', time] }
}
named_scope :anonymous_extension do
def one
1
end
end
named_scope :named_extension, :extend => NamedExtension
named_scope :multiple_extensions,
:extend => [MultipleExtensionTwo, MultipleExtensionOne]
proxy_options로 named_scope 테스팅하기
Named scopes은 레일스 2.1에서 매우 흥미로운 새 기능이지만, 어느 정도 사용하고 난 뒤 보다 복잡한 상황에서의 테스트를 작성하는데 있어 곤란을 겪을 수도 있다.
예제를 하나 보자.
- class Shirt < ActiveRecord::Base
named_scope :colored, lambda { |color|
{ :conditions => { :color => color } }
}
end
범위 생성(scope generation)을 검증하는 테스트는 어떻게 작성할까?
이 문제를 해결하고자, proxy_options 메서드가 나왔다. 이 메서드는 named_scope에 사용된 옵션들을 조사할 수 있게 해준다. 만약 위의 코드를 테스트한다면, 다음과 같이 작성할 수 있을 것이다.
- class ShirtTest < Test::Unit
def test_colored_scope
red_scope = { :conditions => { :colored => 'red' } }
blue_scope = { :conditions => { :colored => 'blue' } }
assert_equal red_scope, Shirt.colored('red').scope_options
assert_equal blue_scope, Shirt.colored('blue').scope_options
end
end
Increment와 decrement
액티브레코드의 메서드 increment와 increment!, 그리고 decrement와 decrement!는 이제 새로운 옵션 매개변수를 받을 수 있게 되었다. 이전 버전의 레일스에서라면 여러분은 주어진 컬럼에서 1(하나)을 더하거나 빼는 데에 이들 메서드를 사용할 수 있었을 것이다. 레일스 2.1에서는 더하거나 빼는 값을 지정할 수 있다. 다음과 같다.
- player1.increment!(:points, 5)
player2.decrement!(:points, 2)
위 예제에서 나는 player1에는 5점을 더하고, player2에서는 2점을 빼고 있다. 이 인수는 선택적이기 때문에, 기존(legacy) 코드는 영향을 받지 않는다.
Find
Conditions
이제부터는, 액티브레코드의 find 메서드에 매개변수로 객체를 넘길 수 있다. 다음 예제를 보자.
- class Account < ActiveRecord::Base
composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
end
이 경우, 우리는 다음과 같이 Account 클래스의 find 메서드 매개변수로 Money의 인스턴스를 전달할 수 있다.
- amount = 500
currency = "USD"
Account.find(:all, :conditions => { :balance => Money.new(amount, currency) })
Last
지금까지 우리는 액티브레코드의 find 메서드를 사용하여 데이터를 찾는데 세 개의 연산자만을 사용할 수 있었다. 즉, :first와 :all 그리고 객체의 자체 id가 그것이었다(이 경우는 id 자체를 제외한 어떠한 인수도 전달하지 않는다).
레일스 2.1에서는 네번째 연산자가 생겼으니, 이름하여 :last다. 다음은 몇 가지 예제이다.
- Person.find(:last)
Person.find(:last, :conditions => [ "user_name = ?", user_name])
Person.find(:last, :order => "created_on DESC", :offset => 5)
이 새로운 연산자가 어떻게 작동하는건지 완전하게 이해하려면, 다음 테스트만 보면 된다.
- def test_find_last
last = Developer.find :last
assert_equal last, Developer.find(:first, :order => 'id desc')
end
All
정적 메서드인 all은 마찬가지로 정적 메서드인 find(:all)의 별칭이다. 예제는 다음과 같다.
- Topic.all is the same as Topic.find(:all)
First
정적 메서드인 first은 마찬가지로 정적 메서드인 find(:first)의 별칭이다. 예제는 다음과 같다.
- Topic.first is the same as Topic.find(:first)
Last
정적 메서드인 last은 마찬가지로 정적 메서드인 find(:last)의 별칭이다. 예제는 다음과 같다.
- Topic.last is the same as Topic.find(:last)
named_scope에서 first와 last 메서드 사용하기
앞서 언급한 모든 메서드들은 named_scope에서도 동작한다. recent라는 named_scope을 만든다고 하자. 다음은 문법적으로 올바른 것이다.
- post.comments.recent.last
Eager Loading
이 새로운 기능을 설명하기 위해, 다음 코드를 보자.
- Author.find(:all, :include => [:posts, :comments])
여기서 나는 authors 테이블을 검색 중이며, 또한 posts와 comments 테이블도 쿼리 속에 포함하여 검색 중이다. 이 때 쿼리는 author id 컬럼을 통해 이루어지는데, 왜냐면 이게 레일스에서 외래 키 이름 관례상 디폴트 컬럼명이기 때문이다. 이 검색은 다음과 같은 SQL 쿼리를 생성하게 된다.
- SELECT
authors."id" AS t0_r0,
authors."created_at" AS t0_r1,
authors."updated_at" AS t0_r2,
posts."id" AS t1_r0,
posts."author_id" AS t1_r1,
posts."created_at" AS t1_r2,
posts."updated_at" AS t1_r3,
comments."id" AS t2_r0,
comments."author_id" AS t2_r1,
comments."created_at" AS t2_r2,
comments."updated_at" AS t2_r3
FROM
authors
LEFT OUTER JOIN posts ON posts.author_id = authors.id
LEFT OUTER JOIN comments ON comments.author_id = authors.id
authors와 posts, 그리고 comments 테이블 간의 조인(joins)을 가지는 정확히 하나의 긴 SQL 쿼리이다. 우리는 이것을 카테시안 곱(cartesian product)이라 부른다.
이런 유형의 쿼리가 언제나 성능면에서 좋은 것은 아니기에, 레일스 2.1에서는 변경되었다. Author에 대한 동일한 쿼리가 이제는 세 개의 테이블에서 정보를 추출하는데 있어 다른 접근법을 사용한다. 레일스는 이제 세 개의 다른 쿼리 - 각 테이블 당 하나씩 -를 사용하며, 이것은 이전에 생성된 쿼리보다 짧은 쿼리들이다. 그 결과는 앞서 보인 레일스 코드를 실행시킨 뒤에 로그 파일을 통해 확인해 볼 수 있다.
- SELECT * FROM "authors"
SELECT posts.* FROM "posts" WHERE (posts.author_id IN (1))
SELECT comments.* FROM "comments" WHERE (comments.author_id IN (1))
대부분의 경우 세 개의 보다 간단한 쿼리가 하나의 복잡하고 긴 쿼리보다 빠르게 실행된다.
Belongs_to
belongs_to 메서드는 연관에서 :dependent => :destroy와 :delete를 사용할 수 있게 변경되었다. 다음은 예제다.
- belongs_to :author_address
belongs_to :author_address, :dependent => :destroy
belongs_to :author_address_extra, :dependent => :delete,
:class_name => "AuthorAddress"
다형적 URL(Polymorphic url)
액티브레코드를 가지고 작업을 하는 동안에 이름이 변경되는 라우터에 대한 조금 더 근사한 솔루션으로 다형적 URL에 대한 헬퍼 메서드를 사용할 수 있다.
이들 메서드는 연관지을 타입을 지정하지 않은 상태로 RESTful 리소스의 URL을 생성하고자 할 때 유용하다.
사용법은 간단하다. 몇 가지 예제를 보자.(주석으로 처리한 부분은 동일한 것을 레일스 2.1 이전 버전에서 했던 방식이다).
- record = Article.find(:first)
polymorphic_url(record) #-> article_url(record)
record = Comment.find(:first)
polymorphic_url(record) #-> comment_url(record)
# 방금 생성한 요소들도 식별할 수 있다
record = Comment.new
polymorphic_url(record) #-> comments_url()
polymorphic_url이 어떻게 주어진 타입을 식별하고서 정확한 라우터를 만들어 내는지를 유심히 보자. 중첩 리소스(nested resources)와 네임스페이스(namespaces)도 지원된다.
- polymorphic_url([:admin, @article, @comment])
#-> this will return:
admin_article_comment_url(@article, @comment)
또한 new나 edit, formatted 같은 접두어도 사용할 수 있다. 몇 가지 예제를 보자.
- edit_polymorphic_path(@post)
#=> /posts/1/edit
formatted_polymorphic_path([@post, :pdf])
#=> /posts/1.pdf
읽기전용 연관(Readonly relationships)
모델 간 연관관계에도 새로운 기능이 추가되었다. 모델의 상태가 변경되는 것을 막으려면 이제 연관을 기술할 때 :readonly를 사용할 수 있다. 몇 가지 예제를 보자.
- has_many :reports, :readonly => true
has_one :boss, :readonly => :true
belongs_to :project, :readonly => true
has_and_belongs_to_many :categories, :readonly => true
이렇게 하면 여러분의 연관 모델은 이 모델 속에서 수정으로부터 자유로울 것이다. 만약 이들 중 무언가를 수정하려고 하면, 여러분은 ActiveRecord::ReadOnlyRecord 예외를 받을 것이다.
add_timestamps와 remove_timestamps 메서드
add_timestamps와 remove_timestamps라는 두 개의 새 메서드가 생겼다. 이 메서드들은 각각 timestamp 컬럼을 추가하고 삭제한다. 예제를 하나 보자.
- def self.up
add_timestamps :feeds
add_timestamps :urls
end
def self.down
remove_timestamps :urls
remove_timestamps :feeds
end
Calculations
ActiveRecord::Calculations이 테이블명을 지원하기 위해 조금 변경되었다. 이것은 동일한 컬럼명을 가지는 서로 다른 테이블 간에 연관이 있는 경우 편리하다. 이제 다음 두 가지 옵션이 있다.
- authors.categories.maximum(:id)
authors.categories.maximum("categories.id")
ActiveRecord::Base.create accepts blocks
이미 우리는 ActiveRecord::Base.new 에서 블록을 받을 수 있다. 이제 동일한 것을 create 메서드에서도 할 수 있게 되었다.
- # 객체를 생성하여 블록으로 넘겨서 그 속성을 기술함.
User.create(:first_name => 'Jamie') do |u|
u.is_admin = false
end
바로 그 메서드는 동시에 여러 객체를 생성하는데도 사용할 수 있다.
- # 새로운 객체의 배열을 블록을 사용하여 생성함.
# 블록은 생성된 각 객체에 대해 한번씩 실행됨.
User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}]) do |u|
u.is_admin = false
end
또한 연관에서도 작동한다.
- author.posts.create!(:title => "New on Edge") {|p| p.body = "More cool stuff!"}
# 또는
author.posts.create!(:title => "New on Edge") do |p|
p.body = "More cool stuff!"
end
change_table
레일스 2.0에서 마이그레이션을 생성하는 것은 이전 버전에 비해 많이 섹시해 졌지만, 테이블을 변경하는 부분에서 마이그레이션을 사용하는 것은 전혀 섹시하지 않았다.
레일스 2.1에서는, 새로운 메서드인 change_table을 사용함으로써 테이블을 변경하는 것 역시 섹시해 졌다. 예제를 하나 살펴보자.
- change_table :videos do |t|
t.timestamps # 이것은 created_at과 updated_at 컬럼을 생성
t.belongs_to :goat # 이러면 goat_id 컬럼을 추가(integer)
t.string :name, :email, :limit => 20 # 컬럼 name과 email을 추가
t.remove :name, :email # name과 email 컬럼을 제거
end
이 새 메서드 change_table은 그 사촌격인 create_table과 비슷하게 작동하지만, 새로 테이블을 생성하는 대신, 이미 존재하는 테이블에서 컬럼이나 인덱스를 추가 또는 제거하는 방식으로 기존 테이블을 변경한다.
- change_table :table do |t|
t.column # 통상적인 컬럼 추가. 예: t.column(:name, :string)
t.index # 새 인덱스 추가.
t.timestamps
t.change # 컬럼 정의를 변경. 예: t.change(:name, :string, :limit => 80)
t.change_default # 컬럼의 디폴트 값을 변경.
t.rename # 컬럼명을 변경.
t.references
t.belongs_to
t.string
t.text
t.integer
t.float
t.decimal
t.datetime
t.timestamp
t.time
t.date
t.binary
t.boolean
t.remove
t.remove_references
t.remove_belongs_to
t.remove_index
t.remove_timestamps
end
Dirty Objects
이제 레일스에서 액티브레코드에 생긴 변경을 추적할 수 있게 되었다. 어떤 객체가 변경되었는지 여부를 검사하는 것이 가능해 진 것이다. 만약 변경이 된 경우라면, 최근의 변경을 추적할 수 있다. 몇 가지 예제를 보자.
- article = Article.find(:first) article.changed? #=> false
- article.title #=> "Title"
article.title = "New Title"
article.title_changed? #=> true
# 변경 전 title을 보여주오
article.title_was #=> "Title"
# 변경되기 전과 변경된 후
article.title_change #=> ["Title", "New Title"]
보다시피 아주 간단하다. 또한 다음 둘 중 하나의 방식으로 모든 변경의 목록을 볼 수도 있다.
- # 변경된 모든 속성들의 배열을 반환
article.changed #=> ['title']
# 변경 전후의 값과 함께 변경된 속성들의 해시를 반환
article.changes #=> { 'title’ => ["Title", "New Title"] }
어떤 객체가 저장되면, 그 상태가 변경됨에 유의하자.
- article.changed? #=> true
article.save #=> true
article.changed? #=> false
만약 attr=을 사용하지 않고 객체의 상태를 변경하려는 경우라면, 속성이 변경되었다는 사실을 명시적으로 알려야 하며, 이 때 사용하는 메서드가 attr_name_will_change!이다(attr 를 객체의 실제 속성으로 대체할 것). 마지막 하나의 예제를 보자.
- article = Article.find(:first)
article.title_will_change!
article.title.upcase!
article.title_change #=> ['Title', 'TITLE']
부분 갱신(Partial Updates)
Dirty Objects를 구현하는 것은 다른 아주 흥미로운 기능들의 시작점이 된다.
이제 우리는 객체의 변경된 상태가 어떤지를 추적할 수 있게 되었다. 그렇다면 데이터베이스에 불필요한 업데이트를 피하지 못할 이유가 어디 있을까?
이전 버전의 레일스에서는 이미 존재하는 액티브레코드 객체에 대해 save 메서드를 호출하면, 그 객체의 모든 필드들이 데이터베이스에 업데이트되었다. 심지어는 아무런 변경도 하지 않은 경우도 마찬가지였다.
이 작업은 Dirty Objects을 사용하는 것으로 상당부분 개선될 수 있었고, 그게 바로 지금 일어난 일이다. 레일스 2.1이 생성한 SQL 쿼리를 하나 살펴보자. 부분적인 변경을 가한 객체를 저장하는 것이다.
- article = Article.find(:first)
article.title #=> "Title"
article.subject #=> "Edge Rails"
# title을 변경하자
article.title = "New Title"
# 다음과 같은 SQL을 생성한다
article.save
#=> "UPDATE articles SET title = 'New Title' WHERE id = 1"
애플리케이션에서 변경된 필드만이 데이터베이스에 업데이트됨에 유의하자. 만약 애플리케이션에서 아무런 필드도 업데이트되지 않았다면, 액티브레코드는 아무런 업데이트도 수행하지 않을 것이다.
이 새 기능을 활성/비활성 시키려면, 모델과 연관된 partial_updates 속성을 변경하면 된다.
- # 활성화시키기
MyClass.partial_updates = true
만약 모든 모델에 대해 이 기능을 활성/비활성 시키고자 한다면, config/initializers/new_rails_defaults.rb 파일을 수정해야 한다.
- # 모든 모델에서 기능 활성화시키기
ActiveRecord::Base.partial_updates = true
만약 attr=메서드를 사용하지 않고 필드를 수정할 요량이라면, 이것 역시 config/initializers/new_rails_defaults.rb를 통해 레일스에 알려야 함을 잊지 말자.
- # 만약 여러분이 **attr=**를 사용한다면,
# 알리지 않아도 좋아요
person.name = 'bobby'
person.name_change # => ['bob', 'bobby']
# 그렇지만 **attr=**를 사용할 게 아니라면
# 필드가 변경될 거란 사실을 꼭 알려 주세요
person.name_will_change!
person.name << 'by'
person.name_change # => ['bob', 'bobby']
만약 이런 식으로 변경을 알리지 않으면, 추적이 이루어지지 않을 것이고, 따라서 데이터베이스 테이블은 제대로 업데이트되지 않을 것이다.
MySQL에서 Smallint, int 또는 bigint?
액티브레코드 MySQL 어댑터는 이제 데이터베이스의 컬럼을 생성하거나 변경할 때 조금 더 영리해져서 integer 타입을 사용할 수 있게 되었다. :limit 옵션을 통해 이제 컬럼이 smallint인지 int인지 또는 bigint인지를 알릴 것이다. 다음은 그게 어떻게 작동하는지를 보이는 예제다.
- case limit
when 0..3
"smallint(#{limit})"
when 4..8
"int(#{limit})"
when 9..20
"bigint(#{limit})"
else
'int(11)'
end
이제 이것은 migration 파일에 적용하여 각 컬럼에 대해 어떤 컬럼 타입이 만들어 지는지 알아 보자.
- create_table :table_name, :force => true do |t|
# 0 - 3: smallint
t.integer :column_one, :limit => 2 # smallint(2)
# 4 - 8: int
t.integer :column_two, :limit => 6 # int(6)
# 9 - 20: bigint
t.integer :column_three, :limit => 15 # bigint(15)
# if :limit is not informed: int(11)
t.integer :column_four # int(11)
end
PostgreSQL 어댑터에는 이 기능이 이미 들어가 있으며 이제 MySQL에서도 따라 왔다.
has_one과 belongs_to에서의 :select 옵션
이미 알려진 메서드인 has_one과 belongs_to에 새로운 옵션:select가 들어 왔다.
이 옵션의 디폴트 값은 "*"이지만("SELECT * FROM table"에서와 같이), 여러분은 사용하려고 하는 컬럼만 추출하도록 그 값을 편집할 수가 있다..
이 때 주(primary) 키와 외래 키(foreign keys)를 포함시키는걸 잊어서는 안된다. 그러지 않으면 오류가 나올 것이다.
belongs_to 메서드는 더 이상 :order 옵션을 가지지 않는다. 그렇다고 걱정할 건 없다. 그걸 사용하는 경우는 없었으니 말이다.
STI를 사용할 때 클래스의 완전한 이름 저장하기
네임스페이스(namespace) 및 STI와 함께 모델을 사용할 때, 액티브레코드는 단지 클래스명만을 저장하며, 그 네임스페이스는 저장하지 않는다(모듈이 풀어진다demodulized). 이렇게 되면 STI 상에 있는 모든 클래스가 동일한 네임스페이스에 있는 경우에만 작동하게 된다. 예제를 보자.
- class CollectionItem < ActiveRecord::Base; end
class ComicCollection::Item < CollectionItem; end
item = ComicCollection::Item.new
item.type # => 'Item’
item2 = CollectionItem.find(item.id)
# 오류가 나온다. 왜냐면 Item 클래스를 찾을 수 없기 때문이다.
이번 변경에서는 액티브레코드가 클래스의 전체 이름을 저장하도록 해주는 새로운 옵션이 하나 추가되었다.
이 기능을 활성/비활성 시키려면, 다음을 environment.rb에 포함시키거나 또는 편집해야 한다.
- ActiveRecord::Base.store_full_sti_class = true
Its default value is true.
table_exists? 메서드
AbstractAdapter 클래스에 table_exists?라는 새 메서드가 추가되었다. 사용법은 간단하다.
- >> ActiveRecord::Base.connection.table_exists?("users")
=> true
타임스탬프 기반 마이그레이션
이제 막 레일스를 시작하거나 또는 여러분만의 무언가를 개발하려 할 때, 마이그레이션은 여러분의 모든 문제를 해결해 주는 최상의 솔루션같아 보인다. 그러나 개발자들과 팀을 이루는 프로젝트에서는, 마이그레이션에서 생기는 레이스 조건(race condition)을 다루는 것이 꽤나 골칫거리가 된다는 걸 알게 될 것이다(아직은 아닐 수도 있지만). 이런 경우는 레일스 2.1에 새로 도입된 타임스탬프 기반 마이그레이션이 구원투수다.
타임스탬프 기반 마이그레이션(timestamped migrations)이 도입되기 전에는, 각각의 새로 생성된 마이그레이션에는 마이그레이션명 앞에 숫자가 따라 붙었다. 만약 두 개의 마이그레이션이 서로 다른 개발자에 의해 만들어져서 그 즉시 커밋되지 않은 경우라면, 똑같은 마이그레이션 번호에 서로 다른 정보를 담고 있는 마이그레이션이 만들어 질 수 있었다. 이 때 여러분의 schema_info는 구닥다리(out of date)가 되고 소스 컨트롤 상에는 충돌이 생긴다.
이 문제를 해결하기 위해 많은 "시도"들이 있었다. 여러 가지 플러그인들이 각기 다른 접근법으로 이 문제를 해결하기 위해 만들어 졌다. 사용할 수 있는 플러그인들에도 불구하고, 한가지는 분명해 졌다. 즉, 옛날 방식은 한마디로 작동하지 않았다는 것이다.
만약 여러분이 Git을 사용하고 있다면, 더 깊은 수렁에 빠지게 될 것인데, 왜냐면 여러분 팀에는 아마도 두 개 이상의 작업 브렌치(working branch)가 존재하고 그들 모두에 구닥다리 마이그레이션이 있을 것이기 때문이다. 아마도 브렌치를 병합할 때에 심각한 충돌 문제에 직면하게 될 것이다.
이 커다란 문제를 해결하기 위해 코어팀은 레일스의 마이그레이션 작동 방법을 변경하였다. schema_info의 버전 카운트에 대응하는 번호를 각 마이그레이션 파일의 앞에 붙이는 대신, UTC 시간에 기반하면서 YYYYMMDDHHMMSS 형식을 따르는 문자열을 앞에 붙이기로 한 것이다.
이와 함께 schema_migrations라고 하는 새로운 테이블도 만들어져서 이미 실행된 마이그레이션들이 무언지에 관한 정보를 담게 된다. 이런 식으로 하여, 만약 누군가가 조금 더 적은 번호의 마이그레이션을 생성하면, 레일스는 그 이전 버전까지 로 롤백(rollback)하게 되고 이어서 현재 버전에 이르기까지의 모든 것들을 실행할 것이다.
명백히, 이 방식은 마이그레이션과 관련된 충돌 문제를 해결한다.
이 기능을 비활성화 시키려면 environment.rb에 다음 줄을 포함시키면 된다.
- config.active_record.timestamped_migrations = false
또한 마이그레이션을 "헤집고 다니는" 새 레이크 태스크도 생겼다.
- rake db:migrate:up
rake db:migrate:down
History
Last edited on 08/19/2008 11:04 by deepblue
Comments (1)
Rails 킹왕짱
10/31/2008 18:10