railties 3.0.20 → 3.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (193) hide show
  1. data/CHANGELOG +36 -49
  2. data/README.rdoc +2 -1
  3. data/guides/assets/stylesheets/fixes.css +16 -0
  4. data/guides/rails_guides.rb +2 -2
  5. data/guides/rails_guides/generator.rb +8 -3
  6. data/guides/rails_guides/textile_extensions.rb +4 -2
  7. data/guides/source/2_2_release_notes.textile +3 -3
  8. data/guides/source/2_3_release_notes.textile +2 -2
  9. data/guides/source/3_0_release_notes.textile +14 -14
  10. data/guides/source/action_controller_overview.textile +54 -79
  11. data/guides/source/action_mailer_basics.textile +39 -9
  12. data/guides/source/action_view_overview.textile +257 -211
  13. data/guides/source/active_record_basics.textile +1 -1
  14. data/guides/source/active_record_querying.textile +217 -27
  15. data/guides/source/active_record_validations_callbacks.textile +94 -25
  16. data/guides/source/active_support_core_extensions.textile +109 -77
  17. data/guides/source/ajax_on_rails.textile +15 -150
  18. data/guides/source/api_documentation_guidelines.textile +12 -12
  19. data/guides/source/association_basics.textile +74 -60
  20. data/guides/source/caching_with_rails.textile +59 -60
  21. data/guides/source/command_line.textile +46 -47
  22. data/guides/source/configuring.textile +55 -37
  23. data/guides/source/contribute.textile +7 -7
  24. data/guides/source/contributing_to_ruby_on_rails.textile +14 -23
  25. data/guides/source/credits.html.erb +3 -3
  26. data/guides/source/debugging_rails_applications.textile +59 -46
  27. data/guides/source/form_helpers.textile +76 -31
  28. data/guides/source/generators.textile +39 -40
  29. data/guides/source/getting_started.textile +73 -94
  30. data/guides/source/i18n.textile +64 -58
  31. data/guides/source/index.html.erb +3 -3
  32. data/guides/source/initialization.textile +634 -3284
  33. data/guides/source/layout.html.erb +6 -7
  34. data/guides/source/layouts_and_rendering.textile +59 -60
  35. data/guides/source/migrations.textile +63 -59
  36. data/guides/source/nested_model_forms.textile +2 -2
  37. data/guides/source/performance_testing.textile +16 -16
  38. data/guides/source/plugins.textile +236 -1280
  39. data/guides/source/rails_application_templates.textile +37 -29
  40. data/guides/source/rails_on_rack.textile +4 -9
  41. data/guides/source/routing.textile +96 -75
  42. data/guides/source/ruby_on_rails_guides_guidelines.textile +19 -12
  43. data/guides/source/security.textile +57 -30
  44. data/guides/source/testing.textile +26 -24
  45. data/guides/w3c_validator.rb +2 -2
  46. data/lib/rails.rb +1 -7
  47. data/lib/rails/application.rb +46 -76
  48. data/lib/rails/application/bootstrap.rb +6 -11
  49. data/lib/rails/application/configuration.rb +43 -40
  50. data/lib/rails/application/finisher.rb +16 -4
  51. data/lib/rails/application/railties.rb +6 -24
  52. data/lib/rails/application/routes_reloader.rb +45 -0
  53. data/lib/rails/backtrace_cleaner.rb +1 -1
  54. data/lib/rails/cli.rb +7 -5
  55. data/lib/rails/commands.rb +27 -2
  56. data/lib/rails/commands/application.rb +14 -1
  57. data/lib/rails/commands/benchmarker.rb +3 -1
  58. data/lib/rails/commands/dbconsole.rb +2 -2
  59. data/lib/rails/commands/destroy.rb +3 -1
  60. data/lib/rails/commands/generate.rb +3 -1
  61. data/lib/rails/commands/plugin.rb +2 -7
  62. data/lib/rails/commands/plugin_new.rb +10 -0
  63. data/lib/rails/commands/profiler.rb +3 -1
  64. data/lib/rails/commands/server.rb +4 -0
  65. data/lib/rails/configuration.rb +8 -81
  66. data/lib/rails/console/app.rb +2 -2
  67. data/lib/rails/engine.rb +460 -78
  68. data/lib/rails/engine/configuration.rb +46 -49
  69. data/lib/rails/engine/railties.rb +33 -0
  70. data/lib/rails/generators.rb +11 -5
  71. data/lib/rails/generators/actions.rb +2 -27
  72. data/lib/rails/generators/app_base.rb +216 -0
  73. data/lib/rails/generators/base.rb +3 -2
  74. data/lib/rails/generators/erb/scaffold/templates/index.html.erb +1 -1
  75. data/lib/rails/generators/generated_attribute.rb +2 -1
  76. data/lib/rails/generators/migration.rb +6 -2
  77. data/lib/rails/generators/named_base.rb +79 -3
  78. data/lib/rails/generators/rails/app/app_generator.rb +44 -209
  79. data/lib/rails/generators/rails/app/templates/Gemfile +15 -31
  80. data/lib/rails/generators/rails/app/templates/README +2 -2
  81. data/lib/rails/generators/rails/app/templates/Rakefile +1 -1
  82. data/lib/rails/generators/rails/app/templates/{public → app/assets}/images/rails.png +0 -0
  83. data/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +8 -0
  84. data/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +5 -0
  85. data/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory +0 -0
  86. data/lib/rails/generators/rails/app/templates/app/models/.empty_directory +0 -0
  87. data/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +4 -4
  88. data/lib/rails/generators/rails/app/templates/config/application.rb +19 -3
  89. data/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +4 -4
  90. data/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +11 -6
  91. data/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +3 -3
  92. data/lib/rails/generators/rails/app/templates/config/databases/oracle.yml +1 -1
  93. data/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +1 -2
  94. data/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +14 -11
  95. data/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +5 -1
  96. data/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +1 -1
  97. data/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +12 -0
  98. data/lib/rails/generators/rails/app/templates/config/locales/en.yml +1 -1
  99. data/lib/rails/generators/rails/app/templates/config/routes.rb +1 -1
  100. data/lib/rails/generators/rails/app/templates/db/{seeds.rb → seeds.rb.tt} +2 -2
  101. data/lib/rails/generators/rails/app/templates/public/index.html +10 -8
  102. data/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory +0 -0
  103. data/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory +0 -0
  104. data/lib/rails/generators/rails/app/templates/test/functional/.empty_directory +0 -0
  105. data/lib/rails/generators/rails/app/templates/test/integration/.empty_directory +0 -0
  106. data/lib/rails/generators/rails/app/templates/test/{test_helper.rb.tt → test_helper.rb} +0 -0
  107. data/lib/rails/generators/rails/app/templates/test/unit/.empty_directory +0 -0
  108. data/lib/rails/generators/rails/assets/USAGE +20 -0
  109. data/lib/rails/generators/rails/assets/assets_generator.rb +39 -0
  110. data/lib/rails/generators/rails/assets/templates/javascript.js +2 -0
  111. data/lib/rails/generators/rails/assets/templates/javascript.js.coffee +3 -0
  112. data/lib/rails/generators/rails/assets/templates/stylesheet.css +4 -0
  113. data/lib/rails/generators/rails/assets/templates/stylesheet.css.scss +5 -0
  114. data/lib/rails/generators/rails/controller/controller_generator.rb +1 -1
  115. data/lib/rails/generators/rails/controller/templates/controller.rb +2 -0
  116. data/lib/rails/generators/rails/generator/generator_generator.rb +2 -2
  117. data/lib/rails/generators/rails/generator/templates/templates/.empty_directory +0 -0
  118. data/lib/rails/generators/rails/helper/templates/helper.rb +2 -0
  119. data/lib/rails/generators/rails/plugin/plugin_generator.rb +7 -0
  120. data/lib/rails/generators/rails/plugin/templates/Rakefile.tt +4 -4
  121. data/lib/rails/generators/rails/plugin_new/USAGE +10 -0
  122. data/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +303 -0
  123. data/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +9 -0
  124. data/lib/rails/generators/rails/plugin_new/templates/Gemfile +11 -0
  125. data/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE +20 -0
  126. data/lib/rails/generators/rails/plugin_new/templates/README.rdoc +3 -0
  127. data/lib/rails/generators/rails/plugin_new/templates/Rakefile +21 -0
  128. data/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt +4 -0
  129. data/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt +4 -0
  130. data/lib/rails/generators/rails/plugin_new/templates/app/models/.empty_directory +0 -0
  131. data/lib/rails/generators/rails/plugin_new/templates/config/routes.rb +6 -0
  132. data/lib/rails/generators/rails/plugin_new/templates/gitignore +6 -0
  133. data/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb +6 -0
  134. data/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb +7 -0
  135. data/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake +4 -0
  136. data/lib/rails/generators/rails/plugin_new/templates/rails/application.rb +16 -0
  137. data/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb +10 -0
  138. data/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb +4 -0
  139. data/lib/rails/generators/rails/plugin_new/templates/script/rails.tt +5 -0
  140. data/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb +7 -0
  141. data/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb +12 -0
  142. data/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb +10 -0
  143. data/lib/rails/generators/rails/resource/resource_generator.rb +2 -2
  144. data/lib/rails/generators/rails/scaffold/scaffold_generator.rb +20 -1
  145. data/lib/rails/generators/rails/{stylesheets → scaffold}/templates/scaffold.css +0 -0
  146. data/lib/rails/generators/rails/scaffold/templates/scaffold.css.scss +58 -0
  147. data/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +21 -19
  148. data/lib/rails/generators/resource_helpers.rb +3 -3
  149. data/lib/rails/generators/test_case.rb +2 -20
  150. data/lib/rails/generators/test_unit/controller/templates/functional_test.rb +5 -4
  151. data/lib/rails/generators/test_unit/helper/templates/helper_test.rb +2 -0
  152. data/lib/rails/generators/test_unit/integration/templates/integration_test.rb +3 -4
  153. data/lib/rails/generators/test_unit/mailer/templates/functional_test.rb +5 -4
  154. data/lib/rails/generators/test_unit/model/templates/fixtures.yml +1 -1
  155. data/lib/rails/generators/test_unit/model/templates/unit_test.rb +5 -4
  156. data/lib/rails/generators/test_unit/observer/templates/unit_test.rb +5 -4
  157. data/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt +3 -4
  158. data/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +7 -5
  159. data/lib/rails/info.rb +0 -1
  160. data/lib/rails/paths.rb +119 -65
  161. data/lib/rails/plugin.rb +18 -19
  162. data/lib/rails/rack/log_tailer.rb +1 -1
  163. data/lib/rails/railtie.rb +50 -47
  164. data/lib/rails/railtie/configurable.rb +20 -10
  165. data/lib/rails/railtie/configuration.rb +20 -19
  166. data/lib/rails/source_annotation_extractor.rb +5 -5
  167. data/lib/rails/tasks.rb +1 -0
  168. data/lib/rails/tasks/assets.rake +10 -0
  169. data/lib/rails/tasks/documentation.rake +2 -8
  170. data/lib/rails/tasks/engine.rake +69 -0
  171. data/lib/rails/tasks/framework.rake +4 -21
  172. data/lib/rails/tasks/misc.rake +1 -1
  173. data/lib/rails/tasks/routes.rake +2 -1
  174. data/lib/rails/test_help.rb +17 -1
  175. data/lib/rails/test_unit/railtie.rb +1 -1
  176. data/lib/rails/test_unit/testing.rake +8 -3
  177. data/lib/rails/version.rb +3 -3
  178. metadata +128 -100
  179. checksums.yaml +0 -7
  180. data/lib/rails/application/configurable.rb +0 -19
  181. data/lib/rails/console/sandbox.rb +0 -6
  182. data/lib/rails/deprecation.rb +0 -41
  183. data/lib/rails/engine/configurable.rb +0 -25
  184. data/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml +0 -62
  185. data/lib/rails/generators/rails/app/templates/public/javascripts/application.js +0 -2
  186. data/lib/rails/generators/rails/app/templates/public/javascripts/controls.js +0 -965
  187. data/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js +0 -974
  188. data/lib/rails/generators/rails/app/templates/public/javascripts/effects.js +0 -1123
  189. data/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js +0 -6001
  190. data/lib/rails/generators/rails/app/templates/public/javascripts/rails.js +0 -202
  191. data/lib/rails/generators/rails/stylesheets/USAGE +0 -5
  192. data/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb +0 -9
  193. data/lib/rails/info_routes.rb +0 -3
