Permalink
Browse files

merges docrails

  • Loading branch information...
2 parents fbfa30a + 220cb10 commit f41bf6938fd4aa5a83777cc767f7f32ace5f6539 @fxn fxn committed Feb 18, 2011
Showing with 452 additions and 1,371 deletions.
  1. +1 −1 actionmailer/lib/action_mailer/base.rb
  2. +37 −0 actionpack/lib/action_controller/metal/renderers.rb
  3. +1 −1 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
  4. +7 −0 actionpack/lib/action_view/helpers/text_helper.rb
  5. +1 −1 activemodel/lib/active_model/naming.rb
  6. +4 −0 activemodel/lib/active_model/observing.rb
  7. +3 −3 activerecord/RUNNING_UNIT_TESTS
  8. +1 −1 activerecord/lib/active_record/associations.rb
  9. +1 −4 activerecord/lib/active_record/base.rb
  10. +1 −1 activerecord/lib/active_record/relation/batches.rb
  11. +10 −0 activerecord/test/cases/autosave_association_test.rb
  12. +1 −1 activesupport/lib/active_support/core_ext/string/inflections.rb
  13. +16 −0 railties/guides/assets/stylesheets/fixes.css
  14. +1 −1 railties/guides/source/action_view_overview.textile
  15. +37 −0 railties/guides/source/active_record_validations_callbacks.textile
  16. +1 −1 railties/guides/source/active_support_core_extensions.textile
  17. +1 −1 railties/guides/source/ajax_on_rails.textile
  18. +1 −1 railties/guides/source/api_documentation_guidelines.textile
  19. +6 −0 railties/guides/source/association_basics.textile
  20. +52 −42 railties/guides/source/caching_with_rails.textile
  21. +17 −15 railties/guides/source/command_line.textile
  22. +1 −1 railties/guides/source/configuring.textile
  23. +2 −2 railties/guides/source/form_helpers.textile
  24. +5 −3 railties/guides/source/getting_started.textile
  25. +5 −5 railties/guides/source/i18n.textile
  26. +2 −0 railties/guides/source/layout.html.erb
  27. +237 −1,286 railties/guides/source/plugins.textile
View
2 actionmailer/lib/action_mailer/base.rb
@@ -222,7 +222,7 @@ module ActionMailer #:nodoc:
#
# An interceptor object must implement the <tt>:delivering_email(message)</tt> 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
View
37 actionpack/lib/action_controller/metal/renderers.rb
@@ -2,6 +2,7 @@
require 'active_support/core_ext/object/blank'
module ActionController
+ # See <tt>Renderers.add</tt>
def self.add_renderer(key, &block)
Renderers.add(key, &block)
end
@@ -39,7 +40,43 @@ def _handle_render_options(options)
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
+ # <tt>AbstractController::Rendering#render</tt>. 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
+ # <tt>Mime::Type.register</tt>.
+ #
+ # 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
+ # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
+ # <tt>ActionController::MimeResponds#respond_with</tt>
def self.add(key, &block)
define_method("_render_option_#{key}", &block)
RENDERERS[key] = block
View
2 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -21,7 +21,7 @@ def initialize(config, controller)
@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 <tt>/dir/</tt> 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.
View
7 actionpack/lib/action_view/helpers/text_helper.rb
@@ -234,6 +234,10 @@ def word_wrap(text, *args)
#
# You can pass any HTML attributes into <tt>html_options</tt>. These
# will be added to all created paragraphs.
+ #
+ # ==== Options
+ # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
+ #
# ==== Examples
# my_text = "Here is some basic text...\n...with a line break."
#
@@ -247,6 +251,9 @@ def word_wrap(text, *args)
#
# simple_format("Look ma! A class!", :class => 'description')
# # => "<p class='description'>Look ma! A class!</p>"
+ #
+ # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
+ # # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options={}, options={})
text = ''.html_safe if text.nil?
start_tag = tag('p', html_options, true)
View
2 activemodel/lib/active_model/naming.rb
@@ -69,7 +69,7 @@ def _singularize(string, replacement='_')
#
# 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.
View
4 activemodel/lib/active_model/observing.rb
@@ -156,6 +156,10 @@ def notify_observers(method)
# 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
View
6 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/<your database>/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
View
2 activerecord/lib/active_record/associations.rb
@@ -804,7 +804,7 @@ def association_instance_set(name, association)
# 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
View
5 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
+ # 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.
#
View
2 activerecord/lib/active_record/relation/batches.rb
@@ -39,7 +39,7 @@ def find_each(options = {})
# 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:
#
View
10 activerecord/test/cases/autosave_association_test.rb
@@ -793,6 +793,7 @@ def destroy(*args)
def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
+<<<<<<< HEAD
@tenderlove
tenderlove Feb 18, 2011

