Skip to content

Commit

Permalink
Add support for belongs_to's optional option to handle data integ…
Browse files Browse the repository at this point in the history
…rity.

By adding this support, model can able to save foreign_key to either
`nil` value or to the valid foreign_key value which means
foreign_key value must present in the foreign table.

    class Post < ActiveRecord::Base
      belongs_to :author, optional: :with_integrity
    end

    author = Author.create!
    post = Post.create!(author_id: author.id)

    post.author_id = 0 # Invalid id which is not present in database
    post.save! # Should raise 'ActiveRecord::RecordInvalid' error

    post.author_id = nil
    post.save! # No Error

Based on this issue's discussion rails#26557. Added this.
  • Loading branch information
mechanicles committed Sep 21, 2016
1 parent 8150c12 commit e13f6cf
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 0 deletions.
14 changes: 14 additions & 0 deletions activerecord/lib/active_record/associations/builder/belongs_to.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ def self.define_validations(model, reflection)

if reflection.options[:optional].nil?
required = model.belongs_to_required_by_default
elsif reflection.options[:optional] == :with_integrity
callback = lambda do |record|
fk = reflection.foreign_key
fk_value = record.public_send(fk)
association_klass = record.association(reflection.name).klass
association_pk = association_klass.primary_key

if fk_value.present? && association_klass.find_by("#{association_pk}": fk_value).nil?
record.errors.add(fk, :invalid)
end
end

model.validate callback
required = false
else
required = !reflection.options[:optional]
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,32 @@ def self.name; "Temp"; end
ActiveRecord::Base.belongs_to_required_by_default = original_value
end

def test_optional_relation_with_integrity_option
original_value = ActiveRecord::Base.belongs_to_required_by_default
ActiveRecord::Base.belongs_to_required_by_default = true

model = Class.new(ActiveRecord::Base) do
self.table_name = "posts"
def self.name; "Temp"; end
belongs_to :author, optional: :with_integrity
end

author1 = Author.create!(name: "abc")
author2 = Author.create!(name: "xyz")

m = model.create!(title: "post title", body: "haha", author_id: author1.id)
m.author_id = 0

assert_raises(ActiveRecord::RecordInvalid) do
assert m.save!
end

m.author_id = author2.id
assert m.save
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
end

def test_required_belongs_to_config
original_value = ActiveRecord::Base.belongs_to_required_by_default
ActiveRecord::Base.belongs_to_required_by_default = true
Expand Down

0 comments on commit e13f6cf

Please sign in to comment.