Rails 플러그인 만들기 기본
Rails 플러그인은 코어 프레임워크의 확장 또는 수정입니다. 플러그인은 다음과 같은 기능을 제공합니다:
- 개발자가 안정적인 코드 베이스를 손상시키지 않고 최신 아이디어를 공유할 수 있는 방법.
- 코드 단위를 독립적으로 수정하거나 업데이트할 수 있는 분할된 아키텍처.
- 코어 개발자가 모든 새로운 기능을 포함할 필요가 없도록 하는 출구.
이 가이드를 읽고 나면 다음을 알 수 있습니다:
- 처음부터 플러그인을 만드는 방법.
- 플러그인에 대한 테스트를 작성하고 실행하는 방법.
이 가이드는 테스트 주도 개발 방식으로 플러그인을 만드는 방법을 설명합니다. 이 플러그인은 다음과 같은 기능을 제공합니다:
- Hash와 String과 같은 핵심 Ruby 클래스를 확장합니다.
ApplicationRecord
에acts_as
플러그인 스타일의 메서드를 추가합니다.- 플러그인의 생성기를 어디에 두어야 하는지에 대한 정보를 제공합니다.
이 가이드의 목적을 위해 당신은 열렬한 새 관찰자라고 가정해 보겠습니다. 당신이 가장 좋아하는 새는 Yaffle이며, 다른 개발자들도 Yaffle의 장점을 공유할 수 있는 플러그인을 만들고 싶습니다.
설정
현재 Rails 플러그인은 gem으로 구축됩니다, gemified 플러그인. 필요한 경우 RubyGems와 Bundler를 사용하여 다른 Rails 애플리케이션 간에 공유할 수 있습니다.
Gemified 플러그인 생성
Rails에는 rails plugin new
명령어가 포함되어 있어, 더미 Rails 애플리케이션을 사용하여 통합 테스트를 실행할 수 있는 기능을 갖춘 모든 종류의 Rails 확장 개발을 위한 스켈레톤을 만들 수 있습니다. 다음 명령어로 플러그인을 생성하세요:
$ rails plugin new yaffle
도움말을 요청하여 사용법과 옵션을 확인하세요:
$ rails plugin new --help
새로 생성된 플러그인 테스트하기
플러그인이 포함된 디렉토리로 이동하여 yaffle.gemspec
파일을 편집하여 TODO
값이 있는 행을 바꾸세요:
spec.homepage = "http://example.com" spec.summary = "Summary of Yaffle." spec.description = "Description of 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
이는 모든 것이 올바르게 생성되었음을 알려주며, 이제 기능을 추가할 준비가 되었습니다.
코어 클래스 확장하기
이 섹션에서는 Rails 애플리케이션 어디에서나 사용할 수 있는 String에 to_squawk
메서드를 추가하는 방법을 설명합니다.
이 예에서는 String에 to_squawk
라는 메서드를 추가할 것입니다. 시작하려면 몇 가지 단언이 포함된 새 테스트 파일을 만드세요:
# 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: undefined method `to_squawk' for "Hello World":String bin/test /path/to/yaffle/test/core_ext_test.rb:4 . Finished in 0.003358s, 595.6483 runs/s, 297.8242 assertions/s. 2 runs, 1 assertions, 0 failures, 1 errors, 0 skips
좋습니다 - 이제 개발을 시작할 준비가 되었습니다.
lib/yaffle.rb
에 require "yaffle/core_ext"
를 추가하세요:
# yaffle/lib/yaffle.rb require "yaffle/version" require "yaffle/railtie" require "yaffle/core_ext" module Yaffle # Your code goes here... end
마지막으로 core_ext.rb
파일을 만들고 to_squawk
메서드를 추가하세요:
# yaffle/lib/yaffle/core_ext.rb class String def to_squawk "squawk! #{self}".strip end end
메서드가 예상대로 작동하는지 확인하려면 플러그인 디렉토리에서 bin/test
를 실행하세요.
$ bin/test
...
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
이를 실제로 확인하려면 test/dummy
디렉토리로 이동하여 bin/rails console
을 시작하고 새를 울리세요:
irb> "Hello World".to_squawk => "squawk! Hello World"
Active Record에 “acts_as” 메서드 추가하기
플러그인에서 일반적인 패턴은 모델에 acts_as_something
메서드를 추가하는 것입니다. 이 경우 acts_as_yaffle
이라는 메서드를 작성하여 Active Record 모델에 squawk
메서드를 추가하려고 합니다.
시작하려면 다음과 같이 파일을 설정하세요:
# 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 # Your code goes here... end
# yaffle/lib/yaffle/acts_as_yaffle.rb module Yaffle module ActsAsYaffle end end
클래스 메서드 추가하기
이 플러그인은 모델에 last_squawk
라는 메서드를 추가했다고 가정합니다. 그러나 플러그인 사용자는 이미 자신의 모델에 last_squawk
라는 메서드를 정의했을 수 있으며, 이 메서드를 다른 용도로 사용하고 있을 수 있습니다. 이 플러그인은 yaffle_text_field
라는 클래스 메서드를 추가하여 이름을 변경할 수 있도록 할 것입니다.
시작하려면 원하는 동작을 보여주는 실패하는 테스트를 작성하세요:
# 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 Error: ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: NameError: uninitialized constant ActsAsYaffleTest::Wickwall bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8 E Error: ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: NameError: uninitialized constant ActsAsYaffleTest::Hickwall bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4 Finished in 0.004812s, 831.2949 runs/s, 415.6475 assertions/s. 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
이는 우리가 테스트하려는 필요한 모델(Hickwall과 Wickwall)이 없다는 것을 알려줍니다.
test/dummy
디렉토리에서 다음 명령어를 실행하여 이러한 모델을 쉽게 생성할 수 있습니다:
$ cd test/dummy $ bin/rails generate model Hickwall last_squawk:string $ bin/rails generate model Wickwall last_squawk:string last_tweet:string
이제 테스트 데이터베이스에 필요한 데이터베이스 테이블을 생성할 수 있습니다. 더미 앱으로 이동하여 데이터베이스를 마이그레이션하세요. 먼저 다음을 실행하세요:
$ cd test/dummy $ bin/rails db:migrate
여기에 있는 동안 Hickwall과 Wickwall 모델이 yaffle처럼 동작하도록 변경하세요.
# 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
그런 다음 플러그인의 루트 디렉토리(cd ../..
)로 돌아가서 bin/test
를 사용하여 테스트를 다시 실행할 수 있습니다.
$ bin/test # Running: .E Error: ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk: NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974ebbe9d8> bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4 E Error: ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet: NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974eb8cfc8> bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8 . Finished in 0.008263s, 484.0999 runs/s, 242.0500 assertions/s. 4 runs, 2 assertions, 0 failures, 2 errors, 0 skips
점점 가까워지고 있습니다… 이제 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
를 실행하면 모든 테스트가 통과되는 것알겠습니다. 계속해서 번역하겠습니다.
인스턴스 메서드 추가하기
이 플러그인은 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'"와 함께 실패할 것입니다. 그런 다음
actsasyaffle.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 runs, 6 assertions, 0 failures, 0 errors, 0 skips
참고: 모델에 필드를 쓰기 위해 write_attribute
를 사용하는 것은 플러그인이 모델과 상호 작용하는 방법 중 하나의 예일 뿐이며, 항상 사용해야 하는 메서드는 아닙니다. 예를 들어 다음과 같이 사용할 수도 있습니다:
send("#{self.class.yaffle_text_field}=", string.to_squawk)
생성기
생성기는 플러그인의 lib/generators
디렉토리에 생성하여 플러그인에 포함시킬 수 있습니다. 생성기 생성에 대한 자세한 내용은 생성기 가이드에서 확인할 수 있습니다.
Gem 배포하기
현재 개발 중인 Gem 플러그인은 Git 저장소에서 쉽게 공유할 수 있습니다. Yaffle Gem을 다른 사람들과 공유하려면 Git 저장소(예: GitHub)에 코드를 커밋하고 대상 애플리케이션의 Gemfile
에 다음 행을 추가하세요:
gem "yaffle", git: "https://github.com/rails/yaffle.git"
bundle install
을 실행하면 애플리케이션에서 Gem 기능을 사용할 수 있습니다.
Gem이 공식 릴리스 준비가 되면 RubyGems에 게시할 수 있습니다.
또한 Bundler의 Rake 작업을 활용할 수 있습니다. 다음과 같이 전체 목록을 확인할 수 있습니다:
$ bundle exec rake -T $ bundle exec rake build # Build yaffle-0.1.0.gem into the pkg directory $ bundle exec rake install # Build and install yaffle-0.1.0.gem into system gems $ bundle exec rake release # Create tag v0.1.0 and build and push yaffle-0.1.0.gem to Rubygems
RubyGems에 Gem을 게시하는 방법에 대한 자세한 내용은 Gem 게시하기를 참조하세요.
RDoc 문서화
플러그인이 안정화되고 배포할 준비가 되면 다른 사람들을 위해 문서화하는 것이 좋습니다! 다행히도 플러그인에 대한 문서 작성은 쉽습니다.
첫 번째 단계는 README 파일을 업데이트하여 플러그인 사용 방법에 대한 자세한 정보를 제공하는 것입니다. 포함해야 할 주요 사항은 다음과 같습니다:
- 이름
- 설치 방법
- 애플리케이션에 기능을 추가하는 방법(일반적인 사용 사례의 여러 예)
- 사용자를 도울 수 있는 경고, 함정 또는 팁
README가 완성되면 개발자가 사용할 모든 메서드에 RDoc 주석을 추가하세요. 또한 공개 API에 포함되지 않는 코드 부분에는 일반적으로 # :nodoc:
주석을 추가합니다.
주석이 준비되면 플러그인 디렉토리로 이동하여 다음을 실행하세요:
$ bundle exec rake rdoc