From 742f5f4a5d0deec132d2bfed53706e05d51871cb Mon Sep 17 00:00:00 2001 From: Henry Phan Date: Sat, 5 Feb 2011 13:09:35 -0800 Subject: Add CSS fix for WebKit/Mac rendering issue. --- railties/guides/assets/stylesheets/fixes.css | 16 ++++++++++++++++ railties/guides/source/layout.html.erb | 2 ++ 2 files changed, 18 insertions(+) create mode 100644 railties/guides/assets/stylesheets/fixes.css diff --git a/railties/guides/assets/stylesheets/fixes.css b/railties/guides/assets/stylesheets/fixes.css new file mode 100644 index 0000000000..54efa5b9b7 --- /dev/null +++ b/railties/guides/assets/stylesheets/fixes.css @@ -0,0 +1,16 @@ +/* + Fix a rendering issue affecting WebKits on Mac. + See https://github.com/lifo/docrails/issues#issue/16 for more information. +*/ +.syntaxhighlighter a, +.syntaxhighlighter div, +.syntaxhighlighter code, +.syntaxhighlighter table, +.syntaxhighlighter table td, +.syntaxhighlighter table tr, +.syntaxhighlighter table tbody, +.syntaxhighlighter table thead, +.syntaxhighlighter table caption, +.syntaxhighlighter textarea { + line-height: 1.2em !important; +} diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index 91dab18b21..e924f2f6c0 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -12,6 +12,8 @@ + + <% if @edge %> -- cgit v1.2.3 From cc2b2719d87a7c3a81fe810211ab39755aec1a8f Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Mon, 7 Feb 2011 01:34:12 +0530 Subject: fix minor typo --- railties/guides/source/getting_started.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 82700532c0..52e398a497 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -411,7 +411,7 @@ Rails will execute this migration command and tell you it created the Posts tabl == CreatePosts: migrated (0.0020s) =========================================== -NOTE. Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. If you would like to execute migrations in other environment, for instance in production, you must explicitely pass it when invoking the command: rake db:migrate RAILS_ENV=production. +NOTE. Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. If you would like to execute migrations in other environment, for instance in production, you must explicitly pass it when invoking the command: rake db:migrate RAILS_ENV=production. h4. Adding a Link -- cgit v1.2.3 From 58dccf305a63e5a77695206453a669bb3fffa17f Mon Sep 17 00:00:00 2001 From: Paul Annesley Date: Sun, 6 Feb 2011 16:04:29 -0800 Subject: ActiveRecord :default_timezone is :utc in Rails, as set by active_record/railtie.rb --- railties/guides/source/configuring.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index eee18f1131..62b846e871 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -218,7 +218,7 @@ h4. Configuring Active Record * +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. -* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:local+. +* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:utc+ for Rails, although ActiveRecord defaults to +:local+ when used outside of Rails. * +config.active_record.schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. -- cgit v1.2.3 From 77582075a66ca6fcd6959e29b4d6625021e67edf Mon Sep 17 00:00:00 2001 From: Kevin Skoglund Date: Mon, 7 Feb 2011 09:25:22 -0500 Subject: Add note that validates_length_of will incorrectly pluralize min. length 1 --- railties/guides/source/active_record_validations_callbacks.textile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index a15571fe58..6f857ab9cc 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -314,6 +314,8 @@ class Essay < ActiveRecord::Base end +Note that the default error messages are plural (e.g., "is too short (minimum is %{count} characters)"). For this reason, when +:minimum+ is 1 you should provide a personalized message or use +validates_presence_of+ instead. When +:in+ or +:within+ have a lower limit of 1, you should either provide a personalized message or call +validates_presence_of+ prior to +validates_length_of+. + The +validates_size_of+ helper is an alias for +validates_length_of+. h4. +validates_numericality_of+ -- cgit v1.2.3 From 435bf4ca4bb4193b18589289ca2cff07dbd3771a Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 9 Feb 2011 03:18:12 +0530 Subject: minor fixes in generators --- railties/guides/source/command_line.textile | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index 1e570c9992..255b9ab190 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -81,7 +81,7 @@ The +rails generate+ command uses templates to create a whole lot of things. You $ rails generate -Usage: rails generate generator [options] [args] +Usage: rails generate generator [args] [options] ... ... @@ -105,7 +105,7 @@ INFO: All Rails console utilities have help text. As with most *nix utilities, y $ rails generate controller -Usage: rails generate controller ControllerName [options] +Usage: rails generate controller NAME [action action] [options] ... ... @@ -122,7 +122,7 @@ Example: Modules Example: rails generate controller 'admin/credit_card' suspend late_fee - Credit card admin controller with URLs /admin/credit_card/suspend. + Credit card admin controller with URLs like /admin/credit_card/suspend. Controller: app/controllers/admin/credit_card_controller.rb Views: app/views/admin/credit_card/debit.html.erb [...] Helper: app/helpers/admin/credit_card_helper.rb @@ -138,10 +138,13 @@ $ rails generate controller Greetings hello invoke erb create app/views/greetings create app/views/greetings/hello.html.erb - error rspec [not found] + invoke test_unit + create test/functional/greetings_controller_test.rb invoke helper create app/helpers/greetings_helper.rb - error rspec [not found] + invoke test_unit + create test/unit/helpers/greetings_helper_test.rb + What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a functional test file, a helper for the view, and a view file. @@ -153,7 +156,6 @@ class GreetingsController < ApplicationController def hello @message = "Hello, how are you today?" end - end @@ -164,7 +166,7 @@ Then the view, to display our message (in +app/views/greetings/hello.html.erb+):

<%= @message %>

