aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile3
-rw-r--r--RAILS_VERSION2
-rw-r--r--actionmailer/lib/action_mailer/version.rb4
-rw-r--r--actionpack/CHANGELOG8
-rw-r--r--actionpack/actionpack.gemspec4
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb9
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb15
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb5
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb4
-rw-r--r--actionpack/lib/action_view/asset_paths.rb106
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb23
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb1
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb11
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb27
-rw-r--r--actionpack/lib/action_view/log_subscriber.rb2
-rw-r--r--actionpack/lib/sprockets/assets.rake (renamed from railties/lib/rails/tasks/assets.rake)0
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb61
-rw-r--r--actionpack/lib/sprockets/railtie.rb10
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb8
-rw-r--r--actionpack/test/controller/mime_responds_test.rb28
-rw-r--r--actionpack/test/controller/send_file_test.rb19
-rw-r--r--actionpack/test/controller/test_test.rb7
-rw-r--r--actionpack/test/dispatch/cookies_test.rb9
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb8
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource.js.erb1
-rw-r--r--actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css1
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb38
-rw-r--r--actionpack/test/template/form_helper_test.rb125
-rw-r--r--actionpack/test/template/form_options_helper_test.rb7
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb83
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb15
-rw-r--r--activemodel/lib/active_model/serializers/json.rb22
-rw-r--r--activemodel/lib/active_model/version.rb4
-rw-r--r--activemodel/test/cases/dirty_test.rb9
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb14
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb53
-rw-r--r--activemodel/test/models/mass_assignment_specific.rb10
-rw-r--r--activerecord/CHANGELOG12
-rw-r--r--activerecord/activerecord.gemspec4
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb36
-rw-r--r--activerecord/lib/active_record/associations/association.rb9
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb6
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb3
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb5
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb5
-rw-r--r--activerecord/lib/active_record/base.rb85
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb2
-rw-r--r--activerecord/lib/active_record/named_scope.rb19
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake13
-rw-r--r--activerecord/lib/active_record/reflection.rb46
-rw-r--r--activerecord/lib/active_record/relation.rb5
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb16
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/version.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb18
-rw-r--r--activerecord/test/cases/base_test.rb13
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb7
-rw-r--r--activerecord/test/cases/reflection_test.rb2
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb26
-rw-r--r--activerecord/test/cases/relation_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb7
-rw-r--r--activerecord/test/models/bulb.rb9
-rw-r--r--activerecord/test/models/developer.rb6
-rw-r--r--activeresource/lib/active_resource/version.rb4
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support/configurable.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb145
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb105
-rw-r--r--activesupport/lib/active_support/ordered_options.rb4
-rw-r--r--activesupport/lib/active_support/version.rb4
-rw-r--r--activesupport/test/configurable_test.rb22
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb5
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb7
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb4
-rw-r--r--activesupport/test/inflector_test.rb86
-rw-r--r--activesupport/test/inflector_test_cases.rb3
-rw-r--r--activesupport/test/time_zone_test.rb8
-rw-r--r--ci/cruise_config.rb12
-rw-r--r--ci/site_config.rb6
-rw-r--r--railties/CHANGELOG2
-rwxr-xr-xrailties/bin/rails2
-rw-r--r--railties/guides/source/active_support_core_extensions.textile14
-rw-r--r--railties/guides/source/initialization.textile4
-rw-r--r--railties/lib/rails/all.rb1
-rw-r--r--railties/lib/rails/engine.rb14
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb15
-rw-r--r--railties/lib/rails/generators/js/assets/assets_generator.rb13
-rw-r--r--railties/lib/rails/generators/js/assets/templates/javascript.js2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb3
-rw-r--r--railties/lib/rails/generators/rails/assets/assets_generator.rb11
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee3
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb8
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/tasks.rb1
-rw-r--r--railties/lib/rails/test_unit/testing.rake8
-rw-r--r--railties/lib/rails/version.rb4
-rw-r--r--railties/railties.gemspec3
-rw-r--r--railties/test/application/rake_test.rb26
-rw-r--r--railties/test/generators/app_generator_test.rb29
-rw-r--r--railties/test/generators/assets_generator_test.rb10
-rw-r--r--railties/test/generators/controller_generator_test.rb2
-rw-r--r--railties/test/generators/scaffold_generator_test.rb21
-rw-r--r--railties/test/railties/generators_test.rb3
-rw-r--r--railties/test/railties/shared_tests.rb9
-rw-r--r--version.rb4
130 files changed, 1257 insertions, 601 deletions
diff --git a/Gemfile b/Gemfile
index ef9f613955..4b53aeb42f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,7 +5,7 @@ gemspec
if ENV['AREL']
gem "arel", :path => ENV['AREL']
else
- gem "arel", '~> 2.1.0'
+ gem "arel", '~> 2.1.3'
end
gem "coffee-script"
@@ -19,6 +19,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
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
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 16b1e78b4d..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('tzinfo', '~> 0.3.29')
s.add_dependency('erubis', '~> 2.7.0')
end
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/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/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_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/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 2b1fe545a6..4dd02755d3 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -4,7 +4,7 @@ module ActionView
class AssetPaths #:nodoc:
attr_reader :config, :controller
- def initialize(config, controller)
+ def initialize(config, controller = nil)
@config = config
@controller = controller
end
@@ -13,21 +13,29 @@ module ActionView
# 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)
+ #
+ # 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)
-
- if controller && include_host
- has_request = controller.respond_to?(:request)
- source = rewrite_host_and_protocol(source, has_request)
- end
-
+ 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
@@ -46,13 +54,45 @@ module ActionView
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
+ def has_request?
+ controller.respond_to?(:request)
+ end
+
+ def rewrite_host_and_protocol(source, protocol = nil)
host = compute_asset_host(source)
- if has_request && host && !is_uri?(host)
- host = "#{controller.request.protocol}#{host}"
+ 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
- "#{host}#{source}"
+ 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,
@@ -61,19 +101,45 @@ module ActionView
# 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 = asset_host_config
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)
+ 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_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 12a304b395..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,3 +1,4 @@
+require 'thread'
require 'active_support/core_ext/file'
module ActionView
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/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 0ef2357368..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,13 +1229,6 @@ 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) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
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/railties/lib/rails/tasks/assets.rake b/actionpack/lib/sprockets/assets.rake
index 0236350576..0236350576 100644
--- a/railties/lib/rails/tasks/assets.rake
+++ b/actionpack/lib/sprockets/assets.rake
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
index 9f1f0f3b68..7d709de9e2 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers"
+require "action_view"
module Sprockets
module Helpers
@@ -10,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
@@ -50,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
@@ -58,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
+
+ # 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:
- def compute_public_path(source, dir, ext=nil, include_host=true)
- super(source, Rails.application.config.assets.prefix, ext, include_host)
+ 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
@@ -98,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 ab5101f6fc..d1e97c594e 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -1,16 +1,12 @@
module Sprockets
autoload :Helpers, "sprockets/helpers"
+ # 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
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/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/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/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 bf65a9359b..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)
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/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..daadc8aa81 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -224,6 +224,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..224e0095d9 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -423,12 +423,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/base.rb b/activerecord/lib/active_record/base.rb
index 6a5d282973..3b8df4f2f1 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 }
@@ -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 090f6bc6fe..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
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 6d638628df..29dd0a9ea3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -940,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}" }
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/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index c98abab091..2eeff7e36f 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -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/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 83909b0766..399f3c5d03 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -377,8 +377,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 +422,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|
@@ -453,9 +452,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..9cd146b857 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -6,7 +6,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 +29,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 +404,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/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index aabe5c269b..9c5f603c01 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -146,10 +146,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 +202,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 +251,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/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 7fbbefeea2..ec2e4d4dcd 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
@@ -307,7 +307,7 @@ module ActiveRecord
order_query.map do |o|
case o
- when Arel::Nodes::Ordering
+ when Arel::Nodes::Ascending, Arel::Nodes::Descending
o.reverse
when String, Symbol
o.to_s.split(',').collect do |s|
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/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 9af1f7249f..7e331658a3 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1557,4 +1557,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_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/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/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 aa94c9b87e..5a95e68eb4 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -176,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
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/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/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/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 07cb2187e3..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
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/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/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/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..0360cf2901 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -353,6 +353,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/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/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 e8b4a32056..a9b4deb551 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -8,6 +8,8 @@
*Rails 3.1.0 (unreleased)*
+* Make sprockets/railtie require explicit and add --skip-sprockets to app generator [José Valim]
+
* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim]
* The new rake task assets:clean removes precompiled assets. [fxn]
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/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 7f33cc4df5..a0ed85cf01 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -1496,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+.
@@ -2241,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/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/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/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 ea9f023511..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,
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 378eceb4c3..bbdd000ad9 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -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('/')})"
@@ -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)
@@ -195,6 +198,16 @@ module Rails
end
end
+ 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/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 cd674ddbdd..88eea40b1b 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -9,11 +9,7 @@ source 'http://rubygems.org'
# Gems used only for assets and not required
# in production environments by default.
-group :assets do
- gem 'sass-rails'
- gem 'coffee-script'
- gem 'uglifier'
-end
+<%= assets_gemfile_entry %>
<%= javascript_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 7f623a7af9..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,6 +8,7 @@ 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 -%>
@@ -46,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/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/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/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/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/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/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 03d185165f..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,13 +125,21 @@ 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
@@ -171,15 +181,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
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/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/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index fe59dcd52b..08dd1ebbc2 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
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