diff options
41 files changed, 392 insertions, 135 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index f93f0d4404..db7b56f47e 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -73,7 +73,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. - # If the address input had been named "post[address][street]", the params would have included + # If the address input had been named \"post[address][street]", the params would have included # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. # # == Sessions diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 1fea8344e7..a5b4679fae 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -110,18 +110,17 @@ module ActionDispatch def visit_CAT(node, optional) left = visit(node.left, optional) right = visit(node.right, optional) + if optional && !(right && left) "" else - left + right + [left, right].join end end def visit_SYMBOL(node) if value = options[node.to_sym] Router::Utils.escape_path(value) - else - nil end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index e4c3ddd3f9..3e9e90a950 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1102,6 +1102,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'projects#index', @response.body end + def test_scoped_root_as_name + draw do + scope '(:locale)', :locale => /en|pl/ do + root :to => 'projects#index', :as => 'projects' + end + end + + assert_equal '/en', projects_path(:locale => 'en') + assert_equal '/', projects_path + get '/en' + assert_equal 'projects#index', @response.body + end + def test_scope_with_format_option draw do get "direct/index", as: :no_format_direct, format: false diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 61a6233430..a09650c559 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,8 @@ +* Fix `collection_check_boxes` generated hidden input to use the name attribute provided + in the options hash. + + *Angel N. Sciortino* + * Fix some edge cases for AV `select` helper with `:selected` option *Bogdan Gusiev* diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 52006d856b..9b77ebeb1b 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -27,7 +27,8 @@ module ActionView # Append a hidden field to make sure something will be sent back to the # server if all check boxes are unchecked. - hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil) + hidden_name = @html_options[:name] || "#{tag_name}[]" + hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil) rendered_collection + hidden end diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index b56847396d..d28e4aeb48 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -179,6 +179,13 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 1 end + test 'collection check boxes generates a hidden field using the given :name in :html_options' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, {}, {name: "user[other_category_ids][]"} + + assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1 + end + test 'collection check boxes accepts a collection and generate a serie of checkboxes with labels for label method' do collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] with_collection_check_boxes :user, :category_ids, collection, :id, :name diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c255f59b00..810a2c668d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,34 @@ +* Fixed `ActiveRecord::Associations::CollectionAssociation#find` + when using `has_many` association with `:inverse_of` and finding an array of one element, + it should return an array of one element too. + + *arthurnn* + +* Callbacks on has_many should access the in memory parent if a inverse_of is set. + + *arthurnn* + +* `ActiveRecord::ConnectionAdapters.string_to_time` respects + string with timezone (e.g. Wed, 04 Sep 2013 20:30:00 JST). + + Fixes: #12278 + + *kennyj* + +* Calling `update_attributes` will now throw an `ArgumentError` whenever it + gets a `nil` argument. More specifically, it will throw an error if the + argument that it gets passed does not respond to to `stringify_keys`. + + Example: + + @my_comment.update_attributes(nil) # => raises ArgumentError + + *John Wang* + +* Deprecate `quoted_locking_column` method, which isn't used anywhere. + + *kennyj* + * Migration dump UUID default functions to schema.rb. Fixes #10751. diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 98d573a3a2..b9199f62b9 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -80,14 +80,13 @@ module ActiveRecord load_target.find(*args) { |*block_args| yield(*block_args) } else if options[:inverse_of] && loaded? - args = args.flatten - raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank? - + args_flatten = args.flatten + raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank? result = find_by_scan(*args) result_size = Array(result).size - if !result || result_size != args.size - scope.raise_record_not_found_exception!(args, result_size, args.size) + if !result || result_size != args_flatten.size + scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size) else result end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index a3fcca8a27..0a23109b9b 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -32,6 +32,7 @@ module ActiveRecord def insert_record(record, validate = true, raise = false) set_owner_attributes(record) + set_inverse_instance(record) if raise record.save!(:validate => validate) diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 6cc9d6c079..127d0e2642 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -46,8 +46,6 @@ module ActiveRecord autoload :BelongsTo, 'active_record/associations/preloader/belongs_to' end - attr_reader :records, :associations, :preload_scope, :model - # Eager loads the named associations for the given Active Record record(s). # # In this description, 'association name' shall refer to the name passed @@ -82,40 +80,47 @@ module ActiveRecord # [ :books, :author ] # { author: :avatar } # [ :books, { author: :avatar } ] - def initialize(records, associations, preload_scope = nil) - @records = Array.wrap(records).compact.uniq - @associations = Array.wrap(associations) - @preload_scope = preload_scope || NULL_RELATION - end NULL_RELATION = Struct.new(:values).new({}) - def run - unless records.empty? - associations.each { |association| preload(association) } + def preload(records, associations, preload_scope = nil) + records = Array.wrap(records).compact.uniq + associations = Array.wrap(associations) + preload_scope = preload_scope || NULL_RELATION + + if records.empty? + [] + else + associations.flat_map { |association| + preloaders_on association, records, preload_scope + } end end private - def preload(association) + def preloaders_on(association, records, scope) case association when Hash - preload_hash(association) + preloaders_for_hash(association, records, scope) when Symbol - preload_one(association) + preloaders_for_one(association, records, scope) when String - preload_one(association.to_sym) + preloaders_for_one(association.to_sym, records, scope) else raise ArgumentError, "#{association.inspect} was not recognised for preload" end end - def preload_hash(association) - association.each do |parent, child| - Preloader.new(records, parent, preload_scope).run - Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run - end + def preloaders_for_hash(association, records, scope) + parent, child = association.to_a.first # hash should only be of length 1 + + loaders = preloaders_for_one parent, records, scope + + recs = loaders.flat_map(&:preloaded_records).uniq + loaders.concat Array.wrap(child).flat_map { |assoc| + preloaders_on assoc, recs, scope + } end # Not all records have the same class, so group then preload group on the reflection @@ -125,45 +130,76 @@ module ActiveRecord # Additionally, polymorphic belongs_to associations can have multiple associated # classes, depending on the polymorphic_type field. So we group by the classes as # well. - def preload_one(association) - grouped_records(association).each do |reflection, klasses| - klasses.each do |klass, records| - preloader_for(reflection).new(klass, records, reflection, preload_scope).run + def preloaders_for_one(association, records, scope) + grouped_records(association, records).flat_map do |reflection, klasses| + klasses.map do |rhs_klass, rs| + loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope) + loader.run self + loader end end end - def grouped_records(association) - Hash[ - records_by_reflection(association).map do |reflection, records| - [reflection, records.group_by { |record| association_klass(reflection, record) }] - end - ] + def grouped_records(association, records) + reflection_records = records_by_reflection(association, records) + + reflection_records.each_with_object({}) do |(reflection, r_records),h| + h[reflection] = r_records.group_by { |record| + association_klass(reflection, record) + } + end end - def records_by_reflection(association) + def records_by_reflection(association, records) records.group_by do |record| - reflection = record.class.reflections[association] + reflection = record.class.reflect_on_association(association) - unless reflection - raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found; " \ - "perhaps you misspelled it?" - end - - reflection + reflection || raise_config_error(association) end end + def raise_config_error(association) + raise ActiveRecord::ConfigurationError, + "Association named '#{association}' was not found; " \ + "perhaps you misspelled it?" + end + def association_klass(reflection, record) if reflection.macro == :belongs_to && reflection.options[:polymorphic] - klass = record.send(reflection.foreign_type) + klass = record.read_attribute(reflection.foreign_type.to_s) klass && klass.constantize else reflection.klass end end - def preloader_for(reflection) + class AlreadyLoaded + attr_reader :owners, :reflection + + def initialize(klass, owners, reflection, preload_scope) + @owners = owners + @reflection = reflection + end + + def run(preloader); end + + def preloaded_records + owners.flat_map { |owner| owner.read_attribute reflection.name } + end + end + + class NullPreloader + def self.new(klass, owners, reflection, preload_scope); self; end + def self.run(preloader); end + end + + def preloader_for(reflection, owners, rhs_klass) + return NullPreloader unless rhs_klass + + if owners.first.association(reflection.name).loaded? + return AlreadyLoaded + end + case reflection.macro when :has_many reflection.options[:through] ? HasManyThrough : HasMany diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 928da71eed..69b65982b3 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -3,6 +3,7 @@ module ActiveRecord class Preloader class Association #:nodoc: attr_reader :owners, :reflection, :preload_scope, :model, :klass + attr_reader :preloaded_records def initialize(klass, owners, reflection, preload_scope) @klass = klass @@ -12,15 +13,14 @@ module ActiveRecord @model = owners.first && owners.first.class @scope = nil @owners_by_key = nil + @preloaded_records = [] end - def run - unless owners.first.association(reflection.name).loaded? - preload - end + def run(preloader) + preload(preloader) end - def preload + def preload(preloader) raise NotImplementedError end @@ -68,36 +68,39 @@ module ActiveRecord private - def associated_records_by_owner + def associated_records_by_owner(preloader) owners_map = owners_by_key owner_keys = owners_map.keys.compact # Each record may have multiple owners, and vice-versa - records_by_owner = Hash[owners.map { |owner| [owner, []] }] + records_by_owner = owners.each_with_object({}) do |owner,h| + h[owner] = [] + end - if klass && owner_keys.any? + if owner_keys.any? # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) - sliced.each { |slice| - records = records_for(slice) - caster = type_caster(records, association_key_name) - records.each do |record| - owner_key = caster.call record[association_key_name] - - owners_map[owner_key].each do |owner| - records_by_owner[owner] << record - end + + records = load_slices sliced + records.each do |record, owner_key| + owners_map[owner_key].each do |owner| + records_by_owner[owner] << record end - } + end end records_by_owner end - IDENTITY = lambda { |value| value } - def type_caster(results, name) - IDENTITY + def load_slices(slices) + @preloaded_records = slices.flat_map { |slice| + records_for(slice) + } + + @preloaded_records.map { |record| + [record, record[association_key_name]] + } end def reflection_scope @@ -116,6 +119,14 @@ module ActiveRecord scope.select! preload_values[:select] || values[:select] || table[Arel.star] scope.includes! preload_values[:includes] || values[:includes] + if preload_values.key? :order + scope.order! preload_values[:order] + else + if values.key? :order + scope.order! values[:order] + end + end + if options[:as] scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) end diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb index e6cd35e7a1..5adffcd831 100644 --- a/activerecord/lib/active_record/associations/preloader/collection_association.rb +++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb @@ -9,8 +9,8 @@ module ActiveRecord super.order(preload_scope.values[:order] || reflection_scope.values[:order]) end - def preload - associated_records_by_owner.each do |owner, records| + def preload(preloader) + associated_records_by_owner(preloader).each do |owner, records| association = owner.association(reflection.name) association.loaded! association.target.concat(records) diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb index c042a44b21..b62ca6f681 100644 --- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb @@ -33,16 +33,22 @@ module ActiveRecord # Once we have used the join table column (in super), we manually instantiate the # actual records, ensuring that we don't create more than one instances of the same # record - def associated_records_by_owner - records = {} - super.each_value do |rows| - rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) } - end - end + def load_slices(slices) + identity_map = {} + caster = nil + name = association_key_name + + records_to_keys = slices.flat_map { |slice| + records = records_for(slice) + caster ||= records.column_types.fetch(name, records.identity_type) + records.map! { |row| + record = identity_map[row[klass.primary_key]] ||= klass.instantiate(row) + [record, caster.type_cast(row[name])] + } + } + @preloaded_records = records_to_keys.map(&:first) - def type_caster(results, name) - caster = results.column_types.fetch(name, results.identity_type) - lambda { |value| caster.type_cast value } + records_to_keys end def build_scope diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb index 157b627ad5..7b37b5942d 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb @@ -4,7 +4,7 @@ module ActiveRecord class HasManyThrough < CollectionAssociation #:nodoc: include ThroughAssociation - def associated_records_by_owner + def associated_records_by_owner(preloader) records_by_owner = super if reflection_scope.distinct_value diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb index 44e804d785..2b5cfda8ce 100644 --- a/activerecord/lib/active_record/associations/preloader/singular_association.rb +++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb @@ -5,8 +5,8 @@ module ActiveRecord private - def preload - associated_records_by_owner.each do |owner, associated_records| + def preload(preloader) + associated_records_by_owner(preloader).each do |owner, associated_records| record = associated_records.first association = owner.association(reflection.name) diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 2c625cec04..ea21836c65 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -2,7 +2,6 @@ module ActiveRecord module Associations class Preloader module ThroughAssociation #:nodoc: - def through_reflection reflection.through_reflection end @@ -11,34 +10,67 @@ module ActiveRecord reflection.source_reflection end - def associated_records_by_owner - through_records = through_records_by_owner + def associated_records_by_owner(preloader) + preloader.preload(owners, + through_reflection.name, + through_scope) + + through_records = owners.map do |owner, h| + association = owner.association through_reflection.name + + [owner, Array(association.reader)] + end + + reset_association owners, through_reflection.name - Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run + middle_records = through_records.map { |(_,rec)| rec }.flatten - through_records.each do |owner, records| - records.map! { |r| r.send(source_reflection.name) }.flatten! - records.compact! + preloaders = preloader.preload(middle_records, + source_reflection.name, + reflection_scope) + + middle_to_pl = preloaders.each_with_object({}) do |pl,h| + pl.owners.each { |middle| + h[middle] = pl + } end + + through_records.each_with_object({}) { |(lhs,center),records_by_owner| + pl_to_middle = center.group_by { |record| middle_to_pl[record] } + + records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles| + rhs_records = middles.flat_map { |r| + r.send(source_reflection.name) + }.compact + + loaded_records = pl.preloaded_records + i = 0 + record_index = loaded_records.each_with_object({}) { |r,indexes| + indexes[r] = i + i += 1 + } + records = rhs_records.sort_by { |rhs| record_index[rhs] } + @preloaded_records.concat rhs_records + records + end + } end private - def through_records_by_owner - Preloader.new(owners, through_reflection.name, through_scope).run - + def reset_association(owners, association_name) should_reset = (through_scope != through_reflection.klass.unscoped) || (reflection.options[:source_type] && through_reflection.collection?) - owners.each_with_object({}) do |owner, h| - association = owner.association through_reflection.name - h[owner] = Array(association.reader) - - # Dont cache the association - we would only be caching a subset - association.reset if should_reset + # Dont cache the association - we would only be caching a subset + if should_reset + owners.each { |owner| + owner.association(association_name).reset + } end end + def through_scope scope = through_reflection.klass.unscoped diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 4f06955406..30fa2c8ba5 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -12,6 +12,9 @@ module ActiveRecord # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt> # exception is raised. def assign_attributes(new_attributes) + if !new_attributes.respond_to?(:stringify_keys) + raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." + end return if new_attributes.blank? attributes = new_attributes.stringify_keys diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index fb53090edc..2596c221bc 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -203,11 +203,19 @@ module ActiveRecord end end - def new_time(year, mon, mday, hour, min, sec, microsec) + def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) # Treat 0000-00-00 00:00:00 as nil. return nil if year.nil? || (year == 0 && mon == 0 && mday == 0) - Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil + if offset + time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil + return nil unless time + + time -= offset + Base.default_timezone == :utc ? time : time.getlocal + else + Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil + end end def fast_string_to_date(string) @@ -232,7 +240,7 @@ module ActiveRecord time_hash = Date._parse(string) time_hash[:sec_fraction] = microseconds(time_hash) - new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)) + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 626fe40103..55776a91c0 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -150,6 +150,7 @@ module ActiveRecord # Quote the column name used for optimistic locking. def quoted_locking_column + ActiveSupport::Deprecation.warn "ActiveRecord::Base.quoted_locking_column is deprecated and will be removed in Rails 4.2 or later." connection.quote_column_name(locking_column) end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4e86e905ed..cfaf566ec4 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -599,8 +599,9 @@ module ActiveRecord preload = preload_values preload += includes_values unless eager_loading? + preloader = ActiveRecord::Associations::Preloader.new preload.each do |associations| - ActiveRecord::Associations::Preloader.new(@records, associations).run + preloader.preload @records, associations end @records.each { |record| record.readonly! } if readonly_value diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ebeead0dc2..064e31f634 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -337,6 +337,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) } end + def test_find_ids_and_inverse_of + force_signal37_to_load_all_clients_of_firm + + firm = companies(:first_firm) + client = firm.clients_of_firm.find(3) + assert_kind_of Client, client + + client_ary = firm.clients_of_firm.find([3]) + assert_kind_of Array, client_ary + assert_equal client, client_ary.first + end + def test_find_all firm = Firm.all.merge!(:order => "id").first assert_equal 2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length @@ -518,6 +530,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_inverse_on_before_validate + firm = companies(:first_firm) + assert_queries(1) do + firm.clients_of_firm << Client.new("name" => "Natural Company") + end + end + def test_new_aliased_to_build company = companies(:first_firm) new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") } diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index dd8b426b25..c0e80c5fe9 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -29,7 +29,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, :owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses, :subscribers, :books, :subscriptions, :developers, :categorizations, :essays, - :categories_posts + :categories_posts, :clubs, :memberships # Dummies to force column loads so query counts are clean. def setup @@ -37,10 +37,38 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase Reader.create :person_id => 0, :post_id => 0 end + def test_preload_sti_middle_relation + club = Club.create!(name: 'Aaron cool banana club') + member1 = Member.create!(name: 'Aaron') + member2 = Member.create!(name: 'Cat') + + SuperMembership.create! club: club, member: member1 + CurrentMembership.create! club: club, member: member2 + + club1 = Club.includes(:members).find_by_id club.id + assert_equal [member1, member2].sort_by(&:id), + club1.members.sort_by(&:id) + end + def make_model(name) Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } end + def test_ordered_habtm + person_prime = Class.new(ActiveRecord::Base) do + def self.name; 'Person'; end + + has_many :readers + has_many :posts, -> { order('posts.id DESC') }, :through => :readers + end + posts = person_prime.includes(:posts).first.posts + + assert_operator posts.length, :>, 1 + posts.each_cons(2) do |left,right| + assert_operator left.id, :>, right.id + end + end + def test_singleton_has_many_through book = make_model "Book" subscription = make_model "Subscription" diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index baba55ea43..9c66ed354e 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -92,7 +92,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_set_attributes_without_hash topic = Topic.new - assert_nothing_raised { topic.attributes = '' } + assert_raise(ArgumentError) { topic.attributes = '' } end def test_integers_as_nil diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb index 3a4f414ae8..5ab2f18e9d 100644 --- a/activerecord/test/cases/column_test.rb +++ b/activerecord/test/cases/column_test.rb @@ -110,6 +110,16 @@ module ActiveRecord assert_equal 1800, column.type_cast(30.minutes) assert_equal 7200, column.type_cast(2.hours) end + + def test_string_to_time_with_timezone + old = ActiveRecord::Base.default_timezone + [:utc, :local].each do |zone| + ActiveRecord::Base.default_timezone = zone + assert_equal Time.utc(2013, 9, 4, 0, 0, 0), Column.string_to_time("Wed, 04 Sep 2013 03:00:00 EAT") + end + rescue + ActiveRecord::Base.default_timezone = old + end end end end diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb index 490b599fb6..981a75faf6 100644 --- a/activerecord/test/cases/forbidden_attributes_protection_test.rb +++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb @@ -61,4 +61,9 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase assert_equal 'Guille', person.first_name assert_equal 'm', person.gender end + + def test_blank_attributes_should_not_raise + person = Person.new + assert_nil person.assign_attributes(ProtectedParams.new({})) + end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index dfa12cb97c..a16ed963fe 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -272,6 +272,10 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert p.treasures.empty? assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty? end + + def test_quoted_locking_column_is_deprecated + assert_deprecated { ActiveRecord::Base.quoted_locking_column } + end end class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 30dc2a34c6..6cd3e2154e 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -419,10 +419,6 @@ class PersistenceTest < ActiveRecord::TestCase assert !Topic.find(1).approved? end - def test_update_attribute_does_not_choke_on_nil - assert Topic.find(1).update(nil) - end - def test_update_attribute_for_readonly_attribute minivan = Minivan.find('m1') assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } @@ -701,6 +697,17 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal topic.title, Topic.find(1234).title end + def test_update_attributes_parameters + topic = Topic.find(1) + assert_nothing_raised do + topic.update_attributes({}) + end + + assert_raises(ArgumentError) do + topic.update_attributes(nil) + end + end + def test_update! Reply.validates_presence_of(:title) reply = Reply.find(2) diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 8104c607b5..1aa77f95bf 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -39,7 +39,7 @@ class Firm < Company has_many :unsorted_clients, :class_name => "Client" has_many :unsorted_clients_with_symbol, :class_name => :Client has_many :clients_sorted_desc, -> { order "id DESC" }, :class_name => "Client" - has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client" + has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :inverse_of => :firm has_many :clients_ordered_by_name, -> { order "name" }, :class_name => "Client" has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false has_many :dependent_clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :dependent => :destroy @@ -117,6 +117,10 @@ class Client < Company has_many :accounts, :through => :firm, :source => :accounts belongs_to :account + validate do + firm + end + class RaisedOnSave < RuntimeError; end attr_accessor :raise_on_save before_save do diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb index bcbb7e42c5..df7167ee93 100644 --- a/activerecord/test/models/membership.rb +++ b/activerecord/test/models/membership.rb @@ -8,6 +8,11 @@ class CurrentMembership < Membership belongs_to :club end +class SuperMembership < Membership + belongs_to :member, -> { order('members.id DESC') } + belongs_to :club +end + class SelectedMembership < Membership def self.default_scope select("'1' as foo") diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 308a1b2cd9..c5633d902f 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -234,7 +234,7 @@ module ActiveSupport # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>. # Yes, this process is extending the time for a stale value by another few # seconds. Because of extended life of the previous cache, other processes - # will continue to use slightly stale data for a just a big longer. In the + # will continue to use slightly stale data for a just a bit longer. In the # meantime that first process will go ahead and will write into cache the # new value. After that all the processes will start getting new value. # The key is to keep <tt>:race_condition_ttl</tt> small. diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 54cff6d394..c9c0eff2bf 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -460,7 +460,7 @@ module ActiveSupport # See <tt>number_to_human_size</tt> if you want to print a file # size. # - # You can also define you own unit-quantifier names if you want + # You can also define your own unit-quantifier names if you want # to use other decimal units (eg.: 1500 becomes "1.5 # kilometers", 0.150 becomes "150 milliliters", etc). You may # define a wide range of unit quantifiers, even fractional ones diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index ad08cb01f7..34baae509b 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -48,10 +48,10 @@ overall database access code. Active Record gives us several mechanisms, the most important being the ability to: -* Represent models and their data -* Represent associations between these models -* Represent inheritance hierarchies through related models -* Validate models before they get persisted to the database +* Represent models and their data. +* Represent associations between these models. +* Represent inheritance hierarchies through related models. +* Validate models before they get persisted to the database. * Perform database operations in an object-oriented fashion. Convention over Configuration in Active Record @@ -78,9 +78,9 @@ of two or more words, the model class name should follow the Ruby conventions, using the CamelCase form, while the table name must contain the words separated by underscores. Examples: -* Database Table - Plural with underscores separating words (e.g., `book_clubs`) +* Database Table - Plural with underscores separating words (e.g., `book_clubs`). * Model Class - Singular with the first letter of each word capitalized (e.g., -`BookClub`) +`BookClub`). | Model / Class | Table / Schema | | ------------- | -------------- | @@ -101,7 +101,7 @@ depending on the purpose of these columns. fields that Active Record will look for when you create associations between your models. * **Primary keys** - By default, Active Record will use an integer column named - `id` as the table's primary key. When using [Rails + `id` as the table's primary key. When using [Active Record Migrations](migrations.html) to create your tables, this column will be automatically created. @@ -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). * `(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 @@ -368,6 +368,6 @@ Rails keeps track of which files have been committed to the database and provides rollback features. To actually create the table, you'd run `rake db:migrate` and to roll it back, `rake db:rollback`. -Note that the above code is database-agnostic: it will run in MySQL, postgresql, -Oracle and others. You can learn more about migrations in the [Active Record -Migrations guide](migrations.html) +Note that the above code is database-agnostic: it will run in MySQL, +PostgreSQL, Oracle and others. You can learn more about migrations in the +[Active Record Migrations guide](migrations.html). diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 28013beeae..faa37efd37 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1358,7 +1358,7 @@ COMMIT The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`). -Suppose we want to set the 'locked' attribute to true if we're +Suppose we want to set the 'locked' attribute to `false` if we're creating a new record, but we don't want to include it in the query. So we want to find the client named "Andy", or if that client doesn't exist, create a client named "Andy" which is not locked. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 8ef982f8c5..91b268d766 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -340,7 +340,7 @@ class CreateAssembliesAndParts < ActiveRecord::Migration t.timestamps end - create_table :assemblies_parts do |t| + create_table :assemblies_parts, id: false do |t| t.belongs_to :assembly t.belongs_to :part end diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 5f170474ee..c499cd0727 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -759,4 +759,15 @@ Since the connection pooling is handled inside of Active Record by default, all Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue. +If you try to use more connections than are available, Active Record will block +and wait for a connection from the pool. When it cannot get connection, a timeout +error similar to given below will be thrown. + +```ruby +ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it: +``` + +If you get the above error, you might want to increase the size of connection +pool by incrementing the `pool` option in `database.yml` + NOTE. If you have enabled `Rails.threadsafe!` mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index e5bc5ef038..e7556111f8 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -22,8 +22,8 @@ with Rails. However, to get the most out of it, you need to have some prerequisites installed: * The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer -* The [RubyGems](http://rubygems.org/) packaging system - * To learn more about RubyGems, please read the [RubyGems User Guide](http://docs.rubygems.org/read/book/1) +* The [RubyGems](http://rubygems.org) packaging system + * To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org) * A working installation of the [SQLite3 Database](http://www.sqlite.org) Rails is a web application framework running on the Ruby programming language. @@ -262,7 +262,7 @@ of code: ### Setting the Application Home Page Now that we have made the controller and view, we need to tell Rails when we -want Hello Rails! to show up. In our case, we want it to show up when we +want `Hello, Rails!` to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, "Welcome Aboard" is occupying that spot. @@ -687,7 +687,7 @@ invoking the command: `rake db:migrate RAILS_ENV=production`. ### Saving data in the controller -Back in `posts_controller`, we need to change the `create` action +Back in `PostsController`, we need to change the `create` action to use the new `Post` model to save the data in the database. Open `app/controllers/posts_controller.rb` and change the `create` action to look like this: @@ -846,7 +846,7 @@ Open `app/views/welcome/index.html.erb` and modify it as follows: ```html+erb <h1>Hello, Rails!</h1> -<%= link_to "My Blog", controller: "posts" %> +<%= link_to 'My Blog', controller: 'posts' %> ``` The `link_to` method is one of Rails' built-in view helpers. It creates a @@ -1013,7 +1013,7 @@ errors with `@post.errors.full_messages`. arguments. If the number is greater than one, the string will be automatically pluralized. -The reason why we added `@post = Post.new` in `posts_controller` is that +The reason why we added `@post = Post.new` in the `PostsController` is that otherwise `@post` would be `nil` in our view, and calling `@post.errors.any?` would throw an error. @@ -1031,7 +1031,7 @@ attempt to do just that on the new post form [(http://localhost:3000/posts/new)] We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating posts. -The first step we'll take is adding an `edit` action to `posts_controller`. +The first step we'll take is adding an `edit` action to the `PostsController`. ```ruby def edit @@ -1338,7 +1338,7 @@ class Comment < ActiveRecord::Base end ``` -This is very similar to the `post.rb` model that you saw earlier. The difference +This is very similar to the `Post` model that you saw earlier. The difference is the line `belongs_to :post`, which sets up an Active Record _association_. You'll learn a little about associations in the next section of this guide. diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 948b6f167c..33daa79133 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -843,6 +843,7 @@ So, for example, instead of the default error message `"can not be blank"` you c | numericality | :equal_to | :equal_to | count | | numericality | :less_than | :less_than | count | | numericality | :less_than_or_equal_to | :less_than_or_equal_to | count | +| numericality | :only_integer | :not_an_integer | - | | numericality | :odd | :odd | - | | numericality | :even | :even | - | diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 1c858069f3..0f5379059e 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -314,7 +314,7 @@ will produce a migration that looks like this ```ruby class AddDetailsToProducts < ActiveRecord::Migration def change - add_column :products, :price, precision: 5, scale: 2 + add_column :products, :price, :decimal, precision: 5, scale: 2 add_reference :products, :supplier, polymorphic: true, index: true end end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index fabc981e7a..a4babbe8c3 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Include `web-console` into newly generated applications' Gemfile. + + *Genadi Samokovarov* + * `rails server` will only extend the logger to output to STDOUT in development environment. diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 5cbca838f4..edc76e6c34 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -10,6 +10,9 @@ source 'https://rubygems.org' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 1.2' +# Run `rails console` in the browser. Read more: https://github.com/rails/web-console +gem 'web-console', group: :development + group :doc do # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', require: false diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index eb569b7dab..0194dce6f3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -55,6 +55,8 @@ production: adapter: postgresql encoding: unicode database: <%= app_name %>_production + # For details on connection pooling, see rails configration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling pool: 5 username: <%= app_name %> password: |