-Deal. Go check it out in your browser. Fire up your server. Remember? +rails server+ at the root of your Rails application should do it. +Deal. Go check it out in your browser. Fire up your server using +rails server+. $ rails server @@ -181,7 +183,7 @@ Rails comes with a generator for data models too: $ rails generate model -Usage: rails generate model ModelName [field:type, field:type] +Usage: rails generate model NAME [field:type field:type] [options] ... @@ -223,7 +225,7 @@ $ rails generate scaffold HighScore game:string score:integer create app/controllers/high_scores_controller.rb create test/functional/high_scores_controller_test.rb create app/helpers/high_scores_helper.rb - route map.resources :high_scores + route resources :high_scores dependency model exists app/models/ exists test/unit/ @@ -284,7 +286,7 @@ Let's say you're creating a website for a client who wants a small accounting sy There is such a thing! The plugin we're installing is called +acts_as_paranoid+, and it lets models implement a +deleted_at+ column that gets set when you call destroy. Later, when calling find, the plugin will tack on a database check to filter out "deleted" things. -$ rails plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_paranoid +$ rails plugin install https://github.com/technoweenie/acts_as_paranoid.git + ./CHANGELOG + ./MIT-LICENSE ... -- cgit v1.2.3 From 04eaacca967830b41a655ce8bb3c816d6e5ff056 Mon Sep 17 00:00:00 2001 From: John Paul Ashenfelter Date: Wed, 9 Feb 2011 11:23:42 -0500 Subject: replaced MODEL.human_name with MODEL.model_name.human now that .human_name is deprecated --- railties/guides/source/i18n.textile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index e47ac7aed6..337ef52d3b 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -1,4 +1,4 @@ -h2. Rails Internationalization (I18n) API +lh2. Rails Internationalization (I18n) API The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for *translating your application to a single custom language* other than English or for *providing multi-language support* in your application. @@ -649,7 +649,7 @@ Generally we recommend using YAML as a format for storing translations. There ar h4. Translations for Active Record Models -You can use the methods +Model.human_name+ and +Model.human_attribute_name(attribute)+ to transparently look up translations for your model and attribute names. +You can use the methods +Model.model_name.human+ and +Model.human_attribute_name(attribute)+ to transparently look up translations for your model and attribute names. For example when you add the following translations: @@ -664,7 +664,7 @@ en: # will translate User attribute "login" as "Handle" -Then +User.human_name+ will return "Dude" and +User.human_attribute_name("login")+ will return "Handle". +Then +User.model_name.human+ will return "Dude" and +User.human_attribute_name("login")+ will return "Handle". h5. Error Message Scopes @@ -786,9 +786,9 @@ h5. Action View Helper Methods h5. Active Record Methods -* +human_name+ and +human_attribute_name+ use translations for model names and attribute names if available in the "activerecord.models":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L29 scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". +* +model_name.human+ and +human_attribute_name+ use translations for model names and attribute names if available in the "activerecord.models":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L29 scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". -* +ActiveRecord::Errors#generate_message+ (which is used by Active Record validations but may also be used manually) uses +human_name+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". +* +ActiveRecord::Errors#generate_message+ (which is used by Active Record validations but may also be used manually) uses +model_name.human+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". *+ ActiveRecord::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "activerecord.errors.format.separator":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and which defaults to +' '+). -- cgit v1.2.3 From b3dad5deb9c4961031a91398bff3e6dcdf8682c9 Mon Sep 17 00:00:00 2001 From: Gabriel Horner Date: Thu, 10 Feb 2011 00:16:06 -0500 Subject: add some docs for ActionController::Renderers --- .../lib/action_controller/metal/renderers.rb | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index d6f6ab1855..38711c8462 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/object/blank' module ActionController + # See Renderers.add def self.add_renderer(key, &block) Renderers.add(key, &block) end @@ -39,7 +40,43 @@ module ActionController nil end + # Hash of available renderers, mapping a renderer name to its proc. + # Default keys are :json, :js, :xml and :update. RENDERERS = {} + + # Adds a new renderer to call within controller actions. + # A renderer is invoked by passing its name as an option to + # AbstractController::Rendering#render. To create a renderer + # pass it a name and a block. The block takes two arguments, the first + # is the value paired with its key and the second is the remaining + # hash of options passed to +render+. + # + # === Example + # Create a csv renderer: + # + # ActionController::Renderers.add :csv do |obj, options| + # filename = options[:filename] || 'data' + # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s + # send_data str, :type => Mime::CSV, + # :disposition => "attachment; filename=#{filename}.csv" + # end + # + # Note that we used Mime::CSV for the csv mime type as it comes with Rails. + # For a custom renderer, you'll need to register a mime type with + # Mime::Type.register. + # + # To use the csv renderer in a controller action: + # + # def show + # @csvable = Csvable.find(params[:id]) + # respond_to do |format| + # format.html + # format.csv { render :csv => @csvable, :filename => @csvable.name } + # } + # end + # To use renderers and their mime types in more concise ways, see + # ActionController::MimeResponds::ClassMethods.respond_to and + # ActionController::MimeResponds#respond_with def self.add(key, &block) define_method("_render_option_#{key}", &block) RENDERERS[key] = block -- cgit v1.2.3 From f68287f0be387c858766bbab237424d2d54edf71 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Thu, 10 Feb 2011 23:37:55 +0530 Subject: fix typos, minor edits --- railties/guides/source/command_line.textile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index 255b9ab190..81d181fe02 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -378,8 +378,8 @@ $ rails new . --git --database=postgresql add 'Rakefile' create README add 'README' - create app/controllers/application_controller_.rb -add 'app/controllers/application_controller_.rb' + create app/controllers/application_controller.rb +add 'app/controllers/application_controller.rb' create app/helpers/application_helper.rb ... create log/test.log @@ -451,7 +451,7 @@ The Rails generator by default looks in these places for available generators, w * Inside any plugin with a directory like "generators" or "rails_generators" * ~/.rails/generators * Inside any Gem you have installed with a name ending in "_generator" -* Inside *any* Gem installed with a "rails_generators" path, and a file ending in "_generator.rb" +* Inside any Gem installed with a "rails_generators" path, and a file ending in "_generator.rb" * Finally, the builtin Rails generators (controller, model, mailer, etc.) Let's try the fourth option (in our home directory), which will be easy to clean up later: @@ -578,13 +578,13 @@ You can list all the timezones Rails knows about with +rake time:zones:all+, whi h5. +tmp:+ Temporary files -The tmp directory is, like in the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of an +rm -rf *+ gone awry. +The tmp directory is, like in the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of deletions gone awry. h5. Miscellaneous Tasks +rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. - +rake secret+ will give you a psuedo-random key to use for your session secret. + +rake secret+ will give you a pseudo-random key to use for your session secret. +rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. -- cgit v1.2.3 From 0db9de6cf66b6a4388794efd8801f720ea95f242 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Fri, 11 Feb 2011 00:17:03 +0530 Subject: updated reference to remote_form_for --- railties/guides/source/form_helpers.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 7b4426b335..7a033a30d7 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -187,7 +187,7 @@ output: Hidden inputs are not shown to the user, but they hold data like any textual input. Values inside them can be changed with JavaScript. -TIP: If you're using password input fields (for any purpose), you might want to prevent their values showing up in application logs by activating +filter_parameter_logging(:password)+ in your ApplicationController. +TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. h3. Dealing with Model Objects @@ -594,7 +594,7 @@ NOTE: If the user has not selected a file the corresponding parameter will be an h4. Dealing with Ajax -Unlike other forms making an asynchronous file upload form is not as simple as replacing +form_for+ with +remote_form_for+. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. +Unlike other forms making an asynchronous file upload form is not as simple as providing +form_for+ with :remote => true. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. h3. Customizing Form Builders -- cgit v1.2.3 From 46ebbcd56b16734fa6ba6f81138827918976a9d5 Mon Sep 17 00:00:00 2001 From: jonnii Date: Thu, 10 Feb 2011 12:22:19 -0800 Subject: Change fenomenal to phenomenal. --- railties/guides/source/api_documentation_guidelines.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile index 2bbb1b5919..5bac75fe8f 100644 --- a/railties/guides/source/api_documentation_guidelines.textile +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -27,7 +27,7 @@ Communicate to the reader the current way of doing things, both explicitly and i Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil? -The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. Historically there has been lack of consistency regarding this, but we checked with David when docrails started. All Rails documentation consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be fenomenal :). +The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. Historically there has been lack of consistency regarding this, but we checked with David when docrails started. All Rails documentation consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be phenomenal:). Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERb. When in doubt, please have a look at some authoritative source like their official documentation. -- cgit v1.2.3 From 0d40e2c397c9ce92c839a3c318ed8757cf213413 Mon Sep 17 00:00:00 2001 From: Corey Ward Date: Sat, 12 Feb 2011 08:12:24 -0800 Subject: Add note about running `bundle install` as root. Solves issue with '#' not being rendered, and it's more clear for beginners. --- railties/guides/source/getting_started.textile | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 52e398a497..bf45c48547 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -195,6 +195,7 @@ h4. Installing the Required Gems Rails applications manage gem dependencies with "Bundler":http://gembundler.com/v1.0/index.html by default. As we don't need any other gems beyond the ones in the generated +Gemfile+ we can directly run +Usually as root user: # bundle install -- cgit v1.2.3 From bc6554f63ccf7a0ab8797472cded62bdea8fbabc Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 13 Feb 2011 01:37:37 +0100 Subject: Revert "Add note about running `bundle install` as root. Solves issue with '#' not being rendered, and it's more clear for beginners." Reason: Running bundle as root is not recommended. This reverts commit 0d40e2c397c9ce92c839a3c318ed8757cf213413. --- railties/guides/source/getting_started.textile | 1 - 1 file changed, 1 deletion(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index bf45c48547..52e398a497 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -195,7 +195,6 @@ h4. Installing the Required Gems Rails applications manage gem dependencies with "Bundler":http://gembundler.com/v1.0/index.html by default. As we don't need any other gems beyond the ones in the generated +Gemfile+ we can directly run -Usually as root user: # bundle install -- cgit v1.2.3 From 69f07dad62b4927b001046012c5a5cdab87dc1ae Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 13 Feb 2011 01:40:32 +0100 Subject: the shell prompt for non-root sessions is "$" by convention --- railties/guides/source/getting_started.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 52e398a497..bf15afcd62 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -195,7 +195,7 @@ h4. Installing the Required Gems Rails applications manage gem dependencies with "Bundler":http://gembundler.com/v1.0/index.html by default. As we don't need any other gems beyond the ones in the generated +Gemfile+ we can directly run -# bundle install +$ bundle install to have them ready. -- cgit v1.2.3 From 0617d3e3c607d6e65a107190cff5404d0b55c790 Mon Sep 17 00:00:00 2001 From: Mike Gehard Date: Sun, 13 Feb 2011 14:21:17 -0700 Subject: change plugins guide to favor gem based plugins, clean up to reflect the state of Rails3 plugins, remove information not compatible with Rails3. --- railties/guides/source/plugins.textile | 1523 +++++--------------------------- 1 file changed, 237 insertions(+), 1286 deletions(-) diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index f7c4a6f85c..2135c0da4e 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -10,285 +10,71 @@ After reading this guide you should be familiar with: * Creating a plugin from scratch * Writing and running tests for the plugin -* Storing models, views, controllers, helpers and even other plugins in your plugins -* Writing generators -* Writing custom Rake tasks in your plugin -* Generating RDoc documentation for your plugin -* Avoiding common pitfalls with 'init.rb' This guide describes how to build a test-driven plugin that will: * Extend core ruby classes like Hash and String * Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins -* Add a view helper that can be used in erb templates -* Add a new generator that will generate a migration -* Add a custom generator command -* A custom route method that can be used in routes.rb +* 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. First, you need to get setup for development. +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. endprologue. h3. Setup -h4. Create the Basic Application +h4. Generating the Plugin Skeleton -The examples in this guide require that you have a working rails application. To create a simple one execute: +Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain +how this generator works. -gem install rails -rails new yaffle_guide -cd yaffle_guide -bundle install -rails generate scaffold bird name:string -rake db:migrate -rails server + rails generate plugin --help -Then navigate to http://localhost:3000/birds. Make sure you have a functioning rails application before continuing. +This generator places the plugin into the vendor/plugins directory. -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. +Vendored plugins are useful for quickly prototyping your plugin but current thinking in the Rails community is shifting towards +packaging plugins as gems, especially with the inclusion of Bundler as the Rails dependency manager. +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. - -h4. Generate the Plugin Skeleton - -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 +--generator+ to add an example generator also. - -This creates a plugin in +vendor/plugins+ including an +init.rb+ and +README+ as well as standard +lib+, +task+, and +test+ directories. - -Examples: - -rails generate plugin yaffle -rails generate plugin yaffle --generator - - -To get more detailed help on the plugin generator, type +rails generate plugin+. - -Later on this guide will describe how to work with generators, so go ahead and generate your plugin with the +--generator+ option now: - - -rails generate plugin yaffle --generator - - -You should see the following output: - - -create vendor/plugins/yaffle -create vendor/plugins/yaffle/init.rb -create vendor/plugins/yaffle/install.rb -create vendor/plugins/yaffle/MIT-LICENSE -create vendor/plugins/yaffle/Rakefile -create vendor/plugins/yaffle/README -create vendor/plugins/yaffle/uninstall.rb -create vendor/plugins/yaffle/lib -create vendor/plugins/yaffle/lib/yaffle.rb -invoke generator -inside vendor/plugins/yaffle -create lib/generators -create lib/generators/yaffle_generator.rb -create lib/generators/USAGE -create lib/generators/templates -invoke test_unit -inside vendor/plugins/yaffle -create test -create test/yaffle_test.rb -create test/test_helper.rb - - -h4. Organize Your Files - -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: +Rails 3.1 will ship with a plugin generator that will default to setting up a plugin +as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the +"Enginex gem":http://www.github.com/josevalim/enginex. -|-- lib -| |-- yaffle -| `-- yaffle.rb -`-- init.rb + gem install enginex + enginex --help + enginex yaffle - -# vendor/plugins/yaffle/init.rb - -require 'yaffle' - - -Now you can add any +require+ statements to +lib/yaffle.rb+ and keep +init.rb+ clean. - -h3. Tests - -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: - - * A +database.yml+ file with all of your connection strings - * A +schema.rb+ file with your table definitions - * A test helper method that sets up the database - -h4. Test Setup - - -# vendor/plugins/yaffle/test/database.yml - -sqlite: - adapter: sqlite - database: vendor/plugins/yaffle/test/yaffle_plugin.sqlite.db - -sqlite3: - adapter: sqlite3 - database: vendor/plugins/yaffle/test/yaffle_plugin.sqlite3.db - -postgresql: - adapter: postgresql - username: postgres - password: postgres - database: yaffle_plugin_test - min_messages: ERROR - -mysql: - adapter: mysql2 - host: localhost - username: root - password: password - database: yaffle_plugin_test - - -For this guide you'll need 2 tables/models, Hickwalls and Wickwalls, so add the following: - - -# vendor/plugins/yaffle/test/schema.rb - -ActiveRecord::Schema.define(:version => 0) do - create_table :hickwalls, :force => true do |t| - t.string :name - t.string :last_squawk - t.datetime :last_squawked_at - end - create_table :wickwalls, :force => true do |t| - t.string :name - t.string :last_tweet - t.datetime :last_tweeted_at - end - create_table :woodpeckers, :force => true do |t| - t.string :name - end -end - - - -# vendor/plugins/yaffle/test/test_helper.rb - -ENV['RAILS_ENV'] = 'test' -ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..' - -require 'test/unit' -require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb')) - -def load_schema - config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) - ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") - - db_adapter = ENV['DB'] - - # no db passed, try one of these fine config-free DBs before bombing. - db_adapter ||= - begin - require 'rubygems' - require 'sqlite' - 'sqlite' - rescue MissingSourceFile - begin - require 'sqlite3' - 'sqlite3' - rescue MissingSourceFile - end - end - - if db_adapter.nil? - raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3." - end - - ActiveRecord::Base.establish_connection(config[db_adapter]) - load(File.dirname(__FILE__) + "/schema.rb") - require File.dirname(__FILE__) + '/../init' -end - - -Now whenever you write a test that requires the database, you can call 'load_schema'. - -h4. Run the Plugin Tests - -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: - - -# vendor/plugins/yaffle/test/yaffle_test.rb - -require 'test_helper' - -class YaffleTest < ActiveSupport::TestCase - load_schema - - class Hickwall < ActiveRecord::Base - end - - class Wickwall < ActiveRecord::Base - end - - def test_schema_has_loaded_correctly - assert_equal [], Hickwall.all - assert_equal [], Wickwall.all - end - -end - - -To run this, go to the plugin directory and run +rake+: - - -cd vendor/plugins/yaffle -rake - +This command will create a new directory named "yaffle" within the current directory. -You should see output like: +h3. Testing your newly generated plugin - -/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" - create_table(:hickwalls, {:force=>true}) - -> 0.0220s --- create_table(:wickwalls, {:force=>true}) - -> 0.0077s --- create_table(:woodpeckers, {:force=>true}) - -> 0.0069s --- initialize_schema_migrations_table() - -> 0.0007s --- assume_migrated_upto_version(0, "db/migrate") - -> 0.0007s -Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader -Started -. -Finished in 0.002236 seconds. - -1 test, 2 assertion, 0 failures, 0 errors, 0 skips - +You can navigate to the directory that contains the plugin, run the +bundle install+ command + and run the one generated test using the +rake+ command. -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: +You should see: -rake DB=sqlite -rake DB=sqlite3 -rake DB=mysql -rake DB=postgresql + 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips -Now you are ready to test-drive your plugin! +This will tell you that everything got generated properly and you are ready to start adding functionality. h3. Extending Core Classes -This section will explain how to add a method to String that will be available anywhere in your Rails application. +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: +In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions: -# vendor/plugins/yaffle/test/core_ext_test.rb +# yaffle/test/core_ext_test.rb -require File.dirname(__FILE__) + '/test_helper' +require 'test_helper' class CoreExtTest < Test::Unit::TestCase def test_to_squawk_prepends_the_word_squawk @@ -297,20 +83,13 @@ class CoreExtTest < Test::Unit::TestCase end -Navigate to your plugin directory and run +rake test+: - - -cd vendor/plugins/yaffle -rake test - - -The test above should fail with the message: +Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method: - 1) Error: -test_to_squawk_prepends_the_word_squawk(CoreExtTest): -NoMethodError: undefined method `to_squawk' for "Hello World":String - ./test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk' + 1) Error: + test_to_squawk_prepends_the_word_squawk(CoreExtTest): + NoMethodError: undefined method `to_squawk' for "Hello World":String + test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk' Great - now you are ready to start development. @@ -318,15 +97,18 @@ Great - now you are ready to start development. Then in +lib/yaffle.rb+ require +lib/core_ext+: -# vendor/plugins/yaffle/lib/yaffle.rb +# yaffle/lib/yaffle.rb require "yaffle/core_ext" + +module Yaffle +end Finally, create the +core_ext.rb+ file and add the +to_squawk+ method: -# vendor/plugins/yaffle/lib/yaffle/core_ext.rb +# yaffle/lib/yaffle/core_ext.rb String.class_eval do def to_squawk @@ -335,7 +117,13 @@ String.class_eval do end -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: +To test that your method does what it says it does, run the unit tests with +rake+ from your plugin directory. + + + 3 tests, 3 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: $ rails console @@ -343,168 +131,204 @@ $ rails console => "squawk! Hello World" -h4. Working with +init.rb+ - -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. - -NOTE: The plugins loader also looks for +rails/init.rb+, but that one is deprecated in favor of the top-level +init.rb+ aforementioned. +h3. Add an "acts_as" Method to Active Record -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. +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. -If you must reopen a class in +init.rb+ you can use +module_eval+ or +class_eval+ to avoid any issues: +To begin, set up your files so that you have: -# vendor/plugins/yaffle/init.rb +# yaffle/test/acts_as_yaffle_test.rb -Hash.class_eval do - def is_a_special_hash? - true - end +require 'test_helper' + +class ActsAsYaffleTest < Test::Unit::TestCase end -Another way is to explicitly define the top-level module space for all modules and classes, like +::Hash+: - -# vendor/plugins/yaffle/init.rb +# yaffle/lib/yaffle.rb -class ::Hash - def is_a_special_hash? - true - end +require "yaffle/core_ext" +require 'yaffle/acts_as_yaffle' + +module Yaffle end -h3. 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 models. - -To begin, set up your files so that you have: - -* *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb* - -require File.dirname(__FILE__) + '/test_helper' +# yaffle/lib/yaffle/acts_as_yaffle.rb -class ActsAsYaffleTest < Test::Unit::TestCase +module Yaffle + module ActsAsYaffle + # your code will go here + end end -* *vendor/plugins/yaffle/lib/yaffle.rb* +h4. Add a Class Method - -require 'yaffle/acts_as_yaffle' - +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'. -* *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb* +To start out, write a failing test that shows the behavior you'd like: -module Yaffle - # your code will go here -end - - -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. +# yaffle/test/acts_as_yaffle_test.rb -One of the most common plugin patterns for 'acts_as_yaffle' plugins is to structure your file like so: +require 'test_helper' -* *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb* +class ActsAsYaffleTest < Test::Unit::TestCase - -module Yaffle - def self.included(base) - base.send :extend, ClassMethods + def test_a_hickwalls_yaffle_text_field_should_be_last_squawk + assert_equal "last_squawk", Hickwall.yaffle_text_field end - module ClassMethods - # any method placed here will apply to classes, like Hickwall - def acts_as_something - send :include, InstanceMethods - end + def test_a_wickwalls_yaffle_text_field_should_be_last_tweet + assert_equal "last_tweet", Wickwall.yaffle_text_field end - module InstanceMethods - # any method placed here will apply to instaces, like @hickwall - end end -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+). +When you run +rake+, you should see the following: -h4. Add a Class Method + + 1) Error: + test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest): + NameError: uninitialized constant ActsAsYaffleTest::Hickwall + test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' -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'. + 2) Error: + test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest): + NameError: uninitialized constant ActsAsYaffleTest::Wickwall + test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' -To start out, write a failing test that shows the behavior you'd like: + 5 tests, 3 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: + + + cd test/dummy + rails generate model Hickwall last_squak:string + rails generate model Wickwall last_squak: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 + + + cd test/dummy + rake db:migrate + rake db:test:prepare + -* *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb* +While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act +like yaffles. -require File.dirname(__FILE__) + '/test_helper' +# test/dummy/app/models/hickwall.rb class Hickwall < ActiveRecord::Base acts_as_yaffle end +# test/dummy/app/models/wickwall.rb + class Wickwall < ActiveRecord::Base acts_as_yaffle :yaffle_text_field => :last_tweet end -class ActsAsYaffleTest < Test::Unit::TestCase - load_schema + - def test_a_hickwalls_yaffle_text_field_should_be_last_squawk - assert_equal "last_squawk", Hickwall.yaffle_text_field - end +We will also add code to define the acts_as_yaffle method. - def test_a_wickwalls_yaffle_text_field_should_be_last_tweet - assert_equal "last_tweet", Wickwall.yaffle_text_field + +# yaffle/lib/yaffle/acts_as_yaffle.rb +module Yaffle + module ActsAsYaffle + extend ActiveSupport::Concern + + included do + end + + module ClassMethods + def acts_as_yaffle(options = {}) + # your code will go here + end + end end end + +ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle -To make these tests pass, you could modify your +acts_as_yaffle+ file like so: +You can then return to the root directory (+cd ../..+) of your plugin and rerun the tests using +rake+. + + + 1) Error: + test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest): + NoMethodError: undefined method `yaffle_text_field' for # + /Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing' + test/acts_as_yaffle_test.rb:5:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk' + + 2) Error: + test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest): + NoMethodError: undefined method `yaffle_text_field' for # + Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing' + test/acts_as_yaffle_test.rb:9:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet' + + 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips + + -* *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb* +Getting closer...now we will implement the code of the acts_as_yaffle method to make the tests pass. +# yaffle/lib/yaffle/acts_as_yaffle.rb + module Yaffle - def self.included(base) - base.send :extend, ClassMethods - end + module ActsAsYaffle + extend ActiveSupport::Concern + + included do + end - module ClassMethods - def acts_as_yaffle(options = {}) - cattr_accessor :yaffle_text_field - self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s + module ClassMethods + def acts_as_yaffle(options = {}) + cattr_accessor :yaffle_text_field + self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s + end end end end -ActiveRecord::Base.send :include, Yaffle +ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle +When you run +rake+ you should see the tests all pass: + + + 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips + + h4. Add an Instance Method -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. +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. To start out, write a failing test that shows the behavior you'd like: -* *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb* - -require File.dirname(__FILE__) + '/test_helper' - -class Hickwall < ActiveRecord::Base - acts_as_yaffle -end - -class Wickwall < ActiveRecord::Base - acts_as_yaffle :yaffle_text_field => :last_tweet -end +# yaffle/test/acts_as_yaffle_test.rb +require 'test_helper' class ActsAsYaffleTest < Test::Unit::TestCase - load_schema def test_a_hickwalls_yaffle_text_field_should_be_last_squawk assert_equal "last_squawk", Hickwall.yaffle_text_field @@ -528,990 +352,117 @@ class ActsAsYaffleTest < Test::Unit::TestCase end -Run this test to make sure the last two tests fail, then update 'acts_as_yaffle.rb' to look like this: - -* *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb* +Run the test to make sure the last two tests fail the an error that contains "NoMethodError: undefined method `squawk'", +then update 'acts_as_yaffle.rb' to look like this: +# yaffle/lib/yaffle/acts_as_yaffle.rb + module Yaffle - def self.included(base) - base.send :extend, ClassMethods - end + module ActsAsYaffle + extend ActiveSupport::Concern - module ClassMethods - def acts_as_yaffle(options = {}) - cattr_accessor :yaffle_text_field - self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s - send :include, InstanceMethods + included do + end + + module ClassMethods + def acts_as_yaffle(options = {}) + cattr_accessor :yaffle_text_field + self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s + end end - end - module InstanceMethods def squawk(string) write_attribute(self.class.yaffle_text_field, string.to_squawk) end + end end -ActiveRecord::Base.send :include, Yaffle +ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle -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)+. - -h3. Models - -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: - +Run +rake+ one final time and you should see: -vendor/plugins/yaffle/ -|-- lib -| |-- app -| | |-- controllers -| | |-- helpers -| | |-- models -| | | `-- woodpecker.rb -| | `-- views -| |-- yaffle -| | |-- acts_as_yaffle.rb -| | |-- commands.rb -| | `-- core_ext.rb -| `-- yaffle.rb + 7 tests, 7 assertions, 0 failures, 0 errors, 0 skips -As always, start with a test: - -* *vendor/plugins/yaffle/test/woodpecker_test.rb:* - - -require File.dirname(__FILE__) + '/test_helper' - -class WoodpeckerTest < Test::Unit::TestCase - load_schema - - def test_woodpecker - assert_kind_of Woodpecker, Woodpecker.new - end -end - - -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: - -* *vendor/plugins/yaffle/lib/yaffle.rb:* - - -%w{ models }.each do |dir| - path = File.join(File.dirname(__FILE__), 'app', dir) - $LOAD_PATH << path - ActiveSupport::Dependencies.autoload_paths << path - ActiveSupport::Dependencies.autoload_once_paths.delete(path) -end - - -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. - -* *vendor/plugins/yaffle/lib/app/models/woodpecker.rb:* - - -class Woodpecker < ActiveRecord::Base -end - - -Finally, add the following to your plugin's 'schema.rb': - -* *vendor/plugins/yaffle/test/schema.rb:* - - -create_table :woodpeckers, :force => true do |t| - t.string :name -end - +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)+. -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. - -h3. Controllers - -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. - -You can test your plugin's controller as you would test any other controller: - -* *vendor/plugins/yaffle/test/woodpeckers_controller_test.rb:* +h3. Generators - -require File.dirname(__FILE__) + '/test_helper' -require 'woodpeckers_controller' -require 'action_controller/test_process' +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 -class WoodpeckersController; def rescue_action(e) raise e end; end +h3. Publishing your Gem -class WoodpeckersControllerTest < Test::Unit::TestCase - def setup - @controller = WoodpeckersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new +Gem plugins in progress can be 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 any application: - ActionController::Routing::Routes.draw do |map| - map.resources :woodpeckers - end - end +gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git' - def test_index - get :index - assert_response :success - end -end - +After running +bundle install+, your gem functionality will be available to the application. -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: +When the gem is ready to be shared as a formal release, it can be published to "RubyGems":http://www.rubygems.org. +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 -* *vendor/plugins/yaffle/lib/yaffle.rb:* +h3. Non-Gem Plugins - -%w{ models controllers }.each do |dir| - path = File.join(File.dirname(__FILE__), 'app', dir) - $LOAD_PATH << path - ActiveSupport::Dependencies.autoload_paths << path - ActiveSupport::Dependencies.autoload_once_paths.delete(path) -end - +Non-gem plugins are useful for functionality that won't be shared with another project. Keeping your custom functionality in the +vendor/plugins directory un-clutters the rest of the application. -* *vendor/plugins/yaffle/lib/app/controllers/woodpeckers_controller.rb:* +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. -class WoodpeckersController < ActionController::Base - - def index - render :text => "Squawk!" - end +# yaffle/init.rb -end +require 'yaffle' -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. +You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the +console we can check to see if the String has an instance method of to_squawk. + + cd my_app + rails console + String.instance_methods.sort + -h3. Helpers +You can also remove the .gemspec, Gemfile and Gemfile.lock files as they will no longer be needed. -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. +h3. RDoc Documentation -You can test your plugin's helper as you would test any other helper: +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. -* *vendor/plugins/yaffle/test/woodpeckers_helper_test.rb* +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: - -require File.dirname(__FILE__) + '/test_helper' -include WoodpeckersHelper +* Your name +* How to install +* How to add the functionality to the app (several examples of common use cases) +* Warning, gotchas or tips that might help save users time -class WoodpeckersHelperTest < Test::Unit::TestCase - def test_tweet - assert_equal "Tweet! Hello", tweet("Hello") - end -end - +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. -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: +Once your comments are good to go, navigate to your plugin directory and run: -* *vendor/plugins/yaffle/lib/yaffle.rb:* + +rake rdoc + - -%w{ models controllers helpers }.each do |dir| - path = File.join(File.dirname(__FILE__), 'app', dir) - $LOAD_PATH << path - ActiveSupport::Dependencies.autoload_paths << path - ActiveSupport::Dependencies.autoload_once_paths.delete(path) -end - +!!!!!!!!!!!!!! Make sure these still make sense. Add any references that you see fit. !!!!!!!!!!!!! -* *vendor/plugins/yaffle/lib/app/helpers/woodpeckers_helper.rb:* +h4. References - -module WoodpeckersHelper - - def tweet(text) - "Tweet! #{text}" - end - -end - - -Now your test should be passing, and you should be able to use the Woodpeckers helper in your app. - -h3. Routes - -You can add your own custom routes from a plugin. This section will describe how to add a custom method that can be called with 'map.yaffles'. - -Testing routes from plugins is slightly different from testing routes in a standard Rails application. To begin, add a test like this: - -* *vendor/plugins/yaffle/test/routing_test.rb* - - -require "#{File.dirname(__FILE__)}/test_helper" - -class RoutingTest < Test::Unit::TestCase - - def setup - ActionController::Routing::Routes.draw do |map| - map.yaffles - end - end - - def test_yaffles_route - assert_recognition :get, "/yaffles", :controller => "yaffles_controller", :action => "index" - end - - private - - def assert_recognition(method, path, options) - result = ActionController::Routing::Routes.recognize_path(path, :method => method) - assert_equal options, result - end -end - - -Once you see the tests fail by running 'rake', you can make them pass with: - -* *vendor/plugins/yaffle/lib/yaffle.rb* - - -require "yaffle/routing" - - -* *vendor/plugins/yaffle/lib/yaffle/routing.rb* - - -module Yaffle #:nodoc: - module Routing #:nodoc: - module MapperExtensions - def yaffles - @set.add_route("/yaffles", {:controller => "yaffles_controller", :action => "index"}) - end - end - end -end - -ActionController::Routing::RouteSet::Mapper.send :include, Yaffle::Routing::MapperExtensions - - -* *config/routes.rb* - - -ActionController::Routing::Routes.draw do |map| - map.yaffles -end - - -You can also see if your routes work by running +rake routes+ from your app directory. - -h3. Generators - -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'. - -Building generators is a complex topic unto itself and this section will cover one small aspect of generators: generating a simple text file. - -h4. Testing Generators - -Many rails plugin authors do not test their generators, however testing generators is quite simple. A typical generator test does the following: - - * Creates a new fake rails root directory that will serve as destination - * Runs the generator - * Asserts that the correct files were generated - * Removes the fake rails root - -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: - -* *vendor/plugins/yaffle/test/definition_generator_test.rb* - - -require File.dirname(__FILE__) + '/test_helper' -require 'rails_generator' -require 'rails_generator/scripts/generate' - -class DefinitionGeneratorTest < Test::Unit::TestCase - - def setup - FileUtils.mkdir_p(fake_rails_root) - @original_files = file_list - end - - def teardown - FileUtils.rm_r(fake_rails_root) - end - - def test_generates_correct_file_name - Rails::Generator::Scripts::Generate.new.run(["yaffle_definition"], :destination => fake_rails_root) - new_file = (file_list - @original_files).first - assert_equal "definition.txt", File.basename(new_file) - end - - private - - def fake_rails_root - File.join(File.dirname(__FILE__), 'rails_root') - end - - def file_list - Dir.glob(File.join(fake_rails_root, "*")) - end - -end - - -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. - -To make it pass, create the generator: - -* *vendor/plugins/yaffle/generators/yaffle_definition/yaffle_definition_generator.rb* - - -class YaffleDefinitionGenerator < Rails::Generator::Base - def manifest - record do |m| - m.file "definition.txt", "definition.txt" - end - end -end - - -h4. The +USAGE+ File - -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. - -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: - - -rails generate - - -You should see something like this: - - -Installed Generators - Plugins (vendor/plugins): yaffle_definition - Builtin: controller, integration_test, mailer, migration, model, observer, plugin, resource, scaffold, session_migration - - -When you run +rails generate yaffle_definition -h+ you should see the contents of your 'vendor/plugins/yaffle/generators/yaffle_definition/USAGE'. - -For this plugin, update the USAGE file could look like this: - - -Description: - Adds a file with the definition of a Yaffle to the app's main directory - - -h3. Add a Custom Generator Command - -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. - -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. - -To start, add the following test method: - -* *vendor/plugins/yaffle/test/generator_test.rb* - - -def test_generates_definition - Rails::Generator::Scripts::Generate.new.run(["yaffle", "bird"], :destination => fake_rails_root) - definition = File.read(File.join(fake_rails_root, "definition.txt")) - assert_match /Yaffle\:/, definition -end - - -Run +rake+ to watch the test fail, then make the test pass add the following: - -* *vendor/plugins/yaffle/generators/yaffle/templates/definition.txt* - - -Yaffle: A bird - - -* *vendor/plugins/yaffle/lib/yaffle.rb* - - -require "yaffle/commands" - - -* *vendor/plugins/yaffle/lib/commands.rb* - - -require 'rails_generator' -require 'rails_generator/commands' - -module Yaffle #:nodoc: - module Generator #:nodoc: - module Commands #:nodoc: - module Create - def yaffle_definition - file("definition.txt", "definition.txt") - end - end - - module Destroy - def yaffle_definition - file("definition.txt", "definition.txt") - end - end - - module List - def yaffle_definition - file("definition.txt", "definition.txt") - end - end - - module Update - def yaffle_definition - file("definition.txt", "definition.txt") - end - end - end - end -end - -Rails::Generator::Commands::Create.send :include, Yaffle::Generator::Commands::Create -Rails::Generator::Commands::Destroy.send :include, Yaffle::Generator::Commands::Destroy -Rails::Generator::Commands::List.send :include, Yaffle::Generator::Commands::List -Rails::Generator::Commands::Update.send :include, Yaffle::Generator::Commands::Update - - -Finally, call your new method in the manifest: - -* *vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb* - - -class YaffleGenerator < Rails::Generator::NamedBase - def manifest - m.yaffle_definition - end -end - - -h3. Generator Commands - -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. - -This section describes how you you can create your own commands to add and remove a line of text from 'config/routes.rb'. - -To start, add the following test method: - -* *vendor/plugins/yaffle/test/route_generator_test.rb* - - -require File.dirname(__FILE__) + '/test_helper' -require 'rails_generator' -require 'rails_generator/scripts/generate' -require 'rails_generator/scripts/destroy' - -class RouteGeneratorTest < Test::Unit::TestCase - - def setup - FileUtils.mkdir_p(File.join(fake_rails_root, "config")) - end - - def teardown - FileUtils.rm_r(fake_rails_root) - end - - def test_generates_route - content = <<-END - ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' - map.connect ':controller/:action/:id.:format' - end - END - File.open(routes_path, 'wb') {|f| f.write(content) } - - Rails::Generator::Scripts::Generate.new.run(["yaffle_route"], :destination => fake_rails_root) - assert_match /map\.yaffles/, File.read(routes_path) - end - - def test_destroys_route - content = <<-END - ActionController::Routing::Routes.draw do |map| - map.yaffles - map.connect ':controller/:action/:id' - map.connect ':controller/:action/:id.:format' - end - END - File.open(routes_path, 'wb') {|f| f.write(content) } - - Rails::Generator::Scripts::Destroy.new.run(["yaffle_route"], :destination => fake_rails_root) - assert_no_match /map\.yaffles/, File.read(routes_path) - end - - private - - def fake_rails_root - File.join(File.dirname(__FILE__), "rails_root") - end - - def routes_path - File.join(fake_rails_root, "config", "routes.rb") - end - -end - - -Run +rake+ to watch the test fail, then make the test pass add the following: - -* *vendor/plugins/yaffle/lib/yaffle.rb* - - -require "yaffle/commands" - - -* *vendor/plugins/yaffle/lib/yaffle/commands.rb* - - -require 'rails_generator' -require 'rails_generator/commands' - -module Yaffle #:nodoc: - module Generator #:nodoc: - module Commands #:nodoc: - module Create - def yaffle_route - logger.route "map.yaffle" - look_for = 'ActionController::Routing::Routes.draw do |map|' - unless options[:pretend] - gsub_file('config/routes.rb', /(#{Regexp.escape(look_for)})/mi){|match| "#{match}\n map.yaffles\n"} - end - end - end - - module Destroy - def yaffle_route - logger.route "map.yaffle" - gsub_file 'config/routes.rb', /\n.+?map\.yaffles/mi, '' - end - end - - module List - def yaffle_route - end - end - - module Update - def yaffle_route - end - end - end - end -end - -Rails::Generator::Commands::Create.send :include, Yaffle::Generator::Commands::Create -Rails::Generator::Commands::Destroy.send :include, Yaffle::Generator::Commands::Destroy -Rails::Generator::Commands::List.send :include, Yaffle::Generator::Commands::List -Rails::Generator::Commands::Update.send :include, Yaffle::Generator::Commands::Update - - -* *vendor/plugins/yaffle/generators/yaffle_route/yaffle_route_generator.rb* - - -class YaffleRouteGenerator < Rails::Generator::Base - def manifest - record do |m| - m.yaffle_route - end - end -end - - -To see this work, type: - - -rails generate yaffle_route -rails destroy yaffle_route - - -NOTE: If you haven't set up the custom route from above, 'rails destroy' will fail and you'll have to remove it manually. - -h3. Migrations - -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. - -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. - -Let's say you have the following migration in your plugin: - -* *vendor/plugins/yaffle/lib/db/migrate/20081116181115_create_birdhouses.rb:* - - -class CreateBirdhouses < ActiveRecord::Migration - def self.up - create_table :birdhouses, :force => true do |t| - t.string :name - t.timestamps - end - end - - def self.down - drop_table :birdhouses - end -end - - -Here are a few possibilities for how to allow developers to use your plugin migrations: - -h4. Create a Custom Rake Task - -* *vendor/plugins/yaffle/tasks/yaffle_tasks.rake:* - - -namespace :db do - namespace :migrate do - description = "Migrate the database through scripts in vendor/plugins/yaffle/lib/db/migrate" - description << "and update db/schema.rb by invoking db:schema:dump." - description << "Target specific version with VERSION=x. Turn off output with VERBOSE=false." - - desc description - task :yaffle => :environment do - ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true - ActiveRecord::Migrator.migrate("vendor/plugins/yaffle/lib/db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil) - Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby - end - end -end - - -h4. Call Migrations Directly - -* *vendor/plugins/yaffle/lib/yaffle.rb:* - - -Dir.glob(File.join(File.dirname(__FILE__), "db", "migrate", "*")).each do |file| - require file -end - - -* *db/migrate/20081116181115_create_birdhouses.rb:* - - -class CreateBirdhouses < ActiveRecord::Migration - def self.up - Yaffle::CreateBirdhouses.up - end - - def self.down - Yaffle::CreateBirdhouses.down - end -end - - -NOTE: several plugin frameworks such as Desert and Engines provide more advanced plugin functionality. - -h4. Generate Migrations - -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: - - * call your rails generate script and pass in whatever options they need - * examine the generated migration, adding/removing columns or other options as necessary - -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: - -* *vendor/plugins/yaffle/test/yaffle_migration_generator_test.rb* - - -require File.dirname(__FILE__) + '/test_helper' -require 'rails_generator' -require 'rails_generator/scripts/generate' - -class MigrationGeneratorTest < Test::Unit::TestCase - - def setup - FileUtils.mkdir_p(fake_rails_root) - @original_files = file_list - end - - def teardown - ActiveRecord::Base.pluralize_table_names = true - FileUtils.rm_r(fake_rails_root) - end - - def test_generates_correct_file_name - Rails::Generator::Scripts::Generate.new.run(["yaffle_migration", "some_name_nobody_is_likely_to_ever_use_in_a_real_migration"], - :destination => fake_rails_root) - new_file = (file_list - @original_files).first - assert_match /add_yaffle_fields_to_some_name_nobody_is_likely_to_ever_use_in_a_real_migrations/, new_file - assert_match /add_column :some_name_nobody_is_likely_to_ever_use_in_a_real_migrations do |t|/, File.read(new_file) - end - - def test_pluralizes_properly - ActiveRecord::Base.pluralize_table_names = false - Rails::Generator::Scripts::Generate.new.run(["yaffle_migration", "some_name_nobody_is_likely_to_ever_use_in_a_real_migration"], - :destination => fake_rails_root) - new_file = (file_list - @original_files).first - assert_match /add_yaffle_fields_to_some_name_nobody_is_likely_to_ever_use_in_a_real_migration/, new_file - assert_match /add_column :some_name_nobody_is_likely_to_ever_use_in_a_real_migration do |t|/, File.read(new_file) - end - - private - def fake_rails_root - File.join(File.dirname(__FILE__), 'rails_root') - end - - def file_list - Dir.glob(File.join(fake_rails_root, "db", "migrate", "*")) - end - -end - - -NOTE: The migration generator checks to see if a migration 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. - -After running the test with 'rake' you can make it pass with: - -* *vendor/plugins/yaffle/generators/yaffle_migration/yaffle_migration_generator.rb* - - -class YaffleMigrationGenerator < Rails::Generator::NamedBase - def manifest - record do |m| - m.migration_template 'migration:migration.rb', "db/migrate", {:assigns => yaffle_local_assigns, - :migration_file_name => "add_yaffle_fields_to_#{custom_file_name}" - } - end - end - - private - def custom_file_name - custom_name = class_name.underscore.downcase - custom_name = custom_name.pluralize if ActiveRecord::Base.pluralize_table_names - custom_name - end - - def yaffle_local_assigns - {}.tap do |assigns| - assigns[:migration_action] = "add" - assigns[:class_name] = "add_yaffle_fields_to_#{custom_file_name}" - assigns[:table_name] = custom_file_name - assigns[:attributes] = [Rails::Generator::GeneratedAttribute.new("last_squawk", "string")] - end - end -end - - -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. - -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. - -To run the generator, type the following at the command line: - - -rails generate yaffle_migration bird - - -and you will see a new file: - -* *db/migrate/20080529225649_add_yaffle_fields_to_birds.rb* - - -class AddYaffleFieldsToBirds < ActiveRecord::Migration - def self.up - add_column :birds, :last_squawk, :string - end - - def self.down - remove_column :birds, :last_squawk - end -end - - -h3. Rake Tasks - -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. - -Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so: - -* *vendor/plugins/yaffle/tasks/yaffle_tasks.rake* - - -namespace :yaffle do - desc "Prints out the word 'Yaffle'" - task :squawk => :environment do - puts "squawk!" - end -end - - -When you run +rake -T+ from your plugin you will see: - - -yaffle:squawk # Prints out the word 'Yaffle' - - -You can add as many files as you want in the tasks directory, and if they end in .rake Rails will pick them up. - -Note that tasks from +vendor/plugins/yaffle/Rakefile+ are not available to the main app. - -h3. Plugins as Gems - -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. - -Rails 3 ignores both init.rb and rails/init.rb file of a gem. 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. - -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: - -* *vendor/plugins/yaffle/Rakefile:* - - -PKG_FILES = FileList[ - '[a-zA-Z]*', - 'generators/**/*', - 'lib/**/*', - 'rails/**/*', - 'tasks/**/*', - 'test/**/*' -] - -spec = Gem::Specification.new do |s| - s.name = "yaffle" - s.version = "0.0.1" - s.author = "Gleeful Yaffler" - s.email = "yaffle@example.com" - s.homepage = "http://yafflers.example.com/" - s.platform = Gem::Platform::RUBY - s.summary = "Sharing Yaffle Goodness" - s.files = PKG_FILES.to_a - s.require_path = "lib" - s.has_rdoc = false - s.extra_rdoc_files = ["README"] -end - -desc 'Turn this plugin into a gem.' -Rake::GemPackageTask.new(spec) do |pkg| - pkg.gem_spec = spec -end - - -To build and install the gem locally, run the following commands: - - -cd vendor/plugins/yaffle -rake gem -sudo gem install pkg/yaffle-0.0.1.gem - - -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. - -h3. 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) -* Warning, gotchas or tips that might help save users 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 part of the public api. - -Once your comments are good to go, navigate to your plugin directory and run: - - -rake rdoc - - -h3. Appendix - -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. - -h4. References - -* "Complete Guide to Rails Plugins - Part 1":http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-i -* "Complete Guide to Rails Plugins - Part 2":http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-ii -* "Attachment_fu Plugin":http://github.com/technoweenie/attachment_fu/tree/master -* "Keeping init.rb thin":http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html +* "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":http://docs.rubygems.org/read/chapter/20 * "GemPlugins":http://www.mbleigh.com/2008/06/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins -* "Extending Routes":http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2 - -h4. Contents of +lib/yaffle.rb+ - -* *vendor/plugins/yaffle/lib/yaffle.rb:* - - -require "yaffle/core_ext" -require "yaffle/acts_as_yaffle" -require "yaffle/commands" -require "yaffle/routing" - -%w{ models controllers helpers }.each do |dir| - path = File.join(File.dirname(__FILE__), 'app', dir) - $LOAD_PATH << path - ActiveSupport::Dependencies.autoload_paths << path - ActiveSupport::Dependencies.autoload_once_paths.delete(path) -end - -# optionally: -# Dir.glob(File.join(File.dirname(__FILE__), "db", "migrate", "*")).each do |file| -# require file -# end - - -h4. Final Plugin Directory Structure - -The final plugin should have a directory structure that looks something like this: - - -|-- MIT-LICENSE -|-- README -|-- Rakefile -|-- generators -| |-- yaffle_definition -| | |-- USAGE -| | |-- templates -| | | `-- definition.txt -| | `-- yaffle_definition_generator.rb -| |-- yaffle_migration -| | |-- USAGE -| | |-- templates -| | `-- yaffle_migration_generator.rb -| `-- yaffle_route -| |-- USAGE -| |-- templates -| `-- yaffle_route_generator.rb -|-- install.rb -|-- lib -| |-- app -| | |-- controllers -| | | `-- woodpeckers_controller.rb -| | |-- helpers -| | | `-- woodpeckers_helper.rb -| | `-- models -| | `-- woodpecker.rb -| |-- db -| | `-- migrate -| | `-- 20081116181115_create_birdhouses.rb -| |-- yaffle -| | |-- acts_as_yaffle.rb -| | |-- commands.rb -| | |-- core_ext.rb -| | `-- routing.rb -| `-- yaffle.rb -|-- pkg -| `-- yaffle-0.0.1.gem -|-- rails -| `-- init.rb -|-- tasks -| `-- yaffle_tasks.rake -|-- test -| |-- acts_as_yaffle_test.rb -| |-- core_ext_test.rb -| |-- database.yml -| |-- debug.log -| |-- definition_generator_test.rb -| |-- migration_generator_test.rb -| |-- route_generator_test.rb -| |-- routes_test.rb -| |-- schema.rb -| |-- test_helper.rb -| |-- woodpecker_test.rb -| |-- woodpeckers_controller_test.rb -| |-- wookpeckers_helper_test.rb -| |-- yaffle_plugin.sqlite3.db -| `-- yaffle_test.rb -`-- uninstall.rb - +* "Keeping init.rb thin":http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html h3. Changelog +* February 13, 2011: Get guide in synch with Rails 3.0.3. Remove information not compatible with Rails 3. Send reader elsewhere +for information that is covered elsewhere. * April 4, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * November 17, 2008: Major revision by Jeff Dean -- cgit v1.2.3 From f7a31ce1cd23b2f05a80d5c954e9a9b833665618 Mon Sep 17 00:00:00 2001 From: Dan Neumann Date: Sun, 13 Feb 2011 17:50:39 -0600 Subject: Added simple demonstration of has_many :through --- railties/guides/source/association_basics.textile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index f22f41e8b1..23bfafa640 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -165,6 +165,12 @@ class Paragraph < ActiveRecord::Base end +With +:through => :sections+ specified, Rails will now understand: + + + @document.paragraphs + + h4. The +has_one :through+ Association A +has_one :through+ association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this: -- cgit v1.2.3 From a1cc07c35eb5f2f66d39fd2ba22a84cdd9cf99be Mon Sep 17 00:00:00 2001 From: Dan Neumann Date: Sun, 13 Feb 2011 18:14:05 -0600 Subject: Added --sandbox option to rails console explanation. --- railties/guides/source/getting_started.textile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index bf15afcd62..6fb54bfd49 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -274,7 +274,7 @@ TIP: Rake is a general-purpose command-runner that Rails uses for many things. Y h3. Hello, Rails! -One of the traditional places to start with a new language is by getting some text up on screen quickly, to do this, you need to get your Rails application server running. +One of the traditional places to start with a new language is by getting some text up on screen quickly. To do this, you need to get your Rails application server running. h4. Starting up the Web Server @@ -469,6 +469,8 @@ To see your validations in action, you can use the console. The console is a com $ rails console +TIP: The default console will make changes to your database. You can instead open a console that will roll back any changes you make by using +rails console --sandbox+. + After the console loads, you can use it to work with your application's models: -- cgit v1.2.3 From 25e3d2caf4a4d567c1087db326cee3dfe15151a3 Mon Sep 17 00:00:00 2001 From: Ben Orenstein Date: Mon, 14 Feb 2011 20:41:32 -0500 Subject: Correct bad wording in description. --- activesupport/lib/active_support/core_ext/string/inflections.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 55b24b0925..2f0676f567 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -3,7 +3,7 @@ require 'active_support/inflector/inflections' require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. -# For instance, you can figure out the name of a database from the name of a class. +# For instance, you can figure out the name of a table from the name of a class. # # "ScaleScore".tableize # => "scale_scores" # -- cgit v1.2.3 From 35a266edfd9ac0f3afe7f7f13c2dcf29cb4b651f Mon Sep 17 00:00:00 2001 From: Ben Orenstein Date: Tue, 15 Feb 2011 11:51:07 -0500 Subject: Fix error: docrails is a fork, not a branch. --- railties/guides/source/contributing_to_ruby_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 4f51c0f859..830b055359 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -253,7 +253,7 @@ h3. Contributing to the Rails Documentation Ruby on Rails has two main sets of documentation: The guides help you to learn Ruby on Rails, and the API is a reference. -You can create a ticket in Lighthouse to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":http://github.com/lifo/docrails/tree/master. docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. +You can create a ticket in Lighthouse to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":http://github.com/lifo/docrails/tree/master. docrails is a fork of the Rails repository with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. When working with documentation, please take into account the "API Documentation Guidelines":api_documentation_guidelines.html and the "Ruby on Rails Guides Guidelines":ruby_on_rails_guides_guidelines.html. -- cgit v1.2.3 From f4ddb4da89906e2339eefcb835e2af4673218b30 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Thu, 10 Feb 2011 17:28:53 -0500 Subject: Added RDoc info for accepted options for simple_format in TextHelper. --- actionpack/lib/action_view/helpers/text_helper.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 4f7f5c454f..2d3c5fe7e7 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -234,6 +234,10 @@ module ActionView # # You can pass any HTML attributes into html_options. These # will be added to all created paragraphs. + # + # ==== Options + # * :sanitize - If +false+, does not sanitize +text+. + # # ==== Examples # my_text = "Here is some basic text...\n...with a line break." # @@ -247,6 +251,9 @@ module ActionView # # simple_format("Look ma! A class!", :class => 'description') # # => "

