aboutsummaryrefslogtreecommitdiffstats
path: root/railties/guides/source
diff options
context:
space:
mode:
authorXavier Noria <fxn@hashref.com>2011-02-18 23:22:15 +0100
committerXavier Noria <fxn@hashref.com>2011-02-18 23:22:15 +0100
commitf41bf6938fd4aa5a83777cc767f7f32ace5f6539 (patch)
treeeb85baf61ec970ca090ed057536c2d0ee1b18e5b /railties/guides/source
parentfbfa30a1eeaaab22ac8f43a4137ec8282920fe15 (diff)
parent220cb107b672d65fdc0488d4ff310ab04b62b463 (diff)
downloadrails-f41bf6938fd4aa5a83777cc767f7f32ace5f6539.tar.gz
rails-f41bf6938fd4aa5a83777cc767f7f32ace5f6539.tar.bz2
rails-f41bf6938fd4aa5a83777cc767f7f32ace5f6539.zip
merges docrails
Diffstat (limited to 'railties/guides/source')
-rw-r--r--railties/guides/source/action_view_overview.textile2
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile37
-rw-r--r--railties/guides/source/active_support_core_extensions.textile2
-rw-r--r--railties/guides/source/ajax_on_rails.textile2
-rw-r--r--railties/guides/source/api_documentation_guidelines.textile2
-rw-r--r--railties/guides/source/association_basics.textile6
-rw-r--r--railties/guides/source/caching_with_rails.textile94
-rw-r--r--railties/guides/source/command_line.textile32
-rw-r--r--railties/guides/source/configuring.textile2
-rw-r--r--railties/guides/source/form_helpers.textile4
-rw-r--r--railties/guides/source/getting_started.textile8
-rw-r--r--railties/guides/source/i18n.textile10
-rw-r--r--railties/guides/source/layout.html.erb2
-rw-r--r--railties/guides/source/plugins.textile1449
14 files changed, 331 insertions, 1321 deletions
diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile
index e1fc0e7732..39f4c33397 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/railties/guides/source/action_view_overview.textile
@@ -888,7 +888,7 @@ Note: Only the +option+ tags are returned, you have to wrap this call in a regul
h5. options_from_collection_for_select
-Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
+Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
<ruby>
# options_from_collection_for_select(collection, value_method, text_method, selected = nil)
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index a15571fe58..ea8cf8afaf 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -314,6 +314,8 @@ class Essay < ActiveRecord::Base
end
</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
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index cf9ebf44e6..9da8ecc6fc 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -2190,7 +2190,7 @@ Array.wrap(0) # => [0]
This method is similar in purpose to <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.
diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile
index 3d7fcdc198..b80df4aa58 100644
--- a/railties/guides/source/ajax_on_rails.textile
+++ b/railties/guides/source/ajax_on_rails.textile
@@ -42,7 +42,7 @@ You are ready to add some AJAX love to your Rails app!
h4. The Quintessential AJAX Rails Helper: link_to_remote
-Let's start with the the probably most often used helper: +link_to_remote+, which has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers.
+Let's start with what is probably the most often used helper: +link_to_remote+. It has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers.
The signature of +link_to_remote+ function is the same as that of the standard +link_to+ helper:
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile
index 68665dce98..7433507866 100644
--- a/railties/guides/source/api_documentation_guidelines.textile
+++ b/railties/guides/source/api_documentation_guidelines.textile
@@ -27,7 +27,7 @@ Communicate to the reader the current way of doing things, both explicitly and i
Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil?
-The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. 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.
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index f22f41e8b1..e5b8c73c43 100644
--- a/railties/guides/source/association_basics.textile
+++ b/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:
diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile
index 63c52da32a..1b5ec40d16 100644
--- a/railties/guides/source/caching_with_rails.textile
+++ b/railties/guides/source/caching_with_rails.textile
@@ -238,86 +238,95 @@ h3. Cache Stores
Rails provides different stores for the cached data created by action and fragment caches. Page caches are always stored on disk.
-Rails 2.1 and above provide +ActiveSupport::Cache::Store+ which can be used to cache strings. Some cache store implementations, like +MemoryStore+, are able to cache arbitrary Ruby objects, but don't count on every cache store to be able to do that.
+h4. Configuration
-The default cache stores provided with Rails include:
-
-1) +ActiveSupport::Cache::MemoryStore+: A cache store implementation which stores everything into memory in the same process. If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then this means that your Rails server process instances won't be able to share cache data with each other. If your application never performs manual cache item expiry (e.g. when you‘re using generational cache keys), then using +MemoryStore+ is ok. Otherwise, consider carefully whether you should be using this cache store.
-
-+MemoryStore+ is not only able to store strings, but also arbitrary Ruby objects.
-
-+MemoryStore+ is not thread-safe. Use +SynchronizedMemoryStore+ instead if you need thread-safety.
+You can set up your application's default cache store by calling +config.cache_store=+ in the Application definition inside your +config/application.rb+ file or in an Application.configure block in an environment specific configuration file (i.e. +config/environments/*.rb+). The first argument will be the cache store to use and the rest of the argument will be passed as arguments to the cache store constructor.
<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
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index 1e570c9992..81d181fe02 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -81,7 +81,7 @@ The +rails generate+ command uses templates to create a whole lot of things. You
<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.
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index eee18f1131..62b846e871 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -218,7 +218,7 @@ h4. Configuring Active Record
* +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table.
-* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:local+.
+* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:utc+ for Rails, although ActiveRecord defaults to +:local+ when used outside of Rails.
* +config.active_record.schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements.
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 40db43079c..ace433e30c 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/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
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 82700532c0..6fb54bfd49 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -195,7 +195,7 @@ h4. Installing the Required Gems
Rails applications manage gem dependencies with "Bundler":http://gembundler.com/v1.0/index.html by default. As we don't need any other gems beyond the ones in the generated +Gemfile+ we can directly run
<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>
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index e47ac7aed6..337ef52d3b 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -1,4 +1,4 @@
-h2. Rails Internationalization (I18n) API
+lh2. Rails Internationalization (I18n) API
The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for *translating your application to a single custom language* other than English or for *providing multi-language support* in your application.
@@ -649,7 +649,7 @@ Generally we recommend using YAML as a format for storing translations. There ar
h4. Translations for Active Record Models
-You can use the methods +Model.human_name+ and +Model.human_attribute_name(attribute)+ to transparently look up translations for your model and attribute names.
+You can use the methods +Model.model_name.human+ and +Model.human_attribute_name(attribute)+ to transparently look up translations for your model and attribute names.
For example when you add the following translations:
@@ -664,7 +664,7 @@ en:
# will translate User attribute "login" as "Handle"
</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;'+).
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index 91dab18b21..e924f2f6c0 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -12,6 +12,8 @@
<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 %>
diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile
index f7c4a6f85c..2135c0da4e 100644
--- a/railties/guides/source/plugins.textile
+++ b/railties/guides/source/plugins.textile
@@ -10,285 +10,71 @@ After reading this guide you should be familiar with:
* Creating a plugin from scratch
* Writing and running tests for the plugin
-* Storing models, views, controllers, helpers and even other plugins in your plugins
-* Writing generators
-* Writing custom Rake tasks in your plugin
-* Generating RDoc documentation for your plugin
-* Avoiding common pitfalls with 'init.rb'
This guide describes how to build a test-driven plugin that will:
* Extend core ruby classes like Hash and String
* Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins
-* Add a view helper that can be used in erb templates
-* Add a new generator that will generate a migration
-* Add a custom generator command
-* A custom route method that can be used in routes.rb
+* Give you information about where to put generators in your plugin.
-For the purpose of this guide pretend for a moment that you are an avid bird watcher. Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle goodness. First, you need to get setup for development.
+For the purpose of this guide pretend for a moment that you are an avid bird watcher.
+Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle
+goodness.
endprologue.
h3. Setup
-h4. Create the Basic Application
+h4. Generating the Plugin Skeleton
-The examples in this guide require that you have a working rails application. To create a simple one execute:
+Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain
+how this generator works.
<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+:
+This command will create a new directory named "yaffle" within the current directory.
-<shell>
-cd vendor/plugins/yaffle
-rake
-</shell>
-
-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,20 +83,13 @@ class CoreExtTest < Test::Unit::TestCase
end
</ruby>
-Navigate to your plugin directory and run +rake test+:
+Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method:
<shell>
-cd vendor/plugins/yaffle
-rake test
-</shell>
-
-The test above should fail with the message:
-
-<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.
@@ -318,15 +97,18 @@ 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,7 +117,13 @@ 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
@@ -343,115 +131,56 @@ $ rails console
=> "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.
-
-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.
-
-If you must reopen a class in +init.rb+ you can use +module_eval+ or +class_eval+ to avoid any issues:
-
-<ruby>
-# vendor/plugins/yaffle/init.rb
-
-Hash.class_eval do
- def is_a_special_hash?
- true
- end
-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
-
-class ::Hash
- def is_a_special_hash?
- true
- end
-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.
+A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you
+want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your Active Record models.
To begin, set up your files so that you have:
-* *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb*
-
<ruby>
-require File.dirname(__FILE__) + '/test_helper'
+# yaffle/test/acts_as_yaffle_test.rb
+
+require 'test_helper'
class ActsAsYaffleTest < Test::Unit::TestCase
end
</ruby>
-* *vendor/plugins/yaffle/lib/yaffle.rb*
-
<ruby>
-require 'yaffle/acts_as_yaffle'
-</ruby>
+# yaffle/lib/yaffle.rb
-* *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb*
+require "yaffle/core_ext"
+require 'yaffle/acts_as_yaffle'
-<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.
-
-One of the most common plugin patterns for 'acts_as_yaffle' plugins is to structure your file like so:
-
-* *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb*
-
<ruby>
-module Yaffle
- def self.included(base)
- base.send :extend, ClassMethods
- end
+# yaffle/lib/yaffle/acts_as_yaffle.rb
- module ClassMethods
- # any method placed here will apply to classes, like Hickwall
- def acts_as_something
- send :include, InstanceMethods
- end
- end
-
- module InstanceMethods
- # any method placed here will apply to instaces, like @hickwall
+module Yaffle
+ module ActsAsYaffle
+ # your code will go here
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+).
-
h4. Add a Class Method
-This plugin will expect that you've added a method to your model named 'last_squawk'. However, the plugin users might have already defined a method on their model named 'last_squawk' that they use for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'.
+This plugin will expect that you've added a method to your model named 'last_squawk'. However, the
+plugin users might have already defined a method on their model named 'last_squawk' that they use
+for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'.
To start out, write a failing test that shows the behavior you'd like:
-* *vendor/plugins/yaffle/test/acts_as_yaffle_test.rb*
-
<ruby>
-require File.dirname(__FILE__) + '/test_helper'
-
-class Hickwall < ActiveRecord::Base
- acts_as_yaffle
-end
+# yaffle/test/acts_as_yaffle_test.rb
-class Wickwall < ActiveRecord::Base
- acts_as_yaffle :yaffle_text_field => :last_tweet
-end
+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
@@ -460,936 +189,247 @@ class ActsAsYaffleTest < Test::Unit::TestCase
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
+
end
</ruby>
-To make these tests pass, you could modify your +acts_as_yaffle+ file like so:
+When you run +rake+, you should see the following:
-* *vendor/plugins/yaffle/lib/yaffle/acts_as_yaffle.rb*
+<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'
-<ruby>
-module Yaffle
- def self.included(base)
- base.send :extend, ClassMethods
- end
+ 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'
- 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
+ 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips
+</shell>
-ActiveRecord::Base.send :include, Yaffle
-</ruby>
+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:
-h4. Add an Instance Method
+<shell>
+ cd test/dummy
+ rails generate model Hickwall last_squak:string
+ rails generate model Wickwall last_squak:string last_tweet:string
+</shell>
-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.
+Now you can create the necessary database tables in your testing database by navigating to your dummy app
+and migrating the database. First
-To start out, write a failing test that shows the behavior you'd like:
+<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
-
- def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
- assert_equal "last_squawk", Hickwall.yaffle_text_field
- end
-
- def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
- assert_equal "last_tweet", Wickwall.yaffle_text_field
- end
-
- def test_hickwalls_squawk_should_populate_last_squawk
- hickwall = Hickwall.new
- hickwall.squawk("Hello World")
- assert_equal "squawk! Hello World", hickwall.last_squawk
- end
-
- def test_wickwalls_squawk_should_populate_last_tweeted_at
- wickwall = Wickwall.new
- wickwall.squawk("Hello World")
- assert_equal "squawk! Hello World", wickwall.last_tweet
- end
-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*
+We will also add code to define the acts_as_yaffle method.
<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
- end
- module InstanceMethods
- def squawk(string)
- write_attribute(self.class.yaffle_text_field, string.to_squawk)
+ module ClassMethods
+ def acts_as_yaffle(options = {})
+ # your code will go here
+ end
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:
+You can then return to the root directory (+cd ../..+) of your plugin and rerun the tests using +rake+.
<shell>
-vendor/plugins/yaffle/
-|-- lib
-| |-- app
-| | |-- controllers
-| | |-- helpers
-| | |-- models
-| | | `-- woodpecker.rb
-| | `-- views
-| |-- yaffle
-| | |-- acts_as_yaffle.rb
-| | |-- commands.rb
-| | `-- core_ext.rb
-| `-- yaffle.rb
-</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:*
+ 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'
-<ruby>
-create_table :woodpeckers, :force => true do |t|
- t.string :name
-end
-</ruby>
-
-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:*
-
-<ruby>
-require File.dirname(__FILE__) + '/test_helper'
-require 'woodpeckers_controller'
-require 'action_controller/test_process'
-
-class WoodpeckersController; def rescue_action(e) raise e end; end
-
-class WoodpeckersControllerTest < Test::Unit::TestCase
- def setup
- @controller = WoodpeckersController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
-
- ActionController::Routing::Routes.draw do |map|
- map.resources :woodpeckers
- end
- end
-
- def test_index
- get :index
- assert_response :success
- end
-end
-</ruby>
-
-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:
-
-* *vendor/plugins/yaffle/lib/yaffle.rb:*
-
-<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>
-
-* *vendor/plugins/yaffle/lib/app/controllers/woodpeckers_controller.rb:*
-
-<ruby>
-class WoodpeckersController < ActionController::Base
-
- def index
- render :text => "Squawk!"
- end
-
-end
-</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.
+ 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'
-h3. Helpers
+ 5 tests, 3 assertions, 0 failures, 2 errors, 0 skips
-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.
-
-You can test your plugin's helper as you would test any other helper:
-
-* *vendor/plugins/yaffle/test/woodpeckers_helper_test.rb*
-
-<ruby>
-require File.dirname(__FILE__) + '/test_helper'
-include WoodpeckersHelper
-
-class WoodpeckersHelperTest < Test::Unit::TestCase
- def test_tweet
- assert_equal "Tweet! Hello", tweet("Hello")
- end
-end
-</ruby>
-
-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:
-
-* *vendor/plugins/yaffle/lib/yaffle.rb:*
-
-<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>
-
-* *vendor/plugins/yaffle/lib/app/helpers/woodpeckers_helper.rb:*
-
-<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:
+</shell>
-* *vendor/plugins/yaffle/test/routing_test.rb*
+Getting closer...now we will implement the code of the acts_as_yaffle method to make the tests pass.
<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
+# yaffle/lib/yaffle/acts_as_yaffle.rb
- private
+module Yaffle
+ module ActsAsYaffle
+ extend ActiveSupport::Concern
- def assert_recognition(method, path, options)
- result = ActionController::Routing::Routes.recognize_path(path, :method => method)
- assert_equal options, result
+ included do
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"})
+ 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
-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
+ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
</ruby>
-Run +rake+ to watch the test fail, then make the test pass add the following:
-
-* *vendor/plugins/yaffle/generators/yaffle/templates/definition.txt*
+When you run +rake+ you should see the tests all pass:
<shell>
-Yaffle: A bird
+ 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
</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'.
+h4. Add an Instance Method
-To start, add the following test 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.
-* *vendor/plugins/yaffle/test/route_generator_test.rb*
+To start out, write a failing test that shows the behavior you'd like:
<ruby>
-require File.dirname(__FILE__) + '/test_helper'
-require 'rails_generator'
-require 'rails_generator/scripts/generate'
-require 'rails_generator/scripts/destroy'
+# yaffle/test/acts_as_yaffle_test.rb
+require 'test_helper'
-class RouteGeneratorTest < Test::Unit::TestCase
+class ActsAsYaffleTest < Test::Unit::TestCase
- def setup
- FileUtils.mkdir_p(File.join(fake_rails_root, "config"))
+ def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
+ assert_equal "last_squawk", Hickwall.yaffle_text_field
end
- def teardown
- FileUtils.rm_r(fake_rails_root)
+ def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
+ assert_equal "last_tweet", Wickwall.yaffle_text_field
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)
+ def test_hickwalls_squawk_should_populate_last_squawk
+ hickwall = Hickwall.new
+ hickwall.squawk("Hello World")
+ assert_equal "squawk! Hello World", hickwall.last_squawk
end
- def test_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)
+ def test_wickwalls_squawk_should_populate_last_tweeted_at
+ wickwall = Wickwall.new
+ wickwall.squawk("Hello World")
+ assert_equal "squawk! Hello World", wickwall.last_tweet
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*
+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>
-require "yaffle/commands"
-</ruby>
+# yaffle/lib/yaffle/acts_as_yaffle.rb
-* *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 Yaffle
+ module ActsAsYaffle
+ extend ActiveSupport::Concern
- module Update
- def yaffle_route
- end
- end
+ included do
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
+ 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
-</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
+ def squawk(string)
+ write_attribute(self.class.yaffle_text_field, string.to_squawk)
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
+ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
</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:
-
+Run +rake+ one final time and you should see:
<shell>
-rails generate yaffle_migration bird
+ 7 tests, 7 assertions, 0 failures, 0 errors, 0 skips
</shell>
-and you will see a new file:
-
-* *db/migrate/20080529225649_add_yaffle_fields_to_birds.rb*
-
-<ruby>
-class AddYaffleFieldsToBirds < ActiveRecord::Migration
- def self.up
- add_column :birds, :last_squawk, :string
- end
-
- def self.down
- remove_column :birds, :last_squawk
- end
-end
-</ruby>
-
-h3. Rake Tasks
-
-When you created the plugin with the built-in rails generator, it generated a rake file for you in 'vendor/plugins/yaffle/tasks/yaffle_tasks.rake'. Any rake task you add here will be available to the app.
-
-Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so:
+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)+.
-* *vendor/plugins/yaffle/tasks/yaffle_tasks.rake*
-
-<ruby>
-namespace :yaffle do
- desc "Prints out the word 'Yaffle'"
- task :squawk => :environment do
- puts "squawk!"
- end
-end
-</ruby>
+h3. Generators
-When you run +rake -T+ from your plugin you will see:
+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
-<shell>
-yaffle:squawk # Prints out the word 'Yaffle'
-</shell>
+h3. Publishing your Gem
-You can add as many files as you want in the tasks directory, and if they end in .rake Rails will pick them up.
+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:
-Note that tasks from +vendor/plugins/yaffle/Rakefile+ are not available to the main app.
+gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git'
-h3. Plugins as Gems
+After running +bundle install+, your gem functionality will be available to the application.
-Turning your rails plugin into a gem is a simple and straightforward task. This section will cover how to turn your plugin into a gem. It will not cover how to distribute that gem.
+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
-Rails 3 ignores both <tt>init.rb</tt> and <tt>rails/init.rb</tt> file of a gem. Also, the name of the plugin now is relevant since +config.gem+ tries to load it. Either name the main file after your gem, or document that users should use the +:lib+ option.
+h3. Non-Gem Plugins
-It's common practice to put any developer-centric rake tasks (such as tests, rdoc and gem package tasks) in +Rakefile+. A rake task that packages the gem might look like this:
+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/Rakefile:*
+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>
-PKG_FILES = FileList[
- '[a-zA-Z]*',
- 'generators/**/*',
- 'lib/**/*',
- 'rails/**/*',
- 'tasks/**/*',
- 'test/**/*'
-]
-
-spec = Gem::Specification.new do |s|
- s.name = "yaffle"
- s.version = "0.0.1"
- s.author = "Gleeful Yaffler"
- s.email = "yaffle@example.com"
- s.homepage = "http://yafflers.example.com/"
- s.platform = Gem::Platform::RUBY
- s.summary = "Sharing Yaffle Goodness"
- s.files = PKG_FILES.to_a
- s.require_path = "lib"
- s.has_rdoc = false
- s.extra_rdoc_files = ["README"]
-end
+# yaffle/init.rb
-desc 'Turn this plugin into a gem.'
-Rake::GemPackageTask.new(spec) do |pkg|
- pkg.gem_spec = spec
-end
+require 'yaffle'
</ruby>
-To build and install the gem locally, run the following commands:
-
+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 vendor/plugins/yaffle
-rake gem
-sudo gem install pkg/yaffle-0.0.1.gem
+ cd my_app
+ rails console
+ String.instance_methods.sort
</shell>
-To test this, create a new rails application, add +config.gem "yaffle"+ to +config/environment.rb+ and all of your plugin's functionality will be available to you.
+You can also remove the .gemspec, Gemfile and Gemfile.lock files as they will no longer be needed.
h3. RDoc Documentation
@@ -1410,108 +450,19 @@ Once your comments are good to go, navigate to your plugin directory and run:
rake rdoc
</shell>
-h3. Appendix
-
-If you prefer to use RSpec instead of Test::Unit, you may be interested in the "RSpec Plugin Generator":http://github.com/patmaddox/rspec-plugin-generator.
+!!!!!!!!!!!!!! Make sure these still make sense. Add any references that you see fit. !!!!!!!!!!!!!
h4. References
-* "Complete Guide to Rails Plugins - Part 1":http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-i
-* "Complete Guide to Rails Plugins - Part 2":http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-ii
-* "Attachment_fu Plugin":http://github.com/technoweenie/attachment_fu/tree/master
-* "Keeping init.rb thin":http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html
+* "Developing a RubyGem using Bundler":https://github.com/radar/guides/blob/master/gem-development.md
+* "Using Gemspecs As Intended":http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/
+* "Gemspec Reference":http://docs.rubygems.org/read/chapter/20
* "GemPlugins":http://www.mbleigh.com/2008/06/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins
-* "Extending Routes":http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2
-
-h4. Contents of +lib/yaffle.rb+
-
-* *vendor/plugins/yaffle/lib/yaffle.rb:*
-
-<ruby>
-require "yaffle/core_ext"
-require "yaffle/acts_as_yaffle"
-require "yaffle/commands"
-require "yaffle/routing"
-
-%w{ models controllers helpers }.each do |dir|
- path = File.join(File.dirname(__FILE__), 'app', dir)
- $LOAD_PATH << path
- ActiveSupport::Dependencies.autoload_paths << path
- ActiveSupport::Dependencies.autoload_once_paths.delete(path)
-end
-
-# optionally:
-# Dir.glob(File.join(File.dirname(__FILE__), "db", "migrate", "*")).each do |file|
-# require file
-# end
-</ruby>
-
-h4. Final Plugin Directory Structure
-
-The final plugin should have a directory structure that looks something like this:
-
-<shell>
-|-- MIT-LICENSE
-|-- README
-|-- Rakefile
-|-- generators
-| |-- yaffle_definition
-| | |-- USAGE
-| | |-- templates
-| | | `-- definition.txt
-| | `-- yaffle_definition_generator.rb
-| |-- yaffle_migration
-| | |-- USAGE
-| | |-- templates
-| | `-- yaffle_migration_generator.rb
-| `-- yaffle_route
-| |-- USAGE
-| |-- templates
-| `-- yaffle_route_generator.rb
-|-- install.rb
-|-- lib
-| |-- app
-| | |-- controllers
-| | | `-- woodpeckers_controller.rb
-| | |-- helpers
-| | | `-- woodpeckers_helper.rb
-| | `-- models
-| | `-- woodpecker.rb
-| |-- db
-| | `-- migrate
-| | `-- 20081116181115_create_birdhouses.rb
-| |-- yaffle
-| | |-- acts_as_yaffle.rb
-| | |-- commands.rb
-| | |-- core_ext.rb
-| | `-- routing.rb
-| `-- yaffle.rb
-|-- pkg
-| `-- yaffle-0.0.1.gem
-|-- rails
-| `-- init.rb
-|-- tasks
-| `-- yaffle_tasks.rake
-|-- test
-| |-- acts_as_yaffle_test.rb
-| |-- core_ext_test.rb
-| |-- database.yml
-| |-- debug.log
-| |-- definition_generator_test.rb
-| |-- migration_generator_test.rb
-| |-- route_generator_test.rb
-| |-- routes_test.rb
-| |-- schema.rb
-| |-- test_helper.rb
-| |-- woodpecker_test.rb
-| |-- woodpeckers_controller_test.rb
-| |-- wookpeckers_helper_test.rb
-| |-- yaffle_plugin.sqlite3.db
-| `-- yaffle_test.rb
-`-- uninstall.rb
-</shell>
+* "Keeping init.rb thin":http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html
h3. Changelog
+* February 13, 2011: Get guide in synch with Rails 3.0.3. Remove information not compatible with Rails 3. Send reader elsewhere
+for information that is covered elsewhere.
* April 4, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
* November 17, 2008: Major revision by Jeff Dean