@@ -1,6 +1,6 @@
1
1
  h2. Rails nested model forms
2
2
 
3
- Creating a form for a model _and_ its associations can become quite tedious. Therefor Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
3
+ Creating a form for a model _and_ its associations can become quite tedious. Therefore Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
4
4
 
5
5
  In this guide you will:
6
6
 
@@ -219,4 +219,4 @@ You can basically see the +projects_attributes+ hash as an array of attribute ha
219
219
 
220
220
  NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep.
221
221
 
222
- TIP: You _can_ however pass an array to the writer method generated by +accepts_nested_attributes_for+ if you're using plain Ruby or some other API access. See (TODO) for more info and example.
222
+ TIP: You _can_ however pass an array to the writer method generated by +accepts_nested_attributes_for+ if you're using plain Ruby or some other API access. See (TODO) for more info and example.
@@ -37,7 +37,7 @@ h4. Generating Performance Tests
37
37
  Rails provides a generator called +test_unit:performance+ for creating new performance tests:
38
38
 
39
39
  <shell>
40
- rails generate test_unit:performance homepage
40
+ $ rails generate test_unit:performance homepage
41
41
  </shell>
42
42
 
43
43
  This generates +homepage_test.rb+ in the +test/performance+ directory:
@@ -60,8 +60,8 @@ Let's assume your application has the following controller and model:
60
60
 
61
61
  <ruby>
62
62
  # routes.rb
63
- map.root :controller => 'home'
64
- map.resources :posts
63
+ root :to => 'home#index'
64
+ resources :posts
65
65
 
66
66
  # home_controller.rb
67
67
  class HomeController < ApplicationController
@@ -316,16 +316,16 @@ Compile Ruby and apply this "GC Patch":http://rubyforge.org/tracker/download.php
316
316
  h5. Download and Extract
317
317
 
318
318
  <shell>
319
- [lifo@null ~]$ mkdir rubygc
320
- [lifo@null ~]$ wget <download the latest stable ruby from ftp://ftp.ruby-lang.org/pub/ruby>
321
- [lifo@null ~]$ tar -xzvf <ruby-version.tar.gz>
322
- [lifo@null ~]$ cd <ruby-version>
319
+ $ mkdir rubygc
320
+ $ wget <download the latest stable ruby from ftp://ftp.ruby-lang.org/pub/ruby>
321
+ $ tar -xzvf <ruby-version.tar.gz>
322
+ $ cd <ruby-version>
323
323
  </shell>
324
324
 
325
325
  h5. Apply the Patch
326
326
 
327
327
  <shell>
328
- [lifo@null ruby-version]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0
328
+ $ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0
329
329
  </shell>
330
330
 
331
331
  h5. Configure and Install
@@ -333,8 +333,8 @@ h5. Configure and Install
333
333
  The following will install ruby in your home directory's +/rubygc+ directory. Make sure to replace +&lt;homedir&gt;+ with a full patch to your actual home directory.
334
334
 
335
335
  <shell>
336
- [lifo@null ruby-version]$ ./configure --prefix=/<homedir>/rubygc
337
- [lifo@null ruby-version]$ make && make install
336
+ $ ./configure --prefix=/<homedir>/rubygc
337
+ $ make && make install
338
338
  </shell>
339
339
 
340
340
  h5. Prepare Aliases
@@ -364,8 +364,8 @@ Additionally, install the following gems:
364
364
  If installing +mysql+ fails, you can try to install it manually:
365
365
 
366
366
  <shell>
367
- [lifo@null mysql]$ gcruby extconf.rb --with-mysql-config
368
- [lifo@null mysql]$ make && make install
367
+ $ gcruby extconf.rb --with-mysql-config
368
+ $ make && make install
369
369
  </shell>
370
370
 
371
371
  And you're ready to go. Don't forget to use +gcruby+ and +gcrake+ aliases when running the performance tests.
@@ -436,7 +436,7 @@ h4. Model
436
436
  Project.benchmark("Creating project") do
437
437
  project = Project.create("name" => "stuff")
438
438
  project.create_manager("name" => "David")
439
- project.milestones << Milestone.find(:all)
439
+ project.milestones << Milestone.all
440
440
  end
441
441
  </ruby>
442
442
 
@@ -469,7 +469,7 @@ And in "views":http://api.rubyonrails.org/classes/ActionController/Benchmarking/
469
469
 
470
470
  <erb>
471
471
  <% benchmark("Showing projects partial") do %>
472
- <%= render :partial => @projects %>
472
+ <%= render @projects %>
473
473
  <% end %>
474
474
  </erb>
475
475
 
@@ -500,8 +500,8 @@ h4. Rails Plugins and Gems
500
500
 
501
501
  * "Rails Analyzer":http://rails-analyzer.rubyforge.org
502
502
  * "Palmist":http://www.flyingmachinestudios.com/projects/
503
- * "Rails Footnotes":http://github.com/josevalim/rails-footnotes/tree/master
504
- * "Query Reviewer":http://github.com/dsboulder/query_reviewer/tree/master
503
+ * "Rails Footnotes":https://github.com/josevalim/rails-footnotes/tree/master
504
+ * "Query Reviewer":https://github.com/dsboulder/query_reviewer/tree/master
505
505
 
506
506
  h4. Generic Tools
507
507
 
@@ -10,281 +10,71 @@ After reading this guide you should be familiar with:
10
10
 
11
11
  * Creating a plugin from scratch
12
12
  * Writing and running tests for the plugin
13
- * Storing models, views, controllers, helpers and even other plugins in your plugins
14
- * Writing generators
15
- * Writing custom Rake tasks in your plugin
16
- * Generating RDoc documentation for your plugin
17
- * Avoiding common pitfalls with 'init.rb'
18
13
 
19
14
  This guide describes how to build a test-driven plugin that will:
20
15
 
21
16
  * Extend core ruby classes like Hash and String
22
17
  * Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins
23
- * Add a view helper that can be used in erb templates
24
- * Add a new generator that will generate a migration
25
- * Add a custom generator command
26
- * A custom route method that can be used in routes.rb
18
+ * Give you information about where to put generators in your plugin.
27
19
 
28
- 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. First, you need to get setup for development.
20
+ For the purpose of this guide pretend for a moment that you are an avid bird watcher.
21
+ Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle
22
+ goodness.
29
23
 
30
24
  endprologue.
31
25
 
32
26
  h3. Setup
33
27
 
34
- h4. Create the Basic Application
28
+ h4. Generating the Plugin Skeleton
35
29
 
36
- The examples in this guide require that you have a working rails application. To create a simple one execute:
30
+ Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain
31
+ how this generator works.
37
32
 
38
33
  <shell>
39
- gem install rails
40
- rails new yaffle_guide
41
- cd yaffle_guide
42
- rails generate scaffold bird name:string
43
- rake db:migrate
44
- rails server
34
+ $ rails generate plugin --help
45
35
  </shell>
46
36
 
47
- Then navigate to http://localhost:3000/birds. Make sure you have a functioning rails application before continuing.
37
+ This generator places the plugin into the vendor/plugins directory.
48
38
 
49
- NOTE: The aforementioned instructions will work for sqlite3. For more detailed instructions on how to create a rails application for other databases see the API docs.
39
+ Vendored plugins are useful for quickly prototyping your plugin but current thinking in the Rails community is shifting towards
40
+ packaging plugins as gems, especially with the inclusion of Bundler as the Rails dependency manager.
41
+ Packaging a plugin as a gem may be overkill for any plugins that will not be shared across projects but doing so from the start makes it easier to share the plugin going forward without adding too much additional overhead during development.
50
42
 
