From 2762aa0cb50dd508425b7095fb8df5ca4a8a1816 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 16 Mar 2017 14:13:28 -0400 Subject: [PATCH] Add :default option to belongs_to Use it to specify that an association should be initialized with a particular record before validation. For example: # Before belongs_to :account before_validation -> { self.account ||= Current.account } # After belongs_to :account, default: -> { Current.account } --- activerecord/CHANGELOG.md | 14 +++++++++++++ .../lib/active_record/associations.rb | 4 ++++ .../associations/belongs_to_association.rb | 4 ++++ .../associations/builder/belongs_to.rb | 9 ++++++++- .../belongs_to_associations_test.rb | 20 +++++++++++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 96094a285f7d1..52cfea79ff894 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,17 @@ +* Add `:default` option to `belongs_to`. + + Use it to specify that an association should be initialized with a particular + record before validation. For example: + + # Before + belongs_to :account + before_validation -> { self.account ||= Current.account } + + # After + belongs_to :account, default: -> { Current.account } + + *George Claghorn* + * Deprecate `Migrator.schema_migrations_table_name`. *Ryuta Kamizono* diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 4606c91ffd344..6efa448d4904c 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1647,6 +1647,9 @@ def has_one(name, scope = nil, options = {}) # +:inverse_of+ to avoid an extra query during validation. # NOTE: required is set to true by default and is deprecated. If # you don't want to have association presence validated, use optional: true. + # [:default] + # Provide a callable (i.e. proc or lambda) to specify that the association should + # be initialized with a particular record before validation. # # Option examples: # belongs_to :firm, foreign_key: "client_of" @@ -1660,6 +1663,7 @@ def has_one(name, scope = nil, options = {}) # belongs_to :comment, touch: true # belongs_to :company, touch: :employees_last_updated_at # belongs_to :user, optional: true + # belongs_to :account, default: -> { company.account } def belongs_to(name, scope = nil, options = {}) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection self, name, reflection diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 64b23119115bd..4a4f88bb94ba2 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -21,6 +21,10 @@ def replace(record) self.target = record end + def default(record) + writer(record) if reader.nil? + end + def reset super @updated = false diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index a1609ab0fbd74..50a1c39ccf645 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -5,7 +5,7 @@ def self.macro end def self.valid_options(options) - super + [:polymorphic, :touch, :counter_cache, :optional] + super + [:polymorphic, :touch, :counter_cache, :optional, :default] end def self.valid_dependent_options @@ -16,6 +16,7 @@ def self.define_callbacks(model, reflection) super add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache] add_touch_callbacks(model, reflection) if reflection.options[:touch] + add_default_callbacks(model, reflection) if reflection.options[:default] end def self.define_accessors(mixin, reflection) @@ -118,6 +119,12 @@ def self.add_touch_callbacks(model, reflection) model.after_destroy callback.(:changes_to_save) end + def self.add_default_callbacks(model, reflection) + model.before_validation lambda { |o| + o.association(reflection.name).default o.instance_exec(&reflection.options[:default]) + } + end + def self.add_destroy_callbacks(model, reflection) model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency } end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 5875a1871f3f6..5b08ba1358b44 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -116,6 +116,26 @@ def self.name; "Temp"; end ActiveRecord::Base.belongs_to_required_by_default = original_value end + def test_default + david = developers(:david) + jamis = developers(:jamis) + + model = Class.new(ActiveRecord::Base) do + self.table_name = "ships" + def self.name; "Temp"; end + belongs_to :developer, default: -> { david } + end + + ship = model.create! + assert_equal david, ship.developer + + ship = model.create!(developer: jamis) + assert_equal jamis, ship.developer + + ship.update!(developer: nil) + assert_equal david, ship.developer + end + def test_default_scope_on_relations_is_not_cached counter = 0