diff options
73 files changed, 1356 insertions, 790 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 48e877a182..a3762bf81b 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -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 @@ module ActionMailer #:nodoc: # 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) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 87f570d55c..852e783f40 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,6 +1,9 @@ *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] diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb index 272b8f6841..9ef093acfc 100644 --- a/actionpack/lib/action_controller/assertions/selector_assertions.rb +++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -21,11 +21,11 @@ module ActionController # 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 @@ module ActionController # === 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 diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index c6d28b492a..e1bf005f39 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -159,28 +159,34 @@ module ActionController #: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 @@ module ActionController #: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>. diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 08cbd13876..c4063dfb4b 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -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): # diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 7aa6ce154b..a70ed72f03 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -4,23 +4,24 @@ require 'uri' 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 ActionController #:nodoc: # # == 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) diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index aa0e05dbd7..e2b7716aa2 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -47,25 +47,27 @@ module ActionController # 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) diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 22dbc8bbc5..643ff7e5f4 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -33,11 +33,17 @@ module ActionController # 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: diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb index b52a2d4ff1..946a0ed152 100644 --- a/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/request_forgery_protection.rb @@ -69,10 +69,10 @@ module ActionController #:nodoc: # # 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) diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index 0f0fa27d74..26f75780c1 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -236,27 +236,27 @@ module ActionController # 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 @@ module ActionController # 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 @@ module ActionController # 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 @@ module ActionController # # --> 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 @@ module ActionController 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) diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index 0bffe21431..6aa266513d 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -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 ActionController 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 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController @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 @@ module ActionController 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? diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index 24ea8c7f2d..b142d18b47 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -244,11 +244,12 @@ module ActionController 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 diff --git a/actionpack/lib/action_controller/session/active_record_store.rb b/actionpack/lib/action_controller/session/active_record_store.rb index 379fcd62b6..1e8eb57acb 100644 --- a/actionpack/lib/action_controller/session/active_record_store.rb +++ b/actionpack/lib/action_controller/session/active_record_store.rb @@ -13,7 +13,7 @@ class CGI # 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 @@ class CGI # 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 @@ class CGI # 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 @@ class CGI # 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, diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index b143806818..3a38f23396 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -29,16 +29,16 @@ module ActionController # 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. diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index a6da81de07..4840b2526d 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -5,9 +5,9 @@ module ActionView #: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 @@ module ActionView #: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 @@ module ActionView #: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 @@ module ActionView #: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 @@ module ActionView #: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 & bold</b></em> + # xml.em { xml.b("emph & bold") } # => <em><b>emph & 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 @@ module ActionView #: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 @@ module ActionView #:nodoc: 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 diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index cbd390421a..8a9c8044ae 100755 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -283,7 +283,7 @@ module ActionView # # 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 @@ module ActionView 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 diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index f37b428b80..922a0662fe 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -316,12 +316,12 @@ module ActionView # 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 diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 908728c0e6..1b12aa8058 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -631,6 +631,27 @@ module ActionView # 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 @@ module ActionView # # 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 diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 6d27494213..375ebfcdc5 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -19,15 +19,15 @@ module ActionView # 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 @@ module ActionView # 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 @@ module ActionView # 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. # diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index a708ecb3fb..6b294be6bd 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -119,7 +119,7 @@ module ActionView "" 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 @@ module ActionView 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 diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb index 32b26206c3..ed10e72953 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -1,49 +1,49 @@ 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 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase 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 diff --git a/actionpack/test/controller/new_render_test.rb b/actionpack/test/controller/new_render_test.rb index 8e39057f55..6e2c6d90c6 100644 --- a/actionpack/test/controller/new_render_test.rb +++ b/actionpack/test/controller/new_render_test.rb @@ -246,6 +246,10 @@ class NewRenderTestController < ActionController::Base 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 @@ class NewRenderTest < Test::Unit::TestCase 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 diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb index def8613215..12c1eaea69 100644 --- a/actionpack/test/controller/record_identifier_test.rb +++ b/actionpack/test/controller/record_identifier_test.rb @@ -57,6 +57,18 @@ class RecordIdentifierTest < Test::Unit::TestCase 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 @@ class NestedRecordIdentifierTest < RecordIdentifierTest 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 diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index b138cee29f..0d089d0f23 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -226,6 +226,28 @@ class ResourcesTest < Test::Unit::TestCase 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 @@ class ResourcesTest < Test::Unit::TestCase 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}" diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 640afd58f8..b28f7bcdff 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -25,7 +25,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase 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 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase 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 diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 2373600bfe..ae83c7bf47 100755 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -950,6 +950,15 @@ class DateHelperTest < ActionView::TestCase 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 @@ class DateHelperTest < ActionView::TestCase 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 diff --git a/activemodel/lib/active_model/validations/associated.rb b/activemodel/lib/active_model/validations/associated.rb index ae3e4974bc..b2d78af580 100644 --- a/activemodel/lib/active_model/validations/associated.rb +++ b/activemodel/lib/active_model/validations/associated.rb @@ -25,7 +25,7 @@ module ActiveModel # # 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. diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 8f5c5e8df8..92ca5f4082 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -15,17 +15,17 @@ module ActiveModel # 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. diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 04cf72b38c..597b876f22 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,9 @@ *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| diff --git a/activerecord/README b/activerecord/README index ff3f55ee8a..d68eb28a64 100755 --- a/activerecord/README +++ b/activerecord/README @@ -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. diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 3e7c787dee..da4ebdef51 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -65,7 +65,13 @@ module ActiveRecord 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 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 7d27b0607a..fb5f1f8a8c 100755..100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -110,7 +110,7 @@ module ActiveRecord # # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association # adds a method with that name to its model, it will override the inherited method and break things. - # For instance, #attributes and #connection would be bad choices for association names. + # For instance, +attributes+ and +connection+ would be bad choices for association names. # # == Auto-generated methods # @@ -258,7 +258,7 @@ module ActiveRecord # order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment # is cancelled. - # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>#association.build</tt> method (documented below). + # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>association.build</tt> method (documented below). # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It # does not save the parent either. # @@ -266,8 +266,8 @@ module ActiveRecord # # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object # (the owner of the collection) is not yet stored in the database. - # * If saving any of the objects being added to a collection (via <tt>#push</tt> or similar) fails, then <tt>#push</tt> returns +false+. - # * You can add an object to a collection without automatically saving it by using the <tt>#collection.build</tt> method (documented below). + # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) fails, then <tt>push</tt> returns +false+. + # * You can add an object to a collection without automatically saving it by using the <tt>collection.build</tt> method (documented below). # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved. # # === Association callbacks @@ -504,8 +504,8 @@ module ActiveRecord # # Address.find(:all, :include => :addressable) # INVALID # - # will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>. The reason is that the parent model's type - # is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that early query. + # will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model's type + # is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that early query. # # In versions greater than 2.0.2 eager loading in polymorphic associations is supported # thanks to a change in the overall preloading strategy. @@ -575,7 +575,7 @@ module ActiveRecord # end # end # - # When <tt>Firm#clients</tt> is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate + # When Firm#clients is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate # with a class in another module scope, this can be done by specifying the complete class name. Example: # # module MyApplication @@ -603,28 +603,28 @@ module ActiveRecord # Adds the following methods for retrieval and query of collections of associated objects: # +collection+ is replaced with the symbol passed as the first argument, so # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. - # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. + # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects. # An empty array is returned if none are found. - # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key. - # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL. + # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. + # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by setting their foreign keys to +NULL+. # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model. - # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate. - # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids - # * <tt>collection_singular_ids=ids</tt> - replace the collection with the objects identified by the primary keys in +ids+ - # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they + # * <tt>collection=objects</tt> - Replaces the collections content by deleting and adding objects as appropriate. + # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids + # * <tt>collection_singular_ids=ids</tt> - Replace the collection with the objects identified by the primary keys in +ids+ + # * <tt>collection.clear</tt> - Removes every object from the collection. This destroys the associated objects if they # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the database if <tt>:dependent => :delete_all</tt>, - # otherwise sets their foreign keys to NULL. - # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects. - # * <tt>collection.size</tt> - returns the number of associated objects. - # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find. - # * <tt>collection.build(attributes = {}, ...)</tt> - returns one or more new objects of the collection type that have been instantiated + # otherwise sets their foreign keys to +NULL+. + # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects. + # * <tt>collection.size</tt> - Returns the number of associated objects. + # * <tt>collection.find</tt> - Finds an associated object according to the same rules as Base.find. + # * <tt>collection.build(attributes = {}, ...)</tt> - Returns one or more new objects of the collection type that have been instantiated # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an # associated object already exists, not if it's +nil+! - # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated + # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation). # *Note:* This only works if an associated object already exists, not if it's +nil+! # - # Example: A +Firm+ class declares <tt>has_many :clients</tt>, which will add: + # Example: A Firm class declares <tt>has_many :clients</tt>, which will add: # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>) # * <tt>Firm#clients<<</tt> # * <tt>Firm#clients.delete</tt> @@ -640,45 +640,45 @@ module ActiveRecord # The declaration can also include an options hash to specialize the behavior of the association. # # Options are: - # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but - # if the real class name is +SpecialProduct+, you'll have to specify it with this option. - # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a +WHERE+ + # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_many :products</tt> will by default be linked to the Product class, but + # if the real class name is SpecialProduct, you'll have to specify it with this option. + # * <tt>:conditions</tt> - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+ # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from the association are scoped if a hash # is used. <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt> # or <tt>@blog.posts.build</tt>. - # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, - # such as <tt>last_name, first_name DESC</tt> - # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_many+ association will use +person_id+ - # as the default +foreign_key+. - # * <tt>:dependent</tt> - if set to <tt>:destroy</tt> all the associated objects are destroyed - # alongside this object by calling their destroy method. If set to <tt>:delete_all</tt> all associated - # objects are deleted *without* calling their destroy method. If set to <tt>:nullify</tt> all associated - # objects' foreign keys are set to +NULL+ *without* calling their save callbacks. *Warning:* This option is ignored when also using - # the <tt>through</tt> option. - # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex + # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, + # such as <tt>last_name, first_name DESC</tt>. + # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id" + # as the default <tt>:foreign_key</tt>. + # * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed + # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated + # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated + # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using + # the <tt>:through</tt> option. + # * <tt>:finder_sql</tt> - Specify a complete SQL statement to fetch the association. This is a good way to go for complex # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added. - # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is + # * <tt>:counter_sql</tt> - Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>. - # * <tt>:extend</tt> - specify a named module for extending the proxy. See "Association extensions". - # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded. - # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause. - # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned. - # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. - # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join + # * <tt>:extend</tt> - Specify a named module for extending the proxy. See "Association extensions". + # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded. + # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause. + # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned. + # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join # but not include the joined columns. - # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>). - # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> + # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>). + # * <tt>:through</tt> - Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt> # or <tt>has_many</tt> association on the join model. - # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be + # * <tt>:source</tt> - Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or - # <tt>:subscriber</tt> on +Subscription+, unless a <tt>:source</tt> is given. - # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source + # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given. + # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_many :through</tt> queries where the source # association is a polymorphic +belongs_to+. - # * <tt>:uniq</tt> - if set to +true+, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>. - # * <tt>:readonly</tt> - if set to +true+, all the associated objects are readonly through the association. + # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>. + # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. # # Option examples: # has_many :comments, :order => "posted_on" @@ -712,14 +712,14 @@ module ActiveRecord # Adds the following methods for retrieval and query of a single associated object: # +association+ is replaced with the symbol passed as the first argument, so # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. - # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found. - # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key, + # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found. + # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, sets it as the foreign key, # and saves the associate object. - # * <tt>association.nil?</tt> - returns +true+ if there is no associated object. - # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated + # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object. + # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if # an association already exists. It will NOT work if the association is +nil+. - # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated + # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation). # # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add: @@ -732,28 +732,28 @@ module ActiveRecord # The declaration can also include an options hash to specialize the behavior of the association. # # Options are: - # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but - # if the real class name is +Person+, you'll have to specify it with this option. - # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+ + # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but + # if the real class name is Person, you'll have to specify it with this option. + # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as <tt>rank = 5</tt>. - # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, - # such as <tt>last_name, first_name DESC</tt> - # * <tt>:dependent</tt> - if set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to + # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, + # such as <tt>last_name, first_name DESC</tt>. + # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to <tt>:nullify</tt>, the associated # object's foreign key is set to +NULL+. Also, association is assigned. - # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_one+ association will use +person_id+ - # as the default +foreign_key+. - # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded. - # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>). + # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id" + # as the default <tt>:foreign_key</tt>. + # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded. + # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>). # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a # <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model. - # * <tt>:source</tt>: Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be + # * <tt>:source</tt> - Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be # inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a - # <tt>:favorite</tt> on +Favorite+, unless a <tt>:source</tt> is given. - # * <tt>:readonly</tt> - if set to +true+, the associated object is readonly through the association. + # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given. + # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -796,12 +796,12 @@ module ActiveRecord # Adds the following methods for retrieval and query for a single associated object for which this object holds an id: # +association+ is replaced with the symbol passed as the first argument, so # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. - # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found. - # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key. - # * <tt>association.nil?</tt> - returns +true+ if there is no associated object. - # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated + # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found. + # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, and sets it as the foreign key. + # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object. + # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. - # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated + # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation). # # Example: A Post class declares <tt>belongs_to :author</tt>, which will add: @@ -814,34 +814,35 @@ module ActiveRecord # The declaration can also include an options hash to specialize the behavior of the association. # # Options are: - # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but - # if the real class name is +Person+, you'll have to specify it with this option. - # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+ + # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but + # if the real class name is Person, you'll have to specify it with this option. + # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as <tt>authorized = 1</tt>. - # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, - # such as <tt>last_name, first_name DESC</tt> - # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name - # of the association with an +_id+ suffix. So a class that defines a +belongs_to :person+ association will use +person_id+ as the default +foreign_key+. - # Similarly, +belongs_to :favorite_person, :class_name => "Person"+ will use a foreign key of +favorite_person_id+. - # * <tt>:dependent</tt> - if set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to + # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, + # such as <tt>last_name, first_name DESC</tt>. + # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name + # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use + # "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt> + # will use a foreign key of "favorite_person_id". + # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when # <tt>belongs_to</tt> is used in conjunction with a <tt>has_many</tt> relationship on another class because of the potential to leave # orphaned records behind. - # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+ + # * <tt>:counter_cache</tt> - Caches the number of belonging objects on the associate class through the use of +increment_counter+ # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's - # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging +Comment+ class) - # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing + # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class) + # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.) # When creating a counter cache column, the database statement or migration must specify a default value of <tt>0</tt>, failing to do - # this results in a counter with NULL value, which will never increment. - # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly. - # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded. + # this results in a counter with +NULL+ value, which will never increment. + # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+. + # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded. # Not allowed if the association is polymorphic. - # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing +true+. + # * <tt>:polymorphic</tt> - Specify this association is a polymorphic association by passing +true+. # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute - # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). - # * <tt>:readonly</tt> - if set to +true+, the associated object is readonly through the association. + # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). + # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. # # Option examples: # belongs_to :firm, :foreign_key => "client_of" @@ -926,14 +927,14 @@ module ActiveRecord end # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as - # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+ - # will give the default join table name of +developers_projects+ because "D" outranks "P". Note that this precedence - # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths, + # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project + # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence + # is calculated using the <tt><</tt> operator for String. This means that if the strings are of different lengths, # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher - # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt> - # to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>, - # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the - # custom <tt>join_table</tt> option if you need to. + # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" + # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", + # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the + # custom <tt>:join_table</tt> option if you need to. # # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as @@ -943,23 +944,23 @@ module ActiveRecord # Adds the following methods for retrieval and query: # +collection+ is replaced with the symbol passed as the first argument, so # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. - # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects. + # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects. # An empty array is returned if none are found. - # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table + # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by creating associations in the join table # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). - # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table. + # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by removing their associations from the join table. # This does not destroy the objects. - # * <tt>collection=objects</tt> - replaces the collection's content by deleting and adding objects as appropriate. - # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids - # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+ - # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects. - # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects. - # * <tt>collection.size</tt> - returns the number of associated objects. - # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that + # * <tt>collection=objects</tt> - Replaces the collection's content by deleting and adding objects as appropriate. + # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids. + # * <tt>collection_singular_ids=ids</tt> - Replace the collection by the objects identified by the primary keys in +ids+. + # * <tt>collection.clear</tt> - Removes every object from the collection. This does not destroy the objects. + # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects. + # * <tt>collection.size</tt> - Returns the number of associated objects. + # * <tt>collection.find(id)</tt> - Finds an associated object responding to the +id+ and that # meets the condition that it has to be associated with this object. - # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated + # * <tt>collection.build(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated # with +attributes+ and linked to this object through the join table, but has not yet been saved. - # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated + # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation). # # Example: A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add: @@ -978,38 +979,38 @@ module ActiveRecord # The declaration may include an options hash to specialize the behavior of the association. # # Options are: - # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred + # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the - # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option. - # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want. + # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. + # * <tt>:join_table</tt> - Specify the name of the join table if the default based on lexical order isn't what you want. # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any # +has_and_belongs_to_many+ declaration in order to work. - # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_and_belongs_to_many+ association - # will use +person_id+ as the default +foreign_key+. - # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is - # guessed to be the name of the associated class in lower-case and +_id+ suffixed. So if the associated class is +Project+, - # the +has_and_belongs_to_many+ association will use +project_id+ as the default association +foreign_key+. - # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+ + # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association + # will use "person_id" as the default <tt>:foreign_key</tt>. + # * <tt>:association_foreign_key</tt> - Specify the association foreign key used for the association. By default this is + # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project, + # the +has_and_belongs_to_many+ association will use "project_id" as the default <tt>:association_foreign_key</tt>. + # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used. # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt> # or <tt>@blog.posts.build</tt>. - # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, + # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, # such as <tt>last_name, first_name DESC</tt> - # * <tt>:uniq</tt> - if set to +true+, duplicate associated objects will be ignored by accessors and query methods - # * <tt>:finder_sql</tt> - overwrite the default generated SQL statement used to fetch the association with a manual statement - # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated - # classes with a manual statement - # * <tt>:insert_sql</tt> - overwrite the default generated SQL statement used to add links between the associated classes - # with a manual statement - # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions". - # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded. - # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause. - # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned. - # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. - # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join + # * <tt>:uniq</tt> - If true, duplicate associated objects will be ignored by accessors and query methods. + # * <tt>:finder_sql</tt> - Overwrite the default generated SQL statement used to fetch the association with a manual statement + # * <tt>:delete_sql</tt> - Overwrite the default generated SQL statement used to remove links between the associated + # classes with a manual statement. + # * <tt>:insert_sql</tt> - Overwrite the default generated SQL statement used to add links between the associated classes + # with a manual statement. + # * <tt>:extend</tt> - Anonymous module for extending the proxy, see "Association extensions". + # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded. + # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause. + # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned. + # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join # but not include the joined columns. - # * <tt>:readonly</tt> - if set to +true+, all the associated objects are readonly through the association. + # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. # # Option examples: # has_and_belongs_to_many :projects @@ -1446,6 +1447,12 @@ module ActiveRecord tables_from_conditions = conditions_tables(options) tables_from_order = order_tables(options) all_tables = tables_from_conditions + tables_from_order + distinct_join_associations = all_tables.uniq.map{|table| + join_dependency.joins_for_table_name(table) + }.flatten.compact.uniq + + + is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order) sql = "SELECT " @@ -1457,7 +1464,7 @@ module ActiveRecord sql << " FROM #{connection.quote_table_name table_name} " if is_distinct - sql << join_dependency.join_associations.reject{ |ja| !all_tables.include?(ja.table_name) }.collect(&:association_join).join + sql << distinct_join_associations.collect(&:association_join).join add_joins!(sql, options, scope) end @@ -1617,6 +1624,23 @@ module ActiveRecord end end + def join_for_table_name(table_name) + @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil + end + + def joins_for_table_name(table_name) + join = join_for_table_name(table_name) + result = nil + if join && join.is_a?(JoinAssociation) + result = [join] + if join.parent && join.parent.is_a?(JoinAssociation) + result = joins_for_table_name(join.parent.aliased_table_name) + + result + end + end + result + end + protected def build(associations, parent = nil) parent ||= @joins.last diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index c415ad2df3..68503a3c40 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -118,7 +118,7 @@ module ActiveRecord end def inspect - reload unless loaded? + load_target @target.inspect end @@ -167,7 +167,7 @@ module ActiveRecord def with_scope(*args, &block) @reflection.klass.send :with_scope, *args, &block end - + private def method_missing(method, *args) if load_target diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 46ecfc1969..2db27226f2 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -179,10 +179,10 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ffefc3cef3..74299bd572 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -92,13 +92,15 @@ module ActiveRecord #:nodoc: 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 @@ module ActiveRecord #:nodoc: 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 @@ module ActiveRecord #:nodoc: # == 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 @@ module ActiveRecord #:nodoc: # # == 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: # # 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 @@ module ActiveRecord #: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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #: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 @@ module ActiveRecord #:nodoc: # 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 @@ module ActiveRecord #:nodoc: 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 @@ module ActiveRecord #:nodoc: 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) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index e6b8e3ae90..2afd6064ad 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -44,6 +44,12 @@ module ActiveRecord @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 diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index b556516572..1594be40e2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -20,6 +20,10 @@ module ActiveRecord # 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 @@ module ActiveRecord 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 " diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 5c7e9f27a5..8c286f64db 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -73,7 +73,7 @@ module ActiveRecord # 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 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e742d60c5f..f00a2c8950 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -146,19 +146,19 @@ module ActiveRecord # # 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: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e3f7969cdf..2ec2d80af4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -228,15 +228,15 @@ module ActiveRecord # # 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 diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 8fa62c1845..8abbc6d0a4 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -70,7 +70,7 @@ module ActiveRecord # # 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' diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 7d5fd35dae..9367ea523d 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -426,7 +426,7 @@ end # == 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) %> diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 61005af83f..8614ef8751 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -153,6 +153,17 @@ module ActiveRecord 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 @@ module ActiveRecord # 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 # diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 50db32725d..d25e8cd0da 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -678,7 +678,7 @@ module ActiveRecord # * <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 @@ module ActiveRecord # # 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 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 8a74b6d6f5..91504af901 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -6,15 +6,16 @@ class AdapterTest < ActiveRecord::TestCase 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 diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 546ed80894..67b57ceb42 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -29,6 +29,10 @@ class EagerAssociationTest < ActiveRecord::TestCase 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 diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index ed2fab6d22..d8fe98bf57 100755 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -149,6 +149,12 @@ class AssociationProxyTest < ActiveRecord::TestCase 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 diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 61a049ab36..c336fd9afb 100755 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -173,6 +173,41 @@ class AttributeMethodsTest < ActiveRecord::TestCase 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 diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index b7f87fe6e8..2acfe9b387 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -8,6 +8,7 @@ require 'models/entrant' 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 @@ class FinderTest < ActiveRecord::TestCase 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) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index d4e81827aa..6be31b5f86 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -209,6 +209,24 @@ if ActiveRecord::Base.connection.supports_migrations? 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. diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index ba8bff3b44..c42b0efba0 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -2,140 +2,137 @@ require "cases/helper" 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 diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 192c2cb5ab..f77fd0e96d 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -49,6 +49,10 @@ class Developer < ActiveRecord::Base 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 diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 22c5a645b8..d9101706b5 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -9,6 +9,8 @@ class Post < ActiveRecord::Base 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") diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index eff92c91de..08fd0123f6 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -14,12 +14,19 @@ module ActiveResource # Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the # URI of the resources. # - # class Person < ActiveResource::Base - # self.site = "http://api.people.com:3000/" - # end + # class Person < ActiveResource::Base + # self.site = "http://api.people.com:3000/" + # end # # Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and - # you can now use Active Resource's lifecycles methods to manipulate resources. + # you can now use Active Resource's lifecycles methods to manipulate resources. In the case where you already have + # an existing model with the same name as the desired RESTful resource you can set the +element_name+ value. + # + # class PersonResource < ActiveResource::Base + # self.site = "http://api.people.com:3000/" + # self.element_name = "person" + # end + # # # == Lifecycle methods # @@ -72,13 +79,13 @@ module ActiveResource # # You can validate resources client side by overriding validation methods in the base class. # - # class Person < ActiveResource::Base - # self.site = "http://api.people.com:3000/" - # protected - # def validate - # errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/ - # end - # end + # class Person < ActiveResource::Base + # self.site = "http://api.people.com:3000/" + # protected + # def validate + # errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/ + # end + # end # # See the ActiveResource::Validations documentation for more information. # @@ -118,18 +125,17 @@ module ActiveResource # exception. # # # GET http://api.people.com:3000/people/999.xml - # ryan = Person.find(999) # => Raises ActiveResource::ResourceNotFound - # # => Response = 404 + # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound # # <tt>404</tt> is just one of the HTTP error response codes that ActiveResource will handle with its own exception. The # following HTTP response codes will also result in these exceptions: # - # 200 - 399:: Valid response, no exception - # 404:: ActiveResource::ResourceNotFound - # 409:: ActiveResource::ResourceConflict - # 422:: ActiveResource::ResourceInvalid (rescued by save as validation errors) - # 401 - 499:: ActiveResource::ClientError - # 500 - 599:: ActiveResource::ServerError + # * 200..399 - Valid response, no exception + # * 404 - ActiveResource::ResourceNotFound + # * 409 - ActiveResource::ResourceConflict + # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors) + # * 401..499 - ActiveResource::ClientError + # * 500..599 - ActiveResource::ServerError # # These custom exceptions allow you to deal with resource errors more naturally and with more precision # rather than returning a general HTTP error. For example: @@ -177,12 +183,15 @@ module ActiveResource # self.timeout = 5 # end # - # This sets the +timeout+ to 5 seconds. You can adjust the timeout to a value suitable for the RESTful API + # This sets the +timeout+ to 5 seconds. You can adjust the +timeout+ to a value suitable for the RESTful API # you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource # clients (especially if you are using Active Resource in a Rails application) to fail-fast (see # http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your # server. # + # When a timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from + # ActiveResource::TimeoutError in your Active Resource method calls. + # # Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+ # sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default # <tt>read_timeout</tt> is 60 seconds on most Ruby implementations. @@ -229,7 +238,7 @@ module ActiveResource end end - # Gets the user for REST HTTP authentication + # Gets the user for REST HTTP authentication. def user # Not using superclass_delegating_reader. See +site+ for explanation if defined?(@user) @@ -239,13 +248,13 @@ module ActiveResource end end - # Sets the user for REST HTTP authentication + # Sets the user for REST HTTP authentication. def user=(user) @connection = nil @user = user end - # Gets the password for REST HTTP authentication + # Gets the password for REST HTTP authentication. def password # Not using superclass_delegating_reader. See +site+ for explanation if defined?(@password) @@ -255,13 +264,13 @@ module ActiveResource end end - # Sets the password for REST HTTP authentication + # Sets the password for REST HTTP authentication. def password=(password) @connection = nil @password = password end - # Sets the format that attributes are sent and received in from a mime type reference. Example: + # Sets the format that attributes are sent and received in from a mime type reference: # # Person.format = :json # Person.find(1) # => GET /people/1.json @@ -269,7 +278,7 @@ module ActiveResource # Person.format = ActiveResource::Formats::XmlFormat # Person.find(1) # => GET /people/1.xml # - # Default format is :xml. + # Default format is <tt>:xml</tt>. def format=(mime_type_reference_or_format) format = mime_type_reference_or_format.is_a?(Symbol) ? ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format @@ -278,7 +287,7 @@ module ActiveResource connection.format = format if site end - # Returns the current format, default is ActiveResource::Formats::XmlFormat + # Returns the current format, default is ActiveResource::Formats::XmlFormat. def format # :nodoc: read_inheritable_attribute("format") || ActiveResource::Formats[:xml] end @@ -367,9 +376,9 @@ module ActiveResource # will split from the prefix options. # # ==== Options - # +prefix_options+:: A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt> + # +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt> # would yield a URL like <tt>/accounts/19/purchases.xml</tt>). - # +query_options+:: A hash to add items to the query string for the request. + # +query_options+ - A hash to add items to the query string for the request. # # ==== Examples # Post.element_path(1) @@ -393,9 +402,9 @@ module ActiveResource # will split from the +prefix_options+. # # ==== Options - # +prefix_options+:: A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt> - # would yield a URL like <tt>/accounts/19/purchases.xml</tt>). - # +query_options+:: A hash to add items to the query string for the request. + # * +prefix_options+ - A hash to add a prefix to the request for nested URL's (e.g., <tt>:account_id => 19</tt> + # would yield a URL like <tt>/accounts/19/purchases.xml</tt>). + # * +query_options+ - A hash to add items to the query string for the request. # # ==== Examples # Post.collection_path @@ -431,40 +440,34 @@ module ActiveResource # ==== Examples # Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true) # my_person = Person.find(:first) - # my_person.email - # # => myname@nospam.com + # my_person.email # => myname@nospam.com # # dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true) - # dhh.valid? - # # => true - # dhh.new? - # # => false + # dhh.valid? # => true + # dhh.new? # => false # # # We'll assume that there's a validation that requires the name attribute # that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true) - # that_guy.valid? - # # => false - # that_guy.new? - # # => true - # + # that_guy.valid? # => false + # that_guy.new? # => true def create(attributes = {}) returning(self.new(attributes)) { |res| res.save } end - # Core method for finding resources. Used similarly to Active Record's find method. + # Core method for finding resources. Used similarly to Active Record's +find+ method. # # ==== Arguments # The first argument is considered to be the scope of the query. That is, how many # resources are returned from the request. It can be one of the following. # - # * <tt>:one</tt>: Returns a single resource. - # * <tt>:first</tt>: Returns the first resource found. - # * <tt>:all</tt>: Returns every resource that matches the request. + # * <tt>:one</tt> - Returns a single resource. + # * <tt>:first</tt> - Returns the first resource found. + # * <tt>:all</tt> - Returns every resource that matches the request. # # ==== Options # - # * +from+: Sets the path or custom method that resources will be fetched from. - # * +params+: Sets query and prefix (nested URL) parameters. + # * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from. + # * <tt>:params</tt> - Sets query and prefix (nested URL) parameters. # # ==== Examples # Person.find(1) @@ -511,19 +514,14 @@ module ActiveResource # All options specify prefix and query parameters. # # ==== Examples - # Event.delete(2) - # # => DELETE /events/2 + # Event.delete(2) # sends DELETE /events/2 # # Event.create(:name => 'Free Concert', :location => 'Community Center') - # my_event = Event.find(:first) - # # => Events (id: 7) - # Event.delete(my_event.id) - # # => DELETE /events/7 + # my_event = Event.find(:first) # let's assume this is event with ID 7 + # Event.delete(my_event.id) # sends DELETE /events/7 # # # Let's assume a request to events/5/cancel.xml - # Event.delete(params[:id]) - # # => DELETE /events/5 - # + # Event.delete(params[:id]) # sends DELETE /events/5 def delete(id, options = {}) connection.delete(element_path(id, options)) end @@ -532,11 +530,9 @@ module ActiveResource # # ==== Examples # Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...') - # Note.exists?(1) - # # => true + # Note.exists?(1) # => true # - # Note.exists(1349) - # # => false + # Note.exists(1349) # => false def exists?(id, options = {}) if id prefix_options, query_options = split_options(options[:params]) @@ -626,7 +622,7 @@ module ActiveResource attr_accessor :attributes #:nodoc: attr_accessor :prefix_options #:nodoc: - # Constructor method for new resources; the optional +attributes+ parameter takes a +Hash+ + # Constructor method for new resources; the optional +attributes+ parameter takes a hash # of attributes for the new resource. # # ==== Examples @@ -643,27 +639,26 @@ module ActiveResource load(attributes) end - # Returns a clone of the resource that hasn't been assigned an id yet and + # Returns a clone of the resource that hasn't been assigned an +id+ yet and # is treated as a new resource. # - # ryan = Person.find(1) - # not_ryan = ryan.clone - # not_ryan.new? # => true + # ryan = Person.find(1) + # not_ryan = ryan.clone + # not_ryan.new? # => true # # Any active resource member attributes will NOT be cloned, though all other - # attributes are. This is to prevent the conflict between any prefix_options + # attributes are. This is to prevent the conflict between any +prefix_options+ # that refer to the original parent resource and the newly cloned parent # resource that does not exist. # - # ryan = Person.find(1) - # ryan.address = StreetAddress.find(1, :person_id => ryan.id) - # ryan.hash = {:not => "an ARes instance"} - # - # not_ryan = ryan.clone - # not_ryan.new? # => true - # not_ryan.address # => NoMethodError - # not_ryan.hash # => {:not => "an ARes instance"} - # + # ryan = Person.find(1) + # ryan.address = StreetAddress.find(1, :person_id => ryan.id) + # ryan.hash = {:not => "an ARes instance"} + # + # not_ryan = ryan.clone + # not_ryan.new? # => true + # not_ryan.address # => NoMethodError + # not_ryan.hash # => {:not => "an ARes instance"} def clone # Clone all attributes except the pk and any nested ARes cloned = attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.inject({}) do |attrs, (k, v)| @@ -684,16 +679,13 @@ module ActiveResource # # ==== Examples # not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall') - # not_new.new? - # # => false + # not_new.new? # => false # # is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM') - # is_new.new? - # # => true + # is_new.new? # => true # # is_new.save - # is_new.new? - # # => false + # is_new.new? # => false # def new? id.nil? @@ -715,7 +707,7 @@ module ActiveResource end # Test for equality. Resource are equal if and only if +other+ is the same object or - # is an instance of the same class, is not +new?+, and has the same +id+. + # is an instance of the same class, is not <tt>new?</tt>, and has the same +id+. # # ==== Examples # ryan = Person.create(:name => 'Ryan') @@ -756,17 +748,13 @@ module ActiveResource # ==== Examples # my_invoice = Invoice.create(:customer => 'That Company') # next_invoice = my_invoice.dup - # next_invoice.new? - # # => true + # next_invoice.new? # => true # # next_invoice.save - # next_invoice == my_invoice - # # => false (different id attributes) + # next_invoice == my_invoice # => false (different id attributes) # - # my_invoice.customer - # # => That Company - # next_invoice.customer - # # => That Company + # my_invoice.customer # => That Company + # next_invoice.customer # => That Company def dup returning self.class.new do |resource| resource.attributes = @attributes @@ -781,16 +769,12 @@ module ActiveResource # # ==== Examples # my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2) - # my_company.new? - # # => true - # my_company.save - # # => POST /companies/ (create) + # my_company.new? # => true + # my_company.save # sends POST /companies/ (create) # - # my_company.new? - # # => false + # my_company.new? # => false # my_company.size = 10 - # my_company.save - # # => PUT /companies/1 (update) + # my_company.save # sends PUT /companies/1 (update) def save new? ? create : update end @@ -801,20 +785,17 @@ module ActiveResource # my_id = 3 # my_person = Person.find(my_id) # my_person.destroy - # Person.find(my_id) - # # => 404 (Resource Not Found) + # Person.find(my_id) # 404 (Resource Not Found) # # new_person = Person.create(:name => 'James') - # new_id = new_person.id - # # => 7 + # new_id = new_person.id # => 7 # new_person.destroy - # Person.find(new_id) - # # => 404 (Resource Not Found) + # Person.find(new_id) # 404 (Resource Not Found) def destroy connection.delete(element_path, self.class.headers) end - # Evaluates to <tt>true</tt> if this resource is not +new?+ and is + # Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is # found on the remote service. Using this method, you can check for # resources that may have been deleted between the object's instantiation # and actions on it. @@ -822,17 +803,14 @@ module ActiveResource # ==== Examples # Person.create(:name => 'Theodore Roosevelt') # that_guy = Person.find(:first) - # that_guy.exists? - # # => true + # that_guy.exists? # => true # # that_lady = Person.new(:name => 'Paul Bean') - # that_lady.exists? - # # => false + # that_lady.exists? # => false # # guys_id = that_guy.id # Person.delete(guys_id) - # that_guy.exists? - # # => false + # that_guy.exists? # => false def exists? !new? && self.class.exists?(to_param, :params => prefix_options) end @@ -844,11 +822,11 @@ module ActiveResource # attribute, so it has the same options as the +to_xml+ methods in # ActiveSupport. # - # indent:: Set the indent level for the XML output (default is +2+). - # dasherize:: Boolean option to determine whether or not element names should - # replace underscores with dashes (default is <tt>false</tt>). - # skip_instruct:: Toggle skipping the +instruct!+ call on the XML builder - # that generates the XML declaration (default is <tt>false</tt>). + # * <tt>:indent</tt> - Set the indent level for the XML output (default is +2+). + # * <tt>:dasherize</tt> - Boolean option to determine whether or not element names should + # replace underscores with dashes (default is <tt>false</tt>). + # * <tt>:skip_instruct</tt> - Toggle skipping the +instruct!+ call on the XML builder + # that generates the XML declaration (default is <tt>false</tt>). # # ==== Examples # my_group = SubsidiaryGroup.find(:first) @@ -870,30 +848,26 @@ module ActiveResource # # ==== Examples # my_branch = Branch.find(:first) - # my_branch.name - # # => Wislon Raod + # my_branch.name # => "Wislon Raod" # # # Another client fixes the typo... # - # my_branch.name - # # => Wislon Raod + # my_branch.name # => "Wislon Raod" # my_branch.reload - # my_branch.name - # # => Wilson Road + # my_branch.name # => "Wilson Road" def reload self.load(self.class.find(to_param, :params => @prefix_options).attributes) end # A method to manually load attributes from a hash. Recursively loads collections of - # resources. This method is called in initialize and create when a +Hash+ of attributes + # resources. This method is called in +initialize+ and +create+ when a hash of attributes # is provided. # # ==== Examples # my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'} # # the_supplier = Supplier.find(:first) - # the_supplier.name - # # => 'J&M Textiles' + # the_supplier.name # => 'J&M Textiles' # the_supplier.load(my_attrs) # the_supplier.name('J&J Textiles') # @@ -924,10 +898,10 @@ module ActiveResource self end - # For checking respond_to? without searching the attributes (which is faster). + # For checking <tt>respond_to?</tt> without searching the attributes (which is faster). alias_method :respond_to_without_attributes?, :respond_to? - # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a +Person+ object with a + # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?("name")</tt>, <tt>my_person.respond_to?("name=")</tt>, and # <tt>my_person.respond_to?("name?")</tt>. def respond_to?(method, include_priv = false) diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 3ee9ed925c..f72825731e 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,13 @@ *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] diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 183471706b..1e2dbf118e 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -25,6 +25,11 @@ module ActiveSupport #:nodoc: 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) diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index 2213b09144..c96c5160b3 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -23,10 +23,7 @@ class HashWithIndifferentAccess < Hash 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 @@ class HashWithIndifferentAccess < Hash 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 @@ class HashWithIndifferentAccess < 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 @@ class HashWithIndifferentAccess < Hash 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 diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index c9f1884da3..7613652c71 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -175,6 +175,20 @@ module ActiveSupport 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 diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index c8736549f4..68fbf3da35 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -3,6 +3,11 @@ require 'singleton' # 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: diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 461d52e40e..21ddcaad48 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -163,6 +163,14 @@ module ActiveSupport 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 diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 9cdc2a74ed..0fa99135e2 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -213,8 +213,14 @@ class TimeZone # 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 diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 2e363c439a..5925ae3a61 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -208,6 +208,29 @@ class DateExtCalculationsTest < Test::Unit::TestCase 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') diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index df70e82c1d..64fcb4af09 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -328,10 +328,18 @@ class TimeWithZoneTest < Test::Unit::TestCase 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 diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index ebfa405947..11357e250f 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -149,3 +149,13 @@ class DeprecationTest < Test::Unit::TestCase 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 diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 2f06c347a1..3757ddcedb 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -173,6 +173,14 @@ class TimeZoneTest < Test::Unit::TestCase 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 @@ class TimeZoneTest < Test::Unit::TestCase 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 diff --git a/railties/CHANGELOG b/railties/CHANGELOG index f52206d4d3..2ca1965d97 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *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] diff --git a/railties/bin/dbconsole b/railties/bin/dbconsole new file mode 100755 index 0000000000..caa60ce829 --- /dev/null +++ b/railties/bin/dbconsole @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/dbconsole' diff --git a/railties/helpers/application.rb b/railties/helpers/application.rb index 9a79f69a41..0a3ed822a4 100644 --- a/railties/helpers/application.rb +++ b/railties/helpers/application.rb @@ -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 diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb new file mode 100644 index 0000000000..28c3a3e41f --- /dev/null +++ b/railties/lib/commands/dbconsole.rb @@ -0,0 +1,55 @@ +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 diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index 6db96f0158..dfd43042be 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -572,11 +572,13 @@ module Rails 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 @@ module Rails 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" diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index b45ec7de0e..04f7e37a20 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -1,14 +1,14 @@ 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 @@ module Rails 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? diff --git a/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/railties/lib/rails_generator/generators/applications/app/app_generator.rb index 85c417f7cd..2f2dd82682 100644 --- a/railties/lib/rails_generator/generators/applications/app/app_generator.rb +++ b/railties/lib/rails_generator/generators/applications/app/app_generator.rb @@ -72,7 +72,7 @@ class AppGenerator < Rails::Generator::Base 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 diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 20fdcce205..63f71448f8 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -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) |