提交 d09a8446 编写于 作者: R rick

fix merge conflict with actionpack changelog

......@@ -95,7 +95,7 @@ module ActionMailer #:nodoc:
#
# ActionMailer::Base.default_url_options[:host] = "example.com"
#
# This can also be set as a configuration option in <tt>environment.rb</tt>:
# This can also be set as a configuration option in <tt>config/environment.rb</tt>:
#
# config.action_mailer.default_url_options = { :host => "example.com" }
#
......@@ -204,22 +204,23 @@ module ActionMailer #:nodoc:
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
#
# * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
# * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
# * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
# * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
# * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
# * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
# * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default "localhost" setting.
# * <tt>:port</tt> - On the off chance that your mail server doesn't run on port 25, you can change it.
# * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
# * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
# This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method
# * <tt>:location</tt> The location of the sendmail executable, defaults to "/usr/sbin/sendmail"
# * <tt>:arguments</tt> The command line arguments
# * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
# * <tt>:location</tt> - The location of the sendmail executable, defaults to "/usr/sbin/sendmail"
# * <tt>:arguments</tt> - The command line arguments
#
# * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered.
#
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, and <tt>:test</tt>.
#
# * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
# * <tt>perform_deliveries</tt> - Determines whether <tt>deliver_*</tt> methods are actually carried out. By default they are,
# but this can be turned off to help functional testing.
#
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with <tt>delivery_method :test</tt>. Most useful
......@@ -406,7 +407,7 @@ def deliver(mail)
# templating language other than rhtml or rxml are supported.
# To use this, include in your template-language plugin's init
# code or on a per-application basis, this can be invoked from
# config/environment.rb:
# <tt>config/environment.rb</tt>:
#
# ActionMailer::Base.register_template_extension('haml')
def register_template_extension(extension)
......
*SVN*
* Change the request forgery protection to go by Content-Type instead of request.format so that you can't bypass it by POSTing to "#{request.uri}.xml" [rick]
* InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing]
* select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing]
* Fixed that TextHelper#text_field would corrypt when raw HTML was used as the value (mchenryc, Kevin Glowacz) [#80]
......
......@@ -21,11 +21,11 @@ module Assertions
# from the response HTML or elements selected by the enclosing assertion.
#
# In addition to HTML responses, you can make the following assertions:
# * #assert_select_rjs -- Assertions on HTML content of RJS update and
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and
# insertion operations.
# * #assert_select_encoded -- Assertions on HTML encoded inside XML,
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML,
# for example for dealing with feed item descriptions.
# * #assert_select_email -- Assertions on the HTML body of an e-mail.
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
#
# Also see HTML::Selector to learn how to use selectors.
module SelectorAssertions
......@@ -136,27 +136,27 @@ def css_select(*args)
# === Equality Tests
#
# The equality test may be one of the following:
# * <tt>true</tt> -- Assertion is true if at least one element selected.
# * <tt>false</tt> -- Assertion is true if no element selected.
# * <tt>String/Regexp</tt> -- Assertion is true if the text value of at least
# * <tt>true</tt> - Assertion is true if at least one element selected.
# * <tt>false</tt> - Assertion is true if no element selected.
# * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
# one element matches the string or regular expression.
# * <tt>Integer</tt> -- Assertion is true if exactly that number of
# * <tt>Integer</tt> - Assertion is true if exactly that number of
# elements are selected.
# * <tt>Range</tt> -- Assertion is true if the number of selected
# * <tt>Range</tt> - Assertion is true if the number of selected
# elements fit the range.
# If no equality test specified, the assertion is true if at least one
# element selected.
#
# To perform more than one equality tests, use a hash with the following keys:
# * <tt>:text</tt> -- Narrow the selection to elements that have this text
# * <tt>:text</tt> - Narrow the selection to elements that have this text
# value (string or regexp).
# * <tt>:html</tt> -- Narrow the selection to elements that have this HTML
# * <tt>:html</tt> - Narrow the selection to elements that have this HTML
# content (string or regexp).
# * <tt>:count</tt> -- Assertion is true if the number of selected elements
# * <tt>:count</tt> - Assertion is true if the number of selected elements
# is equal to this value.
# * <tt>:minimum</tt> -- Assertion is true if the number of selected
# * <tt>:minimum</tt> - Assertion is true if the number of selected
# elements is at least this value.
# * <tt>:maximum</tt> -- Assertion is true if the number of selected
# * <tt>:maximum</tt> - Assertion is true if the number of selected
# elements is at most this value.
#
# If the method is called with a block, once all equality tests are
......
......@@ -159,28 +159,34 @@ class UnknownHttpMethod < ActionControllerError #:nodoc:
#
# Hello #{session[:person]}
#
# For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
# remove the entire session with reset_session.
# For removing objects from the session, you can either assign a single key to +nil+:
#
# Sessions are stored in a browser cookie that's cryptographically signed, but unencrypted, by default. This prevents
# the user from tampering with the session but also allows him to see its contents.
# # removes :person from session
# session[:person] = nil
#
# Do not put secret information in session!
# or you can remove the entire session with +reset_session+.
#
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
# This prevents the user from tampering with the session but also allows him to see its contents.
#
# Do not put secret information in cookie-based sessions!
#
# Other options for session storage are:
#
# ActiveRecordStore: sessions are stored in your database, which works better than PStore with multiple app servers and,
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
# * ActiveRecordStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
#
# config.action_controller.session_store = :active_record_store
# config.action_controller.session_store = :active_record_store
#
# in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
# in your <tt>config/environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
#
# MemCacheStore: sessions are stored as entries in your memcached cache. Set the session store type in <tt>environment.rb</tt>:
# * MemCacheStore - Sessions are stored as entries in your memcached cache.
# Set the session store type in <tt>config/environment.rb</tt>:
#
# config.action_controller.session_store = :mem_cache_store
# config.action_controller.session_store = :mem_cache_store
#
# This assumes that memcached has been installed and configured properly. See the MemCacheStore docs for more information.
# This assumes that memcached has been installed and configured properly.
# See the MemCacheStore docs for more information.
#
# == Responses
#
......@@ -535,20 +541,20 @@ def process(request, response, method = :perform_action, *arguments) #:nodoc:
#
# <tt>url_for</tt> is used to:
#
# All keys given to url_for are forwarded to the Route module, save for the following:
# * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example,
# All keys given to +url_for+ are forwarded to the Route module, save for the following:
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. For example,
# <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
# will produce "/posts/show/10#comments".
# * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default)
# * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default).
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
# is currently not recommended since it breaks caching.
# * <tt>:host</tt> -- overrides the default (current) host if provided.
# * <tt>:protocol</tt> -- overrides the default (current) protocol if provided.
# * <tt>:port</tt> -- optionally specify the port to connect to.
# * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
# * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
# * <tt>:skip_relative_url_root</tt> -- if true, the url is not constructed using the relative_url_root of the request so the path
# will include the web server relative installation directory.
# * <tt>:host</tt> - Overrides the default (current) host if provided.
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the +relative_url_root+
# of the request so the path will include the web server relative installation directory.
#
# The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
# Routes composes a query string as the key/value pairs not included in the <base>.
......
......@@ -20,7 +20,8 @@ module ActionController #:nodoc:
#
# == Caching stores
#
# All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching.
# All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching. This setting only
# affects action and fragment caching as page caching is always written to disk.
#
# Configuration examples (MemoryStore is the default):
#
......
......@@ -4,23 +4,24 @@
module ActionController #:nodoc:
module Caching
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
# can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically
# generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
# are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
# for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
# can serve without going through Action Pack. This is the fastest way to cache your content as opposed to going dynamically
# through the process of generating the content. Unfortunately, this incredible speed-up is only available to stateless pages
# where all visitors are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are
# a great fit for this approach, but account-based systems where people log in and manipulate their own data are often less
# likely candidates.
#
# Specifying which actions to cache is done through the <tt>caches</tt> class method:
# Specifying which actions to cache is done through the <tt>caches_page</tt> class method:
#
# class WeblogController < ActionController::Base
# caches_page :show, :new
# end
#
# This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
# generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to
# the Action Pack to generate it.
# This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>,
# which match the URLs used to trigger the dynamic generation. This is how the web server is able
# pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it.
#
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
# is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
# is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
#
# class WeblogController < ActionController::Base
# def update
......@@ -35,13 +36,17 @@ module Caching
#
# == Setting the cache directory
#
# The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
# For Rails, this directory has already been set to Rails.public_path (which is usually set to RAILS_ROOT + "/public").
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
# web server to look in the new location for cached files.
#
# == Setting the cache extension
#
# By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want
# something else, like .php or .shtml, just set Base.page_cache_extension.
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
module Pages
def self.included(base) #:nodoc:
base.extend(ClassMethods)
......
......@@ -47,25 +47,27 @@ module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
#
# polymorphic_url(post)
# # calls post_url(post) #=> "http://example.com/posts/1"
# # calls post_url(post)
# polymorphic_url(post) # => "http://example.com/posts/1"
#
# ==== Options
# * <tt>:action</tt> -- specifies the action prefix for the named route:
# <tt>:new</tt>, <tt>:edit</tt> or <tt>:formatted</tt>. Default is no prefix.
# * <tt>:routing_type</tt> -- <tt>:path</tt> or <tt>:url</tt> (default <tt>:url</tt>).
#
# * <tt>:action</tt> - Specifies the action prefix for the named route:
# <tt>:new</tt>, <tt>:edit</tt>, or <tt>:formatted</tt>. Default is no prefix.
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
# ==== Examples
#
# # an Article record
# polymorphic_url(record) #-> article_url(record)
# polymorphic_url(record) # same as article_url(record)
#
# # a Comment record
# polymorphic_url(record) #-> comment_url(record)
# polymorphic_url(record) # same as comment_url(record)
#
# # it recognizes new records and maps to the collection
# record = Comment.new
# polymorphic_url(record) #-> comments_url()
# polymorphic_url(record) # same as comments_url()
#
def polymorphic_url(record_or_hash_or_array, options = {})
if record_or_hash_or_array.kind_of?(Array)
......
......@@ -33,11 +33,17 @@ module RecordIdentifier
# Returns plural/singular for a record or class. Example:
#
# partial_path(post) # => "posts/post"
# partial_path(Person) # => "people/person"
def partial_path(record_or_class)
# partial_path(post) # => "posts/post"
# partial_path(Person) # => "people/person"
# partial_path(Person, "admin/games") # => "admin/people/person"
def partial_path(record_or_class, controller_path = nil)
klass = class_from_record_or_class(record_or_class)
"#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
if controller_path && controller_path.include?("/")
"#{File.dirname(controller_path)}/#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
else
"#{klass.name.tableize}/#{klass.name.demodulize.underscore}"
end
end
# The DOM class convention is to use the singular form of an object or class. Examples:
......
......@@ -69,10 +69,10 @@ module ClassMethods
#
# Valid Options:
#
# * <tt>:only/:except</tt> - passed to the <tt>before_filter</tt> call. Set which actions are verified.
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
# * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
# Leave this off if you are using the cookie session store.
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'.
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
......
......@@ -236,27 +236,27 @@ def initialize(entity, options)
# which takes into account whether <tt>@message</tt> is a new record or not and generates the
# path and method accordingly.
#
# The #resources method accepts the following options to customize the resulting routes:
# * <tt>:collection</tt> - add named routes for other actions that operate on the collection.
# The +resources+ method accepts the following options to customize the resulting routes:
# * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of rss_messages_url.
# * <tt>:member</tt> - same as <tt>:collection</tt>, but for actions that operate on a specific member.
# * <tt>:new</tt> - same as <tt>:collection</tt>, but for actions that operate on the new resource action.
# * <tt>:controller</tt> - specify the controller name for the routes.
# * <tt>:singular</tt> - specify the singular name used in the member routes.
# * <tt>:requirements</tt> - set custom routing parameter requirements.
# * <tt>:conditions</tt> - specify custom routing recognition conditions. Resources sets the <tt>:method</tt> value for the method-specific routes.
# * <tt>:as</tt> - specify a different resource name to use in the URL path. For example:
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
# * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new resource action.
# * <tt>:controller</tt> - Specify the controller name for the routes.
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
# * <tt>:requirements</tt> - Set custom routing parameter requirements.
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. Resources sets the <tt>:method</tt> value for the method-specific routes.
# * <tt>:as</tt> - Specify a different resource name to use in the URL path. For example:
# # products_path == '/productos'
# map.resources :products, :as => 'productos' do |product|
# # product_reviews_path(product) == '/productos/1234/comentarios'
# product.resources :product_reviews, :as => 'comentarios'
# end
#
# * <tt>:has_one</tt> - specify nested resources, this is a shorthand for mapping singleton resources beneath the current.
# * <tt>:has_many</tt> - same has <tt>:has_one</tt>, but for plural resources.
# * <tt>:has_one</tt> - Specify nested resources, this is a shorthand for mapping singleton resources beneath the current.
# * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural resources.
#
# You may directly specify the routing association with has_one and has_many like:
# You may directly specify the routing association with +has_one+ and +has_many+ like:
#
# map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
#
......@@ -268,14 +268,14 @@ def initialize(entity, options)
# notes.resources :attachments
# end
#
# * <tt>:path_names</tt> - specify different names for the 'new' and 'edit' actions. For example:
# * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
# # new_products_path == '/productos/nuevo'
# map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
#
# You can also set default action names from an environment, like this:
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
#
# * <tt>:path_prefix</tt> - set a prefix to the routes with required route variables.
# * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
#
# Weblog comments usually belong to a post, so you might use resources like:
#
......@@ -296,7 +296,7 @@ def initialize(entity, options)
# article_comments_url(:article_id => @article)
# article_comment_url(:article_id => @article, :id => @comment)
#
# * <tt>:name_prefix</tt> - define a prefix for all generated routes, usually ending in an underscore.
# * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
# Use this if you have named routes that may clash.
#
# map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
......@@ -343,7 +343,7 @@ def initialize(entity, options)
# # --> GET /categories/7/messages/1
# # has named route "category_message"
#
# The #resources method sets HTTP method restrictions on the routes it generates. For example, making an
# The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
# HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
# <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for resource routes.
def resources(*entities, &block)
......@@ -524,11 +524,9 @@ def map_member_actions(map, resource)
resource.member_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
action_path = action
if resource.options[:path_names]
action_path = resource.options[:path_names][action]
action_path ||= Base.resources_path_names[action] || action
end
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= Base.resources_path_names[action] || action
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
......
......@@ -15,7 +15,7 @@ module ActionController
# The routing module provides URL rewriting in native Ruby. It's a way to
# redirect incoming requests to controllers and actions. This replaces
# mod_rewrite rules. Best of all, Rails' Routing works with any web server.
# Routes are defined in routes.rb in your RAILS_ROOT/config directory.
# Routes are defined in <tt>config/routes.rb</tt>.
#
# Consider the following route, installed by Rails when you generate your
# application:
......@@ -53,7 +53,7 @@ module ActionController
# == Route priority
#
# Not all routes are created equally. Routes have priority defined by the
# order of appearance of the routes in the routes.rb file. The priority goes
# order of appearance of the routes in the <tt>config/routes.rb</tt> file. The priority goes
# from top to bottom. The last route in that file is at the lowest priority
# and will be applied last. If no route matches, 404 is returned.
#
......@@ -118,7 +118,7 @@ module ActionController
# root_url # => 'http://www.example.com/'
# root_path # => ''
#
# You can also specify an already-defined named route in your map.root call:
# You can also specify an already-defined named route in your <tt>map.root</tt> call:
#
# # In routes.rb
# map.new_session :controller => 'sessions', :action => 'new'
......@@ -217,7 +217,7 @@ module ActionController
# ActionController::Routing::Routes.reload
#
# This will clear all named routes and reload routes.rb if the file has been modified from
# last load. To absolutely force reloading, use +reload!+.
# last load. To absolutely force reloading, use <tt>reload!</tt>.
#
# == Testing Routes
#
......@@ -277,6 +277,9 @@ module Helpers
end
class << self
# Expects an array of controller names as the first argument.
# Executes the passed block with only the named controllers named available.
# This method is used in internal Rails testing.
def with_controllers(names)
prior_controllers = @possible_controllers
use_controllers! names
......@@ -285,6 +288,10 @@ def with_controllers(names)
use_controllers! prior_controllers
end
# Returns an array of paths, cleaned of double-slashes and relative path references.
# * "\\\" and "//" become "\\" or "/".
# * "/foo/bar/../config" becomes "/foo/config".
# The returned array is sorted by length, descending.
def normalize_paths(paths)
# do the hokey-pokey of path normalization...
paths = paths.collect do |path|
......@@ -303,6 +310,7 @@ def normalize_paths(paths)
paths = paths.uniq.sort_by { |path| - path.length }
end
# Returns the array of controller names currently available to ActionController::Routing.
def possible_controllers
unless @possible_controllers
@possible_controllers = []
......@@ -327,10 +335,28 @@ def possible_controllers
@possible_controllers
end
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
def use_controllers!(controller_names)
@possible_controllers = controller_names
end
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
# Handles 4 scenarios:
#
# * stay in the previous controller:
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
#
# * stay in the previous namespace:
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
#
# * forced move to the root namespace:
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
#
# * previous namespace is root:
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
#
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
......@@ -344,6 +370,7 @@ def controller_relative_to(controller, previous)
Routes = RouteSet.new
::Inflector.module_eval do
# Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block)
returning(inflections_without_route_reloading(&block)) {
ActionController::Routing::Routes.reload! if block_given?
......
......@@ -244,11 +244,12 @@ def match_extraction(next_capture)
end
class PathSegment < DynamicSegment #:nodoc:
RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/"
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
def interpolation_chunk(value_code = "#{local_name}")
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}"
"\#{#{value_code}}"
end
def extract_value
"#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| URI.escape(path_component, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
end
def default
......
......@@ -13,7 +13,7 @@ def model
# A session store backed by an Active Record class. A default class is
# provided, but any object duck-typing to an Active Record +Session+ class
# provided, but any object duck-typing to an Active Record Session class
# with text +session_id+ and +data+ attributes is sufficient.
#
# The default assumes a +sessions+ tables with columns:
......@@ -26,13 +26,13 @@ def model
# ActionController::SessionOverflowError will be raised.
#
# You may configure the table name, primary key, and data column.
# For example, at the end of config/environment.rb:
# For example, at the end of <tt>config/environment.rb</tt>:
# CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
# CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
# CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
# Note that setting the primary key to the session_id frees you from
# having a separate id column if you don't want it. However, you must
# set session.model.id = session.session_id by hand! A before_filter
# Note that setting the primary key to the +session_id+ frees you from
# having a separate +id+ column if you don't want it. However, you must
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
# on ApplicationController is a good place.
#
# Since the default class is a simple Active Record, you get timestamps
......@@ -42,7 +42,7 @@ def model
# You may provide your own session class implementation, whether a
# feature-packed Active Record or a bare-metal high-performance SQL
# store, by setting
# +CGI::Session::ActiveRecordStore.session_class = MySessionClass+
# CGI::Session::ActiveRecordStore.session_class = MySessionClass
# You must implement these methods:
# self.find_by_session_id(session_id)
# initialize(hash_of_session_id_and_data)
......@@ -154,8 +154,13 @@ def raise_on_session_data_overflow!
# The database connection, table name, and session id and data columns
# are configurable class attributes. Marshaling and unmarshaling
# are implemented as class methods that you may override. By default,
# marshaling data is +ActiveSupport::Base64.encode64(Marshal.dump(data))+ and
# unmarshaling data is +Marshal.load(ActiveSupport::Base64.decode64(data))+.
# marshaling data is
#
# ActiveSupport::Base64.encode64(Marshal.dump(data))
#
# and unmarshaling data is
#
# Marshal.load(ActiveSupport::Base64.decode64(data))
#
# This marshaling behavior is intended to store the widest range of
# binary session data in a +text+ column. For higher performance,
......
......@@ -29,16 +29,16 @@ def self.included(base) #:nodoc:
# Generate a url based on the options provided, default_url_options and the
# routes defined in routes.rb. The following options are supported:
#
# * <tt>:only_path</tt> If true, the relative url is returned. Defaults to +false+.
# * <tt>:protocol</tt> The protocol to connect to. Defaults to 'http'.
# * <tt>:host</tt> Specifies the host the link should be targetted at.
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
# * <tt>:host</tt> - Specifies the host the link should be targetted at.
# If <tt>:only_path</tt> is false, this option must be
# provided either explicitly, or via +default_url_options+.
# * <tt>:port</tt> Optionally specify the port to connect to.
# * <tt>:anchor</tt> An anchor name to be appended to the path.
# * <tt>:skip_relative_url_root</tt> If true, the url is not constructed using the
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
# +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root.
# * <tt>:trailing_slash</tt> If true, adds a trailing slash, as in "/archive/2009/"
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
......
......@@ -5,9 +5,9 @@ class ActionViewError < StandardError #:nodoc:
class MissingTemplate < ActionViewError #:nodoc:
end
# Action View templates can be written in three ways. If the template file has a +.erb+ (or +.rhtml+) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a +.builder+ (or +.rxml+) extension then Jim Weirich's Builder::XmlMarkup library is used.
# If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
# If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
#
# = ERb
#
......@@ -24,7 +24,7 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# Hi, Mr. <% puts "Frodo" %>
#
# If you absolutely must write from within a function, you can use the TextHelper#concat
# If you absolutely must write from within a function, you can use the TextHelper#concat.
#
# <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
#
......@@ -46,7 +46,7 @@ class MissingTemplate < ActionViewError #:nodoc:
# <% @page_title = "A Wonderful Hello" %>
# <%= render "shared/header" %>
#
# Now the header can pick up on the @page_title variable and use it for outputting a title tag:
# Now the header can pick up on the <tt>@page_title</tt> variable and use it for outputting a title tag:
#
# <title><%= @page_title %></title>
#
......@@ -56,7 +56,7 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# <%= render "shared/header", { :headline => "Welcome", :person => person } %>
#
# These can now be accessed in shared/header with:
# These can now be accessed in <tt>shared/header</tt> with:
#
# Headline: <%= headline %>
# First name: <%= person.first_name %>
......@@ -77,13 +77,13 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# == Builder
#
# Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object
# named +xml+ is automatically made available to templates with a +.builder+ extension.
# Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
#
# Here are some basic examples:
#
# xml.em("emphasized") # => <em>emphasized</em>
# xml.em { xml.b("emph & bold") } # => <em><b>emph &amp; bold</b></em>
# xml.em { xml.b("emph & bold") } # => <em><b>emph &amp; bold</b></em>
# xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
# xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\>
# # NOTE: order of attributes is not specified.
......@@ -130,18 +130,18 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# == JavaScriptGenerator
#
# JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to
# JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
# render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
# modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
# and make updates to the page where the request originated from.
#
# An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
#
# When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
# When an <tt>.rjs</tt> action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
#
# link_to_remote :url => {:action => 'delete'}
#
# The subsequently rendered +delete.rjs+ might look like:
# The subsequently rendered <tt>delete.rjs</tt> might look like:
#
# page.replace_html 'sidebar', :partial => 'sidebar'
# page.remove "person-#{@person.id}"
......@@ -185,7 +185,7 @@ class Base
attr_internal :request
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
:flash, :logger, :to => :controller
:flash, :logger, :action_name, :to => :controller
module CompiledTemplates #:nodoc:
# holds compiled template code
......
......@@ -283,7 +283,7 @@ def select_datetime(datetime = Time.current, options = {}, html_options = {})
# # prefixed with 'payday' rather than 'date'
# select_datetime(my_date_time, :prefix => 'payday')
#
def select_date(date = Date.today, options = {}, html_options = {})
def select_date(date = Date.current, options = {}, html_options = {})
options[:order] ||= []
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
......@@ -683,12 +683,13 @@ def default_time_from_options(default)
default[:min] ||= default[:minute]
default[:sec] ||= default[:second]
time = Time.current
[:year, :month, :day, :hour, :min, :sec].each do |key|
default[key] ||= Time.now.send(key)
default[key] ||= time.send(key)
end
Time.mktime(default[:year], default[:month], default[:day],
default[:hour], default[:min], default[:sec])
Time.utc(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec])
end
end
end
......
......@@ -316,12 +316,12 @@ def radio_button_tag(name, value, checked = false, options = {})
# Creates a submit button with the text <tt>value</tt> as the caption.
#
# ==== Options
# * <tt>:confirm => 'question?'</tt> -- This will add a JavaScript confirm
# * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the form is
# processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
# of the submit button when the form is submitted.
# of the submit button when the form is submitted.
# * Any other key creates standard HTML options for the tag.
#
# ==== Examples
......
......@@ -631,6 +631,27 @@ def include_helpers_from_context
# render(:update) { |page| page.update_time }
# end
#
# Calls to JavaScriptGenerator not matching a helper method below
# generate a proxy to the JavaScript Class named by the method called.
#
# Examples:
#
# # Generates:
# # Foo.init();
# update_page do |page|
# page.foo.init
# end
#
# # Generates:
# # Event.observe('one', 'click', function () {
# # $('two').show();
# # });
# update_page do |page|
# page.event.observe('one', 'click') do |p|
# p[:two].show
# end
# end
#
# You can also use PrototypeHelper#update_page_tag instead of
# PrototypeHelper#update_page to wrap the generated JavaScript in a
# <script> tag.
......@@ -855,12 +876,21 @@ def redirect_to(location)
#
# Examples:
#
# # Generates: Element.replace(my_element, "My content to replace with.")
# page.call 'Element.replace', 'my_element', "My content to replace with."
#
# # Generates: alert('My message!')
# page.call 'alert', 'My message!'
#
# # Generates: Element.replace(my_element, "My content to replace with.")
# page.call 'Element.replace', 'my_element', "My content to replace with."
#
# # Generates: alert('My message!')
# page.call 'alert', 'My message!'
#
# # Generates:
# # my_method(function() {
# # $("one").show();
# # $("two").hide();
# # });
# page.call(:my_method) do |p|
# p[:one].show
# p[:two].hide
# end
def call(function, *arguments, &block)
record "#{function}(#{arguments_for_call(arguments, block)})"
end
......
......@@ -19,15 +19,15 @@ module UrlHelper
# need an unescaped url, pass <tt>:escape => false</tt> in the +options+.
#
# ==== Options
# * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path.
# * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified)
# * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
# is currently not recommended since it breaks caching.
# * <tt>:host</tt> -- overrides the default (current) host if provided
# * <tt>:protocol</tt> -- overrides the default (current) protocol if provided
# * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present)
# * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present)
# * <tt>:escape</tt> -- Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default)
# * <tt>:host</tt> - Overrides the default (current) host if provided.
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
# * <tt>:escape</tt> - Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default).
#
# ==== Relying on named routes
#
......@@ -91,14 +91,14 @@ def url_for(options = {})
# a name, the link itself will become the name.
#
# ==== Options
# * <tt>:confirm => 'question?'</tt> -- This will add a JavaScript confirm
# * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
# * <tt>:popup => true || array of window options</tt> -- This will force the
# * <tt>:popup => true || array of window options</tt> - This will force the
# link to open in a popup window. By passing true, a default browser window
# will be opened with the URL. You can also specify an array of options
# that are passed-thru to JavaScripts window.open method.
# * <tt>:method => symbol of HTTP verb</tt> -- This modifier will dynamically
# * <tt>:method => symbol of HTTP verb</tt> - This modifier will dynamically
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow
......@@ -178,9 +178,9 @@ def link_to(name, options = {}, html_options = nil)
# The +options+ hash accepts the same options at url_for.
#
# There are a few special +html_options+:
# * <tt>:method</tt> -- specifies the anchor name to be appended to the path.
# * <tt>:disabled</tt> -- specifies the anchor name to be appended to the path.
# * <tt>:confirm</tt> -- This will add a JavaScript confirm
# * <tt>:method</tt> - Specifies the anchor name to be appended to the path.
# * <tt>:disabled</tt> - Specifies the anchor name to be appended to the path.
# * <tt>:confirm</tt> - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
#
......
......@@ -119,7 +119,7 @@ def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nod
""
end
else
render_partial(ActionController::RecordIdentifier.partial_path(partial_path), partial_path, local_assigns)
render_partial(ActionController::RecordIdentifier.partial_path(partial_path, controller.class.controller_path), partial_path, local_assigns)
end
end
......@@ -147,7 +147,7 @@ def render_partial_collection_with_unknown_partial_path(collection, local_assign
templates = Hash.new
i = 0
collection.map do |element|
partial_path = ActionController::RecordIdentifier.partial_path(element)
partial_path = ActionController::RecordIdentifier.partial_path(element, controller.class.controller_path)
template = templates[partial_path] ||= ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns)
template.counter = i
i += 1
......
require 'active_record_unit'
class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots
class RenderPartialWithRecordIdentificationController < ActionController::Base
def render_with_has_many_and_belongs_to_association
@developer = Developer.find(1)
render :partial => @developer.projects
end
class RenderPartialWithRecordIdentificationController < ActionController::Base
def render_with_has_many_and_belongs_to_association
@developer = Developer.find(1)
render :partial => @developer.projects
end
def render_with_has_many_association
@topic = Topic.find(1)
render :partial => @topic.replies
end
def render_with_named_scope
render :partial => Reply.base
end
def render_with_has_many_through_association
@developer = Developer.find(:first)
render :partial => @developer.topics
end
def render_with_has_one_association
@company = Company.find(1)
render :partial => @company.mascot
end
def render_with_belongs_to_association
@reply = Reply.find(1)
render :partial => @reply.topic
end
def render_with_record
@developer = Developer.find(:first)
render :partial => @developer
end
def render_with_record_collection
@developers = Developer.find(:all)
render :partial => @developers
end
def render_with_has_many_association
@topic = Topic.find(1)
render :partial => @topic.replies
end
def render_with_named_scope
render :partial => Reply.base
end
def render_with_has_many_through_association
@developer = Developer.find(:first)
render :partial => @developer.topics
end
def render_with_has_one_association
@company = Company.find(1)
render :partial => @company.mascot
end
def render_with_belongs_to_association
@reply = Reply.find(1)
render :partial => @reply.topic
end
RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
def render_with_record
@developer = Developer.find(:first)
render :partial => @developer
end
def render_with_record_collection
@developers = Developer.find(:all)
render :partial => @developers
end
end
RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots
def setup
@controller = RenderPartialWithRecordIdentificationController.new
......@@ -84,3 +84,108 @@ def test_rendering_partial_with_has_one_association
assert_equal mascot.name, @response.body
end
end
class RenderPartialWithRecordIdentificationController < ActionController::Base
def render_with_has_many_and_belongs_to_association
@developer = Developer.find(1)
render :partial => @developer.projects
end
def render_with_has_many_association
@topic = Topic.find(1)
render :partial => @topic.replies
end
def render_with_has_many_through_association
@developer = Developer.find(:first)
render :partial => @developer.topics
end
def render_with_belongs_to_association
@reply = Reply.find(1)
render :partial => @reply.topic
end
def render_with_record
@developer = Developer.find(:first)
render :partial => @developer
end
def render_with_record_collection
@developers = Developer.find(:all)
render :partial => @developers
end
end
RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
class Game < Struct.new(:name, :id)
def to_param
id.to_s
end
end
module Fun
class NestedController < ActionController::Base
def render_with_record_in_nested_controller
render :partial => Game.new("Pong")
end
def render_with_record_collection_in_nested_controller
render :partial => [ Game.new("Pong"), Game.new("Tank") ]
end
end
NestedController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
module Serious
class NestedDeeperController < ActionController::Base
def render_with_record_in_deeper_nested_controller
render :partial => Game.new("Chess")
end
def render_with_record_collection_in_deeper_nested_controller
render :partial => [ Game.new("Chess"), Game.new("Sudoku"), Game.new("Solitaire") ]
end
end
NestedDeeperController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
end
end
class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveRecordTestCase
def setup
@controller = Fun::NestedController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
super
end
def test_render_with_record_in_nested_controller
get :render_with_record_in_nested_controller
assert_template 'fun/games/_game'
end
def test_render_with_record_collection_in_nested_controller
get :render_with_record_collection_in_nested_controller
assert_template 'fun/games/_game'
end
end
class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < ActiveRecordTestCase
def setup
@controller = Fun::Serious::NestedDeeperController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
super
end
def test_render_with_record_in_deeper_nested_controller
get :render_with_record_in_deeper_nested_controller
assert_template 'fun/serious/games/_game'
end
def test_render_with_record_collection_in_deeper_nested_controller
get :render_with_record_collection_in_deeper_nested_controller
assert_template 'fun/serious/games/_game'
end
end
\ No newline at end of file
......@@ -246,6 +246,10 @@ def accessing_request_in_template
def accessing_logger_in_template
render :inline => "<%= logger.class %>"
end
def accessing_action_name_in_template
render :inline => "<%= action_name %>"
end
def accessing_params_in_template_with_layout
render :layout => nil, :inline => "Hello: <%= params[:name] %>"
......@@ -545,6 +549,11 @@ def test_access_to_logger_in_view
get :accessing_logger_in_template
assert_equal "Logger", @response.body
end
def test_access_to_action_name_in_view
get :accessing_action_name_in_template
assert_equal "accessing_action_name_in_template", @response.body
end
def test_render_xml
get :render_xml_hello
......
......@@ -57,6 +57,18 @@ def test_partial_path
assert_equal expected, partial_path(Comment)
end
def test_partial_path_with_namespaced_controller_path
expected = "admin/#{@plural}/#{@singular}"
assert_equal expected, partial_path(@record, "admin/posts")
assert_equal expected, partial_path(@klass, "admin/posts")
end
def test_partial_path_with_not_namespaced_controller_path
expected = "#{@plural}/#{@singular}"
assert_equal expected, partial_path(@record, "posts")
assert_equal expected, partial_path(@klass, "posts")
end
def test_dom_class
assert_equal @singular, dom_class(@record)
end
......@@ -100,4 +112,28 @@ def test_partial_path
assert_equal expected, partial_path(@record)
assert_equal expected, partial_path(Comment::Nested)
end
def test_partial_path_with_namespaced_controller_path
expected = "admin/comment/nesteds/nested"
assert_equal expected, partial_path(@record, "admin/posts")
assert_equal expected, partial_path(@klass, "admin/posts")
end
def test_partial_path_with_deeper_namespaced_controller_path
expected = "deeper/admin/comment/nesteds/nested"
assert_equal expected, partial_path(@record, "deeper/admin/posts")
assert_equal expected, partial_path(@klass, "deeper/admin/posts")
end
def test_partial_path_with_even_deeper_namespaced_controller_path
expected = "even/more/deeper/admin/comment/nesteds/nested"
assert_equal expected, partial_path(@record, "even/more/deeper/admin/posts")
assert_equal expected, partial_path(@klass, "even/more/deeper/admin/posts")
end
def test_partial_path_with_not_namespaced_controller_path
expected = "comment/nesteds/nested"
assert_equal expected, partial_path(@record, "posts")
assert_equal expected, partial_path(@klass, "posts")
end
end
......@@ -226,6 +226,28 @@ def test_member_when_override_paths_for_default_restful_actions_with
end
end
def test_member_when_changed_default_restful_actions_and_path_names_not_specified
default_path_names = ActionController::Base.resources_path_names
ActionController::Base.resources_path_names = {:new => 'nuevo', :edit => 'editar'}
with_restful_routing :messages do
new_options = { :action => 'new', :controller => 'messages' }
new_path = "/messages/nuevo"
edit_options = { :action => 'edit', :id => '1', :controller => 'messages' }
edit_path = "/messages/1/editar"
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(new_options), :path => new_path, :method => :get)
end
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(edit_options), :path => edit_path, :method => :get)
end
end
ensure
ActionController::Base.resources_path_names = default_path_names
end
def test_with_two_member_actions_with_same_method
[:put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method, :unmark => method } do
......@@ -691,11 +713,11 @@ def assert_restful_routes_for(controller_name, options = {})
options[:options] ||= {}
options[:options][:controller] = options[:controller] || controller_name.to_s
new_action = "new"
edit_action = "edit"
new_action = ActionController::Base.resources_path_names[:new] || "new"
edit_action = ActionController::Base.resources_path_names[:edit] || "edit"
if options[:path_names]
new_action = options[:path_names][:new] || "new"
edit_action = options[:path_names][:edit] || "edit"
new_action = options[:path_names][:new] if options[:path_names][:new]
edit_action = options[:path_names][:edit] if options[:path_names][:edit]
end
collection_path = "/#{options[:path_prefix]}#{options[:as] || controller_name}"
......
......@@ -25,7 +25,7 @@ def setup
ActionController::Routing.use_controllers! ['controller']
@set = ActionController::Routing::RouteSet.new
@set.draw do |map|
map.connect ':controller/:action/:variable'
map.connect ':controller/:action/:variable/*additional'
end
safe, unsafe = %w(: @ & = + $ , ;), %w(^ / ? # [ ])
......@@ -36,17 +36,19 @@ def setup
end
def test_route_generation_escapes_unsafe_path_characters
assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable",
assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2",
@set.generate(:controller => "contr#{@segment}oller",
:action => "act#{@segment}ion",
:variable => "var#{@segment}iable")
:variable => "var#{@segment}iable",
:additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"])
end
def test_route_recognition_unescapes_path_components
options = { :controller => "controller",
:action => "act#{@segment}ion",
:variable => "var#{@segment}iable" }
assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable")
:variable => "var#{@segment}iable",
:additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"] }
assert_equal options, @set.recognize_path("/controller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2")
end
end
......
......@@ -950,6 +950,15 @@ def test_select_time_uses_time_current_as_default
expects(:select_minute).with(time, anything, anything).returns('')
select_time
end
def test_select_date_uses_date_current_as_default
date = stub(:year => 2004, :month => 6, :day => 15)
Date.expects(:current).returns date
expects(:select_year).with(date, anything, anything).returns('')
expects(:select_month).with(date, anything, anything).returns('')
expects(:select_day).with(date, anything, anything).returns('')
select_date
end
end
def test_date_select
......@@ -1699,4 +1708,27 @@ def test_datetime_select_with_html_options
assert_dom_equal expected, datetime_select("post", "updated_at", {}, :class => 'selector')
end
uses_mocha 'TestInstanceTagDefaultTimeFromOptions' do
def test_instance_tag_default_time_from_options_uses_time_current_as_default_when_hash_passed_as_arg
dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3)
Time.expects(:current).returns Time.now
dummy_instance_tag.send!(:default_time_from_options, :hour => 2)
end
def test_instance_tag_default_time_from_options_respects_hash_arg_settings_when_time_falls_in_system_local_dst_spring_gap
with_env_tz('US/Central') do
dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3)
Time.stubs(:now).returns Time.local(2006, 4, 2, 1)
assert_equal 2, dummy_instance_tag.send!(:default_time_from_options, :hour => 2).hour
end
end
end
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
yield
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
end
......@@ -25,7 +25,7 @@ module ClassMethods
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
# * <tt>:on</tt> Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
......
......@@ -15,17 +15,17 @@ module ClassMethods
# end
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is not a number")
# * <tt>:on</tt> Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is +false+)
# * <tt>:allow_nil</tt> Skip validation if attribute is +nil+ (default is +false+). Notice that for fixnum and float columns empty strings are converted to +nil+
# * <tt>:greater_than</tt> Specifies the value must be greater than the supplied value
# * <tt>:greater_than_or_equal_to</tt> Specifies the value must be greater than or equal the supplied value
# * <tt>:equal_to</tt> Specifies the value must be equal to the supplied value
# * <tt>:less_than</tt> Specifies the value must be less than the supplied value
# * <tt>:less_than_or_equal_to</tt> Specifies the value must be less than or equal the supplied value
# * <tt>:odd</tt> Specifies the value must be an odd number
# * <tt>:even</tt> Specifies the value must be an even number
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:only_integer</tt> - Specifies whether the value has to be an integer, e.g. an integral value (default is +false+).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is +false+). Notice that for fixnum and float columns empty strings are converted to +nil+.
# * <tt>:greater_than</tt> - Specifies the value must be greater than the supplied value.
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater than or equal the supplied value.
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value.
# * <tt>:less_than</tt> - Specifies the value must be less than the supplied value.
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or equal the supplied value.
# * <tt>:odd</tt> - Specifies the value must be an odd number.
# * <tt>:even</tt> - Specifies the value must be an even number.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
......
*SVN*
* Base#instantiate_time_object: eliminate check for Time.zone, since we can assume this is set if time_zone_aware_attributes is set to true [Geoff Buesing]
* Time zone aware attribute methods use Time.zone.parse instead of #to_time for String arguments, so that offset information in String is respected. Resolves #105. [Scott Fleckenstein, Geoff Buesing]
* Added change_table for migrations (Jeff Dean) [#71]. Example:
change_table :videos do |t|
......
......@@ -169,10 +169,10 @@ A short rundown of the major features:
class AddSystemSettings < ActiveRecord::Migration
def self.up
create_table :system_settings do |t|
t.string :name
t.string :label
t.text :value
t.string :type
t.string :name
t.string :label
t.text :value
t.string :type
t.integer :position
end
......@@ -289,16 +289,6 @@ Bi-directional associations thanks to the "belongs_to" macro
thirty_seven_signals.firm.nil? # true
== Examples
Active Record ships with a couple of examples that should give you a good feel for
operating usage. Be sure to edit the <tt>examples/shared_setup.rb</tt> file for your
own database before running the examples. Possibly also the table definition SQL in
the examples themselves.
It's also highly recommended to have a look at the unit tests. Read more in link:files/RUNNING_UNIT_TESTS.html
== Philosophy
Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
......@@ -336,7 +326,7 @@ then use:
% [sudo] gem install activerecord-1.10.0.gem
You can also install Active Record the old-fashion way with the following command:
You can also install Active Record the old-fashioned way with the following command:
% [sudo] ruby install.rb
......@@ -357,5 +347,5 @@ RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Ra
remember to update the corresponding unit tests. If fact, I prefer
new feature to be submitted in the form of new unit tests.
For other information, feel free to ask on the ruby-talk mailing list
(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
For other information, feel free to ask on the rubyonrails-talk
(http://groups.google.com/group/rubyonrails-talk) mailing list.
......@@ -65,7 +65,13 @@ def set_association_collection_records(id_to_record_map, reflection_name, associ
end
def set_association_single_records(id_to_record_map, reflection_name, associated_records, key)
seen_keys = {}
associated_records.each do |associated_record|
#this is a has_one or belongs_to: there should only be one record.
#Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
# only one row per distinct foo_id' so this where we enforce that
next if seen_keys[associated_record[key].to_s]
seen_keys[associated_record[key].to_s] = true
mapped_records = id_to_record_map[associated_record[key].to_s]
mapped_records.each do |mapped_record|
mapped_record.send("set_#{reflection_name}_target", associated_record)
......@@ -122,7 +128,6 @@ def preload_has_one_association(records, reflection, preload_options={})
else
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
end
end
......
此差异已折叠。
......@@ -118,7 +118,7 @@ def target=(target)
end
def inspect
reload unless loaded?
load_target
@target.inspect
end
......@@ -167,7 +167,7 @@ def merge_options_from_reflection!(options)
def with_scope(*args, &block)
@reflection.klass.send :with_scope, *args, &block
end
private
def method_missing(method, *args)
if load_target
......
......@@ -179,10 +179,10 @@ def define_write_method(attr_name)
def define_write_method_for_time_zone_conversion(attr_name)
method_body = <<-EOV
def #{attr_name}=(time)
if time
time = time.to_time rescue time unless time.acts_like?(:time)
time = time.in_time_zone if time.acts_like?(:time)
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
time = time.in_time_zone rescue nil if time
write_attribute(:#{attr_name}, time)
end
EOV
......
......@@ -92,13 +92,15 @@ class Rollback < ActiveRecordError
class DangerousAttributeError < ActiveRecordError
end
# Raised when you've tried to access a column which wasn't
# loaded by your finder. Typically this is because <tt>:select</tt>
# has been specified.
# Raised when you've tried to access a column which wasn't loaded by your finder.
# Typically this is because <tt>:select</tt> has been specified.
class MissingAttributeError < NoMethodError
end
class AttributeAssignmentError < ActiveRecordError #:nodoc:
# Raised when an error occured while doing a mass assignment to an attribute through the
# <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
# offending attribute.
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
def initialize(message, exception, attribute)
@exception = exception
......@@ -107,7 +109,10 @@ def initialize(message, exception, attribute)
end
end
class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
# Raised when there are multiple errors while doing a mass assignment through the +attributes+
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
# objects, each corresponding to the error while assigning to an attribute.
class MultiparameterAssignmentErrors < ActiveRecordError
attr_reader :errors
def initialize(errors)
@errors = errors
......@@ -230,8 +235,8 @@ def initialize(errors)
# == Accessing attributes before they have been typecasted
#
# Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
# That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
# has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
# That can be done by using the <tt><attribute>_before_type_cast</tt> accessors that all attributes have. For example, if your Account model
# has a balance attribute, you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
#
# This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
# the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
......@@ -332,26 +337,26 @@ def initialize(errors)
#
# == Exceptions
#
# * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
# * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include an
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
# <tt>:adapter</tt> key.
# * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
# (or a bad spelling of an existing one).
# * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
# * +SerializationTypeMismatch+ -- the serialized object wasn't of the class specified as the second parameter.
# * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
# * +RecordNotFound+ -- no record responded to the find* method.
# Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
# * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
# Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
# * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
# +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition.
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
# * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt> before querying.
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
# nothing was found, please check its documentation for further details.
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
# * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the <tt>attributes=</tt> method.
# You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
#
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
# So it's possible to assign a logger to the class through Base.logger= which will then be used by all
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
......@@ -601,7 +606,7 @@ def exists?(id_or_conditions)
# User.create(:first_name => 'Jamie')
#
# # Create an Array of new objects
# User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}])
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
#
# # Create a single object and pass it into a block to set other attributes.
# User.create(:first_name => 'Jamie') do |u|
......@@ -609,7 +614,7 @@ def exists?(id_or_conditions)
# end
#
# # Creating an Array of new objects using a block, where the block is executed for each object:
# User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}]) do |u|
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
# u.is_admin = false
# end
def create(attributes = nil, &block)
......@@ -626,18 +631,18 @@ def create(attributes = nil, &block)
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
# ==== Options
# ==== Attributes
#
# +id+ This should be the id or an array of ids to be updated
# +attributes+ This should be a Hash of attributes to be set on the object, or an array of Hashes.
# * +id+ - This should be the id or an array of ids to be updated.
# * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes.
#
# ==== Examples
#
# # Updating one record:
# Person.update(15, {:user_name => 'Samuel', :group => 'expert'})
# Person.update(15, { :user_name => 'Samuel', :group => 'expert' })
#
# # Updating multiple records:
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
# Person.update(people.keys, people.values)
def update(id, attributes)
if id.is_a?(Array)
......@@ -656,9 +661,9 @@ def update(id, attributes)
#
# Objects are _not_ instantiated with this method.
#
# ==== Options
# ==== Attributes
#
# +id+ Can be either an Integer or an Array of Integers
# * +id+ - Can be either an Integer or an Array of Integers.
#
# ==== Examples
#
......@@ -679,9 +684,9 @@ def delete(id)
# This essentially finds the object (or multiple objects) with the given id, creates a new object
# from the attributes, and then calls destroy on it.
#
# ==== Options
# ==== Attributes
#
# +id+ Can be either an Integer or an Array of Integers
# * +id+ - Can be either an Integer or an Array of Integers.
#
# ==== Examples
#
......@@ -702,12 +707,12 @@ def destroy(id)
# Updates all records with details given if they match a set of conditions supplied, limits and order can
# also be supplied.
#
# ==== Options
# ==== Attributes
#
# +updates+ A String of column and value pairs that will be set on any records that match conditions
# +conditions+ An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
# See conditions in the intro for more info.
# +options+ Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage.
# * +updates+ - A String of column and value pairs that will be set on any records that match conditions.
# * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
# See conditions in the intro for more info.
# * +options+ - Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage.
#
# ==== Examples
#
......@@ -734,9 +739,9 @@ def update_all(updates, conditions = nil, options = {})
# many records. If you want to simply delete records without worrying about dependent associations or
# callbacks, use the much faster +delete_all+ method instead.
#
# ==== Options
# ==== Attributes
#
# +conditions+ Conditions are specified the same way as with +find+ method.
# * +conditions+ - Conditions are specified the same way as with +find+ method.
#
# ==== Example
#
......@@ -752,9 +757,9 @@ def destroy_all(conditions = nil)
# calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient
# than destroy_all.
#
# ==== Options
# ==== Attributes
#
# +conditions+ Conditions are specified the same way as with +find+ method.
# * +conditions+ - Conditions are specified the same way as with +find+ method.
#
# ==== Example
#
......@@ -772,9 +777,9 @@ def delete_all(conditions = nil)
# The use of this method should be restricted to complicated SQL queries that can't be executed
# using the ActiveRecord::Calculations class methods. Look into those before using this.
#
# ==== Options
# ==== Attributes
#
# +sql+: An SQL statement which should return a count query from the database, see the example below
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
#
# ==== Examples
#
......@@ -790,12 +795,11 @@ def count_by_sql(sql)
# with the given ID, altering the given hash of counters by the amount
# given by the corresponding value:
#
# ==== Options
# ==== Attributes
#
# +id+ The id of the object you wish to update a counter on
# +counters+ An Array of Hashes containing the names of the fields
# to update as keys and the amount to update the field by as
# values
# * +id+ - The id of the object you wish to update a counter on.
# * +counters+ - An Array of Hashes containing the names of the fields
# to update as keys and the amount to update the field by as values.
#
# ==== Examples
#
......@@ -821,10 +825,10 @@ def update_counters(id, counters)
# For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
# shown it would have to run an SQL query to find how many posts and comments there are.
#
# ==== Options
# ==== Attributes
#
# +counter_name+ The name of the field that should be incremented
# +id+ The id of the object that should be incremented
# * +counter_name+ - The name of the field that should be incremented.
# * +id+ - The id of the object that should be incremented.
#
# ==== Examples
#
......@@ -838,10 +842,10 @@ def increment_counter(counter_name, id)
#
# This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
#
# ==== Options
# ==== Attributes
#
# +counter_name+ The name of the field that should be decremented
# +id+ The id of the object that should be decremented
# * +counter_name+ - The name of the field that should be decremented.
# * +id+ - The id of the object that should be decremented.
#
# ==== Examples
#
......@@ -886,9 +890,9 @@ def protected_attributes # :nodoc:
# overwritten by URL/form hackers. If you'd rather start from an all-open default and restrict
# attributes as needed, have a look at attr_protected.
#
# ==== Options
# ==== Attributes
#
# <tt>*attributes</tt> A comma separated list of symbols that represent columns _not_ to be protected
# * <tt>*attributes</tt> A comma separated list of symbols that represent columns _not_ to be protected
#
# ==== Examples
#
......@@ -927,10 +931,10 @@ def readonly_attributes
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
# class on retrieval or +SerializationTypeMismatch+ will be raised.
#
# ==== Options
# ==== Attributes
#
# +attr_name+ The field name that should be serialized
# +class_name+ Optional, class name that the object type should be equal to
# * +attr_name+ - The field name that should be serialized.
# * +class_name+ - Optional, class name that the object type should be equal to.
#
# ==== Example
# # Serialize a preferences attribute
......@@ -1118,18 +1122,7 @@ def class_name(table_name = table_name) # :nodoc:
# Indicates whether the table associated with this class exists
def table_exists?
if connection.respond_to?(:tables)
connection.tables.include? table_name
else
# if the connection adapter hasn't implemented tables, there are two crude tests that can be
# used - see if getting column info raises an error, or if the number of columns returned is zero
begin
reset_column_information
columns.size > 0
rescue ActiveRecord::StatementInvalid
false
end
end
connection.table_exists?(table_name)
end
# Returns an array of column objects for the table associated with this class.
......@@ -1768,7 +1761,7 @@ def define_attr_method(name, value=nil, &block)
# class Article < ActiveRecord::Base
# def self.find_with_scope
# with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
# with_scope(:find => { :limit => 10})
# with_scope(:find => { :limit => 10 })
# find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
# end
# with_scope(:find => { :conditions => "author_id = 3" })
......@@ -2249,40 +2242,53 @@ def update_attributes!(attributes)
save!
end
# Initializes the +attribute+ to zero if nil and adds the value passed as +by+ (default is one). Only makes sense for number-based attributes. Returns self.
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
# The increment is performed directly on the underlying attribute, no setter is invoked.
# Only makes sense for number-based attributes. Returns +self+.
def increment(attribute, by = 1)
self[attribute] ||= 0
self[attribute] += by
self
end
# Increments the +attribute+ and saves the record.
# Note: Updates made with this method aren't subjected to validation checks
# Wrapper around +increment+ that saves the record. This method differs from
# its non-bang version in that it passes through the attribute setter.
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def increment!(attribute, by = 1)
increment(attribute, by).update_attribute(attribute, self[attribute])
end
# Initializes the +attribute+ to zero if nil and subtracts the value passed as +by+ (default is one). Only makes sense for number-based attributes. Returns self.
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
# The decrement is performed directly on the underlying attribute, no setter is invoked.
# Only makes sense for number-based attributes. Returns +self+.
def decrement(attribute, by = 1)
self[attribute] ||= 0
self[attribute] -= by
self
end
# Decrements the +attribute+ and saves the record.
# Note: Updates made with this method aren't subjected to validation checks
# Wrapper around +decrement+ that saves the record. This method differs from
# its non-bang version in that it passes through the attribute setter.
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def decrement!(attribute, by = 1)
decrement(attribute, by).update_attribute(attribute, self[attribute])
end
# Turns an +attribute+ that's currently true into false and vice versa. Returns self.
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
# if the predicate returns +true+ the attribute will become +false+. This
# method toggles directly the underlying value without calling any setter.
# Returns +self+.
def toggle(attribute)
self[attribute] = !send("#{attribute}?")
self
end
# Toggles the +attribute+ and saves the record.
# Note: Updates made with this method aren't subjected to validation checks
# Wrapper around +toggle+ that saves the record. This method differs from
# its non-bang version in that it passes through the attribute setter.
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def toggle!(attribute)
toggle(attribute).update_attribute(attribute, self[attribute])
end
......@@ -2582,7 +2588,7 @@ def assign_multiparameter_attributes(pairs)
end
def instantiate_time_object(name, values)
if Time.zone && self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
Time.zone.local(*values)
else
Time.time_with_datetime_fallback(@@default_timezone, *values)
......
......@@ -44,6 +44,12 @@ def uncached
@query_cache_enabled = old
end
# Clears the query cache.
#
# One reason you may wish to call this method explicitly is between queries
# that ask the database to randomize results. Otherwise the cache would see
# the same SQL query and repeatedly return the same result each time, silently
# undermining the randomness you were expecting.
def clear_query_cache
@query_cache.clear if @query_cache
end
......
......@@ -20,6 +20,10 @@ def table_alias_for(table_name)
# def tables(name = nil) end
def table_exists?(table_name)
tables.include?(table_name.to_s)
end
# Returns an array of indexes for the given table.
# def indexes(table_name, name = nil) end
......@@ -93,8 +97,8 @@ def create_table(table_name, options = {})
yield table_definition
if options[:force]
drop_table(table_name, options) rescue nil
if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
end
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
......
......@@ -73,7 +73,7 @@ def quote_table_name(name)
# REFERENTIAL INTEGRITY ====================================
# Override to turn off referential integrity while executing +&block+
# Override to turn off referential integrity while executing <tt>&block</tt>.
def disable_referential_integrity(&block)
yield
end
......@@ -101,7 +101,7 @@ def requires_reloading?
false
end
# Lazily verify this connection, calling +active?+ only if it hasn't
# Lazily verify this connection, calling <tt>active?</tt> only if it hasn't
# been called for +timeout+ seconds.
def verify!(timeout)
now = Time.now.to_i
......
......@@ -146,19 +146,19 @@ def missing_default_forged_as_empty_string?(default)
#
# Options:
#
# * <tt>:host</tt> -- Defaults to localhost
# * <tt>:port</tt> -- Defaults to 3306
# * <tt>:socket</tt> -- Defaults to /tmp/mysql.sock
# * <tt>:username</tt> -- Defaults to root
# * <tt>:password</tt> -- Defaults to nothing
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
# * <tt>:encoding</tt> -- (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection
# * <tt>:sslkey</tt> -- Necessary to use MySQL with an SSL connection
# * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
# * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
# * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
# * <tt>:host</tt> - Defaults to "localhost".
# * <tt>:port</tt> - Defaults to 3306.
# * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
# * <tt>:username</tt> - Defaults to "root"
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
#
# By default, the MysqlAdapter will consider all columns of type tinyint(1)
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
# as boolean. If you wish to disable this emulation (which was the default
# behavior in versions 0.13.1 and earlier) you can add the following line
# to your environment.rb file:
......
......@@ -228,15 +228,15 @@ module ConnectionAdapters
#
# Options:
#
# * <tt>:host</tt> -- Defaults to localhost
# * <tt>:port</tt> -- Defaults to 5432
# * <tt>:username</tt> -- Defaults to nothing
# * <tt>:password</tt> -- Defaults to nothing
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
# * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
# * <tt>:encoding</tt> -- An optional client encoding that is used in a SET client_encoding TO <encoding> call on the connection.
# * <tt>:min_messages</tt> -- An optional client min messages that is used in a SET client_min_messages TO <min_messages> call on the connection.
# * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
# * <tt>:host</tt> - Defaults to "localhost".
# * <tt>:port</tt> - Defaults to 5432.
# * <tt>:username</tt> - Defaults to nothing.
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO <encoding></tt> call on the connection.
# * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
# * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
class PostgreSQLAdapter < AbstractAdapter
# Returns 'PostgreSQL' as adapter name for identification purposes.
def adapter_name
......
......@@ -70,7 +70,7 @@ def binary_to_string(value)
#
# Options:
#
# * <tt>:database</tt> -- Path to the database file.
# * <tt>:database</tt> - Path to the database file.
class SQLiteAdapter < AbstractAdapter
def adapter_name #:nodoc:
'SQLite'
......
......@@ -426,7 +426,7 @@ class FixtureClassNotFound < StandardError #:nodoc:
# == Support for YAML defaults
#
# You probably already know how to use YAML to set and reuse defaults in
# your +database.yml+ file,. You can use the same technique in your fixtures:
# your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
#
# DEFAULTS: &DEFAULTS
# created_on: <%= 3.weeks.ago.to_s(:db) %>
......
......@@ -153,6 +153,17 @@ def counter_cache_column
end
end
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
# of a HasManyThrough or HasOneThrough association. Example:
#
# class Post < ActiveRecord::Base
# has_many :taggings
# has_many :tags, :through => :taggings
# end
#
# tags_reflection = Post.reflect_on_association(:tags)
# taggings_reflection = tags_reflection.through_reflection
#
def through_reflection
@through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
end
......@@ -168,7 +179,8 @@ def source_reflection_names
# Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
# (The <tt>:tags</tt> association on Tagging below.)
#
# class Post
# class Post < ActiveRecord::Base
# has_many :taggings
# has_many :tags, :through => :taggings
# end
#
......
......@@ -678,7 +678,7 @@ def validates_uniqueness_of(*attr_names)
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:with</tt> - The regular expression used to validate the format with (note: must be supplied!).
# * <tt>:on</tt> Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
......@@ -784,7 +784,7 @@ def validates_exclusion_of(*attr_names)
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
# * <tt>:on</tt> Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
......@@ -802,8 +802,8 @@ def validates_associated(*attr_names)
end
# Validates whether the value of the specified attribute is numeric by trying to convert it to
# a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>integer</tt> is set to true).
# a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
#
# class Person < ActiveRecord::Base
# validates_numericality_of :value, :on => :create
......
......@@ -6,15 +6,16 @@ def setup
end
def test_tables
if @connection.respond_to?(:tables)
tables = @connection.tables
assert tables.include?("accounts")
assert tables.include?("authors")
assert tables.include?("tasks")
assert tables.include?("topics")
else
warn "#{@connection.class} does not respond to #tables"
end
tables = @connection.tables
assert tables.include?("accounts")
assert tables.include?("authors")
assert tables.include?("tasks")
assert tables.include?("topics")
end
def test_table_exists?
assert @connection.table_exists?("accounts")
assert !@connection.table_exists?("nonexistingtable")
end
def test_indexes
......
......@@ -29,6 +29,10 @@ def test_loading_with_one_association
post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'")
assert_equal 2, post.comments.size
assert post.comments.include?(comments(:greetings))
posts = Post.find(:all, :include => :last_comment)
post = posts.find { |p| p.id == 1 }
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_conditions_with_or
......
......@@ -149,6 +149,12 @@ def test_save_on_parent_does_not_load_target
assert !david.projects.loaded?
end
def test_inspect_does_not_reload_a_not_yet_loaded_target
andreas = Developer.new :name => 'Andreas', :log => 'new developer added'
assert !andreas.audit_logs.loaded?
assert_match(/message: "new developer added"/, andreas.audit_logs.inspect)
end
def test_save_on_parent_saves_children
developer = Developer.create :name => "Bryan", :salary => 50_000
assert_equal 1, developer.reload.audit_logs.size
......
......@@ -173,6 +173,41 @@ def test_setting_time_zone_aware_attribute_in_other_time_zone
end
end
def test_setting_time_zone_aware_attribute_with_string
utc_time = Time.utc(2008, 1, 1)
(-11..13).each do |timezone_offset|
time_string = utc_time.in_time_zone(timezone_offset).to_s
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
record.written_on = time_string
assert_equal Time.zone.parse(time_string), record.written_on
assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
end
end
end
def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
record.written_on = ' '
assert_nil record.written_on
end
end
def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone
time_string = 'Tue Jan 01 00:00:00 2008'
(-11..13).each do |timezone_offset|
in_time_zone timezone_offset do
record = @target.new
record.written_on = time_string
assert_equal Time.zone.parse(time_string), record.written_on
assert_equal TimeZone[timezone_offset], record.written_on.time_zone
assert_equal Time.utc(2008, 1, 1), record.written_on.time
end
end
end
def test_setting_time_zone_aware_attribute_in_current_time_zone
utc_time = Time.utc(2008, 1, 1)
in_time_zone "Pacific Time (US & Canada)" do
......
......@@ -8,6 +8,7 @@
require 'models/developer'
require 'models/post'
require 'models/customer'
require 'models/job'
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers
......@@ -857,6 +858,14 @@ def test_select_rows
Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}
end
def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
assert_equal 2, Post.find(:all,:include=>{:authors=>:author_address},:order=>' author_addresses.id DESC ', :limit=>2).size
assert_equal 3, Post.find(:all,:include=>{:author=>:author_address,:authors=>:author_address},
:order=>' author_addresses_authors.id DESC ', :limit=>3).size
end
protected
def bind(statement, *vars)
if vars.first.is_a?(Hash)
......
......@@ -209,6 +209,24 @@ def test_create_table_with_primary_key_prefix_as_table_name
ActiveRecord::Base.primary_key_prefix_type = nil
end
uses_mocha('test_create_table_with_force_true_does_not_drop_nonexisting_table') do
def test_create_table_with_force_true_does_not_drop_nonexisting_table
if Person.connection.table_exists?(:testings2)
Person.connection.drop_table :testings2
end
# using a copy as we need the drop_table method to
# continue to work for the ensure block of the test
temp_conn = Person.connection.dup
temp_conn.expects(:drop_table).never
temp_conn.create_table :testings2, :force => true do |t|
t.column :foo, :string
end
ensure
Person.connection.drop_table :testings2 rescue nil
end
end
# SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL
# column to a table without a default value.
......
......@@ -2,140 +2,137 @@
require 'active_record/schema_dumper'
require 'stringio'
if ActiveRecord::Base.connection.respond_to?(:tables)
class SchemaDumperTest < ActiveRecord::TestCase
def standard_dump
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = []
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
stream.string
end
class SchemaDumperTest < ActiveRecord::TestCase
def standard_dump
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = []
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
stream.string
end
def test_schema_dump
output = standard_dump
assert_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
end
def test_schema_dump
output = standard_dump
assert_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
end
def test_schema_dump_excludes_sqlite_sequence
output = standard_dump
assert_no_match %r{create_table "sqlite_sequence"}, output
end
def test_schema_dump_excludes_sqlite_sequence
output = standard_dump
assert_no_match %r{create_table "sqlite_sequence"}, output
end
def assert_line_up(lines, pattern, required = false)
return assert(true) if lines.empty?
matches = lines.map { |line| line.match(pattern) }
assert matches.all? if required
matches.compact!
return assert(true) if matches.empty?
assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length
end
def assert_line_up(lines, pattern, required = false)
return assert(true) if lines.empty?
matches = lines.map { |line| line.match(pattern) }
assert matches.all? if required
matches.compact!
return assert(true) if matches.empty?
assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length
end
def column_definition_lines(output = standard_dump)
output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) }
end
def column_definition_lines(output = standard_dump)
output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) }
end
def test_types_line_up
column_definition_lines.each do |column_set|
next if column_set.empty?
def test_types_line_up
column_definition_lines.each do |column_set|
next if column_set.empty?
lengths = column_set.map do |column|
if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/)
match[0].length
end
lengths = column_set.map do |column|
if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/)
match[0].length
end
assert_equal 1, lengths.uniq.length
end
end
def test_arguments_line_up
column_definition_lines.each do |column_set|
assert_line_up(column_set, /:default => /)
assert_line_up(column_set, /:limit => /)
assert_line_up(column_set, /:null => /)
end
assert_equal 1, lengths.uniq.length
end
end
def test_no_dump_errors
output = standard_dump
assert_no_match %r{\# Could not dump table}, output
def test_arguments_line_up
column_definition_lines.each do |column_set|
assert_line_up(column_set, /:default => /)
assert_line_up(column_set, /:limit => /)
assert_line_up(column_set, /:null => /)
end
end
def test_schema_dump_includes_not_null_columns
stream = StringIO.new
def test_no_dump_errors
output = standard_dump
assert_no_match %r{\# Could not dump table}, output
end
ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/]
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_match %r{:null => false}, output
end
def test_schema_dump_includes_not_null_columns
stream = StringIO.new
def test_schema_dump_with_string_ignored_table
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/]
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_match %r{:null => false}, output
end
ActiveRecord::SchemaDumper.ignore_tables = ['accounts']
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
end
def test_schema_dump_with_string_ignored_table
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = ['accounts']
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
end
def test_schema_dump_with_regexp_ignored_table
stream = StringIO.new
def test_schema_dump_with_regexp_ignored_table
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = [/^account/]
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
end
ActiveRecord::SchemaDumper.ignore_tables = [/^account/]
def test_schema_dump_illegal_ignored_table_value
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = [5]
assert_raise(StandardError) do
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
end
end
def test_schema_dump_illegal_ignored_table_value
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = [5]
assert_raise(StandardError) do
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
end
if current_adapter?(:MysqlAdapter)
def test_schema_dump_should_not_add_default_value_for_mysql_text_field
output = standard_dump
assert_match %r{t.text\s+"body",\s+:null => false$}, output
end
if current_adapter?(:MysqlAdapter)
def test_schema_dump_should_not_add_default_value_for_mysql_text_field
output = standard_dump
assert_match %r{t.text\s+"body",\s+:null => false$}, output
end
def test_mysql_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
assert_not_nil(match, "nonstandardpk table not found")
assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
end
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
assert_match %r{t.binary\s+"tiny_blob",\s+:limit => 255$}, output
assert_match %r{t.binary\s+"normal_blob"$}, output
assert_match %r{t.binary\s+"medium_blob",\s+:limit => 16777215$}, output
assert_match %r{t.binary\s+"long_blob",\s+:limit => 2147483647$}, output
assert_match %r{t.text\s+"tiny_text",\s+:limit => 255$}, output
assert_match %r{t.text\s+"normal_text"$}, output
assert_match %r{t.text\s+"medium_text",\s+:limit => 16777215$}, output
assert_match %r{t.text\s+"long_text",\s+:limit => 2147483647$}, output
end
def test_mysql_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
assert_not_nil(match, "nonstandardpk table not found")
assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
end
def test_schema_dump_includes_decimal_options
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/]
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
assert_match %r{t.binary\s+"tiny_blob",\s+:limit => 255$}, output
assert_match %r{t.binary\s+"normal_blob"$}, output
assert_match %r{t.binary\s+"medium_blob",\s+:limit => 16777215$}, output
assert_match %r{t.binary\s+"long_blob",\s+:limit => 2147483647$}, output
assert_match %r{t.text\s+"tiny_text",\s+:limit => 255$}, output
assert_match %r{t.text\s+"normal_text"$}, output
assert_match %r{t.text\s+"medium_text",\s+:limit => 16777215$}, output
assert_match %r{t.text\s+"long_text",\s+:limit => 2147483647$}, output
end
end
def test_schema_dump_includes_decimal_options
stream = StringIO.new
ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/]
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
output = stream.string
assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output
end
end
......@@ -49,6 +49,10 @@ def find_least_recent
before_create do |developer|
developer.audit_logs.build :message => "Computer created"
end
def log=(message)
audit_logs.build :message => message
end
end
class AuditLog < ActiveRecord::Base
......
......@@ -9,6 +9,8 @@ def greeting
belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts
has_one :last_comment, :class_name => 'Comment', :order => 'id desc'
has_many :comments, :order => "body" do
def find_most_recent
find(:first, :order => "id DESC")
......
*SVN*
* Adding Date.current, which returns Time.zone.today if config.time_zone is set; otherwise returns Date.today [Geoff Buesing]
* TimeWithZone: date part getter methods (#year #mon #day etc) are defined on class; no longer relying on method_missing [Geoff Buesing]
* Time.zone.parse return nil for strings with no date information [Geoff Buesing]
* Time.zone.parse respects offset information in string. Resolves #105. [Scott Fleckenstein, Geoff Buesing]
* Added Ruby 1.8 implementation of Process.daemon
* Duration #since and #ago with no argument (e.g., 5.days.ago) return TimeWithZone when config.time_zone is set. Introducing Time.current, which returns Time.zone.now if config.time_zone is set, otherwise just returns Time.now [Geoff Buesing]
......
......@@ -25,6 +25,11 @@ def yesterday
def tomorrow
::Date.today.tomorrow
end
# Returns Time.zone.today when config.time_zone is set, otherwise just returns Date.today.
def current
::Time.zone_default ? ::Time.zone.today : ::Date.today
end
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
......
......@@ -23,10 +23,7 @@ def default(key = nil)
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
alias_method :regular_update, :update unless method_defined?(:regular_update)
#
# Assigns a new value to the hash.
#
# Example:
# Assigns a new value to the hash:
#
# hash = HashWithIndifferentAccess.new
# hash[:key] = "value"
......@@ -35,25 +32,15 @@ def []=(key, value)
regular_writer(convert_key(key), convert_value(value))
end
# Updates the instantized hash with values from the second:
#
# Updates the instantized hash with values from the second.
#
# Example:
#
# >> hash_1 = HashWithIndifferentAccess.new
# => {}
#
# >> hash_1[:key] = "value"
# => "value"
#
# >> hash_2 = HashWithIndifferentAccess.new
# => {}
# hash_1 = HashWithIndifferentAccess.new
# hash_1[:key] = "value"
#
# >> hash_2[:key] = "New Value!"
# => "New Value!"
# hash_2 = HashWithIndifferentAccess.new
# hash_2[:key] = "New Value!"
#
# >> hash_1.update(hash_2)
# => {"key"=>"New Value!"}
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
#
def update(other_hash)
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
......@@ -62,7 +49,13 @@ def update(other_hash)
alias_method :merge!, :update
# Checks the hash for a key matching the argument passed in
# Checks the hash for a key matching the argument passed in:
#
# hash = HashWithIndifferentAccess.new
# hash["key"] = "value"
# hash.key? :key # => true
# hash.key? "key" # => true
#
def key?(key)
super(convert_key(key))
end
......@@ -76,7 +69,13 @@ def fetch(key, *extras)
super(convert_key(key), *extras)
end
# Returns an array of the values at the specified indicies.
# Returns an array of the values at the specified indices:
#
# hash = HashWithIndifferentAccess.new
# hash[:a] = "x"
# hash[:b] = "y"
# hash.values_at("a", "b") # => ["x", "y"]
#
def values_at(*indices)
indices.collect {|key| self[convert_key(key)]}
end
......
......@@ -175,6 +175,20 @@ def warn(callstack, called, args)
ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack)
end
end
class DeprecatedInstanceVariable < Delegator #:nodoc:
def initialize(value, method)
super(value)
@method = method
@value = value
end
def __getobj__
ActiveSupport::Deprecation.warn("Instance variable @#{@method} is deprecated! Call instance method #{@method} instead.")
@value
end
end
end
end
......
......@@ -3,6 +3,11 @@
# The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
# and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
# in inflections.rb.
#
# The Rails core team has stated patches for the inflections library will not be accepted
# in order to avoid breaking legacy applications which may be relying on errant inflections.
# If you discover an incorrect inflection and require it for your application, you'll need
# to correct it yourself (explained below).
module Inflector
# A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
# inflection rules. Examples:
......
......@@ -163,6 +163,14 @@ def advance(options)
utc.advance(options).in_time_zone(time_zone)
end
%w(year mon month day mday hour min sec).each do |method_name|
class_eval <<-EOV
def #{method_name}
time.#{method_name}
end
EOV
end
def usec
time.respond_to?(:usec) ? time.usec : 0
end
......
......@@ -213,8 +213,14 @@ def at(secs)
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
def parse(str, now=now)
date_parts = Date._parse(str)
return if date_parts.blank?
time = Time.parse(str, now) rescue DateTime.parse(str)
ActiveSupport::TimeWithZone.new(nil, self, time)
if date_parts[:offset].nil?
ActiveSupport::TimeWithZone.new(nil, self, time)
else
time.in_time_zone(self)
end
end
# Returns an ActiveSupport::TimeWithZone instance representing the current time
......
......@@ -208,6 +208,29 @@ def test_xmlschema
end
end
end
uses_mocha 'TestDateCurrent' do
def test_current_returns_date_today_when_zone_default_not_set
with_env_tz 'US/Central' do
Time.stubs(:now).returns Time.local(1999, 12, 31, 23)
assert_equal Date.new(1999, 12, 31), Date.today
assert_equal Date.new(1999, 12, 31), Date.current
end
end
def test_current_returns_time_zone_today_when_zone_default_set
silence_warnings do # silence warnings raised by tzinfo gem
Time.zone_default = TimeZone['Eastern Time (US & Canada)']
with_env_tz 'US/Central' do
Time.stubs(:now).returns Time.local(1999, 12, 31, 23)
assert_equal Date.new(1999, 12, 31), Date.today
assert_equal Date.new(2000, 1, 1), Date.current
end
end
ensure
Time.zone_default = nil
end
end
protected
def with_env_tz(new_tz = 'US/Eastern')
......
......@@ -328,10 +328,18 @@ def test_marshal_dump_and_load_with_tzinfo_identifier
assert_equal Time.utc(1999, 12, 31, 19), mtime.time
end
end
def test_method_missing_with_non_time_return_value
silence_warnings do # silence warnings raised by tzinfo gem
@twz.time.expects(:foo).returns('bar')
assert_equal 'bar', @twz.foo
end
end
def test_date_part_value_methods
silence_warnings do # silence warnings raised by tzinfo gem
twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone)
twz.stubs(:method_missing).returns(nil) #ensure these methods are defined directly on class
assert_equal 1999, twz.year
assert_equal 12, twz.month
assert_equal 31, twz.day
......
......@@ -149,3 +149,13 @@ def message
assert_nil @last_message
end
end
class DeprecatedIvarTest < Test::Unit::TestCase
def test_deprecated_ivar
@action = ActiveSupport::Deprecation::DeprecatedInstanceVariable.new("fubar", :foobar)
assert_deprecated(/Instance variable @foobar is deprecated! Call instance method foobar instead/) { assert_equal "fubar", @action }
end
end
......@@ -173,6 +173,14 @@ def test_parse
assert_equal zone, twz.time_zone
end
def test_parse_string_with_timezone
(-11..13).each do |timezone_offset|
zone = TimeZone[timezone_offset]
twz = zone.parse('1999-12-31 19:00:00')
assert_equal twz, zone.parse(twz.to_s)
end
end
def test_parse_with_old_date
silence_warnings do # silence warnings raised by tzinfo gem
zone = TimeZone['Eastern Time (US & Canada)']
......@@ -181,6 +189,23 @@ def test_parse_with_old_date
assert_equal zone, twz.time_zone
end
end
def test_parse_far_future_date_with_time_zone_offset_in_string
silence_warnings do # silence warnings raised by tzinfo gem
zone = TimeZone['Eastern Time (US & Canada)']
twz = zone.parse('2050-12-31 19:00:00 -10:00') # i.e., 2050-01-01 05:00:00 UTC
assert_equal [0,0,0,1,1,2051], twz.to_a[0,6]
assert_equal zone, twz.time_zone
end
end
def test_parse_returns_nil_when_string_without_date_information_is_passed_in
silence_warnings do # silence warnings raised by tzinfo gem
zone = TimeZone['Eastern Time (US & Canada)']
assert_nil zone.parse('foobar')
assert_nil zone.parse(' ')
end
end
uses_mocha 'TestParseWithIncompleteDate' do
def test_parse_with_incomplete_date
......
*SVN*
* script/dbconsole fires up the command-line database client. #102 [Steve Purcell]
* Fix bug where plugin init.rb files from frozen gem specs weren't being run. (pjb3) [#122 state:resolved]
* Made the location of the routes file configurable with config.routes_configuration_file (Scott Fleckenstein) [#88]
......
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/dbconsole'
......@@ -7,4 +7,9 @@ class ApplicationController < ActionController::Base
# See ActionController::RequestForgeryProtection for details
# Uncomment the :secret if you're not using the cookie session store
protect_from_forgery # :secret => '<%= app_secret %>'
# See ActionController::Base for details
# Uncomment this to filter the contents of submitted sensitive data parameters
# from your application log (in this case, all fields with names like "password").
# filter_parameter_logging :password
end
require 'yaml'
require 'optparse'
OptionParser.new do |opt|
opt.banner = "Usage: dbconsole [environment]"
opt.parse!(ARGV)
abort opt.to_s unless (0..1).include?(ARGV.size)
end
env = ARGV.first || ENV['RAILS_ENV'] || 'development'
unless config = YAML.load_file(RAILS_ROOT + "/config/database.yml")[env]
abort "No database is configured for the environment '#{env}'"
end
def find_cmd(*commands)
dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
commands += commands.map{|cmd| "#{cmd}.exe"} if RUBY_PLATFORM =~ /win32/
commands.detect do |cmd|
dirs_on_path.detect do |path|
File.executable? File.join(path, cmd)
end
end || abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
end
case config["adapter"]
when "mysql"
args = {
'host' => '--host',
'port' => '--port',
'socket' => '--socket',
'username' => '--user',
'password' => '--password',
'encoding' => '--default-character-set'
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
args << config['database']
exec(find_cmd('mysql5', 'mysql'), *args)
when "postgresql"
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
exec(find_cmd('psql'), '-U', config["username"], config["database"])
when "sqlite"
exec(find_cmd('sqlite'), config["database"])
when "sqlite3"
exec(find_cmd('sqlite3'), config["database"])
else
abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
end
......@@ -572,11 +572,13 @@ def plugins=(plugins)
attr_accessor :plugin_loader
# Enables or disables plugin reloading. You can get around this setting per plugin.
# If <tt>reload_plugins?</tt> is false, add this to your plugin's init.rb to make it reloadable:
# If <tt>reload_plugins?</tt> is false, add this to your plugin's <tt>init.rb</tt>
# to make it reloadable:
#
# Dependencies.load_once_paths.delete lib_path
#
# If <tt>reload_plugins?</tt> is true, add this to your plugin's init.rb to only load it once:
# If <tt>reload_plugins?</tt> is true, add this to your plugin's <tt>init.rb</tt>
# to only load it once:
#
# Dependencies.load_once_paths << lib_path
#
......@@ -676,7 +678,7 @@ def database_configuration
YAML::load(ERB.new(IO.read(database_configuration_file)).result)
end
# The path to the current environment's file (development.rb, etc.). By
# The path to the current environment's file (<tt>development.rb</tt>, etc.). By
# default the file is at <tt>config/environments/#{environment}.rb</tt>.
def environment_path
"#{root_path}/config/environments/#{environment}.rb"
......
module Rails
# The Plugin class should be an object which provides the following methods:
#
# * +name+ - used during initialisation to order the plugin (based on name and
# the contents of <tt>config.plugins</tt>)
# * +valid?+ - returns true if this plugin can be loaded
# * +load_paths+ - each path within the returned array will be added to the $LOAD_PATH
# * +load+ - finally 'load' the plugin.
# * +name+ - Used during initialisation to order the plugin (based on name and
# the contents of <tt>config.plugins</tt>).
# * +valid?+ - Returns true if this plugin can be loaded.
# * +load_paths+ - Each path within the returned array will be added to the <tt>$LOAD_PATH</tt>.
# * +load+ - Finally 'load' the plugin.
#
# These methods are expected by the Rails::Plugin::Locator and Rails::Plugin::Loader classes.
# The default implementation returns the <tt>lib</tt> directory as its </tt>load_paths</tt>,
# The default implementation returns the <tt>lib</tt> directory as its <tt>load_paths</tt>,
# and evaluates <tt>init.rb</tt> when <tt>load</tt> is called.
#
# You can also inspect the about.yml data programmatically:
......@@ -31,13 +31,13 @@ def valid?
File.directory?(directory) && (has_lib_directory? || has_init_file?)
end
# Returns a list of paths this plugin wishes to make available in $LOAD_PATH
# Returns a list of paths this plugin wishes to make available in <tt>$LOAD_PATH</tt>.
def load_paths
report_nonexistant_or_empty_plugin! unless valid?
has_lib_directory? ? [lib_path] : []
end
# Evaluates a plugin's init.rb file
# Evaluates a plugin's init.rb file.
def load(initializer)
return if loaded?
report_nonexistant_or_empty_plugin! unless valid?
......
......@@ -72,7 +72,7 @@ def manifest
m.file "environments/test.rb", "config/environments/test.rb"
# Scripts
%w( about console destroy generate performance/benchmarker performance/profiler performance/request process/reaper process/spawner process/inspector runner server plugin ).each do |file|
%w( about console dbconsole destroy generate performance/benchmarker performance/profiler performance/request process/reaper process/spawner process/inspector runner server plugin ).each do |file|
m.file "bin/#{file}", "script/#{file}", script_options
end
......
......@@ -46,7 +46,7 @@ namespace :db do
@encoding = config[:encoding] || ENV['CHARSET'] || 'utf8'
begin
ActiveRecord::Base.establish_connection(config.merge('database' => 'template1'))
ActiveRecord::Base.connection.create_database(config['database'], :encoding => @encoding)
ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
ActiveRecord::Base.establish_connection(config)
rescue
$stderr.puts $!, *($!.backtrace)
......@@ -314,14 +314,9 @@ namespace :db do
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"])
when "postgresql"
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
enc_option = "-E #{abcs["test"]["encoding"]}" if abcs["test"]["encoding"]
ActiveRecord::Base.clear_active_connections!
`dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
`createdb #{enc_option} -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}`
drop_database(abcs['test'])
create_database(abcs['test'])
when "sqlite","sqlite3"
dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
File.delete(dbfile) if File.exist?(dbfile)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册