aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md6
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb10
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb38
-rw-r--r--actionpack/test/dispatch/cookies_test.rb2
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb18
-rw-r--r--activerecord/CHANGELOG.md21
-rw-r--r--activerecord/lib/active_record/associations.rb27
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb91
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb78
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb71
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb49
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb34
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb22
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb22
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb46
-rw-r--r--activerecord/lib/active_record/core.rb9
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb41
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb61
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/multiple_db_test.rb4
-rw-r--r--activerecord/test/cases/timestamp_test.rb28
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/company.rb16
-rw-r--r--activerecord/test/models/member.rb5
-rw-r--r--activerecord/test/support/connection.rb4
-rw-r--r--guides/source/association_basics.textile18
-rw-r--r--guides/source/configuring.textile8
-rw-r--r--railties/CHANGELOG.md5
-rw-r--r--railties/lib/rails/engine.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb10
-rw-r--r--railties/test/generators/app_generator_test.rb6
-rw-r--r--railties/test/isolation/abstract_unit.rb1
-rw-r--r--railties/test/railties/engine_test.rb48
-rw-r--r--railties/test/railties/mounted_engine_test.rb15
39 files changed, 403 insertions, 462 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index fd8b38054e..880263ce87 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* Add 'X-Frame-Options' => 'SAMEORIGIN' and
+ 'X-XSS-Protection' => '1; mode=block'
+ as default headers.
+
+ *Egor Homakov*
+
* Allow data attributes to be set as a first-level option for form_for, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH*
* Deprecate `button_to_function` and `link_to_function` helpers.
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index dd5ceb3478..0cdd17bc2e 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -30,9 +30,15 @@ module ActionController
:_recall => request.symbolized_path_parameters
).freeze
- if _routes.equal?(env["action_dispatch.routes"])
+ if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
+ (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
+ (original_script_name = env['SCRIPT_NAME'])
@_url_options.dup.tap do |options|
- options[:script_name] = request.script_name.dup
+ if original_script_name
+ options[:original_script_name] = original_script_name
+ else
+ options[:script_name] = same_origin ? request.script_name.dup : script_name
+ end
options.freeze
end
else
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index e7f3f07390..0dcf1fc4fe 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -19,6 +19,11 @@ module ActionDispatch
:verbose => false
}
+ config.action_dispatch.default_headers = {
+ 'X-Frame-Options' => 'SAMEORIGIN',
+ 'X-XSS-Protection' => '1; mode=block'
+ }
+
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 62c921ff97..32d267d1d6 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -163,9 +163,9 @@ module ActionDispatch
private
def define_named_route_methods(name, route)
- define_url_helper route, :"#{name}_path",
+ define_url_helper route, :"#{name}_path",
route.defaults.merge(:use_route => name, :only_path => true)
- define_url_helper route, :"#{name}_url",
+ define_url_helper route, :"#{name}_url",
route.defaults.merge(:use_route => name, :only_path => false)
end
@@ -226,7 +226,7 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
- attr_accessor :default_url_options, :request_class, :valid_conditions
+ attr_accessor :default_url_options, :request_class
alias :routes :set
@@ -238,13 +238,7 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
-
self.request_class = request_class
- @valid_conditions = { :controller => true, :action => true }
- request_class.public_instance_methods.each { |m|
- @valid_conditions[m] = true
- }
- @valid_conditions.delete(:id)
@append = []
@prepend = []
@@ -375,7 +369,7 @@ module ActionDispatch
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
- conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
+ conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
named_routes[name] = route if name && !named_routes[name]
@@ -412,21 +406,22 @@ module ActionDispatch
end
private :build_path
- def build_conditions(current_conditions, req_predicates, path_values)
+ def build_conditions(current_conditions, path_values)
conditions = current_conditions.dup
- verbs = conditions[:request_method] || []
-
# Rack-Mount requires that :request_method be a regular expression.
# :request_method represents the HTTP verb that matches this route.
#
# Here we munge values before they get sent on to rack-mount.
+ verbs = conditions[:request_method] || []
unless verbs.empty?
conditions[:request_method] = %r[^#{verbs.join('|')}$]
end
- conditions.delete_if { |k,v| !(req_predicates.include?(k) || path_values.include?(k)) }
- conditions
+ conditions.keep_if do |k, _|
+ k == :action || k == :controller ||
+ @request_class.public_method_defined?(k) || path_values.include?(k)
+ end
end
private :build_conditions
@@ -468,7 +463,7 @@ module ActionDispatch
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key)
- @options[key] = @recall.delete(key)
+ @options[key] = @recall.delete(key)
end
end
end
@@ -577,7 +572,8 @@ module ActionDispatch
end
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
- :trailing_slash, :anchor, :params, :only_path, :script_name]
+ :trailing_slash, :anchor, :params, :only_path, :script_name,
+ :original_script_name]
def mounted?
false
@@ -597,7 +593,13 @@ module ActionDispatch
user, password = extract_authentication(options)
recall = options.delete(:_recall)
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
+
+ original_script_name = options.delete(:original_script_name).presence
+ script_name = options.delete(:script_name).presence || _generate_prefix(options)
+
+ if script_name && original_script_name
+ script_name = original_script_name + script_name
+ end
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 2467654a70..347b3b3b5a 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -190,7 +190,7 @@ class CookiesTest < ActionController::TestCase
def test_setting_the_same_value_to_permanent_cookie
request.cookies[:user_name] = 'Jamie'
get :set_permanent_cookie
- assert response.cookies, 'user_name' => 'Jamie'
+ assert_equal response.cookies, 'user_name' => 'Jamie'
end
def test_setting_with_escapable_characters
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index ab2f7612ce..6d75c5ec7a 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -166,18 +166,6 @@ module TestGenerationPrefix
assert_equal "/generate", last_response.body
end
- test "[ENGINE] generating application's url includes default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/pure-awesomeness/blog/url_to_application"
- assert_equal "/something/generate", last_response.body
- end
-
- test "[ENGINE] generating application's url should give higher priority to default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/pure-awesomeness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo'
- assert_equal "/something/generate", last_response.body
- end
-
test "[ENGINE] generating engine's url with polymorphic path" do
get "/pure-awesomeness/blog/polymorphic_path_for_engine"
assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
@@ -200,12 +188,6 @@ module TestGenerationPrefix
assert_equal "/something/awesome/blog/posts/1", last_response.body
end
- test "[APP] generating engine's route should give higher priority to default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/generate", {}, 'SCRIPT_NAME' => "/foo"
- assert_equal "/something/awesome/blog/posts/1", last_response.body
- end
-
test "[APP] generating engine's url with polymorphic path" do
get "/polymorphic_path_for_engine"
assert_equal "/awesome/blog/posts/1", last_response.body
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a99d7fdde8..e6237ef437 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -486,24 +486,13 @@
* Added the `ActiveRecord::NullRelation` class implementing the null
object pattern for the Relation class. *Juanjo Bazán*
-* Added deprecation for the `:dependent => :restrict` association option.
+* Added new `:dependent => :restrict_with_error` option. This will add
+ an error to the model, rather than raising an exception.
- Please note:
-
- * Up until now `has_many` and `has_one`, `:dependent => :restrict`
- option raised a `DeleteRestrictionError` at the time of destroying
- the object. Instead, it will add an error on the model.
-
- * To fix this warning, make sure your code isn't relying on a
- `DeleteRestrictionError` and then add
- `config.active_record.dependent_restrict_raises = false` to your
- application config.
-
- * New rails application would be generated with the
- `config.active_record.dependent_restrict_raises = false` in the
- application config.
+ The `:restrict` option is renamed to `:restrict_with_exception` to
+ make this distinction explicit.
- *Manoj Kumar*
+ *Manoj Kumar & Jon Leighton*
* Added `create_join_table` migration helper to create HABTM join tables
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f5ee4f3ebe..9ba3323bc7 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1094,12 +1094,14 @@ module ActiveRecord
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:dependent]
- # If set to <tt>:destroy</tt> all the associated objects are destroyed
- # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
- # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
- # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
- # <tt>:restrict</tt> an error will be added to the object, preventing its deletion, if any associated
- # objects are present.
+ # Controls what happens to the associated objects when
+ # their owner is destroyed:
+ #
+ # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
+ # * <tt>:delete_all</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
@@ -1203,11 +1205,14 @@ module ActiveRecord
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
# if the real class name is Person, you'll have to specify it with this option.
# [:dependent]
- # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
- # If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
- # If set to <tt>:restrict</tt>, an error will be added to the object, preventing its deletion, if an
- # associated object is present.
+ # Controls what happens to the associated object when
+ # its owner is destroyed:
+ #
+ # * <tt>:destroy</tt> causes the associated object to also be destroyed
+ # * <tt>:delete</tt> causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index ddfc6f6c05..75f72c1a46 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -2,6 +2,11 @@ module ActiveRecord
# = Active Record Belongs To Associations
module Associations
class BelongsToAssociation < SingularAssociation #:nodoc:
+
+ def handle_dependency
+ target.send(options[:dependent]) if load_target
+ end
+
def replace(record)
raise_on_type_mismatch(record) if record
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index c3f32b5ed9..1df876bf62 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -39,6 +39,7 @@ module ActiveRecord::Associations::Builder
def build
validate_options
define_accessors
+ configure_dependency if options[:dependent]
@reflection = model.create_reflection(macro, name, scope, options, model)
super # provides an extension point
@reflection
@@ -52,70 +53,54 @@ module ActiveRecord::Associations::Builder
Association.valid_options
end
- private
-
- def validate_options
- options.assert_valid_keys(valid_options)
- end
+ def validate_options
+ options.assert_valid_keys(valid_options)
+ end
- def define_accessors
- define_readers
- define_writers
- end
+ def define_accessors
+ define_readers
+ define_writers
+ end
- def define_readers
- name = self.name
- mixin.redefine_method(name) do |*params|
- association(name).reader(*params)
+ def define_readers
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}(*args)
+ association(:#{name}).reader(*args)
end
- end
+ CODE
+ end
- def define_writers
- name = self.name
- mixin.redefine_method("#{name}=") do |value|
- association(name).writer(value)
+ def define_writers
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}=(value)
+ association(:#{name}).writer(value)
end
- end
-
- def check_valid_dependent!(dependent, valid_options)
- unless valid_options.include?(dependent)
- valid_options_message = valid_options.map(&:inspect).to_sentence(
- words_connector: ', ', two_words_connector: ' or ', last_word_connector: ' or ')
+ CODE
+ end
- raise ArgumentError, "The :dependent option expects either " \
- "#{valid_options_message} (#{dependent.inspect})"
- end
+ def configure_dependency
+ unless valid_dependent_options.include? options[:dependent]
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
end
- def dependent_restrict_raises?
- ActiveRecord::Base.dependent_restrict_raises == true
+ if options[:dependent] == :restrict
+ ActiveSupport::Deprecation.warn(
+ "The :restrict option is deprecated. Please use :restrict_with_exception instead, which " \
+ "provides the same functionality."
+ )
end
- def dependent_restrict_deprecation_warning
- if dependent_restrict_raises?
- msg = "In the next release, `:dependent => :restrict` will not raise a `DeleteRestrictionError`. "\
- "Instead, it will add an error on the model. To fix this warning, make sure your code " \
- "isn't relying on a `DeleteRestrictionError` and then add " \
- "`config.active_record.dependent_restrict_raises = false` to your application config."
- ActiveSupport::Deprecation.warn msg
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{macro}_dependent_for_#{name}
+ association(:#{name}).handle_dependency
end
- end
+ CODE
- def define_restrict_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- has_one_macro = association(name).reflection.macro == :has_one
- if has_one_macro ? !send(name).nil? : send(name).exists?
- if dependent_restrict_raises?
- raise ActiveRecord::DeleteRestrictionError.new(name)
- else
- key = has_one_macro ? "one" : "many"
- errors.add(:base, :"restrict_dependent_destroy.#{key}",
- :record => self.class.human_attribute_name(name).downcase)
- return false
- end
- end
- end
- end
+ model.before_destroy "#{macro}_dependent_for_#{name}"
+ end
+
+ def valid_dependent_options
+ raise NotImplementedError
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index f205a456f7..2f2600b7fb 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
def macro
@@ -17,72 +16,51 @@ module ActiveRecord::Associations::Builder
reflection = super
add_counter_cache_callbacks(reflection) if options[:counter_cache]
add_touch_callbacks(reflection) if options[:touch]
- configure_dependency
reflection
end
- private
-
- def add_counter_cache_callbacks(reflection)
- cache_column = reflection.counter_cache_column
- name = self.name
+ def add_counter_cache_callbacks(reflection)
+ cache_column = reflection.counter_cache_column
- method_name = "belongs_to_counter_cache_after_create_for_#{name}"
- mixin.redefine_method(method_name) do
- record = send(name)
- record.class.increment_counter(cache_column, record.id) unless record.nil?
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def belongs_to_counter_cache_after_create_for_#{name}
+ record = #{name}
+ record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
end
- model.after_create(method_name)
- method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
- mixin.redefine_method(method_name) do
+ def belongs_to_counter_cache_before_destroy_for_#{name}
unless marked_for_destruction?
- record = send(name)
- record.class.decrement_counter(cache_column, record.id) unless record.nil?
+ record = #{name}
+ record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
end
end
- model.before_destroy(method_name)
+ CODE
- model.send(:module_eval,
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
- )
- end
+ model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
+ model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
- def add_touch_callbacks(reflection)
- name = self.name
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
- touch = options[:touch]
+ klass = reflection.class_name.safe_constantize
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
+ end
- mixin.redefine_method(method_name) do
- record = send(name)
+ def add_touch_callbacks(reflection)
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def belongs_to_touch_after_save_or_destroy_for_#{name}
+ record = #{name}
unless record.nil?
- if touch == true
- record.touch
- else
- record.touch(touch)
- end
+ record.touch #{options[:touch].inspect if options[:touch] != true}
end
end
+ CODE
- model.after_save(method_name)
- model.after_touch(method_name)
- model.after_destroy(method_name)
- end
-
- def configure_dependency
- if dependent = options[:dependent]
- check_valid_dependent! dependent, [:destroy, :delete]
+ model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ end
- method_name = "belongs_to_dependent_#{dependent}_for_#{name}"
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
- def #{method_name}
- association = #{name}
- association.#{dependent} if association
- end
- eoruby
- model.after_destroy method_name
- end
- end
+ def valid_dependent_options
+ [:destroy, :delete]
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 3fb0a57450..1b382f7285 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class CollectionAssociation < Association #:nodoc:
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
@@ -34,53 +33,53 @@ module ActiveRecord::Associations::Builder
end
end
- private
-
- def wrap_block_extension
- if block_extension
- @extension_module = mod = Module.new(&block_extension)
- silence_warnings do
- model.parent.const_set(extension_module_name, mod)
- end
+ def wrap_block_extension
+ if block_extension
+ @extension_module = mod = Module.new(&block_extension)
+ silence_warnings do
+ model.parent.const_set(extension_module_name, mod)
+ end
- prev_scope = @scope
+ prev_scope = @scope
- if prev_scope
- @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
- else
- @scope = proc { extending(mod) }
- end
+ if prev_scope
+ @scope = proc { |owner| instance_exec(owner, &prev_scope).extending(mod) }
+ else
+ @scope = proc { extending(mod) }
end
end
+ end
- def extension_module_name
- @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- end
+ def extension_module_name
+ @extension_module_name ||= "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
+ end
- def define_callback(callback_name)
- full_callback_name = "#{callback_name}_for_#{name}"
+ def define_callback(callback_name)
+ full_callback_name = "#{callback_name}_for_#{name}"
- # TODO : why do i need method_defined? I think its because of the inheritance chain
- model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
- model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
- end
+ # TODO : why do i need method_defined? I think its because of the inheritance chain
+ model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
+ model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
+ end
- def define_readers
- super
+ def define_readers
+ super
- name = self.name
- mixin.redefine_method("#{name.to_s.singularize}_ids") do
- association(name).ids_reader
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name.to_s.singularize}_ids
+ association(:#{name}).ids_reader
end
- end
+ CODE
+ end
- def define_writers
- super
+ def define_writers
+ super
- name = self.name
- mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
- association(name).ids_writer(ids)
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name.to_s.singularize}_ids=(ids)
+ association(:#{name}).ids_writer(ids)
end
- end
+ CODE
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 8df28ad876..bdac02b5bf 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -24,18 +24,16 @@ module ActiveRecord::Associations::Builder
end
end
- private
-
- def define_destroy_hook
- name = self.name
- model.send(:include, Module.new {
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def destroy_associations
- association(#{name.to_sym.inspect}).delete_all
- super
- end
- RUBY
- })
- end
+ def define_destroy_hook
+ name = self.name
+ model.send(:include, Module.new {
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def destroy_associations
+ association(:#{name}).delete_all
+ super
+ end
+ RUBY
+ })
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 9e60dbc30b..ab8225460a 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
def macro
@@ -9,52 +8,8 @@ module ActiveRecord::Associations::Builder
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
end
- def build
- reflection = super
- configure_dependency
- reflection
+ def valid_dependent_options
+ [:destroy, :delete_all, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
end
-
- private
-
- def configure_dependency
- if dependent = options[:dependent]
- check_valid_dependent! dependent, [:destroy, :delete_all, :nullify, :restrict]
- dependent_restrict_deprecation_warning if dependent == :restrict
-
- send("define_#{dependent}_dependency_method")
- model.before_destroy dependency_method_name
- end
- end
-
- def define_destroy_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- send(name).each do |o|
- # No point in executing the counter update since we're going to destroy the parent anyway
- o.mark_for_destruction
- end
-
- send(name).delete_all
- end
- end
-
- def define_delete_all_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- association(name).delete_all
- end
- end
-
- def define_nullify_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- send(name).delete_all
- end
- end
-
- def dependency_method_name
- "has_many_dependent_for_#{name}"
- end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 9c84f1913a..0da564f402 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
def macro
@@ -15,35 +14,12 @@ module ActiveRecord::Associations::Builder
!options[:through]
end
- def build
- reflection = super
- configure_dependency unless options[:through]
- reflection
+ def configure_dependency
+ super unless options[:through]
end
- private
-
- def configure_dependency
- if dependent = options[:dependent]
- check_valid_dependent! dependent, [:destroy, :delete, :nullify, :restrict]
- dependent_restrict_deprecation_warning if dependent == :restrict
-
- send("define_#{dependent}_dependency_method")
- model.before_destroy dependency_method_name
- end
- end
-
- def define_destroy_dependency_method
- name = self.name
- mixin.redefine_method(dependency_method_name) do
- association(name).delete
- end
- end
- alias :define_delete_dependency_method :define_destroy_dependency_method
- alias :define_nullify_dependency_method :define_destroy_dependency_method
-
- def dependency_method_name
- "has_one_dependent_#{options[:dependent]}_for_#{name}"
- end
+ def valid_dependent_options
+ [:destroy, :delete, :nullify, :restrict, :restrict_with_error, :restrict_with_exception]
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 90a4b7c2ef..6a5830e57f 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -13,22 +13,20 @@ module ActiveRecord::Associations::Builder
define_constructors if constructable?
end
- private
-
- def define_constructors
- name = self.name
-
- mixin.redefine_method("build_#{name}") do |*params, &block|
- association(name).build(*params, &block)
+ def define_constructors
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def build_#{name}(*args, &block)
+ association(:#{name}).build(*args, &block)
end
- mixin.redefine_method("create_#{name}") do |*params, &block|
- association(name).create(*params, &block)
+ def create_#{name}(*args, &block)
+ association(:#{name}).create(*args, &block)
end
- mixin.redefine_method("create_#{name}!") do |*params, &block|
- association(name).create!(*params, &block)
+ def create_#{name}!(*args, &block)
+ association(:#{name}).create!(*args, &block)
end
- end
+ CODE
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 7a363c896e..74864d271f 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -7,6 +7,28 @@ module ActiveRecord
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < CollectionAssociation #:nodoc:
+ def handle_dependency
+ case options[:dependent]
+ when :restrict, :restrict_with_exception
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
+
+ when :restrict_with_error
+ unless empty?
+ record = klass.human_attribute_name(reflection.name).downcase
+ owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
+ false
+ end
+
+ else
+ if options[:dependent] == :destroy
+ # No point in executing the counter update since we're going to destroy the parent anyway
+ load_target.each(&:mark_for_destruction)
+ end
+
+ delete_all
+ end
+ end
+
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 191c4e8e39..dd7da59a86 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -3,23 +3,43 @@ module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < SingularAssociation #:nodoc:
- def replace(record, save = true)
- raise_on_type_mismatch(record) if record
- load_target
- reflection.klass.transaction do
- if target && target != record
- remove_target!(options[:dependent]) unless target.destroyed?
+ def handle_dependency
+ case options[:dependent]
+ when :restrict, :restrict_with_exception
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
+
+ when :restrict_with_error
+ if load_target
+ record = klass.human_attribute_name(reflection.name).downcase
+ owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
+ false
end
- if record
- set_owner_attributes(record)
- set_inverse_instance(record)
+ else
+ delete
+ end
+ end
- if owner.persisted? && save && !record.save
- nullify_owner_attributes(record)
- set_owner_attributes(target) if target
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
+ def replace(record, save = true)
+ raise_on_type_mismatch(record) if record
+ load_target
+
+ # If target and record are nil, or target is equal to record,
+ # we don't need to have transaction.
+ if (target || record) && target != record
+ reflection.klass.transaction do
+ remove_target!(options[:dependent]) if target && !target.destroyed?
+
+ if record
+ set_owner_attributes(record)
+ set_inverse_instance(record)
+
+ if owner.persisted? && save && !record.save
+ nullify_owner_attributes(record)
+ set_owner_attributes(target) if target
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
+ end
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1145d2138c..0fddfdf0cb 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -82,15 +82,6 @@ module ActiveRecord
# The connection handler
config_attribute :connection_handler
- ##
- # :singleton-method:
- # Specifies whether or not has_many or has_one association option
- # :dependent => :restrict raises an exception. If set to true, the
- # ActiveRecord::DeleteRestrictionError exception will be raised
- # along with a DEPRECATION WARNING. If set to false, an error would
- # be added to the model instead.
- config_attribute :dependent_restrict_raises
-
%w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
config_attribute name, global: true
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index ec7e4f5fb7..5f7825783b 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -524,13 +524,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_invalid_belongs_to_dependent_option_nullify_raises_exception
assert_raise ArgumentError do
- Author.belongs_to :special_author_address, :dependent => :nullify
+ Class.new(Author).belongs_to :special_author_address, :dependent => :nullify
end
end
def test_invalid_belongs_to_dependent_option_restrict_raises_exception
assert_raise ArgumentError do
- Author.belongs_to :special_author_address, :dependent => :restrict
+ Class.new(Author).belongs_to :special_author_address, :dependent => :restrict
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 43440c1146..04714f42e9 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1091,9 +1091,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_restrict
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
-
firm = RestrictedFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
@@ -1101,15 +1098,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedFirm.exists?(:name => 'restrict')
assert firm.companies.exists?(:name => 'child')
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
- def test_restrict_when_dependent_restrict_raises_config_set_to_false
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = false
+ def test_restrict_is_deprecated
+ klass = Class.new(ActiveRecord::Base)
+ assert_deprecated { klass.has_many :posts, dependent: :restrict }
+ end
- firm = RestrictedFirm.create!(:name => 'restrict')
+ def test_restrict_with_exception
+ firm = RestrictedWithExceptionFirm.create!(:name => 'restrict')
+ firm.companies.create(:name => 'child')
+
+ assert !firm.companies.empty?
+ assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedWithExceptionFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ end
+
+ def test_restrict_with_error
+ firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
assert !firm.companies.empty?
@@ -1119,10 +1126,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !firm.errors.empty?
assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first
- assert RestrictedFirm.exists?(:name => 'restrict')
+ assert RestrictedWithErrorFirm.exists?(:name => 'restrict')
assert firm.companies.exists?(:name => 'child')
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
def test_included_in_collection
@@ -1602,18 +1607,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb1, bulb3], result
end
- def test_building_has_many_association_with_restrict_dependency
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
-
- klass = Class.new(ActiveRecord::Base)
-
- assert_deprecated { klass.has_many :companies, :dependent => :restrict }
- assert_not_deprecated { klass.has_many :companies }
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
- end
-
def test_collection_association_with_private_kernel_method
firm = companies(:first_firm)
assert_equal [accounts(:signals37)], firm.accounts.open
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 112735839f..8bc633f2b5 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -156,10 +156,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised { firm.destroy }
end
- def test_dependence_with_restrict
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
-
+ def test_restrict
firm = RestrictedFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
@@ -168,38 +165,26 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedFirm.exists?(:name => 'restrict')
assert firm.account.present?
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
- def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = false
+ def test_restrict_is_deprecated
+ klass = Class.new(ActiveRecord::Base)
+ assert_deprecated { klass.has_one :post, dependent: :restrict }
+ end
- firm = RestrictedFirm.create!(:name => 'restrict')
+ def test_restrict_with_exception
+ firm = RestrictedWithExceptionFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
assert_not_nil firm.account
- firm.destroy
-
- assert !firm.errors.empty?
- assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
- assert RestrictedFirm.exists?(:name => 'restrict')
+ assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedWithExceptionFirm.exists?(:name => 'restrict')
assert firm.account.present?
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
end
- def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false_and_attribute_name
- old_backend = I18n.backend
- I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations 'en', :activerecord => {:attributes => {:restricted_firm => {:account => "account model"}}}
-
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = false
-
- firm = RestrictedFirm.create!(:name => 'restrict')
+ def test_restrict_with_error
+ firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
assert_not_nil firm.account
@@ -207,12 +192,9 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
assert !firm.errors.empty?
- assert_equal "Cannot delete record because a dependent account model exists", firm.errors[:base].first
- assert RestrictedFirm.exists?(:name => 'restrict')
+ assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
+ assert RestrictedWithErrorFirm.exists?(:name => 'restrict')
assert firm.account.present?
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
- I18n.backend = old_backend
end
def test_successful_build_association
@@ -524,15 +506,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.attributes_after_initialize['car_id']
end
- def test_building_has_one_association_with_dependent_restrict
- option_before = ActiveRecord::Base.dependent_restrict_raises
- ActiveRecord::Base.dependent_restrict_raises = true
+ def test_has_one_transaction
+ company = companies(:first_firm)
+ account = Account.find(1)
- klass = Class.new(ActiveRecord::Base)
+ company.account # force loading
+ assert_no_queries { company.account = account }
- assert_deprecated { klass.has_one :account, :dependent => :restrict }
- assert_not_deprecated { klass.has_one :account }
- ensure
- ActiveRecord::Base.dependent_restrict_raises = option_before
+ company.account = nil
+ assert_no_queries { company.account = nil }
+ account = Account.find(2)
+ assert_queries { company.account = account }
end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 3cea20527e..86893ec4b3 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -385,7 +385,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).tagging
+ assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).taggings_2
end
def test_has_many_through_polymorphic_has_many
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 018064233a..4c6d4666ed 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -19,9 +19,6 @@ require 'support/connection'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
-# Avoid deprecation warning setting dependent_restrict_raises to false. The default is true
-ActiveRecord::Base.dependent_restrict_raises = false
-
# Connect to the database
ARTest.connect
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 06d6596725..42461e8ecb 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -1,9 +1,7 @@
require "cases/helper"
require 'models/entrant'
require 'models/bird'
-
-# So we can test whether Course.connection survives a reload.
-require_dependency 'models/course'
+require 'models/course'
class MultipleDbTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 7444dc5de1..bb034848e1 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -114,9 +114,12 @@ class TimestampTest < ActiveRecord::TestCase
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute
- Pet.belongs_to :owner, :touch => :happy_at
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Pet'; end
+ belongs_to :owner, :touch => :happy_at
+ end
- pet = Pet.first
+ pet = klass.first
owner = pet.owner
previously_owner_happy_at = owner.happy_at
@@ -124,14 +127,15 @@ class TimestampTest < ActiveRecord::TestCase
pet.save
assert_not_equal previously_owner_happy_at, pet.owner.happy_at
- ensure
- Pet.belongs_to :owner, :touch => true
end
def test_touching_a_record_with_a_belongs_to_that_uses_a_counter_cache_should_update_the_parent
- Pet.belongs_to :owner, :counter_cache => :use_count, :touch => true
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Pet'; end
+ belongs_to :owner, :counter_cache => :use_count, :touch => true
+ end
- pet = Pet.first
+ pet = klass.first
owner = pet.owner
owner.update_columns(happy_at: 3.days.ago)
previously_owner_updated_at = owner.updated_at
@@ -140,15 +144,15 @@ class TimestampTest < ActiveRecord::TestCase
pet.save
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
- ensure
- Pet.belongs_to :owner, :touch => true
end
def test_touching_a_record_touches_parent_record_and_grandparent_record
- Toy.belongs_to :pet, :touch => true
- Pet.belongs_to :owner, :touch => true
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, :touch => true
+ end
- toy = Toy.first
+ toy = klass.first
pet = toy.pet
owner = pet.owner
time = 3.days.ago
@@ -158,8 +162,6 @@ class TimestampTest < ActiveRecord::TestCase
owner.reload
assert_not_equal time, owner.updated_at
- ensure
- Toy.belongs_to :pet
end
def test_timestamp_attributes_for_create
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 3157d8fe7f..77f4a2ec87 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -93,8 +93,8 @@ class Author < ActiveRecord::Base
has_many :author_favorites
has_many :favorite_authors, -> { order('name') }, :through => :author_favorites
- has_many :tagging, :through => :posts
has_many :taggings, :through => :posts
+ has_many :taggings_2, :through => :posts, :source => :tagging
has_many :tags, :through => :posts
has_many :post_categories, :through => :posts, :source => :categories
has_many :tagging_tags, :through => :taggings, :source => :tag
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 5bfbb5e855..75f38d275c 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -115,8 +115,20 @@ class DependentFirm < Company
end
class RestrictedFirm < Company
- has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict
- has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict
+ ActiveSupport::Deprecation.silence do
+ has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict
+ has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict
+ end
+end
+
+class RestrictedWithExceptionFirm < Company
+ has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict_with_exception
+ has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict_with_exception
+end
+
+class RestrictedWithErrorFirm < Company
+ has_one :account, -> { order("id") }, :foreign_key => "firm_id", :dependent => :restrict_with_error
+ has_many :companies, -> { order("id") }, :foreign_key => 'client_of', :dependent => :restrict_with_error
end
class Client < Company
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 359b29fac3..1134b09d8b 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -24,11 +24,10 @@ class Member < ActiveRecord::Base
has_one :club_category, :through => :club, :source => :category
- has_many :current_memberships
- has_one :club_through_many, :through => :current_memberships, :source => :club
-
has_many :current_memberships, -> { where :favourite => true }
has_many :clubs, :through => :current_memberships
+
+ has_one :club_through_many, :through => :current_memberships, :source => :club
end
class SelfMember < ActiveRecord::Base
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index c176316a05..92736e0ca9 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -1,6 +1,6 @@
require 'active_support/logger'
-require_dependency 'models/college'
-require_dependency 'models/course'
+require 'models/college'
+require 'models/course'
module ARTest
def self.connection_name
diff --git a/guides/source/association_basics.textile b/guides/source/association_basics.textile
index 2b8b1bd937..d4c0a1ba74 100644
--- a/guides/source/association_basics.textile
+++ b/guides/source/association_basics.textile
@@ -967,10 +967,13 @@ end
h6(#has_one-dependent). +:dependent+
-If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the association object to +NULL+.
-If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+Controls what happens to the associated object when its owner is destroyed:
-NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
+* +:destroy+ causes the associated object to also be destroyed
+* +:delete+ causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
+* +:nullify+ causes the foreign key to be set to +NULL+. Callbacks are not executed.
+* +:restrict_with_exception+ causes an exception to be raised if there is an associated record
+* +:restrict_with_error+ causes an error to be added to the owner if there is an associated object
h6(#has_one-foreign_key). +:foreign_key+
@@ -1307,10 +1310,13 @@ end
h6(#has_many-dependent). +:dependent+
-If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated objects to delete those objects. If you set the +:dependent+ option to +:delete_all+, then deleting this object will delete the associated objects _without_ calling their +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the associated objects to +NULL+.
-If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+Controls what happens to the associated objects when their owner is destroyed:
-NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
+* +:destroy+ causes all the associated objects to also be destroyed
+* +:delete_all+ causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
+* +:nullify+ causes the foreign keys to be set to +NULL+. Callbacks are not executed.
+* +:restrict_with_exception+ causes an exception to be raised if there are any associated records
+* +:restrict_with_error+ causes an error to be added to the owner if there are any associated objects
NOTE: This option is ignored when you use the +:through+ option on the association.
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index cc5c352df4..5ed3ad4a6b 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -286,8 +286,6 @@ h4. Configuring Active Record
* +config.active_record.auto_explain_threshold_in_seconds+ configures the threshold for automatic EXPLAINs (+nil+ disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
-* +config.active_record.dependent_restrict_raises+ will control the behavior when an object with a <tt>:dependent => :restrict</tt> association is deleted. Setting this to false will prevent +DeleteRestrictionError+ from being raised and instead will add an error on the model object. Defaults to false in the development mode.
-
* +config.active_record.mass_assignment_sanitizer+ will determine the strictness of the mass assignment sanitization within Rails. Defaults to +:strict+. In this mode, mass assigning any non-+attr_accessible+ attribute in a +create+ or +update_attributes+ call will raise an exception. Setting this option to +:logger+ will only print to the log file when an attribute is being assigned and will not raise an exception.
The MySQL adapter adds one additional configuration option:
@@ -340,6 +338,12 @@ h4. Configuring Action Dispatch
* +config.action_dispatch.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class.
+* +config.action_dispatch.default_headers+ is a hash with HTTP headers that are set by default in each response. By default, this is defined as:
+
+<ruby>
+config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block' }
+</ruby>
+
* +config.action_dispatch.tld_length+ sets the TLD (top-level domain) length for the application. Defaults to +1+.
* +ActionDispatch::Callbacks.before+ takes a block of code to run before the request.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 18c130e828..851f41249a 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Correctly handle SCRIPT_NAME when generating routes to engine in application
+ that's mounted at a sub-uri. With this behavior, you *should not* use
+ default_url_options[:script_name] to set proper application's mount point by
+ yourself. *Piotr Sarnacki*
+
* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index f469c334a7..40f35ae5a6 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -494,7 +494,11 @@ module Rails
# Define the Rack API for this engine.
def call(env)
- app.call(env.merge!(env_config))
+ env.merge!(env_config)
+ if env['SCRIPT_NAME']
+ env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
+ end
+ app.call(env)
end
# Defines additional Rack env configuration that is added on each call.
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 f20dd78031..b2b760ee7b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -41,11 +41,6 @@ module <%= app_const_base %>
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
- config.action_dispatch.default_headers = {
- 'X-Frame-Options' => 'SAMEORIGIN',
- 'X-XSS-Protection' => '1; mode=block'
- }
-
# Use SQL instead of Active Record's schema dumper when creating the database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types.
@@ -56,11 +51,6 @@ module <%= app_const_base %>
# in your app. As such, your models will need to explicitly whitelist or blacklist accessible
# parameters by using an attr_accessible or attr_protected declaration.
<%= comment_if :skip_active_record %>config.active_record.whitelist_attributes = true
-
- # Specifies whether or not has_many or has_one association option :dependent => :restrict raises
- # an exception. If set to true, then an ActiveRecord::DeleteRestrictionError exception would be
- # raised. If set to false, then an error will be added on the model instead.
- <%= comment_if :skip_active_record %>config.active_record.dependent_restrict_raises = false
<% unless options.skip_sprockets? -%>
# Enable the asset pipeline.
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 907065f75c..c294bfb238 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -213,7 +213,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/database.yml"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "config/application.rb", /#\s+config\.active_record\.whitelist_attributes = true/
- assert_file "config/application.rb", /#\s+config\.active_record\.dependent_restrict_raises = false/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
end
@@ -367,11 +366,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "config/application.rb", /config\.active_record\.whitelist_attributes = true/
end
- def test_active_record_dependent_restrict_raises_is_present_application_config
- run_generator
- assert_file "config/application.rb", /config\.active_record\.dependent_restrict_raises = false/
- end
-
def test_pretend_option
output = run_generator [File.join(destination_root, "myapp"), "--pretend"]
assert_no_match(/run bundle install/, output)
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 6071cd3f39..8f04692aef 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -253,7 +253,6 @@ module TestHelpers
:activerecord] - arr
if to_remove.include? :activerecord
remove_from_config "config.active_record.whitelist_attributes = true"
- remove_from_config "config.active_record.dependent_restrict_raises = false"
end
$:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' }
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 63814f7a04..e52b3efdab 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1193,6 +1193,54 @@ YAML
last_response.body.split("\n").map(&:strip)
end
+ test "paths are properly generated when application is mounted at sub-path" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ app_file "app/controllers/bar_controller.rb", <<-RUBY
+ class BarController < ApplicationController
+ def index
+ render :text => bukkits.bukkit_path
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/bar' => 'bar#index', :as => 'bar'
+ mount Bukkits::Engine => "/bukkits", :as => "bukkits"
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Engine.routes.draw do
+ get '/bukkit' => 'bukkit#index'
+ end
+ RUBY
+
+
+ @plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY
+ class Bukkits::BukkitController < ActionController::Base
+ def index
+ render :text => main_app.bar_path
+ end
+ end
+ RUBY
+
+ boot_rails
+
+ get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'})
+ assert_equal '/foo/bar', last_response.body
+
+ get("/bar", {}, {'SCRIPT_NAME' => '/foo'})
+ assert_equal '/foo/bukkits/bukkit', last_response.body
+ end
+
private
def app
Rails.application
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 4c0fdee556..bd13c3aba3 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -163,24 +163,14 @@ module ApplicationTests
end
end
- def reset_script_name!
- Rails.application.routes.default_url_options = {}
- end
-
- def script_name(script_name)
- Rails.application.routes.default_url_options = {:script_name => script_name}
- end
-
test "routes generation in engine and application" do
# test generating engine's route from engine
get "/john/blog/posts"
assert_equal "/john/blog/posts/1", last_response.body
# test generating engine's route from engine with default_url_options
- script_name "/foo"
get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts/1", last_response.body
- reset_script_name!
# test generating engine's route from application
get "/engine_route"
@@ -193,14 +183,11 @@ module ApplicationTests
assert_equal "/john/blog/posts", last_response.body
# test generating engine's route from application with default_url_options
- script_name "/foo"
get "/engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/anonymous/blog/posts", last_response.body
- script_name "/foo"
get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts", last_response.body
- reset_script_name!
# test generating application's route from engine
get "/someone/blog/generate_application_route"
@@ -210,10 +197,8 @@ module ApplicationTests
assert_equal "/", last_response.body
# test generating application's route from engine with default_url_options
- script_name "/foo"
get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo'
assert_equal "/foo/", last_response.body
- reset_script_name!
# test polymorphic routes
get "/polymorphic_route"