Skip to content

Commit

Permalink
Add :default option to belongs_to
Browse files Browse the repository at this point in the history
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 }
  • Loading branch information
georgeclaghorn committed Mar 17, 2017
1 parent b9c399d commit 2762aa0
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 1 deletion.
14 changes: 14 additions & 0 deletions 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*
Expand Down
4 changes: 4 additions & 0 deletions activerecord/lib/active_record/associations.rb
Expand Up @@ -1647,6 +1647,9 @@ def has_one(name, scope = nil, options = {})
# +:inverse_of+ to avoid an extra query during validation.
# NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
# you don't want to have association presence validated, use <tt>optional: true</tt>.
# [: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"
Expand All @@ -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
Expand Down
Expand Up @@ -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
Expand Down
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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

Expand Down

0 comments on commit 2762aa0

Please sign in to comment.