aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
blob: 905ddef063fb36d4c445afd39abf9eff1e82b701 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# frozen_string_literal: true
require "cases/helper"
require "models/pirate"
require "models/bird"

class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
  Pirate.has_many(:birds_with_add_load,
                  class_name: "Bird",
                  before_add: proc { |p, b|
                    @@add_callback_called << b
                    p.birds_with_add_load.to_a
                  })
  Pirate.has_many(:birds_with_add,
                  class_name: "Bird",
                  before_add: proc { |p, b| @@add_callback_called << b })

  Pirate.accepts_nested_attributes_for(:birds_with_add_load,
                                       :birds_with_add,
                                       allow_destroy: true)

  def setup
    @@add_callback_called = []
    @pirate = Pirate.new.tap do |pirate|
      pirate.catchphrase = "Don't call me!"
      pirate.birds_attributes = [{ name: "Bird1" }, { name: "Bird2" }]
      pirate.save!
    end
    @birds = @pirate.birds.to_a
  end

  def bird_to_update
    @birds[0]
  end

  def bird_to_destroy
    @birds[1]
  end

  def existing_birds_attributes
    @birds.map do |bird|
      bird.attributes.slice("id", "name")
    end
  end

  def new_birds
    @pirate.birds_with_add.to_a - @birds
  end

  def new_bird_attributes
    [{ "name" => "New Bird" }]
  end

  def destroy_bird_attributes
    [{ "id" => bird_to_destroy.id.to_s, "_destroy" => true }]
  end

  def update_new_and_destroy_bird_attributes
    [{ "id" => @birds[0].id.to_s, "name" => "New Name" },
     { "name" => "New Bird" },
     { "id" => bird_to_destroy.id.to_s, "_destroy" => true }]
  end

  # Characterizing when :before_add callback is called
  test ":before_add called for new bird when not loaded" do
    assert_not @pirate.birds_with_add.loaded?
    @pirate.birds_with_add_attributes = new_bird_attributes
    assert_new_bird_with_callback_called
  end

  test ":before_add called for new bird when loaded" do
    @pirate.birds_with_add.load_target
    @pirate.birds_with_add_attributes = new_bird_attributes
    assert_new_bird_with_callback_called
  end

  def assert_new_bird_with_callback_called
    assert_equal(1, new_birds.size)
    assert_equal(new_birds, @@add_callback_called)
  end

  test ":before_add not called for identical assignment when not loaded" do
    assert_not @pirate.birds_with_add.loaded?
    @pirate.birds_with_add_attributes = existing_birds_attributes
    assert_callbacks_not_called
  end

  test ":before_add not called for identical assignment when loaded" do
    @pirate.birds_with_add.load_target
    @pirate.birds_with_add_attributes = existing_birds_attributes
    assert_callbacks_not_called
  end

  test ":before_add not called for destroy assignment when not loaded" do
    assert_not @pirate.birds_with_add.loaded?
    @pirate.birds_with_add_attributes = destroy_bird_attributes
    assert_callbacks_not_called
  end

  test ":before_add not called for deletion assignment when loaded" do
    @pirate.birds_with_add.load_target
    @pirate.birds_with_add_attributes = destroy_bird_attributes
    assert_callbacks_not_called
  end

  def assert_callbacks_not_called
    assert_empty new_birds
    assert_empty @@add_callback_called
  end

  # Ensuring that the records in the association target are updated,
  # whether the association is loaded before or not
  test "Assignment updates records in target when not loaded" do
    assert_not @pirate.birds_with_add.loaded?
    @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes
    assert_assignment_affects_records_in_target(:birds_with_add)
  end

  test "Assignment updates records in target when loaded" do
    @pirate.birds_with_add.load_target
    @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes
    assert_assignment_affects_records_in_target(:birds_with_add)
  end

  test("Assignment updates records in target when not loaded" \
       " and callback loads target") do
    assert_not @pirate.birds_with_add_load.loaded?
    @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
    assert_assignment_affects_records_in_target(:birds_with_add_load)
  end

  test("Assignment updates records in target when loaded" \
       " and callback loads target") do
    @pirate.birds_with_add_load.load_target
    @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
    assert_assignment_affects_records_in_target(:birds_with_add_load)
  end

  def assert_assignment_affects_records_in_target(association_name)
    association = @pirate.send(association_name)
    assert association.detect { |b| b == bird_to_update }.name_changed?,
      "Update record not updated"
    assert association.detect { |b| b == bird_to_destroy }.marked_for_destruction?,
      "Destroy record not marked for destruction"
  end
end