aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile8
-rw-r--r--actionmailer/lib/action_mailer/base.rb3
-rw-r--r--actionmailer/lib/action_mailer/old_api.rb2
-rw-r--r--actionpack/CHANGELOG6
-rw-r--r--actionpack/lib/abstract_controller/base.rb38
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb4
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb6
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb28
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb11
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb16
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb5
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb9
-rw-r--r--actionpack/lib/action_view.rb1
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb51
-rw-r--r--actionpack/lib/action_view/lookup_context.rb10
-rw-r--r--actionpack/lib/action_view/path_set.rb2
-rw-r--r--actionpack/lib/action_view/template/error.rb2
-rw-r--r--actionpack/lib/action_view/template/resolver.rb62
-rw-r--r--actionpack/lib/sprockets/railtie.rb2
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb8
-rw-r--r--actionpack/test/dispatch/request_test.rb7
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb4
-rw-r--r--actionpack/test/template/capture_helper_test.rb16
-rw-r--r--actionpack/test/template/date_helper_test.rb59
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb1
-rw-r--r--actionpack/test/template/lookup_context_test.rb14
-rw-r--r--actionpack/test/template/render_test.rb2
-rw-r--r--activemodel/CHANGELOG4
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb46
-rw-r--r--activemodel/lib/active_model/observer_array.rb45
-rw-r--r--activemodel/lib/active_model/observing.rb9
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb4
-rw-r--r--activerecord/CHANGELOG35
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb55
-rw-r--r--activerecord/lib/active_record/base.rb99
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb11
-rw-r--r--activerecord/lib/active_record/fixtures.rb575
-rw-r--r--activerecord/lib/active_record/identity_map.rb42
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake18
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb7
-rw-r--r--activerecord/lib/active_record/session_store.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb50
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb29
-rw-r--r--activerecord/test/cases/base_test.rb135
-rw-r--r--activerecord/test/cases/finder_test.rb21
-rw-r--r--activerecord/test/cases/fixtures_test.rb42
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/identity_map_test.rb35
-rw-r--r--activerecord/test/cases/lifecycle_test.rb25
-rw-r--r--activerecord/test/cases/locking_test.rb36
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb58
-rw-r--r--activerecord/test/cases/persistence_test.rb4
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb4
-rw-r--r--activerecord/test/cases/session_store/session_test.rb6
-rw-r--r--activerecord/test/fixtures/mateys.yml4
-rw-r--r--activerecord/test/fixtures/parrots_pirates.yml8
-rw-r--r--activerecord/test/fixtures/string_key_objects.yml7
-rw-r--r--activerecord/test/models/string_key_object.rb3
-rw-r--r--activerecord/test/schema/schema.rb6
-rw-r--r--activeresource/lib/active_resource.rb2
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/configurable.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb1
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb16
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb8
-rw-r--r--activesupport/lib/active_support/json/encoding.rb4
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb1
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb4
-rw-r--r--activesupport/lib/active_support/xml_mini.rb5
-rw-r--r--activesupport/test/class_cache_test.rb2
-rw-r--r--activesupport/test/configurable_test.rb7
-rw-r--r--activesupport/test/core_ext/duration_test.rb1
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb25
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb1
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb7
-rw-r--r--activesupport/test/descendants_tracker_test_cases.rb (renamed from activesupport/test/descendants_tracker_test.rb)36
-rw-r--r--activesupport/test/descendants_tracker_with_autoloading_test.rb35
-rw-r--r--activesupport/test/descendants_tracker_without_autoloading_test.rb8
-rw-r--r--activesupport/test/inflector_test_cases.rb30
-rw-r--r--activesupport/test/json/encoding_test.rb33
-rw-r--r--activesupport/test/multibyte_chars_test.rb1
-rw-r--r--activesupport/test/ordered_hash_test.rb5
-rw-r--r--activesupport/test/xml_mini_test.rb (renamed from activesupport/test/test_xml_mini.rb)20
-rw-r--r--railties/CHANGELOG12
-rw-r--r--railties/guides/source/security.textile10
-rw-r--r--railties/lib/rails/application.rb6
-rw-r--r--railties/lib/rails/application/bootstrap.rb3
-rw-r--r--railties/lib/rails/commands.rb3
-rw-r--r--railties/lib/rails/commands/dbconsole.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb22
-rw-r--r--railties/lib/rails/commands/server.rb3
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb8
-rw-r--r--railties/lib/rails/generators/named_base.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb15
-rw-r--r--railties/test/application/assets_test.rb27
-rw-r--r--railties/test/application/configuration_test.rb30
-rw-r--r--railties/test/application/middleware_test.rb4
-rw-r--r--railties/test/application/rack/logger_test.rb40
-rw-r--r--railties/test/application/rake_test.rb13
-rw-r--r--railties/test/application/routing_test.rb4
-rw-r--r--railties/test/generators/app_generator_test.rb12
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb2
-rw-r--r--railties/test/generators/shared_generator_tests.rb3
-rw-r--r--railties/test/railties/shared_tests.rb3
125 files changed, 1654 insertions, 733 deletions
diff --git a/Gemfile b/Gemfile
index a268db7929..23b1a1096b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -21,17 +21,12 @@ group :doc do
gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3"
end
-group :test do
- gem "ruby-prof"
-end
-
# AS
gem "memcache-client", ">= 1.8.5"
platforms :mri_18 do
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
- gem 'ruby-prof'
gem "json"
end
@@ -48,6 +43,9 @@ platforms :ruby do
gem 'yajl-ruby'
gem "nokogiri", ">= 1.4.4"
+ group :test do
+ gem 'ruby-prof'
+ end
# AR
gem "sqlite3", "~> 1.3.3"
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 1e91d62e24..f00a0c8ae0 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -349,8 +349,7 @@ module ActionMailer #:nodoc:
include AbstractController::Translation
include AbstractController::AssetPaths
- cattr_reader :protected_instance_variables
- @@protected_instance_variables = []
+ self.protected_instance_variables = %w(@_action_has_layout)
helper ActionMailer::MailHelper
include ActionMailer::OldApi
diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb
index 04728cafb0..bfa9499764 100644
--- a/actionmailer/lib/action_mailer/old_api.rb
+++ b/actionmailer/lib/action_mailer/old_api.rb
@@ -8,7 +8,7 @@ module ActionMailer
included do
extend ActionMailer::AdvAttrAccessor
- self.protected_instance_variables.concat %w(@parts @mail_was_called)
+ self.protected_instance_variables.concat %w(@parts @mail_was_called @headers)
# Specify the BCC addresses for the message
adv_attr_accessor :bcc
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 046b47cc32..23190ef406 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,9 @@
*Rails 3.1.0 (unreleased)*
+* Warn if we cannot verify CSRF token authenticity [José Valim]
+
+* Allow AM/PM format in datetime selectors [Aditya Sanghi]
+
* Only show dump of regular env methods on exception screen (not all the rack crap) [DHH]
* auto_link has been removed with no replacement. If you still use auto_link
@@ -114,8 +118,6 @@ tested.
Keys are dasherized. Values are JSON-encoded, except for strings and symbols. [Stephen Celis]
-* Added render :once. You can pass either a string or an array of strings and Rails will ensure they each of them are rendered just once. [José Valim]
-
* Deprecate old template handler API. The new API simply requires a template handler to respond to call. [José Valim]
* :rhtml and :rxml were finally removed as template handlers. [José Valim]
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 0951267fea..f67d0e558e 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -130,27 +130,39 @@ module AbstractController
self.class.action_methods
end
- # Returns true if the name can be considered an action. This can
- # be overridden in subclasses to modify the semantics of what
- # can be considered an action.
+ # Returns true if a method for the action is available and
+ # can be dispatched, false otherwise.
#
- # For instance, this is overriden by ActionController to add
- # the implicit rendering feature.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- def action_method?(name)
- self.class.action_methods.include?(name)
+ # Notice that <tt>action_methods.include?("foo")</tt> may return
+ # false and <tt>available_action?("foo")</tt> returns true because
+ # available action consider actions that are also available
+ # through other means, for example, implicit render ones.
+ def available_action?(action_name)
+ method_for_action(action_name).present?
end
private
+ # Returns true if the name can be considered an action because
+ # it has a method defined in the controller.
+ #
+ # ==== Parameters
+ # * <tt>name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
+ #
+ # :api: private
+ def action_method?(name)
+ self.class.action_methods.include?(name)
+ end
+
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
+ #
+ # Notice that the first argument is the method to be dispatched
+ # which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index f7b2b7ff53..e8426bc52b 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -13,8 +13,8 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
- def process_action(method_name, *args)
- run_callbacks(:process_action, method_name) do
+ def process_action(*args)
+ run_callbacks(:process_action, action_name) do
super
end
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index d1b87b67ee..8f73e244d7 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -292,15 +292,15 @@ module AbstractController
end
end
- attr_writer :action_has_layout
+ attr_internal_writer :action_has_layout
def initialize(*)
- @action_has_layout = true
+ @_action_has_layout = true
super
end
def action_has_layout?
- @action_has_layout
+ @_action_has_layout
end
private
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index f78365afdb..ab2c532859 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -32,9 +32,13 @@ module AbstractController
module Rendering
extend ActiveSupport::Concern
-
include AbstractController::ViewPaths
+ included do
+ config_accessor :protected_instance_variables, :instance_reader => false
+ self.protected_instance_variables = []
+ end
+
# Overwrite process to setup I18n proxy.
def process(*) #:nodoc:
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
@@ -53,14 +57,20 @@ module AbstractController
end
end
- attr_writer :view_context_class
+ attr_internal_writer :view_context_class
+
+ # Explicitly define protected_instance_variables so it can be
+ # inherited and overwritten by other modules if needed.
+ def protected_instance_variables
+ config.protected_instance_variables
+ end
def view_context_class
- @view_context_class || self.class.view_context_class
+ @_view_context_class || self.class.view_context_class
end
def initialize(*)
- @view_context_class = nil
+ @_view_context_class = nil
super
end
@@ -79,7 +89,7 @@ module AbstractController
# Returns an object that is able to render templates.
def view_renderer
- @view_renderer ||= ActionView::Renderer.new(lookup_context)
+ @_view_renderer ||= ActionView::Renderer.new(lookup_context)
end
# Normalize arguments, options and then delegates render_to_body and
@@ -112,13 +122,19 @@ module AbstractController
private
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
+ @_action_name @_response_body @_formats @_prefixes @_config
+ @_view_context_class @_view_renderer @_lookup_context
+ )
+
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
# :api: public
def view_assigns
hash = {}
variables = instance_variable_names
- variables -= protected_instance_variables if respond_to?(:protected_instance_variables)
+ variables -= protected_instance_variables
+ variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) }
hash
end
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index 0893459e24..6b7aae8c74 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -39,7 +39,7 @@ module AbstractController
# templates, i.e. view paths and details. Check ActionView::LookupContext for more
# information.
def lookup_context
- @lookup_context ||=
+ @_lookup_context ||=
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3fae697cc3..8d813a8e38 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -7,8 +7,10 @@ module ActionController
def start_processing(event)
payload = event.payload
params = payload[:params].except(*INTERNAL_PARAMS)
+ format = payload[:format]
+ format = format.to_s.upcase if format.is_a?(Symbol)
- info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}"
+ info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
info " Parameters: #{params.inspect}" unless params.empty?
end
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
index 006b9fd456..05dca445a4 100644
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -18,13 +18,10 @@ module ActionController
delegate :default_charset=, :to => "ActionDispatch::Response"
end
- # TODO: Update protected instance variables list
- config_accessor :protected_instance_variables
- self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render
- @variables_added @request_origin @url
- @parent_controller @action_name
- @before_filter_chain_aborted @_headers @_params
- @_response)
+ self.protected_instance_variables = %w(
+ @_status @_headers @_params @_env @_response @_request
+ @_view_runtime @_stream @_url_options @_action_has_layout
+ )
def rescue_action(env)
raise env["action_dispatch.rescue.exception"]
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 3ec0c4c6a4..e8e465d3ba 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,21 +1,19 @@
module ActionController
module ImplicitRender
def send_action(method, *args)
- if respond_to?(method, true)
- ret = super
- default_render unless response_body
- ret
- else
- default_render
- end
+ ret = super
+ default_render unless response_body
+ ret
end
def default_render(*args)
render(*args)
end
- def action_method?(action_name)
- super || template_exists?(action_name.to_s, _prefixes)
+ def method_for_action(action_name)
+ super || if template_exists?(action_name.to_s, _prefixes)
+ "default_render"
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 4e54c2ad88..16cbbce2fb 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -19,7 +19,7 @@ module ActionController
:controller => self.class.name,
:action => self.action_name,
:params => request.filtered_parameters,
- :formats => request.formats.map(&:to_sym),
+ :format => request.format.ref,
:method => request.method,
:path => (request.fullpath rescue "unknown")
}
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 21bbe17dc3..881af74147 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -125,7 +125,7 @@ module ActionController
# module is inherited.
def inherited(klass)
if klass._wrapper_options[:format].present?
- klass._set_wrapper_defaults(klass._wrapper_options)
+ klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
end
super
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 1cd93a188c..13044a7450 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -73,7 +73,10 @@ module ActionController #:nodoc:
protected
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
- verified_request? || handle_unverified_request
+ unless verified_request?
+ logger.debug "WARNING: Can't verify CSRF token authenticity" if logger
+ handle_unverified_request
+ end
end
def handle_unverified_request
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index 09dd08898c..91a97c02ff 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -33,7 +33,7 @@ module HTML
result = super
# strip any comments, and if they have a newline at the end (ie. line with
# only a comment) strip that too
- result.gsub!(/<!--(.*?)-->[\n]?/m, "") if result
+ result = result.gsub(/<!--(.*?)-->[\n]?/m, "") if (result && result =~ /<!--(.*?)-->[\n]?/m)
# Recurse - handle all dirty nested tags
result == text ? result : sanitize(result, options)
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 2099fd069a..4b9d3141d5 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -1,7 +1,7 @@
<h1>
<%=h @exception.class.to_s %>
<% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].classify.pluralize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
+ in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.message %></pre>
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 97e8ccc9a5..5097f6732d 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -224,6 +224,7 @@ module ActionDispatch
self.valid_conditions.push(:controller, :action)
@append = []
+ @prepend = []
@disable_clear_and_finalize = false
clear!
end
@@ -232,7 +233,6 @@ module ActionDispatch
clear! unless @disable_clear_and_finalize
eval_block(block)
finalize! unless @disable_clear_and_finalize
-
nil
end
@@ -240,6 +240,10 @@ module ActionDispatch
@append << block
end
+ def prepend(&block)
+ @prepend << block
+ end
+
def eval_block(block)
if block.arity == 1
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
@@ -262,8 +266,6 @@ module ActionDispatch
end
def clear!
- # Clear the controller cache so we may discover new ones
- @controller_constraints = nil
@finalized = false
routes.clear
named_routes.clear
@@ -271,6 +273,7 @@ module ActionDispatch
:parameters_key => PARAMETERS_KEY,
:request_class => request_class
)
+ @prepend.each { |blk| eval_block(blk) }
end
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 92b6f7c770..a67b61c1ef 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -50,6 +50,7 @@ module ActionView
autoload :Resolver
autoload :PathResolver
autoload :FileSystemResolver
+ autoload :OptimizedFileSystemResolver
autoload :FallbackFileSystemResolver
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index ead7feb091..3b5f4e694f 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -135,8 +135,12 @@ module ActionView
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
- result = @view_flow.append(name, content) if content
- result unless content
+ if content
+ @view_flow.append(name, content)
+ nil
+ else
+ @view_flow.get(name)
+ end
end
# The same as +content_for+ but when used with streaming flushes
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 9277359d5c..d2ddaafcb3 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -94,9 +94,20 @@ module ActionView
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
- distance_in_years = distance_in_minutes / 525600
- minute_offset_for_leap_year = (distance_in_years / 4) * 1440
- remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600)
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # english it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ remainder = (minutes_with_offset % 525600)
+ distance_in_years = (minutes_with_offset / 525600)
if remainder < 131400
locale.t(:about_x_years, :count => distance_in_years)
elsif remainder < 394200
@@ -207,7 +218,8 @@ module ActionView
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
# specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
- # +object+). You can include the seconds with <tt>:include_seconds</tt>.
+ # +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format
+ # with <tt>:ampm</tt> option.
#
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
# <tt>:ignore_date</tt> is set to +true+.
@@ -230,6 +242,9 @@ module ActionView
# time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours
# time_select("post", "written_on", :prompt => true) # generic prompts for all
#
+ # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
+ # time_select 'game', 'game_time', {:ampm => true}
+ #
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -257,6 +272,9 @@ module ActionView
# # be stored in the trip variable in the departing attribute.
# datetime_select("trip", "departing", :default => 3.days.from_now)
#
+ # # Generate a datetime select with hours in the AM/PM format
+ # datetime_select("post", "written_on", :ampm => true)
+ #
# # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable
# # as the written_on attribute.
# datetime_select("post", "written_on", :discard_type => true)
@@ -306,6 +324,9 @@ module ActionView
# # my_date_time (four days after today)
# select_datetime(my_date_time, :discard_type => true)
#
+ # # Generate a datetime field with hours in the AM/PM format
+ # select_datetime(my_date_time, :ampm => true)
+ #
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
# # prefixed with 'payday' rather than 'date'
# select_datetime(my_date_time, :prefix => 'payday')
@@ -387,6 +408,9 @@ module ActionView
# # separated by ':' and includes an input for seconds
# select_time(my_time, :time_separator => ':', :include_seconds => true)
#
+ # # Generate a time select field with hours in the AM/PM format
+ # select_time(my_time, :ampm => true)
+ #
# # Generates a time select with a custom prompt. Use :prompt=>true for generic prompts.
# select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
@@ -468,6 +492,9 @@ module ActionView
# # generic prompt.
# select_hour(13, :prompt => 'Choose hour')
#
+ # # Generate a select field for hours in the AM/PM format
+ # select_hour(my_time, :ampm => true)
+ #
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
end
@@ -601,6 +628,15 @@ module ActionView
:year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
}.freeze
+ AMPM_TRANSLATION = Hash[
+ [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"],
+ [4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"],
+ [8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"],
+ [12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"],
+ [16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"],
+ [20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]]
+ ].freeze
+
def initialize(datetime, options = {}, html_options = {})
@options = options.dup
@html_options = html_options.dup
@@ -692,7 +728,7 @@ module ActionView
if @options[:use_hidden] || @options[:discard_hour]
build_hidden(:hour, hour)
else
- build_options_and_select(:hour, hour, :end => 23)
+ build_options_and_select(:hour, hour, :end => 23, :ampm => @options[:ampm])
end
end
@@ -810,7 +846,7 @@ module ActionView
start = options.delete(:start) || 0
stop = options.delete(:end) || 59
step = options.delete(:step) || 1
- options.reverse_merge!({:leading_zeros => true})
+ options.reverse_merge!({:leading_zeros => true, :ampm => false})
leading_zeros = options.delete(:leading_zeros)
select_options = []
@@ -818,7 +854,8 @@ module ActionView
value = leading_zeros ? sprintf("%02d", i) : i
tag_options = { :value => value }
tag_options[:selected] = "selected" if selected == i
- select_options << content_tag(:option, value, tag_options)
+ text = options[:ampm] ? AMPM_TRANSLATION[i] : value
+ select_options << content_tag(:option, text, tag_options)
end
(select_options.join("\n") + "\n").html_safe
end
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 06975ffa2f..f0ed3425de 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -167,12 +167,12 @@ module ActionView
@frozen_formats = true
end
- # Overload formats= to reject ["*/*"] values.
+ # Overload formats= to expand ["*/*"] values and automatically
+ # add :html as fallback to :js.
def formats=(values)
- if values && values.size == 1
- value = values.first
- values = nil if value == "*/*"
- values << :html if value == :js
+ if values
+ values.concat(_formats_defaults) if values.delete "*/*"
+ values << :html if values == [:js]
end
super(values)
end
diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb
index 8b840a6463..e0cb5d6a70 100644
--- a/actionpack/lib/action_view/path_set.rb
+++ b/actionpack/lib/action_view/path_set.rb
@@ -35,7 +35,7 @@ module ActionView #:nodoc:
each_with_index do |path, i|
path = path.to_s if path.is_a?(Pathname)
next unless path.is_a?(String)
- self[i] = FileSystemResolver.new(path)
+ self[i] = OptimizedFileSystemResolver.new(path)
end
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index e246646963..d4448a7b33 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -1,3 +1,4 @@
+require "active_support/core_ext/array/wrap"
require "active_support/core_ext/enumerable"
module ActionView
@@ -29,6 +30,7 @@ module ActionView
def initialize(paths, path, prefixes, partial, details, *)
@path = path
+ prefixes = Array.wrap(prefixes)
display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
template_type = if partial
"partial"
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 870897958a..2b9427ace5 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -10,17 +10,16 @@ module ActionView
attr_reader :name, :prefix, :partial, :virtual
alias_method :partial?, :partial
- def initialize(name, prefix, partial)
- @name, @prefix, @partial = name, prefix, partial
- rebuild(@name, @prefix, @partial)
+ def self.build(name, prefix, partial)
+ virtual = ""
+ virtual << "#{prefix}/" unless prefix.empty?
+ virtual << (partial ? "_#{name}" : name)
+ new name, prefix, partial, virtual
end
- def rebuild(name, prefix, partial)
- @virtual = ""
- @virtual << "#{prefix}/" unless prefix.empty?
- @virtual << (partial ? "_#{name}" : name)
-
- self.replace(@virtual)
+ def initialize(name, prefix, partial, virtual)
+ @name, @prefix, @partial = name, prefix, partial
+ super(virtual)
end
end
@@ -60,7 +59,7 @@ module ActionView
# Helpers that builds a path. Useful for building virtual paths.
def build_path(name, prefix, partial)
- Path.new(name, prefix, partial)
+ Path.build(name, prefix, partial)
end
# Handles templates caching. If a key is given and caching is on
@@ -112,7 +111,8 @@ module ActionView
end
end
- class PathResolver < Resolver
+ # An abstract class that implements a Resolver with path semantics.
+ class PathResolver < Resolver #:nodoc:
EXTENSIONS = [:locale, :formats, :handlers]
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}"
@@ -124,13 +124,12 @@ module ActionView
private
def find_templates(name, prefix, partial, details)
- path = build_path(name, prefix, partial)
- extensions = Hash[EXTENSIONS.map { |ext| [ext, details[ext]] }.flatten(0)]
- query(path, extensions, details[:formats])
+ path = Path.build(name, prefix, partial)
+ query(path, details, details[:formats])
end
- def query(path, exts, formats)
- query = build_query(path, exts)
+ def query(path, details, formats)
+ query = build_query(path, details)
templates = []
sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] }
@@ -138,7 +137,7 @@ module ActionView
next if File.directory?(p) || !sanitizer[p].include?(p)
handler, format = extract_handler_and_format(p, formats)
- contents = File.open(p, "rb") {|io| io.read }
+ contents = File.open(p, "rb") { |io| io.read }
templates << Template.new(contents, File.expand_path(p), handler,
:virtual_path => path.virtual, :format => format, :updated_at => mtime(p))
@@ -147,18 +146,15 @@ module ActionView
templates
end
- # Helper for building query glob string based on resolver's pattern.
- def build_query(path, exts)
+ # Helper for building query glob string based on resolver's pattern.
+ def build_query(path, details)
query = @pattern.dup
query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
- exts.each { |ext, variants|
+ details.each do |ext, variants|
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
- }
-
- query.gsub!('.{html,', '.{html,text.html,')
- query.gsub!('.{text,', '.{text,text.plain,')
+ end
File.expand_path(query, @path)
end
@@ -235,9 +231,25 @@ module ActionView
alias :== :eql?
end
+ # An Optimized resolver for Rails' most common case.
+ class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
+ def build_query(path, details)
+ exts = EXTENSIONS.map { |ext| details[ext] }
+ query = File.join(@path, path)
+
+ exts.each do |ext|
+ query << "{"
+ ext.compact.each { |e| query << ".#{e}," }
+ query << "}"
+ end
+
+ query
+ end
+ end
+
# The same as FileSystemResolver but does not allow templates to store
# a virtual path since it is invalid for such resolvers.
- class FallbackFileSystemResolver < FileSystemResolver
+ class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
def self.instances
[new(""), new("/")]
end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
index 9c10decd60..0b4b0638b2 100644
--- a/actionpack/lib/sprockets/railtie.rb
+++ b/actionpack/lib/sprockets/railtie.rb
@@ -39,7 +39,7 @@ module Sprockets
end
end
- app.routes.append do
+ app.routes.prepend do
mount app.assets => assets.prefix
end
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 3bb3016fdb..1e2191d417 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -33,10 +33,10 @@ module RenderImplicitAction
assert_status 200
end
- test "action_method? returns true for implicit actions" do
- assert SimpleController.new.action_method?(:hello_world)
- assert SimpleController.new.action_method?(:"hyphen-ated")
- assert SimpleController.new.action_method?(:not_implemented)
+ test "available_action? returns true for implicit actions" do
+ assert SimpleController.new.available_action?(:hello_world)
+ assert SimpleController.new.available_action?(:"hyphen-ated")
+ assert SimpleController.new.available_action?(:not_implemented)
end
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index d128006404..25b1b4f745 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -358,6 +358,13 @@ class RequestTest < ActiveSupport::TestCase
assert request.head?
end
+ test "post masquerading as put" do
+ request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
+ assert_equal "POST", request.method
+ assert_equal "PUT", request.request_method
+ assert request.put?
+ end
+
test "xml format" do
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => 'xml' })
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index cc57a6cba0..42f6c7f79f 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -131,11 +131,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
'action_dispatch.request.parameters' => {
'action' => 'show',
'id' => 'unknown',
- 'controller' => 'featured_tiles'
+ 'controller' => 'featured_tile'
}
})
assert_response 500
- assert_match(/RuntimeError\n in FeaturedTilesController/, body)
+ assert_match(/RuntimeError\n in FeaturedTileController/, body)
end
test "sets the HTTP charset parameter" do
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index 592c7da060..a9157e711c 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -38,7 +38,15 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal '&lt;em&gt;bar&lt;/em&gt;', string
end
- def test_content_for
+ def test_capture_used_for_read
+ content_for :foo, "foo"
+ assert_equal "foo", content_for(:foo)
+
+ content_for(:bar){ "bar" }
+ assert_equal "bar", content_for(:bar)
+ end
+
+ def test_content_for_question_mark
assert ! content_for?(:title)
content_for :title, 'title'
assert content_for?(:title)
@@ -49,14 +57,14 @@ class CaptureHelperTest < ActionView::TestCase
assert !content_for?(:title)
provide :title, "hi"
assert content_for?(:title)
- assert_equal "hi", @view_flow.get(:title)
+ assert_equal "hi", content_for(:title)
provide :title, "<p>title</p>"
- assert_equal "hi&lt;p&gt;title&lt;/p&gt;", @view_flow.get(:title)
+ assert_equal "hi&lt;p&gt;title&lt;/p&gt;", content_for(:title)
@view_flow = ActionView::OutputFlow.new
provide :title, "hi"
provide :title, "<p>title</p>".html_safe
- assert_equal "hi<p>title</p>", @view_flow.get(:title)
+ assert_equal "hi<p>title</p>", content_for(:title)
end
def test_with_output_buffer_swaps_the_output_buffer_given_no_argument
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 12d2410f49..09c53a36f0 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -121,6 +121,10 @@ class DateHelperTest < ActionView::TestCase
start_date = Date.new 1975, 1, 31
end_date = Date.new 1977, 1, 31
assert_equal("about 2 years", distance_of_time_in_words(start_date, end_date))
+
+ start_date = Date.new 1982, 12, 3
+ end_date = Date.new 2010, 11, 30
+ assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date))
end
def test_distance_in_words_with_integers
@@ -416,6 +420,14 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18))
end
+ def test_select_hour_with_ampm
+ expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :ampm => true)
+ end
+
def test_select_hour_with_disabled
expected = %(<select id="date_hour" name="date[hour]" disabled="disabled">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
@@ -937,6 +949,35 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
end
+ def test_select_datetime_with_ampm
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<select id="date_first_day" name="date[first][day]">\n)
+ expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %(<select id="date_first_hour" name="date[first][hour]">\n)
+ expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_first_minute" name="date[first][minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :ampm => true)
+ end
+
+
def test_select_datetime_with_separators
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1136,6 +1177,24 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false)
end
+ def test_select_time_with_ampm
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
+ expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
+
+ expected << %(<select id="date_hour" name="date[hour]">\n)
+ expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n)
+ expected << "</select>\n"
+
+ expected << " : "
+
+ expected << %(<select id="date_minute" name="date[minute]">\n)
+ expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false, :ampm => true)
+ end
+
def test_select_time_with_separator
expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index fcc3782f04..678cb9eeeb 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -20,6 +20,7 @@ class SanitizerTest < ActionController::TestCase
assert_equal "This has a here.", sanitizer.sanitize("This has a <![CDATA[<section>]]> here.")
assert_equal "This has an unclosed ", sanitizer.sanitize("This has an unclosed <![CDATA[<section>]] here...")
[nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) }
+ assert_nothing_raised { sanitizer.sanitize("This is a frozen string with no tags".freeze) }
end
def test_strip_links
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 5fb1fdc044..47b70f05ab 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -51,6 +51,11 @@ class LookupContextTest < ActiveSupport::TestCase
assert_equal Mime::SET, @lookup_context.formats
end
+ test "handles explicitly defined */* formats fallback to :js" do
+ @lookup_context.formats = [:js, Mime::ALL]
+ assert_equal [:js, *Mime::SET.symbols], @lookup_context.formats
+ end
+
test "adds :html fallback to :js formats" do
@lookup_context.formats = [:js]
assert_equal [:js, :html], @lookup_context.formats
@@ -251,4 +256,13 @@ class TestMissingTemplate < ActiveSupport::TestCase
end
assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
end
+
+ test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do
+ e = assert_raise ActionView::MissingTemplate do
+ details = {:handlers=>[], :formats=>[], :locale=>[]}
+ @lookup_context.view_paths.find("foo", "parent", true, details)
+ end
+ assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ end
+
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index d4e912c410..86d08a43a5 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -325,7 +325,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase
# Ensure view path cache is primed
def setup
view_paths = ActionController::Base.view_paths
- assert_equal ActionView::FileSystemResolver, view_paths.first.class
+ assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
setup_view(view_paths)
end
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 03287fac7a..c26924cf09 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis]
+
* Add support for proc or lambda as an option for InclusionValidator,
ExclusionValidator, and FormatValidator [Prem Sichanugrist]
@@ -13,6 +15,8 @@
* ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov]
+* Add support for selectively enabling/disabling observers [Myron Marston]
+
*Rails 3.0.2 (unreleased)*
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 01eef762fd..483b577681 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -35,17 +35,17 @@ module ActiveModel
# protected
#
# def account_params
- # scope = admin ? :admin : :default
- # sanitize_for_mass_assignment(params[:account], scope)
+ # role = admin ? :admin : :default
+ # sanitize_for_mass_assignment(params[:account], role)
# end
#
# end
#
module ClassMethods
# Attributes named in this macro are protected from mass-assignment
- # whenever attributes are sanitized before assignment. A scope for the
- # attributes is optional, if no scope is provided then :default is used.
- # A scope can be defined by using the :as option.
+ # whenever attributes are sanitized before assignment. A role for the
+ # attributes is optional, if no role is provided then :default is used.
+ # A role can be defined by using the :as option.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
@@ -67,7 +67,7 @@ module ActiveModel
# end
# end
#
- # When using a :default scope :
+ # When using the :default role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
@@ -78,7 +78,7 @@ module ActiveModel
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
- # And using the :admin scope :
+ # And using the :admin role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
@@ -93,10 +93,10 @@ module ActiveModel
# to sanitize attributes won't provide sufficient protection.
def attr_protected(*args)
options = args.extract_options!
- scope = options[:as] || :default
+ role = options[:as] || :default
self._protected_attributes = protected_attributes_configs.dup
- self._protected_attributes[scope] = self.protected_attributes(scope) + args
+ self._protected_attributes[role] = self.protected_attributes(role) + args
self._active_authorizer = self._protected_attributes
end
@@ -104,8 +104,8 @@ module ActiveModel
# Specifies a white list of model attributes that can be set via
# mass-assignment.
#
- # Like +attr_protected+, a scope for the attributes is optional,
- # if no scope is provided then :default is used. A scope can be defined by
+ # Like +attr_protected+, a role for the attributes is optional,
+ # if no role is provided then :default is used. A role can be defined by
# using the :as option.
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
@@ -131,7 +131,7 @@ module ActiveModel
# end
# end
#
- # When using a :default scope :
+ # When using the :default role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
@@ -141,7 +141,7 @@ module ActiveModel
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
- # And using the :admin scope :
+ # And using the :admin role :
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
@@ -152,20 +152,20 @@ module ActiveModel
# to sanitize attributes won't provide sufficient protection.
def attr_accessible(*args)
options = args.extract_options!
- scope = options[:as] || :default
+ role = options[:as] || :default
self._accessible_attributes = accessible_attributes_configs.dup
- self._accessible_attributes[scope] = self.accessible_attributes(scope) + args
+ self._accessible_attributes[role] = self.accessible_attributes(role) + args
self._active_authorizer = self._accessible_attributes
end
- def protected_attributes(scope = :default)
- protected_attributes_configs[scope]
+ def protected_attributes(role = :default)
+ protected_attributes_configs[role]
end
- def accessible_attributes(scope = :default)
- accessible_attributes_configs[scope]
+ def accessible_attributes(role = :default)
+ accessible_attributes_configs[role]
end
def active_authorizers
@@ -198,12 +198,12 @@ module ActiveModel
protected
- def sanitize_for_mass_assignment(attributes, scope = :default)
- mass_assignment_authorizer(scope).sanitize(attributes)
+ def sanitize_for_mass_assignment(attributes, role = :default)
+ mass_assignment_authorizer(role).sanitize(attributes)
end
- def mass_assignment_authorizer(scope = :default)
- self.class.active_authorizer[scope]
+ def mass_assignment_authorizer(role = :default)
+ self.class.active_authorizer[role]
end
end
end
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
index ab7f86007f..5fb73f1c78 100644
--- a/activemodel/lib/active_model/observer_array.rb
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -2,7 +2,7 @@ require 'set'
module ActiveModel
# Stores the enabled/disabled state of individual observers for
- # a particular model classes.
+ # a particular model class.
class ObserverArray < Array
attr_reader :model_class
def initialize(model_class, *args)
@@ -10,14 +10,57 @@ module ActiveModel
super(*args)
end
+ # Returns true if the given observer is disabled for the model class.
def disabled_for?(observer)
disabled_observers.include?(observer.class)
end
+ # Disables one or more observers. This supports multiple forms:
+ #
+ # ORM.observers.disable :user_observer
+ # # => disables the UserObserver
+ #
+ # User.observers.disable AuditTrail
+ # # => disables the AuditTrail observer for User notifications.
+ # # Other models will still notify the AuditTrail observer.
+ #
+ # ORM.observers.disable :observer_1, :observer_2
+ # # => disables Observer1 and Observer2 for all models.
+ #
+ # ORM.observers.disable :all
+ # # => disables all observers for all models.
+ #
+ # User.observers.disable :all do
+ # # all user observers are disabled for
+ # # just the duration of the block
+ # end
def disable(*observers, &block)
set_enablement(false, observers, &block)
end
+ # Enables one or more observers. This supports multiple forms:
+ #
+ # ORM.observers.enable :user_observer
+ # # => enables the UserObserver
+ #
+ # User.observers.enable AuditTrail
+ # # => enables the AuditTrail observer for User notifications.
+ # # Other models will not be affected (i.e. they will not
+ # # trigger notifications to AuditTrail if previously disabled)
+ #
+ # ORM.observers.enable :observer_1, :observer_2
+ # # => enables Observer1 and Observer2 for all models.
+ #
+ # ORM.observers.enable :all
+ # # => enables all observers for all models.
+ #
+ # User.observers.enable :all do
+ # # all user observers are enabled for
+ # # just the duration of the block
+ # end
+ #
+ # Note: all observers are enabled by default. This method is only
+ # useful when you have previously disabled one or more observers.
def enable(*observers, &block)
set_enablement(true, observers, &block)
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index cfe71074a5..4682ae07ef 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -40,7 +40,11 @@ module ActiveModel
observers.replace(values.flatten)
end
- # Gets the current observers.
+ # Gets an array of observers observing this model.
+ # The array also provides +enable+ and +disable+ methods
+ # that allow you to selectively enable and disable observers.
+ # (see <tt>ActiveModel::ObserverArray.enable</tt> and
+ # <tt>ActiveModel::ObserverArray.disable</tt> for more on this)
def observers
@observers ||= ObserverArray.new(self)
end
@@ -222,7 +226,8 @@ module ActiveModel
self.class.observed_classes
end
- # Send observed_method(object) if the method exists.
+ # Send observed_method(object) if the method exists and
+ # the observer is enabled for the given object's class.
def update(observed_method, object) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index b22ce874ea..43a12eed61 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -10,7 +10,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal expected, sanitized
end
- def test_only_moderator_scope_attribute_accessible
+ def test_only_moderator_role_attribute_accessible
user = SpecialUser.new
expected = { "name" => "John Smith", "email" => "john@smith.com" }
sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator)
@@ -27,7 +27,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal expected, sanitized
end
- def test_admin_scoped_attributes_accessible
+ def test_attributes_accessible_with_admin_role
user = Person.new
expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true }
sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin)
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9ff29f1155..a03751a6c1 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,20 @@
*Rails 3.1.0 (unreleased)*
+* AR#new, AR#create and AR#update_attributes all accept a second hash as option that allows you
+ to specify which role to consider when assigning attributes. This is built on top of ActiveModel's
+ new mass assignment capabilities:
+
+ class Post < ActiveRecord::Base
+ attr_accessible :title
+ attr_accessible :title, :published_at, :as => :admin
+ end
+
+ Post.new(params[:post], :as => :admin)
+
+ assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated.
+
+ [Josh Kalderimis]
+
* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
evaluation:
@@ -22,25 +37,7 @@
[Jon Leighton]
-* Calling 'default_scope' multiple times in a class (including when a superclass calls
- 'default_scope') is deprecated. The current behavior is that this will merge the default
- scopes together:
-
- class Post < ActiveRecord::Base # Rails 3.1
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:published => true, :hidden => false)
- end
-
- In Rails 3.2, the behavior will be changed to overwrite previous scopes:
-
- class Post < ActiveRecord::Base # Rails 3.2
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:hidden => false)
- end
-
- If you wish to merge default scopes in special ways, it is recommended to define your default
+* If you wish to merge default scopes in special ways, it is recommended to define your default
scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
etc.):
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 6cdec8c487..85a4f47b7d 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -94,7 +94,14 @@ module ActiveRecord
end
def build(attributes = {}, options = {}, &block)
- build_or_create(:build, attributes, options, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| build(attr, options, &block) }
+ else
+ add_to_target(build_record(attributes, options)) do |record|
+ yield(record) if block_given?
+ set_owner_attributes(record)
+ end
+ end
end
def create(attributes = {}, options = {}, &block)
@@ -102,7 +109,16 @@ module ActiveRecord
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
- build_or_create(:create, attributes, options, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, options, &block) }
+ else
+ transaction do
+ add_to_target(build_record(attributes, options)) do |record|
+ yield(record) if block_given?
+ insert_record(record)
+ end
+ end
+ end
end
def create!(attrs = {}, options = {}, &block)
@@ -337,20 +353,18 @@ module ActiveRecord
end
def add_to_target(record)
- transaction do
- callback(:before_add, record)
- yield(record) if block_given?
-
- if options[:uniq] && index = @target.index(record)
- @target[index] = record
- else
- @target << record
- end
+ callback(:before_add, record)
+ yield(record) if block_given?
- callback(:after_add, record)
- set_inverse_instance(record)
+ if options[:uniq] && index = @target.index(record)
+ @target[index] = record
+ else
+ @target << record
end
+ callback(:after_add, record)
+ set_inverse_instance(record)
+
record
end
@@ -403,26 +417,13 @@ module ActiveRecord
end + existing
end
- def build_or_create(method, attributes, options)
- records = Array.wrap(attributes).map do |attrs|
- record = build_record(attrs, options)
-
- add_to_target(record) do
- yield(record) if block_given?
- insert_record(record) if method == :create
- end
- end
-
- attributes.is_a?(Array) ? records : records.first
- end
-
# Do the relevant stuff to insert the given record into the association collection.
def insert_record(record, validate = true)
raise NotImplementedError
end
def build_record(attributes, options)
- reflection.build_association(scoped.scope_for_create.merge(attributes), options)
+ reflection.build_association(scoped.scope_for_create.merge(attributes || {}), options)
end
def delete_or_destroy(records, method)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 58a056bce9..d07f9365b3 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -482,7 +482,7 @@ module ActiveRecord #:nodoc:
# # Create a single new object
# User.create(:first_name => 'Jamie')
#
- # # Create a single new object using the :admin mass-assignment security scope
+ # # Create a single new object using the :admin mass-assignment security role
# User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
#
# # Create a single new object bypassing mass-assignment security
@@ -830,6 +830,10 @@ module ActiveRecord #:nodoc:
@symbolized_base_class ||= base_class.to_s.to_sym
end
+ def symbolized_sti_name
+ @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
+ end
+
# Returns the base AR subclass that this class descends from. If A
# extends AR::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
@@ -1482,7 +1486,7 @@ MSG
# # Instantiates a single new object
# User.new(:first_name => 'Jamie')
#
- # # Instantiates a single new object using the :admin mass-assignment security scope
+ # # Instantiates a single new object using the :admin mass-assignment security role
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
#
# # Instantiates a single new object bypassing mass-assignment security
@@ -1657,8 +1661,8 @@ MSG
end
# Allows you to set all the attributes for a particular mass-assignment
- # security scope by passing in a hash of attributes with keys matching
- # the attribute names (which again matches the column names) and the scope
+ # security role by passing in a hash of attributes with keys matching
+ # the attribute names (which again matches the column names) and the role
# name using the :as option.
#
# To bypass mass-assignment security you can use the :without_protection => true
@@ -1684,13 +1688,15 @@ MSG
# user.name # => "Josh"
# user.is_admin? # => true
def assign_attributes(new_attributes, options = {})
+ return unless new_attributes
+
attributes = new_attributes.stringify_keys
- scope = options[:as] || :default
+ role = options[:as] || :default
multi_parameter_attributes = []
unless options[:without_protection]
- attributes = sanitize_for_mass_assignment(attributes, scope)
+ attributes = sanitize_for_mass_assignment(attributes, role)
end
attributes.each do |k, v|
@@ -1943,32 +1949,9 @@ MSG
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- # in order to allow a date to be set without a year, we must keep the empty values.
- # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
- values = values_with_empty_parameters.reject { |v| v.nil? }
-
- if values.empty?
- send(name + "=", nil)
- else
-
- value = if Time == klass
- instantiate_time_object(name, values)
- elsif Date == klass
- begin
- values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
- Date.new(*values)
- rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
- instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- else
- klass.new(*values)
- end
-
- send(name + "=", value)
- end
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
end
end
unless errors.empty?
@@ -1976,19 +1959,65 @@ MSG
end
end
+ def read_value_from_parameter(name, values_hash_from_param)
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values_hash_from_param.values.all?{|v|v.nil?}
+ nil
+ elsif klass == Time
+ read_time_parameter_value(name, values_hash_from_param)
+ elsif klass == Date
+ read_date_parameter_value(name, values_hash_from_param)
+ else
+ read_other_parameter_value(klass, name, values_hash_from_param)
+ end
+ end
+
+ def read_time_parameter_value(name, values_hash_from_param)
+ # If Date bits were not provided, error
+ raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ # If Date bits were provided but blank, then default to 1
+ # If Time bits are not there, then default to 0
+ [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]}
+ instantiate_time_object(name, set_values)
+ end
+
+ def read_date_parameter_value(name, values_hash_from_param)
+ set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]}
+ begin
+ Date.new(*set_values)
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
+ instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ end
+
+ def read_other_parameter_value(klass, name, values_hash_from_param)
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
+ values = (1..max_position).collect do |position|
+ raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
+ values_hash_from_param[position]
+ end
+ klass.new(*values)
+ end
+
+ def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
+ [values_hash_from_param.keys.max,upper_cap].min
+ end
+
def extract_callstack_for_multiparameter_attributes(pairs)
attributes = { }
for pair in pairs
multiparameter_name, value = pair
attribute_name = multiparameter_name.split("(").first
- attributes[attribute_name] = [] unless attributes.include?(attribute_name)
+ attributes[attribute_name] = {} unless attributes.include?(attribute_name)
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
- attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
end
- attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
+ attributes
end
def type_cast_attribute_value(multiparameter_name, value)
@@ -1996,7 +2025,7 @@ MSG
end
def find_parameter_position(multiparameter_name)
- multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 3045e30407..b3eb23bbb3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -6,15 +6,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(sql, name = nil, binds = [])
- if supports_statement_cache?
- select(sql, name, binds)
- else
- return select(sql, name) if binds.empty?
- binds = binds.dup
- select sql.gsub('?') {
- quote(*binds.shift.reverse)
- }, name
- end
+ select(sql, name, binds)
end
# Returns a record hash with the column names as keys and column values
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 8af22fe9f5..ac2da73a84 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -184,6 +184,10 @@ module ActiveRecord
QUOTED_FALSE
end
+ def substitute_at(column, index)
+ Arel.sql "\0"
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity(&block) #:nodoc:
@@ -292,14 +296,14 @@ module ActiveRecord
binds = binds.dup
# Pretend to support bind parameters
- execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
end
def exec_delete(sql, name, binds)
binds = binds.dup
# Pretend to support bind parameters
- execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
@connection.affected_rows
end
alias :exec_update :exec_delete
@@ -646,7 +650,8 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
- exec_query(sql, name, binds).to_a
+ binds = binds.dup
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
end
def exec_query(sql, name = 'SQL', binds = [])
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 96fea741e0..0e3ed7aac7 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -423,8 +423,8 @@ class FixturesFileNotFound < StandardError; end
# to the rescue:
#
# george_reginald:
-# monkey_id: <%= Fixtures.identify(:reginald) %>
-# pirate_id: <%= Fixtures.identify(:george) %>
+# monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
+# pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
#
# == Support for YAML defaults
#
@@ -444,369 +444,374 @@ class FixturesFileNotFound < StandardError; end
#
# Any fixture labeled "DEFAULTS" is safely ignored.
-class Fixtures
- MAX_ID = 2 ** 30 - 1
+Fixture = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixture', 'ActiveRecord::Fixture')
+Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixtures', 'ActiveRecord::Fixtures')
- @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
+module ActiveRecord
+ class Fixtures
+ MAX_ID = 2 ** 30 - 1
- def self.find_table_name(table_name) # :nodoc:
- ActiveRecord::Base.pluralize_table_names ?
- table_name.to_s.singularize.camelize :
- table_name.to_s.camelize
- end
+ @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
- def self.reset_cache
- @@all_cached_fixtures.clear
- end
+ def self.find_table_name(table_name) # :nodoc:
+ ActiveRecord::Base.pluralize_table_names ?
+ table_name.to_s.singularize.camelize :
+ table_name.to_s.camelize
+ end
- def self.cache_for_connection(connection)
- @@all_cached_fixtures[connection]
- end
+ def self.reset_cache
+ @@all_cached_fixtures.clear
+ end
- def self.fixture_is_cached?(connection, table_name)
- cache_for_connection(connection)[table_name]
- end
+ def self.cache_for_connection(connection)
+ @@all_cached_fixtures[connection]
+ end
- def self.cached_fixtures(connection, keys_to_fetch = nil)
- if keys_to_fetch
- cache_for_connection(connection).values_at(*keys_to_fetch)
- else
- cache_for_connection(connection).values
+ def self.fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
end
- end
- def self.cache_fixtures(connection, fixtures_map)
- cache_for_connection(connection).update(fixtures_map)
- end
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
+ if keys_to_fetch
+ cache_for_connection(connection).values_at(*keys_to_fetch)
+ else
+ cache_for_connection(connection).values
+ end
+ end
+
+ def self.cache_fixtures(connection, fixtures_map)
+ cache_for_connection(connection).update(fixtures_map)
+ end
- def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
- if load_instances
- fixtures.each do |name, fixture|
- begin
- object.instance_variable_set "@#{name}", fixture.find
- rescue FixtureClassNotFound
- nil
+ def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
+ if load_instances
+ fixtures.each do |name, fixture|
+ begin
+ object.instance_variable_set "@#{name}", fixture.find
+ rescue FixtureClassNotFound
+ nil
+ end
end
end
end
- end
- def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each do |table_name, fixtures|
- Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
+ all_loaded_fixtures.each do |table_name, fixtures|
+ ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ end
end
- end
- cattr_accessor :all_loaded_fixtures
- self.all_loaded_fixtures = {}
+ cattr_accessor :all_loaded_fixtures
+ self.all_loaded_fixtures = {}
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = [table_names].flatten.map { |n| n.to_s }
- table_names.each { |n|
- class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
- }
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+ table_names = [table_names].flatten.map { |n| n.to_s }
+ table_names.each { |n|
+ class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
+ }
- # FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
+ # FIXME: Apparently JK uses this.
+ connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name|
- fixture_is_cached?(connection, table_name)
- }
+ files_to_read = table_names.reject { |table_name|
+ fixture_is_cached?(connection, table_name)
+ }
- unless files_to_read.empty?
- connection.disable_referential_integrity do
- fixtures_map = {}
+ unless files_to_read.empty?
+ connection.disable_referential_integrity do
+ fixtures_map = {}
- fixture_files = files_to_read.map do |path|
- table_name = path.tr '/', '_'
+ fixture_files = files_to_read.map do |path|
+ table_name = path.tr '/', '_'
- fixtures_map[path] = Fixtures.new(
- connection,
- table_name,
- class_names[table_name.to_sym] || table_name.classify,
- File.join(fixtures_directory, path))
- end
+ fixtures_map[path] = ActiveRecord::Fixtures.new(
+ connection,
+ table_name,
+ class_names[table_name.to_sym] || table_name.classify,
+ File.join(fixtures_directory, path))
+ end
- all_loaded_fixtures.update(fixtures_map)
+ all_loaded_fixtures.update(fixtures_map)
- connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
+ connection.transaction(:requires_new => true) do
+ fixture_files.each do |ff|
+ conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
+ table_rows = ff.table_rows
- table_rows.keys.each do |table|
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
- end
+ table_rows.keys.each do |table|
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
- table_rows.each do |table_name,rows|
- rows.each do |row|
- conn.insert_fixture(row, table_name)
+ table_rows.each do |table_name,rows|
+ rows.each do |row|
+ conn.insert_fixture(row, table_name)
+ end
end
end
- end
- # Cap primary key sequences to max(pk).
- if connection.respond_to?(:reset_pk_sequence!)
- table_names.each do |table_name|
- connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ table_names.each do |table_name|
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ end
end
end
- end
- cache_fixtures(connection, fixtures_map)
+ cache_fixtures(connection, fixtures_map)
+ end
end
+ cached_fixtures(connection, table_names)
end
- cached_fixtures(connection, table_names)
- end
-
- # Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^32.
- def self.identify(label)
- Zlib.crc32(label.to_s) % MAX_ID
- end
- attr_reader :table_name, :name, :fixtures, :model_class
-
- def initialize(connection, table_name, class_name, fixture_path)
- @connection = connection
- @table_name = table_name
- @fixture_path = fixture_path
- @name = table_name # preserve fixture base name
- @class_name = class_name
-
- @fixtures = ActiveSupport::OrderedHash.new
- @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
-
- # Should be an AR::Base type class
- if class_name.is_a?(Class)
- @table_name = class_name.table_name
- @connection = class_name.connection
- @model_class = class_name
- else
- @model_class = class_name.constantize rescue nil
+ # Returns a consistent, platform-independent identifier for +label+.
+ # Identifiers are positive integers less than 2^32.
+ def self.identify(label)
+ Zlib.crc32(label.to_s) % MAX_ID
end
- read_fixture_files
- end
+ attr_reader :table_name, :name, :fixtures, :model_class
- def [](x)
- fixtures[x]
- end
+ def initialize(connection, table_name, class_name, fixture_path)
+ @connection = connection
+ @table_name = table_name
+ @fixture_path = fixture_path
+ @name = table_name # preserve fixture base name
+ @class_name = class_name
- def []=(k,v)
- fixtures[k] = v
- end
+ @fixtures = ActiveSupport::OrderedHash.new
+ @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
- def each(&block)
- fixtures.each(&block)
- end
+ # Should be an AR::Base type class
+ if class_name.is_a?(Class)
+ @table_name = class_name.table_name
+ @connection = class_name.connection
+ @model_class = class_name
+ else
+ @model_class = class_name.constantize rescue nil
+ end
- def size
- fixtures.size
- end
+ read_fixture_files
+ end
- # Return a hash of rows to be inserted. The key is the table, the value is
- # a list of rows to insert to that table.
- def table_rows
- now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
- now = now.to_s(:db)
+ def [](x)
+ fixtures[x]
+ end
- # allow a standard key to be used for doing defaults in YAML
- fixtures.delete('DEFAULTS')
+ def []=(k,v)
+ fixtures[k] = v
+ end
- # track any join tables we need to insert later
- rows = Hash.new { |h,table| h[table] = [] }
+ def each(&block)
+ fixtures.each(&block)
+ end
- rows[table_name] = fixtures.map do |label, fixture|
- row = fixture.to_hash
+ def size
+ fixtures.size
+ end
- if model_class && model_class < ActiveRecord::Base
- # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
- if model_class.record_timestamps
- timestamp_column_names.each do |name|
- row[name] = now unless row.key?(name)
- end
- end
+ # Return a hash of rows to be inserted. The key is the table, the value is
+ # a list of rows to insert to that table.
+ def table_rows
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
+ now = now.to_s(:db)
- # interpolate the fixture label
- row.each do |key, value|
- row[key] = label if value == "$LABEL"
- end
+ # allow a standard key to be used for doing defaults in YAML
+ fixtures.delete('DEFAULTS')
- # generate a primary key if necessary
- if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = Fixtures.identify(label)
- end
+ # track any join tables we need to insert later
+ rows = Hash.new { |h,table| h[table] = [] }
+
+ rows[table_name] = fixtures.map do |label, fixture|
+ row = fixture.to_hash
- # If STI is used, find the correct subclass for association reflection
- reflection_class =
- if row.include?(inheritance_column_name)
- row[inheritance_column_name].constantize rescue model_class
- else
- model_class
+ if model_class && model_class < ActiveRecord::Base
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
+ if model_class.record_timestamps
+ timestamp_column_names.each do |name|
+ row[name] = now unless row.key?(name)
+ end
end
- reflection_class.reflect_on_all_associations.each do |association|
- case association.macro
- when :belongs_to
- # Do not replace association name with association foreign key if they are named the same
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+ # interpolate the fixture label
+ row.each do |key, value|
+ row[key] = label if value == "$LABEL"
+ end
- if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
- # support polymorphic belongs_to as "label (Type)"
- row[association.foreign_type] = $1
- end
+ # generate a primary key if necessary
+ if has_primary_key_column? && !row.include?(primary_key_name)
+ row[primary_key_name] = ActiveRecord::Fixtures.identify(label)
+ end
- row[fk_name] = Fixtures.identify(value)
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class =
+ if row.include?(inheritance_column_name)
+ row[inheritance_column_name].constantize rescue model_class
+ else
+ model_class
end
- when :has_and_belongs_to_many
- if (targets = row.delete(association.name.to_s))
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- table_name = association.options[:join_table]
- rows[table_name].concat targets.map { |target|
- { association.foreign_key => row[primary_key_name],
- association.association_foreign_key => Fixtures.identify(target) }
- }
+
+ reflection_class.reflect_on_all_associations.each do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
+ if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ # support polymorphic belongs_to as "label (Type)"
+ row[association.foreign_type] = $1
+ end
+
+ row[fk_name] = ActiveRecord::Fixtures.identify(value)
+ end
+ when :has_and_belongs_to_many
+ if (targets = row.delete(association.name.to_s))
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ table_name = association.options[:join_table]
+ rows[table_name].concat targets.map { |target|
+ { association.foreign_key => row[primary_key_name],
+ association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
+ }
+ end
end
end
end
- end
-
- row
- end
- rows
- end
- private
- def primary_key_name
- @primary_key_name ||= model_class && model_class.primary_key
+ row
+ end
+ rows
end
- def has_primary_key_column?
- @has_primary_key_column ||= primary_key_name &&
- model_class.columns.any? { |c| c.name == primary_key_name }
- end
+ private
+ def primary_key_name
+ @primary_key_name ||= model_class && model_class.primary_key
+ end
- def timestamp_column_names
- @timestamp_column_names ||=
- %w(created_at created_on updated_at updated_on) & column_names
- end
+ def has_primary_key_column?
+ @has_primary_key_column ||= primary_key_name &&
+ model_class.columns.any? { |c| c.name == primary_key_name }
+ end
- def inheritance_column_name
- @inheritance_column_name ||= model_class && model_class.inheritance_column
- end
+ def timestamp_column_names
+ @timestamp_column_names ||=
+ %w(created_at created_on updated_at updated_on) & column_names
+ end
- def column_names
- @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
- end
+ def inheritance_column_name
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
+ end
- def read_fixture_files
- if File.file?(yaml_file_path)
- read_yaml_fixture_files
- elsif File.file?(csv_file_path)
- read_csv_fixture_files
- else
- raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ def column_names
+ @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
end
- end
- def read_yaml_fixture_files
- yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
- File.file?(f)
- } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
-
- if yaml = parse_yaml_string(yaml_string)
- # If the file is an ordered map, extract its children.
- yaml_value =
- if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
- yaml.value
- else
- [yaml]
- end
+ def read_fixture_files
+ if File.file?(yaml_file_path)
+ read_yaml_fixture_files
+ elsif File.file?(csv_file_path)
+ read_csv_fixture_files
+ else
+ raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ end
+ end
- yaml_value.each do |fixture|
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
- fixture.each do |name, data|
- unless data
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ def read_yaml_fixture_files
+ yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
+ File.file?(f)
+ } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
+
+ if yaml = parse_yaml_string(yaml_string)
+ # If the file is an ordered map, extract its children.
+ yaml_value =
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
+ yaml.value
+ else
+ [yaml]
end
- fixtures[name] = Fixture.new(data, model_class)
+ yaml_value.each do |fixture|
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
+ fixture.each do |name, data|
+ unless data
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ end
+
+ fixtures[name] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
end
end
- end
- def read_csv_fixture_files
- reader = CSV.parse(erb_render(IO.read(csv_file_path)))
- header = reader.shift
- i = 0
- reader.each do |row|
- data = {}
- row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
- fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ def read_csv_fixture_files
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
+ header = reader.shift
+ i = 0
+ reader.each do |row|
+ data = {}
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
+ fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
- end
- def yaml_file_path
- "#{@fixture_path}.yml"
- end
+ def yaml_file_path
+ "#{@fixture_path}.yml"
+ end
- def csv_file_path
- @fixture_path + ".csv"
- end
+ def csv_file_path
+ @fixture_path + ".csv"
+ end
- def yaml_fixtures_key(path)
- File.basename(@fixture_path).split(".").first
- end
+ def yaml_fixtures_key(path)
+ File.basename(@fixture_path).split(".").first
+ end
- def parse_yaml_string(fixture_content)
- YAML::load(erb_render(fixture_content))
- rescue => error
- raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
- end
+ def parse_yaml_string(fixture_content)
+ YAML::load(erb_render(fixture_content))
+ rescue => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
+ end
- def erb_render(fixture_content)
- ERB.new(fixture_content).result
- end
-end
+ def erb_render(fixture_content)
+ ERB.new(fixture_content).result
+ end
+ end
-class Fixture #:nodoc:
- include Enumerable
+ class Fixture #:nodoc:
+ include Enumerable
- class FixtureError < StandardError #:nodoc:
- end
+ class FixtureError < StandardError #:nodoc:
+ end
- class FormatError < FixtureError #:nodoc:
- end
+ class FormatError < FixtureError #:nodoc:
+ end
- attr_reader :model_class, :fixture
+ attr_reader :model_class, :fixture
- def initialize(fixture, model_class)
- @fixture = fixture
- @model_class = model_class
- end
+ def initialize(fixture, model_class)
+ @fixture = fixture
+ @model_class = model_class
+ end
- def class_name
- model_class.name if model_class
- end
+ def class_name
+ model_class.name if model_class
+ end
- def each
- fixture.each { |item| yield item }
- end
+ def each
+ fixture.each { |item| yield item }
+ end
- def [](key)
- fixture[key]
- end
+ def [](key)
+ fixture[key]
+ end
- alias :to_hash :fixture
+ alias :to_hash :fixture
- def find
- if model_class
- model_class.find(fixture[model_class.primary_key])
- else
- raise FixtureClassNotFound, "No class attached to find."
+ def find
+ if model_class
+ model_class.find(fixture[model_class.primary_key])
+ else
+ raise FixtureClassNotFound, "No class attached to find."
+ end
end
end
end
@@ -832,7 +837,7 @@ module ActiveRecord
self.pre_loaded_fixtures = false
self.fixture_class_names = Hash.new do |h, table_name|
- h[table_name] = Fixtures.find_table_name(table_name)
+ h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name)
end
end
@@ -944,7 +949,7 @@ module ActiveRecord
ActiveRecord::Base.connection.begin_db_transaction
# Load fixtures for every test.
else
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
@@already_loaded_fixtures[self.class] = nil
@loaded_fixtures = load_fixtures
end
@@ -957,7 +962,7 @@ module ActiveRecord
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
unless run_in_transaction?
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
end
# Rollback changes if a transaction is active.
@@ -970,7 +975,7 @@ module ActiveRecord
private
def load_fixtures
- fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
+ fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
Hash[fixtures.map { |f| [f.name, f] }]
end
@@ -979,16 +984,16 @@ module ActiveRecord
def instantiate_fixtures
if pre_loaded_fixtures
- raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
+ raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty?
unless @@required_fixture_classes
- self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
+ self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys
@@required_fixture_classes = true
end
- Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
@loaded_fixtures.each do |fixture_name, fixtures|
- Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 9eb47ad99f..b15b5a8133 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -12,10 +12,36 @@ module ActiveRecord
# In order to enable IdentityMap, set <tt>config.active_record.identity_map = true</tt>
# in your <tt>config/application.rb</tt> file.
#
- # IdentityMap is disabled by default.
+ # IdentityMap is disabled by default and still in development (i.e. use it with care).
+ #
+ # == Associations
+ #
+ # Active Record Identity Map does not track associations yet. For example:
+ #
+ # comment = @post.comments.first
+ # comment.post = nil
+ # @post.comments.include?(comment) #=> true
+ #
+ # Ideally, the example above would return false, removing the comment object from the
+ # post association when the association is nullified. This may cause side effects, as
+ # in the situation below, if Identity Map is enabled:
+ #
+ # Post.has_many :comments, :dependent => :destroy
+ #
+ # comment = @post.comments.first
+ # comment.post = nil
+ # comment.save
+ # Post.destroy(@post.id)
+ #
+ # Without using Identity Map, the code above will destroy the @post object leaving
+ # the comment object intact. However, once we enable Identity Map, the post loaded
+ # by Post.destroy is exactly the same object as the object @post. As the object @post
+ # still has the comment object in @post.comments, once Identity Map is enabled, the
+ # comment object will be accidently removed.
+ #
+ # This inconsistency is meant to be fixed in future Rails releases.
#
module IdentityMap
- extend ActiveSupport::Concern
class << self
def enabled=(flag)
@@ -49,11 +75,11 @@ module ActiveRecord
end
def get(klass, primary_key)
- record = repository[klass.symbolized_base_class][primary_key]
+ record = repository[klass.symbolized_sti_name][primary_key]
if record.is_a?(klass)
ActiveSupport::Notifications.instrument("identity.active_record",
- :line => "From Identity Map (id: #{primary_key})",
+ :line => "From Identity Map (id: #{primary_key})",
:name => "#{klass} Loaded",
:connection_id => object_id)
@@ -64,15 +90,15 @@ module ActiveRecord
end
def add(record)
- repository[record.class.symbolized_base_class][record.id] = record
+ repository[record.class.symbolized_sti_name][record.id] = record
end
def remove(record)
- repository[record.class.symbolized_base_class].delete(record.id)
+ repository[record.class.symbolized_sti_name].delete(record.id)
end
- def remove_by_id(symbolized_base_class, id)
- repository[symbolized_base_class].delete(id)
+ def remove_by_id(symbolized_sti_name, id)
+ repository[symbolized_sti_name].delete(id)
end
def clear
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 9a31675782..cdedcde0eb 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -94,7 +94,7 @@ module ActiveRecord
relation = self.class.unscoped
stmt = relation.where(
- relation.table[self.class.primary_key].eq(quoted_id).and(
+ relation.table[self.class.primary_key].eq(id).and(
relation.table[lock_col].eq(quote_value(previous_lock_value))
)
).arel.compile_update(arel_attributes_values(false, false, attribute_names))
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 0893d7e337..c723436330 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -110,8 +110,8 @@ module ActiveRecord
next unless respond_to?(callback)
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
unless klass.respond_to?(callback_meth)
- klass.send(:define_method, callback_meth) do
- observer.send(callback, self)
+ klass.send(:define_method, callback_meth) do |&block|
+ observer.send(callback, self, &block)
end
klass.send(callback, callback_meth)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b4531ed35f..b9041f44d8 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -146,7 +146,7 @@ module ActiveRecord
# will fail and false will be returned.
#
# When updating model attributes, mass-assignment security protection is respected.
- # If no +:as+ option is supplied then the +:default+ scope will be used.
+ # If no +:as+ option is supplied then the +:default+ role will be used.
# If you want to bypass the protection given by +attr_protected+ and
# +attr_accessible+ then you can do so using the +:without_protection+ option.
def update_attributes(attributes, options = {})
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 5703fac033..85ad43b35f 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -203,18 +203,18 @@ db_namespace = namespace :db do
# only files matching "20091231235959_some_name.rb" pattern
if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
status = db_list.delete(match_data[1]) ? 'up' : 'down'
- file_list << [status, match_data[1], match_data[2]]
+ file_list << [status, match_data[1], match_data[2].humanize]
end
end
+ db_list.map! do |version|
+ ['up', version, '********** NO FILE **********']
+ end
# output
puts "\ndatabase: #{config['database']}\n\n"
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
- file_list.each do |file|
- puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
- end
- db_list.each do |version|
- puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***"
+ (db_list + file_list).sort_by {|migration| migration[1]}.each do |migration|
+ puts "#{migration[0].center(8)} #{migration[1].ljust(14)} #{migration[2]}"
end
puts
end
@@ -305,7 +305,7 @@ db_namespace = namespace :db do
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
- Fixtures.create_fixtures(fixtures_dir, fixture_file)
+ ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -316,13 +316,13 @@ db_namespace = namespace :db do
label, id = ENV['LABEL'], ENV['ID']
raise 'LABEL or ID required' if label.blank? && id.blank?
- puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label
+ puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::Fixtures.identify(label)}.) if label
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
- key_id = Fixtures.identify(key)
+ key_id = ActiveRecord::Fixtures.identify(key)
if key == label || key_id == id.to_i
puts "#{file}: #{key} (#{key_id})"
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 869eebfa34..a8a52867ce 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -197,7 +197,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 = except(:order)
+ relation = reorder(nil)
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 57c9921ea8..32d1cff6c3 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -375,7 +375,12 @@ module ActiveRecord
if loaded?
@records.last
else
- @last ||= reverse_order.limit(1).to_a[0]
+ @last ||=
+ if offset_value || limit_value
+ to_a.last
+ else
+ reverse_order.limit(1).to_a[0]
+ end
end
end
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 7e77aefb21..98e21db908 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -83,6 +83,8 @@ module ActiveRecord
cattr_accessor :data_column_name
self.data_column_name = 'data'
+ attr_accessible :session_id, :data, :marshaled_data
+
before_save :marshal_data!
before_save :raise_on_session_data_overflow!
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 43015098c9..292c7efebb 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
private
# custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
def create_test_fixtures(*fixture_names)
- Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
end
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
new file mode 100644
index 0000000000..cd9c1041dc
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -0,0 +1,50 @@
+require "cases/helper"
+require 'models/topic'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class Mysql2Adapter
+ class BindParameterTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_update_question_marks
+ str = "foo?bar"
+ x = Topic.find :first
+ x.title = str
+ x.content = str
+ x.save!
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_create_question_marks
+ str = "foo?bar"
+ x = Topic.create!(:title => str, :content => str)
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_update_null_bytes
+ str = "foo\0bar"
+ x = Topic.find :first
+ x.title = str
+ x.content = str
+ x.save!
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_create_null_bytes
+ str = "foo\0bar"
+ x = Topic.create!(:title => str, :content => str)
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 1efa7deaeb..752b864818 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
private
# custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
def create_test_fixtures(*fixture_names)
- Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
end
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 247decc67b..dc2481456b 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -66,6 +66,35 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'exotic', bulb.name
end
+ def test_create_from_association_with_nil_values_should_work
+ car = Car.create(:name => 'honda')
+
+ bulb = car.bulbs.new(nil)
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.build(nil)
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.create(nil)
+ assert_equal 'defaulty', bulb.name
+ end
+
+ def test_create_from_association_set_owner_attributes_by_passing_protection
+ Bulb.attr_protected :car_id
+ car = Car.create(:name => 'honda')
+
+ bulb = car.bulbs.new
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.build
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.create
+ assert_equal car.id, bulb.car_id
+ ensure
+ Bulb.attr_protected :id
+ end
+
# When creating objects on the association, we must not do it within a scope (even though it
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5ee3b2d776..1775ba9999 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -575,6 +575,29 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
+ def test_multiparameter_attributes_on_time_with_no_date
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_invalid_time_params
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64",
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
def test_multiparameter_attributes_on_time_with_old_date
attributes = {
"written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
@@ -586,6 +609,82 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
end
+ def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
+ "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 0, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 2, topic.written_on.sec
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 24, topic.written_on.min
+ assert_equal 0, topic.written_on.sec
+ end
+ def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 02, topic.written_on.sec
+ end
+
def test_multiparameter_attributes_on_time_with_utc
ActiveRecord::Base.default_timezone = :utc
attributes = {
@@ -692,6 +791,42 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal address, customer.address
end
+ def test_multiparameter_assignment_of_aggregation_out_of_order
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
+ customer.attributes = attributes
+ assert_equal address, customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_missing_values
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_blank_values
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ assert_equal Address.new(nil, "The City", "The Country"), customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_large_index
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index be4ba18555..4e75eafe3d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -683,6 +683,27 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
end
+ def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(2).limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(3)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
def test_find_all_by_one_attribute
topics = Topic.find_all_by_content("Have a nice day")
assert_equal 2, topics.size
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 3e20155210..2bf192e2c6 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -36,7 +36,7 @@ class FixturesTest < ActiveRecord::TestCase
FIXTURES.each do |name|
fixtures = nil
assert_nothing_raised { fixtures = create_fixtures(name).first }
- assert_kind_of(Fixtures, fixtures)
+ assert_kind_of(ActiveRecord::Fixtures, fixtures)
fixtures.each { |_name, fixture|
fixture.each { |key, value|
assert_match(MATCH_ATTRIBUTE_NAME, key)
@@ -46,7 +46,7 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_create_fixtures
- Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
assert Parrot.find_by_name('Curious George'), 'George is in the database'
end
@@ -54,7 +54,7 @@ class FixturesTest < ActiveRecord::TestCase
fixtures_array = nil
assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) }
assert_kind_of(Array, fixtures_array)
- fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) }
+ fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) }
end
def test_attributes
@@ -75,7 +75,7 @@ class FixturesTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.supports_migrations?
def test_inserts_with_pre_and_suffix
# Reset cache to make finds on the new table work
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
t.column :title, :string
@@ -150,11 +150,11 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_empty_yaml_fixture
- assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts")
end
def test_empty_yaml_fixture_with_a_comment_in_it
- assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies")
end
def test_nonexistent_fixture_file
@@ -164,23 +164,23 @@ class FixturesTest < ActiveRecord::TestCase
assert Dir[nonexistent_fixture_path+"*"].empty?
assert_raise(FixturesFileNotFound) do
- Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path)
+ ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path)
end
end
def test_dirty_dirty_yaml_file
- assert_raise(Fixture::FormatError) do
- Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses")
+ assert_raise(ActiveRecord::Fixture::FormatError) do
+ ActiveRecord::Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses")
end
end
def test_empty_csv_fixtures
- assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
end
def test_omap_fixtures
assert_nothing_raised do
- fixtures = Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered")
+ fixtures = ActiveRecord::Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered")
i = 0
fixtures.each do |name, fixture|
@@ -220,7 +220,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
def setup
@instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')]
- Fixtures.reset_cache # make sure tables get reinitialized
+ ActiveRecord::Fixtures.reset_cache # make sure tables get reinitialized
end
def test_resets_to_min_pk_with_specified_pk_and_sequence
@@ -524,13 +524,13 @@ class FasterFixturesTest < ActiveRecord::TestCase
def load_extra_fixture(name)
fixture = create_fixtures(name).first
- assert fixture.is_a?(Fixtures)
+ assert fixture.is_a?(ActiveRecord::Fixtures)
@loaded_fixtures[fixture.table_name] = fixture
end
def test_cache
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories')
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors')
assert_no_queries do
create_fixtures('categories')
@@ -538,7 +538,7 @@ class FasterFixturesTest < ActiveRecord::TestCase
end
load_extra_fixture('posts')
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
self.class.setup_fixture_accessors('posts')
assert_equal 'Welcome to the weblog', posts(:welcome).title
end
@@ -548,17 +548,17 @@ class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
def test_identifies_strings
- assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo"))
- assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO"))
+ assert_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("foo"))
+ assert_not_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("FOO"))
end
def test_identifies_symbols
- assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo))
+ assert_equal(ActiveRecord::Fixtures.identify(:foo), ActiveRecord::Fixtures.identify(:foo))
end
def test_identifies_consistently
- assert_equal 207281424, Fixtures.identify(:ruby)
- assert_equal 1066363776, Fixtures.identify(:sapphire_2)
+ assert_equal 207281424, ActiveRecord::Fixtures.identify(:ruby)
+ assert_equal 1066363776, ActiveRecord::Fixtures.identify(:sapphire_2)
end
TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index fd20f1b120..fbb4ee6f7b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -104,7 +104,7 @@ class ActiveSupport::TestCase
self.use_transactional_fixtures = true
def create_fixtures(*table_names, &block)
- Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
+ ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
end
end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index 649715fbb5..a0e16400d2 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -129,6 +129,41 @@ class IdentityMapTest < ActiveRecord::TestCase
end
##############################################################################
+ # Tests checking if IM is functioning properly on classes with multiple #
+ # types of inheritance #
+ ##############################################################################
+
+ def test_inherited_without_type_attribute_without_identity_map
+ ActiveRecord::IdentityMap.without do
+ p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
+ p2 = Pirate.find(p1.id)
+ assert_not_same(p1, p2)
+ end
+ end
+
+ def test_inherited_with_type_attribute_without_identity_map
+ ActiveRecord::IdentityMap.without do
+ c = comments(:sub_special_comment)
+ c1 = SubSpecialComment.find(c.id)
+ c2 = Comment.find(c.id)
+ assert_same(c1.class, c2.class)
+ end
+ end
+
+ def test_inherited_without_type_attribute
+ p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
+ p2 = Pirate.find(p1.id)
+ assert_not_same(p1, p2)
+ end
+
+ def test_inherited_with_type_attribute
+ c = comments(:sub_special_comment)
+ c1 = SubSpecialComment.find(c.id)
+ c2 = Comment.find(c.id)
+ assert_same(c1, c2)
+ end
+
+ ##############################################################################
# Tests checking dirty attribute behaviour with IM #
##############################################################################
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 6cd8494c9e..643e949087 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -107,6 +107,23 @@ class ValidatedCommentObserver < ActiveRecord::Observer
end
end
+
+class AroundTopic < Topic
+end
+
+class AroundTopicObserver < ActiveRecord::Observer
+ observe :around_topic
+ def topic_ids
+ @topic_ids ||= []
+ end
+
+ def around_save(topic)
+ topic_ids << topic.id
+ yield(topic)
+ topic_ids << topic.id
+ end
+end
+
class LifecycleTest < ActiveRecord::TestCase
fixtures :topics, :developers, :minimalistics
@@ -206,6 +223,14 @@ class LifecycleTest < ActiveRecord::TestCase
assert_equal developer, SalaryChecker.instance.last_saved
end
+ test "around filter from observer should accept block" do
+ observer = AroundTopicObserver.instance
+ topic = AroundTopic.new
+ topic.save
+ assert_nil observer.topic_ids.first
+ assert_not_nil observer.topic_ids.last
+ end
+
def test_observer_is_called_once
observer = DeveloperObserver.instance # activate
observer.calls.clear
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 636a709924..61baa55027 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -5,6 +5,7 @@ require 'models/job'
require 'models/reader'
require 'models/legacy_thing'
require 'models/reference'
+require 'models/string_key_object'
class LockWithoutDefault < ActiveRecord::Base; end
@@ -18,7 +19,40 @@ class ReadonlyFirstNamePerson < Person
end
class OptimisticLockingTest < ActiveRecord::TestCase
- fixtures :people, :legacy_things, :references
+ fixtures :people, :legacy_things, :references, :string_key_objects
+
+ def test_non_integer_lock_existing
+ s1 = StringKeyObject.find("record1")
+ s2 = StringKeyObject.find("record1")
+ assert_equal 0, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s1.name = 'updated record'
+ s1.save!
+ assert_equal 1, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s2.name = 'doubly updated record'
+ assert_raise(ActiveRecord::StaleObjectError) { s2.save! }
+ end
+
+ def test_non_integer_lock_destroy
+ s1 = StringKeyObject.find("record1")
+ s2 = StringKeyObject.find("record1")
+ assert_equal 0, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s1.name = 'updated record'
+ s1.save!
+ assert_equal 1, s1.lock_version
+ assert_equal 0, s2.lock_version
+ assert_raise(ActiveRecord::StaleObjectError) { s2.destroy }
+
+ assert s1.destroy
+ assert s1.frozen?
+ assert s1.destroyed?
+ assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") }
+ end
def test_lock_existing
p1 = Person.find(1)
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index fbbae99e8b..062a642e50 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -87,7 +87,11 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
end
- def test_assign_attributes_uses_default_scope_when_no_scope_is_provided
+ def test_mass_assigning_does_not_choke_on_nil
+ Firm.new.assign_attributes(nil)
+ end
+
+ def test_assign_attributes_uses_default_role_when_no_role_is_provided
p = LoosePerson.new
p.assign_attributes(attributes_hash)
@@ -101,28 +105,28 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_all_attributes(p)
end
- def test_assign_attributes_with_default_scope_and_attr_protected_attributes
+ def test_assign_attributes_with_default_role_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :default)
assert_default_attributes(p)
end
- def test_assign_attributes_with_admin_scope_and_attr_protected_attributes
+ def test_assign_attributes_with_admin_role_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_assign_attributes_with_default_scope_and_attr_accessible_attributes
+ def test_assign_attributes_with_default_role_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :default)
assert_default_attributes(p)
end
- def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes
+ def test_assign_attributes_with_admin_role_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :admin)
@@ -153,25 +157,25 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_default_attributes(p, true)
end
- def test_new_with_admin_scope_with_attr_accessible_attributes
+ def test_new_with_admin_role_with_attr_accessible_attributes
p = TightPerson.new(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_new_with_admin_scope_with_attr_protected_attributes
+ def test_new_with_admin_role_with_attr_protected_attributes
p = LoosePerson.new(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_create_with_admin_scope_with_attr_accessible_attributes
+ def test_create_with_admin_role_with_attr_accessible_attributes
p = TightPerson.create(attributes_hash, :as => :admin)
assert_admin_attributes(p, true)
end
- def test_create_with_admin_scope_with_attr_protected_attributes
+ def test_create_with_admin_role_with_attr_protected_attributes
p = LoosePerson.create(attributes_hash, :as => :admin)
assert_admin_attributes(p, true)
@@ -230,12 +234,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -257,12 +261,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -284,12 +288,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -318,12 +322,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -345,12 +349,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -372,12 +376,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -406,12 +410,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -433,12 +437,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -460,12 +464,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b066575af8..57d1441128 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -336,6 +336,10 @@ class PersistencesTest < ActiveRecord::TestCase
assert !Topic.find(1).approved?
end
+ def test_update_attribute_does_not_choke_on_nil
+ assert Topic.find(1).update_attributes(nil)
+ end
+
def test_update_attribute_for_readonly_attribute
minivan = Minivan.find('m1')
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 864b3d4846..c215602567 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -462,4 +462,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
end
+
+ def test_default_scope_order_ignored_by_aggregations
+ assert_equal DeveloperOrderedBySalary.all.count, DeveloperOrderedBySalary.count
+ end
end
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
index cee5ddd003..669c0b7b4d 100644
--- a/activerecord/test/cases/session_store/session_test.rb
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -21,6 +21,12 @@ module ActiveRecord
assert_equal 'sessions', Session.table_name
end
+ def test_accessible_attributes
+ assert Session.accessible_attributes.include?(:session_id)
+ assert Session.accessible_attributes.include?(:data)
+ assert Session.accessible_attributes.include?(:marshaled_data)
+ end
+
def test_create_table!
assert !Session.table_exists?
Session.create_table!
diff --git a/activerecord/test/fixtures/mateys.yml b/activerecord/test/fixtures/mateys.yml
index 9ecdd4ecd5..d3690955fc 100644
--- a/activerecord/test/fixtures/mateys.yml
+++ b/activerecord/test/fixtures/mateys.yml
@@ -1,4 +1,4 @@
blackbeard_to_redbeard:
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
- target_id: <%= Fixtures.identify(:redbeard) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
+ target_id: <%= ActiveRecord::Fixtures.identify(:redbeard) %>
weight: 10
diff --git a/activerecord/test/fixtures/parrots_pirates.yml b/activerecord/test/fixtures/parrots_pirates.yml
index 6b17a37d68..66472243c7 100644
--- a/activerecord/test/fixtures/parrots_pirates.yml
+++ b/activerecord/test/fixtures/parrots_pirates.yml
@@ -1,7 +1,7 @@
george_blackbeard:
- parrot_id: <%= Fixtures.identify(:george) %>
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
+ parrot_id: <%= ActiveRecord::Fixtures.identify(:george) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
louis_blackbeard:
- parrot_id: <%= Fixtures.identify(:louis) %>
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
+ parrot_id: <%= ActiveRecord::Fixtures.identify(:louis) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
diff --git a/activerecord/test/fixtures/string_key_objects.yml b/activerecord/test/fixtures/string_key_objects.yml
new file mode 100644
index 0000000000..fa1299915b
--- /dev/null
+++ b/activerecord/test/fixtures/string_key_objects.yml
@@ -0,0 +1,7 @@
+first:
+ id: record1
+ name: first record
+
+second:
+ id: record2
+ name: second record
diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb
new file mode 100644
index 0000000000..f8d4c6e0e4
--- /dev/null
+++ b/activerecord/test/models/string_key_object.rb
@@ -0,0 +1,3 @@
+class StringKeyObject < ActiveRecord::Base
+ set_primary_key :id
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 9479242e4f..c20304c0e2 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -543,6 +543,12 @@ ActiveRecord::Schema.define do
t.string :sponsorable_type
end
+ create_table :string_key_objects, :id => false, :primary_key => :id, :force => true do |t|
+ t.string :id
+ t.string :name
+ t.integer :lock_version, :null => false, :default => 0
+ end
+
create_table :students, :force => true do |t|
t.string :name
end
diff --git a/activeresource/lib/active_resource.rb b/activeresource/lib/active_resource.rb
index 186865f811..0794a1a800 100644
--- a/activeresource/lib/active_resource.rb
+++ b/activeresource/lib/active_resource.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2006 David Heinemeier Hansson
+# Copyright (c) 2006-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 69e9cbfd42..ca8973f001 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -19,7 +19,7 @@ advantage of the new ClassCache.
* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White]
-*Rails 3.0.2 (unreleased)*
+*Rails 3.0.2 (November 15, 2010)*
* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White]
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 6b87774978..a846f81c12 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005 David Heinemeier Hansson
+# Copyright (c) 2005-2011 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 8c56a21ef7..a94446acde 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -2,6 +2,7 @@ require 'active_support/concern'
require 'active_support/ordered_options'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Configurable provides a <tt>config</tt> method to store and retrieve
@@ -51,14 +52,16 @@ module ActiveSupport
# user.allowed_access # => true
#
def config_accessor(*names)
+ options = names.extract_options!
+
names.each do |name|
- code, line = <<-RUBY, __LINE__ + 1
- def #{name}; config.#{name}; end
- def #{name}=(value); config.#{name} = value; end
- RUBY
+ reader, line = "def #{name}; config.#{name}; end", __LINE__
+ writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__
- singleton_class.class_eval code, __FILE__, line
- class_eval code, __FILE__, line
+ singleton_class.class_eval reader, __FILE__, line
+ singleton_class.class_eval writer, __FILE__, line
+ class_eval reader, __FILE__, line unless options[:instance_reader] == false
+ class_eval writer, __FILE__, line unless options[:instance_writer] == false
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 769ead9544..338104fd05 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,6 +1,7 @@
require 'date'
require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
+require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
@@ -13,10 +14,10 @@ class Date
}
# Ruby 1.9 has Date#to_time which converts to localtime only.
- remove_method :to_time if method_defined?(:to_time)
+ remove_possible_method :to_time
# Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
- remove_method :xmlschema if method_defined?(:xmlschema)
+ remove_possible_method :xmlschema
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
#
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 61a1d88b0e..102378a029 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -108,7 +108,8 @@ class Hash
raise "can't typecast #{entries.inspect}"
end
end
- elsif value['type'] == 'file' || value["__content__"].present?
+ elsif value['type'] == 'file' ||
+ (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
content = value["__content__"]
if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index aad4b61e16..c2a6476604 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -9,4 +9,16 @@ class Hash
def with_indifferent_access
ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
end
+
+ # Called when object is nested under an object that receives
+ # #with_indifferent_access. This method with be called on the current object
+ # by the enclosing object and is aliased to #with_indifferent_access by
+ # default. Subclasses of Hash may overwrite this method to return +self+ if
+ # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be
+ # desirable.
+ #
+ # b = {:b => 1}
+ # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access
+ #
+ alias nested_under_indifferent_access with_indifferent_access
end
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 1a4d918ce7..457d3f5b62 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -37,6 +37,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
time.instance_eval do
if zone = defined?(@_zone) && remove_instance_variable('@_zone')
ary = to_a
+ ary[0] += subsec if ary[0] == sec
ary[-1] = zone
utc? ? Time.utc(*ary) : Time.local(*ary)
else
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index 4d1cfacc95..e2a8b4d4e3 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -1,5 +1,3 @@
-require 'active_support/dependencies'
-
module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
@@ -18,12 +16,16 @@ module ActiveSupport
end
def self.clear
- @@direct_descendants.each do |klass, descendants|
- if ActiveSupport::Dependencies.autoloaded?(klass)
- @@direct_descendants.delete(klass)
- else
- descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ if defined? ActiveSupport::Dependencies
+ @@direct_descendants.each do |klass, descendants|
+ if ActiveSupport::Dependencies.autoloaded?(klass)
+ @@direct_descendants.delete(klass)
+ else
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ end
end
+ else
+ @@direct_descendants.clear
end
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 79a0de7940..8ec4f6e09a 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -140,8 +140,8 @@ module ActiveSupport
end
def convert_value(value)
- if value.class == Hash
- self.class.new_from_hash_copying_default(value)
+ if value.is_a? Hash
+ value.nested_under_indifferent_access
elsif value.is_a?(Array)
value.dup.replace(value.map { |e| convert_value(e) })
else
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index de49750083..a2c4f7bfda 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -135,11 +135,13 @@ module ActiveSupport
# ordinalize(2) # => "2nd"
# ordinalize(1002) # => "1002nd"
# ordinalize(1003) # => "1003rd"
+ # ordinalize(-11) # => "-11th"
+ # ordinalize(-1021) # => "-1021st"
def ordinalize(number)
- if (11..13).include?(number.to_i % 100)
+ if (11..13).include?(number.to_i.abs % 100)
"#{number}th"
else
- case number.to_i % 10
+ case number.to_i.abs % 10
when 1; "#{number}st"
when 2; "#{number}nd"
when 3; "#{number}rd"
@@ -148,4 +150,4 @@ module ActiveSupport
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 82b8a7e148..1fafc36ee8 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -205,7 +205,9 @@ class Regexp
end
module Enumerable
- def as_json(options = nil) to_a end #:nodoc:
+ def as_json(options = nil) #:nodoc:
+ to_a.as_json(options)
+ end
end
class Array
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 392e33edbc..3e54134c5c 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -1,5 +1,6 @@
require 'active_support/log_subscriber'
require 'active_support/buffered_logger'
+require 'active_support/notifications'
module ActiveSupport
class LogSubscriber
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index fbc40d1b69..762a64a881 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -43,6 +43,10 @@ module ActiveSupport
end
end
+ def nested_under_indifferent_access
+ self
+ end
+
# Hash is ordered in Ruby 1.9!
if RUBY_VERSION < '1.9'
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index cddfcddb57..6e12404ad4 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/string/inflections'
module ActiveSupport
# = XmlMini
@@ -138,7 +139,9 @@ module ActiveSupport
protected
def _dasherize(key)
- key.gsub(/(?!^[_]*)_(?![_]*$)/, '-')
+ # $2 must be a non-greedy regex for this to work
+ left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3]
+ "#{left}#{middle.tr('_ ', '--')}#{right}"
end
# TODO: Add support for other encodings
diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb
index 8445af8d25..fc2d54515d 100644
--- a/activesupport/test/class_cache_test.rb
+++ b/activesupport/test/class_cache_test.rb
@@ -58,7 +58,7 @@ module ActiveSupport
assert @cache.key?(ClassCacheTest.name)
end
- def test_new_rejects_strings
+ def test_new_rejects_strings_when_called_on_a_new_string
assert_deprecated do
@cache.new ClassCacheTest.name
end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index 2b28e61815..c6d8191298 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -5,6 +5,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
class Parent
include ActiveSupport::Configurable
config_accessor :foo
+ config_accessor :bar, :instance_reader => false, :instance_writer => false
end
class Child < Parent
@@ -36,6 +37,12 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
assert_equal :bar, Parent.config.foo
end
+ test "configuration accessors is not available on instance" do
+ instance = Parent.new
+ assert !instance.respond_to?(:bar)
+ assert !instance.respond_to?(:bar=)
+ end
+
test "configuration hash is available on instance" do
instance = Parent.new
assert_equal :bar, instance.config.foo
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index c0b529d9f8..c8312aa653 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/inflector'
require 'active_support/time'
require 'active_support/json'
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 012b956d7f..4557a10688 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -16,6 +16,12 @@ class HashExtTest < Test::Unit::TestCase
class SubclassingHash < Hash
end
+ class NonIndifferentHash < Hash
+ def nested_under_indifferent_access
+ self
+ end
+ end
+
def setup
@strings = { 'a' => 1, 'b' => 2 }
@symbols = { :a => 1, :b => 2 }
@@ -109,9 +115,12 @@ class HashExtTest < Test::Unit::TestCase
assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
end
- def test_hash_subclass
- flash = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of SubclassingHash, flash["foo"]
+ def test_nested_under_indifferent_access
+ foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
+
+ foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of NonIndifferentHash, foo["foo"]
end
def test_indifferent_assorted
@@ -897,7 +906,13 @@ class HashToXmlTest < Test::Unit::TestCase
hash = Hash.from_xml(xml)
assert_equal "bacon is the best", hash['blog']['name']
end
-
+
+ def test_empty_cdata_from_xml
+ xml = "<data><![CDATA[]]></data>"
+
+ assert_equal "", Hash.from_xml(xml)["data"]
+ end
+
def test_xsd_like_types_from_xml
bacon_xml = <<-EOT
<bacon>
@@ -940,7 +955,7 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
end
-
+
def test_should_use_default_value_for_unknown_key
hash_wia = HashWithIndifferentAccess.new(3)
assert_equal 3, hash_wia[:new_key]
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index f0c289a418..32675c884a 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -3,6 +3,7 @@ require 'date'
require 'abstract_unit'
require 'inflector_test_cases'
+require 'active_support/inflector'
require 'active_support/core_ext/string'
require 'active_support/time'
require 'active_support/core_ext/kernel/reporting'
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 53d497013a..44e02109b1 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -808,4 +808,11 @@ class TimeExtMarshalingTest < Test::Unit::TestCase
assert_equal t.zone, unmarshaled.zone
assert_equal t, unmarshaled
end
+
+ def test_marshalling_preserves_fractional_seconds
+ t = Time.parse('00:00:00.500')
+ unmarshaled = Marshal.load(Marshal.dump(t))
+ assert_equal t.to_f, unmarshaled.to_f
+ assert_equal t, unmarshaled
+ end
end
diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test_cases.rb
index 79fb893592..066ec8549b 100644
--- a/activesupport/test/descendants_tracker_test.rb
+++ b/activesupport/test/descendants_tracker_test_cases.rb
@@ -1,9 +1,4 @@
-require 'abstract_unit'
-require 'test/unit'
-require 'active_support'
-require 'active_support/core_ext/hash/slice'
-
-class DescendantsTrackerTest < Test::Unit::TestCase
+module DescendantsTrackerTestCases
class Parent
extend ActiveSupport::DescendantsTracker
end
@@ -34,7 +29,7 @@ class DescendantsTrackerTest < Test::Unit::TestCase
assert_equal [], Child2.direct_descendants
end
- def test_clear_with_autoloaded_parent_children_and_granchildren
+ def test_clear
mark_as_autoloaded(*ALL) do
ActiveSupport::DescendantsTracker.clear
ALL.each do |k|
@@ -43,35 +38,22 @@ class DescendantsTrackerTest < Test::Unit::TestCase
end
end
- def test_clear_with_autoloaded_children_and_granchildren
- mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
- ActiveSupport::DescendantsTracker.clear
- assert_equal [Child2], Parent.descendants
- assert_equal [], Child2.descendants
- end
- end
-
- def test_clear_with_autoloaded_granchildren
- mark_as_autoloaded Grandchild1, Grandchild2 do
- ActiveSupport::DescendantsTracker.clear
- assert_equal [Child1, Child2], Parent.descendants
- assert_equal [], Child1.descendants
- assert_equal [], Child2.descendants
- end
- end
-
protected
def mark_as_autoloaded(*klasses)
- old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
- ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+ # If ActiveSupport::Dependencies is not loaded, forget about autoloading.
+ # This allows using AS::DescendantsTracker without AS::Dependencies.
+ if defined? ActiveSupport::Dependencies
+ old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
+ ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+ end
old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup
old_descendants.each { |k, v| old_descendants[k] = v.dup }
yield
ensure
- ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded
+ ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded if defined? ActiveSupport::Dependencies
ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants)
end
end \ No newline at end of file
diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb
new file mode 100644
index 0000000000..ae18a56f44
--- /dev/null
+++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -0,0 +1,35 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support/descendants_tracker'
+require 'active_support/dependencies'
+require 'descendants_tracker_test_cases'
+
+class DescendantsTrackerWithAutoloadingTest < Test::Unit::TestCase
+ include DescendantsTrackerTestCases
+
+ def test_clear_with_autoloaded_parent_children_and_granchildren
+ mark_as_autoloaded(*ALL) do
+ ActiveSupport::DescendantsTracker.clear
+ ALL.each do |k|
+ assert ActiveSupport::DescendantsTracker.descendants(k).empty?
+ end
+ end
+ end
+
+ def test_clear_with_autoloaded_children_and_granchildren
+ mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child2], Parent.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ def test_clear_with_autoloaded_granchildren
+ mark_as_autoloaded Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child1, Child2], Parent.descendants
+ assert_equal [], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb
new file mode 100644
index 0000000000..1f0c32dc3f
--- /dev/null
+++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb
@@ -0,0 +1,8 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support/descendants_tracker'
+require 'descendants_tracker_test_cases'
+
+class DescendantsTrackerWithoutAutoloadingTest < Test::Unit::TestCase
+ include DescendantsTrackerTestCases
+end \ No newline at end of file
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 2b144e5931..ec9d92794c 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -215,6 +215,36 @@ module InflectorTestCases
}
OrdinalNumbers = {
+ "-1" => "-1st",
+ "-2" => "-2nd",
+ "-3" => "-3rd",
+ "-4" => "-4th",
+ "-5" => "-5th",
+ "-6" => "-6th",
+ "-7" => "-7th",
+ "-8" => "-8th",
+ "-9" => "-9th",
+ "-10" => "-10th",
+ "-11" => "-11th",
+ "-12" => "-12th",
+ "-13" => "-13th",
+ "-14" => "-14th",
+ "-20" => "-20th",
+ "-21" => "-21st",
+ "-22" => "-22nd",
+ "-23" => "-23rd",
+ "-24" => "-24th",
+ "-100" => "-100th",
+ "-101" => "-101st",
+ "-102" => "-102nd",
+ "-103" => "-103rd",
+ "-104" => "-104th",
+ "-110" => "-110th",
+ "-111" => "-111th",
+ "-112" => "-112th",
+ "-113" => "-113th",
+ "-1000" => "-1000th",
+ "-1001" => "-1001st",
"0" => "0th",
"1" => "1st",
"2" => "2nd",
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index d5fcbf15b7..8cf1a54a99 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -215,6 +215,30 @@ class TestJSONEncoding < Test::Unit::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
+ people = [
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ ]
+ json = people.each.as_json :only => [:address, :city]
+ expected = [
+ { 'address' => { 'city' => 'London' }},
+ { 'address' => { 'city' => 'Paris' }}
+ ]
+
+ assert_equal(expected, json)
+ end
+
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
+ people = [
+ { :name => 'John', :address => { :city => 'London', :country => 'UK' }},
+ { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }}
+ ]
+ json = people.each.to_json :only => [:address, :city]
+
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
+ end
+
def test_struct_encoding
Struct.new('UserNameAndEmail', :name, :email)
Struct.new('UserNameAndDate', :name, :date)
@@ -259,12 +283,3 @@ class TestJSONEncoding < Test::Unit::TestCase
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
end
-
-class JsonOptionsTests < Test::Unit::TestCase
- def test_enumerable_should_passthrough_options_to_elements
- value, options = Object.new, Object.new
- def value.as_json(options) options end
- def options.encode_json(encoder) self end
- assert_equal options, ActiveSupport::JSON.encode(value, options)
- end
-end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 6ebbfdf334..bfff10fff2 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -1,6 +1,7 @@
# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
+require 'active_support/core_ext/string/multibyte'
class String
def __method_for_multibyte_testing_with_integer_result; 1; end
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 50168fa78f..bf99a701a6 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -243,6 +243,11 @@ class OrderedHashTest < Test::Unit::TestCase
assert_equal @other_ordered_hash.keys, @ordered_hash.keys
end
+ def test_nested_under_indifferent_access
+ flash = {:a => ActiveSupport::OrderedHash[:b, 1, :c, 2]}.with_indifferent_access
+ assert_kind_of ActiveSupport::OrderedHash, flash[:a]
+ end
+
def test_each_after_yaml_serialization
values = []
@deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash))
diff --git a/activesupport/test/test_xml_mini.rb b/activesupport/test/xml_mini_test.rb
index 6dbcd1f40b..e2b90ae16e 100644
--- a/activesupport/test/test_xml_mini.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -16,14 +16,6 @@ module XmlMiniTest
assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false)
end
- def test_rename_key_camelizes_with_camelize_false
- assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false)
- end
-
- def test_rename_key_camelizes_with_camelize_nil
- assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil)
- end
-
def test_rename_key_camelizes_with_camelize_true
assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
end
@@ -56,7 +48,7 @@ module XmlMiniTest
assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id")
end
- def test_rename_key_does_not_dasherize_multiple_leading_underscores
+ def test_rename_key_does_not_dasherize_multiple_trailing_underscores
assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__")
end
end
@@ -95,6 +87,16 @@ module XmlMiniTest
@xml.to_tag(:b, "Howdy", @options)
assert_xml "<b>Howdy</b>"
end
+
+ test "#to_tag should dasherize the space when passed a string with spaces as a key" do
+ @xml.to_tag("New York", 33, @options)
+ assert_xml "<New---York type=\"integer\">33</New---York>"
+ end
+
+ test "#to_tag should dasherize the space when passed a symbol with spaces as a key" do
+ @xml.to_tag(:"New York", 33, @options)
+ assert_xml "<New---York type=\"integer\">33</New---York>"
+ end
# TODO: test the remaining functions hidden in #to_tag.
end
end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 0def5bcf6c..21666103de 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,17 @@
*Rails 3.1.0 (unreleased)*
+* Fixed database tasks for jdbc* adapters #jruby
+
+ [Rashmi Yadav]
+
+* Template generation for jdbcpostgresql #jruby
+
+ [Vishnu Atrai]
+
+* Template generation for jdbcmysql and jdbcsqlite3 #jruby
+
+ [Arun Agrawal]
+
* The -j option of the application generator accepts an arbitrary string. If passed "foo",
the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest
requires "foo" and "foo_ujs". As of this writing "prototype-rails" and "jquery-rails"
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 8c408ec06b..e0ccc7a6e6 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -372,7 +372,7 @@ def signup
end
</ruby>
-Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the new() method, or assign attributes=(attributes) a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this:
+Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the +new+ method, or +assign_attributes=+ a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this:
<pre>
"name":http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
@@ -386,7 +386,7 @@ params[:user] # => {:name => “ow3ned”, :admin => true}
So if you create a new user using mass-assignment, it may be too easy to become an administrator.
-Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
+Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3+. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
<ruby>
class Person < ActiveRecord::Base
@@ -410,7 +410,7 @@ To avoid this, Rails provides two class methods in your Active Record class to c
attr_protected :admin
</ruby>
-+attr_protected+ also optionally takes a scope option using :as which allows you to define multiple mass-assignment groupings. If no scope is defined then attributes will be added to the default group.
++attr_protected+ also optionally takes a role option using :as which allows you to define multiple mass-assignment groupings. If no role is defined then attributes will be added to the :default role.
<ruby>
attr_protected :last_login, :as => :admin
@@ -433,7 +433,7 @@ params[:user] # => {:name => "ow3ned", :admin => true}
@user.admin # => true
</ruby>
-When assigning attributes in Active Record using +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example:
+When assigning attributes in Active Record using +attributes=+ the :default role will be used. To assign attributes using different roles you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default role will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example:
<ruby>
@user = User.new
@@ -451,7 +451,7 @@ When assigning attributes in Active Record using +attributes=+, or +update_attri
@user.is_admin # => true
</ruby>
-In a similar way, +new+, +create+ and <tt>create!</tt> methods respect mass-assignment security and accepts either +:as+ or +:without_protection+ options. For example:
+In a similar way, +new+, +create+, <tt>create!</tt>, +update_attributes+, and +update_attributes!+ methods all respect mass-assignment security and accept either +:as+ or +:without_protection+ options. For example:
<ruby>
@user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin)
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index dd01bbab1d..1e4d25f18c 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -157,7 +157,8 @@ module Rails
middleware.use ::Rack::Lock unless config.allow_concurrency
middleware.use ::Rack::Runtime
- middleware.use ::Rails::Rack::Logger
+ middleware.use ::Rack::MethodOverride
+ middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods
middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
@@ -171,7 +172,6 @@ module Rails
end
middleware.use ::ActionDispatch::ParamsParser
- middleware.use ::Rack::MethodOverride
middleware.use ::ActionDispatch::Head
middleware.use ::Rack::ConditionalGet
middleware.use ::Rack::ETag, "no-cache"
@@ -199,4 +199,4 @@ module Rails
require "rails/console/helpers"
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 9c9d85eed6..9f21d273e6 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,4 +1,5 @@
require "active_support/notifications"
+require "active_support/dependencies"
require "active_support/descendants_tracker"
module Rails
@@ -9,7 +10,6 @@ module Rails
initializer :load_environment_hook do end
initializer :load_active_support do
- require 'active_support/dependencies'
require "active_support/all" unless config.active_support.bare
end
@@ -37,6 +37,7 @@ module Rails
)
logger
end
+ at_exit { Rails.logger.flush if Rails.logger.respond_to?(:flush) }
end
# Initialize cache early in the stack so railties can make use of it.
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 4182757346..4a082aedb8 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -6,7 +6,8 @@ aliases = {
"g" => "generate",
"c" => "console",
"s" => "server",
- "db" => "dbconsole"
+ "db" => "dbconsole",
+ "r" => "runner"
}
command = ARGV.shift
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index f0d6ea1687..b0ba76217a 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -23,7 +23,7 @@ module Rails
include_password = false
options = {}
OptionParser.new do |opt|
- opt.banner = "Usage: dbconsole [options] [environment]"
+ opt.banner = "Usage: dbconsole [environment] [options]"
opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
include_password = true
end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 1a91d477ec..ddd08a32ee 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -39,18 +39,12 @@ ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
-begin
- if code_or_file.nil?
- $stderr.puts "Run '#{$0} -h' for help."
- exit 1
- elsif File.exist?(code_or_file)
- $0 = code_or_file
- eval(File.read(code_or_file), nil, code_or_file)
- else
- eval(code_or_file)
- end
-ensure
- if defined? Rails
- Rails.logger.flush if Rails.logger.respond_to?(:flush)
- end
+if code_or_file.nil?
+ $stderr.puts "Run '#{$0} -h' for help."
+ exit 1
+elsif File.exist?(code_or_file)
+ $0 = code_or_file
+ eval(File.read(code_or_file), nil, code_or_file)
+else
+ eval(code_or_file)
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index e447209242..505a4ca2bd 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -55,8 +55,9 @@ module Rails
end
def start
+ url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
- puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}"
+ puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Call with -d to detach" unless options[:daemonize]
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 9be395e989..85c67af19a 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -52,13 +52,13 @@ module Rails
:integration_tool => nil,
:javascripts => true,
:javascript_engine => nil,
- :orm => nil,
+ :orm => false,
:performance_tool => nil,
:resource_controller => :controller,
:scaffold_controller => :scaffold_controller,
:stylesheets => true,
:stylesheet_engine => nil,
- :test_framework => nil,
+ :test_framework => false,
:template_engine => :erb
},
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 3d16304d86..e8709b2ddd 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -120,6 +120,14 @@ module Rails
options[:skip_active_record] ? "" : "gem '#{gem_for_database}'\n"
end
+ def include_all_railties?
+ !options[:skip_active_record] && !options[:skip_test_unit]
+ end
+
+ def comment_if(value)
+ options[value] ? '#' : ''
+ end
+
def rails_gemfile_entry
if options.dev?
<<-GEMFILE.strip_heredoc
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 36bc9e055c..cf317eb21f 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/module/introspection'
require 'rails/generators/base'
require 'rails/generators/generated_attribute'
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index 9f0f1d0e38..7c36f2356e 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -156,6 +156,10 @@ PostgreSQL and SQLite 3.
The default directory structure of a generated Ruby on Rails application:
|-- app
+ | |-- assets
+ | |-- images
+ | |-- javascripts
+ | `-- stylesheets
| |-- controllers
| |-- helpers
| |-- mailers
@@ -172,9 +176,6 @@ The default directory structure of a generated Ruby on Rails application:
| `-- tasks
|-- log
|-- public
- | |-- images
- | |-- javascripts
- | `-- stylesheets
|-- script
|-- test
| |-- fixtures
@@ -188,11 +189,16 @@ The default directory structure of a generated Ruby on Rails application:
| |-- sessions
| `-- sockets
`-- vendor
+ |-- assets
+ `-- stylesheets
`-- plugins
app
Holds all the code that's specific to this particular application.
+app/assets
+ Contains subdirectories for images, stylesheets, and JavaScript files.
+
app/controllers
Holds controllers that should be named like weblogs_controller.rb for
automated URL mapping. All controllers should descend from
@@ -237,8 +243,7 @@ lib
the load path.
public
- The directory available for the web server. Contains subdirectories for
- images, stylesheets, and javascripts. Also contains the dispatchers and the
+ The directory available for the web server. Also contains the dispatchers and the
default HTML files. This should be set as the DOCUMENT_ROOT of your web
server.
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 3723addf2b..8ff80c6fd3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,14 +1,14 @@
require File.expand_path('../boot', __FILE__)
-<% unless options[:skip_active_record] -%>
+<% if include_all_railties? -%>
require 'rails/all'
<% else -%>
# Pick the frameworks you want:
-# require "active_record/railtie"
+<%= 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_test_unit %> require "rails/test_unit/railtie"
<% end -%>
# If you have a Gemfile, require the gems listed there, including any gems
@@ -50,21 +50,12 @@ module <%= app_const_base %>
# config.action_view.javascript_expansions[:defaults] = %w(prototype prototype_ujs)
<% end -%>
-<% if options[:skip_test_unit] -%>
- config.generators.test_framework = false
-<% end -%>
-
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
-<% unless options[:skip_active_record] -%>
- # Enable IdentityMap for Active Record, to disable set to false or remove the line below.
- config.active_record.identity_map = true
-<% end -%>
-
# Enable the asset pipeline
config.assets.enabled = true
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
new file mode 100644
index 0000000000..b03dc3132b
--- /dev/null
+++ b/railties/test/application/assets_test.rb
@@ -0,0 +1,27 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class RoutingTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ test "assets routes have higher priority" do
+ app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();"
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] }
+ end
+ RUBY
+
+ get "/assets/demo.js"
+ assert_match "alert()", last_response.body
+ end
+ end
+end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index b1f7076776..6193e72625 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -12,7 +12,6 @@ end
class ::MyOtherMailObserver < ::MyMailObserver; end
-
module ApplicationTests
class ConfigurationTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
@@ -437,10 +436,35 @@ module ApplicationTests
app_file 'config/initializers/wrap_parameters.rb', <<-RUBY
ActionController::Base.wrap_parameters :format => [:json]
RUBY
+
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ def self.column_names
+ %w(title)
+ end
+ end
+ RUBY
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render :text => params[:post].inspect
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.append do
+ resources :posts
+ end
+ RUBY
+
require "#{app_path}/config/environment"
- require 'action_controller/base'
+ require "rack/test"
+ extend Rack::Test::Methods
- assert_equal [:json], ActionController::Base._wrapper_options[:format]
+ post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json"
+ assert_equal '{"title"=>"foo"}', last_response.body
end
test "config.action_dispatch.ignore_accept_header" do
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 01e6c49d9c..fd6dc46271 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -23,7 +23,8 @@ module ApplicationTests
"Rack::Lock",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
- "Rails::Rack::Logger",
+ "Rack::MethodOverride",
+ "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
"ActionDispatch::ShowExceptions",
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
@@ -36,7 +37,6 @@ module ApplicationTests
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
"ActionDispatch::ParamsParser",
- "Rack::MethodOverride",
"ActionDispatch::Head",
"Rack::ConditionalGet",
"Rack::ETag",
diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb
new file mode 100644
index 0000000000..a29244357c
--- /dev/null
+++ b/railties/test/application/rack/logger_test.rb
@@ -0,0 +1,40 @@
+require "isolation/abstract_unit"
+require "active_support/log_subscriber/test_helper"
+require "rack/test"
+
+module ApplicationTests
+ module RackTests
+ class LoggerTest < Test::Unit::TestCase
+ include ActiveSupport::LogSubscriber::TestHelper
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ require "#{app_path}/config/environment"
+ super
+ end
+
+ def logs
+ @logs ||= @logger.logged(:info)
+ end
+
+ test "logger logs proper HTTP verb and path" do
+ get "/blah"
+ wait
+ assert_match /^Started GET "\/blah"/, logs[0]
+ end
+
+ test "logger logs HTTP verb override" do
+ post "/", {:_method => 'put'}
+ wait
+ assert_match /^Started PUT "\/"/, logs[0]
+ end
+
+ test "logger logs HEAD requests" do
+ post "/", {:_method => 'head'}
+ wait
+ assert_match /^Started HEAD "\/"/, logs[0]
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index a8bcf7beaf..d77c2d14ab 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -73,6 +73,19 @@ module ApplicationTests
assert_match 'custom_assets GET /custom/assets(.:format)', Dir.chdir(app_path){ `rake routes` }
end
+ def test_logger_is_flushed_when_exiting_production_rake_tasks
+ add_to_config <<-RUBY
+ rake_tasks do
+ task :log_something => :environment do
+ Rails.logger.error("Sample log message")
+ end
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `rake log_something RAILS_ENV=production && cat log/production.log` }
+ assert_match "Sample log message", output
+ end
+
def test_model_and_migration_generator_with_change_syntax
Dir.chdir(app_path) do
`rails generate model user username:string password:string`
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 62589c998d..e3a7f8a63c 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -1,14 +1,14 @@
require 'isolation/abstract_unit'
+require 'rack/test'
module ApplicationTests
class RoutingTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
def setup
build_app
boot_rails
- require 'rack/test'
- extend Rack::Test::Methods
end
test "rails/info/properties in development" do
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index e271095636..cc0bd53639 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -39,6 +39,8 @@ DEFAULT_APP_FILES = %w(
class AppGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
arguments [destination_root]
+
+ # brings setup, teardown, and some tests
include SharedGeneratorTests
def default_files
@@ -227,9 +229,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given
run_generator [destination_root, "--skip-test-unit"]
- assert_file "config/application.rb" do |file|
- assert_match /config.generators.test_framework = false/, file
- end
+ assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
+ end
+
+ def test_no_active_record_or_test_unit_if_skips_given
+ run_generator [destination_root, "--skip-test-unit", "--skip-active-record"]
+ assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
+ assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
def test_new_hash_style
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 33c8d83f9c..f637a6a17e 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -22,6 +22,8 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
destination File.join(Rails.root, "tmp/bukkits")
arguments [destination_root]
+
+ # brings setup, teardown, and some tests
include SharedGeneratorTests
def default_files
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index c9c5d2fad2..03fd64ca38 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -1,3 +1,6 @@
+#
+# Tests, setup, and teardown common to the application and plugin generator suites.
+#
module SharedGeneratorTests
def setup
Rails.application = TestApp::Application
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
index e975950b85..e5debf29ae 100644
--- a/railties/test/railties/shared_tests.rb
+++ b/railties/test/railties/shared_tests.rb
@@ -15,11 +15,10 @@ module RailtiesTest
boot_rails
require 'rack/test'
- require 'coffee_script'
extend Rack::Test::Methods
get "/assets/engine.js"
- assert_match "alert();", last_response.body
+ assert_match "alert()", last_response.body
end
def test_copying_migrations