aboutsummaryrefslogtreecommitdiffstats
path: root/guides/source/plugins.md
diff options
context:
space:
mode:
Diffstat (limited to 'guides/source/plugins.md')
-rw-r--r--guides/source/plugins.md484
1 files changed, 484 insertions, 0 deletions
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
new file mode 100644
index 0000000000..7c9784dfe3
--- /dev/null
+++ b/guides/source/plugins.md
@@ -0,0 +1,484 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
+
+The Basics of Creating Rails Plugins
+====================================
+
+A Rails plugin is either an extension or a modification of the core framework. Plugins provide:
+
+* A way for developers to share bleeding-edge ideas without hurting the stable code base.
+* A segmented architecture so that units of code can be fixed or updated on their own release schedule.
+* An outlet for the core developers so that they don't have to include every cool new feature under the sun.
+
+After reading this guide, you will know:
+
+* How to create a plugin from scratch.
+* How to write and run tests for the plugin.
+
+This guide describes how to build a test-driven plugin that will:
+
+* Extend core Ruby classes like Hash and String.
+* Add methods to `ApplicationRecord` in the tradition of the `acts_as` plugins.
+* Give you information about where to put generators in your plugin.
+
+For the purpose of this guide pretend for a moment that you are an avid bird watcher.
+Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle
+goodness.
+
+--------------------------------------------------------------------------------
+
+Setup
+-----
+
+Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared across
+different Rails applications using RubyGems and Bundler if desired.
+
+### Generate a gemified plugin.
+
+
+Rails ships with a `rails plugin new` command which creates a
+skeleton for developing any kind of Rails extension with the ability
+to run integration tests using a dummy Rails application. Create your
+plugin with the command:
+
+```bash
+$ rails plugin new yaffle
+```
+
+See usage and options by asking for help:
+
+```bash
+$ rails plugin new --help
+```
+
+Testing Your Newly Generated Plugin
+-----------------------------------
+
+You can navigate to the directory that contains the plugin, run the `bundle install` command
+ and run the one generated test using the `bin/test` command.
+
+You should see:
+
+```bash
+ 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
+```
+
+This will tell you that everything got generated properly and you are ready to start adding functionality.
+
+Extending Core Classes
+----------------------
+
+This section will explain how to add a method to String that will be available anywhere in your Rails application.
+
+In this example you will add a method to String named `to_squawk`. To begin, create a new test file with a few assertions:
+
+```ruby
+# 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
+```
+
+Run `bin/test` to run the test. This test should fail because we haven't implemented the `to_squawk` method:
+
+```bash
+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
+```
+
+Great - now you are ready to start development.
+
+In `lib/yaffle.rb`, add `require "yaffle/core_ext"`:
+
+```ruby
+# yaffle/lib/yaffle.rb
+
+require "yaffle/railtie"
+require "yaffle/core_ext"
+
+module Yaffle
+ # Your code goes here...
+end
+```
+
+Finally, create the `core_ext.rb` file and add the `to_squawk` method:
+
+```ruby
+# yaffle/lib/yaffle/core_ext.rb
+
+class String
+ def to_squawk
+ "squawk! #{self}".strip
+ end
+end
+```
+
+To test that your method does what it says it does, run the unit tests with `bin/test` from your plugin directory.
+
+```bash
+ 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
+```
+
+To see this in action, change to the `test/dummy` directory, fire up a console, and start squawking:
+
+```bash
+$ rails console
+>> "Hello World".to_squawk
+=> "squawk! Hello World"
+```
+
+Add an "acts_as" Method to Active Record
+----------------------------------------
+
+A common pattern in plugins is to add a method called `acts_as_something` to models. In this case, you
+want to write a method called `acts_as_yaffle` that adds a `squawk` method to your Active Record models.
+
+To begin, set up your files so that you have:
+
+```ruby
+# yaffle/test/acts_as_yaffle_test.rb
+
+require "test_helper"
+
+class ActsAsYaffleTest < ActiveSupport::TestCase
+end
+```
+
+```ruby
+# yaffle/lib/yaffle.rb
+
+require "yaffle/railtie"
+require "yaffle/core_ext"
+require "yaffle/acts_as_yaffle"
+
+module Yaffle
+ # Your code goes here...
+end
+```
+
+```ruby
+# yaffle/lib/yaffle/acts_as_yaffle.rb
+
+module Yaffle
+ module ActsAsYaffle
+ end
+end
+```
+
+### Add a Class Method
+
+This plugin will expect that you've added a method to your model named `last_squawk`. However, the
+plugin users might have already defined a method on their model named `last_squawk` that they use
+for something else. This plugin will allow the name to be changed by adding a class method called `yaffle_text_field`.
+
+To start out, write a failing test that shows the behavior you'd like:
+
+```ruby
+# 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
+```
+
+When you run `bin/test`, you should see the following:
+
+```
+# 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
+```
+
+This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test.
+We can easily generate these models in our "dummy" Rails application by running the following commands from the
+`test/dummy` directory:
+
+```bash
+$ cd test/dummy
+$ rails generate model Hickwall last_squawk:string
+$ rails generate model Wickwall last_squawk:string last_tweet:string
+```
+
+Now you can create the necessary database tables in your testing database by navigating to your dummy app
+and migrating the database. First, run:
+
+```bash
+$ cd test/dummy
+$ rails db:migrate
+```
+
+While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act
+like yaffles.
+
+```ruby
+# 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
+```
+
+We will also add code to define the `acts_as_yaffle` method.
+
+```ruby
+# 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
+```
+
+You can then return to the root directory (`cd ../..`) of your plugin and rerun the tests using `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
+```
+
+Getting closer... Now we will implement the code of the `acts_as_yaffle` method to make the tests pass.
+
+```ruby
+# 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
+```
+
+When you run `bin/test`, you should see the tests all pass:
+
+```bash
+ 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
+```
+
+### Add an Instance Method
+
+This plugin will add a method named 'squawk' to any Active Record object that calls `acts_as_yaffle`. The 'squawk'
+method will simply set the value of one of the fields in the database.
+
+To start out, write a failing test that shows the behavior you'd like:
+
+```ruby
+# 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
+```
+
+Run the test to make sure the last two tests fail with an error that contains "NoMethodError: undefined method `squawk'",
+then update `acts_as_yaffle.rb` to look like this:
+
+```ruby
+# 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
+```
+
+Run `bin/test` one final time and you should see:
+
+```
+ 6 runs, 6 assertions, 0 failures, 0 errors, 0 skips
+```
+
+NOTE: The use of `write_attribute` to write to the field in model is just one example of how a plugin can interact with the model, and will not always be the right method to use. For example, you could also use:
+
+```ruby
+send("#{self.class.yaffle_text_field}=", string.to_squawk)
+```
+
+Generators
+----------
+
+Generators can be included in your gem simply by creating them in a `lib/generators` directory of your plugin. More information about
+the creation of generators can be found in the [Generators Guide](generators.html).
+
+Publishing Your Gem
+-------------------
+
+Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply
+commit the code to a Git repository (like GitHub) and add a line to the `Gemfile` of the application in question:
+
+```ruby
+gem "yaffle", git: "https://github.com/rails/yaffle.git"
+```
+
+After running `bundle install`, your gem functionality will be available to the application.
+
+When the gem is ready to be shared as a formal release, it can be published to [RubyGems](https://rubygems.org).
+For more information about publishing gems to RubyGems, see: [Publishing your gem](https://guides.rubygems.org/publishing).
+
+RDoc Documentation
+------------------
+
+Once your plugin is stable and you are ready to deploy, do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
+
+The first step is to update the README file with detailed information about how to use your plugin. A few key things to include are:
+
+* Your name
+* How to install
+* How to add the functionality to the app (several examples of common use cases)
+* Warnings, gotchas or tips that might help users and save them time
+
+Once your README is solid, go through and add rdoc comments to all of the methods that developers will use. It's also customary to add `#:nodoc:` comments to those parts of the code that are not included in the public API.
+
+Once your comments are good to go, navigate to your plugin directory and run:
+
+```bash
+$ bundle exec rake rdoc
+```
+
+### References
+
+* [Developing a RubyGem using Bundler](https://github.com/radar/guides/blob/master/gem-development.md)
+* [Using .gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/)
+* [Gemspec Reference](https://guides.rubygems.org/specification-reference/)