51
-
52
- h4. Generate the Plugin Skeleton
53
-
54
- Rails ships with a plugin generator which creates a basic plugin skeleton. Pass the plugin name, either 'CamelCased' or 'under_scored', as an argument. Pass +--with-generator+ to add an example generator also.
55
-
56
- This creates a plugin in +vendor/plugins+ including an +init.rb+ and +README+ as well as standard +lib+, +task+, and +test+ directories.
57
-
58
- Examples:
59
- <shell>
60
- rails generate plugin yaffle
61
- rails generate plugin yaffle --with-generator
62
- </shell>
63
-
64
- To get more detailed help on the plugin generator, type +rails generate plugin+.
65
-
66
- Later on this guide will describe how to work with generators, so go ahead and generate your plugin with the +--with-generator+ option now:
67
-
68
- <shell>
69
- rails generate plugin yaffle --with-generator
70
- </shell>
71
-
72
- You should see the following output:
73
-
74
- <shell>
75
- create vendor/plugins/yaffle/lib
76
- create vendor/plugins/yaffle/tasks
77
- create vendor/plugins/yaffle/test
78
- create vendor/plugins/yaffle/README
79
- create vendor/plugins/yaffle/MIT-LICENSE
80
- create vendor/plugins/yaffle/Rakefile
81
- create vendor/plugins/yaffle/init.rb
82
- create vendor/plugins/yaffle/install.rb
83
- create vendor/plugins/yaffle/uninstall.rb
84
- create vendor/plugins/yaffle/lib/yaffle.rb
85
- create vendor/plugins/yaffle/tasks/yaffle_tasks.rake
86
- create vendor/plugins/yaffle/test/core_ext_test.rb
87
- create vendor/plugins/yaffle/generators
88
- create vendor/plugins/yaffle/generators/yaffle
89
- create vendor/plugins/yaffle/generators/yaffle/templates
90
- create vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb
91
- create vendor/plugins/yaffle/generators/yaffle/USAGE
92
- </shell>
93
-
94
- h4. Organize Your Files
95
-
96
- To make it easy to organize your files and to make the plugin more compatible with GemPlugins, start out by altering your file system to look like this:
43
+ Rails 3.1 will ship with a plugin generator that will default to setting up a plugin
44
+ as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the
45
+ "Enginex gem":http://www.github.com/josevalim/enginex.
97
46
 
98
47
  <shell>
