diff options
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/CHANGELOG.md | 29 | ||||
-rw-r--r-- | activerecord/lib/active_record.rb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/base.rb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 7 | ||||
-rw-r--r-- | activerecord/lib/active_record/suppressor.rb | 55 | ||||
-rw-r--r-- | activerecord/test/cases/helper.rb | 2 | ||||
-rw-r--r-- | activerecord/test/cases/suppressor_test.rb | 21 | ||||
-rw-r--r-- | activerecord/test/models/notification.rb | 2 | ||||
-rw-r--r-- | activerecord/test/models/user.rb | 4 | ||||
-rw-r--r-- | activerecord/test/schema/schema.rb | 4 |
11 files changed, 122 insertions, 8 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a3b680f09d..57d135d1aa 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,32 @@ +* Add `ActiveRecord::Base.suppress` to prevent the receiver from being saved + during the given block. + + For example, here's a pattern of creating notifications when new comments + are posted. (The notification may in turn trigger an email, a push + notification, or just appear in the UI somewhere): + + class Comment < ActiveRecord::Base + belongs_to :commentable, polymorphic: true + after_create -> { Notification.create! comment: self, + recipients: commentable.recipients } + end + + That's what you want the bulk of the time. New comment creates a new + Notification. But there may well be off cases, like copying a commentable + and its comments, where you don't want that. So you'd have a concern + something like this: + + module Copyable + def copy_to(destination) + Notification.suppress do + # Copy logic that creates new comments that we do not want triggering + # notifications. + end + end + end + + *Michael Ryan* + * `:time` option added for `#touch` Fixes #18905. diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index d9d47c3d99..ef14e065ae 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -62,6 +62,7 @@ module ActiveRecord autoload :Serialization autoload :StatementCache autoload :Store + autoload :Suppressor autoload :TableMetadata autoload :Timestamp autoload :Transactions diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 100d3780f6..cc03e37a12 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -313,6 +313,7 @@ module ActiveRecord #:nodoc: include Serialization include Store include SecureToken + include Suppressor end ActiveSupport.run_load_hooks(:active_record, Base) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 16f50fc594..64985ee933 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -328,8 +328,8 @@ module ActiveRecord def initialize_type_map(m) # :nodoc: super - m.register_type %r(datetime)i, Fields::DateTime.new - m.register_type %r(time)i, Fields::Time.new + register_class_with_precision m, %r(datetime)i, Fields::DateTime + register_class_with_precision m, %r(time)i, Fields::Time end def exec_without_stmt(sql, name = 'SQL') # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5a887ea529..b4fe7506f8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -477,7 +477,6 @@ module ActiveRecord register_class_with_limit m, 'varbit', OID::BitVarying m.alias_type 'timestamptz', 'timestamp' m.register_type 'date', Type::Date.new - m.register_type 'time', Type::Time.new m.register_type 'money', OID::Money.new m.register_type 'bytea', OID::Bytea.new @@ -503,10 +502,8 @@ module ActiveRecord m.alias_type 'lseg', 'varchar' m.alias_type 'box', 'varchar' - m.register_type 'timestamp' do |_, _, sql_type| - precision = extract_precision(sql_type) - OID::DateTime.new(precision: precision) - end + register_class_with_precision m, 'time', Type::Time + register_class_with_precision m, 'timestamp', OID::DateTime m.register_type 'numeric' do |_, fmod, sql_type| precision = extract_precision(sql_type) diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb new file mode 100644 index 0000000000..b0b86865fd --- /dev/null +++ b/activerecord/lib/active_record/suppressor.rb @@ -0,0 +1,55 @@ +module ActiveRecord + # ActiveRecord::Suppressor prevents the receiver from being saved during + # a given block. + # + # For example, here's a pattern of creating notifications when new comments + # are posted. (The notification may in turn trigger an email, a push + # notification, or just appear in the UI somewhere): + # + # class Comment < ActiveRecord::Base + # belongs_to :commentable, polymorphic: true + # after_create -> { Notification.create! comment: self, + # recipients: commentable.recipients } + # end + # + # That's what you want the bulk of the time. New comment creates a new + # Notification. But there may well be off cases, like copying a commentable + # and its comments, where you don't want that. So you'd have a concern + # something like this: + # + # module Copyable + # def copy_to(destination) + # Notification.suppress do + # # Copy logic that creates new comments that we do not want + # # triggering notifications. + # end + # end + # end + module Suppressor + extend ActiveSupport::Concern + + module ClassMethods + def suppress(&block) + SuppressorRegistry.suppressed[name] = true + yield + ensure + SuppressorRegistry.suppressed[name] = false + end + end + + # Ignore saving events if we're in suppression mode. + def save!(*args) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + end + + class SuppressorRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_reader :suppressed + + def initialize + @suppressed = {} + end + end +end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f1f927852c..f2ba28a32f 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -46,7 +46,7 @@ def in_memory_db? end def mysql_56? - current_adapter?(:Mysql2Adapter) && + current_adapter?(:MysqlAdapter, :Mysql2Adapter) && ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0" end diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb new file mode 100644 index 0000000000..1c449d42fe --- /dev/null +++ b/activerecord/test/cases/suppressor_test.rb @@ -0,0 +1,21 @@ +require 'cases/helper' +require 'models/notification' +require 'models/user' + +class SuppressorTest < ActiveRecord::TestCase + def test_suppresses_creation_of_record_generated_by_callback + assert_difference -> { User.count } do + assert_no_difference -> { Notification.count } do + Notification.suppress { UserWithNotification.create! } + end + end + end + + def test_resumes_saving_after_suppression_complete + Notification.suppress { UserWithNotification.create! } + + assert_difference -> { Notification.count } do + Notification.create! + end + end +end diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb new file mode 100644 index 0000000000..b4b4b8f1b6 --- /dev/null +++ b/activerecord/test/models/notification.rb @@ -0,0 +1,2 @@ +class Notification < ActiveRecord::Base +end diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb index 23cd2e0e1c..f5dc93e994 100644 --- a/activerecord/test/models/user.rb +++ b/activerecord/test/models/user.rb @@ -2,3 +2,7 @@ class User < ActiveRecord::Base has_secure_token has_secure_token :auth_token end + +class UserWithNotification < User + after_create -> { Notification.create! message: "A new user has been created." } +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index a7d90e3f89..08a16d5c9e 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -468,6 +468,10 @@ ActiveRecord::Schema.define do t.string :name end + create_table :notifications, force: true do |t| + t.string :message + end + create_table :numeric_data, force: true do |t| t.decimal :bank_balance, precision: 10, scale: 2 t.decimal :big_bank_balance, precision: 15, scale: 2 |