Look ma! A class!

" + # + # simple_format("I'm allowed! It's true.", {}, :sanitize => false) + # # => "

I'm allowed! It's true.

" def simple_format(text, html_options={}, options={}) text = ''.html_safe if text.nil? start_tag = tag('p', html_options, true) -- cgit v1.2.3 From 5444ed56ab38a742b3ddf6612eadd8cf50d8d517 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 15 Feb 2011 22:17:25 +0100 Subject: Revert "Fix error: docrails is a fork, not a branch." Reason: Calling docrails a "branch" is fine, no need to change the terminology. It is a remote in my machine, and it has been called branch since the very beginning: http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch This reverts commit 35a266edfd9ac0f3afe7f7f13c2dcf29cb4b651f. --- railties/guides/source/contributing_to_ruby_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 830b055359..4f51c0f859 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -253,7 +253,7 @@ h3. Contributing to the Rails Documentation Ruby on Rails has two main sets of documentation: The guides help you to learn Ruby on Rails, and the API is a reference. -You can create a ticket in Lighthouse to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":http://github.com/lifo/docrails/tree/master. docrails is a fork of the Rails repository with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. +You can create a ticket in Lighthouse to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":http://github.com/lifo/docrails/tree/master. docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. When working with documentation, please take into account the "API Documentation Guidelines":api_documentation_guidelines.html and the "Ruby on Rails Guides Guidelines":ruby_on_rails_guides_guidelines.html. -- cgit v1.2.3 From bd7d8665a083f534f9d1aaa544d912f933b4fd61 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 16 Feb 2011 14:15:48 -0800 Subject: add note about observer config for rails apps ActiveModel::Observer and ActiveRecord::Observer have similar documentation, but a Rails user looking at the ActiveModel version might not realize that a change to config/application.rb is required to activate the observer. This change adds a note to that effect, pointing to the AR version from the AM version. --- activemodel/lib/active_model/observing.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index bf4fd0740c..dde3a882cf 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -156,6 +156,10 @@ module ActiveModel # The AuditObserver will now act on both updates to Account and Balance by treating # them both as records. # + # If you're using an Observer in a Rails application with Active Record, be sure to + # read about the necessary configuration in the documentation for + # ActiveRecord::Observer. + # class Observer include Singleton -- cgit v1.2.3 From ed630dfc72109cbe0d89884a9ceacb20ef5b0fa3 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Thu, 17 Feb 2011 16:59:03 +0530 Subject: fix typo --- railties/guides/source/active_support_core_extensions.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index cf9ebf44e6..9da8ecc6fc 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -2190,7 +2190,7 @@ Array.wrap(0) # => [0] This method is similar in purpose to Kernel#Array, but there are some differences: -* If the argument responds to +to_ary+ the method is invoked. Kernel#Array moves on to try +to_a+ if the returned value is +nil+, but Arraw.wrap returns such a +nil+ right away. +* If the argument responds to +to_ary+ the method is invoked. Kernel#Array moves on to try +to_a+ if the returned value is +nil+, but Array.wrap returns +nil+ right away. * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, Kernel#Array raises an exception, while Array.wrap does not, it just returns the value. * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array. -- cgit v1.2.3 From 465dd379e47152c9aa3bde252729b77c6b98f168 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Thu, 17 Feb 2011 17:19:51 +0530 Subject: fix typo --- actionmailer/lib/action_mailer/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 6ae1eac42a..15b0d01154 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -222,7 +222,7 @@ module ActionMailer #:nodoc: # # An interceptor object must implement the :delivering_email(message) method which will be # called before the email is sent, allowing you to make modifications to the email before it hits - # the delivery agents. Your object should make and needed modifications directly to the passed + # the delivery agents. Your object should make any needed modifications directly to the passed # in Mail::Message instance. # # = Default Hash -- cgit v1.2.3 From fead0fc44850480f2f431b8befb5cba284e5e633 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Thu, 17 Feb 2011 10:12:28 -0600 Subject: update caching guide with the changes to ActiveSupport::Cache from the 3.0.0 release. --- railties/guides/source/caching_with_rails.textile | 94 +++++++++++++---------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 63c52da32a..1b5ec40d16 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -238,86 +238,95 @@ h3. Cache Stores Rails provides different stores for the cached data created by action and fragment caches. Page caches are always stored on disk. -Rails 2.1 and above provide +ActiveSupport::Cache::Store+ which can be used to cache strings. Some cache store implementations, like +MemoryStore+, are able to cache arbitrary Ruby objects, but don't count on every cache store to be able to do that. +h4. Configuration -The default cache stores provided with Rails include: - -1) +ActiveSupport::Cache::MemoryStore+: A cache store implementation which stores everything into memory in the same process. If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then this means that your Rails server process instances won't be able to share cache data with each other. If your application never performs manual cache item expiry (e.g. when you‘re using generational cache keys), then using +MemoryStore+ is ok. Otherwise, consider carefully whether you should be using this cache store. - -+MemoryStore+ is not only able to store strings, but also arbitrary Ruby objects. - -+MemoryStore+ is not thread-safe. Use +SynchronizedMemoryStore+ instead if you need thread-safety. +You can set up your application's default cache store by calling +config.cache_store=+ in the Application definition inside your +config/application.rb+ file or in an Application.configure block in an environment specific configuration file (i.e. +config/environments/*.rb+). The first argument will be the cache store to use and the rest of the argument will be passed as arguments to the cache store constructor. -ActionController::Base.cache_store = :memory_store +config.cache_store = :memory_store -2) +ActiveSupport::Cache::FileStore+: Cached data is stored on the disk, this is the default store and the default path for this store is +tmp/cache+. Works well for all types of environments and allows all processes running from the same application directory to access the cached content. If +tmp/cache+ does not exist, the default store becomes +MemoryStore+. +Alternatively, you can call +ActionController::Base.cache_store+ outside of a configuration block. - -ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" - +You can access the cache by calling +Rails.cache+. -3) +ActiveSupport::Cache::DRbStore+: Cached data is stored in a separate shared DRb process that all servers communicate with. This works for all environments and only keeps one cache around for all processes, but requires that you run and manage a separate DRb process. +h4. ActiveSupport::Cache::Store - -ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" - +This class provides the foundation for interacting with the cache in Rails. This is an abstract class and you cannot use it on its own. Rather you must use a concrete implementation of the class tied to a storage engine. Rails ships with several implementations documented below. + +The main methods to call are +read+, +write+, +delete+, +exist?+, and +fetch+. The fetch method takes a block and will either return an existing value from the cache, or evaluate the block and write the result to the cache if no value exists. -4) +ActiveSupport::Cache::MemCacheStore+: Works like +DRbStore+, but uses Danga's +memcached+ instead. Rails uses the bundled +memcached-client+ gem by default. This is currently the most popular cache store for production websites. +There are some common options used by all cache implementations. These can be passed to the constructor or the various methods to interact with entries. -Special features: +* +:namespace+ - This option can be used to create a namespace within the cache store. It is especially useful if your application shares a cache with other applications. The default value will include the application name and Rails environment. -* Clustering and load balancing. One can specify multiple memcached servers, and +MemCacheStore+ will load balance between all available servers. If a server goes down, then +MemCacheStore+ will ignore it until it goes back online. -* Time-based expiry support. See +write+ and the +:expires_in+ option. -* Per-request in memory cache for all communication with the +memcached+ server(s). +* +:compress+ - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network. -It also accepts a hash of additional options: +* +:compress_threshold+ - This options is used in conjunction with the +:compress+ option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes. -* +:namespace+: specifies a string that will automatically be prepended to keys when accessing the memcached store. -* +:readonly+: a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write. -* +:multithread+: a boolean value that adds thread safety to read/write operations - it is unlikely you'll need to use this option as the Rails threadsafe! method offers the same functionality. +* +:expires_in+ - This option sets an expiration time in seconds for the cache entry when it will be automatically removed from the cache. -The read and write methods of the +MemCacheStore+ accept an options hash too. When reading you can specify +:raw => true+ to prevent the object being marshaled (by default this is false which means the raw value in the cache is passed to +Marshal.load+ before being returned to you.) +* +:race_condition_ttl+ - This option is used in conjunction with the +:expires_in+ option. It will prevent race conditions when cache entries expire by preventing multiple processes from simultaneously regenerating the same entry (also known as the dog pile effect). This option sets the number of seconds that an expired entry can be reused while a new value is being regenerated. It's a good practice to set this value if you use the +:expires_in+ option. -When writing to the cache it is also possible to specify +:raw => true+ means the value is not passed to +Marshal.dump+ before being stored in the cache (by default this is false). +h4. ActiveSupport::Cache::MemoryStore -The write method also accepts an +:unless_exist+ flag which determines whether the memcached add (when true) or set (when false) method is used to store the item in the cache and an +:expires_in+ option that specifies the time-to-live for the cached item in seconds. +This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the +:size+ options to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed. -ActionController::Base.cache_store = :mem_cache_store, "localhost" +ActionController::Base.cache_store = :memory_store, :size => 64.megabytes -5) +ActiveSupport::Cache::SynchronizedMemoryStore+: Like +MemoryStore+ but thread-safe. +If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then your Rails server process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments, but can work well for small, low traffic sites with only a couple of server processes or for development and test environments. + +This is the default cache store implementation. + +h4. ActiveSupport::Cache::FileStore + +This cache store uses the file system to store entries. The path to the directory where the store files will be stored must be specified when initializing the cache. -ActionController::Base.cache_store = :synchronized_memory_store +ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" -6) +ActiveSupport::Cache::CompressedMemCacheStore+: Works just like the regular +MemCacheStore+ but uses GZip to decompress/compress on read/write. +With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts. + +Note that the cache will grow until the disk is full unless you periodically clear out old entries. + +h4. ActiveSupport::Cache::MemCacheStore + +This cache store uses Danga's +memcached+ server to provide a centralized cache for your application. Rails uses the bundled +memcached-client+ gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy. + +When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites. + +The +write+ and +fetch+ methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify +:raw+ to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operation like +increment+ and +decrement+ only on raw values. You can also specify +:unless_exist+ if you don't want memcached to overwrite an existing entry. -ActionController::Base.cache_store = :compressed_mem_cache_store, "localhost" +ActionController::Base.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com" -7) Custom store: You can define your own cache store (new in Rails 2.1). +h4. Custom Cache Stores + +You can create your own custom cache store by simply extending +ActiveSupport::Cache::Store+ and implementing the appropriate methods. In this way, you can swap in any number of caching technologies into your Rails application. + +To use a custom cache store, simple set the cache store to a new instance of the class. -ActionController::Base.cache_store = MyOwnStore.new("parameter") +ActionController::Base.cache_store = MyCacheStore.new -NOTE: +config.cache_store+ can be used in place of +ActionController::Base.cache_store+ in your +Rails::Initializer.run+ block in +environment.rb+ +h4. Cache Keys -In addition to all of this, Rails also adds the +ActiveRecord::Base#cache_key+ method that generates a key using the class name, +id+ and +updated_at+ timestamp (if available). +The keys used in a cache can be any object that responds to either +:cache_key+ or to +:to_param+. You can implement the +:cache_key+ method on your classes if you need to generate custom keys. ActiveRecord will generate keys based on the class name and record id. -You can access these cache stores at a low level for storing queries and other objects. Here's an example: +You can use Hashes and Arrays of values as cache keys. -Rails.cache.read("city") # => nil -Rails.cache.write("city", "Duckburgh") -Rails.cache.read("city") # => "Duckburgh" +# This is a legal cache key +Rails.cache.read(:site => "mysite", :owners => [owner_1, owner2]) +The keys you use on +Rails.cache+ will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with +Rails.cache+ and then try to pull them out with the +memcache-client+ gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules. + h3. Conditional GET support Conditional GETs are a feature of the HTTP specification that provide a way for web servers to tell browsers that the response to a GET request hasn't changed since the last request and can be safely pulled from the browser cache. @@ -369,6 +378,7 @@ h3. Further reading h3. Changelog +* Feb 17, 2011: Document 3.0.0 changes to ActiveSupport::Cache * May 02, 2009: Formatting cleanups * April 26, 2009: Clean up typos in submitted patch * April 1, 2009: Made a bunch of small fixes -- cgit v1.2.3 From 017012b43ac80a79b99afbe35e1d9e7f73e62ee9 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Thu, 17 Feb 2011 10:18:56 -0600 Subject: Add description of transaction callbacks to ActiveRecord callbacks guide. --- .../active_record_validations_callbacks.textile | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index 6f857ab9cc..ea8cf8afaf 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -1160,8 +1160,43 @@ In this example, the +after_create+ method would be called whenever a +Registrat config.active_record.observers = :mailer_observer +h3. Transaction Callbacks + +There are two additional callbacks that are triggered by the completion of a database transaction: +after_commit+ and +after_rollback+. These callbacks are very similar to the +after_save+ callback except that they don't execute until after database changes have either been committed or rolled back. They are most useful when your active record models need to interact with external systems which are not part of the database transaction. + +Consider, for example, the previous example where the +PictureFile+ model needs to delete a file after a record is destroyed. If anything raises an exception after the +after_destroy+ callback is called and the transaction rolls back, the file will have been deleted and the model will be left in an inconsistent state. For example, suppose that +picture_file_2+ in the code below is not valid and the +save!+ method raises an error. + + +PictureFile.transaction do + picture_file_1.destroy + picture_file_2.save! +end + + +By using the +after_commit+ callback we can account for this case. + + +class PictureFile < ActiveRecord::Base + attr_accessor :delete_file + + after_destroy do |picture_file| + picture_file.delete_file = picture_file.filepath + end + + after_commit do |picture_file| + if picture_file.delete_file && File.exist?(picture_file.delete_file) + File.delete(picture_file.delete_file) + picture_file.delete_file = nil + end + end +end + + +The +after_commit+ and +after_rollback+ callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. + h3. Changelog +* February 17, 2011: Add description of transaction callbacks. * July 20, 2010: Fixed typos and rephrased some paragraphs for clarity. "Jaime Iniesta":http://jaimeiniesta.com * May 24, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * May 15, 2010: Validation Errors section updated by "Emili Parreño":http://www.eparreno.com -- cgit v1.2.3 From 23a5be74ced0e045bbb58b11b2428d948690dfed Mon Sep 17 00:00:00 2001 From: genlinux Date: Thu, 17 Feb 2011 22:40:58 +0530 Subject: commas to set off expressions that interrupt sentence flow --- activerecord/RUNNING_UNIT_TESTS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS index 18e3936d8a..b3d376772e 100644 --- a/activerecord/RUNNING_UNIT_TESTS +++ b/activerecord/RUNNING_UNIT_TESTS @@ -1,8 +1,8 @@ == Creating the test database The default names for the test databases are "activerecord_unittest" and -"activerecord_unittest2". If you want to use another database name then be sure -to update the connection adapter setups you want to test with in +"activerecord_unittest2". If you want to use another database name, then be sure +to update the connection adapter setups you want to test within test/connections//connection.rb. When you have the database online, you can import the fixture tables with the test/schema/*.sql files. @@ -32,7 +32,7 @@ being initialized - you can initialize the schema with: rake test_mysql TEST=test/cases/aaa_create_tables_test.rb rake mysql:build_databases - + To setup the testing environment for PostgreSQL use this command: rake postgresql:build_databases -- cgit v1.2.3 From 1c21f75f1b32ee8723c74a9bc7af70dfa02a509c Mon Sep 17 00:00:00 2001 From: Kedar Mhaswade Date: Thu, 17 Feb 2011 16:27:01 -0800 Subject: Created a new Troubleshooting section in the migrations guide. Added a bullet point there showing how the Uninitialized Constant (missing migration class) error looks like. --- railties/guides/source/migrations.textile | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 0d13fbc10a..1a23abff2c 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -82,7 +82,7 @@ On databases that support transactions with statements that change the schema (s h4. What's in a Name -Migrations are stored in files in +db/migrate+, one for each migration class. The name of the file is of the form +YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The name of the migration class (CamelCased version) should match the latter part of the file name. For example +20080906120000_create_products.rb+ should define +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you have to update the name of the class inside or Rails will complain about a missing class. +Migrations are stored in files in +db/migrate+, one for each migration class. The name of the file is of the form +YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The name of the migration class (CamelCased version) should match the latter part of the file name. For example +20080906120000_create_products.rb+ should define +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you have to update the name of the class inside or Rails will complain about a "missing class":#missing-class-while-running-a-migration. Internally Rails only uses the migration's number (the timestamp) to identify them. Prior to Rails 2.1 the migration number started at 1 and was incremented each time a migration was generated. With multiple developers it was easy for these to clash requiring you to rollback migrations and renumber them. With Rails 2.1 this is largely avoided by using the creation time of the migration to identify them. You can revert to the old numbering scheme by adding the following line to +config/application.rb+. @@ -588,6 +588,19 @@ Validations such as +validates :foreign_key, :uniqueness => true+ are one way in Although Active Record does not provide any tools for working directly with such features, the +execute+ method can be used to execute arbitrary SQL. There are also a number of plugins such as "foreign_key_migrations":http://github.com/harukizaemon/redhillonrails/tree/master/foreign_key_migrations/ which add foreign key support to Active Record (including support for dumping foreign keys in +db/schema.rb+). +h3. Troubleshooting + +h4. Missing Class While Running a Migration + +Sometimes the names of the migration classes and migration files don't match. In such cases, +rake db:migrate+ generates the following error: + +rake aborted! +An error has occurred, this and all later migrations canceled: + +uninitialized constant CreateOs + +When you get such an error, make sure the names match. For example, in above case, name of the migration class was +CreateOs+, and the name of the migration file was +20110217235207_create_oses.rb+. One possible way to fix this is to delete the migration file, modify pluralization rules for your model and recreate and rerun the migration. + h3. Changelog * July 15, 2010: minor typos corrected by "Jaime Iniesta":http://jaimeiniesta.com -- cgit v1.2.3 From a906deabc6554f6271b3d9a84384c101edaad921 Mon Sep 17 00:00:00 2001 From: Nicholas Rowe Date: Thu, 17 Feb 2011 20:34:22 -0500 Subject: fix type: remove extra period --- activemodel/lib/active_model/naming.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 44aedc8efd..3f430f94a6 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -69,7 +69,7 @@ module ActiveModel # # Providing the functionality that ActiveModel::Naming provides in your object # is required to pass the Active Model Lint test. So either extending the provided - # method below, or rolling your own is required.. + # method below, or rolling your own is required. module Naming # Returns an ActiveModel::Name object for module. It can be # used to retrieve all kinds of naming-related information. -- cgit v1.2.3 From 9a9d895481ada301143c0554dabd4ec9914b8703 Mon Sep 17 00:00:00 2001 From: Nicholas Rowe Date: Thu, 17 Feb 2011 20:46:52 -0500 Subject: Fix Typos: remove several occurences of the the --- actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb | 2 +- activerecord/lib/active_record/associations.rb | 2 +- activerecord/lib/active_record/relation/batches.rb | 2 +- activerecord/test/cases/autosave_association_test.rb | 2 +- railties/guides/source/action_view_overview.textile | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index b4e61f2034..014a03c54d 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -21,7 +21,7 @@ module ActionView @controller = controller end - # Add the the extension +ext+ if not present. Return full URLs otherwise untouched. + # Add the extension +ext+ if not present. Return full URLs otherwise untouched. # Prefix with /dir/ if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 398936b3d8..b39f6a49ae 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -793,7 +793,7 @@ module ActiveRecord # belongs_to :dungeon # end # - # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are + # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+ # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, # Active Record doesn't know anything about these inverse relationships and so no object diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 359af9820f..bf5a60f458 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -39,7 +39,7 @@ module ActiveRecord # ascending on the primary key ("id ASC") to make the batch ordering # work. This also mean that this method only works with integer-based # primary keys. You can't set the limit either, that's used to control - # the the batch sizes. + # the batch sizes. # # Example: # diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 11c0c5b0ef..8688ebc617 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -747,7 +747,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase 2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") } before = @pirate.send(association_name).map { |c| c.mark_for_destruction ; c } - # Stub the destroy method of the the second child to raise an exception + # Stub the destroy method of the second child to raise an exception class << before.last def destroy(*args) super diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index e1fc0e7732..39f4c33397 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -888,7 +888,7 @@ Note: Only the +option+ tags are returned, you have to wrap this call in a regul h5. options_from_collection_for_select -Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. +Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. # options_from_collection_for_select(collection, value_method, text_method, selected = nil) -- cgit v1.2.3 From 7d9a80be93509b47796318caba08c79ab92f4c8d Mon Sep 17 00:00:00 2001 From: Nicholas Rowe Date: Thu, 17 Feb 2011 20:49:25 -0500 Subject: Remove awkward phrasing from Ajax on Rails guide --- railties/guides/source/ajax_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile index 3d7fcdc198..b80df4aa58 100644 --- a/railties/guides/source/ajax_on_rails.textile +++ b/railties/guides/source/ajax_on_rails.textile @@ -42,7 +42,7 @@ You are ready to add some AJAX love to your Rails app! h4. The Quintessential AJAX Rails Helper: link_to_remote -Let's start with the the probably most often used helper: +link_to_remote+, which has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers. +Let's start with what is probably the most often used helper: +link_to_remote+. It has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers. The signature of +link_to_remote+ function is the same as that of the standard +link_to+ helper: -- cgit v1.2.3 From ad3e4e3af698afda19549505cf82efb5eb6427f2 Mon Sep 17 00:00:00 2001 From: Nicholas Rowe Date: Fri, 18 Feb 2011 14:43:53 -0500 Subject: Clarify Example in ActiveRecord base --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index effb17b2ff..cb3279174f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -183,7 +183,7 @@ module ActiveRecord #:nodoc: # Person.find_by_user_name_and_password #with dynamic finder # # Person.where(:user_name => user_name, :password => password, :gender => 'male').first - # Payment.find_by_user_name_and_password_and_gender + # Payment.find_by_user_name_and_password_and_gender(user_name, password, 'male') # # It's even possible to call these dynamic finder methods on relations and named scopes. # -- cgit v1.2.3 From 2acb5e348d2e145119a512a613a986aaf5149211 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 18 Feb 2011 22:54:02 +0100 Subject: removes unrealistic example (authentication plus gender?), that it is not needed anyway --- activerecord/lib/active_record/base.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cb3279174f..c48ba3114e 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -180,10 +180,7 @@ module ActiveRecord #:nodoc: # It's also possible to use multiple attributes in the same find by separating them with "_and_". # # Person.where(:user_name => user_name, :password => password).first - # Person.find_by_user_name_and_password #with dynamic finder - # - # Person.where(:user_name => user_name, :password => password, :gender => 'male').first - # Payment.find_by_user_name_and_password_and_gender(user_name, password, 'male') + # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder # # It's even possible to call these dynamic finder methods on relations and named scopes. # -- cgit v1.2.3 From 7c52da44edec9eae6c26f9ff3187d1cc27e5c120 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 18 Feb 2011 23:03:54 +0100 Subject: Revert "Created a new Troubleshooting section in the migrations guide." Mismatches between migrations and constants have nothing to do with pluralization rules or model names. The mismatch happens between the camelization of the filename minus timestamp, and the name of the migration class itself. This reverts commit 1c21f75f1b32ee8723c74a9bc7af70dfa02a509c. --- railties/guides/source/migrations.textile | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 1a23abff2c..0d13fbc10a 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -82,7 +82,7 @@ On databases that support transactions with statements that change the schema (s h4. What's in a Name -Migrations are stored in files in +db/migrate+, one for each migration class. The name of the file is of the form +YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The name of the migration class (CamelCased version) should match the latter part of the file name. For example +20080906120000_create_products.rb+ should define +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you have to update the name of the class inside or Rails will complain about a "missing class":#missing-class-while-running-a-migration. +Migrations are stored in files in +db/migrate+, one for each migration class. The name of the file is of the form +YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The name of the migration class (CamelCased version) should match the latter part of the file name. For example +20080906120000_create_products.rb+ should define +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you have to update the name of the class inside or Rails will complain about a missing class. Internally Rails only uses the migration's number (the timestamp) to identify them. Prior to Rails 2.1 the migration number started at 1 and was incremented each time a migration was generated. With multiple developers it was easy for these to clash requiring you to rollback migrations and renumber them. With Rails 2.1 this is largely avoided by using the creation time of the migration to identify them. You can revert to the old numbering scheme by adding the following line to +config/application.rb+. @@ -588,19 +588,6 @@ Validations such as +validates :foreign_key, :uniqueness => true+ are one way in Although Active Record does not provide any tools for working directly with such features, the +execute+ method can be used to execute arbitrary SQL. There are also a number of plugins such as "foreign_key_migrations":http://github.com/harukizaemon/redhillonrails/tree/master/foreign_key_migrations/ which add foreign key support to Active Record (including support for dumping foreign keys in +db/schema.rb+). -h3. Troubleshooting - -h4. Missing Class While Running a Migration - -Sometimes the names of the migration classes and migration files don't match. In such cases, +rake db:migrate+ generates the following error: - -rake aborted! -An error has occurred, this and all later migrations canceled: - -uninitialized constant CreateOs - -When you get such an error, make sure the names match. For example, in above case, name of the migration class was +CreateOs+, and the name of the migration file was +20110217235207_create_oses.rb+. One possible way to fix this is to delete the migration file, modify pluralization rules for your model and recreate and rerun the migration. - h3. Changelog * July 15, 2010: minor typos corrected by "Jaime Iniesta":http://jaimeiniesta.com -- cgit v1.2.3 From 220cb107b672d65fdc0488d4ff310ab04b62b463 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 18 Feb 2011 23:13:29 +0100 Subject: removes indent from guides example --- railties/guides/source/association_basics.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index 23bfafa640..e5b8c73c43 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -168,7 +168,7 @@ end With +:through => :sections+ specified, Rails will now understand: - @document.paragraphs +@document.paragraphs h4. The +has_one :through+ Association -- cgit v1.2.3