99
- |-- lib
100
- | |-- yaffle
101
- | `-- yaffle.rb
102
- `-- rails
103
- |
104
- `-- init.rb
48
+ $ gem install enginex
49
+ $ enginex --help
50
+ $ enginex yaffle
105
51
  </shell>
106
52
 
107
- <ruby>
108
- # vendor/plugins/yaffle/init.rb
109
-
110
- require 'yaffle'
111
- </ruby>
112
-
113
- Now you can add any +require+ statements to +lib/yaffle.rb+ and keep +init.rb+ clean.
114
-
115
- h3. Tests
116
-
117
- In this guide you will learn how to test your plugin against multiple different database adapters using Active Record. To setup your plugin to allow for easy testing you'll need to add 3 files:
118
-
119
- * A +database.yml+ file with all of your connection strings
120
- * A +schema.rb+ file with your table definitions
121
- * A test helper method that sets up the database
122
-
123
- h4. Test Setup
124
-
125
- <yaml>
126
- # vendor/plugins/yaffle/test/database.yml
127
-
128
- sqlite:
129
- :adapter: sqlite
130
- :dbfile: vendor/plugins/yaffle/test/yaffle_plugin.sqlite.db
131
-
132
- sqlite3:
133
- :adapter: sqlite3
134
- :dbfile: vendor/plugins/yaffle/test/yaffle_plugin.sqlite3.db
135
-
136
- postgresql:
137
- :adapter: postgresql
138
- :username: postgres
139
- :password: postgres
140
- :database: yaffle_plugin_test
141
- :min_messages: ERROR
142
-
143
- mysql:
144
- :adapter: mysql
145
- :host: localhost
146
- :username: root
147
- :password: password
148
- :database: yaffle_plugin_test
149
- </yaml>
150
-
151
- For this guide you'll need 2 tables/models, Hickwalls and Wickwalls, so add the following:
152
-
153
- <ruby>
154
- # vendor/plugins/yaffle/test/schema.rb
155
-
156
- ActiveRecord::Schema.define(:version => 0) do
157
- create_table :hickwalls, :force => true do |t|
158
- t.string :name
159
- t.string :last_squawk
160
- t.datetime :last_squawked_at
161
- end
162
- create_table :wickwalls, :force => true do |t|
163
- t.string :name
164
- t.string :last_tweet
165
- t.datetime :last_tweeted_at
166
- end
167
- create_table :woodpeckers, :force => true do |t|
168
- t.string :name
169
- end
170
- end
171
- </ruby>
172
-
173
- <ruby>
174
- # vendor/plugins/yaffle/test/test_helper.rb
175
-
176
- ENV['RAILS_ENV'] = 'test'
177
- ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
178
-
179
- require 'test/unit'
180
- require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
181
-
182
- def load_schema
183
- config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
184
- ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
185
-
186
- db_adapter = ENV['DB']
187
-
188
- # no db passed, try one of these fine config-free DBs before bombing.
189
- db_adapter ||=
190
- begin
191
- require 'rubygems'
192
- require 'sqlite'
193
- 'sqlite'
194
- rescue MissingSourceFile
195
- begin
196
- require 'sqlite3'
197
- 'sqlite3'
198
- rescue MissingSourceFile
199
- end
200
- end
201
-
202
- if db_adapter.nil?
203
- raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
204
- end
205
-
206
- ActiveRecord::Base.establish_connection(config[db_adapter])
207
- load(File.dirname(__FILE__) + "/schema.rb")
208
- require File.dirname(__FILE__) + '/../rails/init'
209
- end
210
- </ruby>
211
-
212
- Now whenever you write a test that requires the database, you can call 'load_schema'.
213
-
214
- h4. Run the Plugin Tests
215
-
216
- Once you have these files in place, you can write your first test to ensure that your plugin-testing setup is correct. By default rails generates a file in +vendor/plugins/yaffle/test/yaffle_test.rb+ with a sample test. Replace the contents of that file with:
217
-
218
- <ruby>
219
- # vendor/plugins/yaffle/test/yaffle_test.rb
220
-
221
- require File.dirname(__FILE__) + '/test_helper'
222
-
223
- class YaffleTest < Test::Unit::TestCase
224
- load_schema
225
-
226
- class Hickwall < ActiveRecord::Base
227
- end
228
-
229
- class Wickwall < ActiveRecord::Base
230
- end
231
-
232
- def test_schema_has_loaded_correctly
233
- assert_equal [], Hickwall.all
234
- assert_equal [], Wickwall.all
235
- end
236
-
237
- end
238
- </ruby>
239
-
240
- To run this, go to the plugin directory and run +rake+:
241
-
242
- <shell>
243
- cd vendor/plugins/yaffle
244
- rake
245
- </shell>
53
+ This command will create a new directory named "yaffle" within the current directory.
246
54
 
247
- You should see output like:
55
+ h3. Testing your newly generated plugin
248
56
 
249
- <shell>
250
- /opt/local/bin/ruby -Ilib:lib "/opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/yaffle_test.rb"
251
- create_table(:hickwalls, {:force=>true})
252
- -> 0.0220s
253
- -- create_table(:wickwalls, {:force=>true})
254
- -> 0.0077s
255
- -- initialize_schema_migrations_table()
256
- -> 0.0007s
257
- -- assume_migrated_upto_version(0)
258
- -> 0.0007s
259
- Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
260
- Started
261
- .
262
- Finished in 0.002236 seconds.
263
-
264
- 1 test, 1 assertion, 0 failures, 0 errors
265
- </shell>
57
+ You can navigate to the directory that contains the plugin, run the +bundle install+ command
58
+ and run the one generated test using the +rake+ command.
266
59
 
267
- By default the setup above runs your tests with sqlite or sqlite3. To run tests with one of the other connection strings specified in +database.yml+, pass the DB environment variable to rake:
60
+ You should see:
268
61
 
269
62
  <shell>
270
- rake DB=sqlite
271
- rake DB=sqlite3
272
- rake DB=mysql
273
- rake DB=postgresql
63
+ 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
274
64
  </shell>
275
65
 
276
- Now you are ready to test-drive your plugin!
66
+ This will tell you that everything got generated properly and you are ready to start adding functionality.
277
67
 
278
68
  h3. Extending Core Classes
279
69
 
280
70
  This section will explain how to add a method to String that will be available anywhere in your rails application.
281
71
 
282
- In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions:
72
+ In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions:
283
73
 
284
74
  <ruby>
285
- # vendor/plugins/yaffle/test/core_ext_test.rb
75
+ # yaffle/test/core_ext_test.rb
286
76
 
287
- require File.dirname(__FILE__) + '/test_helper'
77
+ require 'test_helper'
288
78
 
289
79
  class CoreExtTest < Test::Unit::TestCase
290
80
  def test_to_squawk_prepends_the_word_squawk
@@ -293,20 +83,13 @@ class CoreExtTest < Test::Unit::TestCase
293
83
  end
294
84
  </ruby>
295
85
 
296
- Navigate to your plugin directory and run +rake test+:
297
-
298
- <shell>
299
- cd vendor/plugins/yaffle
300
- rake test
301
- </shell>
302
-
303
- The test above should fail with the message:
86
+ Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method:
304
87
 
305
88
  <shell>
306
- 1) Error:
307
- test_to_squawk_prepends_the_word_squawk(CoreExtTest):
308
- NoMethodError: undefined method `to_squawk' for "Hello World":String
309
- ./test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk'
89
+ 1) Error:
90
+ test_to_squawk_prepends_the_word_squawk(CoreExtTest):
91
+ NoMethodError: undefined method `to_squawk' for "Hello World":String
92
+ test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk'
310
93
  </shell>
311
94
 
312
95
  Great - now you are ready to start development.
@@ -314,15 +97,18 @@ Great - now you are ready to start development.
314
97
  Then in +lib/yaffle.rb+ require +lib/core_ext+:
315
98
 
316
99
  <ruby>
317
- # vendor/plugins/yaffle/lib/yaffle.rb
100
+ # yaffle/lib/yaffle.rb
318
101
 
319
102
  require "yaffle/core_ext"
103
+
104
+ module Yaffle
105
+ end
320
106
  </ruby>
321
107
 
322
108
  Finally, create the +core_ext.rb+ file and add the +to_squawk+ method:
323
109
 
324
110
  <ruby>
325
- # vendor/plugins/yaffle/lib/yaffle/core_ext.rb
111
+ # yaffle/lib/yaffle/core_ext.rb
326
112
 
327
113
  String.class_eval do
328
114
  def to_squawk
@@ -331,7 +117,13 @@ String.class_eval do
331
117
  end
332
118
  </ruby>
333
119
 
334
- To test that your method does what it says it does, run the unit tests with +rake+ from your plugin directory. To see this in action, fire up a console and start squawking:
120
+ To test that your method does what it says it does, run the unit tests with +rake+ from your plugin directory.
121
+
122
+ <shell>
123
+ 3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
124
+ </shell>
125
+
126
+ To see this in action, change to the test/dummy directory, fire up a console and start squawking:
335
127
 
336
128
  <shell>
337
129
  $ rails console
@@ -339,115 +131,56 @@ $ rails console
339
131
  => "squawk! Hello World"
340
132
  </shell>
341
133
 
342
- h4. Working with +init.rb+
343
-
344
- When Rails loads plugins it looks for a file named +init.rb+. However, when the plugin is initialized, +init.rb+ is invoked via +eval+ (not +require+) so it has slightly different behavior.
345
-
346
- NOTE: The plugins loader also looks for +rails/init.rb+, but that one is deprecated in favor of the top-level +init.rb+ aforementioned.
347
-
348
- Under certain circumstances if you reopen classes or modules in +init.rb+ you may inadvertently create a new class, rather than reopening an existing class. A better alternative is to reopen the class in a different file, and require that file from +init.rb+, as shown above.
349
-
350
- If you must reopen a class in +init.rb+ you can use +module_eval+ or +class_eval+ to avoid any issues:
351
-
352
- <ruby>
353
- # vendor/plugins/yaffle/init.rb
354
-
355
- Hash.class_eval do
356
- def is_a_special_hash?
357
- true
358
- end
359
- end
360
- </ruby>
361
-
362
- Another way is to explicitly define the top-level module space for all modules and classes, like +::Hash+:
363
-
364
- <ruby>
365
- # vendor/plugins/yaffle/init.rb
366
-
367
- class ::Hash
368
- def is_a_special_hash?
369
- true
370
- end
371
- end
372
- </ruby>
373
-
374
134
  h3. Add an "acts_as" Method to Active Record
375
135
 
376
- 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 models.
136
+ A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you
137
+ want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your Active Record models.
377
138
 
378
139
  To begin, set up your files so that you have:
379
140
 
380
- * *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb*
381
-
382
141
  <ruby>
383
- require File.dirname(__FILE__) + '/test_helper'
142
+ # yaffle/test/acts_as_yaffle_test.rb
143
+
144
+ require 'test_helper'
384
145
 
385
146
  class ActsAsYaffleTest < Test::Unit::TestCase
386
147
  end
387
148
  </ruby>
388
149
 
389
- * *vendor/plugins/yaffle/lib/yaffle.rb*
390
-
391
150
  <ruby>
392
- require 'yaffle/acts_as_yaffle'
393
- </ruby>
151
+ # yaffle/lib/yaffle.rb
394
152
 
395
- * *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb*
153
+ require "yaffle/core_ext"
154
+ require 'yaffle/acts_as_yaffle'
396
155
 
397
- <ruby>
398
156
  module Yaffle
399
- # your code will go here
400
157
  end
401
158
  </ruby>
402
159
 
403
- Note that after requiring 'acts_as_yaffle' you also have to include it into ActiveRecord::Base so that your plugin methods will be available to the rails models.
404
-
405
- One of the most common plugin patterns for 'acts_as_yaffle' plugins is to structure your file like so:
406
-
407
- * *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb*
408
-
409
160
  <ruby>
410
- module Yaffle
411
- def self.included(base)
412
- base.send :extend, ClassMethods
413
- end
414
-
415
- module ClassMethods
416
- # any method placed here will apply to classes, like Hickwall
417
- def acts_as_something
418
- send :include, InstanceMethods
419
- end
420
- end
161
+ # yaffle/lib/yaffle/acts_as_yaffle.rb
421
162
 
422
- module InstanceMethods
423
- # any method placed here will apply to instaces, like @hickwall
163
+ module Yaffle
164
+ module ActsAsYaffle
165
+ # your code will go here
424
166
  end
425
167
  end
426
168
  </ruby>
427
169
 
428
- With structure you can easily separate the methods that will be used for the class (like +Hickwall.some_method+) and the instance (like +@hickwell.some_method+).
429
-
430
170
  h4. Add a Class Method
431
171
 
432
- 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'.
172
+ This plugin will expect that you've added a method to your model named 'last_squawk'. However, the
173
+ plugin users might have already defined a method on their model named 'last_squawk' that they use
174
+ for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'.
433
175
 
434
176
  To start out, write a failing test that shows the behavior you'd like:
435
177
 
436
- * *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb*
437
-
438
178
  <ruby>
439
- require File.dirname(__FILE__) + '/test_helper'
440
-
441
- class Hickwall < ActiveRecord::Base
442
- acts_as_yaffle
443
- end
179
+ # yaffle/test/acts_as_yaffle_test.rb
444
180
 
445
- class Wickwall < ActiveRecord::Base
446
- acts_as_yaffle :yaffle_text_field => :last_tweet
447
- end
181
+ require 'test_helper'
448
182
 
449
183
  class ActsAsYaffleTest < Test::Unit::TestCase
450
- load_schema
451
184
 
452
185
  def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
453
186
  assert_equal "last_squawk", Hickwall.yaffle_text_field
@@ -456,1058 +189,281 @@ class ActsAsYaffleTest < Test::Unit::TestCase
456
189
  def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
457
190
  assert_equal "last_tweet", Wickwall.yaffle_text_field
458
191
  end
192
+
459
193
  end
460
194
  </ruby>
461
195
 
462
- To make these tests pass, you could modify your +acts_as_yaffle+ file like so:
196
+ When you run +rake+, you should see the following:
463
197
 
464
- * *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb*
198
+ <shell>
199
+ 1) Error:
200
+ test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest):
201
+ NameError: uninitialized constant ActsAsYaffleTest::Hickwall
202
+ test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
465
203
 
466
- <ruby>
467
- module Yaffle
468
- def self.included(base)
469
- base.send :extend, ClassMethods
470
- end
204
+ 2) Error:
205
+ test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest):
206
+ NameError: uninitialized constant ActsAsYaffleTest::Wickwall
207
+ test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
471
208
 
472
- module ClassMethods
473
- def acts_as_yaffle(options = {})
474
- cattr_accessor :yaffle_text_field
475
- self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
476
- end
477
- end
478
- end
209
+ 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips
210
+ </shell>
479
211
 
480
- ActiveRecord::Base.send :include, Yaffle
481
- </ruby>
212
+ This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test.
213
+ We can easily generate these models in our "dummy" Rails application by running the following commands from the
214
+ test/dummy directory:
482
215
 
483
- h4. Add an Instance Method
216
+ <shell>
217
+ $ cd test/dummy
218
+ $ rails generate model Hickwall last_squak:string
219
+ $ rails generate model Wickwall last_squak:string last_tweet:string
220
+ </shell>
484
221
 
485
- This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk' method will simply set the value of one of the fields in the database.
222
+ Now you can create the necessary database tables in your testing database by navigating to your dummy app
223
+ and migrating the database. First
486
224
 
487
- To start out, write a failing test that shows the behavior you'd like:
225
+ <shell>
226
+ $ cd test/dummy
227
+ $ rake db:migrate
228
+ $ rake db:test:prepare
229
+ </shell>
488
230
 
489
- * *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb*
231
+ While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act
232
+ like yaffles.
490
233
 
491
234
  <ruby>
492
- require File.dirname(__FILE__) + '/test_helper'
235
+ # test/dummy/app/models/hickwall.rb
493
236
 
494
237
  class Hickwall < ActiveRecord::Base
495
238
  acts_as_yaffle
496
239
  end
497
240
 
241
+ # test/dummy/app/models/wickwall.rb
242
+
498
243
  class Wickwall < ActiveRecord::Base
499
244
  acts_as_yaffle :yaffle_text_field => :last_tweet
500
245
  end
501
246
 
502
- class ActsAsYaffleTest < Test::Unit::TestCase
503
- load_schema
504
-
505
- def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
506
- assert_equal "last_squawk", Hickwall.yaffle_text_field
507
- end
508
-
509
- def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
510
- assert_equal "last_tweet", Wickwall.yaffle_text_field
511
- end
512
-
513
- def test_hickwalls_squawk_should_populate_last_squawk
514
- hickwall = Hickwall.new
515
- hickwall.squawk("Hello World")
516
- assert_equal "squawk! Hello World", hickwall.last_squawk
517
- end
518
-
519
- def test_wickwalls_squawk_should_populate_last_tweeted_at
520
- wickwall = Wickwall.new
521
- wickwall.squawk("Hello World")
522
- assert_equal "squawk! Hello World", wickwall.last_tweet
523
- end
524
- end
525
247
  </ruby>
526
248
 
527
- Run this test to make sure the last two tests fail, then update 'acts_as_yaffle.rb' to look like this:
528
-
529
- * *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb*
249
+ We will also add code to define the acts_as_yaffle method.
530
250
 
531
251
  <ruby>
252
+ # yaffle/lib/yaffle/acts_as_yaffle.rb
532
253
  module Yaffle
533
- def self.included(base)
534
- base.send :extend, ClassMethods
535
- end
254
+ module ActsAsYaffle
255
+ extend ActiveSupport::Concern
536
256
 
537
- module ClassMethods
538
- def acts_as_yaffle(options = {})
539
- cattr_accessor :yaffle_text_field
540
- self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
541
- send :include, InstanceMethods
257
+ included do
542
258
  end
543
- end
544
259
 
545
- module InstanceMethods
546
- def squawk(string)
547
- write_attribute(self.class.yaffle_text_field, string.to_squawk)
260
+ module ClassMethods
261
+ def acts_as_yaffle(options = {})
262
+ # your code will go here
263
+ end
548
264
  end
549
265
  end
550
266
  end
551
267
 
552
- ActiveRecord::Base.send :include, Yaffle
268
+ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
553
269
  </ruby>
554
270
 
555
- 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 +send("#{self.class.yaffle_text_field}=", string.to_squawk)+.
271
+ You can then return to the root directory (+cd ../..+) of your plugin and rerun the tests using +rake+.
272
+
273
+ <shell>
274
+ 1) Error:
275
+ test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest):
276
+ NoMethodError: undefined method `yaffle_text_field' for #<Class:0x000001016661b8>
277
+ /Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing'
278
+ test/acts_as_yaffle_test.rb:5:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
556
279
 
