Rails 생성기 및 템플릿 만들기와 사용자 정의
Rails 생성기는 워크플로우를 개선하는 필수적인 도구입니다. 이 가이드를 통해 생성기를 만들고 기존 생성기를 사용자 정의하는 방법을 배울 수 있습니다.
이 가이드를 읽고 나면 다음을 알 수 있습니다:
- 애플리케이션에서 사용 가능한 생성기를 확인하는 방법.
- 템플릿을 사용하여 생성기를 만드는 방법.
- Rails가 생성기를 호출하기 전에 어떻게 생성기를 검색하는지.
- 생성기 템플릿을 재정의하여 스캐폴드를 사용자 정의하는 방법.
- 생성기를 재정의하여 스캐폴드를 사용자 정의하는 방법.
- 많은 생성기를 덮어쓰지 않도록 대체 방법을 사용하는 방법.
- 애플리케이션 템플릿을 만드는 방법.
첫 번째 접촉
rails
명령을 사용하여 애플리케이션을 만들면 실제로 Rails 생성기를 사용하고 있습니다. 그 후에 bin/rails generate
를 실행하여 사용 가능한 모든 생성기 목록을 확인할 수 있습니다.
$ rails new myapp $ cd myapp $ bin/rails generate
참고: Rails 애플리케이션을 만들려면 rails
전역 명령을 사용하는데, 이는 gem install rails
로 설치된 Rails 버전을 사용합니다. 애플리케이션 디렉토리 내에서는 bin/rails
명령을 사용하는데, 이는 애플리케이션에 번들로 포함된 Rails 버전을 사용합니다.
Rails와 함께 제공되는 모든 생성기 목록이 표시됩니다. 특정 생성기에 대한 자세한 설명을 보려면 --help
옵션과 함께 생성기를 실행하세요. 예:
$ bin/rails generate scaffold --help
첫 번째 생성기 만들기
생성기는 Thor를 기반으로 구축되어 있으며, 구문 분석을 위한 강력한 옵션과 파일 조작을 위한 훌륭한 API를 제공합니다.
config/initializers
디렉토리에 initializer.rb
라는 이름의 초기화기 파일을 만드는 생성기를 만들어 보겠습니다. 첫 번째 단계는 lib/generators/initializer_generator.rb
에 다음 내용으로 파일을 만드는 것입니다:
class InitializerGenerator < Rails::Generators::Base def create_initializer_file create_file "config/initializers/initializer.rb", <<~RUBY # Add initialization content here RUBY end end
새로운 생성기는 매우 간단합니다: Rails::Generators::Base
를 상속받고 메서드 정의가 하나 있습니다. 생성기가 호출되면 정의된 순서대로 생성기의 모든 public 메서드가 순차적으로 실행됩니다. 우리의 메서드는 create_file
을 호출하여 주어진 대상 위치에 주어진 내용으로 파일을 만듭니다.
새로운 생성기를 실행하려면 다음과 같이 실행하면 됩니다:
$ bin/rails generate initializer
계속하기 전에 새로운 생성기의 설명을 확인해 보겠습니다:
$ bin/rails generate initializer --help
Rails는 일반적으로 생성기가 네임스페이스화되어 있으면 좋은 설명을 유추할 수 있지만, 이 경우에는 그렇지 않습니다. 이 문제를 두 가지 방법으로 해결할 수 있습니다. 첫 번째 방법은 생성기 내부에서 desc
를 호출하여 설명을 추가하는 것입니다:
class InitializerGenerator < Rails::Generators::Base desc "This generator creates an initializer file at config/initializers" def create_initializer_file create_file "config/initializers/initializer.rb", <<~RUBY # Add initialization content here RUBY end end
이제 새로운 생성기에 --help
를 실행하면 새로운 설명을 볼 수 있습니다.
두 번째 방법은 생성기와 같은 디렉토리에 USAGE
파일을 만드는 것입니다. 다음 단계에서 이 방법을 사용해 보겠습니다.
생성기로 생성기 만들기
생성기 자체에도 생성기가 있습니다. 우리의 InitializerGenerator
를 제거하고 bin/rails generate generator
를 사용하여 새로운 생성기를 만들어 보겠습니다:
$ rm lib/generators/initializer_generator.rb $ bin/rails generate generator initializer create lib/generators/initializer create lib/generators/initializer/initializer_generator.rb create lib/generators/initializer/USAGE create lib/generators/initializer/templates invoke test_unit create test/lib/generators/initializer_generator_test.rb
이것이 방금 생성된 생성기입니다:
class InitializerGenerator < Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) end
먼저 생성기가 Rails::Generators::Base
대신 Rails::Generators::NamedBase
를 상속받는다는 점에 주목하세요. 이는 생성기에 최소 하나의 인수가 필요하며, 이 인수는 초기화기의 이름이 되고 name
을 통해 코드에서 사용할 수 있습니다.
새로운 생성기의 설명을 확인해 보면 이를 알 수 있습니다:
$ bin/rails generate initializer --help Usage: bin/rails generate initializer NAME [options]
또한 생성기에 source_root
클래스 메서드가 있다는 것을 알 수 있습니다. 이 메서드는 템플릿이 있는 경우 그 위치를 가리킵니다. 기본적으로 방금 생성된 lib/generators/initializer/templates
디렉토리를 가리킵니다.
생성기 템플릿이 어떻게 작동하는지 이해하기 위해 lib/generators/initializer/templates/initializer.rb
파일을 다음 내용으로 만들어 보겠습니다:
# Add initialization content here
그리고 생성기를 호출할 때 이 템플릿을 복사하도록 생성기를 변경해 보겠습니다:
class InitializerGenerator < Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) def copy_initializer_file copy_file "initializer.rb", "config/initializers/#{file_name}.rb" end end
이제 생성기를 실행해 보겠습니다:
$ bin/rails generate initializer core_extensions create config/initializers/core_extensions.rb $ cat config/initializers/core_extensions.rb # Add initialization content here
copy_file
이 우리의 템플릿 내용으로 config/initializers/core_extensions.rb
를 만들었습니다. (file_name
메서드는 Rails::Generators::NamedBase
에서 상속받은 것입니다.)
생성기 명령줄 옵션
생성기는 class_option
을 사용하여 명령줄 옵션을 지원할 수 있습니다. 예:
class InitializerGenerator < Rails::Generators::NamedBase class_option :scope, type: :string, default: "app" end
이제 우리의 생성기를 --scope
옵션과 함께 실행할 수 있습니다:
$ bin/rails generate initializer theme --scope dashboard
옵션 값은 options
를 통해 생성기 메서드에서 접근할 수 있습니다:
def copy_initializer_file @scope = options["scope"] end
생성기 해결
생성기 이름을 해결할 때 Rails는 여러 파일 이름을 사용하여 생성기를 찾습니다. 예를 들어 bin/rails generate initializer core_extensions
를 실행하면 Rails는 다음 파일을 순서대로 로드하려고 시도합니다:
rails/generators/initializer/initializer_generator.rb
generators/initializer/initializer_generator.rb
rails/generators/initializer_generator.rb
generators/initializer_generator.rb
이 중 하나도 찾지 못하면 오류가 발생합니다.
우리는 생성기를 애플리케이션의 lib/
디렉토리에 두었는데, 이 디렉토리는 $LOAD_PATH
에 있기 때문에 Rails가 파일을 찾아 로드할 수 있습니다.
Rails 생성기 템플릿 재정의
Rails는 생성기 템플릿 파일을 해결할 때도 여러 위치를 확인합니다. 그 중 하나는 애플리케이션의 lib/templates/
디렉토리입니다. 이 동작을 통해 Rails의 기본 제공 생성기가 사용하는 템플릿을 재정의할 수 있습니다. 예를 들어 스캐폴드 컨트롤러 템플릿이나 스캐폴드 뷰 템플릿을 재정의할 수 있습니다.
이를 실제로 확인해 보겠습니다. lib/templates/erb/scaffold/index.html.erb.tt
파일을 다음 내용으로 만들어 보겠습니다:
<%% @<%= plural_table_name %>.count %> <%= human_name.pluralize %>
주목할 점은 템플릿이 다른 ERB 템플릿을 렌더링하는 ERB 템플릿이라는 것입니다. 따라서 결과 템플릿에 나타나야 할 <%
는 생성기 템플릿에서 <%%
로 이스케이프되어야 합니다.
이제 Rails의 기본 제공 스캐폴드 생성기를 실행해 보겠습니다:
$ bin/rails generate scaffold Post title:string
...
create app/views/posts/index.html.erb
...
app/views/posts/index.html.erb
의 내용은 다음과 같습니다:
<% @posts.count %> Posts
Rails 생성기 재정의
Rails의 기본 제공 생성기는 config.generators
를 통해 구성할 수 있으며, 일부 생성기를 완전히 재정의할 수도 있습니다.
먼저 스캐폴드 생성기가 어떻게 작동하는지 자세히 살펴보겠습니다.
$ bin/rails generate scaffold User name:string invoke active_record create db/migrate/20230518000000_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml invoke resource_route route resources :users invoke scaffold_controller create app계속해서 Rails 생성기 재정의에 대한 내용을 번역하겠습니다. Rails의 기본 제공 생성기는 [`config.generators`][]를 통해 구성할 수 있으며, 일부 생성기를 완전히 재정의할 수도 있습니다. 먼저 스캐폴드 생성기가 어떻게 작동하는지 자세히 살펴보겠습니다. ```bash $ bin/rails generate scaffold User name:string invoke active_record create db/migrate/20230518000000_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml invoke resource_route route resources :users invoke scaffold_controller create app/controllers/users_controller.rb invoke erb create app/views/users create app/views/users/index.html.erb create app/views/users/edit.html.erb create app/views/users/show.html.erb create app/views/users/new.html.erb create app/views/users/_form.html.erb create app/views/users/_user.html.erb invoke resource_route invoke test_unit create test/controllers/users_controller_test.rb create test/system/users_test.rb invoke helper create app/helpers/users_helper.rb invoke test_unit invoke jbuilder create app/views/users/index.json.jbuilder create app/views/users/show.json.jbuilder
출력 결과를 보면 스캐폴드 생성기가 다른 생성기들을 호출하고 있음을 알 수 있습니다. 예를 들어 scaffold_controller
생성기가 호출되고, 그 생성기 또한 다른 생성기들을 호출합니다. 특히 scaffold_controller
생성기는 helper
생성기를 호출합니다.
이제 기본 제공 helper
생성기를 새로운 my_helper
생성기로 재정의해 보겠습니다.
$ bin/rails generate generator rails/my_helper create lib/generators/rails/my_helper create lib/generators/rails/my_helper/my_helper_generator.rb create lib/generators/rails/my_helper/USAGE create lib/generators/rails/my_helper/templates invoke test_unit create test/lib/generators/rails/my_helper_generator_test.rb
그리고 lib/generators/rails/my_helper/my_helper_generator.rb
에서 생성기를 다음과 같이 정의합니다:
class Rails::MyHelperGenerator < Rails::Generators::NamedBase def create_helper_file create_file "app/helpers/#{file_name}_helper.rb", <<~RUBY module #{class_name}Helper # I'm helping! end RUBY end end
마지막으로 Rails가 기본 제공 helper
생성기 대신 my_helper
생성기를 사용하도록 config.generators
에 설정해야 합니다. config/application.rb
에 다음을 추가합니다:
config.generators do |g| g.helper :my_helper end
이제 다시 스캐폴드 생성기를 실행하면 my_helper
생성기가 사용되는 것을 확인할 수 있습니다:
$ bin/rails generate scaffold Article body:text
...
invoke scaffold_controller
...
invoke my_helper
create app/helpers/articles_helper.rb
...
참고: 기본 제공 helper
생성기의 출력에는 “invoke testunit"이 포함되지만, `myhelper의 출력에는 포함되지 않습니다.
helper생성기는 기본적으로 테스트를 생성하지 않지만, [
hookfor][]를 사용하여 테스트 생성을 위한 훅을 제공합니다.
MyHelperGenerator클래스에
hookfor :testframework, as: :helper를 포함하여 동일한 기능을 구현할 수 있습니다.
hookfor` 문서를 참고하세요.
생성기 대체 방법
특정 생성기를 재정의하는 또 다른 방법은 대체 방법(fallbacks)을 사용하는 것입니다. 대체 방법을 통해 생성기 네임스페이스를 다른 생성기 네임스페이스에 위임할 수 있습니다.
예를 들어 test_unit:model
생성기를 my_test_unit:model
생성기로 재정의하고 싶지만, test_unit:*
생성기 전체를 대체하고 싶지는 않다고 가정해 보겠습니다.
먼저 lib/generators/my_test_unit/model/model_generator.rb
에 my_test_unit:model
생성기를 만듭니다:
module MyTestUnit class ModelGenerator < Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) def do_different_stuff say "Doing different stuff..." end end end
다음으로 config.generators
를 사용하여 test_framework
생성기를 my_test_unit
으로 구성하고, my_test_unit:*
생성기가 해결되지 않으면 test_unit:*
으로 대체되도록 설정합니다:
config.generators do |g| g.test_framework :my_test_unit, fixture: false g.fallbacks[:my_test_unit] = :test_unit end
이제 스캐폴드 생성기를 실행하면 my_test_unit
이 test_unit
을 대체했지만, 모델 테스트만 영향을 받았음을 확인할 수 있습니다:
$ bin/rails generate scaffold Comment body:text invoke active_record create db/migrate/20230518000000_create_comments.rb create app/models/comment.rb invoke my_test_unit Doing different stuff... invoke resource_route route resources :comments invoke scaffold_controller create app/controllers/comments_controller.rb invoke erb create app/views/comments create app/views/comments/index.html.erb create app/views/comments/edit.html.erb create app/views/comments/show.html.erb create app/views/comments/new.html.erb create app/views/comments/_form.html.erb create app/views/comments/_comment.html.erb invoke resource_route invoke my_test_unit create test/controllers/comments_controller_test.rb create test/system/comments_test.rb invoke helper create app/helpers/comments_helper.rb invoke my_test_unit invoke jbuilder create app/views/comments/index.json.jbuilder create app/views/comments/show.json.jbuilder
애플리케이션 템플릿
애플리케이션 템플릿은 특별한 종류의 생성기입니다. 생성기 헬퍼 메서드를 모두 사용할 수 있지만, Ruby 클래스 대신 Ruby 스크립트로 작성됩니다. 다음은 예시입니다:
# template.rb if yes?("Would you like to install Devise?") gem "devise" devise_model = ask("What would you like the user model to be called?", default: "User") end after_bundle do if devise_model generate "devise:install" generate "devise", devise_model rails_command "db:migrate" end git add: ".", commit: %(-m 'Initial commit') end
먼저 템플릿은 사용자에게 Devise를 설치할지 묻습니다. 사용자가 "yes”(또는 “y”)로 답하면 Devise를 Gemfile
에 추가하고 Devise 사용자 모델의 이름을 묻습니다(기본값은 User
).
이후 bundle install
이 실행된 후에는 Devise 모델이 지정된 경우 Devise 생성기와 rails db:migrate
를 실행합니다. 마지막으로 템플릿은 전체 앱 디렉토리를 git add
하고 git commit
합니다.
우리의 템플릿은 새 Rails 애플리케이션을 생성할 때 -m
옵션을 사용하여 실행할 수 있습니다:
$ rails new my_cool_app -m path/to/template.rb
또는 기존 애플리케이션 내에서 bin/rails app:template
를 사용하여 실행할 수 있습니다:
$ bin/rails app:template LOCATION=path/to/template.rb
템플릿은 로컬에 저장할 필요가 없으며, URL을 지정할 수도 있습니다:
$ rails new my_cool_app -m http://example.com/template.rb $ bin/rails app:template LOCATION=http://example.com/template.rb
생성기 헬퍼 메서드
Thor는 [Thor::Actions
][]를 통해 많은 생성기 헬퍼 메서드를 제공합니다. 예:
이 외에도 Rails는 Rails::Generators::Actions
를 통해 많은 헬퍼 메서드를 제공합니다. 예:
생성기 테스트
Rails는 Rails::Generators::Testing::Behaviour
를 통해 테스트 헬퍼 메서드를 제공합니다. 예:
생성기에 대한 테스트를 실행할 때는 디버깅 도구가 작동하도록 RAILS_LOG_TO_STDOUT=true
를 설정해야 합니다.
RAILS_LOG_TO_STDOUT=true ./bin/test test/generators/actions_test.rb
이 외에도 Rails는 Rails::Generators::Testing::Assertions
를 통해 추가적인 어서션을 제공합니다.