diff options
211 files changed, 2641 insertions, 1083 deletions
@@ -5,12 +5,15 @@ gemspec if ENV['AREL'] gem "arel", :path => ENV['AREL'] else - gem "arel", '~> 2.1.0' + gem "arel", '~> 2.1.3' end gem "coffee-script" gem "sass" -gem "uglifier", :git => "git://github.com/lautis/uglifier.git" + +# This needs to be with require false to avoid +# it being automatically loaded by sprockets +gem "uglifier", ">= 1.0.0", :require => false gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -19,6 +22,7 @@ group :doc do gem "rdoc", "~> 3.4" gem "horo", "= 1.0.3" gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3" + gem "w3c_validators" end # AS @@ -26,9 +30,6 @@ gem "memcache-client", ">= 1.8.5" platforms :mri_18 do gem "system_timer" - # ruby-debug requires linecache which depends on require_relative but doesn't explicitly - # declare this in its gemspec - gem "require_relative" gem "ruby-debug", ">= 0.10.3" gem "json" end diff --git a/RAILS_VERSION b/RAILS_VERSION index 90040cdc46..dd8b7cd23b 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -3.1.0.rc1 +3.2.0.beta @@ -61,6 +61,9 @@ RDoc::Task.new do |rdoc| # The temporary solution is to have a README.rdoc without backslashes for # GitHub, and gsub it to generate the main page of the API. # + # Also, relative links in GitHub have to point to blobs, whereas in the API + # they need to point to files. + # # The idea for the future is to have totally different files, since the # API is no longer a generic entry point to Rails and deserves a # dedicated main page specifically thought as an API entry point. @@ -71,7 +74,7 @@ RDoc::Task.new do |rdoc| # since no autolinking happens there and RDoc displays the backslash # otherwise. rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" } - rdoc_main.gsub!(/link:blob\/master\/(\w+)\/README.rdoc/, "link:files/\\1/README_rdoc.html") + rdoc_main.gsub!(%r{link:blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html") File.open(RDOC_MAIN, 'w') do |f| f.write(rdoc_main) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 085afb57c1..cb289fd693 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -122,18 +122,19 @@ module ActionMailer #:nodoc: # # <%= users_url(:host => "example.com") %> # - # You want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't - # make sense to generate relative URLs in email messages. + # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the + # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will + # have no concept of a current URL from which to determine a relative path. # # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> # option as a configuration option in <tt>config/application.rb</tt>: # # config.action_mailer.default_url_options = { :host => "example.com" } # - # If you do decide to set a default <tt>:host</tt> for your mailers you want to use the - # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are - # generated because the <tt>url_for</tt> view helper will, by default, generate relative URLs when a - # <tt>:host</tt> option isn't explicitly provided. + # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the + # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper + # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing + # <tt>:only_path => false</tt> will ensure that absolute URLs are generated. # # = Sending mail # @@ -148,9 +149,9 @@ module ActionMailer #:nodoc: # # = Multipart Emails # - # Multipart messages can also be used implicitly because Action Mailer will automatically - # detect and use multipart templates, where each template is named after the name of the action, followed - # by the content type. Each such detected template will be added as separate part to the message. + # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use + # multipart templates, where each template is named after the name of the action, followed by the content + # type. Each such detected template will be added as a separate part to the message. # # For example, if the following templates exist: # * signup_notification.text.erb @@ -732,3 +733,4 @@ module ActionMailer #:nodoc: ActiveSupport.run_load_hooks(:action_mailer, self) end end + diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index b324ba790d..d1467fb526 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -1,7 +1,7 @@ require 'tmpdir' module ActionMailer - # This modules handles everything related to the delivery, from registering new + # This module handles everything related to mail delivery, from registering new # delivery methods to configuring the mail object to be sent. module DeliveryMethods extend ActiveSupport::Concern diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 5654e54d7a..b74ba3cb0d 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,9 +1,9 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index f4b6464bdc..b0f7d0bc11 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,9 @@ *Rails 3.2.0 (unreleased)* +* send_file now guess the mime type [Esad Hajdarevic] + +* Mime type entries for PDF, ZIP and other formats were added [Esad Hajdarevic] + * Generate hidden input before select with :multiple option set to true. This is useful when you rely on the fact that when no options is set, the state of select will be sent to rails application. Without hidden field @@ -26,6 +30,8 @@ *Rails 3.1.0 (unreleased)* +* Make sure respond_with with :js tries to render a template in all cases [José Valim] + * json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove] * Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist] @@ -59,8 +65,6 @@ You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb -* Added 'ActionView::Helpers::FormHelper.fields_for_with_index', similar to fields_for but allows to have access to the current iteration index [Jorge Bejar] - * Warn if we cannot verify CSRF token authenticity [José Valim] * Allow AM/PM format in datetime selectors [Aditya Sanghi] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 37a3d960f1..642fbcb8e6 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -18,13 +18,13 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('rack-cache', '~> 1.0.1') + s.add_dependency('rack-cache', '~> 1.0.2') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6') s.add_dependency('rack', '~> 1.3.0') s.add_dependency('rack-test', '~> 0.6.0') s.add_dependency('rack-mount', '~> 0.8.1') - s.add_dependency('sprockets', '~> 2.0.0.beta.10') - s.add_dependency('tzinfo', '~> 0.3.27') + s.add_dependency('sprockets', '= 2.0.0.beta.10') + s.add_dependency('tzinfo', '~> 0.3.29') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 8f73e244d7..d6f75bded0 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -81,11 +81,12 @@ module AbstractController # class EmployeeController < BankController # layout nil # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # The TellerController uses +teller.html.erb+, and TillController inherits that layout and - # uses it as well. + # In these examples: + # * The InformationController uses the "bank_standard" layout, inherited from BankController. + # * The TellerController follows convention and uses +app/views/layouts/teller.html.erb+. + # * The TillController inherits the layout from TellerController and uses +teller.html.erb+ as well. + # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method. + # * The EmployeeController does not use a layout at all. # # == Types of layouts # @@ -138,8 +139,8 @@ module AbstractController # # end # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. + # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will + # be rendered directly, without wrapping a layout around the rendered view. # # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. @@ -158,7 +159,7 @@ module AbstractController # end # end # - # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. + # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. module Layouts extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index c03c77cb4a..d14c5f940b 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -31,7 +31,7 @@ module ActionController # "302 Moved" HTTP response that takes the user to the index action. # # These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. - # Most actions are variations of these themes. + # Most actions are variations on these themes. # # == Requests # @@ -116,8 +116,8 @@ module ActionController # # Title: <%= @post.title %> # - # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use - # the manual rendering methods: + # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates + # will use the manual rendering methods: # # def search # @results = Search.find(params[:query]) @@ -132,9 +132,9 @@ module ActionController # # == Redirects # - # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database, - # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to) - # a <tt>show</tt> action that we'll assume has already been created. The code might look like this: + # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the + # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're + # going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this: # # def create # @entry = Entry.new(params[:entry]) @@ -146,7 +146,9 @@ module ActionController # end # end # - # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed. + # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed. + # Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action), + # and not some internal re-routing which calls both "create" and then "show" within one request. # # Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting. # diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 8c583c7ce0..496390402b 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -16,9 +16,10 @@ module ActionController #:nodoc: # caches_page :show, :new # end # - # 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. + # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used + # that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the + # existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack. + # This is much faster than handling the full dynamic request in the usual way. # # 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: @@ -132,8 +133,8 @@ module ActionController #:nodoc: end end - # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used - # If no options are provided, the requested url is used. Example: + # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used. + # If no options are provided, the url of the current request being handled is used. Example: # cache_page "I'm the cached content", :controller => "lists", :action => "show" def cache_page(content = nil, options = nil) return unless self.class.perform_caching && caching_allowed? diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index e9db0d97b6..938a6ae81c 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -61,6 +61,7 @@ module ActionController #:nodoc: end def after(controller) + self.controller = controller callback(:after) if controller.perform_caching # Clean up, so that the controller can be collected after this request self.controller = nil diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 8d813a8e38..35e29398e6 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -10,7 +10,7 @@ module ActionController format = payload[:format] format = format.to_s.upcase if format.is_a?(Symbol) - info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}" + info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}" info " Parameters: #{params.inspect}" unless params.empty? end @@ -20,10 +20,11 @@ module ActionController status = payload[:status] if status.nil? && payload[:exception].present? - status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil - end + status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil + end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? + message << "\n" info(message) end @@ -59,4 +60,4 @@ module ActionController end end -ActionController::LogSubscriber.attach_to :action_controller
\ No newline at end of file +ActionController::LogSubscriber.attach_to :action_controller diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 623a9873fc..50827d8107 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -26,8 +26,11 @@ module ActionController #:nodoc: # Options: # * <tt>:filename</tt> - suggests a filename for the browser to use. # Defaults to <tt>File.basename(path)</tt>. - # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json + # * <tt>:type</tt> - specifies an HTTP content type. + # You can specify either a string or a symbol for a registered type register with + # <tt>Mime::Type.register</tt>, for example :json + # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>. + # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. @@ -84,6 +87,8 @@ module ActionController #:nodoc: # * <tt>:filename</tt> - suggests a filename for the browser to use. # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json + # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>. + # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. @@ -108,6 +113,8 @@ module ActionController #:nodoc: private def send_file_headers!(options) + type_provided = options.has_key?(:type) + options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) [:type, :disposition].each do |arg| raise ArgumentError, ":#{arg} option required" if options[arg].nil? @@ -123,6 +130,10 @@ module ActionController #:nodoc: raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension self.content_type = extension else + if !type_provided && options[:filename] + # If type wasn't provided, try guessing from file extension. + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type + end self.content_type = content_type end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 2080e9b5b9..2271470334 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -7,17 +7,16 @@ module ActionController #:nodoc: # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks # by including a token in the rendered html for your application. This token is # stored as a random string in the session, to which an attacker does not have - # access. When a request reaches your application, \Rails then verifies the received - # token with the token in the session. Only HTML and javascript requests are checked, + # access. When a request reaches your application, \Rails verifies the received + # token with the token in the session. Only HTML and JavaScript requests are checked, # so this will not protect your XML API (presumably you'll have a different # authentication scheme there anyway). Also, GET requests are not protected as these # should be idempotent. # # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method, - # which will check the token and raise an ActionController::InvalidAuthenticityToken - # if it doesn't match what was expected. A call to this method is generated for new - # \Rails applications by default. You can customize the error message by editing - # public/422.html. + # which checks the token and resets the session if it doesn't match what was expected. + # A call to this method is generated for new \Rails applications by default. + # You can customize the error message by editing public/422.html. # # The token parameter is named <tt>authenticity_token</tt> by default. The name and # value of this token must be added to every layout that renders forms by including @@ -79,6 +78,8 @@ module ActionController #:nodoc: end end + # This is the method that defines the application behaviour when a request is found to be unverified. + # By default, \Rails resets the session when it finds an unverified request. def handle_unverified_request reset_session end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index ebadb29ea7..f3b7357e64 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -162,6 +162,11 @@ module ActionController #:nodoc: navigation_behavior(e) end + # to_js simply tries to render a template. If no template is found, raises the error. + def to_js + default_render + end + # All other formats follow the procedure below. First we try to render a # template, if the template is not available, we verify if the resource # responds to :to_format and display it. diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0bb436a476..5fe5334458 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -24,20 +24,8 @@ module ActionController #:nodoc: # # == Examples # - # Streaming can be added to a controller easily, all you need to do is - # call +stream+ in the controller class: - # - # class PostsController - # stream - # end - # - # The +stream+ method accepts the same options as +before_filter+ and friends: - # - # class PostsController - # stream :only => :index - # end - # - # You can also selectively turn on streaming for specific actions: + # Streaming can be added to a given template easily, all you need to do is + # to pass the :stream option. # # class PostsController # def index @@ -72,6 +60,9 @@ module ActionController #:nodoc: # render :stream => true # end # + # Notice that :stream only works with templates. Rendering :json + # or :xml with :stream won't work. + # # == Communication between layout and template # # When streaming, rendering happens top-down instead of inside-out. @@ -209,33 +200,9 @@ module ActionController #:nodoc: extend ActiveSupport::Concern include AbstractController::Rendering - attr_internal :stream - - module ClassMethods - # Render streaming templates. It accepts :only, :except, :if and :unless as options - # to specify when to stream, as in ActionController filters. - def stream(options={}) - if defined?(Fiber) - before_filter :_stream_filter, options - else - raise "You cannot use streaming if Fiber is not available." - end - end - end protected - # Mark following render calls as streaming. - def _stream_filter #:nodoc: - self.stream = true - end - - # Consider the stream option when normalazing options. - def _normalize_options(options) #:nodoc: - super - options[:stream] = self.stream unless options.key?(:stream) - end - # Set proper cache control and transfer encoding when streaming def _process_options(options) #:nodoc: super diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index d2ba052c8d..f0c29825ba 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -4,7 +4,6 @@ require "action_dispatch/railtie" require "action_view/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/paths" -require "sprockets/railtie" module ActionController class Railtie < Rails::Railtie diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index d14bb666cc..45bb641aee 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -210,7 +210,7 @@ module ActionController DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS def initialize(session = {}) - @env, @by = nil, nil + super(nil, nil) replace(session.stringify_keys) @loaded = true end diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 68f37d2f65..3da4f91051 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -7,6 +7,15 @@ Mime::Type.register "text/javascript", :js, %w( application/javascript applicati Mime::Type.register "text/css", :css Mime::Type.register "text/calendar", :ics Mime::Type.register "text/csv", :csv + +Mime::Type.register "image/png", :png, [], %w(png) +Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe) +Mime::Type.register "image/gif", :gif, [], %w(gif) +Mime::Type.register "image/bmp", :bmp, [], %w(bmp) +Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) + +Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) + Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml ) Mime::Type.register "application/rss+xml", :rss Mime::Type.register "application/atom+xml", :atom @@ -19,5 +28,8 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.json.org/JSONRequest.html Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) +Mime::Type.register "application/pdf", :pdf, [], %w(pdf) +Mime::Type.register "application/zip", :zip, [], %w(zip) + # Create Mime::ALL but do not add it to the SET. Mime::ALL = Mime::Type.new("*/*", :all, []) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 8cee9ecdc4..1c312f2587 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -129,6 +129,11 @@ module ActionDispatch @cookies[name.to_s] end + def key?(name) + @cookies.key?(name.to_s) + end + alias :has_key? :key? + def update(other_hash) @cookies.update other_hash.stringify_keys self diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 82c4fadb50..49aef0bf72 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -182,10 +182,12 @@ module ActionDispatch if record.is_a?(Symbol) || record.is_a?(String) route << record - else + elsif record route << ActiveModel::Naming.route_key(record) route = [route.join("_").singularize] if inflection == :singular route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural + else + raise ArgumentError, "Nil location provided. Can't build URI." end route << routing_type(options) diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 367c295cf5..f668b81b45 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -39,7 +39,7 @@ module ActionDispatch # # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary) def fixture_file_upload(path, mime_type = nil, binary = false) - fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path) + fixture_path = self.class.fixture_path if self.class.respond_to?(:fixture_path) Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary) end end diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index fcf0eb9565..add6b56425 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,9 +1,9 @@ module ActionPack module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 78eddb7530..d7229419a9 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -30,6 +30,7 @@ module ActionView extend ActiveSupport::Autoload eager_autoload do + autoload :AssetPaths autoload :Base autoload :Context autoload :Helpers diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb new file mode 100644 index 0000000000..4dd02755d3 --- /dev/null +++ b/actionpack/lib/action_view/asset_paths.rb @@ -0,0 +1,145 @@ +require 'active_support/core_ext/file' + +module ActionView + class AssetPaths #:nodoc: + attr_reader :config, :controller + + def initialize(config, controller = nil) + @config = config + @controller = controller + end + + # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. + # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL + # roots. Rewrite the asset path for cache-busting asset ids. Include + # asset host, if configured, with the correct request protocol. + # + # When include_host is true and the asset host does not specify the protocol + # the protocol parameter specifies how the protocol will be added. + # When :relative (default), the protocol will be determined by the client using current protocol + # When :request, the protocol will be the request protocol + # Otherwise, the protocol is used (E.g. :http, :https, etc) + def compute_public_path(source, dir, ext = nil, include_host = true, protocol = nil) + source = source.to_s + return source if is_uri?(source) + + source = rewrite_extension(source, dir, ext) if ext + source = rewrite_asset_path(source, dir) + source = rewrite_relative_url_root(source, relative_url_root) if has_request? + source = rewrite_host_and_protocol(source, protocol) if include_host + source + end + + # Return the filesystem path for the source + def compute_source_path(source, dir, ext) + source = rewrite_extension(source, dir, ext) if ext + File.join(config.assets_dir, dir, source) + end + + def is_uri?(path) + path =~ %r{^[-a-z]+://|^cid:|^//} + end + + private + + def rewrite_extension(source, dir, ext) + raise NotImplementedError + end + + def rewrite_asset_path(source, path = nil) + raise NotImplementedError + end + + def rewrite_relative_url_root(source, relative_url_root) + relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source + end + + def has_request? + controller.respond_to?(:request) + end + + def rewrite_host_and_protocol(source, protocol = nil) + host = compute_asset_host(source) + if host && !is_uri?(host) + if (protocol || default_protocol) == :request && !has_request? + host = nil + else + host = "#{compute_protocol(protocol)}#{host}" + end + end + host.nil? ? source : "#{host}#{source}" + end + + def compute_protocol(protocol) + protocol ||= default_protocol + case protocol + when :relative + "//" + when :request + unless @controller + invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.") + end + @controller.request.protocol + else + "#{protocol}://" + end + end + + def default_protocol + protocol = @config.action_controller.default_asset_host_protocol if @config.action_controller.present? + protocol ||= @config.default_asset_host_protocol + protocol || (has_request? ? :request : :relative) + end + + def invalid_asset_host!(help_message) + raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}" + end + + # Pick an asset host for this source. Returns +nil+ if no host is set, + # the host if no wildcard is set, the host interpolated with the + # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), + # or the value returned from invoking call on an object responding to call + # (proc or otherwise). + def compute_asset_host(source) + if host = asset_host_config + if host.respond_to?(:call) + args = [source] + arity = arity_of(host) + if arity > 1 && !has_request? + invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request.") + end + args << current_request if (arity > 1 || arity < 0) && has_request? + host.call(*args) + else + (host =~ /%d/) ? host % (source.hash % 4) : host + end + end + end + + def relative_url_root + config = controller.config if controller.respond_to?(:config) + config ||= config.action_controller if config.action_controller.present? + config ||= config + config.relative_url_root + end + + def asset_host_config + if config.action_controller.present? + config.action_controller.asset_host + else + config.asset_host + end + end + + # Returns the current request if one exists. + def current_request + controller.request if has_request? + end + + # Returns the arity of a callable + def arity_of(callable) + callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity + end + + end +end diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index 9a99c3cf52..fae2e4fc1c 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -1,83 +1,7 @@ -require 'active_support/core_ext/file' +ActiveSupport::Deprecation.warn "ActionView::Helpers::AssetPaths is deprecated. Please use ActionView::AssetPaths instead." module ActionView module Helpers - - class AssetPaths #:nodoc: - attr_reader :config, :controller - - def initialize(config, controller) - @config = config - @controller = controller - end - - # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. - # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL - # roots. Rewrite the asset path for cache-busting asset ids. Include - # asset host, if configured, with the correct request protocol. - def compute_public_path(source, dir, ext = nil, include_host = true) - source = source.to_s - return source if is_uri?(source) - - source = rewrite_extension(source, dir, ext) if ext - source = rewrite_asset_path(source, dir) - - if controller && include_host - has_request = controller.respond_to?(:request) - source = rewrite_host_and_protocol(source, has_request) - end - - source - end - - def is_uri?(path) - path =~ %r{^[-a-z]+://|^cid:|^//} - end - - private - - def rewrite_extension(source, dir, ext) - raise NotImplementedError - end - - def rewrite_asset_path(source, path = nil) - raise NotImplementedError - end - - def rewrite_relative_url_root(source, relative_url_root) - relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source - end - - def rewrite_host_and_protocol(source, has_request) - source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request - host = compute_asset_host(source) - if has_request && host && !is_uri?(host) - host = "#{controller.request.protocol}#{host}" - end - "#{host}#{source}" - end - - # Pick an asset host for this source. Returns +nil+ if no host is set, - # the host if no wildcard is set, the host interpolated with the - # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), - # or the value returned from invoking call on an object responding to call - # (proc or otherwise). - def compute_asset_host(source) - if host = config.asset_host - if host.respond_to?(:call) - case host.is_a?(Proc) ? host.arity : host.method(:call).arity - when 2 - request = controller.respond_to?(:request) && controller.request - host.call(source, request) - else - host.call(source) - end - else - (host =~ /%d/) ? host % (source.hash % 4) : host - end - end - end - end - + AssetPaths = ::ActionView::AssetPaths end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 7970176d37..0c3f011c92 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -347,7 +347,7 @@ module ActionView src = options[:src] = path_to_image(source) unless src =~ /^cid:/ - options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize } + options[:alt] = options.fetch(:alt){ image_alt(src) } end if size = options.delete(:size) @@ -362,6 +362,10 @@ module ActionView tag("img", options) end + def image_alt(src) + File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize + end + # Returns an html video tag for the +sources+. If +sources+ is a string, # a single video tag will be returned. If +sources+ is an array, a video # tag with nested source tags for each source will be returned. The diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index e4662a2919..3c05173a1b 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -60,12 +60,16 @@ module ActionView private - def path_to_asset(source, include_host = true) - asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host) + def path_to_asset(source, include_host = true, protocol = nil) + asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host, protocol) + end + + def path_to_asset_source(source) + asset_paths.compute_source_path(source, asset_name.to_s.pluralize, extension) end def compute_paths(*args) - expand_sources(*args).collect { |source| asset_paths.compute_public_path(source, asset_name.pluralize, extension, false) } + expand_sources(*args).collect { |source| path_to_asset_source(source) } end def expand_sources(sources, recursive) @@ -92,7 +96,7 @@ module ActionView def ensure_sources!(sources) sources.each do |source| - asset_file_path!(path_to_asset(source, false)) + asset_file_path!(path_to_asset_source(source)) end end @@ -123,19 +127,14 @@ module ActionView # Set mtime to the latest of the combined files to allow for # consistent ETag without a shared filesystem. - mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max + mt = asset_paths.map { |p| File.mtime(asset_file_path!(p)) }.max File.utime(mt, mt, joined_asset_path) end - def asset_file_path(path) - File.join(config.assets_dir, path.split('?').first) - end - - def asset_file_path!(path, error_if_file_is_uri = false) - if asset_paths.is_uri?(path) + def asset_file_path!(absolute_path, error_if_file_is_uri = false) + if asset_paths.is_uri?(absolute_path) raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri else - absolute_path = asset_file_path(path) raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path) return absolute_path end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 2d49823412..8b35aa8896 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -1,11 +1,11 @@ +require 'thread' require 'active_support/core_ext/file' -require 'action_view/helpers/asset_paths' module ActionView module Helpers module AssetTagHelper - class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: + class AssetPaths < ::ActionView::AssetPaths #:nodoc: # You can enable or disable the asset tag ids cache. # With the cache enabled, the asset tag helper methods will make fewer # expensive file system calls (the default implementation checks the file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index e4f11c9bc7..8c25d38bbd 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -16,7 +16,8 @@ module ActionView end def asset_tag(source, options) - tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => ERB::Util.html_escape(path_to_asset(source)) }.merge(options), false, false) + # We force the :request protocol here to avoid a double-download bug in IE7 and IE8 + tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => ERB::Util.html_escape(path_to_asset(source, true, :request)) }.merge(options), false, false) end def custom_dir @@ -52,7 +53,7 @@ module ActionView # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs). # Full paths from the document root will be passed through. # Used internally by +stylesheet_link_tag+ to build the stylesheet path. - # + # # ==== Examples # stylesheet_path "style" # => /stylesheets/style.css # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css @@ -60,11 +61,7 @@ module ActionView # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css def stylesheet_path(source) - if config.use_sprockets - asset_path(source, 'css') - else - asset_paths.compute_public_path(source, 'stylesheets', 'css') - end + asset_paths.compute_public_path(source, 'stylesheets', 'css', true, :request) end alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index b57617b3d1..f81ce3e31c 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -51,12 +51,10 @@ module ActionView # This dance is needed because Builder can't use capture pos = output_buffer.length yield - if output_buffer.is_a?(ActionView::OutputBuffer) - safe_output_buffer = output_buffer.to_str - fragment = safe_output_buffer.slice!(pos..-1) - self.output_buffer = ActionView::OutputBuffer.new(safe_output_buffer) - else - fragment = output_buffer.slice!(pos..-1) + output_safe = output_buffer.html_safe? + fragment = output_buffer.slice!(pos..-1) + if output_safe + self.output_buffer = output_buffer.html_safe end controller.write_fragment(name, fragment, options) end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 3debc9cc66..3a30263b49 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/module/method_names' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' @@ -567,18 +568,13 @@ module ActionView # ... # <% end %> # - # In addition, you may want to have access to the current iteration index. - # In that case, you can use a similar method called fields_for_with_index - # which receives a block with an extra parameter: + # When projects is already an association on Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: # - # <%= form_for @person do |person_form| %> - # ... - # <%= person_form.fields_for_with_index :projects do |project_fields, index| %> - # Position: <%= index %> - # Name: <%= project_fields.text_field :name %> - # <% end %> - # ... - # <% end %> + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects + # end # # If you want to destroy any of the associated models through the # form, you have to enable it first using the <tt>:allow_destroy</tt> @@ -1153,7 +1149,7 @@ module ActionView options["name"] ||= tag_name_with_index(@auto_index) options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else - options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '') + options["name"] ||= tag_name + (options['multiple'] ? '[]' : '') options["id"] = options.fetch("id"){ tag_id } end end @@ -1233,15 +1229,8 @@ module ActionView RUBY_EVAL end - # Check +fields_for+ for docs and examples. - def fields_for_with_index(record_name, record_object = nil, fields_options = {}, &block) - index = fields_options[:index] || options[:child_index] || nested_child_index(@object_name) - block_with_index = Proc.new{ |obj| block.call(obj, index) } - fields_for(record_name, record_object, fields_options, &block_with_index) - end - def fields_for(record_name, record_object = nil, fields_options = {}, &block) - fields_options, record_object = record_object, nil if record_object.is_a?(Hash) + fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] fields_options[:parent_builder] = self diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 4be95d8f7e..ae71ade588 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -256,6 +256,7 @@ module ActionView # # => "<p><span>I'm allowed!</span> It's true.</p>" def simple_format(text, html_options={}, options={}) text = '' if text.nil? + text = text.dup if text.frozen? start_tag = tag('p', html_options, true) text = sanitize(text) unless options[:sanitize] == false text = text.to_str diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 489b96856d..d70ae4196b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -55,9 +55,9 @@ module ActionView # # ==== Relying on named routes # - # If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter, - # you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing - # a Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as + # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will + # trigger the named route for that record. The lookup will happen on the name of the class. So passing a + # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). # # ==== Examples @@ -112,13 +112,13 @@ module ActionView end end - # Creates a link tag of the given +name+ using a URL created by the set - # of +options+. See the valid options in the documentation for - # +url_for+. It's also possible to pass a string instead - # of an options hash to get a link tag that uses the value of the string as the - # href for the link, or use <tt>:back</tt> to link to the referrer - a JavaScript back - # link will be used in place of a referrer if none exists. If +nil+ is passed as - # a name, the link itself will become the name. + # Creates a link tag of the given +name+ using a URL created by the set of +options+. + # See the valid options in the documentation for +url_for+. It's also possible to + # pass a String instead of an options hash, which generates a link tag that uses the + # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead + # of an options hash will generate a link to the referrer (a JavaScript back link + # will be used in place of a referrer if none exists). If +nil+ is passed as the name + # the value of the link itself will become the name. # # ==== Signatures # @@ -619,7 +619,9 @@ module ActionView end def add_method_to_attributes!(html_options, method) - html_options["rel"] = "nofollow" if method.to_s.downcase != "get" + if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/ + html_options["rel"] = "#{html_options["rel"]} nofollow".strip + end html_options["data-method"] = method end diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb index 29ffbd6fdd..bf90d012bf 100644 --- a/actionpack/lib/action_view/log_subscriber.rb +++ b/actionpack/lib/action_view/log_subscriber.rb @@ -4,7 +4,7 @@ module ActionView # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber def render_template(event) - message = "Rendered #{from_rails_root(event.payload[:identifier])}" + message = " Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] message << (" (%.1fms)" % event.duration) info(message) diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake new file mode 100644 index 0000000000..0236350576 --- /dev/null +++ b/actionpack/lib/sprockets/assets.rake @@ -0,0 +1,26 @@ +namespace :assets do + desc "Compile all the assets named in config.assets.precompile" + task :precompile do + if ENV["RAILS_GROUPS"].to_s.empty? + ENV["RAILS_GROUPS"] = "assets" + Kernel.exec $0, *ARGV + else + Rake::Task["environment"].invoke + Sprockets::Helpers::RailsHelper + + assets = Rails.application.config.assets.precompile + Rails.application.assets.precompile(*assets) + end + end + + desc "Remove compiled assets" + task :clean => :environment do + assets = Rails.application.config.assets + public_asset_path = Rails.public_path + assets.prefix + file_list = FileList.new("#{public_asset_path}/**/*") + file_list.each do |file| + rm_rf file + rm_rf "#{file}.gz" + end + end +end diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb new file mode 100644 index 0000000000..6544953df4 --- /dev/null +++ b/actionpack/lib/sprockets/compressors.rb @@ -0,0 +1,21 @@ +module Sprockets + class NullCompressor + def compress(content) + content + end + end + + class LazyCompressor + def initialize(&block) + @block = block + end + + def compressor + @compressor ||= @block.call || NullCompressor.new + end + + def compress(content) + compressor.compress(content) + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index 0b6bd8ca40..7d709de9e2 100644 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ b/actionpack/lib/sprockets/helpers/rails_helper.rb @@ -1,5 +1,4 @@ -require "action_view/helpers/asset_paths" -require "action_view/helpers/asset_tag_helper" +require "action_view" module Sprockets module Helpers @@ -11,7 +10,16 @@ module Sprockets @asset_paths ||= begin config = self.config if respond_to?(:config) controller = self.controller if respond_to?(:controller) - RailsHelper::AssetPaths.new(config, controller) + config ||= Rails.application.config + if config.action_controller.present? + config.action_controller.default_asset_host_protocol ||= :relative + else + config.default_asset_host_protocol ||= :relative + end + paths = RailsHelper::AssetPaths.new(config, controller) + paths.asset_environment = asset_environment + paths.asset_prefix = asset_prefix + paths end end @@ -51,7 +59,7 @@ module Sprockets 'rel' => "stylesheet", 'type' => "text/css", 'media' => "screen", - 'href' => asset_path(source, 'css', body) + 'href' => asset_path(source, 'css', body, :request) }.merge(options.stringify_keys) tag 'link', tag_options @@ -59,35 +67,59 @@ module Sprockets end.join("\n").html_safe end + def asset_path(source, default_ext = nil, body = false, protocol = nil) + source = source.logical_path if source.respond_to?(:logical_path) + path = asset_paths.compute_public_path(source, 'assets', default_ext, true, protocol) + body ? "#{path}?body=1" : path + end + private def debug_assets? params[:debug_assets] == '1' || params[:debug_assets] == 'true' end - def asset_path(source, default_ext = nil, body = false) - source = source.logical_path if source.respond_to?(:logical_path) - path = asset_paths.compute_public_path(source, Rails.application.config.assets.prefix, default_ext, true) - body ? "#{path}?body=1" : path + # Override to specify an alternative prefix for asset path generation. + # When combined with a custom +asset_environment+, this can be used to + # implement themes that can take advantage of the asset pipeline. + # + # If you only want to change where the assets are mounted, refer to + # +config.assets.prefix+ instead. + def asset_prefix + Rails.application.config.assets.prefix end - class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: - def compute_public_path(source, dir, ext=nil, include_host=true) - super(source, Rails.application.config.assets.prefix, ext, include_host) + # Override to specify an alternative asset environment for asset + # path generation. The environment should already have been mounted + # at the prefix returned by +asset_prefix+. + def asset_environment + Rails.application.assets + end + + class AssetPaths < ::ActionView::AssetPaths #:nodoc: + attr_accessor :asset_environment, :asset_prefix + + def compute_public_path(source, dir, ext=nil, include_host=true, protocol=nil) + super(source, asset_prefix, ext, include_host, protocol) + end + + # Return the filesystem path for the source + def compute_source_path(source, ext) + asset_for(source, ext) end def asset_for(source, ext) source = source.to_s return nil if is_uri?(source) source = rewrite_extension(source, nil, ext) - assets[source] + asset_environment[source] end def rewrite_asset_path(source, dir) if source[0] == ?/ source else - assets.path(source, performing_caching?, dir) + asset_environment.path(source, performing_caching?, dir) end end @@ -99,13 +131,9 @@ module Sprockets end end - def assets - Rails.application.assets - end - # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available def performing_caching? - @config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching + config.action_controller.present? ? config.action_controller.perform_caching : config.perform_caching end end end diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 38eb00ce01..2020f8f095 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -1,16 +1,14 @@ module Sprockets autoload :Helpers, "sprockets/helpers" + autoload :LazyCompressor, "sprockets/compressors" + autoload :NullCompressor, "sprockets/compressors" + # TODO: Get rid of config.assets.enabled class Railtie < ::Rails::Railtie - def self.using_coffee? - require 'coffee-script' - defined?(CoffeeScript) - rescue LoadError - false + rake_tasks do + load "sprockets/assets.rake" end - config.app_generators.javascript_engine :coffee if using_coffee? - # Configure ActionController to use sprockets. initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app| ActiveSupport.on_load(:action_controller) do @@ -63,11 +61,15 @@ module Sprockets env.logger = Rails.logger + if env.respond_to?(:cache) + env.cache = assets.cache_store || Rails.cache + end + if assets.compress # temporarily hardcode default JS compressor to uglify. Soon, it will work # the same as SCSS, where a default plugin sets the default. - env.js_compressor = expand_js_compressor(assets.js_compressor || :uglifier) - env.css_compressor = expand_css_compressor(assets.css_compressor) + env.js_compressor = LazyCompressor.new { expand_js_compressor(assets.js_compressor || :uglifier) } + env.css_compressor = LazyCompressor.new { expand_css_compressor(assets.css_compressor) } end env diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index f9e47d5118..20d11377f6 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -87,6 +87,14 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_nil + with_test_routes do + assert_raise ArgumentError, "Nil location provided. Can't build URI." do + polymorphic_url(nil) + end + end + end + def test_with_record with_test_routes do @project.save diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 9e44e8e088..d5e3da4d88 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -530,6 +530,11 @@ class FilterTest < ActionController::TestCase assert sweeper.before(TestController.new) end + def test_after_method_of_sweeper_should_always_return_nil + sweeper = ActionController::Caching::Sweeper.send(:new) + assert_nil sweeper.after(TestController.new) + end + def test_non_yielding_around_filters_not_returning_false_do_not_raise controller = NonYieldingAroundFilterController.new controller.instance_variable_set "@filter_return_value", true diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 26270571cf..afb2d39955 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -509,7 +509,7 @@ end class RespondWithController < ActionController::Base respond_to :html, :json respond_to :xml, :except => :using_resource_with_block - respond_to :js, :only => [ :using_resource_with_block, :using_resource ] + respond_to :js, :only => [ :using_resource_with_block, :using_resource, :using_hash_resource ] def using_resource respond_with(resource) @@ -575,11 +575,6 @@ protected def resource Customer.new("david", request.delete? ? nil : 13) end - - def _render_js(js, options) - self.content_type ||= Mime::JS - self.response_body = js.respond_to?(:to_js) ? js.to_js : js - end end class InheritedRespondWithController < RespondWithController @@ -638,6 +633,20 @@ class RespondWithControllerTest < ActionController::TestCase end end + def test_using_resource_with_js_simply_tries_to_render_the_template + @request.accept = "text/javascript" + get :using_resource + assert_equal "text/javascript", @response.content_type + assert_equal "alert(\"Hi\");", @response.body + end + + def test_using_hash_resource_with_js_raises_an_error_if_template_cant_be_found + @request.accept = "text/javascript" + assert_raise ActionView::MissingTemplate do + get :using_hash_resource + end + end + def test_using_hash_resource @request.accept = "application/xml" get :using_hash_resource @@ -650,6 +659,13 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body end + def test_using_hash_resource_with_post + @request.accept = "application/json" + assert_raise ArgumentError, "Nil location provided. Can't build URI." do + post :using_hash_resource + end + end + def test_using_resource_with_block @request.accept = "*/*" get :using_resource_with_block diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 48cf0ab9cb..1a17e24914 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -10,9 +10,9 @@ module RenderStreaming )] layout "application" - stream :only => [:hello_world, :skip] def hello_world + render :stream => true end def layout_exception diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index c7c8360ae6..8f885ff28e 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -138,6 +138,25 @@ class SendFileTest < ActionController::TestCase @controller.headers = {} assert_raise(ArgumentError){ @controller.send(:send_file_headers!, options) } end + + def test_send_file_headers_guess_type_from_extension + { + 'image.png' => 'image/png', + 'image.jpeg' => 'image/jpeg', + 'image.jpg' => 'image/jpeg', + 'image.tif' => 'image/tiff', + 'image.gif' => 'image/gif', + 'movie.mpg' => 'video/mpeg', + 'file.zip' => 'application/zip', + 'file.unk' => 'application/octet-stream', + 'zip' => 'application/octet-stream' + }.each do |filename,expected_type| + options = { :filename => filename } + @controller.headers = {} + @controller.send(:send_file_headers!, options) + assert_equal expected_type, @controller.content_type + end + end %w(file data).each do |method| define_method "test_send_#{method}_status" do diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 6265e78030..043d44500a 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -649,6 +649,13 @@ XML end + def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case + TestTest.stubs(:fixture_path).returns(FILES_DIR) + + uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png') + assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + end + def test_test_uploaded_file_with_binary filename = 'mona_lisa.jpg' path = "#{FILES_DIR}/#{filename}" diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 8e5fd97cc6..fb67ecb07d 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -148,6 +148,15 @@ class CookiesTest < ActionController::TestCase @request.host = "www.nextangle.com" end + def test_key_methods + assert !request.cookie_jar.key?(:foo) + assert !request.cookie_jar.has_key?("foo") + + request.cookie_jar[:foo] = :bar + assert request.cookie_jar.key?(:foo) + assert request.cookie_jar.has_key?("foo") + end + def test_setting_cookie get :authenticate assert_cookie_header "user_name=david; path=/" diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 11cf68fdb3..851fb59d51 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -52,7 +52,7 @@ class MimeTypeTest < ActiveSupport::TestCase test "parse application with trailing star" do accept = "application/*" - expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF] + expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF, Mime::ZIP] parsed = Mime::Type.parse(accept) assert_equal expect, parsed end @@ -86,12 +86,12 @@ class MimeTypeTest < ActiveSupport::TestCase test "custom type" do begin - Mime::Type.register("image/gif", :gif) + Mime::Type.register("image/foo", :foo) assert_nothing_raised do - assert_equal Mime::GIF, Mime::SET.last + assert_equal Mime::FOO, Mime::SET.last end ensure - Mime::Type.unregister(:gif) + Mime::Type.unregister(:FOO) end end diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.erb b/actionpack/test/fixtures/respond_with/using_resource.js.erb new file mode 100644 index 0000000000..4417680bce --- /dev/null +++ b/actionpack/test/fixtures/respond_with/using_resource.js.erb @@ -0,0 +1 @@ +alert("Hi");
\ No newline at end of file diff --git a/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css new file mode 100644 index 0000000000..bfb90bfa48 --- /dev/null +++ b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css @@ -0,0 +1 @@ +/* Different from other style.css */
\ No newline at end of file diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 67baf369e2..cbef74f992 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -170,6 +170,17 @@ class Author < Comment def post_attributes=(attributes); end end +class HashBackedAuthor < Hash + extend ActiveModel::Naming + include ActiveModel::Conversion + + def persisted?; false; end + + def name + "hash backed author" + end +end + module Blog def self._railtie self diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 2abc806e97..df61901b44 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -424,6 +424,14 @@ class AssetTagHelperTest < ActionView::TestCase PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_image_alt + [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| + assert_equal 'Rails', image_alt("#{prefix}rails.png") + assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") + assert_equal 'Avatar-0000', image_alt("#{prefix}avatar-0000.png") + end + end + def test_image_tag ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -854,7 +862,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'a0.example.com' config.perform_caching = true assert_dom_equal( @@ -964,7 +972,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } + @controller.config.asset_host = Proc.new { |source| "a#{source.length}.example.com" } config.perform_caching = true assert_equal '/stylesheets/styles.css'.length, 23 @@ -1091,13 +1099,23 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase end def test_should_compute_proper_path_with_asset_host - @controller.config.asset_host = "http://assets.example.com" + @controller.config.asset_host = "assets.example.com" assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) - assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) - assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) - assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) - assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) - assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) + assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) + assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) + end + + def test_should_compute_proper_path_with_asset_host_and_default_protocol + @controller.config.asset_host = "assets.example.com" + @controller.config.default_asset_host_protocol = :request + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) + assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) + assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) end def test_should_ignore_asset_host_on_complete_url @@ -1115,12 +1133,12 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png')) end - def test_asset_host_without_protocol_should_use_request_protocol + def test_asset_host_without_protocol_should_be_protocol_relative @controller.config.asset_host = 'a.example.com' assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png') end - def test_asset_host_without_protocol_should_use_request_protocol_even_if_path_present + def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present @controller.config.asset_host = 'a.example.com/files/go/here' assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png') end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 0507045ad2..cc3d2cddf7 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -974,22 +974,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_index_and_parent_fields - form_for(@post, :index => 1) do |c| - concat c.text_field(:title) - concat c.fields_for_with_index('comment', @comment, :index => 1) { |r, comment_index| - concat r.text_field(:name, "data-index" => comment_index) - } - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" + - "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' data-index='1' />" - end - - assert_dom_equal expected, output_buffer - end - def test_form_for_with_index_and_nested_fields_for output_buffer = form_for(@post, :index => 1) do |f| concat f.fields_for(:comment, @post) { |c| @@ -1046,20 +1030,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_index_radio_button - form_for(@post) do |f| - concat f.fields_for_with_index(:comment, @post, :index => 5) { |c, index| - concat c.radio_button(:title, "hello", "data-index" => index) - } - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' data-index='5' />" - end - - assert_dom_equal expected, output_buffer - end - def test_nested_fields_for_with_auto_index_on_both form_for(@post, :as => "post[]") do |f| concat f.fields_for("comment[]", @post) { |c| @@ -1259,29 +1229,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_existing_records_on_a_nested_attributes_collection_association - @post.comments = Array.new(2) { |id| Comment.new(id + 1) } - - form_for(@post) do |f| - concat f.text_field(:title) - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment) { |cf, index| - concat cf.text_field(:name, "data-index" => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" data-index="0" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" data-index="1" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' - end - - assert_dom_equal expected, output_buffer - end - def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id @post.comments = Array.new(2) { |id| Comment.new(id + 1) } @post.author = Author.new(321) @@ -1309,33 +1256,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id - @post.comments = Array.new(2) { |id| Comment.new(id + 1) } - @post.author = Author.new(321) - - form_for(@post) do |f| - concat f.text_field(:title) - concat f.fields_for(:author) { |af| - concat af.text_field(:name) - } - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment, :include_id => false) { |cf, index| - concat cf.text_field(:name, 'data-index' => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" data-index="0" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" data-index="1" />' - end - - assert_dom_equal expected, output_buffer - end - def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id_inherited @post.comments = Array.new(2) { |id| Comment.new(id + 1) } @post.author = Author.new(321) @@ -1457,28 +1377,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_new_records_on_a_nested_attributes_collection_association - @post.comments = [Comment.new, Comment.new] - - form_for(@post) do |f| - concat f.text_field(:title) - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment) { |cf, index| - concat cf.text_field(:name, "data-index" => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" data-index="0" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" data-index="1" />' - end - - assert_dom_equal expected, output_buffer - end - - def test_nested_fields_for_with_existing_and_new_records_on_a_nested_attributes_collection_association @post.comments = [Comment.new(321), Comment.new] @@ -1501,29 +1399,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index_with_existing_and_new_records_on_a_nested_attributes_collection_association - @post.comments = [Comment.new(321), Comment.new] - - form_for(@post) do |f| - concat f.text_field(:title) - @post.comments.each do |comment| - concat f.fields_for_with_index(:comments, comment) { |cf, index| - concat cf.text_field(:name, "data-index" => index) - } - end - end - - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" data-index="0" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" data-index="1" />' - end - - assert_dom_equal expected, output_buffer - end - - def test_nested_fields_for_with_an_empty_supplied_attributes_collection form_for(@post) do |f| concat f.text_field(:title) @@ -1689,6 +1564,22 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_for_with_hash_like_model + @author = HashBackedAuthor.new + + form_for(@post) do |f| + concat f.fields_for(:author, @author) { |af| + concat af.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="hash backed author" />' + end + + assert_dom_equal expected, output_buffer + end + def test_fields_for output_buffer = fields_for(:post, @post) do |f| concat f.text_field(:title) diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index f3969895ae..a4599a3f00 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -378,6 +378,13 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_without_multiple + assert_dom_equal( + "<select id=\"post_category\" name=\"post[category]\"></select>", + select(:post, :category, "", {}, :multiple => false) + ) + end + def test_select_with_boolean_method @post = Post.new @post.allow_comments = false diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index b1317d0a35..f11d1bba15 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -31,18 +31,21 @@ class SprocketsHelperTest < ActionView::TestCase Rails.stubs(:application).returns(application) application.stubs(:config).returns(config) application.stubs(:assets).returns(@assets) - - config.perform_caching = true + @config = config + @config.action_controller ||= ActiveSupport::InheritableOptions.new + @config.perform_caching = true end def url_for(*args) "http://www.example.com" end - test "asset path" do + test "asset_path" do assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png", asset_path("logo.png") + end + test "asset_path with root relative assets" do assert_equal "/images/logo", asset_path("/images/logo") assert_equal "/images/logo.gif", @@ -50,13 +53,73 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "/dir/audio", asset_path("/dir/audio") - + end + + test "asset_path with absolute urls" do assert_equal "http://www.example.com/video/play", asset_path("http://www.example.com/video/play") assert_equal "http://www.example.com/video/play.mp4", asset_path("http://www.example.com/video/play.mp4") end + test "with a simple asset host the url should default to protocol relative" do + @controller.config.asset_host = "assets-%d.example.com" + assert_match %r{//assets-\d.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "with a simple asset host the url can be changed to use the request protocol" do + @controller.config.asset_host = "assets-%d.example.com" + @controller.config.default_asset_host_protocol = :request + assert_match %r{http://assets-\d.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "With a proc asset host that returns no protocol the url should be protocol relative" do + @controller.config.asset_host = Proc.new do |asset| + "assets-999.example.com" + end + assert_match %r{//assets-999.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "with a proc asset host that returns a protocol the url use it" do + @controller.config.asset_host = Proc.new do |asset| + "http://assets-999.example.com" + end + assert_match %r{http://assets-999.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "stylesheets served with a controller in scope can access the request" do + config.asset_host = Proc.new do |asset, request| + assert_not_nil request + "http://assets-666.example.com" + end + assert_match %r{http://assets-666.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "stylesheets served without a controller in scope cannot access the request" do + remove_instance_variable("@controller") + @config.action_controller.asset_host = Proc.new do |asset, request| + fail "This should not have been called." + end + assert_raises ActionController::RoutingError do + asset_path("logo.png") + end + end + + test "stylesheets served without a controller in do not use asset hosts when the default protocol is :request" do + remove_instance_variable("@controller") + @config.action_controller.asset_host = "assets-%d.example.com" + @config.action_controller.default_asset_host_protocol = :request + @config.action_controller.perform_caching = true + + assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png", + asset_path("logo.png") + end + test "asset path with relative url root" do @controller.config.relative_url_root = "/collaboration/hieraki" assert_equal "/collaboration/hieraki/images/logo.gif", @@ -134,4 +197,16 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "<link href=\"/assets/style-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/assets/extra-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />", stylesheet_link_tag("style", "extra") end + + test "alternate asset prefix" do + stubs(:asset_prefix).returns("/themes/test") + assert_equal "/themes/test/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css") + end + + test "alternate asset environment" do + assets = Sprockets::Environment.new + assets.paths << FIXTURES.join("sprockets/alternate/stylesheets") + stubs(:asset_environment).returns(assets) + assert_equal "/assets/style-df0b97ad35a8e1f7f61097461f77c19a.css", asset_path("style", "css") + end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 5a43b5f864..f7c3986bb1 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -36,8 +36,8 @@ class TextHelperTest < ActionView::TestCase text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text) - assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') - assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') + assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') + assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') end def test_simple_format_should_sanitize_input_when_sanitize_option_is_not_false @@ -48,6 +48,13 @@ class TextHelperTest < ActionView::TestCase assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false) end + def test_simple_format_should_not_change_the_frozen_text_passed + text = "<b>Ok</b><script>code!</script>" + text_clone = text.dup + simple_format(text.freeze) + assert_equal text_clone, text + end + def test_truncate_should_not_be_html_safe assert !truncate("Hello World!", :length => 12).html_safe? end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 1e54f0174c..a70c02a429 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -245,6 +245,13 @@ class UrlHelperTest < ActiveSupport::TestCase ) end + def test_link_tag_using_post_javascript_and_rel + assert_dom_equal( + "<a href='http://www.example.com' data-method=\"post\" rel=\"example nofollow\">Hello</a>", + link_to("Hello", "http://www.example.com", :method => :post, :rel => 'example') + ) + end + def test_link_tag_using_post_javascript_and_confirm assert_dom_equal( "<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>", diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 3b412d3dd7..166cccf161 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -156,7 +156,7 @@ module ActiveModel rescue TypeError, NoMethodError end - changed_attributes[attr] = value + changed_attributes[attr] = value unless changed_attributes.include?(attr) end # Handle <tt>reset_*!</tt> for +method_missing+. diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index a7b4706906..3f9feb7631 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/array/wrap' require 'active_model/mass_assignment_security/permission_set' require 'active_model/mass_assignment_security/sanitizer' @@ -110,8 +111,11 @@ module ActiveModel options = args.extract_options! role = options[:as] || :default - self._protected_attributes = protected_attributes_configs.dup - self._protected_attributes[role] = self.protected_attributes(role) + args + self._protected_attributes = protected_attributes_configs.dup + + Array.wrap(role).each do |name| + self._protected_attributes[name] = self.protected_attributes(name) + args + end self._active_authorizer = self._protected_attributes end @@ -169,8 +173,11 @@ module ActiveModel options = args.extract_options! role = options[:as] || :default - self._accessible_attributes = accessible_attributes_configs.dup - self._accessible_attributes[role] = self.accessible_attributes(role) + args + self._accessible_attributes = accessible_attributes_configs.dup + + Array.wrap(role).each do |name| + self._accessible_attributes[name] = self.accessible_attributes(name) + args + end self._active_authorizer = self._accessible_attributes end diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 0405705b35..4fbccd7419 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -32,8 +32,15 @@ module ActiveModel # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} # - # The remainder of the examples in this section assume +include_root_in_json+ - # is false. + # This behavior can also be achieved by setting the <tt>:root</tt> option to +false+ as in: + # + # user = User.find(1) + # user.as_json(root: false) + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true} + # + # The remainder of the examples in this section assume include_root_in_json is set to + # <tt>false</tt>. # # Without any +options+, the returned JSON string will include all the model's # attributes. For example: @@ -83,7 +90,12 @@ module ActiveModel def as_json(options = nil) hash = serializable_hash(options) - if include_root_in_json + include_root = include_root_in_json + if options.try(:key?, :root) + include_root = options[:root] + end + + if include_root custom_root = options && options[:root] hash = { custom_root || self.class.model_name.element => hash } end @@ -91,9 +103,9 @@ module ActiveModel hash end - def from_json(json) + def from_json(json, include_root=include_root_in_json) hash = ActiveSupport::JSON.decode(json) - hash = hash.values.first if include_root_in_json + hash = hash.values.first if include_root self.attributes = hash self end diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index 09684ac4df..dbda55ca7c 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,9 +1,9 @@ module ActiveModel module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index 858ae9cb69..98244a6290 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -106,4 +106,13 @@ class DirtyTest < ActiveModel::TestCase assert_equal [nil, "Jericho Cane"], @model.previous_changes['name'] end + test "changing the same attribute multiple times retains the correct original value" do + @model.name = "Otto" + @model.save + @model.name = "DudeFella ManGuy" + @model.name = "Mr. Manfredgensonton" + assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change + assert_equal @model.name_was, "Otto" + end + end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index a778240827..be07e59a2f 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -43,6 +43,20 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end + def test_attributes_accessible_with_roles_given_as_array + user = Account.new + expected = { "name" => "John Smith", "email" => "john@smith.com" } + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) + assert_equal expected, sanitized + end + + def test_attributes_accessible_with_admin_role_when_roles_given_as_array + user = Account.new + expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } + sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) + assert_equal expected, sanitized + end + def test_attributes_protected_by_default firm = Firm.new expected = { } diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb index 500a5c575f..5e1e7d897a 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -8,6 +8,12 @@ class Contact include ActiveModel::Serializers::JSON include ActiveModel::Validations + def attributes=(hash) + hash.each do |k, v| + instance_variable_set("@#{k}", v) + end + end + def attributes instance_values end unless method_defined?(:attributes) @@ -34,7 +40,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"preferences":\{"shows":"anime"\}}, json end - test "should not include root in json" do + test "should not include root in json (class method)" do begin Contact.include_root_in_json = false json = @contact.to_json @@ -50,6 +56,13 @@ class JsonSerializationTest < ActiveModel::TestCase end end + test "should not include root in json (option)" do + + json = @contact.to_json(:root => false) + + assert_no_match %r{^\{"contact":\{}, json + end + test "should include custom root in json" do json = @contact.to_json(:root => 'json_contact') @@ -135,6 +148,44 @@ class JsonSerializationTest < ActiveModel::TestCase end end + test "from_json should set the object's attributes" do + json = @contact.to_json + result = Contact.new.from_json(json) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + end + + test "from_json should work without a root (method parameter)" do + json = @contact.to_json(:root => false) + result = Contact.new.from_json(json, false) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + end + + test "from_json should work without a root (class attribute)" do + begin + Contact.include_root_in_json = false + json = @contact.to_json + result = Contact.new.from_json(json) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + ensure + Contact.include_root_in_json = true + end + end + test "custom as_json should be honored when generating json" do def @contact.as_json(options); { :name => name, :created_at => created_at }; end json = @contact.to_json diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb index 53b37369ff..1d123fa58c 100644 --- a/activemodel/test/models/mass_assignment_specific.rb +++ b/activemodel/test/models/mass_assignment_specific.rb @@ -20,6 +20,14 @@ class Person public :sanitize_for_mass_assignment end +class Account + include ActiveModel::MassAssignmentSecurity + attr_accessible :name, :email, :as => [:default, :admin] + attr_accessible :admin, :as => :admin + + public :sanitize_for_mass_assignment +end + class Firm include ActiveModel::MassAssignmentSecurity @@ -65,4 +73,4 @@ end class TightDescendant < TightPerson attr_accessible :phone_number attr_accessible :super_powers, :as => :admin -end
\ No newline at end of file +end diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index ead2a244e5..4113a16c12 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,17 @@ *Rails 3.1.0 (unreleased)* +* ActiveRecord::MacroReflection::AssociationReflection#build_record has a new method signature. + + Before: def build_association(*options) + After: def build_association(*options, &block) + + Users who are redefining this method to extend functionality should ensure that the block is + passed through to ActiveRecord::Base#new. + + This change is necessary to fix https://github.com/rails/rails/issues/1842. + + [Jon Leighton] + * AR#pluralize_table_names can be used to singularize/pluralize table name of an individual model: class User < ActiveRecord::Base diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index b7e23faa09..8ac4d9a225 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -21,6 +21,6 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('arel', '~> 2.1.1') - s.add_dependency('tzinfo', '~> 0.3.27') + s.add_dependency('arel', '~> 2.1.3') + s.add_dependency('tzinfo', '~> 0.3.29') end diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index bd98cf2f46..92ed844a2e 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -9,7 +9,7 @@ module ActiveRecord # table_joins is an array of arel joins which might conflict with the aliases we assign here def initialize(table_joins = []) - @aliases = Hash.new + @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) } @table_joins = table_joins end @@ -26,8 +26,6 @@ module ActiveRecord def aliased_name_for(table_name, aliased_name = nil) aliased_name ||= table_name - initialize_count_for(table_name) if aliases[table_name].nil? - if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 @@ -36,8 +34,6 @@ module ActiveRecord # Otherwise, we need to use an alias aliased_name = connection.table_alias_for(aliased_name) - initialize_count_for(aliased_name) if aliases[aliased_name].nil? - # Update the count aliases[aliased_name] += 1 @@ -49,32 +45,24 @@ module ActiveRecord end end - def pluralize(table_name, base) - base.pluralize_table_names ? table_name.to_s.pluralize : table_name.to_s - end - private - def initialize_count_for(name) - aliases[name] = 0 - - unless Arel::Table === table_joins - # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase - quoted_name = connection.quote_table_name(name).downcase + def initial_count_for(name) + return 0 if Arel::Table === table_joins - aliases[name] += table_joins.map { |join| - # Table names + table aliases - join.left.downcase.scan( - /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ - ).size - }.sum - end + # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase + quoted_name = connection.quote_table_name(name).downcase - aliases[name] + table_joins.map { |join| + # Table names + table aliases + join.left.downcase.scan( + /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ + ).size + }.sum end def truncate(name) - name[0..connection.table_alias_length-3] + name.slice(0, connection.table_alias_length - 2) end def connection diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index c3aee90124..bb519c5703 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -30,7 +30,7 @@ module ActiveRecord @updated = false reset - construct_scope + reset_scope end # Returns the name of the table of the related class: @@ -51,7 +51,7 @@ module ActiveRecord # Reloads the \target and returns +self+ on success. def reload reset - construct_scope + reset_scope load_target self unless target.nil? end @@ -84,21 +84,25 @@ module ActiveRecord end def scoped - target_scope.merge(@association_scope) + target_scope.merge(association_scope) end - # Construct the scope for this association. + # The scope for this association. # # Note that the association_scope is merged into the target_scope only when the # scoped method is called. This is because at that point the call may be surrounded # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which # actually gets built. - def construct_scope + def association_scope if klass - @association_scope = AssociationScope.new(self).scope + @association_scope ||= AssociationScope.new(self).scope end end + def reset_scope + @association_scope = nil + end + # Set the inverse association, if possible def set_inverse_instance(record) if record && invertible_for?(record) @@ -224,6 +228,15 @@ module ActiveRecord def association_class @reflection.klass end + + def build_record(attributes, options) + reflection.build_association(attributes, options) do |record| + record.assign_attributes( + create_scope.except(*record.changed), + :without_protection => true + ) + end + end end end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 337a0d48f1..3e68f973e7 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -50,7 +50,7 @@ module ActiveRecord else column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" - scoped.select(column).except(:includes).map! do |record| + scoped.select(column).map! do |record| record.send(reflection.association_primary_key) end end @@ -78,10 +78,14 @@ module ActiveRecord end def find(*args) - if options[:finder_sql] - find_by_scan(*args) + if block_given? + load_target.find(*args) { |*block_args| yield(*block_args) } else - scoped.find(*args) + if options[:finder_sql] + find_by_scan(*args) + else + scoped.find(*args) + end end end @@ -423,12 +427,6 @@ module ActiveRecord scoped.scope_for_create.stringify_keys end - def build_record(attributes, options) - record = reflection.build_association(attributes, options) - record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) - record - end - def delete_or_destroy(records, method) records = records.flatten records.each { |record| raise_on_type_mismatch(record) } diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 5267116c42..7f7dec467a 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -58,10 +58,10 @@ module ActiveRecord alias_method :new, :build - def respond_to?(*args) + def respond_to?(name, include_private = false) super || - (load_target && target.respond_to?(*args)) || - @association.klass.respond_to?(*args) + (load_target && target.respond_to?(name, include_private)) || + @association.klass.respond_to?(name, include_private) end def method_missing(method, *args, &block) diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 6b8201973a..2131edbc20 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -19,7 +19,7 @@ module ActiveRecord if owner.persisted? && save && !record.save nullify_owner_attributes(record) - set_owner_attributes(target) + set_owner_attributes(target) if target raise RecordNotSaved, "Failed to save the new associated #{reflection.name}." end end diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb index 87e33891a5..f83138195c 100644 --- a/activerecord/lib/active_record/associations/join_helper.rb +++ b/activerecord/lib/active_record/associations/join_helper.rb @@ -32,8 +32,7 @@ module ActiveRecord end def table_alias_for(reflection, join = false) - name = alias_tracker.pluralize(reflection.name, reflection.active_record) - name << "_#{alias_suffix}" + name = "#{reflection.plural_name}_#{alias_suffix}" name << "_join" if join name end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 7256dd5288..779f8164cc 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -68,7 +68,8 @@ module ActiveRecord private def associated_records_by_owner - owner_keys = owners.map { |owner| owner[owner_key_name] }.compact.uniq + owners_map = owners_by_key + owner_keys = owners_map.keys.compact if klass.nil? || owner_keys.empty? records = [] @@ -84,7 +85,7 @@ module ActiveRecord records.each do |record| owner_key = record[association_key_name].to_s - owners_by_key[owner_key].each do |owner| + owners_map[owner_key].each do |owner| records_by_owner[owner] << record end end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index ce1f2a5543..6b010064d5 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -26,8 +26,7 @@ module ActiveRecord end def build(attributes = {}, options = {}) - record = reflection.build_association(attributes, options) - record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) + record = build_record(attributes, options) yield(record) if block_given? set_new_record(record) record diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 5833c65893..d0687ed0b6 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -61,7 +61,7 @@ module ActiveRecord end end - def respond_to?(*args) + def respond_to?(name, include_private = false) self.class.define_attribute_methods unless self.class.attribute_methods_generated? super end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 7d4182291a..9a50a20fbc 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -99,8 +99,9 @@ module ActiveRecord # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - if respond_to? "_#{attr_name}" - send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s) + method = "_#{attr_name}" + if respond_to? method + send method if @attributes.has_key?(attr_name.to_s) else _read_attribute attr_name end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 036d514a03..085fdba639 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -347,7 +347,7 @@ module ActiveRecord end # reconstruct the scope now that we know the owner's id - association.send(:construct_scope) if association.respond_to?(:construct_scope) + association.send(:reset_scope) if association.respond_to?(:reset_scope) end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 6a5d282973..eb4a16ecf5 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -709,6 +709,12 @@ module ActiveRecord #:nodoc: connection_pool.columns_hash[table_name] end + # Returns a hash where the keys are column names and the values are + # default values when instantiating the AR object for this table. + def column_defaults + connection_pool.column_defaults[table_name] + end + # Returns an array of column names as strings. def column_names @column_names ||= columns.map { |column| column.name } @@ -1052,7 +1058,7 @@ module ActiveRecord #:nodoc: if match.finder? options = arguments.extract_options! relation = options.any? ? scoped(options) : scoped - relation.send :find_by_attributes, match, attribute_names, *arguments + relation.send :find_by_attributes, match, attribute_names, *arguments, &block elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end @@ -1204,11 +1210,11 @@ MSG end def current_scope #:nodoc: - Thread.current[:"#{self}_current_scope"] + Thread.current["#{self}_current_scope"] end def current_scope=(scope) #:nodoc: - Thread.current[:"#{self}_current_scope"] = scope + Thread.current["#{self}_current_scope"] = scope end # Use this macro in your model to set a default scope for all operations on @@ -1527,6 +1533,7 @@ MSG @marked_for_destruction = false @previously_changed = {} @changed_attributes = {} + @relation = nil ensure_proper_type set_serialized_attributes @@ -1535,9 +1542,8 @@ MSG assign_attributes(attributes, options) if attributes - result = yield self if block_given? + yield self if block_given? run_callbacks :initialize - result end # Populate +coder+ with attributes about this record that should be @@ -1568,6 +1574,7 @@ MSG # post.title # => 'hello world' def init_with(coder) @attributes = coder['attributes'] + @relation = nil set_serialized_attributes @@ -1702,27 +1709,24 @@ MSG return unless new_attributes attributes = new_attributes.stringify_keys - role = options[:as] || :default - multi_parameter_attributes = [] + @mass_assignment_options = options unless options[:without_protection] - attributes = sanitize_for_mass_assignment(attributes, role) + attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role) end attributes.each do |k, v| if k.include?("(") multi_parameter_attributes << [ k, v ] + elsif respond_to?("#{k}=") + send("#{k}=", v) else - method_name = "#{k}=" - if respond_to?(method_name) - method(method_name).arity == -2 ? send(method_name, v, options) : send(method_name, v) - else - raise(UnknownAttributeError, "unknown attribute: #{k}") - end + raise(UnknownAttributeError, "unknown attribute: #{k}") end end + @mass_assignment_options = nil assign_multiparameter_attributes(multi_parameter_attributes) end @@ -1777,16 +1781,12 @@ MSG # Note also that destroying a record preserves its ID in the model instance, so deleted # models are still comparable. def ==(comparison_object) - comparison_object.equal?(self) || + super || comparison_object.instance_of?(self.class) && id.present? && comparison_object.id == id end - - # Delegates to == - def eql?(comparison_object) - self == comparison_object - end + alias :eql? :== # Delegates to id in order to allow two records of the same type and id to work with something like: # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] @@ -1804,6 +1804,15 @@ MSG @attributes.frozen? end + # Allows sort on objects + def <=>(other_object) + if other_object.is_a?(self.class) + self.to_key <=> other_object.to_key + else + nil + end + end + # Backport dup from 1.9 so that initialize_dup() gets called unless Object.respond_to?(:initialize_dup) def dup # :nodoc: @@ -1882,12 +1891,33 @@ MSG value end + def mass_assignment_options + @mass_assignment_options ||= {} + end + + def mass_assignment_role + mass_assignment_options[:as] || :default + end + private + # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements + # of the array, and then rescues from the possible NoMethodError. If those elements are + # ActiveRecord::Base's, then this triggers the various method_missing's that we have, + # which significantly impacts upon performance. + # + # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. + # + # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ + def to_ary # :nodoc: + nil + end + def set_serialized_attributes - (@attributes.keys & self.class.serialized_attributes.keys).each do |key| - coder = self.class.serialized_attributes[key] - @attributes[key] = coder.load @attributes[key] + sattrs = self.class.serialized_attributes + + sattrs.each do |key, coder| + @attributes[key] = coder.load @attributes[key] if @attributes.key?(key) end end @@ -1897,8 +1927,9 @@ MSG # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. # No such attribute would be set for objects of the Message class in that example. def ensure_proper_type - unless self.class.descends_from_active_record? - write_attribute(self.class.inheritance_column, self.class.sti_name) + klass = self.class + if klass.finder_needs_type_condition? + write_attribute(klass.inheritance_column, klass.sti_name) end end @@ -2072,8 +2103,10 @@ MSG end def populate_with_current_scope_attributes - self.class.scoped.scope_for_create.each do |att,value| - respond_to?("#{att}=") && send("#{att}=", value) + return unless self.class.scope_attributes? + + self.class.scope_attributes.each do |att,value| + send("#{att}=", value) if respond_to?("#{att}=") end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index dd1d2d4fba..ddfdb05297 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -60,6 +60,7 @@ module ActiveRecord attr_accessor :automatic_reconnect attr_reader :spec, :connections attr_reader :columns, :columns_hash, :primary_keys, :tables + attr_reader :column_defaults # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, @@ -106,6 +107,12 @@ module ActiveRecord }] end + @column_defaults = Hash.new do |h, table_name| + h[table_name] = Hash[columns[table_name].map { |col| + [col.name, col.default] + }] + end + @primary_keys = Hash.new do |h, table_name| h[table_name] = with_connection do |conn| table_exists?(table_name) ? conn.primary_key(table_name) : 'id' @@ -133,6 +140,7 @@ module ActiveRecord def clear_cache! @columns.clear @columns_hash.clear + @column_defaults.clear @tables.clear end @@ -140,6 +148,7 @@ module ActiveRecord def clear_table_cache!(table_name) @columns.delete table_name @columns_hash.delete table_name + @column_defaults.delete table_name @primary_keys.delete table_name end @@ -426,6 +435,14 @@ module ActiveRecord @testing = testing end + def method_missing(method_sym, *arguments, &block) + @body.send(method_sym, *arguments, &block) + end + + def respond_to?(method_sym, include_private = false) + super || @body.respond_to?(method_sym) + end + def each(&block) body.each(&block) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index a9e3c83eb0..82f564e41d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -328,7 +328,7 @@ module ActiveRecord end # Checks to see if a column exists. See SchemaStatements#column_exists? - def column_exists?(column_name, type = nil, options = nil) + def column_exists?(column_name, type = nil, options = {}) @base.column_exists?(@table_name, column_name, type, options) end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 3eddb69e73..a7856539b7 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -1,3 +1,5 @@ +require 'set' + module ActiveRecord # :stopdoc: module ConnectionAdapters diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 1a24ed1375..29dd0a9ea3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -466,10 +466,11 @@ module ActiveRecord # Executes an INSERT query and returns the new record's ID def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - # Extract the table from the insert sql. Yuck. - _, table = extract_schema_and_table(sql.split(" ", 4)[2]) - - pk ||= primary_key(table) + unless pk + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref + end if pk select_value("#{sql} RETURNING #{quote_column_name(pk)}") @@ -565,9 +566,9 @@ module ActiveRecord def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk - _, table = extract_schema_and_table(sql.split(" ", 4)[2]) - - pk = primary_key(table) + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref end sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk @@ -665,34 +666,33 @@ module ActiveRecord SQL end + # Returns true if table exists. + # If the schema is not specified as part of +name+ then it will only find tables within + # the current schema search path (regardless of permissions to access tables in other schemas) def table_exists?(name) schema, table = extract_schema_and_table(name.to_s) + return false unless table - binds = [[nil, table.gsub(/(^"|"$)/,'')]] + binds = [[nil, table]] binds << [nil, schema] if schema exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 - SELECT COUNT(*) - FROM pg_tables - WHERE tablename = $1 - #{schema ? "AND schemaname = $2" : ''} + SELECT COUNT(*) + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind in ('v','r') + AND c.relname = $1 + AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} SQL end - # Extracts the table and schema name from +name+ - def extract_schema_and_table(name) - schema, table = name.split('.', 2) - - unless table # A table was provided without a schema - table = schema - schema = nil - end - - if name =~ /^"/ # Handle quoted table names - table = name - schema = nil - end - [schema, table] + # Returns true if schema exists. + def schema_exists?(name) + exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0 + SELECT COUNT(*) + FROM pg_namespace + WHERE nspname = $1 + SQL end # Returns an array of indexes for the given table. @@ -742,6 +742,11 @@ module ActiveRecord query('select current_database()')[0][0] end + # Returns the current schema name. + def current_schema + query('SELECT current_schema', 'SCHEMA')[0][0] + end + # Returns the current database encoding format. def encoding query(<<-end_sql)[0][0] @@ -935,7 +940,7 @@ module ActiveRecord # Construct a clean list of column names from the ORDER BY clause, removing # any ASC/DESC modifiers - order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s } + order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') } order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } @@ -960,27 +965,27 @@ module ActiveRecord end private - def exec_no_cache(sql, binds) - @connection.async_exec(sql) - end - - def exec_cache(sql, binds) - unless @statements.key? sql - nextkey = "a#{@statements.length + 1}" - @connection.prepare nextkey, sql - @statements[sql] = nextkey + def exec_no_cache(sql, binds) + @connection.async_exec(sql) end - key = @statements[sql] + def exec_cache(sql, binds) + unless @statements.key? sql + nextkey = "a#{@statements.length + 1}" + @connection.prepare nextkey, sql + @statements[sql] = nextkey + end - # Clear the queue - @connection.get_last_result - @connection.send_query_prepared(key, binds.map { |col, val| - type_cast(val, col) - }) - @connection.block - @connection.get_last_result - end + key = @statements[sql] + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result + end # The internal PostgreSQL identifier of the money data type. MONEY_COLUMN_TYPE_OID = 790 #:nodoc: @@ -1080,9 +1085,29 @@ module ActiveRecord end end - def table_definition - TableDefinition.new(self) - end + # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. + # +schema_name+ is nil if not specified in +name+. + # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) + # +name+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * <tt>table_name</tt> + # * <tt>"table.name"</tt> + # * <tt>schema_name.table_name</tt> + # * <tt>schema_name."table.name"</tt> + # * <tt>"schema.name"."table name"</tt> + def extract_schema_and_table(name) + table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse + [schema, table] + end + + def extract_table_ref_from_insert_sql(sql) + sql[/into\s+([^\(]*).*values\s*\(/i] + $1.strip if $1 + end + + def table_definition + TableDefinition.new(self) + end end end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 3afa257a76..6cfce6e573 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -73,7 +73,7 @@ module ActiveRecord # <tt>locking_enabled?</tt> at this point as # <tt>@attributes</tt> may not have been initialized yet. - if lock_optimistically && result.include?(self.class.locking_column) + if result.key?(self.class.locking_column) && lock_optimistically result[self.class.locking_column] ||= 0 end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 3d1bc5c1e0..c459846264 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -66,9 +66,9 @@ module ActiveRecord # create_table :system_settings do |t| # t.string :name # t.string :label - # t.text :value + # t.text :value # t.string :type - # t.integer :position + # t.integer :position # end # # SystemSetting.create :name => "notice", @@ -181,7 +181,7 @@ module ActiveRecord # # class RemoveEmptyTags < ActiveRecord::Migration # def up - # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? } + # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } # end # # def down @@ -227,7 +227,7 @@ module ActiveRecord # def up # add_column :people, :salary, :integer # Person.reset_column_information - # Person.find(:all).each do |p| + # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end @@ -247,7 +247,7 @@ module ActiveRecord # def up # ... # say_with_time "Updating salaries..." do - # Person.find(:all).each do |p| + # Person.all.each do |p| # p.update_attribute :salary, SalaryCalculator.compute(p) # end # end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index f01e94169f..2eeff7e36f 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -6,7 +6,7 @@ module ActiveRecord # # * add_column # * add_index - # * add_timestamp + # * add_timestamps # * create_table # * remove_timestamps # * rename_column @@ -48,7 +48,7 @@ module ActiveRecord super || delegate.respond_to?(*args) end - [:create_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| + [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method}(*args) # def create_table(*args) record(:"#{method}", args) # record(:create_table, args) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 14db7a6cd6..0313abe456 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -40,6 +40,25 @@ module ActiveRecord end end + ## + # Collects attributes from scopes that should be applied when creating + # an AR instance for the particular class this is called on. + def scope_attributes # :nodoc: + if current_scope + current_scope.scope_for_create + else + scope = relation.clone + scope.default_scoped = true + scope.scope_for_create + end + end + + ## + # Are there default attributes associated with this scope? + def scope_attributes? # :nodoc: + current_scope || default_scopes.any? + end + # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query, # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>. # diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index f51fd21077..53617059d0 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -276,15 +276,15 @@ module ActiveRecord type = (reflection.collection? ? :collection : :one_to_one) - # def pirate_attributes=(attributes, assignment_opts = {}) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, assignment_opts) + # def pirate_attributes=(attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options) # end class_eval <<-eoruby, __FILE__, __LINE__ + 1 if method_defined?(:#{association_name}_attributes=) remove_method(:#{association_name}_attributes=) end - def #{association_name}_attributes=(attributes, assignment_opts = {}) - assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, assignment_opts) + def #{association_name}_attributes=(attributes) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options) end eoruby else diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 367a1be4d9..f425118f59 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -319,9 +319,7 @@ module ActiveRecord # that a new instance, or one populated from a passed-in Hash, still has all the attributes # that instances loaded from the database would. def attributes_from_column_definition - Hash[self.class.columns.map do |column| - [column.name, column.default] - end] + self.class.column_defaults.dup end end end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 4e61671473..e485901440 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -33,6 +33,14 @@ module ActiveRecord @target = target end + def method_missing(method_sym, *arguments, &block) + @target.send(method_sym, *arguments, &block) + end + + def respond_to?(method_sym, include_private = false) + super || @target.respond_to?(method_sym) + end + def each(&block) @target.each(&block) end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6ef24a4eaf..44e5520230 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -44,6 +44,12 @@ db_namespace = namespace :db do create_database(ActiveRecord::Base.configurations[Rails.env]) end + def mysql_creation_options(config) + @charset = ENV['CHARSET'] || 'utf8' + @collation = ENV['COLLATION'] || 'utf8_unicode_ci' + {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} + end + def create_database(config) begin if config['adapter'] =~ /sqlite/ @@ -67,9 +73,6 @@ db_namespace = namespace :db do rescue case config['adapter'] when /mysql/ - @charset = ENV['CHARSET'] || 'utf8' - @collation = ENV['COLLATION'] || 'utf8_unicode_ci' - creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} if config['adapter'] =~ /jdbc/ #FIXME After Jdbcmysql gives this class require 'active_record/railties/jdbcmysql_error' @@ -80,7 +83,7 @@ db_namespace = namespace :db do access_denied_error = 1045 begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) - ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config)) ActiveRecord::Base.establish_connection(config) rescue error_class => sqlerr if sqlerr.errno == access_denied_error @@ -369,7 +372,7 @@ db_namespace = namespace :db do ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password'] search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? - search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") + search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ") end `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}` raise 'Error dumping database' if $?.exitstatus == 1 @@ -377,8 +380,7 @@ db_namespace = namespace :db do dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile'] `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql` when 'sqlserver' - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r` - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r` + `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\#{Rails.env}_structure.sql -A -U` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) @@ -423,7 +425,7 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` when 'sqlserver' - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql` when 'oci', 'oracle' ActiveRecord::Base.establish_connection(:test) IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl| @@ -444,7 +446,7 @@ db_namespace = namespace :db do case abcs['test']['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], abcs['test']) + ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test'])) when /postgresql/ ActiveRecord::Base.clear_active_connections! drop_database(abcs['test']) @@ -453,9 +455,11 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] File.delete(dbfile) if File.exist?(dbfile) when 'sqlserver' - dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}` - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + test = abcs.deep_dup['test'] + test_database = test['database'] + test['database'] = 'master' + ActiveRecord::Base.establish_connection(test) + ActiveRecord::Base.connection.recreate_database!(test_database) when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 6df473d011..a2324039cf 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -80,12 +80,6 @@ module ActiveRecord # Abstract base class for AggregateReflection and AssociationReflection. Objects of # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. class MacroReflection - attr_reader :active_record - - def initialize(macro, name, options, active_record) - @macro, @name, @options, @active_record = macro, name, options, active_record - end - # Returns the name of the macro. # # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt> @@ -104,6 +98,19 @@ module ActiveRecord # <tt>has_many :clients</tt> returns +{}+ attr_reader :options + attr_reader :active_record + + attr_reader :plural_name # :nodoc: + + def initialize(macro, name, options, active_record) + @macro = macro + @name = name + @options = options + @active_record = active_record + @plural_name = active_record.pluralize_table_names ? + name.to_s.pluralize : name.to_s + end + # Returns the class for the macro. # # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class @@ -123,7 +130,11 @@ module ActiveRecord # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, # and +other_aggregation+ has an options hash assigned to it. def ==(other_aggregation) - other_aggregation.kind_of?(self.class) && name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record + super || + other_aggregation.kind_of?(self.class) && + name == other_aggregation.name && + other_aggregation.options && + active_record == other_aggregation.active_record end def sanitized_conditions #:nodoc: @@ -168,25 +179,8 @@ module ActiveRecord # Returns a new, unsaved instance of the associated class. +options+ will # be passed to the class's constructor. - def build_association(*options) - klass.new(*options) - end - - # Creates a new instance of the associated class, and immediately saves it - # with ActiveRecord::Base#save. +options+ will be passed to the class's - # creation method. Returns the newly created object. - def create_association(*options) - klass.create(*options) - end - - # Creates a new instance of the associated class, and immediately saves it - # with ActiveRecord::Base#save!. +options+ will be passed to the class's - # creation method. If the created record doesn't pass validations, then an - # exception will be raised. - # - # Returns the newly created object. - def create_association!(*options) - klass.create!(*options) + def build_association(*options, &block) + klass.new(*options, &block) end def table_name diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 317af8a15d..aa7f7d6cea 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/module/delegation' module ActiveRecord # = Active Record Relation @@ -6,7 +7,7 @@ module ActiveRecord JoinOperation = Struct.new(:relation, :join_class, :on) ASSOCIATION_METHODS = [:includes, :eager_load, :preload] MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] - SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder, :reverse_order] + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order] include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches @@ -29,6 +30,7 @@ module ActiveRecord SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)} (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])} @extensions = [] + @create_with_value = {} end def insert(values) @@ -403,7 +405,7 @@ module ActiveRecord end def scope_for_create - @scope_for_create ||= where_values_hash.merge(@create_with_value || {}) + @scope_for_create ||= where_values_hash.merge(create_with_value) end def eager_loading? diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index d52b84179f..46ab67d1cf 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -20,8 +20,6 @@ module ActiveRecord find_in_batches(options) do |records| records.each { |record| yield record } end - - self end # Yields each batch of records that was found by the find +options+ as diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index aabe5c269b..0ac821b2d7 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -89,8 +89,12 @@ module ActiveRecord # +calculate+ for examples with options. # # Person.sum('age') # => 4562 - def sum(column_name, options = {}) - calculate(:sum, column_name, options) + def sum(*args) + if block_given? + self.to_a.sum(*args) {|*block_args| yield(*block_args)} + else + calculate(:sum, *args) + end end # This calculates aggregate values in the given column. Methods for count, sum, average, @@ -146,10 +150,16 @@ module ActiveRecord if options.except(:distinct).present? apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct]) else - if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) - construct_relation_for_association_calculations.calculate(operation, column_name, options) + relation = with_default_scope + + if relation.equal?(self) + if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) + construct_relation_for_association_calculations.calculate(operation, column_name, options) + else + perform_calculation(operation, column_name, options) + end else - perform_calculation(operation, column_name, options) + relation.calculate(operation, column_name, options) end end rescue ThrowResult @@ -196,7 +206,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = with_default_scope.reorder(nil) + relation = reorder(nil) if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. @@ -245,7 +255,7 @@ module ActiveRecord "#{field} AS #{aliaz}" } - relation = with_default_scope.except(:group).group(group.join(',')) + relation = except(:group).group(group.join(',')) relation.select_values = select_values calculated_data = @klass.connection.select_all(relation.to_sql) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c6e8762b4a..422d89060b 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -259,6 +259,7 @@ module ActiveRecord if match.bang? && result.blank? raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" else + yield(result) if block_given? result end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 739363415c..d17861f407 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -137,7 +137,7 @@ module ActiveRecord def create_with(value) relation = clone - relation.create_with_value = value && (@create_with_value || {}).merge(value) + relation.create_with_value = value ? create_with_value.merge(value) : {} relation end @@ -305,9 +305,18 @@ module ActiveRecord def reverse_sql_order(order_query) order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? - order_query.join(', ').split(',').collect do |s| - s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') - end + order_query.map do |o| + case o + when Arel::Nodes::Ordering + o.reverse + when String, Symbol + o.to_s.split(',').collect do |s| + s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + end + else + o + end + end.flatten end def array_of_strings?(o) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 69706b5ead..ba882beca9 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -55,7 +55,7 @@ module ActiveRecord merged_relation.lock_value = r.lock_value unless merged_relation.lock_value - merged_relation = merged_relation.create_with(r.create_with_value) if r.create_with_value + merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty? # Apply scope extension modules merged_relation.send :apply_modules, r.extensions diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 43fe1cdee5..838aa8fb1e 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,9 +1,9 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 7c49236854..d57794daf8 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -10,6 +10,45 @@ module ActiveRecord @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end + def test_primary_key + assert_equal 'id', @connection.primary_key('ex') + end + + def test_non_standard_primary_key + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(data character varying(255) primary key)') + assert_equal 'data', @connection.primary_key('ex') + end + + def test_primary_key_returns_nil_for_no_pk + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer)') + assert_nil @connection.primary_key('ex') + end + + def test_primary_key_raises_error_if_table_not_found + assert_raises(ActiveRecord::StatementInvalid) do + @connection.primary_key('unobtainium') + end + end + + def test_insert_sql_with_proprietary_returning_clause + id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") + assert_equal "5150", id + end + + def test_insert_sql_with_quoted_schema_and_table_name + id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end + + def test_insert_sql_with_no_space_after_table_name + id = @connection.insert_sql("insert into ex(number) values(5150)") + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end + def test_serial_sequence assert_equal 'public.accounts_id_seq', @connection.serial_sequence('accounts', 'id') @@ -35,6 +74,36 @@ module ActiveRecord @connection.default_sequence_name('zomg') end + def test_pk_and_sequence_for + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @connection.default_sequence_name('ex', 'id'), seq + end + + def test_pk_and_sequence_for_with_non_standard_primary_key + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(code serial primary key)') + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @connection.default_sequence_name('ex', 'code'), seq + end + + def test_pk_and_sequence_for_returns_nil_if_no_seq + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer primary key)') + assert_nil @connection.pk_and_sequence_for('ex') + end + + def test_pk_and_sequence_for_returns_nil_if_no_pk + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer)') + assert_nil @connection.pk_and_sequence_for('ex') + end + + def test_pk_and_sequence_for_returns_nil_if_table_not_found + assert_nil @connection.pk_and_sequence_for('unobtainium') + end + def test_exec_insert_number insert(@connection, 'number' => 10) diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index a5c3e69af9..27e7b1a1c3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -20,6 +20,7 @@ class SchemaTest < ActiveRecord::TestCase 'email character varying(50)', 'moment timestamp without time zone default now()' ] + PK_TABLE_NAME = 'table_with_pk' class Thing1 < ActiveRecord::Base set_table_name "test_schema.things" @@ -49,6 +50,7 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" + @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" end def teardown @@ -63,12 +65,30 @@ class SchemaTest < ActiveRecord::TestCase end end + def test_table_exists_when_on_schema_search_path + with_schema_search_path(SCHEMA_NAME) do + assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found") + end + end + + def test_table_exists_when_not_on_schema_search_path + with_schema_search_path('PUBLIC') do + assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found") + end + end + def test_table_exists_wrong_schema assert(!@connection.table_exists?("foo.things"), "table should not exist") end - def test_table_exists_quoted_table - assert(@connection.table_exists?('"things.table"'), "table should exist") + def test_table_exists_quoted_names + [ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given| + assert(@connection.table_exists?(given), "table should exist when specified as #{given}") + end + with_schema_search_path(SCHEMA_NAME) do + given = %("#{TABLE_NAME}") + assert(@connection.table_exists?(given), "table should exist when specified as #{given}") + end end def test_with_schema_prefixed_table_name @@ -91,7 +111,6 @@ class SchemaTest < ActiveRecord::TestCase end end - def test_proper_encoding_of_table_name assert_equal '"table_name"', @connection.quote_table_name('table_name') assert_equal '"table.name"', @connection.quote_table_name('"table.name"') @@ -164,6 +183,79 @@ class SchemaTest < ActiveRecord::TestCase ActiveRecord::Base.connection.schema_search_path = "public" end + def test_primary_key_with_schema_specified + [ + %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) + ].each do |given| + assert_equal 'id', @connection.primary_key(given), "primary key should be found when table referenced as #{given}" + end + end + + def test_primary_key_assuming_schema_search_path + with_schema_search_path(SCHEMA_NAME) do + assert_equal 'id', @connection.primary_key(PK_TABLE_NAME), "primary key should be found" + end + end + + def test_primary_key_raises_error_if_table_not_found_on_schema_search_path + with_schema_search_path(SCHEMA2_NAME) do + assert_raises(ActiveRecord::StatementInvalid) do + @connection.primary_key(PK_TABLE_NAME) + end + end + end + + def test_pk_and_sequence_for_with_schema_specified + [ + %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) + ].each do |given| + pk, seq = @connection.pk_and_sequence_for(given) + assert_equal 'id', pk, "primary key should be found when table referenced as #{given}" + assert_equal "#{SCHEMA_NAME}.#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" + end + end + + def test_extract_schema_and_table + { + %(table_name) => [nil,'table_name'], + %("table.name") => [nil,'table.name'], + %(schema.table_name) => %w{schema table_name}, + %("schema".table_name) => %w{schema table_name}, + %(schema."table_name") => %w{schema table_name}, + %("schema"."table_name") => %w{schema table_name}, + %("even spaces".table) => ['even spaces','table'], + %(schema."table.name") => ['schema', 'table.name'] + }.each do |given,expect| + assert_equal expect, @connection.send(:extract_schema_and_table, given) + end + end + + def test_current_schema + { + %('$user',public) => 'public', + SCHEMA_NAME => SCHEMA_NAME, + %(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME, + %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => 'public' + }.each do |given,expect| + with_schema_search_path(given) { assert_equal expect, @connection.current_schema } + end + end + + def test_schema_exists? + { + 'public' => true, + SCHEMA_NAME => true, + SCHEMA2_NAME => true, + 'darkside' => false + }.each do |given,expect| + assert_equal expect, @connection.schema_exists?(given) + end + end + private def columns(table_name) @connection.send(:column_definitions, table_name).map do |name, type, default| diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb new file mode 100644 index 0000000000..303ba9245a --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -0,0 +1,49 @@ +require "cases/helper" + +class ViewTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + SCHEMA_NAME = 'test_schema' + TABLE_NAME = 'things' + VIEW_NAME = 'view_things' + COLUMNS = [ + 'id integer', + 'name character varying(50)', + 'email character varying(50)', + 'moment timestamp without time zone' + ] + + class ThingView < ActiveRecord::Base + set_table_name 'test_schema.view_things' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" + @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})" + @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}" + end + + def teardown + @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" + end + + def test_table_exists + name = ThingView.table_name + assert @connection.table_exists?(name), "'#{name}' table should exist" + end + + def test_column_definitions + assert_nothing_raised do + assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}") + end + end + + private + def columns(table_name) + @connection.send(:column_definitions, table_name).map do |name, type, default| + "#{name} #{type}" + (default ? " default #{default}" : '') + end + end + +end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 9af1f7249f..a2764f3e3b 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -226,6 +226,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, Firm.find(:first, :order => "id").clients.length end + def test_finding_array_compatibility + assert_equal 2, Firm.order(:id).find{|f| f.id > 0}.clients.length + end + def test_find_with_blank_conditions [[], {}, nil, ""].each do |blank| assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size @@ -1557,4 +1561,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, contract.hi_count assert_equal 1, contract.bye_count end + + def test_association_attributes_are_available_to_after_initialize + car = Car.create(:name => 'honda') + bulb = car.bulbs.build + + assert_equal car.id, bulb.attributes_after_initialize['car_id'] + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 877148bd5e..0b1ba31ac2 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -714,6 +714,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [categories(:general).id], authors(:mary).categories_like_general_ids end + def test_get_collection_singular_ids_on_has_many_through_with_conditions_and_include + person = Person.first + assert_equal person.posts_with_no_comment_ids, person.posts_with_no_comments.map(&:id) + end + def test_count_has_many_through_with_named_scope assert_equal 2, authors(:mary).categories.count assert_equal 1, authors(:mary).categories.general.count diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 299688d840..26931e3e85 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -345,6 +345,17 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert orig_ship.destroyed? end + def test_creation_failure_due_to_new_record_should_raise_error + pirate = pirates(:redbeard) + new_ship = Ship.new + + assert_raise(ActiveRecord::RecordNotSaved) do + pirate.ship = new_ship + end + assert_nil pirate.ship + assert_nil new_ship.pirate_id + end + def test_replacement_failure_due_to_existing_record_should_raise_error pirate = pirates(:blackbeard) pirate.ship.name = nil @@ -438,4 +449,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase bulb = car.create_bulb!{ |b| b.color = 'Red' } assert_equal 'RED!', bulb.color end + + def test_association_attributes_are_available_to_after_initialize + car = Car.create(:name => 'honda') + bulb = car.create_bulb + + assert_equal car.id, bulb.attributes_after_initialize['car_id'] + end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 2224097f04..5fef3faab9 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -478,6 +478,19 @@ class BasicsTest < ActiveRecord::TestCase def test_hashing assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end + + def test_comparison + topic_1 = Topic.create! + topic_2 = Topic.create! + + assert_equal [topic_2, topic_1].sort, [topic_1, topic_2] + end + + def test_comparison_with_different_objects + topic = Topic.create + category = Category.create(:name => "comparison") + assert_nil topic <=> category + end def test_readonly_attributes assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 6620464d6a..50ddf6c757 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -18,6 +18,13 @@ class EachTest < ActiveRecord::TestCase end end + def test_each_should_not_return_query_chain_and_execcute_only_one_query + assert_queries(1) do + result = Post.find_each(:batch_size => 100000){ } + assert_nil result + end + end + def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do Post.find_each(:select => :title, :batch_size => 1) { |post| post } diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 56f6d795b6..224b3f3d1f 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -397,6 +397,10 @@ class CalculationsTest < ActiveRecord::TestCase Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") end + def test_sum_array_compatibility + assert_equal Account.sum(:credit_limit), Account.sum(&:credit_limit) + end + def test_average_with_from_option assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts') assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"), diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index 85871aebdf..a1d1177289 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -77,6 +77,13 @@ module ActiveRecord @management.call(@env) assert ActiveRecord::Base.connection_handler.active_connections? end + + test "proxy is polite to it's body and responds to it" do + body = Class.new(String) { def to_path; "/path"; end }.new + proxy = ConnectionManagement::Proxy.new(body) + assert proxy.respond_to?(:to_path) + assert_equal proxy.to_path, "/path" + end end end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 306b437fb3..854eb86c09 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -589,8 +589,8 @@ class FoxyFixturesTest < ActiveRecord::TestCase end def test_preserves_existing_fixture_data - assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date) - assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date) + assert_equal(2.weeks.ago.utc.to_date, pirates(:redbeard).created_on.utc.to_date) + assert_equal(2.weeks.ago.utc.to_date, pirates(:redbeard).updated_on.utc.to_date) end def test_generates_unique_ids diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 0f79c99e1a..36007255fa 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -29,7 +29,12 @@ module ActiveRecord assert_equal [[:create_table, [:horses]]], recorder.commands end - def test_unknown_commands_raise_exception + def test_unknown_commands_delegate + recorder = CommandRecorder.new(stub(:foo => 'bar')) + assert_equal 'bar', recorder.foo + end + + def test_unknown_commands_raise_exception_if_they_cannot_delegate @recorder.record :execute, ['some sql'] assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index bf7565a0d0..93a1249e43 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1071,6 +1071,18 @@ if ActiveRecord::Base.connection.supports_migrations? Person.connection.drop_table :testings rescue nil end + def test_column_exists_on_table_with_no_options_parameter_supplied + Person.connection.create_table :testings do |t| + t.string :foo + end + Person.connection.change_table :testings do |t| + assert t.column_exists?(:foo) + assert !(t.column_exists?(:bar)) + end + ensure + Person.connection.drop_table :testings rescue nil + end + def test_add_table assert !Reminder.table_exists? diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index a61180cfaf..ad17f6f83a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -203,3 +203,14 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase end end end + +class QueryCacheBodyProxyTest < ActiveRecord::TestCase + + test "is polite to it's body and responds to it" do + body = Class.new(String) { def to_path; "/path"; end }.new + proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body) + assert proxy.respond_to?(:to_path) + assert_equal proxy.to_path, "/path" + end + +end
\ No newline at end of file diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 58c78ab058..41312e8661 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -76,7 +76,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_reflection_klass_for_nested_class_name - reflection = MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil) + reflection = MacroReflection.new(:company, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base) assert_nothing_raised do assert_equal MyApplication::Business::Company, reflection.klass end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index a6e08f95d0..f2d177d834 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -15,6 +15,18 @@ class RelationScopingTest < ActiveRecord::TestCase assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order end + def test_reverse_order_with_arel_node + assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order + end + + def test_reverse_order_with_multiple_arel_nodes + assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order + end + + def test_reverse_order_with_arel_nodes_and_strings + assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order + end + def test_double_reverse_order_produces_original_order assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order end @@ -472,6 +484,13 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 'Jamis', jamis.name end + # FIXME: I don't know if this is *desired* behavior, but it is *today's* + # behavior. + def test_create_with_empty_hash_will_not_reset + jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with({}).new + assert_equal 'Aaron', jamis.name + end + def test_unscoped_with_named_scope_should_not_have_default_scope assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor @@ -498,4 +517,11 @@ class DefaultScopingTest < ActiveRecord::TestCase lowest_salary_dev = DeveloperOrderedBySalary.find(developers(:poor_jamis).id) assert_equal lowest_salary_dev, DeveloperOrderedBySalary.last end + + def test_default_scope_include_with_count + d = DeveloperWithIncludes.create! + d.audit_logs.create! :message => 'foo' + + assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count + end end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 12e58bd340..b23ead6feb 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -20,7 +20,7 @@ module ActiveRecord end def test_single_values - assert_equal [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder, :reverse_order].map(&:to_s).sort, + assert_equal [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order].map(&:to_s).sort, Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fc9df8c7a3..0aaa0342b1 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -145,6 +145,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:first).title, topics.first.title end + + def test_finding_with_arel_order + topics = Topic.order(Topic.arel_table[:id].asc) + assert_equal 4, topics.to_a.size + assert_equal topics(:first).title, topics.first.title + end + + def test_finding_last_with_arel_order + topics = Topic.order(Topic.arel_table[:id].asc) + assert_equal topics(:fourth).title, topics.last.title + end + def test_finding_with_order_concatenated topics = Topic.order('author_name').order('title') assert_equal 4, topics.to_a.size @@ -164,6 +176,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal entrants(:first).name, entrants.first.name end + def test_finding_with_cross_table_order_and_limit + tags = Tag.includes(:taggings). + order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)"). + limit(1).to_a + assert_equal 1, tags.length + end + def test_finding_with_complex_order_and_limit tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a assert_equal 1, tags.length @@ -372,6 +391,15 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.find(1).last_comment, post.last_comment end + def test_dynamic_find_by_attributes_should_yield_found_object + david = authors(:david) + yielded_value = nil + Author.find_by_name(david.name) do |author| + yielded_value = author + end + assert_equal david, yielded_value + end + def test_dynamic_find_by_attributes david = authors(:david) author = Author.preload(:taggings).find_by_id(david.id) diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 0dcc8d5970..efb98b66e7 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -4,13 +4,18 @@ class Bulb < ActiveRecord::Base attr_protected :car_id, :frickinawesome - attr_reader :scope_after_initialize + attr_reader :scope_after_initialize, :attributes_after_initialize after_initialize :record_scope_after_initialize def record_scope_after_initialize @scope_after_initialize = self.class.scoped end + after_initialize :record_attributes_after_initialize + def record_attributes_after_initialize + @attributes_after_initialize = attributes.dup + end + def color=(color) self[:color] = color.upcase + "!" end @@ -28,4 +33,4 @@ class Bulb < ActiveRecord::Base end class CustomBulb < Bulb -end
\ No newline at end of file +end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 98d6aa22f7..f182a7fa97 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -91,6 +91,12 @@ class DeveloperWithSelect < ActiveRecord::Base default_scope select('name') end +class DeveloperWithIncludes < ActiveRecord::Base + self.table_name = 'developers' + has_many :audit_logs, :foreign_key => :developer_id + default_scope includes(:audit_logs) +end + class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope :order => 'salary DESC' diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index 440b504344..d53374b261 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -1,9 +1,9 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 23f25d6197..034d03533f 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.2.0 (unreleased)* +* The inflector understands acronyms. [dlee] + * Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim] * Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH] diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 0e6bc30fa2..8f8deb9692 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -1,12 +1,12 @@ module ActiveSupport - # Many backtraces include too much information that's not relevant for the context. This makes it hard to find the signal - # in the backtrace and adds debugging time. With a BacktraceCleaner, you can setup filters and silencers for your particular - # context, so only the relevant lines are included. + # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the + # signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to + # remove the noisy lines, so that only the most relevant lines remain. # - # If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always - # call BacktraceCleaner#remove_silencers! Also, if you need to reconfigure an existing BacktraceCleaner so that it does not - # filter or modify the paths of any lines of the backtrace, you can call BacktraceCleaner#remove_filters! These two methods - # will give you a completely untouched backtrace. + # Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case + # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory + # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the + # backtrace, so that you can focus on the rest. # # ==== Example: # @@ -15,13 +15,18 @@ module ActiveSupport # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems # + # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can + # always call <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the backtrace to a pristine state. If you + # need to reconfigure an existing BacktraceCleaner so that it does not filter or modify the paths of any lines of the + # backtrace, you can call BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace. + # # Inspired by the Quiet Backtrace gem by Thoughtbot. class BacktraceCleaner def initialize @filters, @silencers = [], [] end - # Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers. + # Returns the backtrace after all filters and silencers have been run against it. Filters run first, then silencers. def clean(backtrace, kind = :silent) filtered = filter(backtrace) @@ -45,8 +50,8 @@ module ActiveSupport @filters << block end - # Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the - # clean backtrace. + # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from + # the clean backtrace. # # Example: # @@ -57,7 +62,7 @@ module ActiveSupport end # Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as - # you suspect a bug in the libraries you use. + # you suspect a bug in one of the libraries you use. def remove_silencers! @silencers = [] end diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index e303eaa5ce..cc94041a1d 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -3,28 +3,28 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport module Benchmarkable - # Allows you to measure the execution time of a block - # in a template and records the result to the log. Wrap this block around - # expensive operations or possible bottlenecks to get a time reading - # for the operation. For example, let's say you thought your file - # processing method was taking too long; you could wrap it in a benchmark block. + # Allows you to measure the execution time of a block in a template and records the result to + # the log. Wrap this block around expensive operations or possible bottlenecks to get a time + # reading for the operation. For example, let's say you thought your file processing method + # was taking too long; you could wrap it in a benchmark block. # # <% benchmark "Process data files" do %> # <%= expensive_files_operation %> # <% end %> # - # That would add something like "Process data files (345.2ms)" to the log, - # which you can then use to compare timings when optimizing your code. + # That would add something like "Process data files (345.2ms)" to the log, which you can then + # use to compare timings when optimizing your code. # - # You may give an optional logger level as the :level option. - # (:debug, :info, :warn, :error); the default value is :info. + # You may give an optional logger level (:debug, :info, :warn, :error) as the :level option. + # The default logger level value is :info. # # <% benchmark "Low-level files", :level => :debug do %> # <%= lowlevel_files_operation %> # <% end %> # - # Finally, you can pass true as the third argument to silence all log activity - # inside the block. This is great for boiling down a noisy block to just a single statement: + # Finally, you can pass true as the third argument to silence all log activity (other than the + # timing information) from inside the block. This is great for boiling down a noisy block to + # just a single statement that produces one log line: # # <% benchmark "Process data files", :level => :info, :silence => true do %> # <%= expensive_and_chatty_files_operation %> diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 10c457bb1d..85692428e3 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -116,31 +116,32 @@ module ActiveSupport # cache.read("city") # => "Duckburgh" # # Keys are always translated into Strings and are case sensitive. When an - # object is specified as a key, its +cache_key+ method will be called if it - # is defined. Otherwise, the +to_param+ method will be called. Hashes and - # Arrays can be used as keys. The elements will be delimited by slashes - # and Hashes elements will be sorted by key so they are consistent. + # object is specified as a key and has a +cache_key+ method defined, this + # method will be called to define the key. Otherwise, the +to_param+ + # method will be called. Hashes and Arrays can also be used as keys. The + # elements will be delimited by slashes, and the elements within a Hash + # will be sorted by key so they are consistent. # # cache.read("city") == cache.read(:city) # => true # # Nil values can be cached. # - # If your cache is on a shared infrastructure, you can define a namespace for - # your cache entries. If a namespace is defined, it will be prefixed on to every - # key. The namespace can be either a static value or a Proc. If it is a Proc, it - # will be invoked when each key is evaluated so that you can use application logic - # to invalidate keys. + # If your cache is on a shared infrastructure, you can define a namespace + # for your cache entries. If a namespace is defined, it will be prefixed on + # to every key. The namespace can be either a static value or a Proc. If it + # is a Proc, it will be invoked when each key is evaluated so that you can + # use application logic to invalidate keys. # # cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace # # - # Caches can also store values in a compressed format to save space and reduce - # time spent sending data. Since there is some overhead, values must be large - # enough to warrant compression. To turn on compression either pass - # <tt>:compress => true</tt> in the initializer or to +fetch+ or +write+. - # To specify the threshold at which to compress values, set - # <tt>:compress_threshold</tt>. The default threshold is 32K. + # Caches can also store values in a compressed format to save space and + # reduce time spent sending data. Since there is overhead, values must be + # large enough to warrant compression. To turn on compression either pass + # <tt>:compress => true</tt> in the initializer or as an option to +fetch+ + # or +write+. To specify the threshold at which to compress values, set the + # <tt>:compress_threshold</tt> option. The default threshold is 32K. class Store cattr_accessor :logger, :instance_writer => true @@ -180,11 +181,11 @@ module ActiveSupport # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # - # If there is no such data in the cache (a cache miss occurred), - # then nil will be returned. However, if a block has been passed, then - # that block will be run in the event of a cache miss. The return value - # of the block will be written to the cache under the given cache key, - # and that return value will be returned. + # If there is no such data in the cache (a cache miss), then nil will be + # returned. However, if a block has been passed, that block will be run + # in the event of a cache miss. The return value of the block will be + # written to the cache under the given cache key, and that return value + # will be returned. # # cache.write("today", "Monday") # cache.fetch("today") # => "Monday" @@ -205,10 +206,11 @@ module ActiveSupport # in a compressed format. # # - # Setting <tt>:expires_in</tt> will set an expiration time on the cache. All caches - # support auto expiring content after a specified number of seconds. This value can - # be specified as an option to the construction in which call all entries will be - # affected. Or it can be supplied to the +fetch+ or +write+ method for just one entry. + # Setting <tt>:expires_in</tt> will set an expiration time on the cache. + # All caches support auto-expiring content after a specified number of + # seconds. This value can be specified as an option to the constructor + # (in which case all entries will be affected), or it can be supplied to + # the +fetch+ or +write+ method to effect just one entry. # # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes) # cache.write(key, value, :expires_in => 1.minute) # Set a lower value for one entry diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index a94446acde..a2d2719de7 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -12,12 +12,12 @@ module ActiveSupport class Configuration < ActiveSupport::InheritableOptions def compile_methods! - self.class.compile_methods!(keys.reject {|key| respond_to?(key)}) + self.class.compile_methods!(keys) end # compiles reader methods so we don't have to go through method_missing def self.compile_methods!(keys) - keys.each do |key| + keys.reject { |m| method_defined?(m) }.each do |key| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{key}; _get(#{key.inspect}); end RUBY diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index f420270dc4..4834eca8b1 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -40,7 +40,7 @@ class Array if object.nil? [] elsif object.respond_to?(:to_ary) - object.to_ary + object.to_ary || [object] else [object] end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 7baba75ad3..ca9b2c1b60 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/array/extract_options' class Class # Declare a class-level attribute whose value is inheritable by subclasses. @@ -56,11 +57,18 @@ class Class # object.setting # => false # Base.setting # => true # + # To opt out of the instance reader method, pass :instance_reader => false. + # + # object.setting # => NoMethodError + # object.setting? # => NoMethodError + # # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError def class_attribute(*attrs) - instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer] + options = attrs.extract_options! + instance_reader = options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -84,13 +92,15 @@ class Class val end - remove_possible_method :#{name} - def #{name} - defined?(@#{name}) ? @#{name} : self.class.#{name} - end + if instance_reader + remove_possible_method :#{name} + def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} + end - def #{name}? - !!#{name} + def #{name}? + !!#{name} + end end RUBY diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 871f5cef3b..be94ae1565 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -12,7 +12,7 @@ class Module end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -31,7 +31,7 @@ class Module end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj @@ -53,6 +53,10 @@ class Module # end # # AppConfiguration.google_api_key = "overriding the api key!" + # + # To opt out of the instance writer method, pass :instance_writer => false. + # To opt out of the instance reader method, pass :instance_reader => false. + # To opt out of both instance methods, pass :instance_accessor => false. def mattr_accessor(*syms) mattr_reader(*syms) mattr_writer(*syms) diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 5b2cb6e331..664537eea4 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -34,9 +34,9 @@ class String # Form can be either :utc (default) or :local. def to_time(form = :utc) return nil if self.blank? - d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction).map { |arg| arg || 0 } + d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).map { |arg| arg || 0 } d[6] *= 1000000 - ::Time.send("#{form}_time", *d) + ::Time.send("#{form}_time", *d[0..6]) - d[7] end def to_date diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2f0676f567..002688d6c0 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,4 @@ require 'active_support/inflector/methods' -require 'active_support/inflector/inflections' require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 71f3879e49..3bf4edbdef 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -67,7 +67,7 @@ class Object end end -class Fixnum +class Numeric def html_safe? true end @@ -137,14 +137,14 @@ module ActiveSupport #:nodoc: UNSAFE_STRING_METHODS.each do |unsafe_method| class_eval <<-EOT, __FILE__, __LINE__ - def #{unsafe_method}(*args) - super.to_str - end - - def #{unsafe_method}!(*args) - @dirty = true - super - end + def #{unsafe_method}(*args, &block) # def gsub(*args, &block) + to_str.#{unsafe_method}(*args, &block) # to_str.gsub(*args, &block) + end # end + + def #{unsafe_method}!(*args) # def gsub!(*args) + @dirty = true # @dirty = true + super # super + end # end EOT end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index d5d55b7207..90bb62f57b 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -20,10 +20,61 @@ module ActiveSupport @__instance__ ||= new end - attr_reader :plurals, :singulars, :uncountables, :humans + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex def initialize - @plurals, @singulars, @uncountables, @humans = [], [], [], [] + @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ + end + + # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore + # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`. + # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will + # convert the acronym into a non-delimited single lowercase word when passed to +underscore+. + # + # Examples: + # acronym 'HTML' + # titleize 'html' #=> 'HTML' + # camelize 'html' #=> 'HTML' + # underscore 'MyHTML' #=> 'my_html' + # + # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it: + # + # acronym 'HTTP' + # camelize 'my_http_delimited' #=> 'MyHTTPDelimited' + # camelize 'https' #=> 'Https', not 'HTTPs' + # underscore 'HTTPS' #=> 'http_s', not 'https' + # + # acronym 'HTTPS' + # camelize 'https' #=> 'HTTPS' + # underscore 'HTTPS' #=> 'https' + # + # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as + # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an + # acronym as well: + # + # acronym 'API' + # camelize(pluralize('api')) #=> 'Apis' + # + # acronym 'APIs' + # camelize(pluralize('api')) #=> 'APIs' + # + # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard + # capitalization. The only restriction is that the word must begin with a capital letter. + # + # Examples: + # acronym 'RESTful' + # underscore 'RESTful' #=> 'restful' + # underscore 'RESTfulController' #=> 'restful_controller' + # titleize 'RESTfulController' #=> 'RESTful Controller' + # camelize 'restful' #=> 'RESTful' + # camelize 'restful_controller' #=> 'RESTfulController' + # + # acronym 'McDonald' + # underscore 'McDonald' #=> 'mcdonald' + # camelize 'mcdonald' #=> 'McDonald' + def acronym(word) + @acronyms[word.downcase] = word + @acronym_regex = /#{@acronyms.values.join("|")}/ end # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. @@ -117,95 +168,5 @@ module ActiveSupport Inflections.instance end end - - # Returns the plural form of the word in the string. - # - # Examples: - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # Examples: - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - result = word.to_s.dup - - if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. - # - # Examples: - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" - def humanize(lower_case_and_underscored_word) - result = lower_case_and_underscored_word.to_s.dup - - inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - # Capitalizes all the words and replaces some characters in the string to create - # a nicer looking title. +titleize+ is meant for creating pretty output. It is not - # used in the Rails internals. - # - # +titleize+ is also aliased as as +titlecase+. - # - # Examples: - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" - def titleize(word) - humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } - end - - # Create the name of a table like Rails does for models to table names. This method - # uses the +pluralize+ method on the last word in the string. - # - # Examples - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end - - # Create a class name from a plural table name like Rails does for table names to models. - # Note that this returns a string and not a Class. (To convert to an actual class - # follow +classify+ with +constantize+.) - # - # Examples: - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" - # - # Singular names are not handled correctly: - # "business".classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a2c4f7bfda..3d28d33f40 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,3 +1,5 @@ +require 'active_support/inflector/inflections' + module ActiveSupport # 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 @@ -10,6 +12,44 @@ module ActiveSupport module Inflector extend self + # Returns the plural form of the word in the string. + # + # Examples: + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.include?(result.downcase) + result + else + inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # Examples: + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singularize # => "sheep" + # "word".singularize # => "word" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + result = word.to_s.dup + + if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } + result + else + inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. # @@ -25,12 +65,14 @@ module ActiveSupport # though there are cases where that does not hold: # # "SSLError".underscore.camelize # => "SslError" - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + def camelize(term, uppercase_first_letter = true) + string = term.to_s + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } else - lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] + string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end + string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') end # Makes an underscored, lowercase form from the expression in the string. @@ -48,13 +90,66 @@ module ActiveSupport def underscore(camel_cased_word) word = camel_cased_word.to_s.dup word.gsub!(/::/, '/') - word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") word.downcase! word end + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # Examples: + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result.gsub!(/_id$/, "") + result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase } + end + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as as +titlecase+. + # + # Examples: + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + def titleize(word) + humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # Examples: + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end + # Replaces underscores with dashes in the string. # # Example: diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 1c4dd24227..6296c1d4b8 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -3,8 +3,8 @@ require 'active_support/core_ext/class/attribute' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications - # with solely purpose of logging. The log subscriber dispatches notifications to a - # registered object based on its given namespace. + # with the sole purpose of logging them. The log subscriber dispatches notifications to + # a registered object based on its given namespace. # # An example would be Active Record log subscriber responsible for logging queries: # @@ -109,8 +109,8 @@ module ActiveSupport # Set color by using a string or one of the defined constants. If a third # option is set to true, it also adds bold to the string. This is based - # on Highline implementation and it automatically appends CLEAR to the end - # of the returned String. + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. # def color(text, color, bold=false) return text unless colorize_logging diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 8d8e6ebc58..bf81567d22 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -36,6 +36,10 @@ module ActiveSupport #:nodoc: self[name] end end + + def respond_to?(name) + true + end end class InheritableOptions < OrderedOptions diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 05da50e150..3864b1f5a6 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -68,7 +68,7 @@ module ActiveSupport # # A error message can be specified. # - # assert_no_difference 'Article.count', "An Article should not be destroyed" do + # assert_no_difference 'Article.count', "An Article should not be created" do # post :create, :article => invalid_attributes # end def assert_no_difference(expression, message = nil, &block) diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index e135872bf6..bd8e7f907a 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,9 +1,9 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index c6d8191298..2e5ea2c360 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -58,16 +58,26 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase child = Class.new(parent) parent.config.bar = :foo - assert !parent.config.respond_to?(:bar) - assert !child.config.respond_to?(:bar) - assert !child.new.config.respond_to?(:bar) + assert_method_not_defined parent.config, :bar + assert_method_not_defined child.config, :bar + assert_method_not_defined child.new.config, :bar parent.config.compile_methods! assert_equal :foo, parent.config.bar assert_equal :foo, child.new.config.bar - assert_respond_to parent.config, :bar - assert_respond_to child.config, :bar - assert_respond_to child.new.config, :bar + assert_method_defined parent.config, :bar + assert_method_defined child.config, :bar + assert_method_defined child.new.config, :bar + end + + def assert_method_defined(object, method) + methods = object.public_methods.map(&:to_s) + assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}" + end + + def assert_method_not_defined(object, method) + methods = object.public_methods.map(&:to_s) + assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 0e5407bc35..e532010b18 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -456,8 +456,9 @@ class ArrayWrapperTests < Test::Unit::TestCase assert_equal [o], Array.wrap(o) end - def test_wrap_returns_nil_if_to_ary_returns_nil - assert_nil Array.wrap(NilToAry.new) + def test_wrap_returns_wrapped_if_to_ary_returns_nil + o = NilToAry.new + assert_equal [o], Array.wrap(o) end def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index d58b60482b..e290a6e012 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -60,6 +60,12 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting = 'boom' } end + test 'disabling instance reader' do + object = Class.new { class_attribute :setting, :instance_reader => false }.new + assert_raise(NoMethodError) { object.setting } + assert_raise(NoMethodError) { object.setting? } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 118fb070a0..29889b51e0 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -7,6 +7,7 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase mattr_accessor :foo mattr_accessor :bar, :instance_writer => false mattr_reader :shaq, :instance_reader => false + mattr_accessor :camp, :instance_accessor => false end @class = Class.new @class.instance_eval { include m } @@ -37,4 +38,10 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase assert_respond_to @module, :shaq assert !@object.respond_to?(:shaq) end + + def test_should_not_create_instance_accessors + assert_respond_to @module, :camp + assert !@object.respond_to?(:camp) + assert !@object.respond_to?(:camp=) + end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 7a8cda160d..4000cc913a 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -158,6 +158,7 @@ class StringInflectionsTest < Test::Unit::TestCase assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local) assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time assert_equal Time.local_time(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local) + assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 22:50 -0100".to_time assert_nil "".to_time end @@ -353,6 +354,10 @@ class OutputSafetyTest < ActiveSupport::TestCase test "A fixnum is safe by default" do assert 5.html_safe? end + + test "a float is safe by default" do + assert 5.7.html_safe? + end test "An object is unsafe by default" do assert !@object.html_safe? diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 95f18126d4..b9e299af75 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -94,6 +94,88 @@ class InflectorTest < Test::Unit::TestCase assert_equal('capital', ActiveSupport::Inflector.camelize('Capital', false)) end + def test_camelize_with_underscores + assert_equal("CamelCase", ActiveSupport::Inflector.camelize('Camel_Case')) + end + + def test_acronyms + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + inflect.acronym("HTTP") + inflect.acronym("RESTful") + inflect.acronym("W3C") + inflect.acronym("PhD") + inflect.acronym("RoR") + inflect.acronym("SSL") + end + + # camelize underscore humanize titleize + [ + ["API", "api", "API", "API"], + ["APIController", "api_controller", "API controller", "API Controller"], + ["Nokogiri::HTML", "nokogiri/html", "Nokogiri/HTML", "Nokogiri/HTML"], + ["HTTPAPI", "http_api", "HTTP API", "HTTP API"], + ["HTTP::Get", "http/get", "HTTP/get", "HTTP/Get"], + ["SSLError", "ssl_error", "SSL error", "SSL Error"], + ["RESTful", "restful", "RESTful", "RESTful"], + ["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"], + ["IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"], + ["PhDRequired", "phd_required", "PhD required", "PhD Required"], + ["IRoRU", "i_ror_u", "I RoR u", "I RoR U"], + ["RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"], + + # misdirection + ["Capistrano", "capistrano", "Capistrano", "Capistrano"], + ["CapiController", "capi_controller", "Capi controller", "Capi Controller"], + ["HttpsApis", "https_apis", "Https apis", "Https Apis"], + ["Html5", "html5", "Html5", "Html5"], + ["Restfully", "restfully", "Restfully", "Restfully"], + ["RoRails", "ro_rails", "Ro rails", "Ro Rails"] + ].each do |camel, under, human, title| + assert_equal(camel, ActiveSupport::Inflector.camelize(under)) + assert_equal(camel, ActiveSupport::Inflector.camelize(camel)) + assert_equal(under, ActiveSupport::Inflector.underscore(under)) + assert_equal(under, ActiveSupport::Inflector.underscore(camel)) + assert_equal(title, ActiveSupport::Inflector.titleize(under)) + assert_equal(title, ActiveSupport::Inflector.titleize(camel)) + assert_equal(human, ActiveSupport::Inflector.humanize(under)) + end + end + + def test_acronym_override + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("LegacyApi") + end + + assert_equal("LegacyApi", ActiveSupport::Inflector.camelize("legacyapi")) + assert_equal("LegacyAPI", ActiveSupport::Inflector.camelize("legacy_api")) + assert_equal("SomeLegacyApi", ActiveSupport::Inflector.camelize("some_legacyapi")) + assert_equal("Nonlegacyapi", ActiveSupport::Inflector.camelize("nonlegacyapi")) + end + + def test_acronyms_camelize_lower + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + end + + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("html_api", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("htmlAPI", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("HTMLAPI", false)) + end + + def test_underscore_acronym_sequence + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML5") + inflect.acronym("HTML") + end + + assert_equal("html5_html_api", ActiveSupport::Inflector.underscore("HTML5HTMLAPI")) + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) @@ -148,8 +230,8 @@ class InflectorTest < Test::Unit::TestCase end def test_parameterize_with_custom_separator - StringToParameterized.each do |some_string, parameterized_string| - assert_equal(parameterized_string.gsub('-', '_'), ActiveSupport::Inflector.parameterize(some_string, '_')) + StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index ec9d92794c..0cb1f70657 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -168,6 +168,7 @@ module InflectorTestCases StringToParameterizeWithNoSeparator = { "Donald E. Knuth" => "donaldeknuth", + "With-some-dashes" => "with-some-dashes", "Random text with *(bad)* characters" => "randomtextwithbadcharacters", "Trailing bad characters!@#" => "trailingbadcharacters", "!@#Leading bad characters" => "leadingbadcharacters", @@ -179,6 +180,8 @@ module InflectorTestCases StringToParameterizeWithUnderscore = { "Donald E. Knuth" => "donald_e_knuth", "Random text with *(bad)* characters" => "random_text_with_bad_characters", + "With-some-dashes" => "with-some-dashes", + "Retain_underscore" => "retain_underscore", "Trailing bad characters!@#" => "trailing_bad_characters", "!@#Leading bad characters" => "leading_bad_characters", "Squeeze separators" => "squeeze_separators", diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index a8b39d2c38..46bc0d7a34 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -104,4 +104,8 @@ class SafeBufferTest < ActiveSupport::TestCase @buffer.safe_concat "BUSTED" end end + + test "should not fail if the returned object is not a string" do + assert_kind_of NilClass, @buffer.slice("chipchop") + end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 49cefc6e82..3575175517 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -224,9 +224,9 @@ class TimeZoneTest < Test::Unit::TestCase end def test_formatted_offset_positive - zone = ActiveSupport::TimeZone['Moscow'] - assert_equal "+03:00", zone.formatted_offset - assert_equal "+0300", zone.formatted_offset(false) + zone = ActiveSupport::TimeZone['New Delhi'] + assert_equal "+05:30", zone.formatted_offset + assert_equal "+0530", zone.formatted_offset(false) end def test_formatted_offset_negative @@ -257,7 +257,7 @@ class TimeZoneTest < Test::Unit::TestCase end def test_to_s - assert_equal "(GMT+03:00) Moscow", ActiveSupport::TimeZone['Moscow'].to_s + assert_equal "(GMT+05:30) New Delhi", ActiveSupport::TimeZone['New Delhi'].to_s end def test_all_sorted diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb index ec3ca99ee6..1acaf7228f 100644 --- a/activesupport/test/whiny_nil_test.rb +++ b/activesupport/test/whiny_nil_test.rb @@ -33,11 +33,10 @@ class WhinyNilTest < Test::Unit::TestCase end def test_id - nil.stubs(:object_id).returns(999) nil.id rescue RuntimeError => nme assert_no_match(/nil:NilClass/, nme.message) - assert_match(/999/, nme.message) + assert_match(Regexp.new(nil.object_id.to_s), nme.message) end def test_no_to_ary_coercion diff --git a/ci/cruise_config.rb b/ci/cruise_config.rb index 9c7fa98a70..b64cd8aaea 100644 --- a/ci/cruise_config.rb +++ b/ci/cruise_config.rb @@ -1,9 +1,9 @@ Project.configure do |project| - # Send email notifications about broken and fixed builds to core mailing list - if Socket.gethostname =~ /ci.rubyonrails.org/ && ENV['ENABLE_RAILS_CI_EMAILS'] == 'true' - project.email_notifier.emails = ['rubyonrails-core@googlegroups.com'] - end - project.build_command = 'sudo gem update --system && ruby ci/ci_build.rb' - project.email_notifier.from = 'thewoolleyman@gmail.com' + project.email_notifier.from = 'rails-ci@wyeworks.com' + + # project.campfire_notifier.account = 'rails' + # project.campfire_notifier.token = '' + # project.campfire_notifier.room = 'Rails 3' + # project.campfire_notifier.ssl = true end diff --git a/ci/site_config.rb b/ci/site_config.rb index 09d5b550e8..f9db39ed57 100644 --- a/ci/site_config.rb +++ b/ci/site_config.rb @@ -45,16 +45,16 @@ Configuration.dashboard_refresh_interval = 60.seconds # Site-wide setting for the email "from" field. This can also be set on per-project basis, # through project.email.notifier.from attribute -Configuration.email_from = 'thewoolleyman+railsci@gmail.com' +Configuration.email_from = 'rails-ci@wyeworks.com' # Root URL of the dashboard application. Setting this attribute allows various notifiers to include a link to the # build page in the notification message. -Configuration.dashboard_url = 'http://ci.rubyonrails.org/' +Configuration.dashboard_url = 'http://rails-ci.wyeworks.com/' # If you don't want to allow triggering builds through dashboard Build Now button. Useful when you host CC.rb as a # public web site (such as http://cruisecontrolrb.thoughtworks.com/projects - try clicking on Build Now button there # and see what happens): -# Configuration.disable_build_now = true +Configuration.disable_build_now = true # If you want to only allow one project to build at a time, uncomment this line # by default, cruise allows multiple projects to build at a time diff --git a/railties/CHANGELOG b/railties/CHANGELOG index f2b0cf15cd..a9b4deb551 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -6,24 +6,21 @@ * Removed old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API. [Guillermo Iguaran] - *Rails 3.1.0 (unreleased)* -* The new rake task assets:clean removes precompiled assets. [fxn] - -* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] +* Make sprockets/railtie require explicit and add --skip-sprockets to app generator [José Valim] -* Fixed database tasks for jdbc* adapters #jruby +* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim] - [Rashmi Yadav] +* The new rake task assets:clean removes precompiled assets. [fxn] -* Template generation for jdbcpostgresql #jruby +* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] - [Vishnu Atrai] +* Fixed database tasks for jdbc* adapters #jruby [Rashmi Yadav] -* Template generation for jdbcmysql and jdbcsqlite3 #jruby +* Template generation for jdbcpostgresql #jruby [Vishnu Atrai] - [Arun Agrawal] +* Template generation for jdbcmysql and jdbcsqlite3 #jruby [Arun Agrawal] * The -j option of the application generator accepts an arbitrary string. If passed "foo", the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest diff --git a/railties/bin/rails b/railties/bin/rails new file mode 100755 index 0000000000..a7d6938e0d --- /dev/null +++ b/railties/bin/rails @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +require "rails/cli"
\ No newline at end of file diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 891bae3d5e..073e3bddcf 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -802,7 +802,7 @@ class DinnerController end </ruby> -Just like the filter, you could also passing +:only+ and +:except+ to enforce the secure connection only to specific actions +Just like the filter, you could also passing +:only+ and +:except+ to enforce the secure connection only to specific actions. <ruby> class DinnerController diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index e1ff49cd60..2eaee158ff 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -284,16 +284,37 @@ class UserMailer < ActionMailer::Base @user = user @url = "http://example.com/login" mail(:to => user.email, + :subject => "Welcome to My Awesome Site", + :template_path => 'notifications', + :template_name => 'another') + end + end + +end +</ruby> + +In this case it will look for templates at +app/views/notifications+ with name +another+. + +If you want more flexibility you can also pass a block and render specific templates or even render inline or text without using a template file: + +<ruby> +class UserMailer < ActionMailer::Base + default :from => "notifications@example.com" + + def welcome_email(user) + @user = user + @url = "http://example.com/login" + mail(:to => user.email, :subject => "Welcome to My Awesome Site") do |format| format.html { render 'another_template' } - format.text { render 'another_template' } + format.text { render :text => 'Render text' } end end end </ruby> -Will render 'another_template.text.erb' and 'another_template.html.erb'. The render command is the same one used inside of Action Controller, so you can use all the same options, such as <tt>:text</tt> etc. +This will render the template 'another_template.html.erb' for the HTML part and use the rendered text for the text part. The render command is the same one used inside of Action Controller, so you can use all the same options, such as <tt>:text</tt>, <tt>:inline</tt> etc. h4. Action Mailer Layouts diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index b4ce60fcaa..8937a0c172 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -57,6 +57,7 @@ The methods are: * +group+ * +order+ * +reorder+ +* +reverse_order+ * +limit+ * +offset+ * +joins+ @@ -465,7 +466,7 @@ To apply a +GROUP BY+ clause to the SQL fired by the finder, you can specify the For example, if you want to find a collection of the dates orders were created on: <ruby> -Order.group("date(created_at)").order("created_at") +Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)") </ruby> And this will give you a single +Order+ object for each date where there are orders in the database. @@ -473,7 +474,7 @@ And this will give you a single +Order+ object for each date where there are ord The SQL that would be executed would be something like this: <sql> -SELECT * FROM orders GROUP BY date(created_at) ORDER BY created_at +SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) </sql> h3. Having @@ -483,16 +484,16 @@ SQL uses the +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You For example: <ruby> -Order.group("date(created_at)").having("created_at < ?", 1.month.ago) +Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)").having("sum(price) > ?", 100) </ruby> The SQL that would be executed would be something like this: <sql> -SELECT * FROM orders GROUP BY date(created_at) HAVING created_at < '2011-04-27' +SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) HAVING sum(price) > 100 </sql> -This will return single order objects for each day, but only those that are at least one month old. +This will return single order objects for each day, but only those that are ordered more than $100 in a day. h3. Overriding Conditions @@ -550,6 +551,32 @@ In case the +reorder+ clause is not used, the SQL executed would be: SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC </sql> +h4. +reverse_order+ + +The +reverse_order+ method reverses the ordering clause if specified. + +<ruby> +Client.where("orders_count > 10").order(:name).reverse_order +</ruby> + +The SQL that would be executed: +<sql> +SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC +</sql> + +If no ordering clause is specified in the query, the +reverse_order+ orders by the primary key in reverse order. + +<ruby> +Client.where("orders_count > 10").reverse_order +</ruby> + +The SQL that would be executed: +<sql> +SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC +</sql> + +This method accepts *no* arguments. + h3. Readonly Objects Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. @@ -938,6 +965,47 @@ Using a class method is the preferred way to accept arguments for scopes. These category.posts.1_week_before(time) </ruby> +h4. Working with scopes + +Where a relational object is required, the +scoped+ method may come in handy. This will return an +ActiveRecord::Relation+ object which can have further scoping applied to it afterwards. A place where this may come in handy is on associations + +<ruby> +client = Client.find_by_first_name("Ryan") +orders = client.orders.scoped +</ruby> + +With this new +orders+ object, we are able to ascertain that this object can have more scopes applied to it. For instance, if we wanted to return orders only in the last 30 days at a later point. + +<ruby> +orders.where("created_at > ?", 30.days.ago) +</ruby> + +h4. Applying a default scope + +If we wish for a scope to be applied across all queries to the model we can use the +default_scope+ method within the model itself. + +<ruby> +class Client < ActiveRecord::Base + default_scope where("removed_at IS NULL") +end +</ruby> + +When queries are executed on this model, the SQL query will now look something like this: + +<sql> +SELECT * FROM clients WHERE removed_at IS NULL +</sql> + +h4. Removing all scoping + +If we wish to remove scoping for any reason we can use the +unscoped+ method. This is especially useful if a +default_scope+ is specified in the model and should not be applied for this particular query. + +<ruby> +Client.unscoped.all +</ruby> + +This method removes all scoping and will do a normal query on the table. + h3. Dynamic Finders For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +first_name+ on your +Client+ model for example, you get +find_by_first_name+ and +find_all_by_first_name+ for free from Active Record. If you have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+ methods. @@ -1119,7 +1187,8 @@ For options, please see the parent section, "Calculations":#calculations. h3. Changelog -* December 23 2010: Add documentation for the +scope+ method. "Ryan Bigg":http://ryanbigg.com +* June 26 2011: Added documentation for the +scoped+, +unscoped+ and +default+ methods. "Ryan Bigg":credits.html#radar +* December 23 2010: Add documentation for the +scope+ method. "Ryan Bigg":credits.html#radar * April 7, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * February 3, 2010: Update to Rails 3 by "James Miller":credits.html#bensie * February 7, 2009: Second version by "Pratik":credits.html#lifo diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 221d20fee6..a0ed85cf01 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -947,7 +947,7 @@ h4. Class Attributes h5. +class_attribute+ -The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy: +The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy. <ruby> class A @@ -983,7 +983,7 @@ self.default_params = { }.freeze </ruby> -They can be also accessed and overridden at the instance level: +They can be also accessed and overridden at the instance level. <ruby> A.x = 1 @@ -996,7 +996,7 @@ a1.x # => 1, comes from A a2.x # => 2, overridden in a2 </ruby> -The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to false, as in +The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to +false+. <ruby> module ActiveRecord @@ -1009,8 +1009,20 @@ end A model may find that option useful as a way to prevent mass-assignment from setting the attribute. +The generation of the reader instance method can be prevented by setting the option +:instance_reader+ to +false+. + +<ruby> +class A + class_attribute :x, :instance_reader => false +end + +A.x = 1 # NoMethodError +</ruby> + For convenience +class_attribute+ also defines an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called +x?+. +When +:instance_reader+ is +false+, the instance predicate returns a +NoMethodError+ just like the reader method. + NOTE: Defined in +active_support/core_ext/class/attribute.rb+ h5. +cattr_reader+, +cattr_writer+, and +cattr_accessor+ @@ -1484,7 +1496,15 @@ end That may be handy to compute method names in a language that follows that convention, for example JavaScript. -INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>. +INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>. To support cases such as this, Active Support allows you to specify acronyms in +config/initializers/inflections.rb+: + +<ruby> +ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym 'SSL' +end + +"SSLError".underscore.camelize #=> "SSLError" +</ruby> +camelize+ is aliased to +camelcase+. @@ -2229,8 +2249,8 @@ The method +Array.wrap+ wraps its argument in an array unless it is already an a Specifically: * If the argument is +nil+ an empty list is returned. -* Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned. -* Otherwise, returns an array with the argument as its single element. +* Otherwise, if the argument responds to +to_ary+ it is invoked, and if the value of +to_ary+ is not +nil+, it is returned. +* Otherwise, an array with the argument as its single element is returned. <ruby> Array.wrap(nil) # => [] diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile index 1b444811a2..d2441727ee 100644 --- a/railties/guides/source/asset_pipeline.textile +++ b/railties/guides/source/asset_pipeline.textile @@ -6,104 +6,305 @@ By referring to this guide you will be able to: * Understand what the asset pipeline is and what it does * Properly organize your application assets * Understand the benefits of the asset pipeline -* Adding a preprocessor to the pipeline +* Adding a pre-processor to the pipeline * Package assets with a gem endprologue. h3. What Is The Asset Pipeline? -With Rails 3.1 comes a new feature known as the asset pipeline. The asset pipeline provides features that have usually been implemented by external gems, such as Jammit and Sprockets. These gems would serve concatenated or compressed versions of the assets of an application, such as stylesheets or javascript files so that the number of requests made to the server are lessened, making the page load faster. +The asset pipeline provides a framework to concatenate and minify or compress Javascript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, SCSS and ERB. + +Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 includes the +sprockets-rails+ gem, which depends on the +sprockets+ gem, by default. + +By having this as a core feature of Rails, all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "Fast by default" strategy as outlined by DHH in his 2011 keynote at Railsconf. + +In new Rails 3.1 application the asset pipeline is enable by default. It can be disabled in +application.rb+ by putting this line inside the +Application+ class definition: + +<plain> + config.assets.enabled = false +</plain> + +It is recommended that you use the defaults for all new apps. + + +h4. Main Features + +The first is to concatenate of assets. This is important in a production environment to reduce the number of requests that a client browser has to make to render a web page. While Rails already has a feature to concatenate these types of asset--by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+--, many people do not use it. + +The default behavior in 3.1 and onward is to concatenate all files into one master file each for JS and CSS, however you can separate files or groups of files if required (see below). In production an MD5 fingerprint is inserted into each filename. + +The second feature of the pipeline is to minify or compress. For CSS this usually involves removing whitespace and comments. For Javascript more complex processes can be applied. + +You can choose from a set of built in options or specify your own. + +The third feature is the ability to code these assets using another language, or language extension. These include SCSS or Sass for CSS, CoffeeScript for Javascript, and ERB for both. + +h4. What is fingerprinting and why should I care? + +Fingerprinting is a technique where the filenames of content that is static or infrequently updated is altered to be unique to the content contained in the file. + +When a filename is unique and based on its content, http headers can be set to encourage caches everywhere (at ISPs, in browsers) to keep there own copy of the content. When the content is updated, the fingerprint will change and the remote clients will request the new file. This is generally known as _cachebusting_. + +The most effective technique is to insert a hash of the content into the name, usually at the end. For example a CSS file +global.css+ is hashed and the filename is updated to incorporate the hash. + +<plain> +global.css => global-908e25f4bf641868d8683022a5b62f54.css +</plain> + +This is the strategy adopted by the Rails asset pipeline. + +Rails old strategy was to append a query string to every asset linked with a built-in helper. In the source the generated code looked like this: + +<plain> +/stylesheets/global.css?1309495796 +</plain> + +This has several disadvantages: + +1. Not all caches will cache content with a query string + +"Steve Souders recommends":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/, "...avoiding a querystring for cacheable resources". He found that in these case 5-20% of requests will not be cached. + +2. The filename can change between nodes in multi-server environments. + +The query string in Rails is based on the files mtime (mtime is the file modification time). When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request. + +The other problems is that when static assets are deployed with each new release of code, the mtime of *all* these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. + +Fingerprinting avoids all these problems be ensuring filenames are consistent based on the content. + +More reading: + +* "Optimize caching":http://code.google.com/speed/page-speed/docs/caching.html +* "Revving Filenames: don’t use querystring":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ -By having this now as a core feature of Rails, all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central gem, Sprockets. h3. How to Use the Asset Pipeline -In previous versions of Rails, all assets lived under the +public+ directory in directories such as +images+, +javascripts+ and +stylesheets+. With Rails 3.1, the preferred location for these assets is now the +app/assets+ directory. Files in this directory will be served by the Sprockets middleware included in the sprockets gem. +In previous versions of Rails, all assets were located in subdirectories of +public+ such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory will be served by the Sprockets middleware included in the sprockets gem. -This is not to say that assets can (or should) no longer be placed in +public+. They still can be, they will just be served by the application or the web server which is running the application and served just like normal files. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served. +This is not to say that assets can (or should) no longer be placed in +public+. They still can be and will be served as static files by the application or web server. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served. + +When a scaffold or controller is generated for the application, Rails will also generate a JavaScript file (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and a Cascading Style Sheet file (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller. + +For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You can put any JavaScript or CSS unique to a controller inside their respective asset files. -When a scaffold or controller is generated for the application, Rails will also generate a JavaScript (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and CSS (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller. For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. It's in these files that JavaScript and CSS unique to this part of the application belong. h4. Asset Organization Assets can be placed inside an application in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+. -+app/assets+ is for assets that are owned by the application, such as custom images, javascript files or stylesheets. ++app/assets+ is for assets that are owned by the application, such as custom images, JavaScript files or stylesheets. +lib/assets+ is for your own libraries' code that doesn't really fit into the scope of the application or those libraries which are shared across applications. -+vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins. - -Any subdirectory that exists within these three locations will be added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths will be looked through to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and then served up. - -h4. External Assets ++vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins. -Assets can also come from external sources such as engines. A good example of this is the +jquery_rails+ gem which comes with Rails 3.1 as standard. This gem contains an engine class which inherits from +Rails::Engine+. By doing this, Rails is informed that the directory for this gem may contain assets and the +app/assets+, +lib/assets+ and +vendor/assets+ directories of this engine are added to the search path of Sprockets. +All subdirectories that exists within these three locations will be added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths will be looked through to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and served. -h4. Serving Assets +h4. Coding links to Assets -To serve assets, we can use the same tags that we are generally familiar with: +To access assets, we can use the same tags that we are generally familiar with: <erb> <%= image_tag "rails.png" %> </erb> -Providing that assets are enabled within our application (+Rails.application.config.assets.enabled+ is set to +true+), this file will be served by Sprockets unless a file at +public/images/rails.png+ exists, in which case that file will be served. If there is no file at +public/images+, Sprockets will look through the available paths until it finds a file that matches the name and then will serve it, first looking in the application's assets directories and then falling back to the various engines of the application. +Providing that assets are enabled within our application (+config.assets.enabled+ in the current environment's file is not set to +false+), this file will be served by Sprockets unless a file at +public/assets/rails.png+ exists, in which case that file will be served. Alternatively, a file with an MD5 hash after its name such as +public/assets/rails-af27b6a414e6da00003503148be9b409.png+ will also be picked up by Sprockets. How these hashes are generated is covered in the "Production Assets":#production_assets section later on in this guide. -To include a JavaScript file we can still use the familiar +javascript_include_tag+. +Otherwise, Sprockets will look through the available paths until it finds a file that matches the name and then will serve it, first looking in the application's assets directories and then falling back to the various engines of the application. -<erb> - <%= javascript_include_tag "application" %> -</erb> - -Similarly, to include a CSS file we can also still use +stylesheet_link_tag+. +Sprockets does not add any new methods to require your assets, we still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+. <erb> <%= stylesheet_link_tag "application" %> + <%= javascript_include_tag "application" %> </erb> -These files could just be straight JavaScript or CSS files, or they could be _manifest files_. +These helpers (when the pipeline is on) are providing links to the compiled manifest with the specified name (or names). h4. Manifest Files and Directives -Sprockets allows some assets to be manifest files. These manifest files require what's known as _directives_, which instruct Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets will load the files specified, process them if necessary, concatenate them into one single file and then compress them (if +Rails.application.config.assets.compress+ is set to +true+). By serving one file rather than many, a page's load time can be greatly reduced. +Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ - instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets will load the files specified, process them if necessary, concatenate them into one single file and then compress them (if +Rails.application.config.assets.compress+ is set to +true+). By serving one file rather than many, a page's load time is greatly reduced. For example, in the default Rails application there's a +app/assets/javascripts/application.js+ file which contains the following lines: <plain> - //= require jquery - //= require jquery_ujs - //= require_tree . +//= require jquery +//= require jquery_ujs +//= require_tree . </plain> -In JS files, directives begin with +//=+. In this case, the file is using the +require+ directive twice and the +require_tree+ directive once. The +require+ directive tells Sprockets that we would like to require a file called +jquery.js+ that is available somewhere in the search path for Sprockets. By default, this is located inside the +vendor/assets/javascripts+ directory contained within the +jquery_rails+ gem. An identical event takes place for the +jquery_ujs+ require specified here also. +In JavaScript files, directives begin with +//=+. In this case, the following file is using the +require+ directive and the +require_tree+ directive. The +require+ directive tells Sprockets that we would like to require a file called +jquery.js+ that is available somewhere in the search path for Sprockets. By default, this is located inside the +vendor/assets/javascripts+ directory contained within the +jquery-rails+ gem. An identical event takes place for the +jquery_ujs+ require -The +require_tree .+ directive tells Sprockets to include _all_ JavaScript files in this directory into the output. A path relative to the file can be specified if only certain files are required to be loaded. +The +require_tree .+ directive tells Sprockets to include _all_ JavaScript files in this directory into the output. Only a path relative to the file can be specified. There's also a default +app/assets/stylesheets/application.css+ file which contains these lines: <plain> - /* ... - *= require_self - *= require_tree . - */ +/* ... +*= require_self +*= require_tree . +*/ </plain> -The directives that work in the JavaScript files will also work in stylesheets, obviously requiring stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory. +The directives that work in the JavaScript files will also work in stylesheets, obviously including stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory. In this example +require_self+ is used. This will put the CSS contained within the file (if any) at the top of any other CSS in this file unless +require_self+ is specified after another +require+ directive. +You can have as many manifest files as you need. For example the +admin.css+ and +admin.js+ manifest could contain the JS and CSS files that are used for the admin section of an application. + +For some assets (like CSS) the compiled order is important. You can specify individual files and they will be compiled in the order specified: + +<plain> +/* ... +*= require reset +*= require layout +*= require chrome +*/ +</plain> + + h4. Preprocessing -Based on the extensions of the assets, Sprockets will do preprocessing on the files. With the default gemset that comes with Rails, when a controller or a scaffold is generated, a CoffeeScript file and a SCSS file will be generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and a +app/assets/stylesheets/projects.css.scss+ file. +The file extensions used on an asset will determine what preprocssing will be applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file will be generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and a +app/assets/stylesheets/projects.css.scss+ file. -When these files are requested, they will be processed by the processors provided by the +coffee-script+ and +sass-rails+ gems and then sent back to the browser as JavaScript and SCSS respectively. +When these files are requested, they will be processed by the processors provided by the +coffee-script+ and +sass-rails+ gems and then sent back to the browser as JavaScript and CSS respectively. -In addition to this single layer of pre-processing, we can also put on additional extensions to the end of the file in order for them to be processed using other languages first. For example, we could call our stylesheet +app/assets/stylesheets/projects.css.scss.erb+ it would first be processed as ERB, then SCSS and finally served as CSS. We could also do this with our JavaScript file, calling it +app/assets/javascripts/projects.js.coffee.erb+. +Additional layers of pre-processing can be requested by adding other extensions. These should be used in the order the processing should be applied. For example, a stylesheet called +app/assets/stylesheets/projects.css.scss.erb+ would first be processed as ERB, then SCSS and finally served as CSS. The same applies to a JavaScript file - +app/assets/javascripts/projects.js.coffee.erb+ would be process as ERB, CoffeeScript and served as JavaScript. Keep in mind that the order of these pre-processors is important. For example, if we called our JavaScript file +app/assets/javascripts/projects.js.erb.coffee+ then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore we would run into problems. -h4. Compressing Assets +h3. In Development + +TODO: Talk about: Rack::Cache's caching (used in dev and production. The only difference is hashing and headers). + +In the development environment assets are compiled and cached on the first request after the server is started. Sprockets sets a +must-validate+ cache-control http header to reduce request overhead on subsequent requests - on these the browser gets a 304 (not-modified) response. + +If any of the files in the manifest have changed between requests, the server will respond with a new compiled file. + +h3. In Production + +In the production environment, assets are served slightly differently. + +On the first request the assets are compiled and cached as described above, however the manifest names are altered to include an MD5 hash. Files names typically will look like these: + +<plain> +/assets/application-908e25f4bf641868d8683022a5b62f54.js +/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css +</plain> + +The MD5 is generated from the contents of the compiled files, and is included in the http +Content-MD5+ header. + +Sprockets also sets the +Cache-Control+ http header to +max-age=31536000+. This signals all caches between your server and the client browser that this content (the file served) can be cached for 1 year. The effect of this is to reduce the number of requests for this asset from your server; the asset has a good chance of being in the local browser cache or some intermediate cache. + +This behavior is controlled by the setting of +config.action_controller.perform_caching+ setting in Rails (which is +true+ for production, +false+ for everything else). This value is propagated to Sprockets during initialization for use when action_controller is not available. + +TODO: +describe each and the differences between: + * Sass-rails's handy +image_url+ helpers + * ERB pre-processing and +asset_path+ + +h4. Precompiling assets + +Even though assets are served by Rack::Cache with far-future headers, in high traffic sites this may not be fast enough. + +Rails comes bundled with a rake task to compile the manifests to files on disc. These are located in the +public/assets+ directory where they will be served by your web server instead of the Rails application. + +TODO: Add section about image assets + +The rake task is: + +<erb> +rake assets:precompile +</erb> + +TODO: explain where to use this with Capistrano + +TODO: talk about the +config.assets.precompile+ option and the default matcher for files: + +<erb> +[ /\w+\.(?!js|css).+/, "application.js", "application.css" ] +</erb> + + +Sprockets also creates a "gzip":http://en.wikipedia.org/wiki/Gzip (.gz) of your assets. This prevents your server from contently compressing your assets for each request. You must configure your server to use gzip compression and serve the compressed assets that will be stored in the public/assets folder. The following are some configuration blocks that you can use for common servers. +NGINX & Apache examples? + + + +h3. Customizing The Pipeline + +h4. CSS + +There is currently one option for processing CSS - SCSS. This Gem extends the CSS syntax and offers minification. + +The following line will enable SCSS in you project. + +<erb> +config.assets.css_compressor = :scss +</erb> + +This option is for compression only and does not relate to the SCSS language extensions that apply when using the +.scss+ file extension on CSS assets. + +h4. Javascript + +There are three options available to process javascript - uglifier, closure and yui. + +The default Gemfile includes "uglifier":https://github.com/lautis/uglifier. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compress your code by removing white spaces and other magical things like changing your if and else statements to ternary operators when possible. + +TODO: Add detail about the other two + +The following line will invoke uglifier for Javascript compression. + +<erb> +config.assets.js_compressor = :uglifier +</erb> + + + +h4. Using your own compressor + +The compressor config settings for CSS and Javascript will also take an Object. + +This object must have a +compress+ method that takes a string as the sole argument and it must return a string. + +<erb> +class Transformer + def compress(string) + do_something_returning_a_string(string) + end +end +</erb> + +To enable this pass a +new+ Object to the config option in +application.rb+: + +<erb> +config.assets.css_compressor = Transformer.new +</erb> + + +h4. Changing the _assets_ path + +The public path that Sprockets uses by default is +/assets+. + +This can be changed to something else: + +<erb> +config.assets.prefix = "/some_other_path" +</erb> + +This is a handy option if you have any existing project (pre Rails 3.1) that already uses this path. + + +h3. Adding Assets to Your Gems + +Assets can also come from external sources in the form of gems. + +A good example of this is the +jquery-rails+ gem which comes with Rails as the standard JavaScript library gem. This gem contains an engine class which inherits from +Rails::Engine+. By doing this, Rails is informed that the directory for this gem may contain assets and the +app/assets+, +lib/assets+ and +vendor/assets+ directories of this engine are added to the search path of Sprockets. + +h3. Making Your Library or Gem a Pre-Processor -WIP: Compressed Assets in Rails are served ... how?
\ No newline at end of file +"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh +Tilt: https://github.com/rtomayko/tilt diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index 458bfefad8..3c2497e83a 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1120,11 +1120,9 @@ h6(#has_many-collection-find). <tt><em>collection</em>.find(...)</tt> The <tt><em>collection</em>.find</tt> method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+. <ruby> -@open_orders = @customer.orders.all(:conditions => "open = 1") +@open_orders = @customer.orders.where(:open => 1) </ruby> -NOTE: Starting Rails 3, supplying options to +ActiveRecord::Base.find+ method is discouraged. Use <tt><em>collection</em>.where</tt> instead when you need to pass conditions. - h6(#has_many-collection-where). <tt><em>collection</em>.where(...)</tt> The <tt><em>collection</em>.where</tt> method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index b0edc1017d..9e3b25d794 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -507,7 +507,7 @@ We had to create the *gitapp* directory and initialize an empty git repository b <shell> $ cat config/database.yml -# PostgreSQL. Versions 7.4 and 8.x are supported. +# PostgreSQL. Versions 8.2 and up are supported. # # Install the ruby-postgres driver: # gem install ruby-postgres diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index c7e45c0a23..4314d00f2e 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -777,7 +777,7 @@ If you need to post some data to an external resource it is still great to build Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an +authenticity_token+ hidden field at all. For doing this just pass +false+ to the +:authenticity_token+ option: <erb> -<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %> +<%= form_tag 'http://farfar.away/form', :authenticity_token => false) do %> Form contents <% end %> </erb> diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 3011f7136b..6aca5d3420 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -5,7 +5,7 @@ This guide covers getting up and running with Ruby on Rails. After reading it, y * Installing Rails, creating a new Rails application, and connecting your application to a database * The general layout of a Rails application * The basic principles of MVC (Model, View Controller) and RESTful design -* How to quickly generate the starting pieces of a Rails application. +* How to quickly generate the starting pieces of a Rails application endprologue. @@ -205,8 +205,8 @@ h4. Configuring a Database Just about every Rails application will interact with a database. The database to use is specified in a configuration file, +config/database.yml+. If you open this file in a new Rails application, you'll see a default database configuration using SQLite3. The file contains sections for three different environments in which Rails can run by default: -* The +development+ environment is used on your development computer as you interact manually with the application -* The +test+ environment is used to run automated tests +* The +development+ environment is used on your development computer as you interact manually with the application. +* The +test+ environment is used to run automated tests. * The +production+ environment is used when you deploy your application for the world to use. h5. Configuring an SQLite3 Database @@ -289,7 +289,7 @@ development: database: blog_development username: blog password: -<yaml> +</yaml> Change the username and password in the +development+ section as appropriate. @@ -448,10 +448,10 @@ h4. Adding a Link To hook the posts up to the home page you've already created, you can add a link to the home page. Open +app/views/home/index.html.erb+ and modify it as follows: -<code lang="ruby"> +<ruby> <h1>Hello, Rails!</h1> <%= link_to "My Blog", posts_path %> -</code> +</ruby> The +link_to+ method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path for posts. diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 1d5b0c0c11..340699419b 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -661,9 +661,9 @@ require 'active_support/inflections' require 'active_support/core_ext/string/inflections' </ruby> -The +active_support/inflector/methods+ file has already been required by +active_support/autoload+ and so won't be loaded again here. +The +active_support/inflector/methods+ file has already been required by +active_support/autoload+ and so won't be loaded again here. The +activesupport/lib/active_support/inflector/inflections.rb+ is required by +active_support/inflector/methods+. -h4. +activesupport/lib/active_support/inflector/inflections.rb+ +h4. +active_support/inflections+ This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes". diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index f17f686d47..dbbf8f3b51 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -344,7 +344,7 @@ The +change+ method removes the need to write both +up+ and +down+ methods in th * +add_column+ * +add_index+ -* +add_timestamp+ +* +add_timestamps+ * +create_table+ * +remove_timestamps+ * +rename_column+ diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile index a4187a3135..dbe6f97f5c 100644 --- a/railties/guides/source/performance_testing.textile +++ b/railties/guides/source/performance_testing.textile @@ -151,7 +151,7 @@ Performance tests can be run in two modes: Benchmarking and Profiling. h5. Benchmarking -Benchmarking makes it easy to quickly gather a few metrics about each test tun. By default, each test case is run +4 times+ in benchmarking mode. +Benchmarking makes it easy to quickly gather a few metrics about each test tun. By default, each test case is run *4 times* in benchmarking mode. To run performance tests in benchmarking mode: @@ -161,7 +161,7 @@ $ rake test:benchmark h5. Profiling -Profiling allows you to make an in-depth analysis of each of your tests by using an external profiler. Depending on your Ruby interpreter, this profiler can be native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each test case is run +1 time+ in profiling mode. +Profiling allows you to make an in-depth analysis of each of your tests by using an external profiler. Depending on your Ruby interpreter, this profiler can be native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each test case is run *once* in profiling mode. To run performance tests in profiling mode: diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index 2eb71e49c4..79bbe495bd 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -37,7 +37,7 @@ Use the +rails generate plugin+ command in your Rails root directory directory. See usage and options by asking for help: <shell> -$ rails generate plugin new --help +$ rails generate plugin --help </shell> h4. Or generate a gemified plugin. diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index db9c7545c8..cc55d1f756 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -944,7 +944,7 @@ The built-in +test/unit+ based testing is not the only way to test Rails applica * "Factory Girl":https://github.com/thoughtbot/factory_girl/tree/master, a replacement for fixtures. * "Machinist":https://github.com/notahat/machinist/tree/master, another replacement for fixtures. * "Shoulda":http://www.thoughtbot.com/projects/shoulda, an extension to +test/unit+ with additional helpers, macros, and assertions. -* "RSpec":http://rspec.info/, a behavior-driven development framework +* "RSpec":http://relishapp.com/rspec, a behavior-driven development framework h3. Changelog diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index cca0891835..df92934457 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -87,6 +87,29 @@ module Rails RAILS_CACHE end + # Returns all rails groups for loading based on: + # + # * The Rails environment; + # * The environment variable RAILS_GROUPS; + # * The optional hash given as argument with group dependencies; + # + # == Examples + # + # groups :assets => [:development, :test] + # + # # Returns + # # => [:default, :development, :assets] for Rails.env == "development" + # # => [:default, :production] for Rails.env == "production" + # + def groups(hash={}) + env = Rails.env + groups = [:default, env] + groups.concat ENV["RAILS_GROUPS"].to_s.split(",") + groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) } + groups.compact! + groups + end + def version VERSION::STRING end diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 82775b7e3b..01ceb80972 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -6,6 +6,7 @@ require "rails" action_mailer active_resource rails/test_unit + sprockets ).each do |framework| begin require "#{framework}/railtie" diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 3b74de690a..1a29483a73 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -34,7 +34,7 @@ module Rails @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false @assets.paths = [] - @assets.precompile = [ /\w+\.(?!js|css).+/, "application.js", "application.css" ] + @assets.precompile = [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ] @assets.prefix = "/assets" @assets.js_compressor = nil diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 52c89274e7..eb6fcd5dd7 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -538,15 +538,9 @@ module Rails end initializer :append_assets_path do |app| - if app.config.assets.respond_to?(:prepend_path) - app.config.assets.prepend_path(*paths["vendor/assets"].existent) - app.config.assets.prepend_path(*paths["lib/assets"].existent) - app.config.assets.prepend_path(*paths["app/assets"].existent) - else - app.config.assets.paths.unshift(*paths["vendor/assets"].existent) - app.config.assets.paths.unshift(*paths["lib/assets"].existent) - app.config.assets.paths.unshift(*paths["app/assets"].existent) - end + app.config.assets.paths.unshift(*paths["vendor/assets"].existent) + app.config.assets.paths.unshift(*paths["lib/assets"].existent) + app.config.assets.paths.unshift(*paths["app/assets"].existent) end initializer :prepend_helpers_path do |app| @@ -592,7 +586,7 @@ module Rails end def has_migrations? - paths["db/migrate"].first.present? + paths["db/migrate"].existent.any? end def find_root_with_flag(flag, default=nil) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 355b05ce0b..fdafc91fcb 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -53,7 +53,7 @@ module Rails :helper => true, :integration_tool => nil, :javascripts => true, - :javascript_engine => nil, + :javascript_engine => :js, :orm => false, :performance_tool => nil, :resource_controller => :controller, @@ -299,9 +299,6 @@ module Rails return rescue LoadError => e raise unless e.message =~ /#{Regexp.escape(path)}$/ - rescue NameError => e - raise unless e.message =~ /Rails::Generator([\s(::)]|$)/ - warn "[WARNING] Could not load generator #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}.\n#{e.backtrace.join("\n")}" rescue Exception => e warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index d31a3262e3..433a56dc57 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -114,11 +114,11 @@ module Rails # git :add => "this.file that.rb" # git :add => "onefile.rb", :rm => "badfile.cxx" # - def git(command={}) - if command.is_a?(Symbol) - run "git #{command}" + def git(commands={}) + if commands.is_a?(Symbol) + run "git #{commands}" else - command.each do |command, options| + commands.each do |command, options| run "git #{command} #{options}" end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 1196e2b7eb..bbdd000ad9 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -10,7 +10,7 @@ module Rails module Generators class AppBase < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql ) + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) DATABASES.concat(JDBC_DATABASES) attr_accessor :rails_template @@ -37,6 +37,9 @@ module Rails class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false, :desc => "Skip Active Record files" + class_option :skip_sprockets, :type => :boolean, :aliases => "-S", :default => false, + :desc => "Skip Sprockets files" + class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" @@ -64,8 +67,8 @@ module Rails def initialize(*args) @original_wd = Dir.pwd - super + convert_database_option_for_jruby end protected @@ -124,7 +127,7 @@ module Rails end def include_all_railties? - !options[:skip_active_record] && !options[:skip_test_unit] + !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets] end def comment_if(value) @@ -157,14 +160,26 @@ module Rails when "postgresql" then "pg" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" - when "jdbcmysql" then "activerecord-jdbcmysql-adapter" - when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" - when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" + when "jdbcmysql" then "activerecord-jdbcmysql-adapter" + when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" + when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" + when "jdbc" then "activerecord-jdbc-adapter" else options[:database] end end - def gem_for_ruby_debugger + def convert_database_option_for_jruby + if defined?(JRUBY_VERSION) + case options[:database] + when "oracle" then options[:database].replace "jdbc" + when "postgresql" then options[:database].replace "jdbcpostgresql" + when "mysql" then options[:database].replace "jdbcmysql" + when "sqlite3" then options[:database].replace "jdbcsqlite3" + end + end + end + + def ruby_debugger_gemfile_entry if RUBY_VERSION < "1.9" "gem 'ruby-debug'" else @@ -172,7 +187,7 @@ module Rails end end - def gem_for_turn + def turn_gemfile_entry unless RUBY_VERSION < "1.9.2" || options[:skip_test_unit] <<-GEMFILE.strip_heredoc group :test do @@ -183,7 +198,17 @@ module Rails end end - def gem_for_javascript + def assets_gemfile_entry + <<-GEMFILE.strip_heredoc + group :assets do + gem 'sass-rails', :git => 'git://github.com/rails/sass-rails' + gem 'coffee-rails', :git => 'git://github.com/rails/coffee-rails' + gem 'uglifier' + end + GEMFILE + end + + def javascript_gemfile_entry "gem '#{options[:javascript]}-rails'" unless options[:skip_javascript] end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 1f6a7a2f59..b9dc31457a 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -259,9 +259,9 @@ module Rails extra << false unless Object.method(:const_defined?).arity == 1 # Extract the last Module in the nesting - last = nesting.inject(Object) do |last, nest| - break unless last.const_defined?(nest, *extra) - last.const_get(nest) + last = nesting.inject(Object) do |last_module, nest| + break unless last_module.const_defined?(nest, *extra) + last_module.const_get(nest) end if last && last.const_defined?(last_name.camelize, *extra) diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb new file mode 100644 index 0000000000..d134a9e392 --- /dev/null +++ b/railties/lib/rails/generators/js/assets/assets_generator.rb @@ -0,0 +1,13 @@ +require "rails/generators/named_base" + +module Js + module Generators + class AssetsGenerator < Rails::Generators::NamedBase + source_root File.expand_path("../templates", __FILE__) + + def copy_javascript + copy_file "javascript.js", File.join('app/assets/javascripts', class_path, "#{file_name}.js") + end + end + end +end diff --git a/railties/lib/rails/generators/js/assets/templates/javascript.js b/railties/lib/rails/generators/js/assets/templates/javascript.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/railties/lib/rails/generators/js/assets/templates/javascript.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 242677cc65..6af9d299aa 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -88,6 +88,7 @@ module Rails def lib empty_directory "lib" empty_directory_with_gitkeep "lib/tasks" + empty_directory_with_gitkeep "lib/assets" end def log diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ebe38bf8e6..88eea40b1b 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -5,13 +5,13 @@ source 'http://rubygems.org' <%= database_gemfile_entry -%> <%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%> -# Asset template engines <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> -gem 'sass-rails' -gem 'coffee-script' -gem 'uglifier' -<%= gem_for_javascript %> +# Gems used only for assets and not required +# in production environments by default. +<%= assets_gemfile_entry %> + +<%= javascript_gemfile_entry %> # Use unicorn as the web server # gem 'unicorn' @@ -20,6 +20,6 @@ gem 'uglifier' # gem 'capistrano' # To use debugger -# <%= gem_for_ruby_debugger %> +# <%= ruby_debugger_gemfile_entry %> -<%= gem_for_turn -%> +<%= turn_gemfile_entry -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 37c2fb1263..eaa31e7386 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -8,12 +8,14 @@ require 'rails/all' require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" +<%= comment_if :skip_sprockets %> require "sprockets/railtie" <%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" <% end -%> -# If you have a Gemfile, require the gems listed there, including any gems -# you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) if defined?(Bundler) +# If you have a Gemfile, require the default gems, the ones in the +# current environment and also include :assets gems if in development +# or test environments. +Bundler.require *Rails.groups(:assets => %w(development test)) if defined?(Bundler) module <%= app_const_base %> class Application < Rails::Application @@ -45,7 +47,9 @@ module <%= app_const_base %> # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] + <% unless options.skip_sprockets? %> # Enable the asset pipeline config.assets.enabled = true + <% end %> end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml new file mode 100644 index 0000000000..1d2bf08b91 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -0,0 +1,62 @@ +# If you are using mssql, derby, hsqldb, or h2 with one of the +# ActiveRecord JDBC adapters, install the appropriate driver, e.g.,: +# gem install activerecord-jdbcmssql-adapter +# +# Configure using Gemfile: +# gem 'activerecord-jdbcmssql-adapter' +# +#development: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_development +# +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +# +#test: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_test +# +#production: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_production + +# If you are using oracle, db2, sybase, informix or prefer to use the plain +# JDBC adapter, configure your database setting as the example below (requires +# you to download and manually install the database vendor's JDBC driver .jar +# file). See your driver documentation for the apropriate driver class and +# connection string: + +development: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. + +test: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_test + +production: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_production diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index 6bf83e86a5..5a594ac1f3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -9,7 +9,7 @@ # And be sure to use new-style password hashing: # http://dev.mysql.com/doc/refman/5.0/en/old-client.html development: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_development username: root password: @@ -19,14 +19,14 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_test username: root password: host: localhost production: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_production username: root password: diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 0c7f45322b..fe9466b366 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -1,19 +1,10 @@ -# PostgreSQL. Versions 7.4 and 8.x are supported. -# -# Install the pg driver: -# gem install pg -# On Mac OS X with macports: -# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. +# PostgreSQL. Versions 8.2 and up are supported. # # Configure Using Gemfile # gem 'activerecord-jdbcpostgresql-adapter' development: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_development username: <%= app_name %> @@ -38,14 +29,14 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_test username: <%= app_name %> password: production: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_production username: <%= app_name %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml index 6d241d57ae..175f3eb3db 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -5,16 +5,16 @@ # gem 'activerecord-jdbcsqlite3-adapter' # development: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/development.sqlite3 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/test.sqlite3 production: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index 467dfc3956..f08f86aac3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 7.4 and 8.x are supported. +# PostgreSQL. Versions 8.2 and up are supported. # # Install the pg driver: # gem install pg diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index ffd9058238..3e0c29a587 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -22,6 +22,11 @@ # Only use best-standards-support built into browsers config.action_dispatch.best_standards_support = :builtin + <%- unless options.skip_active_record? -%> + # Raise exception on mass assignment protection for ActiveRecord models + config.active_record.mass_assignment_sanitizer = :strict + <%- end -%> + # Do not compress assets config.assets.compress = false end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index ee068b0202..80198cc21e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -34,6 +34,11 @@ # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql + <%- unless options.skip_active_record? -%> + # Raise exception on mass assignment protection for ActiveRecord models + config.active_record.mass_assignment_sanitizer = :strict + <%- end -%> + # Print deprecation notices to the stderr config.active_support.deprecation = :stderr end diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb index db3422fe83..2e7f25a0b7 100644 --- a/railties/lib/rails/generators/rails/assets/assets_generator.rb +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -7,21 +7,14 @@ module Rails class_option :javascript_engine, :desc => "Engine for JavaScripts" class_option :stylesheet_engine, :desc => "Engine for Stylesheets" - def create_javascript_files - return unless options.javascripts? - copy_file "javascript.#{javascript_extension}", - File.join('app/assets/javascripts', class_path, "#{asset_name}.#{javascript_extension}") - end - protected def asset_name file_name end - def javascript_extension - options.javascript_engine.present? ? - "js.#{options.javascript_engine}" : "js" + hook_for :javascript_engine do |javascript_engine| + invoke javascript_engine, [name] if options[:javascripts] end hook_for :stylesheet_engine do |stylesheet_engine| diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee deleted file mode 100644 index 761567942f..0000000000 --- a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index c28e568711..7e6eb18341 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -7,9 +7,8 @@ source "http://rubygems.org" <% end -%> <% if mountable? -%> -<%= gem_for_javascript -%> +<%= javascript_gemfile_entry -%> <% end -%> -if RUBY_VERSION < '1.9' - gem "ruby-debug", ">= 0.10.3" -end +# To use debugger +# <%= ruby_debugger_gemfile_entry %>
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb index 8b68280a5e..4864ead960 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb @@ -1,13 +1,15 @@ require File.expand_path('../boot', __FILE__) -<% unless options[:skip_active_record] -%> +<% if include_all_railties? -%> require 'rails/all' <% else -%> -# require "active_record/railtie" +# Pick the frameworks you want: +<%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" -require "rails/test_unit/railtie" +<%= comment_if :skip_sprockets %> require "sprockets/railtie" +<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" <% end -%> Bundler.require diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb index e7a06e4a73..dea7e22196 100644 --- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb +++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb @@ -1,8 +1,6 @@ require 'test_helper' class <%= class_name %>Test < ActionDispatch::IntegrationTest - fixtures :all - # test "the truth" do # assert true # end diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index d05e031f56..a1e15092b2 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -78,6 +78,10 @@ module Rails Rails::VERSION::STRING end + property 'JavaScript Runtime' do + ExecJS.runtime.name + end + # Versions of each Rails framework (Active Record, Action Pack, # Active Resource, Action Mailer, and Active Support). frameworks.each do |framework| diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index 166d518f7c..9807000578 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -3,7 +3,6 @@ $VERBOSE = nil # Load Rails rakefile extensions %w( annotations - assets documentation framework log diff --git a/railties/lib/rails/tasks/assets.rake b/railties/lib/rails/tasks/assets.rake deleted file mode 100644 index 4678ea460e..0000000000 --- a/railties/lib/rails/tasks/assets.rake +++ /dev/null @@ -1,21 +0,0 @@ -namespace :assets do - desc "Compile all the assets named in config.assets.precompile" - task :precompile => :environment do - # Give assets access to asset_path - Sprockets::Helpers::RailsHelper - - assets = Rails.application.config.assets.precompile - Rails.application.assets.precompile(*assets) - end - - desc "Remove compiled assets" - task :clean => :environment do - assets = Rails.application.config.assets - public_asset_path = Rails.public_path + assets.prefix - file_list = FileList.new("#{public_asset_path}/*.js", "#{public_asset_path}/*.css") - file_list.each do |file| - rm file - rm "#{file}.gz", :force => true - end - end -end diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index 2f0e7be896..2152e811f5 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -67,3 +67,5 @@ def find_engine_path(path) find_engine_path(File.expand_path('..', path)) end end + +Rake.application.invoke_task(:load_app) diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 28dc40379b..b9877a83b5 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -79,10 +79,14 @@ task :test do Rake::Task[task].invoke nil rescue => e - task + { :task => task, :exception => e } end end.compact - abort "Errors running #{errors * ', '}!" if errors.any? + + if errors.any? + puts errors.map { |e| "Errors running #{e[:task]}! #{e[:exception].inspect}" }.join("\n") + abort + end end namespace :test do diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index 3d6ecb9d30..254227ecf7 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -1,9 +1,9 @@ module Rails module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 9c535f4f90..b917128885 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -15,6 +15,9 @@ Gem::Specification.new do |s| s.files = Dir['CHANGELOG', 'README.rdoc', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] s.require_path = 'lib' + s.bindir = 'bin' + s.executables = ['rails'] + s.rdoc_options << '--exclude' << '.' s.add_dependency('rake', '>= 0.8.7') diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 2b593005a2..e1eee71a9e 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -1,8 +1,9 @@ require 'isolation/abstract_unit' +require 'active_support/core_ext/kernel/reporting' require 'rack/test' module ApplicationTests - class RoutingTest < Test::Unit::TestCase + class AssetsTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation include Rack::Test::Methods @@ -34,6 +35,45 @@ module ApplicationTests assert_match "alert()", last_response.body end + test "assets do not require compressors until it is used" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + ENV["RAILS_ENV"] = "production" + require "#{app_path}/config/environment" + + assert !defined?(Uglifier) + get "/assets/demo.js" + assert_match "alert()", last_response.body + assert defined?(Uglifier) + end + + test "assets are compiled properly" do + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/javascripts/foo/application.js", "alert();" + + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:precompile` } + end + files = Dir["#{app_path}/public/assets/application-*.js"] + files << Dir["#{app_path}/public/assets/foo/application-*.js"].first + files.each do |file| + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();\n", File.read(file) + end + end + + test "assets are cleaned up properly" do + app_file "public/assets/application.js", "alert();" + app_file "public/assets/application.css", "a { color: green; }" + app_file "public/assets/subdir/broken.png", "not really an image file" + + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:clean` } + end + + files = Dir["#{app_path}/public/assets/**/*"] + assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}" + end + test "does not stream session cookies back" do app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 477dada820..2547863d12 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -39,6 +39,21 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) end + test "Rails.groups returns available groups" do + require "rails" + + Rails.env = "development" + assert_equal [:default, "development"], Rails.groups + assert_equal [:default, "development", :assets], Rails.groups(:assets => [:development]) + assert_equal [:default, "development", :assets], Rails.groups(:assets => %w(development)) + + Rails.env = "test" + assert_equal [:default, "test"], Rails.groups(:assets => [:development]) + + ENV["RAILS_GROUPS"] = "javascripts,stylesheets" + assert_equal [:default, "test", "javascripts", "stylesheets"], Rails.groups + end + test "Rails.application is nil until app is initialized" do require 'rails' assert_nil Rails.application diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 44ac6615de..b61e2851bf 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -59,6 +59,32 @@ module ApplicationTests Dir.chdir(app_path){ `rake stats` } end + def test_rake_test_error_output + Dir.chdir(app_path){ `rake db:migrate` } + + app_file "config/database.yml", <<-RUBY + development: + RUBY + + app_file "test/unit/one_unit_test.rb", <<-RUBY + RUBY + + app_file "test/functional/one_functional_test.rb", <<-RUBY + raise RuntimeError + RUBY + + app_file "test/integration/one_integration_test.rb", <<-RUBY + raise RuntimeError + RUBY + + silence_stderr do + output = Dir.chdir(app_path){ `rake test` } + assert_match /Errors running test:units! #<ActiveRecord::AdapterNotSpecified/, output + assert_match /Errors running test:functionals! #<RuntimeError/, output + assert_match /Errors running test:integration! #<RuntimeError/, output + end + end + def test_rake_routes_output_strips_anchors_from_http_verbs app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do diff --git a/railties/test/fixtures/lib/generators/wrong_generator.rb b/railties/test/fixtures/lib/generators/wrong_generator.rb deleted file mode 100644 index 6aa7cb052e..0000000000 --- a/railties/test/fixtures/lib/generators/wrong_generator.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Old generator version -class WrongGenerator < Rails::Generator::NamedBase -end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index c31c65a27d..81f0bf5e82 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -10,6 +10,7 @@ DEFAULT_APP_FILES = %w( config.ru app/assets/javascripts app/assets/stylesheets + app/assets/images app/controllers app/helpers app/mailers @@ -22,8 +23,8 @@ DEFAULT_APP_FILES = %w( doc lib lib/tasks + lib/assets log - app/assets/images script/rails test/fixtures test/functional @@ -47,11 +48,12 @@ class AppGeneratorTest < Rails::Generators::TestCase ::DEFAULT_APP_FILES end - def test_application_controller_and_layout_files + def test_assets run_generator assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application"/ assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/ assert_file "app/assets/stylesheets/application.css" + assert_file "config/application.rb", /config\.assets\.enabled = true/ end def test_invalid_application_name_raises_an_error @@ -123,18 +125,26 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_database_is_added_by_default run_generator assert_file "config/database.yml", /sqlite3/ - assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ + unless defined?(JRUBY_VERSION) + assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ + else + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ + end end def test_config_another_database run_generator([destination_root, "-d", "mysql"]) assert_file "config/database.yml", /mysql/ - assert_file "Gemfile", /^gem\s+["']mysql2["']$/ + unless defined?(JRUBY_VERSION) + assert_file "Gemfile", /^gem\s+["']mysql2["']$/ + else + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ + end end def test_config_jdbcmysql_database run_generator([destination_root, "-d", "jdbcmysql"]) - assert_file "config/database.yml", /jdbcmysql/ + assert_file "config/database.yml", /mysql/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ # TODO: When the JRuby guys merge jruby-openssl in # jruby this will be removed @@ -143,28 +153,48 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_jdbcsqlite3_database run_generator([destination_root, "-d", "jdbcsqlite3"]) - assert_file "config/database.yml", /jdbcsqlite3/ + assert_file "config/database.yml", /sqlite3/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ end def test_config_jdbcpostgresql_database run_generator([destination_root, "-d", "jdbcpostgresql"]) - assert_file "config/database.yml", /jdbcpostgresql/ + assert_file "config/database.yml", /postgresql/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/ end + def test_config_jdbc_database + run_generator([destination_root, "-d", "jdbc"]) + assert_file "config/database.yml", /jdbc/ + assert_file "config/database.yml", /mssql/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbc-adapter["']$/ + end + + def test_config_jdbc_database_when_no_option_given + if defined?(JRUBY_VERSION) + run_generator([destination_root]) + assert_file "config/database.yml", /sqlite3/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ + end + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" + assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ assert_file "test/test_helper.rb" do |helper_content| assert_no_match(/fixtures :all/, helper_content) end assert_file "test/performance/browsing_test.rb" end - def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given - run_generator [destination_root, "--skip-active-record"] - assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + def test_generator_if_skip_active_record_is_given + run_generator [destination_root, "--skip-sprockets"] + assert_file "config/application.rb" do |content| + assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) + assert_no_match(/config\.assets\.enabled = true/, content) + end + assert_file "test/performance/browsing_test.rb" end def test_creation_of_a_test_directory diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb index 2d20982d04..044e0b6bc6 100644 --- a/railties/test/generators/assets_generator_test.rb +++ b/railties/test/generators/assets_generator_test.rb @@ -8,19 +8,13 @@ class AssetsGeneratorTest < Rails::Generators::TestCase def test_assets run_generator - assert_file "app/assets/javascripts/posts.js.coffee" + assert_file "app/assets/javascripts/posts.js" assert_file "app/assets/stylesheets/posts.css" end def test_skipping_assets content = run_generator ["posts", "--no-stylesheets", "--no-javascripts"] - assert_no_file "app/assets/javascripts/posts.js.coffee" + assert_no_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end - - def test_vanilla_assets - run_generator ["posts", "--no-javascript-engine"] - assert_file "app/assets/javascripts/posts.js" - assert_file "app/assets/stylesheets/posts.css" - end end diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 3adf7be118..c3fa9ebb03 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -39,7 +39,7 @@ class ControllerGeneratorTest < Rails::Generators::TestCase def test_invokes_assets run_generator - assert_file "app/assets/javascripts/account.js.coffee" + assert_file "app/assets/javascripts/account.js" assert_file "app/assets/stylesheets/account.css" end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index be72391e58..4bd77ff7e3 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -159,6 +159,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end + def test_ensure_that_migration_tasks_work_with_mountable_option + run_generator [destination_root, "--mountable"] + FileUtils.cd destination_root + quietly { system 'bundle install' } + `bundle exec rake db:migrate` + assert_equal 0, $?.exitstatus + end + def test_creating_engine_in_full_mode run_generator [destination_root, "--full"] assert_file "app/assets/javascripts/bukkits" diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index c3c6015291..2db8090621 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -81,7 +81,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css" - assert_file "app/assets/javascripts/product_lines.js.coffee" + assert_file "app/assets/javascripts/product_lines.js" assert_file "app/assets/stylesheets/product_lines.css" end @@ -114,7 +114,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css", /:visited/ - assert_no_file "app/assets/javascripts/product_lines.js.coffee" + assert_no_file "app/assets/javascripts/product_lines.js" assert_no_file "app/assets/stylesheets/product_lines.css" end @@ -190,7 +190,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css", /:visited/ - assert_file "app/assets/javascripts/admin/roles.js.coffee" + assert_file "app/assets/javascripts/admin/roles.js" assert_file "app/assets/stylesheets/admin/roles.css" end @@ -224,7 +224,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css" - assert_no_file "app/assets/javascripts/admin/roles.js.coffee" + assert_no_file "app/assets/javascripts/admin/roles.js" assert_no_file "app/assets/stylesheets/admin/roles.css" end @@ -246,28 +246,21 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase def test_scaffold_generator_no_assets run_generator [ "posts", "--no-assets" ] assert_file "app/assets/stylesheets/scaffold.css" - assert_no_file "app/assets/javascripts/posts.js.coffee" + assert_no_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end def test_scaffold_generator_no_stylesheets run_generator [ "posts", "--no-stylesheets" ] assert_no_file "app/assets/stylesheets/scaffold.css" - assert_file "app/assets/javascripts/posts.js.coffee" + assert_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end def test_scaffold_generator_no_javascripts run_generator [ "posts", "--no-javascripts" ] assert_file "app/assets/stylesheets/scaffold.css" - assert_no_file "app/assets/javascripts/posts.js.coffee" - assert_file "app/assets/stylesheets/posts.css" - end - - def test_scaffold_generator_no_engines - run_generator [ "posts", "--no-javascript-engine" ] - assert_file "app/assets/stylesheets/scaffold.css" - assert_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/javascripts/posts.js" assert_file "app/assets/stylesheets/posts.css" end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 301ae80bcf..56329f3183 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -88,12 +88,6 @@ class GeneratorsTest < Rails::Generators::TestCase assert Rails::Generators.find_by_namespace(:model) end - def test_find_by_namespace_show_warning_if_generator_cant_be_loaded - output = capture(:stderr) { Rails::Generators.find_by_namespace(:wrong) } - assert_match(/\[WARNING\] Could not load generator/, output) - assert_match(/Rails 2\.x generator/, output) - end - def test_invoke_with_nested_namespaces model_generator = mock('ModelGenerator') do expects(:start).with(["Account"], {}) diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 0a203fd4d0..685d1b154b 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -238,6 +238,10 @@ module TestHelpers end end + def remove_file(path) + FileUtils.rm_rf "#{app_path}/#{path}" + end + def controller(name, contents) app_file("app/controllers/#{name}_controller.rb", contents) end diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb index fe59dcd52b..1d4ba0e5b6 100644 --- a/railties/test/railties/generators_test.rb +++ b/railties/test/railties/generators_test.rb @@ -1,7 +1,8 @@ RAILS_ISOLATED_ENGINE = true require "isolation/abstract_unit" -require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/rails/generators/test_case" +require 'generators/generators_test_helper' +require "rails/generators/test_case" module RailtiesTests class GeneratorTest < Rails::Generators::TestCase @@ -31,8 +32,8 @@ module RailtiesTests end def build_engine(is_mountable=false) + FileUtils.rm_rf(engine_path) FileUtils.mkdir_p(engine_path) - FileUtils.rm_r(engine_path) mountable = is_mountable ? "--mountable" : "" diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index 8cbc76db71..9a64b7c64e 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -84,6 +84,15 @@ module RailtiesTest end end + def test_no_rake_task_without_migrations + boot_rails + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + Rails.application.load_tasks + assert !Rake::Task.task_defined?('bukkits:install:migrations') + end + def test_puts_its_lib_directory_on_load_path boot_rails require "another" diff --git a/version.rb b/version.rb index 3d6ecb9d30..254227ecf7 100644 --- a/version.rb +++ b/version.rb @@ -1,9 +1,9 @@ module Rails module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end |