557
- h3. Models
280
+ 2) Error:
281
+ test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest):
282
+ NoMethodError: undefined method `yaffle_text_field' for #<Class:0x00000101653748>
283
+ Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing'
284
+ test/acts_as_yaffle_test.rb:9:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
558
285
 
559
- This section describes how to add a model named 'Woodpecker' to your plugin that will behave the same as a model in your main app. When storing models, controllers, views and helpers in your plugin, it's customary to keep them in directories that match the rails directories. For this example, create a file structure like this:
286
+ 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips
560
287
 
561
- <shell>
562
- vendor/plugins/yaffle/
563
- |-- lib
564
- | |-- app
565
- | | |-- controllers
566
- | | |-- helpers
567
- | | |-- models
568
- | | | `-- woodpecker.rb
569
- | | `-- views
570
- | |-- yaffle
571
- | | |-- acts_as_yaffle.rb
572
- | | |-- commands.rb
573
- | | `-- core_ext.rb
574
- | `-- yaffle.rb
575
288
  </shell>
576
289
 
577
- As always, start with a test:
578
-
579
- * *vendor/plugins/yaffle/test/woodpecker_test.rb:*
290
+ Getting closer...now we will implement the code of the acts_as_yaffle method to make the tests pass.
580
291
 
581
292
  <ruby>
582
- require File.dirname(__FILE__) + '/test_helper'
293
+ # yaffle/lib/yaffle/acts_as_yaffle.rb
294
+
295
+ module Yaffle
296
+ module ActsAsYaffle
297
+ extend ActiveSupport::Concern
583
298
 
584
- class WoodpeckerTest < Test::Unit::TestCase
585
- load_schema
299
+ included do
300
+ end
586
301
 
587
- def test_woodpecker
588
- assert_kind_of Woodpecker, Woodpecker.new
302
+ module ClassMethods
303
+ def acts_as_yaffle(options = {})
304
+ cattr_accessor :yaffle_text_field
305
+ self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
306
+ end
307
+ end
589
308
  end
590
309
  end
591
- </ruby>
592
-
593
- This is just a simple test to make sure the class is being loaded correctly. After watching it fail with +rake+, you can make it pass like so:
594
310
 
595
- * *vendor/plugins/yaffle/lib/yaffle.rb:*
596
-
597
- <ruby>
598
- %w{ models }.each do |dir|
599
- path = File.join(File.dirname(__FILE__), 'app', dir)
600
- $LOAD_PATH << path
601
- ActiveSupport::Dependencies.load_paths << path
602
- ActiveSupport::Dependencies.load_once_paths.delete(path)
603
- end
311
+ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
604
312
  </ruby>
605
313
 
606
- Adding directories to the load path makes them appear just like files in the main app directory - except that they are only loaded once, so you have to restart the web server to see the changes in the browser. Removing directories from the 'load_once_paths' allow those changes to picked up as soon as you save the file - without having to restart the web server. This is particularly useful as you develop the plugin.
314
+ When you run +rake+ you should see the tests all pass:
607
315
 
608
- * *vendor/plugins/yaffle/lib/app/models/woodpecker.rb:*
316
+ <shell>
317
+ 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
318
+ </shell>
609
319
 
610
- <ruby>
611
- class Woodpecker < ActiveRecord::Base
612
- end
613
- </ruby>
320
+ h4. Add an Instance Method
614
321
 
615
- Finally, add the following to your plugin's 'schema.rb':
322
+ This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk'
323
+ method will simply set the value of one of the fields in the database.
616
324
 
617
- * *vendor/plugins/yaffle/test/schema.rb:*
325
+ To start out, write a failing test that shows the behavior you'd like:
618
326
 
619
327
  <ruby>
620
- create_table :woodpeckers, :force => true do |t|
621
- t.string :name
622
- end
623
- </ruby>
328
+ # yaffle/test/acts_as_yaffle_test.rb
329
+ require 'test_helper'
330
+
331
+ class ActsAsYaffleTest < Test::Unit::TestCase
624
332
 
625
- Now your test should be passing, and you should be able to use the Woodpecker model from within your rails application, and any changes made to it are reflected immediately when running in development mode.
333
+ def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
334
+ assert_equal "last_squawk", Hickwall.yaffle_text_field
335
+ end
626
336
 
627
- h3. Controllers
337
+ def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
338
+ assert_equal "last_tweet", Wickwall.yaffle_text_field
339
+ end
628
340
 
629
- This section describes how to add a controller named 'woodpeckers' to your plugin that will behave the same as a controller in your main app. This is very similar to adding a model.
341
+ def test_hickwalls_squawk_should_populate_last_squawk
342
+ hickwall = Hickwall.new
343
+ hickwall.squawk("Hello World")
344
+ assert_equal "squawk! Hello World", hickwall.last_squawk
345
+ end
630
346
 
631
- You can test your plugin's controller as you would test any other controller:
347
+ def test_wickwalls_squawk_should_populate_last_tweeted_at
348
+ wickwall = Wickwall.new
349
+ wickwall.squawk("Hello World")
350
+ assert_equal "squawk! Hello World", wickwall.last_tweet
351
+ end
352
+ end
353
+ </ruby>
632
354
 
633
- * *vendor/plugins/yaffle/test/woodpeckers_controller_test.rb:*
355
+ Run the test to make sure the last two tests fail the an error that contains "NoMethodError: undefined method `squawk'",
356
+ then update 'acts_as_yaffle.rb' to look like this:
634
357
 
635
358
  <ruby>
636
- require File.dirname(__FILE__) + '/test_helper'
637
- require 'woodpeckers_controller'
638
- require 'action_controller/test_process'
359
+ # yaffle/lib/yaffle/acts_as_yaffle.rb
360
+
361
+ module Yaffle
362
+ module ActsAsYaffle
363
+ extend ActiveSupport::Concern
639
364
 
640
- class WoodpeckersController; def rescue_action(e) raise e end; end
365
+ included do
366
+ end
641
367
 
642
- class WoodpeckersControllerTest < Test::Unit::TestCase
643
- def setup
644
- @controller = WoodpeckersController.new
645
- @request = ActionController::TestRequest.new
646
- @response = ActionController::TestResponse.new
368
+ module ClassMethods
369
+ def acts_as_yaffle(options = {})
370
+ cattr_accessor :yaffle_text_field
371
+ self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
372
+ end
373
+ end
647
374
 
648
- ActionController::Routing::Routes.draw do |map|
649
- map.resources :woodpeckers
375
+ def squawk(string)
376
+ write_attribute(self.class.yaffle_text_field, string.to_squawk)
650
377
  end
651
- end
652
378
 
653
- def test_index
654
- get :index
655
- assert_response :success
656
379
  end
657
380
  end
381
+
382
+ ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
658
383
  </ruby>
659
384
 
660
- This is just a simple test to make sure the controller is being loaded correctly. After watching it fail with +rake+, you can make it pass like so:
385
+ Run +rake+ one final time and you should see:
386
+ <shell>
387
+ 7 tests, 7 assertions, 0 failures, 0 errors, 0 skips
388
+ </shell>
661
389
 
662
- * *vendor/plugins/yaffle/lib/yaffle.rb:*
390
+ NOTE: The use of +write_attribute+ to write to the field in model is just one example of how a plugin can
391
+ interact with the model, and will not always be the right method to use. For example, you could also
392
+ use +send("#{self.class.yaffle_text_field}=", string.to_squawk)+.
663
393
 
664
- <ruby>
665
- %w{ models controllers }.each do |dir|
666
- path = File.join(File.dirname(__FILE__), 'app', dir)
667
- $LOAD_PATH << path
668
- ActiveSupport::Dependencies.load_paths << path
669
- ActiveSupport::Dependencies.load_once_paths.delete(path)
670
- end
671
- </ruby>
394
+ h3. Generators
672
395
 
673
- * *vendor/plugins/yaffle/lib/app/controllers/woodpeckers_controller.rb:*
396
+ Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about
397
+ the creation of generators can be found in the "Generators Guide":generators.html
674
398
 
675
- <ruby>
676
- class WoodpeckersController < ActionController::Base
399
+ h3. Publishing your Gem
677
400
 
678
- def index
679
- render :text => "Squawk!"
680
- end
401
+ Gem plugins in progress can be easily be shared from any Git repository. To share the Yaffle gem with others, simply
402
+ commit the code to a Git repository (like Github) and add a line to the Gemfile of the any application:
681
403
 
682
- end
404
+ <ruby>
405
+ gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git'
683
406
  </ruby>
684
407
 
685
- Now your test should be passing, and you should be able to use the Woodpeckers controller in your app. If you add a route for the woodpeckers controller you can start up your server and go to http://localhost:3000/woodpeckers to see your controller in action.
408
+ After running +bundle install+, your gem functionality will be available to the application.
686
409
 
687
- h3. Helpers
410
+ When the gem is ready to be shared as a formal release, it can be published to "RubyGems":http://www.rubygems.org.
411
+ For more information about publishing gems to RubyGems, see: "http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html":http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html
688
412
 
689
- This section describes how to add a helper named 'WoodpeckersHelper' to your plugin that will behave the same as a helper in your main app. This is very similar to adding a model and a controller.
413
+ h3. Non-Gem Plugins
690
414
 
691
- You can test your plugin's helper as you would test any other helper:
415
+ Non-gem plugins are useful for functionality that won't be shared with another project. Keeping your custom functionality in the
416
+ vendor/plugins directory un-clutters the rest of the application.
692
417
 
693
- * *vendor/plugins/yaffle/test/woodpeckers_helper_test.rb*
418
+ Move the directory that you created for the gem based plugin into the vendor/plugins directory of a generated Rails application, create a vendor/plugins/yaffle/init.rb file that contains "require 'yaffle'" and everything will still work.
694
419
 
695
420
  <ruby>
696
- require File.dirname(__FILE__) + '/test_helper'
697
- include WoodpeckersHelper
421
+ # yaffle/init.rb
698
422
 
699
- class WoodpeckersHelperTest < Test::Unit::TestCase
700
- def test_tweet
701
- assert_equal "Tweet! Hello", tweet("Hello")
702
- end
703
- end
423
+ require 'yaffle'
704
424
  </ruby>
705
425
 
706
- This is just a simple test to make sure the helper is being loaded correctly. After watching it fail with +rake+, you can make it pass like so:
707
-
708
- * *vendor/plugins/yaffle/lib/yaffle.rb:*
426
+ You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the
427
+ console we can check to see if the String has an instance method of to_squawk.
428
+ <shell>
429
+ $ cd my_app
430
+ $ rails console
431
+ $ String.instance_methods.sort
432
+ </shell>
709
433
 
710
- <ruby>
711
- %w{ models controllers helpers }.each do |dir|
712
- path = File.join(File.dirname(__FILE__), 'app', dir)
713
- $LOAD_PATH << path
714
- ActiveSupport::Dependencies.load_paths << path
715
- ActiveSupport::Dependencies.load_once_paths.delete(path)
716
- end
717
- </ruby>
434
+ You can also remove the .gemspec, Gemfile and Gemfile.lock files as they will no longer be needed.
718
435
 
