1 설정
현재 Rails 플러그인은 gem으로 만들어지며, 이를 gemified plugins라고 합니다. RubyGems와 Bundler를 사용하면 원하는 경우 서로 다른 Rails 애플리케이션에서 공유할 수 있습니다.
1.1 Gemified Plugin 생성하기
Rails는 rails plugin new
명령어를 제공합니다. 이 명령어는 dummy Rails 애플리케이션을 사용하여 통합 테스트를 실행할 수 있는 모든 종류의 Rails extension을 개발하기 위한 뼈대를 생성합니다. 다음 명령어로 plugin을 생성할 수 있습니다:
$ rails plugin new yaffle
help를 입력해서 사용법과 옵션을 확인해보세요:
$ rails plugin new --help
Usage:
rails plugin new APP_PATH [options]
Options:
-r, [--ruby=PATH] # Ruby 바이너리를 위한 경로
# Default: /Users/fxn/.rvm/rubies/ruby-2.7.2/bin/ruby
[--skip-bundle], [--no-skip-bundle] # bundle install 실행을 건너뜀
-B, [--skip-bundle] # bundle install 실행을 건너뜀
[--skip-gemfile], [--no-skip-gemfile] # Gemfile 생성을 건너뜀
-G, [--skip-gemfile] # Gemfile 생성을 건너뜀
-O, [--skip-active-record] # Active Record 파일을 건너뜀
[--skip-keeps], [--no-skip-keeps] # .keep 파일 생성을 건너뜀
[--skip-action-mailer], [--no-skip-action-mailer] # Action Mailer 파일을 건너뜀
-M, [--skip-action-mailer] # Action Mailer 파일을 건너뜀
[--skip-active-storage], [--no-skip-active-storage] # Active Storage 파일을 건너뜀
[--skip-puma], [--no-skip-puma] # Puma 관련 파일을 건너뜀
[--skip-action-cable], [--no-skip-action-cable] # Action Cable 파일을 건너뜀
[--skip-sprockets], [--no-skip-sprockets] # Sprockets 파일을 건너뜀
[--skip-spring], [--no-skip-spring] # Spring 적용을 건너뜀
[--skip-listen], [--no-skip-listen] # Listen gem을 건너뜀
[--skip-javascript], [--no-skip-javascript] # JavaScript 파일을 건너뜀
-J, [--skip-javascript] # JavaScript 파일을 건너뜀
[--skip-turbolinks], [--no-skip-turbolinks] # Turbolinks를 건너뜀
[--skip-test], [--no-skip-test] # 테스트 파일을 건너뜀
-T, [--skip-test] # 테스트 파일을 건너뜀
[--skip-system-test], [--no-skip-system-test] # System test 파일을 건너뜀
[--skip-bootsnap], [--no-skip-bootsnap] # Bootsnap gem을 건너뜀
[--dev], [--no-dev] # 개발 의존성을 설정
[--edge], [--no-edge] # master branch에서 Rails를 설정
[--master], [--no-master] # master branch에서 Rails를 설정 (--edge의 별칭)
[--rc=RC] # rails rc 파일 경로
[--no-rc], [--no-no-rc] # rails rc 파일 실행을 건너뜀
[--database=DATABASE] # 사용할 데이터베이스를 설정
# Default: sqlite3
-d, [--database=DATABASE] # 지정된 데이터베이스를 선택(mysql/postgresql/sqlite3)
[--skip-namespace], [--no-skip-namespace] # 모듈 네임스페이스를 건너뜀
[--skip-collision-check], [--no-skip-collision-check] # 충돌 검사를 건너뜀
[--mountable], [--no-mountable] # 마운트 가능한 엔진 생성
[--full], [--no-full] # 전체 엔진 생성
[--dummy-path=DUMMY_PATH] # 더미 앱 경로
# Default: test/dummy
[--help], [--no-help] # 도움말 표시
[--quiet], [--no-quiet] # 진행상황 메시지 감추기
Runtime options:
-f, [--force] # 기존 파일을 덮어씀
-p, [--pretend] # 실행할 작업을 나열하지만 실제로는 아무것도 하지 않음
-q, [--quiet] # 진행상황 메시지 표시 억제
-s, [--skip] # 이미 존재하는 파일 건너뜀
Rails options:
-h, [--help] # 도움말 메시지 표시
-v, [--version] # 버전 표시
Description:
플러그인 생성기는 Rails 플러그인을 위한 골격 구조를 생성합니다.
이를 통해 자신만의 Rails 확장 기능을 패키징할 수 있습니다.
Example:
rails plugin new ~/Code/Ruby/blog
이는 ~/Code/Ruby/blog 디렉토리에 Rails 플러그인을 생성합니다.
2 새로 생성한 Plugin 테스트하기
plugin이 포함된 디렉토리로 이동하여 yaffle.gemspec
파일을 편집하고 TODO
값이 있는 모든 줄을 교체하세요:
spec.homepage = "http://example.com"
spec.summary = "Yaffle의 요약"
spec.description = "Yaffle의 설명"
...
spec.metadata["source_code_uri"] = "http://example.com"
spec.metadata["changelog_uri"] = "http://example.com"
그런 다음 bundle install
명령어를 실행하세요.
이제 bin/test
명령어를 사용하여 테스트를 실행할 수 있으며, 다음과 같이 표시됩니다:
$ bin/test
...
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
모든 것이 적절하게 생성되었다는 것을 알려주며, 이제 기능을 추가할 준비가 되었습니다.
3 Extending Core Classes
이 섹션에서는 Rails 애플리케이션 어디에서나 사용할 수 있는 메소드를 String에 추가하는 방법을 설명합니다.
이 예제에서는 to_squawk
라는 메소드를 String에 추가할 것입니다. 먼저, 몇 가지 assertion이 포함된 새로운 테스트 파일을 생성하세요:
# yaffle/test/core_ext_test.rb
require "test_helper"
class CoreExtTest < ActiveSupport::TestCase
def test_to_squawk_prepends_the_word_squawk
assert_equal "squawk! Hello World", "Hello World".to_squawk
end
end
bin/test
를 실행하여 테스트를 실행하세요. to_squawk
메서드를 아직 구현하지 않았기 때문에 이 테스트는 실패해야 합니다:
$ bin/test
E
Error:
CoreExtTest#test_to_squawk_prepends_the_word_squawk:
NoMethodError: "Hello World":String에 대해 정의되지 않은 메서드 `to_squawk'입니다
bin/test /path/to/yaffle/test/core_ext_test.rb:4
.
0.003358초 만에 완료, 595.6483 실행/초, 297.8242 검증/초.
2 실행, 1 검증, 0 실패, 1 에러, 0 스킵
좋습니다 - 이제 개발을 시작할 준비가 되었습니다.
lib/yaffle.rb
에서 require "yaffle/core_ext"
를 추가하세요:
# yaffle/lib/yaffle.rb
require "yaffle/version"
require "yaffle/railtie"
require "yaffle/core_ext"
module Yaffle
# 여기에 코드를 작성하세요...
end
마지막으로 core_ext.rb
파일을 생성하고 to_squawk
메서드를 추가합니다:
# yaffle/lib/yaffle/core_ext.rb
class String
def to_squawk
"squawk! #{self}".strip
end
end
작성한 메서드가 의도한대로 동작하는지 확인하려면 plugin 디렉토리에서 bin/test
를 실행하여 unit test를 수행하세요.
$ bin/test
...
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
test/dummy
디렉토리로 이동하여 bin/rails console
을 시작하고 squawking을 시작해보세요:
irb> "Hello World".to_squawk
=> "squawk! Hello World"
4 acts_as
메서드를 Active Record에 추가하기
plugin에서 흔히 볼 수 있는 패턴은 model에 acts_as_something
이라는 메서드를 추가하는 것입니다. 이 경우에는 Active Record model에 squawk
메서드를 추가하는 acts_as_yaffle
이라는 메서드를 작성하려고 합니다.
시작하기 위해 다음과 같이 파일을 설정하세요:
# yaffle/test/acts_as_yaffle_test.rb
require "test_helper"
class ActsAsYaffleTest < ActiveSupport::TestCase
end
# yaffle/lib/yaffle.rb
require "yaffle/version"
require "yaffle/railtie"
require "yaffle/core_ext"
require "yaffle/acts_as_yaffle"
module Yaffle
# 여기에 코드를 작성하세요...
end
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
end
end
4.1 Class Method 추가하기
이 플러그인은 모델에 last_squawk
라는 이름의 메소드를 추가했다고 가정합니다. 하지만 플러그인 사용자들이 이미 자신의 모델에 다른 용도로 쓰이는 last_squawk
라는 이름의 메소드를 정의했을 수 있습니다. 이 플러그인은 yaffle_text_field
라는 이름의 class method를 추가하여 이름을 변경할 수 있도록 합니다.
시작하기 위해, 원하는 동작을 보여주는 실패하는 테스트를 작성해봅시다:
# yaffle/test/acts_as_yaffle_test.rb
require "test_helper"
class ActsAsYaffleTest < ActiveSupport::TestCase
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
end
bin/test
를 실행하면 다음과 같이 표시됩니다:
$ bin/test
# Running:
..E
에러:
ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
NameError: 초기화되지 않은 상수 ActsAsYaffleTest::Wickwall
bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8
E
에러:
ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
NameError: 초기화되지 않은 상수 ActsAsYaffleTest::Hickwall
bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4
0.004812초 만에 완료됨, 831.2949 실행/초, 415.6475 검증/초.
4 실행, 2 검증, 0 실패, 2 에러, 0 건너뜀
이는 우리가 테스트하려는 필요한 모델(Hickwall과 Wickwall)이 없다는 것을 알려줍니다.
test/dummy
디렉토리에서 다음 명령어를 실행하여 우리의 "dummy" Rails 애플리케이션에서 이러한 모델들을 쉽게 생성할 수 있습니다:
$ cd test/dummy
$ bin/rails generate model Hickwall last_squawk:string
$ bin/rails generate model Wickwall last_squawk:string last_tweet:string
이제 dummy app으로 이동하여 testing database에 필요한 database table들을 migration하여 생성할 수 있습니다. 먼저 다음을 실행하세요:
$ cd test/dummy
$ bin/rails db:migrate
여기에서 Hickwall과 Wickwall 모델이 yaffles처럼 동작해야 한다는 것을 알 수 있도록 수정하세요.
# test/dummy/app/models/hickwall.rb
class Hickwall < ApplicationRecord
acts_as_yaffle
end
# test/dummy/app/models/wickwall.rb
class Wickwall < ApplicationRecord
acts_as_yaffle yaffle_text_field: :last_tweet
end
acts_as_yaffle
메서드를 정의하는 코드도 추가할 것입니다.
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
class_methods do
def acts_as_yaffle(options = {})
end
end
end
end
# test/dummy/app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
include Yaffle::ActsAsYaffle
self.abstract_class = true
end
그런 다음 plugin의 루트 디렉토리로 돌아가서(cd ../..
) bin/test
를 사용하여 test를 다시 실행할 수 있습니다.
$ bin/test
# 실행 중:
.E
오류:
ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
NoMethodError: #<Class:0x0055974ebbe9d8>에 대해 정의되지 않은 메서드 'yaffle_text_field'
bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4
E
오류:
ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
NoMethodError: #<Class:0x0055974eb8cfc8>에 대해 정의되지 않은 메서드 'yaffle_text_field'
bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8
.
0.008263초 만에 완료, 484.0999 실행/초, 242.0500 검증/초.
4회 실행, 2개 검증, 0개 실패, 2개 오류, 0개 건너뜀
더 가까워졌습니다... 이제 테스트를 통과하기 위해 acts_as_yaffle
메서드의 코드를 구현할 것입니다.
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
class_methods do
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s
end
end
end
end
# test/dummy/app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
include Yaffle::ActsAsYaffle
self.abstract_class = true
end
bin/test
를 실행하면 모든 테스트가 통과하는 것을 볼 수 있습니다:
$ bin/test
...
4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
4.2 인스턴스 메서드 추가하기
이 플러그인은 acts_as_yaffle
을 호출하는 모든 Active Record 객체에 'squawk'라는 메서드를 추가합니다. 'squawk' 메서드는 데이터베이스의 필드 중 하나의 값을 단순히 설정합니다.
시작하기 위해, 원하는 동작을 보여주는 실패하는 테스트를 작성하세요:
# yaffle/test/acts_as_yaffle_test.rb
require "test_helper"
class ActsAsYaffleTest < ActiveSupport::TestCase
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
def test_hickwalls_squawk_should_populate_last_squawk
hickwall = Hickwall.new
hickwall.squawk("Hello World")
assert_equal "squawk! Hello World", hickwall.last_squawk
end
def test_wickwalls_squawk_should_populate_last_tweet
wickwall = Wickwall.new
wickwall.squawk("Hello World")
assert_equal "squawk! Hello World", wickwall.last_tweet
end
end
마지막 두 개의 테스트가 "NoMethodError: undefined method `squawk'"라는 에러와 함께 실패하는지 확인하기 위해 테스트를 실행하고, acts_as_yaffle.rb
를 다음과 같이 업데이트하세요:
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
included do
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
class_methods do
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s
end
end
end
end
# test/dummy/app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
include Yaffle::ActsAsYaffle
self.abstract_class = true
end
bin/test
를 마지막으로 한 번 더 실행하면 다음과 같이 표시됩니다:
$ bin/test
...
6회 실행, 6개 assertion, 0건 실패, 0건 오류, 0건 건너뜀
모델의 필드에 쓰기 위해 write_attribute
를 사용하는 것은 plugin이 모델과 상호 작용하는 방법의 한 예시일 뿐이며, 항상 사용할 수 있는 올바른 메서드는 아닙니다. 예를 들어, 다음과 같이 사용할 수도 있습니다:
send("#{self.class.yaffle_text_field}=", string.to_squawk)
5 Generators
Generator들은 플러그인의 lib/generators
디렉토리에 생성하기만 하면 gem에 포함시킬 수 있습니다. Generator 생성에 대한 자세한 정보는 Generators 가이드에서 확인할 수 있습니다.
6 Publishing Your Gem
개발 중인 gem 플러그인은 Git 저장소에서 쉽게 공유할 수 있습니다. Yaffle gem을 다른 사람들과 공유하려면 코드를 Git 저장소(GitHub 등)에 커밋하고 해당 애플리케이션의 Gemfile
에 한 줄을 추가하기만 하면 됩니다:
gem "yaffle", git: "https://github.com/rails/yaffle.git"
bundle install
을 실행한 후에는 gem 기능을 애플리케이션에서 사용할 수 있게 됩니다.
gem이 정식 릴리스로 공유될 준비가 되면 RubyGems에 퍼블리시할 수 있습니다.
또는 Bundler의 Rake task들을 활용할 수도 있습니다. 다음과 같이 전체 목록을 확인할 수 있습니다:
$ bundle exec rake -T
$ bundle exec rake build
# pkg 디렉토리에 yaffle-0.1.0.gem 파일을 빌드합니다
$ bundle exec rake install
# yaffle-0.1.0.gem을 빌드하고 시스템 gems에 설치합니다
$ bundle exec rake release
# v0.1.0 태그를 생성하고 yaffle-0.1.0.gem을 빌드하여 Rubygems에 푸시합니다
RubyGems에 gem을 배포하는 것에 대한 자세한 내용은 다음을 참조하세요: gem 배포하기.
7 RDoc 문서화
플러그인이 안정화되고 배포할 준비가 되면, 다른 사람들을 위해 문서화를 해주세요! 다행히도 플러그인 문서화는 쉽습니다.
첫 번째 단계는 플러그인 사용 방법에 대한 자세한 정보로 README 파일을 업데이트하는 것입니다. 포함해야 할 몇 가지 핵심 사항은 다음과 같습니다:
- 당신의 이름
- 설치 방법
- 앱에 기능을 추가하는 방법(일반적인 사용 사례의 여러 예시)
- 사용자들에게 도움이 되고 시간을 절약할 수 있는 경고, 주의사항 또는 팁
README가 완성되면, 개발자들이 사용할 모든 메서드에 RDoc 주석을 추가하세요. 또한 공개 API에 포함되지 않는 코드 부분에는 # :nodoc:
주석을 추가하는 것이 관례입니다.
주석 작성이 완료되면 플러그인 디렉토리로 이동하여 다음을 실행하세요:
$ bundle exec rake rdoc