diff options
46 files changed, 456 insertions, 250 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 7af9165605..9fb914ac40 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,21 @@ +* Respect `SCRIPT_NAME` when using `redirect` with a relative path + + Example: + # application routes.rb + mount BlogEngine => '/blog' + + # engine routes.rb + get '/admin' => redirect('admin/dashboard') + + This now redirects to the path `/blog/admin/dashboard`, whereas before it would've + generated an invalid url because there would be no slash between the host name and + the path. It also allows redirects to work where the application is deployed to a + subdirectory of a website. + + Fixes #7977 + + *Andrew White* + * Fixing repond_with working directly on the options hash This fixes an issue where the respond_with worked directly with the given options hash, so that if a user relied on it after calling respond_with, diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 68094f129f..3e54c7e71c 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -30,6 +30,10 @@ module ActionDispatch uri.host ||= req.host uri.port ||= req.port unless req.standard_port? + if relative_path?(uri.path) + uri.path = "#{req.script_name}/#{uri.path}" + end + body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>) headers = { @@ -48,6 +52,11 @@ module ActionDispatch def inspect "redirect(#{status})" end + + private + def relative_path?(path) + path && !path.empty? && path[0] != '/' + end end class PathRedirect < Redirect @@ -81,6 +90,11 @@ module ActionDispatch url_options[:path] = (url_options[:path] % escape_path(params)) end + if relative_path?(url_options[:path]) + url_options[:path] = "/#{url_options[:path]}" + url_options[:script_name] = request.script_name + end + ActionDispatch::Http::URL.url_for url_options end @@ -104,6 +118,10 @@ module ActionDispatch # # get 'docs/:article', to: redirect('/wiki/%{article}') # + # Note that if you return a path without a leading slash then the url is prefixed with the + # current SCRIPT_NAME environment variable. This is typically '/' but may be different in + # a mounted engine or where the application is deployed to a subdirectory of a website. + # # Alternatively you can use one of the other syntaxes: # # The block version of redirect allows for the easy encapsulation of any logic associated with diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 113608ecf4..e519fff51e 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -31,6 +31,14 @@ module TestGenerationPrefix get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine" get "/conflicting_url", :to => "inside_engine_generating#conflicting" get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test + + get "/relative_path_redirect", :to => redirect("foo") + get "/relative_option_redirect", :to => redirect(:path => "foo") + get "/relative_custom_redirect", :to => redirect { |params, request| "foo" } + + get "/absolute_path_redirect", :to => redirect("/foo") + get "/absolute_option_redirect", :to => redirect(:path => "/foo") + get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" } end routes @@ -182,6 +190,48 @@ module TestGenerationPrefix assert_equal "engine", last_response.body end + test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do + get "/awesome/blog/relative_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do + get "/awesome/blog/relative_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do + get "/awesome/blog/relative_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do + get "/awesome/blog/absolute_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do + get "/awesome/blog/absolute_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do + get "/awesome/blog/absolute_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + # Inside Application test "[APP] generating engine's route includes prefix" do get "/generate" @@ -281,6 +331,14 @@ module TestGenerationPrefix routes = ActionDispatch::Routing::RouteSet.new routes.draw do get "/posts/:id", :to => "posts#show", :as => :post + + get "/relative_path_redirect", :to => redirect("foo") + get "/relative_option_redirect", :to => redirect(:path => "foo") + get "/relative_custom_redirect", :to => redirect { |params, request| "foo" } + + get "/absolute_path_redirect", :to => redirect("/foo") + get "/absolute_option_redirect", :to => redirect(:path => "/foo") + get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" } end routes @@ -331,5 +389,47 @@ module TestGenerationPrefix get "/posts/1" assert_equal "/posts/1", last_response.body end + + test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do + get "/relative_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do + get "/relative_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do + get "/relative_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do + get "/absolute_path_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do + get "/absolute_option_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end + + test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do + get "/absolute_custom_redirect" + assert_equal 301, last_response.status + assert_equal "http://example.org/foo", last_response.headers["Location"] + assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body + end end end diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 8e1aea50a9..d7e8e99200 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -531,7 +531,7 @@ module ActionView # my_date = Time.now + 2.days # # # Generates a select field for days that defaults to the day for the date in my_date. - # select_day(my_time) + # select_day(my_date) # # # Generates a select field for days that defaults to the number given. # select_day(5) @@ -541,7 +541,7 @@ module ActionView # # # Generates a select field for days that defaults to the day for the date in my_date # # that is named 'due' rather than 'day'. - # select_day(my_time, field_name: 'due') + # select_day(my_date, field_name: 'due') # # # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a # # generic prompt. diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 8a4918a8c0..2f5246f42a 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -455,7 +455,7 @@ module ActionView html_options, name = name, nil if block_given? html_options = (html_options || {}).stringify_keys - extras = %w{ cc bcc body subject }.map { |item| + extras = %w{ cc bcc body subject }.map! { |item| option = html_options.delete(item) || next "#{item}=#{Rack::Utils.escape_path(option)}" }.compact diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index eb21b69163..e8602ecbcf 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,7 @@ +* Fix `has_secure_password` to honor bcrypt-ruby's cost attribute. + + *T.J. Schuck* + * Updated the `ActiveModel::Dirty#changed_attributes` method to be indifferent between using symbols and strings as keys. diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 17fafe4be9..f87c36e39e 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -103,7 +103,7 @@ module ActiveModel def password=(unencrypted_password) unless unencrypted_password.blank? @password = unencrypted_password - cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost) end end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 98e5c747d5..41d0b2263e 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -82,6 +82,14 @@ class SecurePasswordTest < ActiveModel::TestCase assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost end + test "Password digest cost honors bcrypt cost attribute when min_cost is false" do + ActiveModel::SecurePassword.min_cost = false + BCrypt::Engine.cost = 5 + + @user.password = "secret" + assert_equal BCrypt::Engine.cost, @user.password_digest.cost + end + test "Password digest cost can be set to bcrypt min cost to speed up tests" do ActiveModel::SecurePassword.min_cost = true diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 83c94caa53..e122b0181e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,26 @@ +* Generate subquery for `Relation` if it passed as array condition for `where` method + + Example: + # Before + Blog.where('id in (?)', Blog.where(id: 1)) + # => SELECT "blogs".* FROM "blogs" WHERE "blogs"."id" = 1 + # => SELECT "blogs".* FROM "blogs" WHERE (id IN (1)) + + # After + Blog.where('id in (?)', Blog.where(id: 1).select(:id)) + # => SELECT "blogs".* FROM "blogs" + # WHERE "blogs"."id" IN (SELECT "blogs"."id" FROM "blogs" WHERE "blogs"."id" = 1) + + Fixes: #12415 + + *Paul Nikitochkin* + +* For missed association exception message + which is raised in `ActiveRecord::Associations::Preloader` class + added owner record class name in order to simplify to find problem code. + + *Paul Nikitochkin* + * `has_and_belongs_to_many` is now transparently implemented in terms of `has_many :through`. Behavior should remain the same, if not, it is a bug. @@ -199,6 +222,12 @@ *Yves Senn* +* Fixes bug when using includes combined with select, the select statement was overwritten. + + Fixes #11773 + + *Edo Balvers* + * Load fixtures from linked folders. *Kassio Borges* diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index fe8274e1d8..d8d68eb908 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -18,18 +18,15 @@ module ActiveRecord::Associations::Builder VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] - attr_reader :name, :scope, :options - def self.build(model, name, scope, options, &block) - builder = create_builder model, name, scope, options, &block - reflection = builder.build(model) + extension = define_extensions model, name, &block + reflection = create_reflection model, name, scope, options, extension define_accessors model, reflection define_callbacks model, reflection - builder.define_extensions model reflection end - def self.create_builder(model, name, scope, options, &block) + def self.create_reflection(model, name, scope, options, extension = nil) raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) if scope.is_a?(Hash) @@ -37,38 +34,44 @@ module ActiveRecord::Associations::Builder scope = nil end - new(name, scope, options, &block) - end + validate_options(options) + + scope = build_scope(scope, extension) - def initialize(name, scope, options) - @name = name - @scope = scope - @options = options + ActiveRecord::Reflection.create(macro, name, scope, options, model) + end - validate_options + def self.build_scope(scope, extension) + new_scope = scope if scope && scope.arity == 0 - @scope = proc { instance_exec(&scope) } + new_scope = proc { instance_exec(&scope) } end + + if extension + new_scope = wrap_scope new_scope, extension + end + + new_scope end - def build(model) - ActiveRecord::Reflection.create(macro, name, scope, options, model) + def self.wrap_scope(scope, extension) + scope end - def macro + def self.macro raise NotImplementedError end - def valid_options + def self.valid_options(options) VALID_OPTIONS + Association.extensions.flat_map(&:valid_options) end - def validate_options - options.assert_valid_keys(valid_options) + def self.validate_options(options) + options.assert_valid_keys(valid_options(options)) end - def define_extensions(model) + def self.define_extensions(model, name) end def self.define_callbacks(model, reflection) @@ -111,8 +114,6 @@ module ActiveRecord::Associations::Builder raise NotImplementedError end - private - def self.add_before_destroy_callbacks(model, reflection) unless valid_dependent_options.include? reflection.options[:dependent] raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}" diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index c0b1847f33..aa43c34d86 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -1,10 +1,10 @@ module ActiveRecord::Associations::Builder class BelongsTo < SingularAssociation #:nodoc: - def macro + def self.macro :belongs_to end - def valid_options + def self.valid_options(options) super + [:foreign_type, :polymorphic, :touch] end @@ -23,8 +23,6 @@ module ActiveRecord::Associations::Builder add_counter_cache_methods mixin end - private - def self.add_counter_cache_methods(mixin) return if mixin.method_defined? :belongs_to_counter_cache_after_create diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index d68f5d6792..2ff67f904d 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -7,22 +7,11 @@ module ActiveRecord::Associations::Builder CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] - def valid_options + def self.valid_options(options) super + [:table_name, :before_add, :after_add, :before_remove, :after_remove, :extend] end - attr_reader :block_extension - - def initialize(name, scope, options) - super - @mod = nil - if block_given? - @mod = Module.new(&Proc.new) - @scope = wrap_scope @scope, @mod - end - end - def self.define_callbacks(model, reflection) super name = reflection.name @@ -32,10 +21,11 @@ module ActiveRecord::Associations::Builder } end - def define_extensions(model) - if @mod + def self.define_extensions(model, name) + if block_given? extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" - model.parent.const_set(extension_module_name, @mod) + extension = Module.new(&Proc.new) + model.parent.const_set(extension_module_name, extension) end end @@ -78,9 +68,7 @@ module ActiveRecord::Associations::Builder CODE end - private - - def wrap_scope(scope, mod) + def self.wrap_scope(scope, mod) if scope proc { |owner| instance_exec(owner, &scope).extending(mod) } else 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 af596a3a64..1c9c04b044 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 @@ -84,11 +84,11 @@ module ActiveRecord::Associations::Builder middle_name = [lhs_model.name.downcase.pluralize, association_name].join('_').gsub(/::/, '_').to_sym middle_options = middle_options join_model - hm_builder = HasMany.create_builder(lhs_model, - middle_name, - nil, - middle_options) - hm_builder.build lhs_model + + HasMany.create_reflection(lhs_model, + middle_name, + nil, + middle_options) end private diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 7909b93622..227184cd19 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -1,10 +1,10 @@ module ActiveRecord::Associations::Builder class HasMany < CollectionAssociation #:nodoc: - def macro + def self.macro :has_many end - def valid_options + def self.valid_options(options) super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache] end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index f359efd496..064a3c8b51 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -1,10 +1,10 @@ module ActiveRecord::Associations::Builder class HasOne < SingularAssociation #:nodoc: - def macro + def self.macro :has_one end - def valid_options + def self.valid_options(options) valid = super + [:order, :as] valid += [:through, :source, :source_type] if options[:through] valid @@ -14,8 +14,6 @@ module ActiveRecord::Associations::Builder [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] end - private - def self.add_before_destroy_callbacks(model, reflection) super unless reflection.options[:through] end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 9a25980be8..2a4b1c441f 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -2,7 +2,7 @@ module ActiveRecord::Associations::Builder class SingularAssociation < Association #:nodoc: - def valid_options + def self.valid_options(options) super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of] end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 75f8990cf0..6b06a5f7fd 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -195,9 +195,7 @@ module ActiveRecord # Count all records using SQL. Construct options and pass them with # scope to the target class's +count+. - def count(column_name = nil, count_options = {}) - column_name, count_options = nil, column_name if column_name.is_a?(Hash) - + def count(column_name = nil) relation = scope if association_scope.distinct_value # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index ea7f768a68..0b6cdf5217 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -669,8 +669,8 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - def count(column_name = nil, options = {}) - @association.count(column_name, options) + def count(column_name = nil) + @association.count(column_name) end # Returns the size of the collection. If the collection hasn't been loaded, diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index f12d5c49b5..6e08f67286 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -4,7 +4,7 @@ module ActiveRecord autoload :JoinBase, 'active_record/associations/join_dependency/join_base' autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association' - attr_reader :join_parts, :alias_tracker, :base_klass + attr_reader :alias_tracker, :base_klass, :join_root def self.make_tree(associations) hash = {} @@ -54,43 +54,37 @@ module ActiveRecord def initialize(base, associations, joins) @base_klass = base @table_joins = joins - @join_parts = [JoinBase.new(base)] + @join_root = JoinBase.new(base) @alias_tracker = AliasTracker.new(base.connection, joins) @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1 tree = self.class.make_tree associations - build tree, join_parts.last, Arel::InnerJoin + build tree, @join_root, Arel::InnerJoin end - def graft(*associations) - join_assocs = join_associations - base = join_base - - associations.reject { |association| - join_assocs.detect { |a| association == a } - }.each { |association| - join_part = find_parent_part(association.parent) || base - type = association.join_type - find_or_build_scalar association.reflection, join_part, type - } - self + def reflections + join_root.drop(1).map!(&:reflection) end - def join_associations - join_parts.drop 1 - end + def merge_outer_joins!(other) + left = join_root + right = other.join_root - def reflections - join_associations.map(&:reflection) + if left.match? right + merge_node left, right + else + # If the roots aren't the same, then deep copy the RHS to the LHS + left.children.concat right.children.map { |node| + deep_copy left, node + } + end end - def join_relation(relation) - join_associations.inject(relation) do |rel,association| - association.join_relation(rel) - end + def join_constraints + join_root.children.flat_map { |c| c.flat_map(&:join_constraints) } end def columns - join_parts.collect { |join_part| + join_root.collect { |join_part| table = join_part.aliased_table join_part.column_names_with_alias.collect{ |column_name, aliased_name| table[column_name].as Arel.sql(aliased_name) @@ -99,49 +93,43 @@ module ActiveRecord end def instantiate(result_set) - primary_key = join_base.aliased_primary_key + primary_key = join_root.aliased_primary_key parents = {} type_caster = result_set.column_type primary_key - assoc = associations records = result_set.map { |row_hash| primary_id = type_caster.type_cast row_hash[primary_key] - parent = parents[primary_id] ||= join_base.instantiate(row_hash) - construct(parent, assoc, join_associations, row_hash, result_set) + parent = parents[primary_id] ||= join_root.instantiate(row_hash) + construct(parent, join_root, row_hash, result_set) parent }.uniq - remove_duplicate_results!(base_klass, records, assoc) + remove_duplicate_results!(base_klass, records, join_root.children) records end private - def associations - join_associations.each_with_object({}) do |assoc, tree| - cache_joined_association assoc, tree - end - end + def merge_node(left, right) + intersection, missing = right.children.map { |node1| + [left.children.find { |node2| node1.match? node2 }, node1] + }.partition(&:first) - def find_parent_part(parent) - join_parts.detect do |join_part| - case parent - when JoinBase - parent.base_klass == join_part.base_klass - else - parent == join_part - end - end + intersection.each { |l,r| merge_node l, r } + + left.children.concat missing.map { |_,node| deep_copy left, node } end - def join_base - join_parts.first + def deep_copy(parent, node) + dup = build_join_association(node.reflection, parent, Arel::OuterJoin) + dup.children.concat node.children.map { |n| deep_copy dup, n } + dup end def remove_duplicate_results!(base, records, associations) - associations.each_key do |name| - reflection = base.reflect_on_association(name) + associations.each do |node| + reflection = base.reflect_on_association(node.name) remove_uniq_by_reflection(reflection, records) parent_records = [] @@ -156,26 +144,13 @@ module ActiveRecord end unless parent_records.empty? - remove_duplicate_results!(reflection.klass, parent_records, associations[name]) + remove_duplicate_results!(reflection.klass, parent_records, node.children) end end end - def cache_joined_association(association, tree) - associations = [] - parent = association.parent - while parent != join_base - associations.unshift(parent.reflection.name) - parent = parent.parent - end - ref = associations.inject(tree) do |cache,key| - cache[key] - end - ref[association.reflection.name] ||= {} - end - def find_reflection(klass, name) - klass.reflect_on_association(name.intern) or + klass.reflect_on_association(name) or raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?" end @@ -183,23 +158,14 @@ module ActiveRecord associations.each do |name, right| reflection = find_reflection parent.base_klass, name join_association = build_join_association reflection, parent, join_type - @join_parts << join_association + parent.children << join_association build right, join_association, join_type end end - def find_or_build_scalar(reflection, parent, join_type) - unless join_association = find_join_association(reflection, parent) - join_association = build_join_association(reflection, parent, join_type) - @join_parts << join_association - end - join_association - end - - def find_join_association(reflection, parent) - join_associations.detect { |j| - j.reflection == reflection && j.parent == parent - } + def build_scalar(reflection, parent, join_type) + join_association = build_join_association(reflection, parent, join_type) + parent.children << join_association end def remove_uniq_by_reflection(reflection, records) @@ -215,33 +181,19 @@ module ActiveRecord raise EagerLoadPolymorphicError.new(reflection) end - JoinAssociation.new(reflection, join_parts.length, parent, join_type, alias_tracker) + JoinAssociation.new(reflection, join_root.to_a.length, parent, join_type, alias_tracker) end - def construct(parent, associations, join_parts, row, rs) - associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc| - association = construct_scalar(parent, association_name, join_parts, row, rs) - construct(association, assoc, join_parts, row, rs) if association + def construct(ar_parent, parent, row, rs) + parent.children.each do |node| + association = construct_association(ar_parent, parent, node, row, rs) + construct(association, node, row, rs) if association end end - def construct_scalar(parent, associations, join_parts, row, rs) - name = associations.to_s - - join_part = join_parts.detect { |j| - j.reflection.name.to_s == name && - j.parent_table_name == parent.class.table_name - } - - raise(ConfigurationError, "No such association") unless join_part - - join_parts.delete(join_part) - construct_association(parent, join_part, row, rs) - end - - def construct_association(record, join_part, row, rs) - caster = rs.column_type(join_part.parent.aliased_primary_key) - row_id = caster.type_cast row[join_part.parent.aliased_primary_key] + def construct_association(record, parent, join_part, row, rs) + caster = rs.column_type(parent.aliased_primary_key) + row_id = caster.type_cast row[parent.aliased_primary_key] return if record.id != row_id diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index f6a04ef9f1..3af613d2d1 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -9,10 +9,6 @@ module ActiveRecord # The reflection of the association represented attr_reader :reflection - # A JoinBase instance representing the active record we are joining onto. - # (So in Author.has_many :posts, the Author would be that base record.) - attr_reader :parent - # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin attr_accessor :join_type @@ -25,11 +21,10 @@ module ActiveRecord delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection def initialize(reflection, index, parent, join_type, alias_tracker) - super(reflection.klass) + super(reflection.klass, parent) @reflection = reflection @alias_tracker = alias_tracker - @parent = parent @join_type = join_type @aliased_prefix = "t#{ index }" @tables = construct_tables.reverse @@ -38,10 +33,9 @@ module ActiveRecord def parent_table_name; parent.table_name; end alias :alias_suffix :parent_table_name - def ==(other) - other.class == self.class && - other.reflection == reflection && - other.parent == parent + def match?(other) + return true if self == other + super && reflection == other.reflection end def join_constraints @@ -131,11 +125,6 @@ module ActiveRecord constraint end - def join_relation(joining_relation) - self.join_type = Arel::OuterJoin - joining_relation.joins(self) - end - def table tables.last end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb index c689d06594..48de12bcd5 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb @@ -4,9 +4,13 @@ module ActiveRecord module Associations class JoinDependency # :nodoc: class JoinBase < JoinPart # :nodoc: - def ==(other) - other.class == self.class && - other.base_klass == base_klass + def initialize(klass) + super(klass, nil) + end + + def match?(other) + return true if self == other + super && base_klass == other.base_klass end def aliased_prefix diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index 2b6034ca9d..d39ce94c99 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -8,25 +8,42 @@ module ActiveRecord # operations (for example a has_and_belongs_to_many JoinAssociation would result in # two; one for the join table and one for the target table). class JoinPart # :nodoc: + include Enumerable + + # A JoinBase instance representing the active record we are joining onto. + # (So in Author.has_many :posts, the Author would be that base record.) + attr_reader :parent + # The Active Record class which this join part is associated 'about'; for a JoinBase # this is the actual base model, for a JoinAssociation this is the target model of the # association. - attr_reader :base_klass + attr_reader :base_klass, :children delegate :table_name, :column_names, :primary_key, :arel_engine, :to => :base_klass - def initialize(base_klass) + def initialize(base_klass, parent) @base_klass = base_klass + @parent = parent @cached_record = {} @column_names_with_alias = nil + @children = [] end - def aliased_table - Arel::Nodes::TableAlias.new table, aliased_table_name + def name + reflection.name end - def ==(other) - raise NotImplementedError + def match?(other) + self.class == other.class + end + + def each(&block) + yield self + children.each { |child| child.each(&block) } + end + + def aliased_table + Arel::Nodes::TableAlias.new table, aliased_table_name end # An Arel::Table for the active_record @@ -54,7 +71,7 @@ module ActiveRecord unless @column_names_with_alias @column_names_with_alias = [] - ([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i| + column_names.each_with_index do |column_name, i| @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"] end end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 713ff80d47..2393667ac8 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -153,13 +153,13 @@ module ActiveRecord records.group_by do |record| reflection = record.class.reflect_on_association(association) - reflection || raise_config_error(association) + reflection || raise_config_error(record, association) end end - def raise_config_error(association) + def raise_config_error(record, association) raise ActiveRecord::ConfigurationError, - "Association named '#{association}' was not found; " \ + "Association named '#{association}' was not found on #{record.class.name}; " \ "perhaps you misspelled it?" end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index ea21836c65..3166df57eb 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -15,7 +15,7 @@ module ActiveRecord through_reflection.name, through_scope) - through_records = owners.map do |owner, h| + through_records = owners.map do |owner| association = owner.association through_reflection.name [owner, Array(association.reader)] diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index e826762def..7e1e120288 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -160,7 +160,7 @@ module ActiveRecord end def type_condition(table = arel_table) - sti_column = table[inheritance_column.to_sym] + sti_column = table[inheritance_column] sti_names = ([self] + descendants).map { |model| model.sti_name } sti_column.in(sti_names) diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 716020e7e7..1f3d377e53 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -50,7 +50,7 @@ module ActiveRecord 0 end - def calculate(_operation, _column_name, _options = {}) + def calculate(_operation, _column_name) if _operation == :count 0 else diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 27c04b0952..2d267183ce 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -19,17 +19,16 @@ module ActiveRecord # # Person.group(:city).count # # => { 'Rome' => 5, 'Paris' => 3 } - def count(column_name = nil, options = {}) - column_name, options = nil, column_name if column_name.is_a?(Hash) - calculate(:count, column_name, options) + def count(column_name = nil) + calculate(:count, column_name) end # Calculates the average value on a given column. Returns +nil+ if there's # no row. See +calculate+ for examples with options. # # Person.average(:age) # => 35.8 - def average(column_name, options = {}) - calculate(:average, column_name, options) + def average(column_name) + calculate(:average, column_name) end # Calculates the minimum value on a given column. The value is returned @@ -37,8 +36,8 @@ module ActiveRecord # +calculate+ for examples with options. # # Person.minimum(:age) # => 7 - def minimum(column_name, options = {}) - calculate(:minimum, column_name, options) + def minimum(column_name) + calculate(:minimum, column_name) end # Calculates the maximum value on a given column. The value is returned @@ -46,8 +45,8 @@ module ActiveRecord # +calculate+ for examples with options. # # Person.maximum(:age) # => 93 - def maximum(column_name, options = {}) - calculate(:maximum, column_name, options) + def maximum(column_name) + calculate(:maximum, column_name) end # Calculates the sum of values on a given column. The value is returned @@ -90,15 +89,15 @@ module ActiveRecord # Person.group(:last_name).having("min(age) > 17").minimum(:age) # # Person.sum("2 * age") - def calculate(operation, column_name, options = {}) + def calculate(operation, column_name) if column_name.is_a?(Symbol) && attribute_alias?(column_name) column_name = attribute_alias(column_name) end if has_include?(column_name) - construct_relation_for_association_calculations.calculate(operation, column_name, options) + construct_relation_for_association_calculations.calculate(operation, column_name) else - perform_calculation(operation, column_name, options) + perform_calculation(operation, column_name) end end @@ -181,7 +180,7 @@ module ActiveRecord eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?)) end - def perform_calculation(operation, column_name, options = {}) + def perform_calculation(operation, column_name) operation = operation.to_s.downcase # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 0132a02f83..8583286de5 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -261,13 +261,13 @@ module ActiveRecord end def construct_relation_for_association_find(join_dependency) - relation = except(:select).select(join_dependency.columns) + relation = except(:select).select(join_dependency.columns + select_values) apply_join_dependency(relation, join_dependency) end def apply_join_dependency(relation, join_dependency) relation = relation.except(:includes, :eager_load, :preload) - relation = join_dependency.join_relation(relation) + relation = relation.joins join_dependency if using_limitable_reflections?(join_dependency.reflections) relation diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index c05632e688..182b9ed89c 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -94,7 +94,7 @@ module ActiveRecord []) relation.joins! rest - @relation = join_dependency.join_relation(relation) + @relation = relation.joins join_dependency end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9fcb6db726..9c9690215a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -928,7 +928,7 @@ module ActiveRecord :string_join when Hash, Symbol, Array :association_join - when ActiveRecord::Associations::JoinDependency::JoinAssociation + when ActiveRecord::Associations::JoinDependency :stashed_join when Arel::Nodes::Join :join_node @@ -950,10 +950,11 @@ module ActiveRecord join_list ) - join_dependency.graft(*stashed_association_joins) + stashed_association_joins.each do |dep| + join_dependency.merge_outer_joins! dep + end - joins = join_dependency.join_associations.map!(&:join_constraints) - joins.flatten! + joins = join_dependency.join_constraints joins.each { |join| manager.from(join) } diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 0b87ab9926..cab8fd745a 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -127,7 +127,17 @@ module ActiveRecord raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size) bound = values.dup c = connection - statement.gsub('?') { quote_bound_value(bound.shift, c) } + statement.gsub('?') do + replace_bind_variable(bound.shift, c) + end + end + + def replace_bind_variable(value, c = connection) #:nodoc: + if ActiveRecord::Relation === value + value.to_sql + else + quote_bound_value(value, c) + end end def replace_named_bind_variables(statement, bind_vars) #:nodoc: @@ -135,7 +145,7 @@ module ActiveRecord if $1 == ':' # skip postgresql casts $& # return the whole match elsif bind_vars.include?(match = $2.to_sym) - quote_bound_value(bind_vars[match]) + replace_bind_variable(bind_vars[match]) else raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 47dff7d0ea..f8f2832ab1 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -75,7 +75,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase private def extend!(model) - builder = ActiveRecord::Associations::Builder::HasMany.new(:association_name, nil, {}) { } - builder.define_extensions(model) + ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { } end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index e61deb1080..bffff07089 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -253,7 +253,8 @@ class FixturesTest < ActiveRecord::TestCase end def test_fixtures_are_set_up_with_database_env_variable - ENV.stubs(:[]).with("DATABASE_URL").returns("sqlite3:///:memory:") + db_url_tmp = ENV['DATABASE_URL'] + ENV['DATABASE_URL'] = "sqlite3:///:memory:" ActiveRecord::Base.stubs(:configurations).returns({}) test_case = Class.new(ActiveRecord::TestCase) do fixtures :accounts @@ -266,6 +267,8 @@ class FixturesTest < ActiveRecord::TestCase result = test_case.new(:test_fixtures).run assert result.passed?, "Expected #{result.name} to pass:\n#{result}" + ensure + ENV['DATABASE_URL'] = db_url_tmp end end diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index 71ade0bcc2..c171c5e14e 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -9,10 +9,11 @@ module ActiveRecord def assert_responds(target, method) assert target.respond_to?(method) assert_nothing_raised do - case target.to_a.method(method).arity - when 0 + method_arity = target.to_a.method(method).arity + + if method_arity.zero? target.send(method) - when -1 + elsif method_arity < 0 if method == :shuffle! target.send(method) else diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index f814947ab2..ec43ded690 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -295,7 +295,7 @@ class RelationTest < ActiveRecord::TestCase def test_null_relation_calculations_methods assert_no_queries do assert_equal 0, Developer.none.count - assert_equal 0, Developer.none.calculate(:count, nil, {}) + assert_equal 0, Developer.none.calculate(:count, nil) assert_equal nil, Developer.none.calculate(:average, 'salary') end end @@ -486,6 +486,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal Developer.where(name: 'David').map(&:id).sort, developers end + def test_includes_with_select + query = Post.select('comments_count AS ranking').order('ranking').includes(:comments) + .where(comments: { id: 1 }) + + assert_equal ['comments_count AS ranking'], query.select_values + assert_equal 1, query.to_a.size + end + def test_loading_with_one_association posts = Post.preload(:comments) post = posts.find { |p| p.id == 1 } @@ -626,6 +634,11 @@ class RelationTest < ActiveRecord::TestCase relation = Author.where(:id => Author.where(:id => david.id)) assert_equal [david], relation.to_a } + + assert_queries(1) { + relation = Author.where('id in (?)', Author.where(id: david).select(:id)) + assert_equal [david], relation.to_a + } end def test_find_all_using_where_with_relation_and_alternate_primary_key diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 082570c55b..4c0762deca 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -31,4 +31,10 @@ class SanitizeTest < ActiveRecord::TestCase assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"]) assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars]) end + + def test_sanitize_sql_array_handles_relations + assert_match(/\(\bselect\b.*?\bwhere\b.*?\)/i, + Binary.send(:sanitize_sql_array, ["id in (?)", Binary.where(id: 1)]), + "should sanitize `Relation` as subquery") + end end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 17206ffe99..980981903a 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -421,7 +421,9 @@ class TransactionTest < ActiveRecord::TestCase topic = Topic.new(:title => 'test') topic.freeze e = assert_raise(RuntimeError) { topic.save } - assert_equal "can't modify frozen Hash", e.message + assert_match(/frozen/i, e.message) # Not good enough, but we can't do much + # about it since there is no specific error + # for frozen objects. assert !topic.persisted?, 'not persisted' assert_nil topic.id assert topic.frozen?, 'not frozen' diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 94aa0f8502..d19dd11181 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -269,7 +269,7 @@ Rails will render the `_product_ruler` partial (with no data passed to it) betwe ### Layouts -Layouts can be used to render a common view template around the results of Rails controller actions. Typically, every Rails has a couple of overall layouts that most pages are rendered within. For example, a site might have a layout for a logged in user, and a layout for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us." You would expect each layout to have a different look and feel. You can read more details about Layouts in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. +Layouts can be used to render a common view template around the results of Rails controller actions. Typically, every Rails application has a couple of overall layouts that most pages are rendered within. For example, a site might have a layout for a logged in user, and a layout for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us." You would expect each layout to have a different look and feel. You can read more details about Layouts in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide. Partial Layouts --------------- diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 34baae509b..a184f0753d 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -116,7 +116,7 @@ to Active Record instances: locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to a model. * `type` - Specifies that the model uses [Single Table - Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html). + Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance). * `(association_name)_type` - Stores the type for [polymorphic associations](association_basics.html#polymorphic-associations). * `(table_name)_count` - Used to cache the number of belonging objects on diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 339612ebc5..797b996357 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -528,7 +528,7 @@ If you validate the presence of an object associated via a `has_one` or Since `false.blank?` is true, if you want to validate the presence of a boolean field you should use `validates :field_name, inclusion: { in: [true, false] }`. -The default error message is _"can't be empty"_. +The default error message is _"can't be blank"_. ### `absence` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index bb2e8e906f..2f322d15da 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -1490,8 +1490,8 @@ So first, we'll wire up the Post show template </p> <% end %> -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> +<%= link_to 'Back', posts_path %> +| <%= link_to 'Edit', edit_post_path(@post) %> ``` This adds a form on the `Post` show page that creates a new comment by diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 91d12b4432..fe6b1ad906 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -468,14 +468,24 @@ def initialize!(group=:default) #:nodoc: end ``` -As you can see, you can only initialize an app once. This is also where the initializers are run. +As you can see, you can only initialize an app once. The initializers are run through +the `run_initializers` method which is defined in `railties/lib/rails/initializable.rb` -TODO: review this +```ruby +def run_initializers(group=:default, *args) + return if instance_variable_defined?(:@ran) + initializers.tsort_each do |initializer| + initializer.run(*args) if initializer.belongs_to?(group) + end + @ran = true +end +``` -The initializers code itself is tricky. What Rails is doing here is it -traverses all the class ancestors looking for an `initializers` method, -sorting them and running them. For example, the `Engine` class will make -all the engines available by providing the `initializers` method. +The run_initializers code itself is tricky. What Rails is doing here is +traversing all the class ancestors looking for those that respond to an +`initializers` method. It then sorts the ancestors by name, and runs them. +For example, the `Engine` class will make all the engines available by +providing an `initializers` method on them. The `Rails::Application` class, as defined in `railties/lib/rails/application.rb` defines `bootstrap`, `railtie`, and `finisher` initializers. The `bootstrap` initializers diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index 20313a2608..dd70c272c6 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -1,4 +1,3 @@ -require 'rbconfig' require 'rails/app_rails_loader' # If we are inside a Rails application this method performs an exec and thus diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 485bd1eb09..4201dac42f 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -1,6 +1,7 @@ require 'fileutils' require 'optparse' require 'action_dispatch' +require 'rails' module Rails class Server < ::Rack::Server @@ -32,7 +33,7 @@ module Rails opt_parser.parse! args - options[:log_stdout] = options[:daemonize].blank? && options[:environment] == "development" + options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development" options[:server] = args.shift options end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 20afca2618..ba688f1e9e 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -27,26 +27,62 @@ class Rails::ServerTest < ActiveSupport::TestCase end def test_environment_with_rails_env - with_rails_env 'production' do - server = Rails::Server.new - assert_equal 'production', server.options[:environment] + with_rack_env nil do + with_rails_env 'production' do + server = Rails::Server.new + assert_equal 'production', server.options[:environment] + end end end def test_environment_with_rack_env - with_rack_env 'production' do - server = Rails::Server.new - assert_equal 'production', server.options[:environment] + with_rails_env nil do + with_rack_env 'production' do + server = Rails::Server.new + assert_equal 'production', server.options[:environment] + end end end def test_log_stdout - args = ["-e", "development"] - options = Rails::Server::Options.new.parse!(args) - assert_equal true, options[:log_stdout] + with_rack_env nil do + with_rails_env nil do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] - args = ["-e", "production"] - options = Rails::Server::Options.new.parse!(args) - assert_equal false, options[:log_stdout] + args = ["-e", "development"] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] + + args = ["-e", "production"] + options = Rails::Server::Options.new.parse!(args) + assert_equal false, options[:log_stdout] + + with_rack_env 'development' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] + end + + with_rack_env 'production' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal false, options[:log_stdout] + end + + with_rails_env 'development' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal true, options[:log_stdout] + end + + with_rails_env 'production' do + args = [] + options = Rails::Server::Options.new.parse!(args) + assert_equal false, options[:log_stdout] + end + end + end end end diff --git a/railties/test/env_helpers.rb b/railties/test/env_helpers.rb index 6223c85bbf..330fe150ca 100644 --- a/railties/test/env_helpers.rb +++ b/railties/test/env_helpers.rb @@ -1,7 +1,10 @@ +require 'rails' + module EnvHelpers private def with_rails_env(env) + Rails.instance_variable_set :@_env, nil switch_env 'RAILS_ENV', env do switch_env 'RACK_ENV', nil do yield @@ -10,6 +13,7 @@ module EnvHelpers end def with_rack_env(env) + Rails.instance_variable_set :@_env, nil switch_env 'RACK_ENV', env do switch_env 'RAILS_ENV', nil do yield |