719
- * *vendor/plugins/yaffle/lib/app/helpers/woodpeckers_helper.rb:*
436
+ h3. RDoc Documentation
720
437
 
721
- <ruby>
722
- module WoodpeckersHelper
438
+ 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.
723
439
 
724
- def tweet(text)
725
- "Tweet! #{text}"
726
- end
440
+ 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:
727
441
 
728
- end
729
- </ruby>
442
+ * Your name
443
+ * How to install
444
+ * How to add the functionality to the app (several examples of common use cases)
445
+ * Warning, gotchas or tips that might help save users time
730
446
 
731
- Now your test should be passing, and you should be able to use the Woodpeckers helper in your app.
447
+ 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 part of the public api.
732
448
 
733
- h3. Routes
449
+ Once your comments are good to go, navigate to your plugin directory and run:
734
450
 
735
- In a standard 'routes.rb' file you use routes like 'map.connect' or 'map.resources'. You can add your own custom routes from a plugin. This section will describe how to add a custom method called that can be called with 'map.yaffles'.
451
+ <shell>
452
+ $ rake rdoc
453
+ </shell>
736
454
 
737
- Testing routes from plugins is slightly different from testing routes in a standard rails application. To begin, add a test like this:
455
+ h4. References
738
456
 
739
- * *vendor/plugins/yaffle/test/routing_test.rb*
740
-
741
- <ruby>
742
- require "#{File.dirname(__FILE__)}/test_helper"
743
-
744
- class RoutingTest < Test::Unit::TestCase
745
-
746
- def setup
747
- ActionController::Routing::Routes.draw do |map|
748
- map.yaffles
749
- end
750
- end
751
-
752
- def test_yaffles_route
753
- assert_recognition :get, "/yaffles", :controller => "yaffles_controller", :action => "index"
754
- end
755
-
756
- private
757
-
758
- def assert_recognition(method, path, options)
759
- result = ActionController::Routing::Routes.recognize_path(path, :method => method)
760
- assert_equal options, result
761
- end
762
- end
763
- </ruby>
764
-
765
- Once you see the tests fail by running 'rake', you can make them pass with:
766
-
767
- * *vendor/plugins/yaffle/lib/yaffle.rb*
768
-
769
- <ruby>
770
- require "yaffle/routing"
771
- </ruby>
772
-
773
- * *vendor/plugins/yaffle/lib/yaffle/routing.rb*
774
-
775
- <ruby>
776
- module Yaffle #:nodoc:
777
- module Routing #:nodoc:
778
- module MapperExtensions
779
- def yaffles
780
- @set.add_route("/yaffles", {:controller => "yaffles_controller", :action => "index"})
781
- end
782
- end
783
- end
784
- end
785
-
786
- ActionController::Routing::RouteSet::Mapper.send :include, Yaffle::Routing::MapperExtensions
787
- </ruby>
788
-
789
- * *config/routes.rb*
790
-
791
- <ruby>
792
- ActionController::Routing::Routes.draw do |map|
793
- map.yaffles
794
- end
795
- </ruby>
796
-
797
- You can also see if your routes work by running +rake routes+ from your app directory.
798
-
799
- h3. Generators
800
-
801
- Many plugins ship with generators. When you created the plugin above, you specified the +--generator+ option, so you already have the generator stubs in 'vendor/plugins/yaffle/generators/yaffle'.
802
-
803
- Building generators is a complex topic unto itself and this section will cover one small aspect of generators: generating a simple text file.
804
-
805
- h4. Testing Generators
806
-
807
- Many rails plugin authors do not test their generators, however testing generators is quite simple. A typical generator test does the following:
808
-
809
- * Creates a new fake rails root directory that will serve as destination
810
- * Runs the generator
811
- * Asserts that the correct files were generated
812
- * Removes the fake rails root
813
-
814
- This section will describe how to create a simple generator that adds a file. For the generator in this section, the test could look something like this:
815
-
816
- * *vendor/plugins/yaffle/test/definition_generator_test.rb*
817
-
818
- <ruby>
819
- require File.dirname(__FILE__) + '/test_helper'
820
- require 'rails_generator'
821
- require 'rails_generator/scripts/generate'
822
-
823
- class DefinitionGeneratorTest < Test::Unit::TestCase
824
-
825
- def setup
826
- FileUtils.mkdir_p(fake_rails_root)
827
- @original_files = file_list
828
- end
829
-
830
- def teardown
831
- FileUtils.rm_r(fake_rails_root)
832
- end
833
-
834
- def test_generates_correct_file_name
835
- Rails::Generator::Scripts::Generate.new.run(["yaffle_definition"], :destination => fake_rails_root)
836
- new_file = (file_list - @original_files).first
837
- assert_equal "definition.txt", File.basename(new_file)
838
- end
839
-
840
- private
841
-
842
- def fake_rails_root
843
- File.join(File.dirname(__FILE__), 'rails_root')
844
- end
845
-
846
- def file_list
847
- Dir.glob(File.join(fake_rails_root, "*"))
848
- end
849
-
850
- end
851
- </ruby>
852
-
853
- You can run 'rake' from the plugin directory to see this fail. Unless you are doing more advanced generator commands it typically suffices to just test the Generate script, and trust that rails will handle the Destroy and Update commands for you.
854
-
855
- To make it pass, create the generator:
856
-
857
- * *vendor/plugins/yaffle/generators/yaffle_definition/yaffle_definition_generator.rb*
858
-
859
- <ruby>
860
- class YaffleDefinitionGenerator < Rails::Generator::Base
861
- def manifest
862
- record do |m|
863
- m.file "definition.txt", "definition.txt"
864
- end
865
- end
866
- end
867
- </ruby>
868
-
869
- h4. The +USAGE+ File
870
-
871
- If you plan to distribute your plugin, developers will expect at least a minimum of documentation. You can add simple documentation to the generator by updating the USAGE file.
872
-
873
- Rails ships with several built-in generators. You can see all of the generators available to you by typing the following at the command line:
874
-
875
- <shell>
876
- rails generate
877
- </shell>
878
-
879
- You should see something like this:
880
-
881
- <shell>
882
- Installed Generators
883
- Plugins (vendor/plugins): yaffle_definition
884
- Builtin: controller, integration_test, mailer, migration, model, observer, plugin, resource, scaffold, session_migration
885
- </shell>
886
-
887
- When you run +rails generate yaffle_definition -h+ you should see the contents of your 'vendor/plugins/yaffle/generators/yaffle_definition/USAGE'.
888
-
889
- For this plugin, update the USAGE file could look like this:
890
-
891
- <shell>
892
- Description:
893
- Adds a file with the definition of a Yaffle to the app's main directory
894
- </shell>
895
-
896
- h3. Add a Custom Generator Command
897
-
898
- You may have noticed above that you can used one of the built-in rails migration commands +migration_template+. If your plugin needs to add and remove lines of text from existing files you will need to write your own generator methods.
899
-
900
- This section describes how you you can create your own commands to add and remove a line of text from 'routes.rb'. This example creates a very simple method that adds or removes a text file.
901
-
902
- To start, add the following test method:
903
-
904
- * *vendor/plugins/yaffle/test/generator_test.rb*
905
-
906
- <ruby>
907
- def test_generates_definition
908
- Rails::Generator::Scripts::Generate.new.run(["yaffle", "bird"], :destination => fake_rails_root)
909
- definition = File.read(File.join(fake_rails_root, "definition.txt"))
910
- assert_match /Yaffle\:/, definition
911
- end
912
- </ruby>
913
-
914
- Run +rake+ to watch the test fail, then make the test pass add the following:
915
-
916
- * *vendor/plugins/yaffle/generators/yaffle/templates/definition.txt*
917
-
918
- <shell>
919
- Yaffle: A bird
920
- </shell>
921
-
922
- * *vendor/plugins/yaffle/lib/yaffle.rb*
923
-
924
- <ruby>
925
- require "yaffle/commands"
926
- </ruby>
927
-
928
- * *vendor/plugins/yaffle/lib/commands.rb*
929
-
930
- <ruby>
931
- require 'rails_generator'
932
- require 'rails_generator/commands'
933
-
934
- module Yaffle #:nodoc:
935
- module Generator #:nodoc:
936
- module Commands #:nodoc:
937
- module Create
938
- def yaffle_definition
939
- file("definition.txt", "definition.txt")
940
- end
941
- end
942
-
943
- module Destroy
944
- def yaffle_definition
945
- file("definition.txt", "definition.txt")
946
- end
947
- end
948
-
949
- module List
950
- def yaffle_definition
951
- file("definition.txt", "definition.txt")
952
- end
953
- end
954
-
955
- module Update
956
- def yaffle_definition
957
- file("definition.txt", "definition.txt")
958
- end
959
- end
960
- end
961
- end
962
- end
963
-
964
- Rails::Generator::Commands::Create.send :include, Yaffle::Generator::Commands::Create
965
- Rails::Generator::Commands::Destroy.send :include, Yaffle::Generator::Commands::Destroy
966
- Rails::Generator::Commands::List.send :include, Yaffle::Generator::Commands::List
967
- Rails::Generator::Commands::Update.send :include, Yaffle::Generator::Commands::Update
968
- </ruby>
969
-
970
- Finally, call your new method in the manifest:
971
-
972
- * *vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb*
973
-
974
- <ruby>
975
- class YaffleGenerator < Rails::Generator::NamedBase
976
- def manifest
977
- m.yaffle_definition
978
- end
979
- end
980
- </ruby>
981
-
982
- h3. Generator Commands
983
-
984
- You may have noticed above that you can used one of the built-in rails migration commands +migration_template+. If your plugin needs to add and remove lines of text from existing files you will need to write your own generator methods.
985
-
986
- This section describes how you you can create your own commands to add and remove a line of text from 'config/routes.rb'.
987
-
988
- To start, add the following test method:
989
-
990
- * *vendor/plugins/yaffle/test/route_generator_test.rb*
991
-
992
- <ruby>
993
- require File.dirname(__FILE__) + '/test_helper'
994
- require 'rails_generator'
995
- require 'rails_generator/scripts/generate'
996
- require 'rails_generator/scripts/destroy'
997
-
998
- class RouteGeneratorTest < Test::Unit::TestCase
999
-
1000
- def setup
1001
- FileUtils.mkdir_p(File.join(fake_rails_root, "config"))
1002
- end
1003
-
1004
- def teardown
1005
- FileUtils.rm_r(fake_rails_root)
1006
- end
1007
-
1008
- def test_generates_route
1009
- content = <<-END
1010
- ActionController::Routing::Routes.draw do |map|
1011
- map.connect ':controller/:action/:id'
1012
- map.connect ':controller/:action/:id.:format'
1013
- end
1014
- END
1015
- File.open(routes_path, 'wb') {|f| f.write(content) }
1016
-
1017
- Rails::Generator::Scripts::Generate.new.run(["yaffle_route"], :destination => fake_rails_root)
1018
- assert_match /map\.yaffles/, File.read(routes_path)
1019
- end
1020
-
1021
- def test_destroys_route
1022
- content = <<-END
1023
- ActionController::Routing::Routes.draw do |map|
1024
- map.yaffles
1025
- map.connect ':controller/:action/:id'
1026
- map.connect ':controller/:action/:id.:format'
1027
- end
1028
- END
1029
- File.open(routes_path, 'wb') {|f| f.write(content) }
1030
-
1031
- Rails::Generator::Scripts::Destroy.new.run(["yaffle_route"], :destination => fake_rails_root)
1032
- assert_no_match /map\.yaffles/, File.read(routes_path)
1033
- end
1034
-
1035
- private
1036
-
1037
- def fake_rails_root
1038
- File.join(File.dirname(__FILE__), "rails_root")
1039
- end
1040
-
1041
- def routes_path
1042
- File.join(fake_rails_root, "config", "routes.rb")
1043
- end
1044
-
1045
- end
1046
- </ruby>
1047
-
1048
- Run +rake+ to watch the test fail, then make the test pass add the following:
1049
-
1050
- * *vendor/plugins/yaffle/lib/yaffle.rb*
1051
-
1052
- <ruby>
1053
- require "yaffle/commands"
1054
- </ruby>
1055
-
1056
- * *vendor/plugins/yaffle/lib/yaffle/commands.rb*
1057
-
1058
- <ruby>
1059
- require 'rails_generator'
1060
- require 'rails_generator/commands'
1061
-
1062
- module Yaffle #:nodoc:
1063
- module Generator #:nodoc:
1064
- module Commands #:nodoc:
1065
- module Create
1066
- def yaffle_route
1067
- logger.route "map.yaffle"
1068
- look_for = 'ActionController::Routing::Routes.draw do |map|'
1069
- unless options[:pretend]
1070
- gsub_file('config/routes.rb', /(#{Regexp.escape(look_for)})/mi){|match| "#{match}\n map.yaffles\n"}
1071
- end
1072
- end
1073
- end
1074
-
1075
- module Destroy
1076
- def yaffle_route
1077
- logger.route "map.yaffle"
1078
- gsub_file 'config/routes.rb', /\n.+?map\.yaffles/mi, ''
1079
- end
1080
- end
1081
-
1082
- module List
1083
- def yaffle_route
1084
- end
1085
- end
1086
-
1087
- module Update
1088
- def yaffle_route
1089
- end
1090
- end
1091
- end
1092
- end
1093
- end
1094
-
1095
- Rails::Generator::Commands::Create.send :include, Yaffle::Generator::Commands::Create
1096
- Rails::Generator::Commands::Destroy.send :include, Yaffle::Generator::Commands::Destroy
1097
- Rails::Generator::Commands::List.send :include, Yaffle::Generator::Commands::List
1098
- Rails::Generator::Commands::Update.send :include, Yaffle::Generator::Commands::Update
1099
- </ruby>
1100
-
1101
- * *vendor/plugins/yaffle/generators/yaffle_route/yaffle_route_generator.rb*
1102
-
1103
- <ruby>
1104
- class YaffleRouteGenerator < Rails::Generator::Base
1105
- def manifest
1106
- record do |m|
1107
- m.yaffle_route
1108
- end
1109
- end
1110
- end
1111
- </ruby>
1112
-
1113
- To see this work, type:
1114
-
1115
- <shell>
1116
- rails generate yaffle_route
1117
- rails destroy yaffle_route
1118
- </shell>
1119
-
1120
- NOTE: If you haven't set up the custom route from above, 'rails destroy' will fail and you'll have to remove it manually.
1121
-
1122
- h3. Migrations
1123
-
1124
- If your plugin requires changes to the app's database you will likely want to somehow add migrations. Rails does not include any built-in support for calling migrations from plugins, but you can still make it easy for developers to call migrations from plugins.
1125
-
1126
- If you have a very simple needs, like creating a table that will always have the same name and columns, then you can use a more simple solution, like creating a custom rake task or method. If your migration needs user input to supply table names or other options, you probably want to opt for generating a migration.
1127
-
1128
- Let's say you have the following migration in your plugin:
1129
-
1130
- * *vendor/plugins/yaffle/lib/db/migrate/20081116181115_create_birdhouses.rb:*
1131
-
1132
- <ruby>
1133
- class CreateBirdhouses < ActiveRecord::Migration
1134
- def self.up
1135
- create_table :birdhouses, :force => true do |t|
1136
- t.string :name
1137
- t.timestamps
1138
- end
1139
- end
1140
-
1141
- def self.down
1142
- drop_table :birdhouses
1143
- end
1144
- end
1145
- </ruby>
1146
-
1147
- Here are a few possibilities for how to allow developers to use your plugin migrations:
1148
-
1149
- h4. Create a Custom Rake Task
1150
-
1151
- * *vendor/plugins/yaffle/tasks/yaffle_tasks.rake:*
1152
-
1153
- <ruby>
1154
- namespace :db do
1155
- namespace :migrate do
1156
- description = "Migrate the database through scripts in vendor/plugins/yaffle/lib/db/migrate"
1157
- description << "and update db/schema.rb by invoking db:schema:dump."
1158
- description << "Target specific version with VERSION=x. Turn off output with VERBOSE=false."
1159
-
1160
- desc description
1161
- task :yaffle => :environment do
1162
- ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
1163
- ActiveRecord::Migrator.migrate("vendor/plugins/yaffle/lib/db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
1164
- Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
1165
- end
1166
- end
1167
- end
1168
- </ruby>
1169
-
1170
- h4. Call Migrations Directly
1171
-
1172
- * *vendor/plugins/yaffle/lib/yaffle.rb:*
1173
-
1174
- <ruby>
1175
- Dir.glob(File.join(File.dirname(__FILE__), "db", "migrate", "*")).each do |file|
1176
- require file
1177
- end
1178
- </ruby>
1179
-
1180
- * *db/migrate/20081116181115_create_birdhouses.rb:*
1181
-
1182
- <ruby>
1183
- class CreateBirdhouses < ActiveRecord::Migration
1184
- def self.up
1185
- Yaffle::CreateBirdhouses.up
1186
- end
1187
-
1188
- def self.down
1189
- Yaffle::CreateBirdhouses.down
1190
- end
1191
- end
1192
- </ruby>
1193
-
1194
- NOTE: several plugin frameworks such as Desert and Engines provide more advanced plugin functionality.
1195
-
1196
- h4. Generate Migrations
1197
-
1198
- Generating migrations has several advantages over other methods. Namely, you can allow other developers to more easily customize the migration. The flow looks like this:
1199
-
1200
- * call your rails generate script and pass in whatever options they need
1201
- * examine the generated migration, adding/removing columns or other options as necessary
1202
-
1203
- This example will demonstrate how to use one of the built-in generator methods named 'migration_template' to create a migration file. Extending the rails migration generator requires a somewhat intimate knowledge of the migration generator internals, so it's best to write a test first:
1204
-
1205
- * *vendor/plugins/yaffle/test/yaffle_migration_generator_test.rb*
1206
-
1207
- <ruby>
1208
- require File.dirname(__FILE__) + '/test_helper'
1209
- require 'rails_generator'
1210
- require 'rails_generator/scripts/generate'
1211
-
1212
- class MigrationGeneratorTest < Test::Unit::TestCase
1213
-
1214
- def setup
1215
- FileUtils.mkdir_p(fake_rails_root)
1216
- @original_files = file_list
1217
- end
1218
-
1219
- def teardown
1220
- ActiveRecord::Base.pluralize_table_names = true
1221
- FileUtils.rm_r(fake_rails_root)
1222
- end
1223
-
1224
- def test_generates_correct_file_name
1225
- Rails::Generator::Scripts::Generate.new.run(["yaffle_migration", "some_name_nobody_is_likely_to_ever_use_in_a_real_migration"],
1226
- :destination => fake_rails_root)
1227
- new_file = (file_list - @original_files).first
1228
- assert_match /add_yaffle_fields_to_some_name_nobody_is_likely_to_ever_use_in_a_real_migrations/, new_file
1229
- assert_match /add_column :some_name_nobody_is_likely_to_ever_use_in_a_real_migrations do |t|/, File.read(new_file)
1230
- end
1231
-
1232
- def test_pluralizes_properly
1233
- ActiveRecord::Base.pluralize_table_names = false
1234
- Rails::Generator::Scripts::Generate.new.run(["yaffle_migration", "some_name_nobody_is_likely_to_ever_use_in_a_real_migration"],
1235
- :destination => fake_rails_root)
1236
- new_file = (file_list - @original_files).first
1237
- assert_match /add_yaffle_fields_to_some_name_nobody_is_likely_to_ever_use_in_a_real_migration/, new_file
1238
- assert_match /add_column :some_name_nobody_is_likely_to_ever_use_in_a_real_migration do |t|/, File.read(new_file)
1239
- end
1240
-
1241
- private
1242
- def fake_rails_root
1243
- File.join(File.dirname(__FILE__), 'rails_root')
1244
- end
1245
-
1246
- def file_list
1247
- Dir.glob(File.join(fake_rails_root, "db", "migrate", "*"))
1248
- end
1249
-
1250
- end
1251
- </ruby>
1252
-
1253
- NOTE: the migration generator checks to see if a migation already exists, and it's hard-coded to check the 'db/migrate' directory. As a result, if your test tries to generate a migration that already exists in the app, it will fail. The easy workaround is to make sure that the name you generate in your test is very unlikely to actually appear in the app.
1254
-
1255
- After running the test with 'rake' you can make it pass with:
1256
-
1257
- * *vendor/plugins/yaffle/generators/yaffle_migration/yaffle_migration_generator.rb*
1258
-
1259
- <ruby>
1260
- class YaffleMigrationGenerator < Rails::Generator::NamedBase
1261
- def manifest
1262
- record do |m|
1263
- m.migration_template 'migration:migration.rb', "db/migrate", {:assigns => yaffle_local_assigns,
1264
- :migration_file_name => "add_yaffle_fields_to_#{custom_file_name}"
1265
- }
1266
- end
1267
- end
1268
-
1269
- private
1270
- def custom_file_name
1271
- custom_name = class_name.underscore.downcase
1272
- custom_name = custom_name.pluralize if ActiveRecord::Base.pluralize_table_names
1273
- custom_name
1274
- end
1275
-
1276
- def yaffle_local_assigns
1277
- {}.tap do |assigns|
1278
- assigns[:migration_action] = "add"
1279
- assigns[:class_name] = "add_yaffle_fields_to_#{custom_file_name}"
1280
- assigns[:table_name] = custom_file_name
1281
- assigns[:attributes] = [Rails::Generator::GeneratedAttribute.new("last_squawk", "string")]
1282
- end
1283
- end
1284
- end
1285
- </ruby>
1286
-
1287
- The generator creates a new file in 'db/migrate' with a timestamp and an 'add_column' statement. It reuses the built-in rails +migration_template+ method, and reuses the built-in rails migration template.
1288
-
1289
- It's courteous to check to see if table names are being pluralized whenever you create a generator that needs to be aware of table names. This way people using your generator won't have to manually change the generated files if they've turned pluralization off.
1290
-
1291
- To run the generator, type the following at the command line:
1292
-
1293
- <shell>
1294
- rails generate yaffle_migration bird
1295
- </shell>
1296
-
1297
- and you will see a new file:
1298
-
1299
- * *db/migrate/20080529225649_add_yaffle_fields_to_birds.rb*
1300
-
1301
- <ruby>
1302
- class AddYaffleFieldsToBirds < ActiveRecord::Migration
1303
- def self.up
1304
- add_column :birds, :last_squawk, :string
1305
- end
1306
-
1307
- def self.down
1308
- remove_column :birds, :last_squawk
1309
- end
1310
- end
1311
- </ruby>
1312
-
1313
- h3. Rake tasks
1314
-
1315
- When you created the plugin with the built-in rails generator, it generated a rake file for you in 'vendor/plugins/yaffle/tasks/yaffle_tasks.rake'. Any rake task you add here will be available to the app.
1316
-
1317
- Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so:
1318
-
1319
- * *vendor/plugins/yaffle/tasks/yaffle_tasks.rake*
1320
-
1321
- <ruby>
1322
- namespace :yaffle do
1323
- desc "Prints out the word 'Yaffle'"
1324
- task :squawk => :environment do
1325
- puts "squawk!"
1326
- end
1327
- end
1328
- </ruby>
1329
-
1330
- When you run +rake -T+ from your plugin you will see:
1331
-
1332
- <shell>
1333
- yaffle:squawk # Prints out the word 'Yaffle'
1334
- </shell>
1335
-
1336
- You can add as many files as you want in the tasks directory, and if they end in .rake Rails will pick them up.
1337
-
1338
- Note that tasks from +vendor/plugins/yaffle/Rakefile+ are not available to the main app.
1339
-
1340
- h3. Plugins as Gems
1341
-
1342
- Turning your rails plugin into a gem is a simple and straightforward task. This section will cover how to turn your plugin into a gem. It will not cover how to distribute that gem.
1343
-
1344
- The initialization file has to be called +rails/init.rb+, the root +init.rb+ file, if any, is ignored by Rails. Also, the name of the plugin now is relevant since +config.gem+ tries to load it. Either name the main file after your gem, or document that users should use the +:lib+ option.
1345
-
1346
- It's common practice to put any developer-centric rake tasks (such as tests, rdoc and gem package tasks) in +Rakefile+. A rake task that packages the gem might look like this:
1347
-
1348
- * *vendor/plugins/yaffle/Rakefile:*
1349
-
1350
- <ruby>
1351
- PKG_FILES = FileList[
1352
- '[a-zA-Z]*',
1353
- 'generators/**/*',
1354
- 'lib/**/*',
1355
- 'rails/**/*',
1356
- 'tasks/**/*',
1357
- 'test/**/*'
1358
- ]
1359
-
1360
- spec = Gem::Specification.new do |s|
1361
- s.name = "yaffle"
1362
- s.version = "0.0.1"
1363
- s.author = "Gleeful Yaffler"
1364
- s.email = "yaffle@example.com"
1365
- s.homepage = "http://yafflers.example.com/"
1366
- s.platform = Gem::Platform::RUBY
1367
- s.summary = "Sharing Yaffle Goodness"
1368
- s.files = PKG_FILES.to_a
1369
- s.require_path = "lib"
1370
- s.has_rdoc = false
1371
- s.extra_rdoc_files = ["README"]
1372
- end
1373
-
1374
- desc 'Turn this plugin into a gem.'
1375
- Rake::GemPackageTask.new(spec) do |pkg|
1376
- pkg.gem_spec = spec
1377
- end
1378
- </ruby>
1379
-
1380
- To build and install the gem locally, run the following commands:
1381
-
1382
- <shell>
1383
- cd vendor/plugins/yaffle
1384
- rake gem
1385
- sudo gem install pkg/yaffle-0.0.1.gem
1386
- </shell>
1387
-
1388
- To test this, create a new rails application, add +config.gem "yaffle"+ to +config/environment.rb+ and all of your plugin's functionality will be available to you.
1389
-
1390
- h3. RDoc Documentation
1391
-
1392
- 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.
1393
-
1394
- 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:
1395
-
1396
- * Your name
1397
- * How to install
1398
- * How to add the functionality to the app (several examples of common use cases)
1399
- * Warning, gotchas or tips that might help save users time
1400
-
1401
- 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 part of the public api.
1402
-
1403
- Once your comments are good to go, navigate to your plugin directory and run:
1404
-
1405
- <shell>
1406
- rake rdoc
1407
- </shell>
1408
-
1409
- h3. Appendix
1410
-
1411
- If you prefer to use RSpec instead of Test::Unit, you may be interested in the "RSpec Plugin Generator":http://github.com/patmaddox/rspec-plugin-generator.
1412
-
1413
- h4. References
1414
-
1415
- * http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-i
1416
- * http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-ii
1417
- * http://github.com/technoweenie/attachment_fu/tree/master
1418
- * http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html
1419
- * http://www.mbleigh.com/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins
1420
- * http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2.
1421
-
1422
- h4. Contents of +lib/yaffle.rb+
1423
-
1424
- * *vendor/plugins/yaffle/lib/yaffle.rb:*
1425
-
1426
- <ruby>
1427
- require "yaffle/core_ext"
1428
- require "yaffle/acts_as_yaffle"
1429
- require "yaffle/commands"
1430
- require "yaffle/routing"
1431
-
1432
- %w{ models controllers helpers }.each do |dir|
1433
- path = File.join(File.dirname(__FILE__), 'app', dir)
1434
- $LOAD_PATH << path
1435
- ActiveSupport::Dependencies.load_paths << path
1436
- ActiveSupport::Dependencies.load_once_paths.delete(path)
1437
- end
1438
-
1439
- # optionally:
1440
- # Dir.glob(File.join(File.dirname(__FILE__), "db", "migrate", "*")).each do |file|
1441
- # require file
1442
- # end
1443
- </ruby>
1444
-
1445
- h4. Final Plugin Directory Structure
1446
-
1447
- The final plugin should have a directory structure that looks something like this:
1448
-
1449
- <shell>
1450
- |-- MIT-LICENSE
1451
- |-- README
1452
- |-- Rakefile
1453
- |-- generators
1454
- | |-- yaffle_definition
1455
- | | |-- USAGE
1456
- | | |-- templates
1457
- | | | `-- definition.txt
1458
- | | `-- yaffle_definition_generator.rb
1459
- | |-- yaffle_migration
1460
- | | |-- USAGE
1461
- | | |-- templates
1462
- | | `-- yaffle_migration_generator.rb
1463
- | `-- yaffle_route
1464
- | |-- USAGE
1465
- | |-- templates
1466
- | `-- yaffle_route_generator.rb
1467
- |-- install.rb
1468
- |-- lib
1469
- | |-- app
1470
- | | |-- controllers
1471
- | | | `-- woodpeckers_controller.rb
1472
- | | |-- helpers
1473
- | | | `-- woodpeckers_helper.rb
1474
- | | `-- models
1475
- | | `-- woodpecker.rb
1476
- | |-- db
1477
- | | `-- migrate
1478
- | | `-- 20081116181115_create_birdhouses.rb
1479
- | |-- yaffle
1480
- | | |-- acts_as_yaffle.rb
1481
- | | |-- commands.rb
1482
- | | |-- core_ext.rb
1483
- | | `-- routing.rb
1484
- | `-- yaffle.rb
1485
- |-- pkg
1486
- | `-- yaffle-0.0.1.gem
1487
- |-- rails
1488
- | `-- init.rb
1489
- |-- tasks
1490
- | `-- yaffle_tasks.rake
1491
- |-- test
1492
- | |-- acts_as_yaffle_test.rb
1493
- | |-- core_ext_test.rb
1494
- | |-- database.yml
1495
- | |-- debug.log
1496
- | |-- definition_generator_test.rb
1497
- | |-- migration_generator_test.rb
1498
- | |-- route_generator_test.rb
1499
- | |-- routes_test.rb
1500
- | |-- schema.rb
1501
- | |-- test_helper.rb
1502
- | |-- woodpecker_test.rb
1503
- | |-- woodpeckers_controller_test.rb
1504
- | |-- wookpeckers_helper_test.rb
1505
- | |-- yaffle_plugin.sqlite3.db
1506
- | `-- yaffle_test.rb
1507
- `-- uninstall.rb
1508
- </shell>
457
+ * "Developing a RubyGem using Bundler":https://github.com/radar/guides/blob/master/gem-development.md
458
+ * "Using Gemspecs As Intended":http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/
459
+ * "Gemspec Reference":http://docs.rubygems.org/read/chapter/20
460
+ * "GemPlugins":http://www.mbleigh.com/2008/06/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins
461
+ * "Keeping init.rb thin":http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html
1509
462
 
1510
463
  h3. Changelog
1511
464
 
465
+ * March 10, 2011: Minor formatting tweaks.
466
+ * February 13, 2011: Get guide in synch with Rails 3.0.3. Remove information not compatible with Rails 3. Send reader elsewhere
467
+ for information that is covered elsewhere.
1512
468
  * April 4, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
1513
469
  * November 17, 2008: Major revision by Jeff Dean