- app/assets 디렉터리에서 파일을 읽지 마십시오
- 시퀀스 생성 속성의 절대값에 대해 단언하지 마십시오
-
expect_any_instance_of
또는allow_any_instance_of
를 RSpec에서 사용하지 마십시오 Exception
을rescue
하지 마세요- 뷰에서 인라인 JavaScript를 사용하지 마세요
- 사전 컴파일이 필요 없는 자산 저장
has_many through:
또는has_one through:
연관 관계를 재정의하지 마세요
주의사항
이 가이드의 목적은 기여자들이 GitLab CE와 EE의 개발 중에 만날 수 있는 잠재적인 “주의사항”을 문서화하는 것입니다.
app/assets 디렉터리에서 파일을 읽지 마십시오
Omnibus GitLab은 자산 컴파일 후 app/assets
디렉터리를 삭제하였습니다.
ee/app/assets
, vendor/assets
디렉터리도 삭제됩니다.
이는 Omnibus 설치된 GitLab 인스턴스에서 해당 디렉터리의 파일을 읽는 것이 실패함을 의미합니다:
file = Rails.root.join('app/assets/images/logo.svg')
# 이 파일이 존재하지 않으므로, 읽기 시도는 실패합니다:
# Errno::ENOENT: No such file or directory @ rb_sysopen
File.read(file)
시퀀스 생성 속성의 절대값에 대해 단언하지 마십시오
다음 공장을 고려해 보십시오:
FactoryBot.define do
factory :label do
sequence(:title) { |n| "label#{n}" }
end
end
다음 API 스펙을 고려해 보십시오:
require 'spec_helper'
RSpec.describe API::Labels do
it '첫 번째 라벨을 생성합니다' do
create(:label)
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('label1')
end
it '두 번째 라벨을 생성합니다' do
create(:label)
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('label1')
end
end
실행하면, 이 스펙은 우리가 기대하는 것과 다르게 동작합니다:
1) API::API 시퀀스 문제 재현 두 번째 라벨을 생성합니다
Failure/Error: expect(json_response.first['name']).to eq('label1')
expected: "label1"
got: "label2"
(compared using ==)
이는 FactoryBot 시퀀스가 각 예제에 대해 초기화되지 않기 때문입니다.
시퀀스 생성 값은 공장을 사용할 때 고유성 제약이 있는 속성을 명시적으로 설정하지 않기 위해 존재함을 기억하세요.
해결 방법
시퀀스 생성 속성 값에 대해 단언하려는 경우, 명시적으로 설정해야 합니다.
또한, 설정한 값은 시퀀스 패턴과 일치하지 않아야 합니다.
예를 들어, :label
공장을 사용할 때, create(:label, title: 'foo')
는 괜찮지만 create(:label, title: 'label1')
는 안 됩니다.
다음은 수정된 API 스펙입니다:
require 'spec_helper'
RSpec.describe API::Labels do
it '첫 번째 라벨을 생성합니다' do
create(:label, title: 'foo')
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('foo')
end
it '두 번째 라벨을 생성합니다' do
create(:label, title: 'bar')
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('bar')
end
end
expect_any_instance_of
또는 allow_any_instance_of
를 RSpec에서 사용하지 마십시오
이유
-
고립되어 있지 않기 때문에 때때로 깨질 수 있습니다.
-
우리가 스텁하고자 하는 메서드가 전처리 모듈에 정의되어 있을 경우 작동하지 않습니다. 이는 EE에서 매우 일반적입니다. 다음과 같은 오류가 발생할 수 있습니다:
1.1) Failure/Error: expect_any_instance_of(ApplicationSetting).to receive_messages(messages) Using `any_instance` to stub a method (elasticsearch_indexing) that has been defined on a prepended module (EE::ApplicationSetting) is not supported.
대안
대신, 다음 중 하나를 사용하세요:
expect_next_instance_of
allow_next_instance_of
expect_next_found_instance_of
allow_next_found_instance_of
예를 들어:
# 이렇게 하지 마세요:
expect_any_instance_of(Project).to receive(:add_import_job)
# 이렇게 하지 마세요:
allow_any_instance_of(Project).to receive(:add_import_job)
다음과 같이 작성할 수 있습니다:
# 이렇게 하세요:
expect_next_instance_of(Project) do |project|
expect(project).to receive(:add_import_job)
end
# 이렇게 하세요:
allow_next_instance_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
# 이렇게 하세요:
expect_next_found_instance_of(Project) do |project|
expect(project).to receive(:add_import_job)
end
# 이렇게 하세요:
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
Active Record가 모델 클래스에서 객체를 인스턴스화하기 위해 .new
메서드를 호출하지 않기 때문에,
Active Record 쿼리 및 파인더 메서드에서 반환된 객체에 대해 모의 설정을 하기 위해 expect_next_found_instance_of
또는 allow_next_found_instance_of
모의 헬퍼를 사용해야 합니다.
같은 Active Record 모델의 여러 인스턴스에 대해 모의 및 기대를 설정할 수도 있습니다.
expect_next_found_(number)_instances_of
및 allow_next_found_(number)_instances_of
헬퍼를 사용하여 다음과 같이 할 수 있습니다:
expect_next_found_2_instances_of(Project) do |project|
expect(project).to receive(:add_import_job)
end
allow_next_found_2_instances_of(Project) do |project|
allow(project).to receive(:add_import_job)
end
인스턴스를 특정 인수로 초기화하고 싶다면, 다음과 같이 전달할 수도 있습니다:
# 이렇게 하세요:
expect_next_instance_of(MergeRequests::RefreshService, project, user) do |refresh_service|
expect(refresh_service).to receive(:execute).with(oldrev, newrev, ref)
end
이는 다음을 기대합니다:
# 위의 기대:
refresh_service = MergeRequests::RefreshService.new(project, user)
refresh_service.execute(oldrev, newrev, ref)
Exception
을 rescue
하지 마세요
“Ruby에서 rescue Exception => e
가 나쁜 스타일인 이유는 무엇입니까?”를 참조하세요.
이 규칙은 RuboCop에 의해 자동으로 시행됩니다.
뷰에서 인라인 JavaScript를 사용하지 마세요
인라인 :javascript
Haml 필터를 사용할 경우 성능 오버헤드가 발생합니다. 인라인 JavaScript를 사용하는 것은 코드를 구조화하는 좋은 방법이 아니며 피해야 합니다.
우리는 이 두 필터를 제거했습니다.
추가 읽기
- 스택 오버플로: 인라인 JavaScript를 작성하지 말아야 하는 이유
사전 컴파일이 필요 없는 자산 저장
사용자에게 제공되어야 하는 자산은 app/assets
디렉토리에 저장되며, 이후 사전 컴파일되어 public/
디렉토리에 배치됩니다.
그러나 app/assets
내의 파일 내용에 애플리케이션 코드에서 접근할 수 없습니다.
이는 공간 절약 조치로 인해 프로덕션 설치에서 해당 폴더를 포함하지 않기 때문입니다.
support_bot = Users::Internal.support_bot
# `app/assets` 폴더에서 파일 접근하기
support_bot.avatar = Rails.root.join('app', 'assets', 'images', 'bot_avatars', 'support_bot.png').open
support_bot.save!
위 코드는 로컬 환경에서는 작동하지만, 프로덕션 설치에서는 app/assets
폴더가 포함되지 않기 때문에 오류가 발생합니다.
솔루션
대안은 lib/assets
폴더입니다. 다음 조건을 충족하는 자산(이미지 등)을 리포지토리에 추가해야 할 경우 사용하세요:
- 자산이 사용자에게 직접 제공될 필요가 없고 (따라서 미리 컴파일될 필요도 없음)
- 자산이 애플리케이션 코드에서 접근되어야 함.
간단히 말해:
app/assets
는 미리 컴파일되고 최종 사용자에게 제공되어야 하는 모든 자산을 저장하는 데 사용하세요.
lib/assets
는 최종 사용자에게 직접 제공될 필요는 없지만 애플리케이션 코드에서 접근해야 하는 모든 자산을 저장하는 데 사용하세요.
참조용 MR: !37671
has_many through:
또는 has_one through:
연관 관계를 재정의하지 마세요
:through
옵션이 있는 연관 관계는 재정의하지 않는 것이 좋습니다. 우리는 실수로 잘못된 객체를 파괴할 수 있기 때문입니다.
이는 destroy()
메소드가 has_many through:
및 has_one through:
연관 관계에 대해 다르게 동작하기 때문입니다.
group.users.destroy(id)
위의 코드 예제는 User
레코드를 파괴하는 것처럼 보이지만, 실제로는 Member
레코드를 파괴하고 있습니다. 이는 users
연관 관계가 Group
에서 has_many through:
연관 관계로 정의되어 있기 때문입니다:
class Group < Namespace
has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source
has_many :users, through: :group_members
end
그리고 Rails에서는 이러한 연관 관계에서 destroy()
를 사용할 때 다음과 같은 동작을 가지게 됩니다:
:through 옵션이 사용되는 경우, 객체 자체가 아닌 조인 레코드가 대신 파괴됩니다.
그래서 User
와 Group
을 연결하는 조인 레코드인 Member
레코드가 파괴되고 있습니다.
이제 users
연관 관계를 재정의하면 다음과 같이 됩니다:
class Group < Namespace
has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source
has_many :users, through: :group_members
def users
super.where(admin: false)
end
end
재정의된 메소드는 이제 destroy()
의 위의 동작을 변경하여 다음과 같이 실행할 경우
group.users.destroy(id)
User
레코드가 삭제되며, 이는 데이터 손실로 이어질 수 있습니다.
간단히 말해, has_many through:
또는 has_one through:
연관 관계를 재정의하는 것은 위험할 수 있습니다.
이를 방지하기 위해 !131455에서 자동 검사를 도입하고 있습니다.
자세한 내용은 이슈 424536을 참조하세요.