mergefail! :'(

assert !@pirate.parrots.any? { |parrot| parrot.marked_for_destruction? }
@pirate.parrots.each { |parrot| parrot.mark_for_destruction }
@@ -808,6 +809,15 @@ def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marke
def test_should_skip_validation_on_habtm_if_marked_for_destruction
2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
+=======
+ # Stub the destroy method of the second child to raise an exception
+ class << before.last
+ def destroy(*args)
+ super
+ raise 'Oh noes!'
+ end
+ end
+>>>>>>> 220cb107b672d65fdc0488d4ff310ab04b62b463
@pirate.parrots.each { |parrot| parrot.name = '' }
assert !@pirate.valid?
View
2 activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -3,7 +3,7 @@
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"
#
View
16 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;
+}
View
2 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.
<ruby>
# options_from_collection_for_select(collection, value_method, text_method, selected = nil)
View
37 railties/guides/source/active_record_validations_callbacks.textile
@@ -314,6 +314,8 @@ class Essay < ActiveRecord::Base
end
</ruby>
+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+
@@ -1158,8 +1160,43 @@ In this example, the +after_create+ method would be called whenever a +Registrat
config.active_record.observers = :mailer_observer
</ruby>
+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.
+
+<ruby>
+PictureFile.transaction do
+ picture_file_1.destroy
+ picture_file_2.save!
+end
+</ruby>
+
+By using the +after_commit+ callback we can account for this case.
+
+<ruby>
+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
+</ruby>
+
+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
View
2 railties/guides/source/active_support_core_extensions.textile
@@ -2190,7 +2190,7 @@ Array.wrap(0) # => [0]
This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
-* If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns such a +nil+ right away.
+* If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns +nil+ right away.
* If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> raises an exception, while <tt>Array.wrap</tt> 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.
View
2 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:
View
2 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. All Rails documentation should 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. All Rails documentation should 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.
View
6 railties/guides/source/association_basics.textile
@@ -165,6 +165,12 @@ class Paragraph < ActiveRecord::Base
end
</ruby>
+With +:through => :sections+ specified, Rails will now understand:
+
+<ruby>
+@document.paragraphs
+</ruby>
+
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:
View
94 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.
<ruby>
-ActionController::Base.cache_store = :memory_store
+config.cache_store = :memory_store
</ruby>
-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.
-<ruby>
-ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
-</ruby>
+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
-<ruby>
-ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
-</ruby>
+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.
<ruby>
-ActionController::Base.cache_store = :mem_cache_store, "localhost"
+ActionController::Base.cache_store = :memory_store, :size => 64.megabytes
</ruby>
-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.
<ruby>
-ActionController::Base.cache_store = :synchronized_memory_store
+ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
</ruby>
-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.
<ruby>
-ActionController::Base.cache_store = :compressed_mem_cache_store, "localhost"
+ActionController::Base.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
</ruby>
-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.
<ruby>
-ActionController::Base.cache_store = MyOwnStore.new("parameter")
+ActionController::Base.cache_store = MyCacheStore.new
</ruby>
-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.
<ruby>
-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])
</ruby>
+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
View
32 railties/guides/source/command_line.textile
@@ -81,7 +81,7 @@ The +rails generate+ command uses templates to create a whole lot of things. You
<shell>
$ 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
<shell>
$ 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
+
</shell>
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
</ruby>
@@ -164,7 +166,7 @@ Then the view, to display our message (in +app/views/greetings/hello.html.erb+):
<p><%= @message %></p>
</html>
-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+.
<shell>
$ rails server
@@ -181,7 +183,7 @@ Rails comes with a generator for data models too:
<shell>
$ 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.
<shell>
-$ 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
...
@@ -376,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
@@ -449,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:
@@ -576,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.
View
2 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.
View
4 railties/guides/source/form_helpers.textile
@@ -188,7 +188,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
@@ -595,7 +595,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 <tt>:remote => true</tt>. 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
View
8 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
<shell>
-# bundle install
+$ bundle install
</shell>
to have them ready.
@@ -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
@@ -411,7 +411,7 @@ Rails will execute this migration command and tell you it created the Posts tabl
== CreatePosts: migrated (0.0020s) ===========================================
</shell>
-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: <tt>rake db:migrate RAILS_ENV=production</tt>.
+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: <tt>rake db:migrate RAILS_ENV=production</tt>.
h4. Adding a Link
@@ -469,6 +469,8 @@ To see your validations in action, you can use the console. The console is a com
$ rails console
</shell>
+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:
<shell>
View
10 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"
</ruby>
-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 +'&nbsp;'+).
View
2 railties/guides/source/layout.html.erb
@@ -12,6 +12,8 @@
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
+
+<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
</head>
<body class="guide">
<% if @edge %>
View
1,523 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.
<shell>
-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
</shell>
-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:
-<shell>
-rails generate plugin yaffle
-rails generate plugin yaffle --generator
-</shell>
-
-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:
-
-<shell>
-rails generate plugin yaffle --generator
-</shell>
-
-You should see the following output:
-
-<shell>
-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
-</shell>
-
-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.
<shell>
-|-- lib
-| |-- yaffle
-| `-- yaffle.rb
-`-- init.rb
+ gem install enginex
+ enginex --help
+ enginex yaffle
</shell>
-<ruby>
-# vendor/plugins/yaffle/init.rb
-
-require 'yaffle'
-</ruby>
-
-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
-
-<yaml>
-# 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
-</yaml>
-
-For this guide you'll need 2 tables/models, Hickwalls and Wickwalls, so add the following:
-
-<ruby>
-# 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
-</ruby>
-
-<ruby>
-# 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
-</ruby>
-
-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:
-
-<ruby>
-# 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
-</ruby>
-
-To run this, go to the plugin directory and run +rake+:
-
-<shell>
-cd vendor/plugins/yaffle
-rake
-</shell>
+This command will create a new directory named "yaffle" within the current directory.
-You should see output like:
+h3. Testing your newly generated plugin
-<shell>
-/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
-</shell>
+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:
<shell>
-rake DB=sqlite
-rake DB=sqlite3
-rake DB=mysql
-rake DB=postgresql
+ 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
</shell>
-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:
<ruby>
-# 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,36 +83,32 @@ class CoreExtTest < Test::Unit::TestCase
end
</ruby>
-Navigate to your plugin directory and run +rake test+:
-
-<shell>
-cd vendor/plugins/yaffle
-rake test
-</shell>
-
-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:
<shell>
- 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'
</shell>
Great - now you are ready to start development.
Then in +lib/yaffle.rb+ require +lib/core_ext+:
<ruby>
-# vendor/plugins/yaffle/lib/yaffle.rb
+# yaffle/lib/yaffle.rb
require "yaffle/core_ext"
+
+module Yaffle
+end
</ruby>
Finally, create the +core_ext.rb+ file and add the +to_squawk+ method:
<ruby>
-# vendor/plugins/yaffle/lib/yaffle/core_ext.rb
+# yaffle/lib/yaffle/core_ext.rb
String.class_eval do
def to_squawk
@@ -335,176 +117,218 @@ String.class_eval do
end
</ruby>
-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.
+
+<shell>
+ 3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
+</shell>
+
+To see this in action, change to the test/dummy directory, fire up a console and start squawking:
<shell>
$ rails console
>> "Hello World".to_squawk
=> "squawk! Hello World"
</shell>
-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:
<ruby>
-# 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
</ruby>
-Another way is to explicitly define the top-level module space for all modules and classes, like +::Hash+:
-
<ruby>
-# 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
</ruby>
-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*
-
<ruby>
-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
</ruby>
-* *vendor/plugins/yaffle/lib/yaffle.rb*
+h4. Add a Class Method
-<ruby>
-require 'yaffle/acts_as_yaffle'
-</ruby>
+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:
<ruby>
-module Yaffle
- # your code will go here
-end
-</ruby>
-
-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
-<ruby>
-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
</ruby>
-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
+<shell>
+ 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
+</shell>
+
+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:
+
+<shell>
+ cd test/dummy
+ rails generate model Hickwall last_squak:string
+ rails generate model Wickwall last_squak:string last_tweet:string
+</shell>
+
+Now you can create the necessary database tables in your testing database by navigating to your dummy app
+and migrating the database. First
+
+<shell>
+ cd test/dummy
+ rake db:migrate
+ rake db:test:prepare
+</shell>
-* *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.
<ruby>
-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
+</ruby>
- 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
+<ruby>
+# 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
</ruby>
-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+.
+
+<shell>
+ 1) Error:
+ test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest):
+ NoMethodError: undefined method `yaffle_text_field' for #<Class:0x000001016661b8>
+ /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 #<Class:0x00000101653748>
+ 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
+
+</shell>
-* *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.
<ruby>
+# 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
</ruby>
+When you run +rake+ you should see the tests all pass:
+
+<shell>
+ 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
+</shell>
+
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*
-
<ruby>
-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
</ruby>
-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:
<ruby>
+# 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
</ruby>
-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:
<shell>
-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
</shell>
-As always, start with a test:
-
-* *vendor/plugins/yaffle/test/woodpecker_test.rb:*
-
-<ruby>
-require File.dirname(__FILE__) + '/test_helper'
-
-class WoodpeckerTest < Test::Unit::TestCase
- load_schema
-
- def test_woodpecker
- assert_kind_of Woodpecker, Woodpecker.new
- end
-end
-</ruby>
-
-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:*
-
-<ruby>
-%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
-</ruby>
-
-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:*
-
-<ruby>
-class Woodpecker < ActiveRecord::Base
-end
-</ruby>
-
-Finally, add the following to your plugin's 'schema.rb':
-
-* *vendor/plugins/yaffle/test/schema.rb:*
-
-<ruby>
-create_table :woodpeckers, :force => true do |t|
- t.string :name
-end
-</ruby>
+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
-<ruby>
-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
-</ruby>
+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
-<ruby>
-%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
-</ruby>
+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.
<ruby>
-class WoodpeckersController < ActionController::Base
-
- def index
- render :text => "Squawk!"
- end
+# yaffle/init.rb
-end
+require 'yaffle'
</ruby>
-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.
+<shell>
+ cd my_app
+ rails console
+ String.instance_methods.sort
+</shell>
-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:
-<ruby>
-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
-</ruby>
+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:*
+<shell>
+rake rdoc
+</shell>
-<ruby>
-%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
-</ruby>
+!!!!!!!!!!!!!! Make sure these still make sense. Add any references that you see fit. !!!!!!!!!!!!!
-* *vendor/plugins/yaffle/lib/app/helpers/woodpeckers_helper.rb:*
+h4. References
-<ruby>
-module WoodpeckersHelper
-
- def tweet(text)
- "Tweet! #{text}"
- end
-
-end
-</ruby>
-
-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*
-
-<ruby>
-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
-</ruby>
-
-Once you see the tests fail by running 'rake', you can make them pass with:
-
-* *vendor/plugins/yaffle/lib/yaffle.rb*
-
-<ruby>
-require "yaffle/routing"
-</ruby>
-
-* *vendor/plugins/yaffle/lib/yaffle/routing.rb*
-
-<ruby>
-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
-</ruby>
-
-* *config/routes.rb*
-
-<ruby>
-ActionController::Routing::Routes.draw do |map|
- map.yaffles
-end
-</ruby>
-
-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*
-
-<ruby>
-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
-</ruby>
-
-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*
-
-<ruby>
-class YaffleDefinitionGenerator < Rails::Generator::Base
- def manifest
- record do |m|
- m.file "definition.txt", "definition.txt"
- end
- end
-end
-</ruby>
-
-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:
-
-<shell>
-rails generate
-</shell>
-
-You should see something like this:
-
-<shell>
-Installed Generators
- Plugins (vendor/plugins): yaffle_definition
- Builtin: controller, integration_test, mailer, migration, model, observer, plugin, resource, scaffold, session_migration
-</shell>
-
-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:
-
-<shell>
-Description:
- Adds a file with the definition of a Yaffle to the app's main directory
-</shell>
-
-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*
-
-<ruby>
-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
-</ruby>
-
-Run +rake+ to watch the test fail, then make the test pass add the following:
-
-* *vendor/plugins/yaffle/generators/yaffle/templates/definition.txt*
-
-<shell>
-Yaffle: A bird
-</shell>
-
-* *vendor/plugins/yaffle/lib/yaffle.rb*
-
-<ruby>
-require "yaffle/commands"
-</ruby>
-
-* *vendor/plugins/yaffle/lib/commands.rb*
-
-<ruby>
-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
-</ruby>
-
-Finally, call your new method in the manifest:
-
-* *vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb*
-
-<ruby>
-class YaffleGenerator < Rails::Generator::NamedBase
- def manifest
- m.yaffle_definition
- end
-end
-</ruby>
-
-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*
-
-<ruby>
-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
-</ruby>
-
-Run +rake+ to watch the test fail, then make the test pass add the following:
-
-* *vendor/plugins/yaffle/lib/yaffle.rb*
-
-<ruby>
-require "yaffle/commands"
-</ruby>
-
-* *vendor/plugins/yaffle/lib/yaffle/commands.rb*
-
-<ruby>
-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
-</ruby>
-
-* *vendor/plugins/yaffle/generators/yaffle_route/yaffle_route_generator.rb*
-
-<ruby>
-class YaffleRouteGenerator < Rails::Generator::Base
- def manifest
- record do |m|
- m.yaffle_route
- end
- end
-end
-</ruby>
-
-To see this work, type:
-
-<shell>
-rails generate yaffle_route
-rails destroy yaffle_route
-</shell>
-
-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:*
-
-<ruby>
-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
-</ruby>
-
-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:*
-
-<ruby>
-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
-</ruby>
-
-h4. Call Migrations Directly
-
-* *vendor/plugins/yaffle/lib/yaffle.rb:*
-
-<ruby>
-Dir.glob(File.join(File.dirname(__FILE__), "db", "migrate", "*")).each do |file|
- require file
-end
-</ruby>
-
-* *db/migrate/20081116181115_create_birdhouses.rb:*
-
-<ruby>
-class CreateBirdhouses < ActiveRecord::Migration
- def self.up
- Yaffle::CreateBirdhouses.up
- end
-
- def self.down
- Yaffle::CreateBirdhouses.down
- end
-end
-</ruby>
-
-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*
-
-<ruby>
-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
-</ruby>
-
-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*
-
-<ruby>
-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
-</ruby>
-
-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:
-
-<shell>
-rails generate yaffle_migration bird
-</shell>
-
-and you will see a new file:
-
-* *db/migrate/20080529225649_add_yaffle_fields_to_birds.rb*
-