#218 Making Generators in Rails 3
- Download:
- source codeProject Files in Zip (95.2 KB)
- mp4Full Size H.264 Video (15 MB)
- m4vSmaller H.264 Video (10.4 MB)
- webmFull Size VP8 Video (28 MB)
- ogvFull Size Theora Video (20.9 MB)
216번 연제 [watch, read]에서 소개한 바와 같이, 레일스3의 제너레이터는 훨씬 더 모듈화 하기가 쉽어서 레일스2에서는 불가능했던 방법으로 원하는데로 제너레이터의 기능을 수정할 수 있습니다. 그러나, 내장된 제너레이터의 기능이 마음에 들지 않을 경우 처음부터 완전히 새로운 제너레이터를 만들고 싶을 때가 있습니다. 커스텀 제너레이터를 만드는 것은 58번 연제에서 논의된 바 있지만, 그 이후에 방법이 변경되어 58번 연제의 내용을 수정해서 레일스3에서 작성하는 방법을 소개하도록 하겠습니다.
시작하기
제너레이터를 만드는 가장 쉬운 방법은 레일스3 어플리케이션을 새로 만든 후에 시작하는 것입니다. 그래서 레일스3 어플리케이션을 하나 만들고 시작해 보도록 하겠습니다. 레일스 3.0 베터 4가 방금 릴리스되었는데 어플레케이션을 새로 생성하는 문법이 변경되었습니다. 그래서 todo라는 어플리케이션을 새로 만들기 위해서 아래와 같이 실행하지 않고
$ rails todo
다음과 같이 new 옵션을 추가해서 실행합니다.
$ rails new todo
새 어플리케이션을 생성한 후, 레일스의 내장 제너레이터 중의 하나를 이용해서 제너레이터를 생성합니다. rails g generator
명령은 새로운 제너레이터를 만들 때 필요한 기본 파일들을 만들어 줍니다. 아래와 같이 실행하여 이 명령에서 사용할 수 있는 파라메터들을 검토해 볼 수 있습니다.
$ rails g generator --help
이 제너레이터는 사용하기 매우 쉬워서 만들고자 하는 제너레이터의 이름을 넘겨 주기만 하면 됩니다. 여기서는 어플리케이션 레이아웃을 생성해 주는 제너레이터를 만들 것이므로 layout
이라고 하겠습니다.
$ rails g generator layout create lib/generators/layout create lib/generators/layout/layout_generator.rb create lib/generators/layout/USAGE create lib/generators/layout/templates
제너레이터는 어플리케이션의 lib
디렉토리 아래에 파일들을 생성하게 되는데, 만들게 되는 layout 제너레이터는 이 어플레키이션에서만 사용할 수 있다는 것을 의미합니다. 따라서 모든 어플리케이션에서 사용할 수 있게 하기 위해서는, 몇가지 작업을 해주어야 하는데, 본 연제의 후반부에서 이에 대해 다루도록 하겠습니다.
생성된 제너레이터 클래스 파일은 내용이 많지 않고 단 하나의 클래스만 정의되어 있습니다. 이 클래스에 있는 하나의 코드라인은 향후 추가되는 파일을 templates라는 디렉토리에서 찾아 보도록 알려 줍니다.
class LayoutGenerator < Rails::Generators::NamedBase source_root File.expand_path('../templates', __FILE__) end
USAGE
라는 파일을 생성해 주는데, 현재의 제너레이터에 대한 문서화를 위해서 사용하게 됩니다. 이것에 대한 안내 문서는 제너레이터에 대해서 --help
옵션을 사용할 경우 볼 수 있습니다.
제너레이터가 생성해 주는 제너레이터 클래스는 Rails::Generators::NamedBase
로부터 상속됩니다. 이것은 generator
제너레이터를 실행할 때 특정 이름을 명시해 주어야 하는 것과 같이 해당 제너레이터를 실행할 때도 이름이 필요하다는 것을 의미합니다. 새로운 제너레이터를 --help
옵션을 지정하여 실행하면 알 수 있습니다.
$ rails g layout --help Usage: rails generate layout NAME [options] Runtime options: -f, [--force] # Overwrite files that already exist -p, [--pretend] # Run but do not make any changes -s, [--skip] # Skip files that already exist -q, [--quiet] # Supress status output Description: Explain the generator Example: rails generate layout Thing This will create: what/will/it/create
위에서 보는 바와 같이, USAGE
파일의 내용은 실행결과물의 마지막에 보여지게 된다는 것을 주목하기 바랍니다.
여기서는 NAME
옵션을 지정하지 않을 경우 디폴트로 application
을 사용하도록 할 것입니다. NamedBase
대신에 Rails::Generators::Base
로부터 상속받도록 해주면 됩니다. 이렇게 하면 모든 파라메터를 옵션으로 만들어 주어 제너레이터를 우리의 요구에 맞도록 변경할 수 있는 유연성을 제공해 주게 됩니다.
argument
메소드를 사용해서 해당 제너레이터가 취할 수 있는 인수를 정의할 수 있습니다. 디폴트 NAME
옵션 대신에 별도의 인수를 정의하는 것은 각각의 인수에 대한 디폴트 값을 정의할 수 있는 말입니다. 여기서는 application
이라는 디폴트 값을 가지는 layout_name
인수를 추가해 보겠습니다.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" end
이제 --help
옵션으로 layout 제너레이터 명령을 실행하면, 새로 추가한 인수가 보이게 될 것입니다. 브래킷기호로 둘러 싸여 보인다는 것은 해당 인수가 옵션이라는 것을 의미합니다.
$ rails g layout --help Usage: rails generate layout [LAYOUT_NAME] [options]
레일스3의 제너레이터들은 rake와 비슷한 Thor library를 이용하여 만들어졌습니다. 제너레이터 클래스에서 호출하게 될 많은 메소드들은 Thor에 정의되어 있어서 이러한 메소드들에 대해서 자세한 내용을 알고자 할 때 Thor 라이브러리의 소스와 문서를 참조하면 됩니다.
제너레이터가 사용하게 될 파일들을 생성하기
여기서 만들게 되는 제너레이터는 두개의 파일을 생성하도록 할 것입니다. 하나의 application 레아이웃 파일과 하나의 stylesheet 파일. 이를 위해서 제너레이터의 templates
디렉토리에 이 두개의 파일을 생성해서, 제너레이터가 실행될 때 어플리케이션의 정확한 디렉토리에 이 파일들을 복사하도록 할 것입니다.
그렇다면, 제너레이터의 기능은 어떻게 정의할까요? 음, 좀 특이한 방법으로 정의하게 됩니다. 제너레이터가 실행될 때, 제너레이터 클래스에서 정의된 모든 public 메소드가 실행될 것입니다. 이것이 의미하는 바는, generate_layout
이라는 메소드를 정의할 수 있고 이 메소드는 제너레이터를 실행할 때 자동으로 실행될 것이라는 것입니다. 처음에는 이러한 것이 일반적이지 않은 개념으로 생각될 수 있지만, 제너레이터에서 코드를 구조화하는 좋은 방법이 됩니다.
제너레이터가 수행할 첫번째 일은 templates
디렉토리로부터 stylesheet 하나를 어플리케이션의 /public/stylesheets
디렉토리로 복사하는 일이며 copy_file
메소드를 이용하면 이 작업을 할 수 있습니다.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" def generate_layout copy_file "stylesheet.css", "public/stylesheets/#{layout_name.underscore}.css" end end
copy_file
메소드는 두개의 인수를 가집니다. 첫번째 인수는 templates
디렉토리에 있는 복사될 파일의 이름이고, 두번째 인수는 복사할 위치 경로입니다. 주목할 것은, 복사할 위치의 파일 이름은 제너레이터로 넘겨지는 layout_name
인수에 근거한다는 것입니다. argument
메소드는 우리가 앞으로 사용하게 될 layout_name
메소드를 생성하지만, 넘어오는 layout 이름은 카멜형일 수 있기 때문에, 파일 이름에 적합한 포맷임을 확인하기 위해서 layout 이름에 underscore
를 호출할 것입니다.
마지막으로, templates
디렉토리에 stylesheet.css
파일을 생성해서 어플리케이션에서 사용하기를 원는 디폴트 CSS 파일을 복사해 넣습니다.
이제 아무런 인수 없이 제너레이터를 실행하면, application.css
파일이 생성되어야 합니다.
$ rails g layout create public/stylesheets/application.css
다음으로, 레이아웃 파일에 대해서 동일한 작업을 해 줄 필요가 있습니다. underscore 형 layout 이름을 여러 차례 사용할 것이기 때문에 해당 코드를 별도로 빼내서 메소드로 만들어 file_name
과 같이 호출하는 것이 최선의 방법일 것입니다. 또한, 이전에 언급한 바와 같이, 제너레이터에 있는 모든 public 메소드는 자동으로 실행되기 때문에 file_name
메소드를 private으로 선언해 줄 필요가 있습니다.
다음으로 해야할 작업은, 레이아웃 파일 자체를 생성하는 것입니다. 이번에도 copy_file
을 사용하여 템블릿 파일을 직접 복사할 수 있지만, 이 템플릿 파일내에서 erb 코드를 실행토록 하여, 제너레이터가 실행될 때 파일명이 변경될 수 있도록 할 것입니다.
이를 위해서, template
메소드를 사용할 수 있는데, 이 때 copy_file
에서 사용하는 것과 비슷한 인수들을 사용하게 되지만, 템플릿에 있는 erb 코드를 먼저 파싱한 후 대상 디렉토리에 해당 파일이 복사될 것입니다.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" def generate_layout copy_file "stylesheet.css", "public/stylesheets/#{file_name}.css" template "layout.html.erb", "app/views/layouts/#{file_name}.html.erb" end private def file_name layout_name.underscore end end
다음에는, templates
디렉토리에 템플릿 파일 자체를 생성할 것입니다. 이 템플릿 파일에 다음과 같이 코드를 작성할 것입니다.
<!DOCTYPE html> <html> <head> <title>Untitled</title> <%%= stylesheet_link_tag "<%= file_name %>" %> <%%= javascript_include_tag :defaults %> <%%= csrf_meta_tag %> <%%= yield(:head) %> </head> <body> <div id="container"> <%% flash.each do |name, msg| %> <%%= content_tag :div, msg, :id => "flash_#{name}" %> <%% end %> <%%= yield %> </div> </body> </html>
눈여겨 봐야 할 첫번째 것은, template
메소드를 사용할 것이기 때문에, 이 코드 중의 모든 erb 태그들이 제너레이터가 실행될 때 실행이 되어 버릴 것이라는 것입니다. 그러나, 생성된 파일에서 이 모든 erb 태그들을 포함하고자 한다면, 각 erb 태그 앞에 있는 퍼센트 표시를 이스케이프해 주어야 하기 때문에 위의 코드 상에 있는 모든 erb 코드에 대해서 이와 같은 작업을 해 놓았습니다.
템프릿에 동적인 컨텐츠를 포함하기 위해서 통상적인 erb 코드를 포함할 수 있습니다. private 메소드를 포함해서, 제너레이터내의 모든 메소드를 사용할 수 있기 때문에, file_name
를 호출해서 stylesheet 파일에 대한 정확한 이름을 stylesheet_link_tag
호출로 넘겨 줄 수 있게 됩니다.
모든 것이 제대로 동작하는지를 알아보기 위해서, 제너레이터를 다시 실행하는데, 이번에는 layout 이름을 넘겨 줄 것입니다.
$ rails g layout admin create public/stylesheets/admin.css create app/views/layouts/admin.html.erb
이번에는 제너레이터가 두개의 파일을 만들게 됩니다. 넘겨 준 layout 이름에 근거해서 각각의 이름이 생성되기 때문에, 모든 것이 제대로 동작하는 것 같습니다. 생성된 레이아웃 파일을 살펴보면, 레이아웃 파일이 생성될 때 file_name
메소드가 호출되어 stylesheet_link_tag
메소드가 정확한 인수를 가지게 된 것을 알게 수 있습니다. erb 태그들은 이스케이프되어 레이아웃에서 보이게 됩니다.
<head> <title>Untitled</title> <%= stylesheet_link_tag "admin" %> <%= javascript_include_tag :defaults %> <%= csrf_meta_tag %> <%= yield(:head) %> </head>
옵션들을 선택적으로 만들기
제너레이터에 추가하고 싶은 기능이 하나 더 있습니다. stylesheet 파일이 생성되지 못하게 하는 옵션을 넘겨 줄 수 있게 해주는 것입니다. class_option
메소드를 사용하면 이와 같이 할 수 있습니다.
class LayoutGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :layout_name, :type => :string, :default => "application" class_option :stylesheet, :type => :boolean, :default => true, :description => "Include stylesheet file" def generate_layout copy_file "stylesheet.css", "public/stylesheets/#{file_name}.css" if options.stylesheet? template "layout.html.erb", "app/views/layouts/#{file_name}.html.erb" end private def file_name layout_name.underscore end end
이미 stylesheet
옵션을 지정하였으며, type을 boolean
으로 설정하여 디폴트 값을 true
로 할당한 후 description 옵션도 지정했습니다. 그리고, copy_file
코드라인에 if
문을 추가하여 stylesheet 파일이 stylesheet
옵션이 true
인 경우에만 복사되도록 했습니다.
레이아웃 템플릿 파일에서도 비스하게, stylesheet
옵션이 true
값인 경우에만 stylesheet_link_tag
코드라인을 포함하도록 합니다. 이를 위해 이 코드라인을 if
문으로 감싸 줍니다.
<!DOCTYPE html> <html> <head> <title>Untitled</title> <%- if options.stylesheet? -%> <%%= stylesheet_link_tag "<%= file_name %>" %> <%- end -%> <%%= javascript_include_tag :defaults %> <%%= csrf_meta_tag %> <%%= yield(:head) %> </head> <body> <div id="container"> <%% flash.each do |name, msg| %> <%%= content_tag :div, msg, :id => "flash_#{name}" %> <%% end %> <%%= yield %> </div> </body> </html>
다시 제너레이터에 대한 도움말 안내문을 보기 위해서 help 옵션을 사용하면, stylesheet 옵션에 대한 내용을 볼 수 있게 될 것입니다.
$ rails g layout --help Usage: rails generate layout [LAYOUT_NAME] [options] Options: [--stylesheet] # Indicates when to generate stylesheet # Default: true
이제 --skip-stylesheet
(또는 --no-stylesheet
) 옵션을 사용해서 stylesheet 파일없이 레이아웃을 생성할 수 있게 됩니다.
$ rails g layout foo --skip-stylesheet create app/views/layouts/foo.html.erb
레일스3에서 제너레이터를 만드는 것에 대한 본 연제는 여기까지 입니다. 이와 같이 제너레이터를 만드는 것을 직접해 볼 것을 권합니다. 레일스 어플리케이션마다 동일하거나 비슷한 코드를 특정 위치에 생성하고자 할 경우에는 제너레이터를 고려해 볼 필요가 있습니다. 이 제너레이터를 다른 어플리케이션과 공유하고자 할 때는, 레일스 플러그인이나 젬을 만들어 해당 제너레이터를 lib/generators
디렉토리에 두기만 하면 됩니다. 따라서 해당 젬이나 플러그인을 어플리케이션에 포함시키기만하면 누구라도 이 제너레이터를 사용할 수 있게 될 것입니다.