From 3fe83d1dd99ac2662852da0dcfe7e91dc08ae160 Mon Sep 17 00:00:00 2001 From: Abhay Nikam Date: Fri, 5 Apr 2019 10:14:30 +0530 Subject: Adds touch option to has_one association --- activerecord/CHANGELOG.md | 4 +++ .../active_record/associations/builder/has_one.rb | 36 ++++++++++++++++++-- .../associations/has_one_associations_test.rb | 39 +++++++++++++++++++++- activerecord/test/models/club.rb | 4 ++- activerecord/test/schema/schema.rb | 2 ++ 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index adc7e754a4..a8893d7164 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add `touch` option to `has_one` association. + + *Abhay Nikam* + * Deprecate `where.not` working as NOR and will be changed to NAND in Rails 6.1. ```ruby diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index a17cfcb805..27ebe8cb71 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc: end def self.valid_options(options) - valid = super + [:as] + valid = super + [:as, :touch] valid += [:through, :source, :source_type] if options[:through] valid end @@ -16,6 +16,11 @@ module ActiveRecord::Associations::Builder # :nodoc: [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] end + def self.define_callbacks(model, reflection) + super + add_touch_callbacks(model, reflection) if reflection.options[:touch] + end + def self.add_destroy_callbacks(model, reflection) super unless reflection.options[:through] end @@ -27,6 +32,33 @@ module ActiveRecord::Associations::Builder # :nodoc: end end - private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks, :define_validations + def self.touch_record(o, name, touch) + record = o.send name + + return unless record && record.persisted? + + if touch != true + record.touch(touch) + else + record.touch + end + end + + def self.add_touch_callbacks(model, reflection) + name = reflection.name + touch = reflection.options[:touch] + + callback = lambda { |record| + HasOne.touch_record(record, name, touch) + } + + model.after_create callback, if: :saved_changes? + model.after_update callback, if: :saved_changes? + model.after_destroy callback + model.after_touch callback + end + + private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks, + :define_callbacks, :define_validations, :add_touch_callbacks end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 7bb629466d..fd727757a3 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -15,10 +15,13 @@ require "models/post" require "models/drink_designer" require "models/chef" require "models/department" +require "models/club" +require "models/membership" class HasOneAssociationsTest < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? - fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates, :authors, :author_addresses + fixtures :accounts, :companies, :developers, :projects, :developers_projects, + :ships, :pirates, :authors, :author_addresses, :memberships, :clubs def setup Account.destroyed_account_ids.clear @@ -706,6 +709,40 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end end + def test_has_one_with_touch_option_on_create + assert_queries(3) { + Club.create(name: "1000 Oaks", membership_attributes: { favourite: true }) + } + end + + def test_has_one_with_touch_option_on_update + new_club = Club.create(name: "1000 Oaks") + new_club.create_membership + + assert_queries(2) { new_club.update(name: "Effingut") } + end + + def test_has_one_with_touch_option_on_touch + new_club = Club.create(name: "1000 Oaks") + new_club.create_membership + + assert_queries(1) { new_club.touch } + end + + def test_has_one_with_touch_option_on_destroy + new_club = Club.create(name: "1000 Oaks") + new_club.create_membership + + assert_queries(2) { new_club.destroy } + end + + def test_has_one_with_touch_option_on_empty_update + new_club = Club.create(name: "1000 Oaks") + new_club.create_membership + + assert_no_queries { new_club.save } + end + class SpecialBook < ActiveRecord::Base self.table_name = "books" belongs_to :author, class_name: "SpecialAuthor" diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 13e72e9c50..bb49fb300c 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Club < ActiveRecord::Base - has_one :membership + has_one :membership, touch: true has_many :memberships, inverse_of: false has_many :members, through: :memberships has_one :sponsor @@ -12,6 +12,8 @@ class Club < ActiveRecord::Base scope :general, -> { left_joins(:category).where(categories: { name: "General" }).unscope(:limit) } + accepts_nested_attributes_for :membership + private def private_method diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 7d9b8afeb6..41920b3719 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -524,6 +524,8 @@ ActiveRecord::Schema.define do t.integer :club_id, :member_id t.boolean :favourite, default: false t.integer :type + t.datetime :created_at + t.datetime :updated_at end create_table :member_types, force: true do |t| -- cgit v1.2.3