Rails 플러그인 만들기 기본

Rails 플러그인은 코어 프레임워크의 확장 또는 수정입니다. 플러그인은 다음과 같은 기능을 제공합니다:

  • 개발자가 안정적인 코드 베이스를 손상시키지 않고 최신 아이디어를 공유할 수 있는 방법.
  • 코드 단위를 독립적으로 수정하거나 업데이트할 수 있는 분할된 아키텍처.
  • 코어 개발자가 모든 새로운 기능을 포함할 필요가 없도록 하는 출구.

이 가이드를 읽고 나면 다음을 알 수 있습니다:

  • 처음부터 플러그인을 만드는 방법.
  • 플러그인에 대한 테스트를 작성하고 실행하는 방법.

이 가이드는 테스트 주도 개발 방식으로 플러그인을 만드는 방법을 설명합니다. 이 플러그인은 다음과 같은 기능을 제공합니다:

  • Hash와 String과 같은 핵심 Ruby 클래스를 확장합니다.
  • ApplicationRecordacts_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.rbrequire "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

참고 자료