From e0cb21f5f767606ad3ecf2db33855d27aa9f083d Mon Sep 17 00:00:00 2001 From: Tristan Gamilis <tristan@2suggestions.com.au> Date: Tue, 7 Apr 2015 17:50:32 +1000 Subject: Require explicit counter_cache option for has_many Previously has_many associations assumed a counter_cache was to be used based on the presence of an appropriately named column. This is inconsistent, since the inverse belongs_to association will not make this assumption. See issues #19042 #8446. This commit checks for the presence of the counter_cache key in the options of either the has_many or belongs_to association as well as ensuring that the *_count column is present. --- .../lib/active_record/associations/has_many_association.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index ca27c9fdde..794eb9e183 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -80,8 +80,15 @@ module ActiveRecord [association_scope.limit_value, count].compact.min end + + # Returns whether a counter cache should be used for this association. + # + # The counter_cache option must be given on either the owner or inverse + # association, and the column must be present on the owner. def has_cached_counter?(reflection = reflection()) - owner.attribute_present?(cached_counter_attribute_name(reflection)) + if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache] + owner.attribute_present?(cached_counter_attribute_name(reflection)) + end end def cached_counter_attribute_name(reflection = reflection()) -- cgit v1.2.3 From 929fcd26179f2b589d52f9515cbd349f1197ece9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= <mehmetemininac@gmail.com> Date: Wed, 15 Apr 2015 21:00:09 +0300 Subject: Prevent duplicating `where` clauses when model is extended from an abstract class Fixes #19528 fix for mysql2 test better test --- activerecord/lib/active_record/scoping/default.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 3590b8846e..a1adf8e3ee 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -100,6 +100,7 @@ module ActiveRecord end def build_default_scope(base_rel = relation) # :nodoc: + return if abstract_class? if !Base.is_a?(method(:default_scope).owner) # The user has defined their own default scope method, so call that evaluate_default_scope { default_scope } -- cgit v1.2.3 From 7f2037a990fd81e07f612169f72e8d59fc2a4e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= <mehmetemininac@gmail.com> Date: Mon, 8 Jun 2015 10:57:03 +0300 Subject: Add missing data types for ActiveRecord migrations --- .../postgresql/schema_definitions.rb | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index 022dbdfa27..6399bddbee 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -103,6 +103,30 @@ module ActiveRecord args.each { |name| column(name, :point, options) } end + def line(*args, **options) + args.each { |name| column(name, :line, options) } + end + + def lseg(*args, **options) + args.each { |name| column(name, :lseg, options) } + end + + def box(*args, **options) + args.each { |name| column(name, :box, options) } + end + + def path(*args, **options) + args.each { |name| column(name, :path, options) } + end + + def polygon(*args, **options) + args.each { |name| column(name, :polygon, options) } + end + + def circle(*args, **options) + args.each { |name| column(name, :circle, options) } + end + def serial(*args, **options) args.each { |name| column(name, :serial, options) } end -- cgit v1.2.3 From beb07fbfae845d20323a9863c7216c6b63aff9c7 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan <tgx_world@hotmail.com> Date: Wed, 15 Jul 2015 22:00:36 +0800 Subject: Revert "Revert "Reduce allocations when running AR callbacks."" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit bdc1d329d4eea823d07cf010064bd19c07099ff3. Before: Calculating ------------------------------------- 22.000 i/100ms ------------------------------------------------- 229.700 (± 0.4%) i/s - 1.166k Total Allocated Object: 9939 After: Calculating ------------------------------------- 24.000 i/100ms ------------------------------------------------- 246.443 (± 0.8%) i/s - 1.248k Total Allocated Object: 7939 ``` begin require 'bundler/inline' rescue LoadError => e $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler' raise e end gemfile(true) do source 'https://rubygems.org' # gem 'rails', github: 'rails/rails', ref: 'bdc1d329d4eea823d07cf010064bd19c07099ff3' gem 'rails', github: 'rails/rails', ref: 'd2876141d08341ec67cf6a11a073d1acfb920de7' gem 'arel', github: 'rails/arel' gem 'sqlite3' gem 'benchmark-ips' end require 'active_record' require 'benchmark/ips' ActiveRecord::Base.establish_connection('sqlite3::memory:') ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table :users, force: true do |t| t.string :name, :email t.boolean :admin t.timestamps null: false end end class User < ActiveRecord::Base default_scope { where(admin: true) } end admin = true 1000.times do attributes = { name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", email: "foobar@email.com", admin: admin } User.create!(attributes) admin = !admin end GC.disable Benchmark.ips(5, 3) do |x| x.report { User.all.to_a } end key = if RUBY_VERSION < '2.2' :total_allocated_object else :total_allocated_objects end before = GC.stat[key] User.all.to_a after = GC.stat[key] puts "Total Allocated Object: #{after - before}" ``` --- .../associations/has_many_through_association.rb | 2 +- activerecord/lib/active_record/callbacks.rb | 11 ++++++----- .../connection_adapters/abstract/connection_pool.rb | 4 ++-- activerecord/lib/active_record/core.rb | 8 ++++---- activerecord/lib/active_record/transactions.rb | 12 ++++++------ 5 files changed, 19 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index cd79266952..1aa6a2ca74 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -133,7 +133,7 @@ module ActiveRecord if scope.klass.primary_key count = scope.destroy_all.length else - scope.each { |record| record.run_callbacks :destroy } + scope.each(&:_run_destroy_callbacks) arel = scope.arel diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 3027ce928e..19f0dca5a6 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -289,24 +289,25 @@ module ActiveRecord end def destroy #:nodoc: - run_callbacks(:destroy) { super } + _run_destroy_callbacks { super } end def touch(*) #:nodoc: - run_callbacks(:touch) { super } + _run_touch_callbacks { super } end private + def create_or_update(*) #:nodoc: - run_callbacks(:save) { super } + _run_save_callbacks { super } end def _create_record #:nodoc: - run_callbacks(:create) { super } + _run_create_callbacks { super } end def _update_record(*) #:nodoc: - run_callbacks(:update) { super } + _run_update_callbacks { super } end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 6535121075..282af220fb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -508,7 +508,7 @@ module ActiveRecord synchronize do remove_connection_from_thread_cache conn - conn.run_callbacks :checkin do + conn._run_checkin_callbacks do conn.expire end @@ -764,7 +764,7 @@ module ActiveRecord end def checkout_and_verify(c) - c.run_callbacks :checkout do + c._run_checkout_callbacks do c.verify! end c diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 8a014e682e..b82488a59c 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -303,7 +303,7 @@ module ActiveRecord assign_attributes(attributes) if attributes yield self if block_given? - run_callbacks :initialize + _run_initialize_callbacks end # Initialize an empty model object from +coder+. +coder+ should be @@ -330,8 +330,8 @@ module ActiveRecord self.class.define_attribute_methods - run_callbacks :find - run_callbacks :initialize + _run_find_callbacks + _run_initialize_callbacks self end @@ -367,7 +367,7 @@ module ActiveRecord @attributes = @attributes.dup @attributes.reset(self.class.primary_key) - run_callbacks(:initialize) + _run_initialize_callbacks @new_record = true @destroyed = false diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 6f2def0df1..3131723828 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -319,8 +319,8 @@ module ActiveRecord end def before_committed! # :nodoc: - run_callbacks :before_commit_without_transaction_enrollment - run_callbacks :before_commit + _run_before_commit_without_transaction_enrollment_callbacks + _run_before_commit_callbacks end # Call the +after_commit+ callbacks. @@ -329,8 +329,8 @@ module ActiveRecord # but call it after the commit of a destroyed object. def committed!(should_run_callbacks: true) #:nodoc: if should_run_callbacks && destroyed? || persisted? - run_callbacks :commit_without_transaction_enrollment - run_callbacks :commit + _run_commit_without_transaction_enrollment_callbacks + _run_commit_callbacks end ensure force_clear_transaction_record_state @@ -340,8 +340,8 @@ module ActiveRecord # state should be rolled back to the beginning or just to the last savepoint. def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc: if should_run_callbacks - run_callbacks :rollback - run_callbacks :rollback_without_transaction_enrollment + _run_rollback_callbacks + _run_rollback_without_transaction_enrollment_callbacks end ensure restore_transaction_record_state(force_restore_state) -- cgit v1.2.3 From 6eae366d0d2e5d5211eeaf955f56bd1dc6836758 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist <s@sikac.hu> Date: Wed, 15 Jul 2015 14:07:45 -0400 Subject: Deprecate force association reload by passing true This is to simplify the association API, as you can call `reload` on the association proxy or the parent object to get the same result. For collection association, you can call `#reload` on association proxy to force a reload: @user.posts.reload # Instead of @user.posts(true) For singular association, you can call `#reload` on the parent object to clear its association cache then call the association method: @user.reload.profile # Instead of @user.profile(true) Passing a truthy argument to force association to reload will be removed in Rails 5.1. --- .../lib/active_record/associations/collection_association.rb | 8 ++++++++ .../lib/active_record/associations/singular_association.rb | 8 ++++++++ 2 files changed, 16 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 6caadb4ce8..87576abd92 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -1,3 +1,5 @@ +require "active_support/deprecation" + module ActiveRecord module Associations # = Active Record Association Collection @@ -28,6 +30,12 @@ module ActiveRecord # Implements the reader method, e.g. foo.items for Foo.has_many :items def reader(force_reload = false) if force_reload + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing an argument to force an association to reload is now + deprecated and will be removed in Rails 5.1. Please call `reload` + on the result collection proxy instead. + MSG + klass.uncached { reload } elsif stale_target? reload diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index bec9505bd2..30c5d72482 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -1,9 +1,17 @@ +require "active_support/deprecation" + module ActiveRecord module Associations class SingularAssociation < Association #:nodoc: # Implements the reader method, e.g. foo.bar for Foo.has_one :bar def reader(force_reload = false) if force_reload && klass + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing an argument to force an association to reload is now + deprecated and will be removed in Rails 5.1. Please call `reload` + on the parent object instead. + MSG + klass.uncached { reload } elsif !loaded? || stale_target? reload -- cgit v1.2.3 From a8e11ff5285c31c91255acfb462e731c1255c080 Mon Sep 17 00:00:00 2001 From: Andrii Ponomarov <andrii.ponomarov@gmail.com> Date: Fri, 17 Jul 2015 21:03:49 -0400 Subject: [ci skip] Fix typo in #any? RDoc --- activerecord/lib/active_record/associations/collection_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index ddeafb40ea..19e9ffcb3c 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -781,7 +781,7 @@ module ActiveRecord # person.pets.any? # => false # # person.pets << Pet.new(name: 'Snoop') - # person.pets.count # => 0 + # person.pets.count # => 1 # person.pets.any? # => true # # You can also pass a +block+ to define criteria. The behavior -- cgit v1.2.3 From 68af63618223c238468af1afb093eb4ccc706761 Mon Sep 17 00:00:00 2001 From: Sean Griffin <sean@thoughtbot.com> Date: Sat, 18 Jul 2015 08:42:29 -0400 Subject: Ensure that `ActionController::Parameters` can still be passed to AR Since nested hashes are also instances of `ActionController::Parameters`, and we're explicitly looking to work with a hash for nested attributes, this caused breakage in several points. This is the minimum viable fix for the issue (and one that I'm not terribly fond of). I can't think of a better place to handle this at the moment. I'd prefer to use some sort of solution that doesn't special case AC::Parameters, but we can't use something like `to_h` or `to_a` since `Enumerable` adds both. While I've added a trivial test case for verifying this fix in isolation, we really need better integration coverage to prevent regressions like this in the future. We don't actually have a lot of great places for integration coverage at the moment, so I'm deferring it for now. Fixes #20922. --- activerecord/lib/active_record/nested_attributes.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index c942d0e265..c337e1d18f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -386,6 +386,9 @@ module ActiveRecord # then the existing record will be marked for destruction. def assign_nested_attributes_for_one_to_one_association(association_name, attributes) options = self.nested_attributes_options[association_name] + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end attributes = attributes.with_indifferent_access existing_record = send(association_name) -- cgit v1.2.3 From 7550f0a016ee6647aaa76c0c0ae30bebc3867288 Mon Sep 17 00:00:00 2001 From: Sean Griffin <sean@thoughtbot.com> Date: Sat, 18 Jul 2015 10:28:24 -0400 Subject: Ensure cyclic associations w/ autosave don't cause duplicate errors This code is so fucked. Things that cause this bug not to replicate: - Defining the validation before the association (we end up calling `uniq!` on the errors in the autosave validation) - Adding `accepts_nested_attributes_for` (I have no clue why. The only thing it does that should affect this is adds `autosave: true` to the inverse reflection, and doing that manually doesn't fix this). This solution is a hack, and I'm almost certain there's a better way to go about it, but this shouldn't cause a huge hit on validation times, and is the simplest way to get it done. Fixes #20874. --- activerecord/lib/active_record/autosave_association.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 0792d19c3e..5f38bd51f6 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -222,6 +222,7 @@ module ActiveRecord true end validate validation_method + after_validation :_ensure_no_duplicate_errors end end end @@ -456,5 +457,11 @@ module ActiveRecord end end end + + def _ensure_no_duplicate_errors + errors.messages.each_key do |attribute| + errors[attribute].uniq! + end + end end end -- cgit v1.2.3 From b5d4dd47deaae27e8f362bb9636246c5b4c56e5c Mon Sep 17 00:00:00 2001 From: Thomas Walpole <twalpole@gmail.com> Date: Sat, 18 Jul 2015 08:23:52 -0700 Subject: Ensure that 'ActionController::Parameters' can still be passed to AR for collection associations --- activerecord/lib/active_record/nested_attributes.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index c337e1d18f..a6b76b25bf 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -445,6 +445,9 @@ module ActiveRecord # ]) def assign_nested_attributes_for_collection_association(association_name, attributes_collection) options = self.nested_attributes_options[association_name] + if attributes_collection.respond_to?(:permitted?) + attributes_collection = attributes_collection.to_h + end unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" @@ -471,6 +474,9 @@ module ActiveRecord end attributes_collection.each do |attributes| + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end attributes = attributes.with_indifferent_access if attributes['id'].blank? -- cgit v1.2.3 From 0ed096ddf5416fefa3afacb72c64632c02826f95 Mon Sep 17 00:00:00 2001 From: Stefan Kanev <stefan.kanev@gmail.com> Date: Sat, 9 Aug 2014 22:19:02 +0300 Subject: Fix counter_cache for polymorphic associations Also removes a false positive test that depends on the fixed bug: At this time, counter_cache does not work with polymorphic relationships (which is a bug). The test was added to make sure that no StaleObjectError is raised when the car is destroyed. No such error is currently raised because the lock version is not incremented by appending a wheel to the car. Furthermore, `assert_difference` succeeds because `car.wheels.count` does not check the counter cache, but the collection size. The test will fail if it is replaced with `car.wheels_count || 0`. --- .../lib/active_record/associations/builder/belongs_to.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 97eb007f62..6e4a53f7fb 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -33,16 +33,24 @@ module ActiveRecord::Associations::Builder if (@_after_create_counter_called ||= false) @_after_create_counter_called = false - elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable? - model = reflection.klass + elsif attribute_changed?(foreign_key) && !new_record? + if reflection.polymorphic? + model = attribute(reflection.foreign_type).try(:constantize) + model_was = attribute_was(reflection.foreign_type).try(:constantize) + else + model = reflection.klass + model_was = reflection.klass + end + foreign_key_was = attribute_was foreign_key foreign_key = attribute foreign_key if foreign_key && model.respond_to?(:increment_counter) model.increment_counter(cache_column, foreign_key) end - if foreign_key_was && model.respond_to?(:decrement_counter) - model.decrement_counter(cache_column, foreign_key_was) + + if foreign_key_was && model_was.respond_to?(:decrement_counter) + model_was.decrement_counter(cache_column, foreign_key_was) end end end -- cgit v1.2.3 From 5bb1d4d288d019e276335465d0389fd2f5246bfd Mon Sep 17 00:00:00 2001 From: schneems <richard.schneeman@gmail.com> Date: Sun, 19 Jul 2015 16:19:15 -0500 Subject: Freeze string literals when not mutated. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I wrote a utility that helps find areas where you could optimize your program using a frozen string instead of a string literal, it's called [let_it_go](https://github.com/schneems/let_it_go). After going through the output and adding `.freeze` I was able to eliminate the creation of 1,114 string objects on EVERY request to [codetriage](codetriage.com). How does this impact execution? To look at memory: ```ruby require 'get_process_mem' mem = GetProcessMem.new GC.start GC.disable 1_114.times { " " } before = mem.mb after = mem.mb GC.enable puts "Diff: #{after - before} mb" ``` Creating 1,114 string objects results in `Diff: 0.03125 mb` of RAM allocated on every request. Or 1mb every 32 requests. To look at raw speed: ```ruby require 'benchmark/ips' number_of_objects_reduced = 1_114 Benchmark.ips do |x| x.report("freeze") { number_of_objects_reduced.times { " ".freeze } } x.report("no-freeze") { number_of_objects_reduced.times { " " } } end ``` We get the results ``` Calculating ------------------------------------- freeze 1.428k i/100ms no-freeze 609.000 i/100ms ------------------------------------------------- freeze 14.363k (± 8.5%) i/s - 71.400k no-freeze 6.084k (± 8.1%) i/s - 30.450k ``` Now we can do some maths: ```ruby ips = 6_226k # iterations / 1 second call_time_before = 1.0 / ips # seconds per iteration ips = 15_254 # iterations / 1 second call_time_after = 1.0 / ips # seconds per iteration diff = call_time_before - call_time_after number_of_objects_reduced * diff * 100 # => 0.4530373333993266 miliseconds saved per request ``` So we're shaving off 1 second of execution time for every 220 requests. Is this going to be an insane speed boost to any Rails app: nope. Should we merge it: yep. p.s. If you know of a method call that doesn't modify a string input such as [String#gsub](https://github.com/schneems/let_it_go/blob/b0e2da69f0cca87ab581022baa43291cdf48638c/lib/let_it_go/core_ext/string.rb#L37) please [give me a pull request to the appropriate file](https://github.com/schneems/let_it_go/blob/b0e2da69f0cca87ab581022baa43291cdf48638c/lib/let_it_go/core_ext/string.rb#L37), or open an issue in LetItGo so we can track and freeze more strings. Keep those strings Frozen  --- activerecord/lib/active_record/attribute_methods.rb | 2 +- activerecord/lib/active_record/attribute_methods/read.rb | 2 +- activerecord/lib/active_record/attribute_methods/write.rb | 2 +- .../active_record/connection_adapters/postgresql/type_metadata.rb | 2 +- activerecord/lib/active_record/relation/delegation.rb | 2 +- activerecord/lib/active_record/relation/predicate_builder.rb | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 9d58a19304..abe1d465a5 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -42,7 +42,7 @@ module ActiveRecord def [](name) @method_cache.compute_if_absent(name) do - safe_name = name.unpack('h*').first + safe_name = name.unpack('h*'.freeze).first temp_method = "__temp__#{safe_name}" ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__ diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 0d989c2eca..2363cf7608 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -37,7 +37,7 @@ module ActiveRecord protected def define_method_attribute(name) - safe_name = name.unpack('h*').first + safe_name = name.unpack('h*'.freeze).first temp_method = "__temp__#{safe_name}" ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index ab017c7b54..07d5e7d38e 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -24,7 +24,7 @@ module ActiveRecord protected def define_method_attribute=(name) - safe_name = name.unpack('h*').first + safe_name = name.unpack('h*'.freeze).first ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb index 58715978f7..b2c49989a4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb @@ -12,7 +12,7 @@ module ActiveRecord end def sql_type - super.gsub(/\[\]$/, "") + super.gsub(/\[\]$/, "".freeze) end def ==(other) diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 86f2c30168..d75ec72b1a 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -18,7 +18,7 @@ module ActiveRecord delegate = Class.new(klass) { include ClassSpecificRelation } - const_set klass.name.gsub('::', '_'), delegate + const_set klass.name.gsub('::'.freeze, '_'.freeze), delegate cache[klass] = delegate end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 43e9afe853..d26db7d4cf 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -52,7 +52,7 @@ module ActiveRecord key else key = key.to_s - key.split('.').first if key.include?('.') + key.split('.'.freeze).first if key.include?('.'.freeze) end end.compact end @@ -123,10 +123,10 @@ module ActiveRecord end def convert_dot_notation_to_hash(attributes) - dot_notation = attributes.keys.select { |s| s.include?(".") } + dot_notation = attributes.keys.select { |s| s.include?(".".freeze) } dot_notation.each do |key| - table_name, column_name = key.split(".") + table_name, column_name = key.split(".".freeze) value = attributes.delete(key) attributes[table_name] ||= {} -- cgit v1.2.3 From 476e3f552f59d208cb284f509760b44ad780c17a Mon Sep 17 00:00:00 2001 From: "Alberto F. Capel" <afcapel@gmail.com> Date: Tue, 14 Jul 2015 23:47:16 +0100 Subject: Add #cache_key to ActiveRecord::Relation. --- activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/base.rb | 1 + .../lib/active_record/collection_cache_key.rb | 29 ++++++++++++++++++++++ activerecord/lib/active_record/relation.rb | 26 +++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 activerecord/lib/active_record/collection_cache_key.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index f5cf92db64..264f869c68 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -53,6 +53,7 @@ module ActiveRecord autoload :Persistence autoload :QueryCache autoload :Querying + autoload :CollectionCacheKey autoload :ReadonlyAttributes autoload :RecordInvalid, 'active_record/validations' autoload :Reflection diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c918e88590..55a7e053bc 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -280,6 +280,7 @@ module ActiveRecord #:nodoc: extend Explain extend Enum extend Delegation::DelegateCache + extend CollectionCacheKey include Core include Persistence diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb new file mode 100644 index 0000000000..72b50c1d28 --- /dev/null +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -0,0 +1,29 @@ +module ActiveRecord + module CollectionCacheKey + + def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc: + query_signature = Digest::MD5.hexdigest(collection.to_sql) + key = "#{collection.model_name.cache_key}/query-#{query_signature}" + + if collection.loaded? + size = collection.size + timestamp = collection.max_by(×tamp_column).public_send(timestamp_column) + else + column_type = type_for_attribute(timestamp_column.to_s) + column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}" + + query = collection.select("COUNT(*) AS size", "MAX(#{column}) AS timestamp") + result = connection.select_one(query) + + size = result["size"] + timestamp = column_type.deserialize(result["timestamp"]) + end + + if timestamp + "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}" + else + "#{key}-#{size}" + end + end + end +end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e4df122af6..3ed04dee3b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -298,6 +298,32 @@ module ActiveRecord limit_value ? to_a.many? : size > 1 end + # Returns a cache key that can be used to identify the records fetched by + # this query. The cache key is built with a fingerprint of the sql query, + # the number of records matched by the query and a timestamp of the last + # updated record. When a new record comes to match the query, or any of + # the existing records is updated or deleted, the cache key changes. + # + # Product.where("name like ?", "%Cosmic Encounter%").cache_key + # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000" + # + # If the collection is loaded, the method will iterate through the records + # to generate the timestamp, otherwise it will trigger one SQL query like: + # + # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%') + # + # You can also pass a custom timestamp column to fetch the timestamp of the + # last updated record. + # + # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at) + # + # You can customize the strategy to generate the key on a per model basis + # overriding ActiveRecord::Base#collection_cache_key. + def cache_key(timestamp_column = :updated_at) + @cache_keys ||= {} + @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column) + end + # Scope all queries to the current scope. # # Comment.where(post_id: 1).scoping do -- cgit v1.2.3 From 1883f37742b976d3d20e0ba4a3564911f4868172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= <mehmetemininac@gmail.com> Date: Mon, 20 Jul 2015 10:58:59 +0300 Subject: Add missing method name to exception description --- activerecord/lib/active_record/associations/collection_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 19e9ffcb3c..b5a8c81fe4 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -971,7 +971,7 @@ module ActiveRecord alias_method :append, :<< def prepend(*args) - raise NoMethodError, "prepend on association is not defined. Please use << or append" + raise NoMethodError, "prepend on association is not defined. Please use <<, push or append" end # Equivalent to +delete_all+. The difference is that returns +self+, instead -- cgit v1.2.3 From cc54f6be15d412b4fcb2a99d7bdf9244d756ce3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= <mehmetemininac@gmail.com> Date: Mon, 20 Jul 2015 11:11:24 +0300 Subject: fix doc about ActiveRecord::Transactions::ClassMethods#transaction [ci skip] --- activerecord/lib/active_record/transactions.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 3131723828..267ac26c79 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -204,9 +204,8 @@ module ActiveRecord # # Note that "TRUNCATE" is also a MySQL DDL statement! module ClassMethods - # See ActiveRecord::Transactions::ClassMethods for detailed documentation. + # See the ConnectionAdapters::DatabaseStatements#transaction API docs. def transaction(options = {}, &block) - # See the ConnectionAdapters::DatabaseStatements#transaction API docs. connection.transaction(options, &block) end -- cgit v1.2.3 From c0ef95a1c6db3095c4b5f80f8044fbbbdfebeff1 Mon Sep 17 00:00:00 2001 From: Sean Griffin <sean@thoughtbot.com> Date: Mon, 20 Jul 2015 09:00:00 -0600 Subject: Correctly ignore `mark_for_destruction` without `autosave` As per the docs, `mark_for_destruction` should do nothing if `autosave` is not set to true. We normally persist associations on a record no matter what if the record is a new record, but we were always skipping records which were `marked_for_destruction?`. Fixes #20882 --- activerecord/lib/active_record/autosave_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 5f38bd51f6..dbb0e2fab2 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -325,7 +325,7 @@ module ActiveRecord # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt> # enabled records if they're marked_for_destruction? or destroyed. def association_valid?(reflection, record) - return true if record.destroyed? || record.marked_for_destruction? + return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) validation_context = self.validation_context unless [:create, :update].include?(self.validation_context) unless valid = record.valid?(validation_context) -- cgit v1.2.3 From 12b0b26df7560ab5199ba830586864085441508f Mon Sep 17 00:00:00 2001 From: Roque Pinel <repinel@gmail.com> Date: Sun, 19 Jul 2015 22:00:36 -0400 Subject: Fix state being carried over from previous transaction This clears the transaction record state when the transaction finishes with a `:committed` status. Considering the following example where `name` is a required attribute. Before we had `new_record?` returning `true` for a persisted record: ```ruby author = Author.create! name: 'foo' author.name = nil author.save # => false author.new_record? # => true ``` --- activerecord/lib/active_record/transactions.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 267ac26c79..887d7a5903 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -379,6 +379,10 @@ module ActiveRecord raise ActiveRecord::Rollback unless status end status + ensure + if @transaction_state && @transaction_state.committed? + clear_transaction_record_state + end end protected -- cgit v1.2.3 From e975d7cd1a6cb177f914024ffec8dd9a6cdc4ba1 Mon Sep 17 00:00:00 2001 From: Jori Hardman <jorihardman@gmail.com> Date: Mon, 29 Jun 2015 11:37:05 -0500 Subject: Ensure that microsecond precision is only used for version of mysql that support it. Fixes #19711 --- .../connection_adapters/abstract_adapter.rb | 12 ++++++++++++ .../connection_adapters/abstract_mysql_adapter.rb | 18 +++++++++++++----- .../connection_adapters/sqlite3_adapter.rb | 12 ------------ 3 files changed, 25 insertions(+), 17 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 6d3a21a3dc..56227ddd80 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -107,6 +107,18 @@ module ActiveRecord @prepared_statements = false end + class Version + include Comparable + + def initialize(version_string) + @version = version_string.split('.').map(&:to_i) + end + + def <=>(version_string) + @version <=> version_string.split('.').map(&:to_i) + end + end + class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) casted_binds = conn.prepare_binds_for_database(bvs) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 2027492f29..af156c9c78 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -307,7 +307,7 @@ module ActiveRecord # # http://bugs.mysql.com/bug.php?id=39170 def supports_transaction_isolation? - version[0] >= 5 + version >= '5.0.0' end def supports_indexes_in_create? @@ -319,11 +319,11 @@ module ActiveRecord end def supports_views? - version[0] >= 5 + version >= '5.0.0' end def supports_datetime_with_precision? - (version[0] == 5 && version[1] >= 6) || version[0] >= 6 + version >= '5.6.4' end def native_database_types @@ -386,6 +386,14 @@ module ActiveRecord 0 end + def quoted_date(value) + if supports_datetime_with_precision? + super + else + super.sub(/\.\d{6}\z/, '') + end + end + # REFERENTIAL INTEGRITY ==================================== def disable_referential_integrity #:nodoc: @@ -938,7 +946,7 @@ module ActiveRecord end def version - @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i) + @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0]) end def mariadb? @@ -946,7 +954,7 @@ module ActiveRecord end def supports_rename_index? - mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6 + mariadb? ? false : version >= '5.7.6' end def configure_connection diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7c809b088c..358039723f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -65,18 +65,6 @@ module ActiveRecord boolean: { name: "boolean" } } - class Version - include Comparable - - def initialize(version_string) - @version = version_string.split('.').map(&:to_i) - end - - def <=>(version_string) - @version <=> version_string.split('.').map(&:to_i) - end - end - class StatementPool < ConnectionAdapters::StatementPool private -- cgit v1.2.3 From b184398b5359da5b76367aa61551d0f3d7e99fc5 Mon Sep 17 00:00:00 2001 From: Roque Pinel <repinel@gmail.com> Date: Mon, 22 Jun 2015 22:28:57 -0400 Subject: Deprecate and rename the keys for association restrict_dependent_destroy Previously `has_one` and `has_many` associations were using the `one` and `many` keys respectively. Both of these keys have special meaning in I18n (they are considered to be pluralizations) so by renaming them to `has_one` and `has_many` we make the messages more explicit and most importantly they don't clash with linguistical systems that need to validate translation keys (and their pluralizations). The `:'restrict_dependent_destroy.one'` key should be replaced with `:'restrict_dependent_destroy.has_one'`, and `:'restrict_dependent_destroy.many'` with `:'restrict_dependent_destroy.has_many'`. [Roque Pinel & Christopher Dell] --- .../lib/active_record/associations/has_many_association.rb | 9 ++++++++- .../lib/active_record/associations/has_one_association.rb | 9 ++++++++- activerecord/lib/active_record/locale/en.yml | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index ca27c9fdde..9f6c832c1b 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -16,7 +16,14 @@ module ActiveRecord when :restrict_with_error unless empty? record = klass.human_attribute_name(reflection.name).downcase - owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record) + message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil + if message + ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) + The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1. + Please use `:'restrict_dependent_destroy.has_many'` instead. + MESSAGE + end + owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record) throw(:abort) end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 41a75b820e..5a92bc5e8a 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -12,7 +12,14 @@ module ActiveRecord when :restrict_with_error if load_target record = klass.human_attribute_name(reflection.name).downcase - owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record) + message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.one', record: record, raise: true) rescue nil + if message + ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) + The error key `:'restrict_dependent_destroy.one'` has been deprecated and will be removed in Rails 5.1. + Please use `:'restrict_dependent_destroy.has_one'` instead. + MESSAGE + end + owner.errors.add(:base, message || :'restrict_dependent_destroy.has_one', record: record) throw(:abort) end diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index 8a3c27e6da..0b35027b2b 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -16,8 +16,8 @@ en: messages: record_invalid: "Validation failed: %{errors}" restrict_dependent_destroy: - one: "Cannot delete record because a dependent %{record} exists" - many: "Cannot delete record because dependent %{record} exist" + has_one: "Cannot delete record because a dependent %{record} exists" + has_many: "Cannot delete record because dependent %{record} exist" # Append your own errors here or at the model/attributes scope. # You can define own errors for models or model attributes. -- cgit v1.2.3 From d763956ed95bce581f12151a5ed4df12bcefdeee Mon Sep 17 00:00:00 2001 From: Sameer Rahmani <lxsameer@gnu.org> Date: Tue, 21 Jul 2015 18:49:09 +0430 Subject: Extra caller details added to ActiveRecord::RecordNotFound ActiveRecord::RecordNotFound modified to store model name, primary_key and id of the caller model. It allows the catcher of this exception to make a better decision to what to do with it. For example consider this simple example: class SomeAbstractController < ActionController::Base rescue_from ActiveRecord::RecordNotFound, with: :redirect_to_404 private def redirect_to_404(e) return redirect_to(posts_url) if e.model == 'Post' raise end end --- activerecord/lib/active_record/core.rb | 8 +++++--- activerecord/lib/active_record/errors.rb | 9 +++++++++ activerecord/lib/active_record/nested_attributes.rb | 4 +++- activerecord/lib/active_record/relation/finder_methods.rb | 3 ++- 4 files changed, 19 insertions(+), 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index b82488a59c..ffce2173ec 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -162,11 +162,13 @@ module ActiveRecord } record = statement.execute([id], self, connection).first unless record - raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}" + raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", + name, primary_key, id) end record rescue RangeError - raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'" + raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'", + name, primary_key) end def find_by(*args) # :nodoc: @@ -199,7 +201,7 @@ module ActiveRecord end def find_by!(*args) # :nodoc: - find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}") + find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}", name) end def initialize_generated_modules # :nodoc: diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 0f1759abaa..d589620f8a 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -47,6 +47,15 @@ module ActiveRecord # Raised when Active Record cannot find record by given id or set of ids. class RecordNotFound < ActiveRecordError + attr_reader :model, :primary_key, :id + + def initialize(message = nil, model = nil, primary_key = nil, id = nil) + @primary_key = primary_key + @model = model + @id = id + + super(message) + end end # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index a6b76b25bf..c5a1488588 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -561,7 +561,9 @@ module ActiveRecord end def raise_nested_attributes_record_not_found!(association_name, record_id) - raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}" + model = self.class._reflect_on_association(association_name).klass.name + raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", + model, 'id', record_id) end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 9fef55adea..009b2bad57 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -85,7 +85,8 @@ module ActiveRecord def find_by!(arg, *args) where(arg, *args).take! rescue RangeError - raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value" + raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value", + @klass.name) end # Gives a record (or N records if a parameter is supplied) without any implied -- cgit v1.2.3 From 4f1ec3ac96d4593063603306d2548e0206124d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= <mehmetemininac@gmail.com> Date: Sun, 12 Jul 2015 03:03:05 +0300 Subject: Fix misleading errors for has_one through relations --- activerecord/lib/active_record/associations.rb | 16 ++++++++++++++-- .../active_record/associations/through_association.rb | 12 ++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 82cb3fed59..a830b0e0e4 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -61,12 +61,18 @@ module ActiveRecord end end - class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc: + class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") end end + class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc: + end + + class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc: + end + class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.") @@ -79,12 +85,18 @@ module ActiveRecord end end - class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc: + class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.") end end + class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc: + end + + class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc: + end + class EagerLoadPolymorphicError < ActiveRecordError #:nodoc: def initialize(reflection) super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}") diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 55ee9f04e0..d0ec3e8015 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -76,13 +76,21 @@ module ActiveRecord def ensure_mutable unless source_reflection.belongs_to? - raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + if reflection.has_one? + raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + else + raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + end end end def ensure_not_nested if reflection.nested? - raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) + if reflection.has_one? + raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection) + else + raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) + end end end -- cgit v1.2.3 From a20965287caa59dae7360b74086c27ae945f94c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=BE=20=D0=91=D1=83=D0=B4?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA?= <dmitriy.budnik@gmail.com> Date: Thu, 23 Jul 2015 11:41:51 +0300 Subject: Fixes documentation typo. Documentation had extra colon after keyword. --- .../active_record/connection_adapters/abstract/schema_statements.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index e3115abe66..a30945d0ee 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -761,9 +761,9 @@ module ActiveRecord # [<tt>:name</tt>] # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>. # [<tt>:on_delete</tt>] - # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+ + # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+ # [<tt>:on_update</tt>] - # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+ + # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+ def add_foreign_key(from_table, to_table, options = {}) return unless supports_foreign_keys? -- cgit v1.2.3 From ad5c1a39346d559f26c11c2399117491a3d81c0b Mon Sep 17 00:00:00 2001 From: Robin Dupret <robin.dupret@gmail.com> Date: Thu, 23 Jul 2015 14:27:09 +0200 Subject: Rename the enum_{prefix,suffix} options to _{prefix,suffix} This makes it more clear that they are reserved keywords and also it seems less redundant as the line already starts with the call to the `enum` method. --- activerecord/lib/active_record/enum.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index c0d9d9c1c8..9c9307df15 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -75,22 +75,22 @@ module ActiveRecord # # Conversation.where("status <> ?", Conversation.statuses[:archived]) # - # You can use the +:enum_prefix+ or +:enum_suffix+ options when you need - # to define multiple enums with same values. If the passed value is +true+, - # the methods are prefixed/suffixed with the name of the enum. + # You can use the +:_prefix+ or +:_suffix+ options when you need to define + # multiple enums with same values. If the passed value is +true+, the methods + # are prefixed/suffixed with the name of the enum. # # class Invoice < ActiveRecord::Base - # enum verification: [:done, :fail], enum_prefix: true + # enum verification: [:done, :fail], _prefix: true # end # - # It is also possible to supply a custom prefix. + # It is also possible to supply a custom value: # # class Invoice < ActiveRecord::Base - # enum verification: [:done, :fail], enum_prefix: :verification_status + # enum verification: [:done, :fail], _prefix: :verification_status # end # - # Note that <tt>:enum_prefix</tt>/<tt>:enum_suffix</tt> are reserved keywords - # and can not be used as an enum name. + # Note that <tt>:_prefix</tt>/<tt>:_suffix</tt> are reserved keywords and can + # not be used as enum names. module Enum def self.extended(base) # :nodoc: @@ -137,8 +137,8 @@ module ActiveRecord def enum(definitions) klass = self - enum_prefix = definitions.delete(:enum_prefix) - enum_suffix = definitions.delete(:enum_suffix) + enum_prefix = definitions.delete(:_prefix) + enum_suffix = definitions.delete(:_suffix) definitions.each do |name, values| # statuses = { } enum_values = ActiveSupport::HashWithIndifferentAccess.new -- cgit v1.2.3 From cdc32defcfc2ce5312c4b02e09f6cef2172843c6 Mon Sep 17 00:00:00 2001 From: Robin Dupret <robin.dupret@gmail.com> Date: Thu, 23 Jul 2015 19:21:19 +0200 Subject: Improvements on the enum documentation [ci skip] The note regarding the `_prefix` and `_suffix` options is no longer useful since they were renamed specifically for this purpose. Also the given example doesn't show what these options enable and in which case they are really useful (when there are conflicting values for instance). Refs #20999. [Godfrey Chan & Robin Dupret] --- activerecord/lib/active_record/enum.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 9c9307df15..91a13cb0cd 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -77,20 +77,22 @@ module ActiveRecord # # You can use the +:_prefix+ or +:_suffix+ options when you need to define # multiple enums with same values. If the passed value is +true+, the methods - # are prefixed/suffixed with the name of the enum. + # are prefixed/suffixed with the name of the enum. It is also possible to + # supply a custom value: # - # class Invoice < ActiveRecord::Base - # enum verification: [:done, :fail], _prefix: true + # class Conversation < ActiveRecord::Base + # enum status: [:active, :archived], _suffix: true + # enum comments_status: [:active, :inactive], _prefix: :comments # end # - # It is also possible to supply a custom value: + # With the above example, the bang and predicate methods along with the + # associated scopes are now prefixed and/or suffixed accordingly: # - # class Invoice < ActiveRecord::Base - # enum verification: [:done, :fail], _prefix: :verification_status - # end + # conversation.active_status! + # conversation.archived_status? # => false # - # Note that <tt>:_prefix</tt>/<tt>:_suffix</tt> are reserved keywords and can - # not be used as enum names. + # conversation.comments_inactive! + # conversation.comments_active? # => false module Enum def self.extended(base) # :nodoc: -- cgit v1.2.3 From d937a1175f10586b892842348c1d6ecaa47aad2e Mon Sep 17 00:00:00 2001 From: Sean Griffin <sean@thoughtbot.com> Date: Fri, 24 Jul 2015 09:13:20 -0600 Subject: `destroy` shouldn't raise when child associations fail to save Deep down in the association internals, we're calling `destroy!` rather than `destroy` when handling things like `dependent` or autosave association callbacks. Unfortunately, due to the structure of the code (e.g. it uses callbacks for everything), it's nearly impossible to pass whether to call `destroy` or `destroy!` down to where we actually need it. As such, we have to do some legwork to handle this. Since the callbacks are what actually raise the exception, we need to rescue it in `ActiveRecord::Callbacks`, rather than `ActiveRecord::Persistence` where it matters. (As an aside, if this code wasn't so callback heavy, it would handling this would likely be as simple as changing `destroy` to call `destroy!` instead of the other way around). Since we don't want to lose the exception when `destroy!` is called (in particular, we don't want the value of the `record` field to change to the parent class), we have to do some additional legwork to hold onto it where we can use it. Again, all of this is ugly and there is definitely a better way to do this. However, barring a much more significant re-architecting for what I consider to be a reletively minor improvement, I'm willing to take this small hit to the flow of this code (begrudgingly). --- activerecord/lib/active_record/callbacks.rb | 3 +++ activerecord/lib/active_record/persistence.rb | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 19f0dca5a6..c7c769b283 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -290,6 +290,9 @@ module ActiveRecord def destroy #:nodoc: _run_destroy_callbacks { super } + rescue RecordNotDestroyed => e + @_association_destroy_exception = e + false end def touch(*) #:nodoc: diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 0a6e4ac0bd..09c36d7b4d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -193,7 +193,7 @@ module ActiveRecord # and #destroy! raises ActiveRecord::RecordNotDestroyed. # See ActiveRecord::Callbacks for further details. def destroy! - destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self)) + destroy || _raise_record_not_destroyed end # Returns an instance of the specified +klass+ with the attributes of the @@ -548,5 +548,12 @@ module ActiveRecord def verify_readonly_attribute(name) raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name) end + + def _raise_record_not_destroyed + @_association_destroy_exception ||= nil + raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", self) + ensure + @_association_destroy_exception = nil + end end end -- cgit v1.2.3 From 119b9181ece399c67213543fb5227b82688b536f Mon Sep 17 00:00:00 2001 From: Sean Griffin <sean@thoughtbot.com> Date: Sat, 25 Jul 2015 13:05:05 -0600 Subject: Properly allow uniqueness validations on primary keys. This is an alternate implementation of #20966. [Sean Griffin & presskey] --- activerecord/lib/active_record/validations/uniqueness.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 5106f4e127..32d17a1392 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -17,7 +17,9 @@ module ActiveRecord value = map_enum_attribute(finder_class, attribute, value) relation = build_relation(finder_class, table, attribute, value) - relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted? + if record.persisted? && finder_class.primary_key.to_s != attribute.to_s + relation = relation.where.not(finder_class.primary_key => record.id) + end relation = scope_relation(record, table, relation) relation = relation.merge(options[:conditions]) if options[:conditions] -- cgit v1.2.3 From a74fbb2972bb161c553d7a5bd3a13299de6c7927 Mon Sep 17 00:00:00 2001 From: Robin Dupret <robin.dupret@gmail.com> Date: Tue, 28 Jul 2015 12:22:37 +0200 Subject: Add `:nodoc:` for internal testing methods [ci skip] --- activerecord/lib/active_record/fixtures.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index d062dd9e34..f1dc56df63 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -827,12 +827,12 @@ module ActiveRecord module TestFixtures extend ActiveSupport::Concern - def before_setup + def before_setup # :nodoc: setup_fixtures super end - def after_teardown + def after_teardown # :nodoc: super teardown_fixtures end -- cgit v1.2.3 From f80aa5994603e684e3fecd3f53bfbf242c73a107 Mon Sep 17 00:00:00 2001 From: schneems <richard.schneeman@gmail.com> Date: Fri, 24 Jul 2015 00:46:12 -0500 Subject: Decrease string allocations on AR#respond_to? When a symbol is passed in, we call `to_s` on it which allocates a string. The two hardcoded symbols that are used internally are `:to_partial_path` and `:to_model`. This change buys us 71,136 bytes of memory and 1,777 fewer objects per request. --- activerecord/lib/active_record/attribute_methods.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index abe1d465a5..7fb899c242 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -230,7 +230,15 @@ module ActiveRecord # person.respond_to(:nothing) # => false def respond_to?(name, include_private = false) return false unless super - name = name.to_s + + case name + when :to_partial_path + name = "to_partial_path".freeze + when :to_model + name = "to_model".freeze + else + name = name.to_s + end # If the result is true then check for the select case. # For queries selecting a subset of columns, return false for unselected columns. -- cgit v1.2.3 From 07f8a96aa14b642a8641dcb22dad07f995d3917e Mon Sep 17 00:00:00 2001 From: starbelly <starbelly@pobox.com> Date: Sat, 1 Aug 2015 16:48:56 -0500 Subject: Add run_cmd class method to ActiveRecord::Tasks::DatabaseTasks - Added run_cmd() class method to dry up Kernel.system() messages within this namespace and avoid shell expansion by passing a list of arguments instead of a string - Update structure_dump, structure_load, and related tests units to pass a list of params instead of using a string to avoid shell expansion --- .../lib/active_record/tasks/database_tasks.rb | 13 ++++++++++++ .../tasks/postgresql_database_tasks.rb | 23 +++++++++++----------- 2 files changed, 25 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 683741768b..94a8116b8b 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -252,6 +252,19 @@ module ActiveRecord end end + def self.run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end + + def run_cmd_error(cmd, args, action) + msg = "failed to execute:\n" + msg << "#{cmd} #{args.join(' ')}\n\n" + msg << "Please check that `#{cmd}` is :\n" + msg << " - present on this system\n" + msg << " - in your PATH\n" + msg << " - has proper permissions\n\n" + end + private def class_for_adapter(adapter) diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index d7da95c8a9..d406f24c31 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -1,5 +1,3 @@ -require 'shellwords' - module ActiveRecord module Tasks # :nodoc: class PostgreSQLDatabaseTasks # :nodoc: @@ -55,19 +53,22 @@ module ActiveRecord when String ActiveRecord::Base.dump_schemas end - unless search_path.blank? - search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ") - end - - command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}" - raise 'Error dumping database' unless Kernel.system(command) + args = ['-i', '-s', '-x', '-O', '-f', filename] + unless search_path.blank? + args << search_path.split(',').map do |part| + "--schema=#{part.strip}" + end.join(' ') + end + args << configuration['database'] + ActiveRecord::Tasks::DatabaseTasks.run_cmd('pg_dump', args, 'dumping') File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" } end - def structure_load(filename) - set_psql_env - Kernel.system("psql -X -q -f #{Shellwords.escape(filename)} #{configuration['database']}") + def structure_load(filename) + set_psql_env + args = [ '-q', '-f', filename, configuration['database'] ] + ActiveRecord::Tasks::DatabaseTasks.run_cmd('psql', args, 'loading' ) end private -- cgit v1.2.3 From 722abe1722a8bcf1798fc7f7f9a8cf4dcfa28e88 Mon Sep 17 00:00:00 2001 From: Sean Griffin <sean@thoughtbot.com> Date: Sat, 1 Aug 2015 18:15:46 -0600 Subject: Fix test failures caused by #20884 PostgreSQL is strict about the usage of `DISTINCT` and `ORDER BY`, which one of the tests demonstrated. The order clause is never going to be relevant in the query we're performing, so let's just remove it entirely. --- activerecord/lib/active_record/collection_cache_key.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index 72b50c1d28..3c4ca3d116 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -12,7 +12,9 @@ module ActiveRecord column_type = type_for_attribute(timestamp_column.to_s) column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}" - query = collection.select("COUNT(*) AS size", "MAX(#{column}) AS timestamp") + query = collection + .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp") + .unscope(:order) result = connection.select_one(query) size = result["size"] -- cgit v1.2.3 From f744d627364a9a98dedda5b30711bf80ebc3451f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= <mehmetemininac@gmail.com> Date: Sun, 2 Aug 2015 04:38:36 +0300 Subject: Use memoization for collection associations ids reader Fixes #21082 remove extra space --- .../lib/active_record/associations/collection_association.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 87576abd92..0fc2b83b71 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -62,8 +62,10 @@ module ActiveRecord record.send(reflection.association_primary_key) end else - column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" - scope.pluck(column) + @association_ids ||= ( + column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" + scope.pluck(column) + ) end end -- cgit v1.2.3 From 977ffe880624bbd05f5ee1cc6e4fa51a999884ab Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono <kamipo@gmail.com> Date: Tue, 4 Aug 2015 04:59:50 +0900 Subject: Should use `server_info[:version]` instead of `info[:version]` Because `info[:version]` is a client version, the server version is `server_info[:version]`. --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index e97e82f056..b7db57c9fe 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -254,7 +254,7 @@ module ActiveRecord end def full_version - @full_version ||= @connection.info[:version] + @full_version ||= @connection.server_info[:version] end def set_field_encoding field_name -- cgit v1.2.3 From 2bba65a2c6f5fdf5241b3be73f56a316fa823a61 Mon Sep 17 00:00:00 2001 From: Brendan Buckingham <brendan.buckingham@gmail.com> Date: Thu, 6 Aug 2015 13:58:59 -0500 Subject: better docs for ActiveRecord::Migration#table_name_options --- activerecord/lib/active_record/migration.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4cfda302ea..b7b508d853 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -716,7 +716,9 @@ module ActiveRecord end end - def table_name_options(config = ActiveRecord::Base) + # Builds a hash for use in ActiveRecord::Migration#proper_table_name using + # the Active Record object's table_name prefix and suffix + def table_name_options(config = ActiveRecord::Base) #:nodoc: { table_name_prefix: config.table_name_prefix, table_name_suffix: config.table_name_suffix -- cgit v1.2.3 From 25cee1f0373aa3b1d893413a959375480e0ac684 Mon Sep 17 00:00:00 2001 From: Sina Siadat <siadat@gmail.com> Date: Sat, 18 Jul 2015 20:44:13 +0430 Subject: Add ActiveRecord::Relation#in_batches `in_batches` yields Relation objects if a block is given, otherwise it returns an instance of `BatchEnumerator`. The existing `find_each` and `find_in_batches` methods work with batches of records. The new API allows working with relation batches as well. Examples: Person.in_batches.each_record(&:party_all_night!) Person.in_batches.update_all(awesome: true) Person.in_batches.delete_all Person.in_batches.map do |relation| relation.delete_all sleep 10 # Throttles the delete queries end --- activerecord/lib/active_record/querying.rb | 2 +- activerecord/lib/active_record/relation.rb | 7 ++ activerecord/lib/active_record/relation/batches.rb | 98 ++++++++++++++++++++-- .../relation/batches/batch_enumerator.rb | 67 +++++++++++++++ 4 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 activerecord/lib/active_record/relation/batches/batch_enumerator.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 4e597590e9..87a1988f2f 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -6,7 +6,7 @@ module ActiveRecord delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all delegate :find_by, :find_by!, to: :all delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all - delegate :find_each, :find_in_batches, to: :all + delegate :find_each, :find_in_batches, :in_batches, to: :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 3ed04dee3b..e47b7b1ed9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -667,6 +667,13 @@ module ActiveRecord "#<#{self.class.name} [#{entries.join(', ')}]>" end + protected + + def load_records(records) + @records = records + @loaded = true + end + private def exec_queries diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index e07580a563..beb8fa511c 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -1,3 +1,5 @@ +require "active_record/relation/batches/batch_enumerator" + module ActiveRecord module Batches # Looping through a collection of records from the database @@ -122,24 +124,102 @@ module ActiveRecord end end + in_batches(of: batch_size, begin_at: begin_at, end_at: end_at, load: true) do |batch| + yield batch.to_a + end + end + + # Yields ActiveRecord::Relation objects to work with a batch of records. + # + # Person.where("age > 21").in_batches do |relation| + # relation.delete_all + # sleep(10) # Throttle the delete queries + # end + # + # If you do not provide a block to #in_batches, it will return a + # BatchEnumerator which is enumerable. + # + # Person.in_batches.with_index do |relation, batch_index| + # puts "Processing relation ##{batch_index}" + # relation.each { |relation| relation.delete_all } + # end + # + # Examples of calling methods on the returned BatchEnumerator object: + # + # Person.in_batches.delete_all + # Person.in_batches.update_all(awesome: true) + # Person.in_batches.each_record(&:party_all_night!) + # + # ==== Options + # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000. + # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false. + # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value. + # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value. + # + # This is especially useful if you want to work with the + # ActiveRecord::Relation object instead of the array of records, or if + # you want multiple workers dealing with the same processing queue. You can + # make worker 1 handle all the records between id 0 and 10,000 and worker 2 + # handle from 10,000 and beyond (by setting the +:begin_at+ and +:end_at+ + # option on each worker). + # + # # Let's process the next 2000 records + # Person.in_batches(of: 2000, begin_at: 2000).update_all(awesome: true) + # + # An example of calling where query method on the relation: + # + # Person.in_batches.each do |relation| + # relation.update_all('age = age + 1') + # relation.where('age > 21').update_all(should_party: true) + # relation.where('age <= 21').delete_all + # end + # + # NOTE: If you are going to iterate through each record, you should call + # #each_record on the yielded BatchEnumerator: + # + # Person.in_batches.each_record(&:party_all_night!) + # + # NOTE: It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # consistent. Therefore the primary key must be orderable, e.g an integer + # or a string. + # + # NOTE: You can't set the limit either, that's used to control the batch + # sizes. + def in_batches(of: 1000, begin_at: nil, end_at: nil, load: false) + relation = self + unless block_given? + return BatchEnumerator.new(of: of, begin_at: begin_at, end_at: end_at, relation: self) + end + if logger && (arel.orders.present? || arel.taken.present?) logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") end - relation = relation.reorder(batch_order).limit(batch_size) + relation = relation.reorder(batch_order).limit(of) relation = apply_limits(relation, begin_at, end_at) - records = relation.to_a + batch_relation = relation + + loop do + if load + records = batch_relation.to_a + ids = records.map(&:id) + yielded_relation = self.where(primary_key => ids) + yielded_relation.load_records(records) + else + ids = batch_relation.pluck(primary_key) + yielded_relation = self.where(primary_key => ids) + end - while records.any? - records_size = records.size - primary_key_offset = records.last.id - raise "Primary key not included in the custom select clause" unless primary_key_offset + break if ids.empty? - yield records + primary_key_offset = ids.last + raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset - break if records_size < batch_size + yield yielded_relation - records = relation.where(table[primary_key].gt(primary_key_offset)).to_a + break if ids.length < of + batch_relation = relation.where(table[primary_key].gt(primary_key_offset)) end end diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb new file mode 100644 index 0000000000..153aae9584 --- /dev/null +++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb @@ -0,0 +1,67 @@ +module ActiveRecord + module Batches + class BatchEnumerator + include Enumerable + + def initialize(of: 1000, begin_at: nil, end_at: nil, relation:) #:nodoc: + @of = of + @relation = relation + @begin_at = begin_at + @end_at = end_at + end + + # Looping through a collection of records from the database (using the + # +all+ method, for example) is very inefficient since it will try to + # instantiate all the objects at once. + # + # In that case, batch processing methods allow you to work with the + # records in batches, thereby greatly reducing memory consumption. + # + # Person.in_batches.each_record do |person| + # person.do_awesome_stuff + # end + # + # Person.where("age > 21").in_batches(of: 10).each_record do |person| + # person.party_all_night! + # end + # + # If you do not provide a block to #each_record, it will return an Enumerator + # for chaining with other methods: + # + # Person.in_batches.each_record.with_index do |person, index| + # person.award_trophy(index + 1) + # end + def each_record + return to_enum(:each_record) unless block_given? + + @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: true).each do |relation| + relation.to_a.each { |record| yield record } + end + end + + # Delegates #delete_all, #update_all, #destroy_all methods to each batch. + # + # People.in_batches.delete_all + # People.in_batches.destroy_all('age < 10') + # People.in_batches.update_all('age = age + 1') + [:delete_all, :update_all, :destroy_all].each do |method| + define_method(method) do |*args, &block| + @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false).each do |relation| + relation.send(method, *args, &block) + end + end + end + + # Yields an ActiveRecord::Relation object for each batch of records. + # + # Person.in_batches.each do |relation| + # relation.update_all(awesome: true) + # end + def each + enum = @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false) + return enum.each { |relation| yield relation } if block_given? + enum + end + end + end +end -- cgit v1.2.3 From f7ebdb1ac51f26cff76e5642a75717df1b446746 Mon Sep 17 00:00:00 2001 From: Zachary Scott <e@zzak.io> Date: Tue, 5 May 2015 11:11:35 -0700 Subject: Remove XML Serialization from core. This includes the following classes: - ActiveModel::Serializers::Xml - ActiveRecord::Serialization::XmlSerializer --- activerecord/lib/active_record/serialization.rb | 2 - .../active_record/serializers/xml_serializer.rb | 193 --------------------- 2 files changed, 195 deletions(-) delete mode 100644 activerecord/lib/active_record/serializers/xml_serializer.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 48c12dcf9f..23dc6465af 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -18,5 +18,3 @@ module ActiveRecord #:nodoc: end end end - -require 'active_record/serializers/xml_serializer' diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb deleted file mode 100644 index 89b7e0be82..0000000000 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ /dev/null @@ -1,193 +0,0 @@ -require 'active_support/core_ext/hash/conversions' - -module ActiveRecord #:nodoc: - module Serialization - include ActiveModel::Serializers::Xml - - # Builds an XML document to represent the model. Some configuration is - # available through +options+. However more complicated cases should - # override ActiveRecord::Base#to_xml. - # - # By default the generated XML document will include the processing - # instruction and all the object's attributes. For example: - # - # <?xml version="1.0" encoding="UTF-8"?> - # <topic> - # <title>The First Topic</title> - # <author-name>David</author-name> - # <id type="integer">1</id> - # <approved type="boolean">false</approved> - # <replies-count type="integer">0</replies-count> - # <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time> - # <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on> - # <content>Have a nice day</content> - # <author-email-address>david@loudthinking.com</author-email-address> - # <parent-id></parent-id> - # <last-read type="date">2004-04-15</last-read> - # </topic> - # - # This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>, - # <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> . - # The <tt>:only</tt> and <tt>:except</tt> options are the same as for the - # +attributes+ method. The default is to dasherize all column names, but you - # can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt> - # to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>. - # To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+. - # - # For instance: - # - # topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ]) - # - # <topic> - # <title>The First Topic</title> - # <author-name>David</author-name> - # <approved type="boolean">false</approved> - # <content>Have a nice day</content> - # <author-email-address>david@loudthinking.com</author-email-address> - # <parent-id></parent-id> - # <last-read type="date">2004-04-15</last-read> - # </topic> - # - # To include first level associations use <tt>:include</tt>: - # - # firm.to_xml include: [ :account, :clients ] - # - # <?xml version="1.0" encoding="UTF-8"?> - # <firm> - # <id type="integer">1</id> - # <rating type="integer">1</rating> - # <name>37signals</name> - # <clients type="array"> - # <client> - # <rating type="integer">1</rating> - # <name>Summit</name> - # </client> - # <client> - # <rating type="integer">1</rating> - # <name>Microsoft</name> - # </client> - # </clients> - # <account> - # <id type="integer">1</id> - # <credit-limit type="integer">50</credit-limit> - # </account> - # </firm> - # - # Additionally, the record being serialized will be passed to a Proc's second - # parameter. This allows for ad hoc additions to the resultant document that - # incorporate the context of the record being serialized. And by leveraging the - # closure created by a Proc, to_xml can be used to add elements that normally fall - # outside of the scope of the model -- for example, generating and appending URLs - # associated with models. - # - # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } - # firm.to_xml procs: [ proc ] - # - # <firm> - # # ... normal attributes as shown above ... - # <name-reverse>slangis73</name-reverse> - # </firm> - # - # To include deeper levels of associations pass a hash like this: - # - # firm.to_xml include: {account: {}, clients: {include: :address}} - # <?xml version="1.0" encoding="UTF-8"?> - # <firm> - # <id type="integer">1</id> - # <rating type="integer">1</rating> - # <name>37signals</name> - # <clients type="array"> - # <client> - # <rating type="integer">1</rating> - # <name>Summit</name> - # <address> - # ... - # </address> - # </client> - # <client> - # <rating type="integer">1</rating> - # <name>Microsoft</name> - # <address> - # ... - # </address> - # </client> - # </clients> - # <account> - # <id type="integer">1</id> - # <credit-limit type="integer">50</credit-limit> - # </account> - # </firm> - # - # To include any methods on the model being called use <tt>:methods</tt>: - # - # firm.to_xml methods: [ :calculated_earnings, :real_earnings ] - # - # <firm> - # # ... normal attributes as shown above ... - # <calculated-earnings>100000000000000000</calculated-earnings> - # <real-earnings>5</real-earnings> - # </firm> - # - # To call any additional Procs use <tt>:procs</tt>. The Procs are passed a - # modified version of the options hash that was given to +to_xml+: - # - # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') } - # firm.to_xml procs: [ proc ] - # - # <firm> - # # ... normal attributes as shown above ... - # <abc>def</abc> - # </firm> - # - # Alternatively, you can yield the builder object as part of the +to_xml+ call: - # - # firm.to_xml do |xml| - # xml.creator do - # xml.first_name "David" - # xml.last_name "Heinemeier Hansson" - # end - # end - # - # <firm> - # # ... normal attributes as shown above ... - # <creator> - # <first_name>David</first_name> - # <last_name>Heinemeier Hansson</last_name> - # </creator> - # </firm> - # - # As noted above, you may override +to_xml+ in your ActiveRecord::Base - # subclasses to have complete control about what's generated. The general - # form of doing this is: - # - # class IHaveMyOwnXML < ActiveRecord::Base - # def to_xml(options = {}) - # require 'builder' - # options[:indent] ||= 2 - # xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent]) - # xml.instruct! unless options[:skip_instruct] - # xml.level_one do - # xml.tag!(:second_level, 'content') - # end - # end - # end - def to_xml(options = {}, &block) - XmlSerializer.new(self, options).serialize(&block) - end - end - - class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc: - class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: - def compute_type - klass = @serializable.class - cast_type = klass.type_for_attribute(name) - - type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type - - { :text => :string, - :time => :datetime }[type] || type - end - protected :compute_type - end - end -end -- cgit v1.2.3 From 5ec9e9349e320e5547c8b36266dbeed07082dd51 Mon Sep 17 00:00:00 2001 From: Matt Hanlon <hanlon@skytap.com> Date: Fri, 7 Aug 2015 12:33:09 -0700 Subject: use correct DB connection for generated HABTM table --- .../associations/builder/has_and_belongs_to_many.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') 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 ffd9c9d6fc..b18d99d54e 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 @@ -46,7 +46,7 @@ module ActiveRecord::Associations::Builder join_model = Class.new(ActiveRecord::Base) { class << self; - attr_accessor :class_resolver + attr_accessor :left_model attr_accessor :name attr_accessor :table_name_resolver attr_accessor :left_reflection @@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder end def self.compute_type(class_name) - class_resolver.compute_type class_name + left_model.compute_type class_name end def self.add_left_association(name, options) @@ -72,11 +72,15 @@ module ActiveRecord::Associations::Builder self.right_reflection = _reflect_on_association(rhs_name) end + def self.retrieve_connection + left_model.retrieve_connection + end + } join_model.name = "HABTM_#{association_name.to_s.camelize}" join_model.table_name_resolver = habtm - join_model.class_resolver = lhs_model + join_model.left_model = lhs_model join_model.add_left_association :left_side, anonymous_class: lhs_model join_model.add_right_association association_name, belongs_to_options(options) -- cgit v1.2.3 From 468cdfcc8516bf9f0d990bcf11b785875c57b286 Mon Sep 17 00:00:00 2001 From: Miles Starkenburg <miles@substantial.com> Date: Sat, 8 Aug 2015 13:15:28 -0700 Subject: Reference actual classes --- activerecord/lib/active_record/railtie.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index da6b8447d3..459d6256fa 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -16,11 +16,11 @@ module ActiveRecord config.app_generators.orm :active_record, :migration => true, :timestamps => true - config.app_middleware.insert_after "::ActionDispatch::Callbacks", - "ActiveRecord::QueryCache" + config.app_middleware.insert_after ::ActionDispatch::Callbacks, + ActiveRecord::QueryCache - config.app_middleware.insert_after "::ActionDispatch::Callbacks", - "ActiveRecord::ConnectionAdapters::ConnectionManagement" + config.app_middleware.insert_after ::ActionDispatch::Callbacks, + ActiveRecord::ConnectionAdapters::ConnectionManagement config.action_dispatch.rescue_responses.merge!( 'ActiveRecord::RecordNotFound' => :not_found, @@ -78,7 +78,7 @@ module ActiveRecord initializer "active_record.migration_error" do if config.active_record.delete(:migration_error) == :page_load - config.app_middleware.insert_after "::ActionDispatch::Callbacks", + config.app_middleware.insert_after ::ActionDispatch::Callbacks, "ActiveRecord::Migration::CheckPending" end end -- cgit v1.2.3 From ddbd6e7e44e5183ae64ca8749dcf720e843d2355 Mon Sep 17 00:00:00 2001 From: akihiro17 <coolwizard11@gmail.com> Date: Wed, 12 Aug 2015 11:33:02 +0900 Subject: [ci skip] Fix the indentation --- .../lib/active_record/attribute_methods.rb | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 7fb899c242..0862306749 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -385,27 +385,27 @@ module ActiveRecord # # For example: # - # class PostsController < ActionController::Base - # after_action :print_accessed_fields, only: :index + # class PostsController < ActionController::Base + # after_action :print_accessed_fields, only: :index # - # def index - # @posts = Post.all - # end + # def index + # @posts = Post.all + # end # - # private + # private # - # def print_accessed_fields - # p @posts.first.accessed_fields + # def print_accessed_fields + # p @posts.first.accessed_fields + # end # end - # end # # Which allows you to quickly change your code to: # - # class PostsController < ActionController::Base - # def index - # @posts = Post.select(:id, :title, :author_id, :updated_at) + # class PostsController < ActionController::Base + # def index + # @posts = Post.select(:id, :title, :author_id, :updated_at) + # end # end - # end def accessed_fields @attributes.accessed end -- cgit v1.2.3 From 60f7e0ca11d7fd33756ad36881eb947e0eb4d149 Mon Sep 17 00:00:00 2001 From: Yves Senn <yves.senn@gmail.com> Date: Wed, 12 Aug 2015 16:17:04 +0200 Subject: docs, tiny rdoc markup fix. [ci skip] `+` doesn't work around content with spaces fallback `<tt>`. --- activerecord/lib/active_record/callbacks.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index c7c769b283..ccdbebbc77 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -206,7 +206,8 @@ module ActiveRecord # == Ordering callbacks # # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+ - # callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option. + # callback (+log_children+ in this case) should be executed before the children get destroyed by the + # <tt>dependent: destroy</tt> option. # # Let's look at the code below: # -- cgit v1.2.3 From 93dc0b43d1fae1aa34032f9cab8f0822ea33b54d Mon Sep 17 00:00:00 2001 From: akihiro17 <coolwizard11@gmail.com> Date: Wed, 12 Aug 2015 23:53:10 +0900 Subject: [ci skip] Fix rdoc markup --- activerecord/lib/active_record/attribute_methods/serialization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index e03bf5945d..60eecab0d0 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -11,7 +11,7 @@ module ActiveRecord # serialized object must be of that class on assignment and retrieval. # Otherwise <tt>SerializationTypeMismatch</tt> will be raised. # - # Empty objects as +{}+, in the case of +Hash+, or +[]+, in the case of + # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of # +Array+, will always be persisted as null. # # Keep in mind that database adapters handle certain serialization tasks -- cgit v1.2.3 From dc3230b156a4cfe5a8fbe3636edf0117f8e122cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= <rafaelmfranca@gmail.com> Date: Wed, 12 Aug 2015 21:14:42 -0300 Subject: Skip statement cache on through association reader If the through class has default scopes we should skip the statement cache. Closes #20745. --- activerecord/lib/active_record/associations/association.rb | 8 ++++++++ .../lib/active_record/associations/collection_association.rb | 7 +------ .../lib/active_record/associations/singular_association.rb | 7 +------ 3 files changed, 10 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 7c729676a7..c7b396f3d4 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -251,6 +251,14 @@ module ActiveRecord initialize_attributes(record) end end + + # Returns true if statement cache should be skipped on the association reader. + def skip_statement_cache? + reflection.scope_chain.any?(&:any?) || + scope.eager_loading? || + klass.scope_attributes? || + reflection.source_reflection.active_record.default_scopes.any? + end end end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 0fc2b83b71..256df3ca11 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -442,12 +442,7 @@ module ActiveRecord private def get_records - if reflection.scope_chain.any?(&:any?) || - scope.eager_loading? || - klass.scope_attributes? - - return scope.to_a - end + return scope.to_a if skip_statement_cache? conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 30c5d72482..03cb8cb8c3 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -47,12 +47,7 @@ module ActiveRecord end def get_records - if reflection.scope_chain.any?(&:any?) || - scope.eager_loading? || - klass.scope_attributes? - - return scope.limit(1).to_a - end + return scope.limit(1).to_a if skip_statement_cache? conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do -- cgit v1.2.3 From e50fe85180648be0c4216bd0111f05be1df0988a Mon Sep 17 00:00:00 2001 From: Yves Senn <yves.senn@gmail.com> Date: Thu, 13 Aug 2015 16:35:09 +0200 Subject: descriptive error message when fixtures contian a missing column. Closes #21201. --- .../connection_adapters/abstract/database_statements.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 38dd9578fe..1e13b24867 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -289,8 +289,12 @@ module ActiveRecord columns = schema_cache.columns_hash(table_name) binds = fixture.map do |name, value| - type = lookup_cast_type_from_column(columns[name]) - Relation::QueryAttribute.new(name, value, type) + if column = columns[name] + type = lookup_cast_type_from_column(column) + Relation::QueryAttribute.new(name, value, type) + else + raise Fixture::FixtureError, %(table "#{table_name}" has no column named "#{name}".) + end end key_list = fixture.keys.map { |name| quote_column_name(name) } value_list = prepare_binds_for_database(binds).map do |value| -- cgit v1.2.3 From cdbed54fa999aacf388899b761de84d91fdb5a7e Mon Sep 17 00:00:00 2001 From: Jon Atack <jonnyatack@gmail.com> Date: Fri, 14 Aug 2015 14:58:56 +0200 Subject: Fix middleware deprecation message. Related to #21172. --- activerecord/lib/active_record/railtie.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 459d6256fa..6dd54f9262 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -79,7 +79,7 @@ module ActiveRecord initializer "active_record.migration_error" do if config.active_record.delete(:migration_error) == :page_load config.app_middleware.insert_after ::ActionDispatch::Callbacks, - "ActiveRecord::Migration::CheckPending" + ActiveRecord::Migration::CheckPending end end -- cgit v1.2.3 From 8bd064ec2b9f51461aa5407b8b171d0ba22d48ff Mon Sep 17 00:00:00 2001 From: ravindra kumar kumawat <ravikumawat87@gmail.com> Date: Tue, 18 Aug 2015 11:13:15 +0530 Subject: Add Docs for ActiveRecord #check_pending [ci skip] --- activerecord/lib/active_record/migration.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 192a456846..5af788bc10 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -373,6 +373,7 @@ module ActiveRecord attr_accessor :delegate # :nodoc: attr_accessor :disable_ddl_transaction # :nodoc: + # Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending. def check_pending!(connection = Base.connection) raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection) end -- cgit v1.2.3 From 820726704d2b2b4ba040b7ab54f9f626b622247e Mon Sep 17 00:00:00 2001 From: prakash <prakash@punchh.com> Date: Tue, 18 Aug 2015 11:39:52 +0530 Subject: Correct error message in Standard American english and add a test case for the same. --- activerecord/lib/active_record/associations/preloader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 6ecc741195..3992a240b9 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -116,7 +116,7 @@ module ActiveRecord when String preloaders_for_one(association.to_sym, records, scope) else - raise ArgumentError, "#{association.inspect} was not recognised for preload" + raise ArgumentError, "#{association.inspect} was not recognized for preload" end end -- cgit v1.2.3 From 89d5d1cafb23280bda3f303442387e71353c9e49 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono <kamipo@gmail.com> Date: Tue, 4 Aug 2015 04:16:54 +0900 Subject: Add a native JSON data type support in MySQL As of MySQL 5.7.8, MySQL supports a native JSON data type. Example: create_table :json_data_type do |t| t.json :settings end --- .../connection_adapters/abstract_adapter.rb | 5 +++ .../connection_adapters/abstract_mysql_adapter.rb | 39 ++++++++++++++++------ .../connection_adapters/mysql2_adapter.rb | 4 +++ .../connection_adapters/postgresql/oid.rb | 1 - .../connection_adapters/postgresql/oid/json.rb | 35 ------------------- .../connection_adapters/postgresql/oid/jsonb.rb | 2 +- .../connection_adapters/postgresql_adapter.rb | 7 ++-- activerecord/lib/active_record/type.rb | 2 ++ activerecord/lib/active_record/type/json.rb | 31 +++++++++++++++++ 9 files changed, 76 insertions(+), 50 deletions(-) delete mode 100644 activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb create mode 100644 activerecord/lib/active_record/type/json.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 56227ddd80..ed14c781c6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -266,6 +266,11 @@ module ActiveRecord false end + # Does this adapter support json data type? + def supports_json? + false + end + # This is meant to be implemented by the adapters that support extensions def disable_extension(name) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index af156c9c78..96ea866580 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -10,6 +10,10 @@ module ActiveRecord options[:auto_increment] = true if type == :bigint super end + + def json(*args, **options) + args.each { |name| column(name, :json, options) } + end end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition @@ -242,17 +246,19 @@ module ActiveRecord QUOTED_TRUE, QUOTED_FALSE = '1', '0' NATIVE_DATABASE_TYPES = { - :primary_key => "int(11) auto_increment PRIMARY KEY", - :string => { :name => "varchar", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "int", :limit => 4 }, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "datetime" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "blob" }, - :boolean => { :name => "tinyint", :limit => 1 } + primary_key: "int(11) auto_increment PRIMARY KEY", + string: { name: "varchar", limit: 255 }, + text: { name: "text" }, + integer: { name: "int", limit: 4 }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: { name: "datetime" }, + time: { name: "time" }, + date: { name: "date" }, + binary: { name: "blob" }, + boolean: { name: "tinyint", limit: 1 }, + bigint: { name: "bigint" }, + json: { name: "json" }, } INDEX_TYPES = [:fulltext, :spatial] @@ -790,6 +796,7 @@ module ActiveRecord m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) m.register_type %r(^float)i, Type::Float.new(limit: 24) m.register_type %r(^double)i, Type::Float.new(limit: 53) + m.register_type %r(^json)i, MysqlJson.new register_integer_type m, %r(^bigint)i, limit: 8 register_integer_type m, %r(^int)i, limit: 4 @@ -1043,6 +1050,14 @@ module ActiveRecord end end + class MysqlJson < Type::Json # :nodoc: + def changed_in_place?(raw_old_value, new_value) + # Normalization is required because MySQL JSON data format includes + # the space between the elements. + super(serialize(deserialize(raw_old_value)), new_value) + end + end + class MysqlString < Type::String # :nodoc: def serialize(value) case value @@ -1063,6 +1078,8 @@ module ActiveRecord end end + ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql) + ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index b7db57c9fe..ff43c7ec42 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -41,6 +41,10 @@ module ActiveRecord true end + def supports_json? + version >= '5.7.8' + end + # HELPER METHODS =========================================== def each_hash(result) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 68752cdd80..37226831dc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -8,7 +8,6 @@ require 'active_record/connection_adapters/postgresql/oid/decimal' require 'active_record/connection_adapters/postgresql/oid/enum' require 'active_record/connection_adapters/postgresql/oid/hstore' require 'active_record/connection_adapters/postgresql/oid/inet' -require 'active_record/connection_adapters/postgresql/oid/json' require 'active_record/connection_adapters/postgresql/oid/jsonb' require 'active_record/connection_adapters/postgresql/oid/money' require 'active_record/connection_adapters/postgresql/oid/point' diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb deleted file mode 100644 index 8e1256baad..0000000000 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ /dev/null @@ -1,35 +0,0 @@ -module ActiveRecord - module ConnectionAdapters - module PostgreSQL - module OID # :nodoc: - class Json < Type::Value # :nodoc: - include Type::Helpers::Mutable - - def type - :json - end - - def deserialize(value) - if value.is_a?(::String) - ::ActiveSupport::JSON.decode(value) rescue nil - else - value - end - end - - def serialize(value) - if value.is_a?(::Array) || value.is_a?(::Hash) - ::ActiveSupport::JSON.encode(value) - else - value - end - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - end - end - end -end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb index 87391b5dc7..1f6d63582c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -2,7 +2,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Jsonb < Json # :nodoc: + class Jsonb < Type::Json # :nodoc: def type :jsonb end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2c43c46a3d..861edbf3a2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -201,6 +201,10 @@ module ActiveRecord true end + def supports_json? + postgresql_version >= 90200 + end + def index_algorithms { concurrently: 'CONCURRENTLY' } end @@ -478,7 +482,7 @@ module ActiveRecord m.register_type 'bytea', OID::Bytea.new m.register_type 'point', OID::Point.new m.register_type 'hstore', OID::Hstore.new - m.register_type 'json', OID::Json.new + m.register_type 'json', Type::Json.new m.register_type 'jsonb', OID::Jsonb.new m.register_type 'cidr', OID::Cidr.new m.register_type 'inet', OID::Inet.new @@ -834,7 +838,6 @@ module ActiveRecord ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql) ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql) ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql) - ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql) ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql) ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql) ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql) diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 2c0cda69d0..8f56a37f3c 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -10,6 +10,7 @@ require 'active_record/type/decimal' require 'active_record/type/decimal_without_scale' require 'active_record/type/float' require 'active_record/type/integer' +require 'active_record/type/json' require 'active_record/type/serialized' require 'active_record/type/string' require 'active_record/type/text' @@ -59,6 +60,7 @@ module ActiveRecord register(:decimal, Type::Decimal, override: false) register(:float, Type::Float, override: false) register(:integer, Type::Integer, override: false) + register(:json, Type::Json, override: false) register(:string, Type::String, override: false) register(:text, Type::Text, override: false) register(:time, Type::Time, override: false) diff --git a/activerecord/lib/active_record/type/json.rb b/activerecord/lib/active_record/type/json.rb new file mode 100644 index 0000000000..1728bd3a8e --- /dev/null +++ b/activerecord/lib/active_record/type/json.rb @@ -0,0 +1,31 @@ +module ActiveRecord + module Type + class Json < Type::Value # :nodoc: + include Type::Helpers::Mutable + + def type + :json + end + + def deserialize(value) + if value.is_a?(::String) + ::ActiveSupport::JSON.decode(value) rescue nil + else + value + end + end + + def serialize(value) + if value.is_a?(::Array) || value.is_a?(::Hash) + ::ActiveSupport::JSON.encode(value) + else + value + end + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + end + end +end -- cgit v1.2.3 From c351a823adf7b92e8439bf68c043019b95733d18 Mon Sep 17 00:00:00 2001 From: sjain1107 <sakshijain1107@gmail.com> Date: Tue, 18 Aug 2015 10:37:44 +0530 Subject: Added docs for TableDefinition #coloumns & #remove_column [ci skip] --- .../active_record/connection_adapters/abstract/schema_definitions.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index d17e272ed1..1658317408 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -214,6 +214,7 @@ module ActiveRecord @name = name end + # Returns an array of ColumnDefinitions for the columns of the table. def columns; @columns_hash.values; end # Returns a ColumnDefinition for the column with name +name+. @@ -369,6 +370,8 @@ module ActiveRecord self end + # remove the column +name+ from the table. + # remove_column(:account_id) def remove_column(name) @columns_hash.delete name.to_s end -- cgit v1.2.3 From 50e4afff209ee6dcb50938b59bff32255a3f543e Mon Sep 17 00:00:00 2001 From: Yves Senn <yves.senn@gmail.com> Date: Thu, 20 Aug 2015 12:07:02 +0200 Subject: uniqueness validation raises error for persisted record without pk. Closes #21304. While we can validate uniqueness for record without primary key on creation, there is no way to exclude the current record when updating. (The update itself will need a primary key to work correctly). --- activerecord/lib/active_record/errors.rb | 7 ++++--- activerecord/lib/active_record/validations/uniqueness.rb | 6 +++++- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index d589620f8a..718f04871d 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -218,11 +218,12 @@ module ActiveRecord class UnknownPrimaryKey < ActiveRecordError attr_reader :model - def initialize(model) - super("Unknown primary key for table #{model.table_name} in model #{model}.") + def initialize(model, description = nil) + message = "Unknown primary key for table #{model.table_name} in model #{model}." + message += "\n#{description}" if description + super(message) @model = model end - end # Raised when a relation cannot be mutated because it's already loaded. diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 32d17a1392..5706bbd903 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -18,7 +18,11 @@ module ActiveRecord relation = build_relation(finder_class, table, attribute, value) if record.persisted? && finder_class.primary_key.to_s != attribute.to_s - relation = relation.where.not(finder_class.primary_key => record.id) + if finder_class.primary_key + relation = relation.where.not(finder_class.primary_key => record.id) + else + raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") + end end relation = scope_relation(record, table, relation) relation = relation.merge(options[:conditions]) if options[:conditions] -- cgit v1.2.3 From 23dbe10f221fec406d95b70c9c0bfc06cd5fe2f4 Mon Sep 17 00:00:00 2001 From: Robert Eshleman <c.robert.eshleman@gmail.com> Date: Thu, 20 Aug 2015 15:57:05 -0400 Subject: Fix Punctuation in `AutosaveAssociation` RDoc [ci skip] --- activerecord/lib/active_record/autosave_association.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index dbb0e2fab2..d0de42d27c 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -234,7 +234,7 @@ module ActiveRecord super end - # Marks this record to be destroyed as part of the parents save transaction. + # Marks this record to be destroyed as part of the parent's save transaction. # This does _not_ actually destroy the record instantly, rather child record will be destroyed # when <tt>parent.save</tt> is called. # @@ -243,7 +243,7 @@ module ActiveRecord @marked_for_destruction = true end - # Returns whether or not this record will be destroyed as part of the parents save transaction. + # Returns whether or not this record will be destroyed as part of the parent's save transaction. # # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model. def marked_for_destruction? -- cgit v1.2.3 From 3088460df04a8060a099398904bcd0a3098a6bef Mon Sep 17 00:00:00 2001 From: Yves Senn <yves.senn@gmail.com> Date: Fri, 21 Aug 2015 11:06:41 +0200 Subject: better docs for `disable_ddl_transaction!`. Closes #21044. --- activerecord/lib/active_record/migration.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b5b91451c7..c35efbdee8 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -409,7 +409,10 @@ module ActiveRecord new.migrate direction end - # Disable DDL transactions for this migration. + # Disable the transaction wrapping this migration. + # You can still create your own transactions even after calling #disable_ddl_transaction! + # + # For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration]. def disable_ddl_transaction! @disable_ddl_transaction = true end -- cgit v1.2.3 From ffc4710c2bff273b82ddb76675701f986d82ef4f Mon Sep 17 00:00:00 2001 From: Sean Griffin <sean@thoughtbot.com> Date: Fri, 21 Aug 2015 17:45:46 -0600 Subject: JSON is still an adapter specific type. Several changes were made in #21110 which I am strongly opposed to. (this is what I get for going on vacation. :trollface:) No type should be introduced into the generic `ActiveRecord::Type` namespace, and *certainly* should not be registered into the registry unconstrained unless it is supported by *all* adapters (which basically means that it was specified in the ANSI SQL standard). I do not think `# :nodoc:` ing the type is sufficient, as it still makes the code of Rails itself very unclear as to what the role of that class is. While I would argue that this shouldn't even be a super class, and that MySql and PG's JSON types are only superficially duplicated (they might look the same but will change for different reasons in the future). However, I don't feel strongly enough about it as a point of contention (and the biggest cost of harming the blameability has already occured), so I simply moved the superclass into a namespace where its role is absolutely clear. After this change, `attribute :foo, :json` will once again work with MySQL and PG, but not with Sqlite3 or any third party adapters. Unresolved questions -------------------- The types that and adapter publishes (at least those are unique to that adapter, and not adding additional behavior like `MysqlString` should probably be part of the adapter's public API. Should we standardize the namespace for these, and document them? --- .../connection_adapters/abstract_mysql_adapter.rb | 2 +- .../connection_adapters/postgresql/oid.rb | 1 + .../connection_adapters/postgresql/oid/json.rb | 10 +++++++ .../connection_adapters/postgresql/oid/jsonb.rb | 2 +- .../connection_adapters/postgresql_adapter.rb | 3 +- activerecord/lib/active_record/type.rb | 4 +-- .../active_record/type/internal/abstract_json.rb | 33 ++++++++++++++++++++++ activerecord/lib/active_record/type/json.rb | 31 -------------------- 8 files changed, 50 insertions(+), 36 deletions(-) create mode 100644 activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb create mode 100644 activerecord/lib/active_record/type/internal/abstract_json.rb delete mode 100644 activerecord/lib/active_record/type/json.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 96ea866580..b3e55a0b90 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1050,7 +1050,7 @@ module ActiveRecord end end - class MysqlJson < Type::Json # :nodoc: + class MysqlJson < Type::Internal::AbstractJson # :nodoc: def changed_in_place?(raw_old_value, new_value) # Normalization is required because MySQL JSON data format includes # the space between the elements. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 37226831dc..68752cdd80 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -8,6 +8,7 @@ require 'active_record/connection_adapters/postgresql/oid/decimal' require 'active_record/connection_adapters/postgresql/oid/enum' require 'active_record/connection_adapters/postgresql/oid/hstore' require 'active_record/connection_adapters/postgresql/oid/inet' +require 'active_record/connection_adapters/postgresql/oid/json' require 'active_record/connection_adapters/postgresql/oid/jsonb' require 'active_record/connection_adapters/postgresql/oid/money' require 'active_record/connection_adapters/postgresql/oid/point' diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb new file mode 100644 index 0000000000..dbc879ffd4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -0,0 +1,10 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Json < Type::Internal::AbstractJson + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb index 1f6d63582c..87391b5dc7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -2,7 +2,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Jsonb < Type::Json # :nodoc: + class Jsonb < Json # :nodoc: def type :jsonb end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 861edbf3a2..27291bd2ea 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -482,7 +482,7 @@ module ActiveRecord m.register_type 'bytea', OID::Bytea.new m.register_type 'point', OID::Point.new m.register_type 'hstore', OID::Hstore.new - m.register_type 'json', Type::Json.new + m.register_type 'json', OID::Json.new m.register_type 'jsonb', OID::Jsonb.new m.register_type 'cidr', OID::Cidr.new m.register_type 'inet', OID::Inet.new @@ -838,6 +838,7 @@ module ActiveRecord ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql) ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql) ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql) + ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql) ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql) ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql) ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql) diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 8f56a37f3c..53f3b53bec 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -10,7 +10,6 @@ require 'active_record/type/decimal' require 'active_record/type/decimal_without_scale' require 'active_record/type/float' require 'active_record/type/integer' -require 'active_record/type/json' require 'active_record/type/serialized' require 'active_record/type/string' require 'active_record/type/text' @@ -21,6 +20,8 @@ require 'active_record/type/adapter_specific_registry' require 'active_record/type/type_map' require 'active_record/type/hash_lookup_type_map' +require 'active_record/type/internal/abstract_json' + module ActiveRecord module Type @registry = AdapterSpecificRegistry.new @@ -60,7 +61,6 @@ module ActiveRecord register(:decimal, Type::Decimal, override: false) register(:float, Type::Float, override: false) register(:integer, Type::Integer, override: false) - register(:json, Type::Json, override: false) register(:string, Type::String, override: false) register(:text, Type::Text, override: false) register(:time, Type::Time, override: false) diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb new file mode 100644 index 0000000000..963a8245d0 --- /dev/null +++ b/activerecord/lib/active_record/type/internal/abstract_json.rb @@ -0,0 +1,33 @@ +module ActiveRecord + module Type + module Internal # :nodoc: + class AbstractJson < Type::Value # :nodoc: + include Type::Helpers::Mutable + + def type + :json + end + + def deserialize(value) + if value.is_a?(::String) + ::ActiveSupport::JSON.decode(value) rescue nil + else + value + end + end + + def serialize(value) + if value.is_a?(::Array) || value.is_a?(::Hash) + ::ActiveSupport::JSON.encode(value) + else + value + end + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + end + end + end +end diff --git a/activerecord/lib/active_record/type/json.rb b/activerecord/lib/active_record/type/json.rb deleted file mode 100644 index 1728bd3a8e..0000000000 --- a/activerecord/lib/active_record/type/json.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ActiveRecord - module Type - class Json < Type::Value # :nodoc: - include Type::Helpers::Mutable - - def type - :json - end - - def deserialize(value) - if value.is_a?(::String) - ::ActiveSupport::JSON.decode(value) rescue nil - else - value - end - end - - def serialize(value) - if value.is_a?(::Array) || value.is_a?(::Hash) - ::ActiveSupport::JSON.encode(value) - else - value - end - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - end -end -- cgit v1.2.3 From f9f156b22c77eb01c009eaac421afcb2a2d6673f Mon Sep 17 00:00:00 2001 From: Ronak Jangir <ronakjangir47@gmail.com> Date: Sat, 22 Aug 2015 22:56:04 +0530 Subject: Added docs for CollectionProxy#take [ci skip] --- .../active_record/associations/collection_proxy.rb | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index b5a8c81fe4..29c711082a 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -227,6 +227,31 @@ module ActiveRecord @association.last(*args) end + # Gives a record (or N records if a parameter is supplied) from the collection + # using the same rules as <tt>ActiveRecord::Base.take</tt>. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1>, + # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> + # # ] + # + # person.pets.take # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1> + # + # person.pets.take(2) + # # => [ + # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, + # # #<Pet id: 2, name: "Spook", person_id: 1> + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.take # => nil + # another_person_without.pets.take(2) # => [] def take(n = nil) @association.take(n) end -- cgit v1.2.3 From 3048f2f89f62b28591c4c9e40e0a23e88ff13e7f Mon Sep 17 00:00:00 2001 From: yui-knk <spiketeika@gmail.com> Date: Sun, 23 Aug 2015 11:15:05 +0900 Subject: Remove not used a block argument (`&block`) --- activerecord/lib/active_record/migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c35efbdee8..04c6124eef 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -814,7 +814,7 @@ module ActiveRecord new(:up, migrations, target_version).migrate end - def down(migrations_paths, target_version = nil, &block) + def down(migrations_paths, target_version = nil) migrations = migrations(migrations_paths) migrations.select! { |m| yield m } if block_given? -- cgit v1.2.3