composite_primary_keys 12.0.8 → 13.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +888 -874
  3. data/README.rdoc +181 -180
  4. data/lib/composite_primary_keys/active_model/attribute_assignment.rb +19 -19
  5. data/lib/composite_primary_keys/associations/association_scope.rb +66 -68
  6. data/lib/composite_primary_keys/associations/join_dependency.rb +137 -103
  7. data/lib/composite_primary_keys/attribute_methods/primary_key.rb +0 -2
  8. data/lib/composite_primary_keys/attribute_methods/read.rb +30 -30
  9. data/lib/composite_primary_keys/attribute_methods/write.rb +35 -35
  10. data/lib/composite_primary_keys/attribute_methods.rb +21 -9
  11. data/lib/composite_primary_keys/base.rb +141 -141
  12. data/lib/composite_primary_keys/composite_predicates.rb +2 -1
  13. data/lib/composite_primary_keys/connection_adapters/abstract/database_statements.rb +37 -22
  14. data/lib/composite_primary_keys/connection_adapters/sqlserver/database_statements.rb +44 -44
  15. data/lib/composite_primary_keys/core.rb +48 -48
  16. data/lib/composite_primary_keys/nested_attributes.rb +1 -1
  17. data/lib/composite_primary_keys/persistence.rb +82 -81
  18. data/lib/composite_primary_keys/reflection.rb +91 -29
  19. data/lib/composite_primary_keys/relation/batches.rb +15 -7
  20. data/lib/composite_primary_keys/relation/calculations.rb +104 -81
  21. data/lib/composite_primary_keys/relation/finder_methods.rb +235 -235
  22. data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +39 -20
  23. data/lib/composite_primary_keys/relation/query_methods.rb +42 -42
  24. data/lib/composite_primary_keys/relation/where_clause.rb +18 -23
  25. data/lib/composite_primary_keys/relation.rb +197 -193
  26. data/lib/composite_primary_keys/table_metadata.rb +11 -0
  27. data/lib/composite_primary_keys/version.rb +8 -8
  28. data/lib/composite_primary_keys.rb +119 -119
  29. data/test/abstract_unit.rb +114 -114
  30. data/test/connections/databases.ci.yml +22 -22
  31. data/test/fixtures/db_definitions/db2-create-tables.sql +112 -112
  32. data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -16
  33. data/test/fixtures/db_definitions/mysql.sql +180 -180
  34. data/test/fixtures/db_definitions/oracle.drop.sql +41 -41
  35. data/test/fixtures/db_definitions/oracle.sql +199 -199
  36. data/test/fixtures/db_definitions/postgresql.sql +182 -182
  37. data/test/fixtures/db_definitions/sqlite.sql +169 -169
  38. data/test/fixtures/db_definitions/sqlserver.sql +176 -176
  39. data/test/fixtures/department.rb +16 -16
  40. data/test/fixtures/departments.yml +19 -15
  41. data/test/fixtures/employees.yml +33 -28
  42. data/test/fixtures/membership.rb +8 -6
  43. data/test/fixtures/restaurants_suburbs.yml +10 -10
  44. data/test/fixtures/streets.yml +16 -16
  45. data/test/fixtures/suburbs.yml +14 -14
  46. data/test/fixtures/user.rb +11 -11
  47. data/test/test_associations.rb +372 -358
  48. data/test/test_attributes.rb +75 -60
  49. data/test/test_calculations.rb +49 -42
  50. data/test/test_create.rb +218 -206
  51. data/test/test_delete.rb +188 -179
  52. data/test/test_exists.rb +39 -39
  53. data/test/test_find.rb +170 -164
  54. data/test/test_ids.rb +112 -112
  55. data/test/test_nested_attributes.rb +67 -67
  56. data/test/test_update.rb +102 -96
  57. metadata +6 -6
  58. data/lib/composite_primary_keys/connection_adapters/mysql/database_statements.rb +0 -24
@@ -1,141 +1,141 @@
1
- module ActiveRecord
2
- class CompositeKeyError < StandardError #:nodoc:
3
- end
4
-
5
- class Base
6
- INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
7
- NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
8
-
9
- class << self
10
- alias_method :primary_key_without_composite_key_support=, :primary_key=
11
- def primary_key=(keys)
12
- unless keys.kind_of?(Array)
13
- self.primary_key_without_composite_key_support = keys
14
- return
15
- end
16
-
17
- @primary_keys = keys.map { |k| k.to_s }.to_composite_keys
18
-
19
- class_eval <<-EOV
20
- extend CompositeClassMethods
21
- include CompositeInstanceMethods
22
- EOV
23
- end
24
- alias_method :primary_keys=, :primary_key=
25
-
26
- def set_primary_keys(*keys)
27
- ActiveSupport::Deprecation.warn(
28
- "Calling set_primary_keys is deprecated. Please use `self.primary_keys = keys` instead."
29
- )
30
-
31
- keys = keys.first if keys.first.is_a?(Array)
32
- if keys.length == 1
33
- self.primary_key = keys.first
34
- else
35
- self.primary_keys = keys
36
- end
37
- end
38
-
39
- def composite?
40
- false
41
- end
42
- end
43
-
44
- def composite?
45
- self.class.composite?
46
- end
47
-
48
- module CompositeClassMethods
49
- def primary_keys
50
- @primary_keys = reset_primary_keys unless defined? @primary_keys
51
- @primary_keys
52
- end
53
-
54
- # Don't like this method name, but its modeled after how AR does it
55
- def reset_primary_keys #:nodoc:
56
- if self == base_class
57
- # CPK
58
- self.primary_keys = get_primary_key(base_class.name)
59
- else
60
- self.primary_keys = base_class.primary_keys
61
- end
62
- end
63
-
64
- def primary_key
65
- primary_keys
66
- end
67
-
68
- def primary_key=(keys)
69
- self.primary_keys = keys
70
- end
71
-
72
- def composite?
73
- true
74
- end
75
-
76
- #ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
77
- #ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
78
- def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
79
- many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
80
- end
81
- end
82
-
83
- module CompositeInstanceMethods
84
- # A model instance's primary keys is always available as model.ids
85
- # whether you name it the default 'id' or set it to something else.
86
- def id
87
- attr_names = self.class.primary_keys
88
- ::CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
89
- end
90
- alias_method :ids, :id
91
-
92
- # This is overridden purely for json serialization support. If the model is composite
93
- # and one of the keys is id, then we don't want to call the id method, instead we want
94
- # to get the id attribute value
95
- def read_attribute_for_serialization(attribute)
96
- if self.composite? && attribute == 'id'
97
- read_attribute(attribute)
98
- else
99
- send(attribute)
100
- end
101
- end
102
-
103
- def ids_hash
104
- self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
105
- hash[key] = value
106
- hash
107
- end
108
- end
109
-
110
- def id_before_type_cast
111
- self.class.primary_keys.map do |key|
112
- self.read_attribute_before_type_cast(key)
113
- end
114
- end
115
-
116
- # Sets the primary ID.
117
- def id=(ids)
118
- ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
119
- unless ids.length == self.class.primary_keys.length
120
- raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
121
- end
122
- [self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
123
- id
124
- end
125
-
126
- def can_change_primary_key_values?
127
- false
128
- end
129
-
130
- # Returns this record's primary keys values in an Array
131
- # if any value is available
132
- def to_key
133
- ids.to_a if !ids.compact.empty? # XXX Maybe use primary_keys with send instead of ids
134
- end
135
-
136
- def to_param
137
- persisted? ? to_key.to_composite_keys.to_s : nil
138
- end
139
- end
140
- end
141
- end
1
+ module ActiveRecord
2
+ class CompositeKeyError < StandardError #:nodoc:
3
+ end
4
+
5
+ class Base
6
+ INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
7
+ NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
8
+
9
+ class << self
10
+ alias_method :primary_key_without_composite_key_support=, :primary_key=
11
+ def primary_key=(keys)
12
+ unless keys.kind_of?(Array)
13
+ self.primary_key_without_composite_key_support = keys
14
+ return
15
+ end
16
+
17
+ @primary_keys = keys.map { |k| k.to_s }.to_composite_keys
18
+
19
+ class_eval <<-EOV
20
+ extend CompositeClassMethods
21
+ include CompositeInstanceMethods
22
+ EOV
23
+ end
24
+ alias_method :primary_keys=, :primary_key=
25
+
26
+ def set_primary_keys(*keys)
27
+ ActiveSupport::Deprecation.warn(
28
+ "Calling set_primary_keys is deprecated. Please use `self.primary_keys = keys` instead."
29
+ )
30
+
31
+ keys = keys.first if keys.first.is_a?(Array)
32
+ if keys.length == 1
33
+ self.primary_key = keys.first
34
+ else
35
+ self.primary_keys = keys
36
+ end
37
+ end
38
+
39
+ def composite?
40
+ false
41
+ end
42
+ end
43
+
44
+ def composite?
45
+ self.class.composite?
46
+ end
47
+
48
+ module CompositeClassMethods
49
+ def primary_keys
50
+ @primary_keys = reset_primary_keys unless defined? @primary_keys
51
+ @primary_keys
52
+ end
53
+
54
+ # Don't like this method name, but its modeled after how AR does it
55
+ def reset_primary_keys #:nodoc:
56
+ if self == base_class
57
+ # CPK
58
+ self.primary_keys = get_primary_key(base_class.name)
59
+ else
60
+ self.primary_keys = base_class.primary_keys
61
+ end
62
+ end
63
+
64
+ def primary_key
65
+ primary_keys
66
+ end
67
+
68
+ def primary_key=(keys)
69
+ self.primary_keys = keys
70
+ end
71
+
72
+ def composite?
73
+ true
74
+ end
75
+
76
+ #ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
77
+ #ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
78
+ def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
79
+ many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
80
+ end
81
+ end
82
+
83
+ module CompositeInstanceMethods
84
+ # A model instance's primary keys is always available as model.ids
85
+ # whether you name it the default 'id' or set it to something else.
86
+ def id
87
+ attr_names = self.class.primary_keys
88
+ ::CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
89
+ end
90
+ alias_method :ids, :id
91
+
92
+ # This is overridden purely for json serialization support. If the model is composite
93
+ # and one of the keys is id, then we don't want to call the id method, instead we want
94
+ # to get the id attribute value
95
+ def read_attribute_for_serialization(attribute)
96
+ if self.composite? && attribute == 'id'
97
+ read_attribute(attribute)
98
+ else
99
+ send(attribute)
100
+ end
101
+ end
102
+
103
+ def ids_hash
104
+ self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
105
+ hash[key] = value
106
+ hash
107
+ end
108
+ end
109
+
110
+ def id_before_type_cast
111
+ self.class.primary_keys.map do |key|
112
+ self.read_attribute_before_type_cast(key)
113
+ end
114
+ end
115
+
116
+ # Sets the primary ID.
117
+ def id=(ids)
118
+ ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
119
+ unless ids.length == self.class.primary_keys.length
120
+ raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
121
+ end
122
+ [self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
123
+ id
124
+ end
125
+
126
+ def can_change_primary_key_values?
127
+ false
128
+ end
129
+
130
+ # Returns this record's primary keys values in an Array
131
+ # if any value is available
132
+ def to_key
133
+ ids.to_a if !ids.compact.empty? # XXX Maybe use primary_keys with send instead of ids
134
+ end
135
+
136
+ def to_param
137
+ persisted? ? to_key.to_composite_keys.to_s : nil
138
+ end
139
+ end
140
+ end
141
+ end
@@ -32,7 +32,8 @@ module CompositePrimaryKeys
32
32
  end
33
33
 
34
34
  def cpk_id_predicate(table, keys, values)
35
- eq_predicates = keys.zip(values).map do |key, value|
35
+ # We zip on values then keys in case values are not provided for each key field
36
+ eq_predicates = values.zip(keys).map do |value, key|
36
37
  table[key].eq(value)
37
38
  end
38
39
  cpk_and_predicate(eq_predicates)
@@ -1,22 +1,37 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- module DatabaseStatements
4
- def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
5
- sql, binds = to_sql_and_binds(arel, binds)
6
- value = exec_insert(sql, name, binds, pk, sequence_name)
7
-
8
- if pk.is_a?(Array) && !value.empty?
9
- # This is a CPK model and the query result is not empty. Thus we can figure out the new ids for each
10
- # auto incremented field
11
- id_value || pk.map {|key| value.first[key]}
12
- elsif pk.is_a?(Array)
13
- # This is CPK, but we don't know what autoincremented fields were updated. So return nil, which means
14
- # the existing id_value of the model will be used.
15
- id_value || Array.new(pk.size)
16
- else
17
- id_value || last_inserted_id(value)
18
- end
19
- end
20
- end
21
- end
22
- end
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module DatabaseStatements
4
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
5
+ sql, binds = to_sql_and_binds(arel, binds)
6
+ value = exec_insert(sql, name, binds, pk, sequence_name)
7
+
8
+ return id_value if id_value
9
+
10
+ if pk.is_a?(Array) && !value.empty?
11
+ # This is a CPK model and the query result is not empty. Thus we can figure out the new ids for each
12
+ # auto incremented field
13
+ pk.map {|key| value.first[key]}
14
+ elsif pk.is_a?(Array)
15
+ # This is CPK, but we don't know what autoincremented fields were updated.
16
+ result = Array.new(pk.size)
17
+
18
+ # Is there an autoincrementing field?
19
+ auto_key = pk.find do |key|
20
+ attribute = arel.ast.relation[key]
21
+ column = column_for_attribute(attribute)
22
+ if column.respond_to?(:auto_increment?)
23
+ column.auto_increment?
24
+ end
25
+ end
26
+
27
+ if auto_key
28
+ result[pk.index(auto_key)] = last_inserted_id(value)
29
+ end
30
+ result
31
+ else
32
+ last_inserted_id(value)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,44 +1,44 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- module SQLServer
4
- module DatabaseStatements
5
- def sql_for_insert(sql, pk, binds)
6
- if pk.nil?
7
- table_name = query_requires_identity_insert?(sql)
8
- pk = primary_key(table_name)
9
- end
10
-
11
- sql = if pk && use_output_inserted? && !database_prefix_remote_server?
12
- # CPK
13
- #quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
14
- quoted_pk = Array(pk).map {|subkey| SQLServer::Utils.extract_identifiers(subkey).quoted}
15
-
16
- table_name ||= get_table_name(sql)
17
- exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
18
- if exclude_output_inserted
19
- id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
20
- # CPK
21
- # <<~SQL.squish
22
- # DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
23
- # #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
24
- # SELECT CAST(#{quoted_pk.join(',')} AS #{id_sql_type}) FROM @ssaIdInsertTable
25
- # SQL
26
- <<~SQL.squish
27
- DECLARE @ssaIdInsertTable table (#{quoted_pk.map {|subkey| "#{subkey} #{id_sql_type}"}.join(", ")});
28
- #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk.join(', INSERTED.')} INTO @ssaIdInsertTable"}
29
- SELECT #{quoted_pk.map {|subkey| "CAST(#{subkey} AS #{id_sql_type}) #{subkey}"}.join(", ")} FROM @ssaIdInsertTable
30
- SQL
31
- else
32
- # CPK
33
- # sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
34
- sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk.join(', INSERTED.')}"
35
- end
36
- else
37
- "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
38
- end
39
- super
40
- end
41
- end
42
- end
43
- end
44
- end
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module DatabaseStatements
5
+ def sql_for_insert(sql, pk, binds)
6
+ if pk.nil?
7
+ table_name = query_requires_identity_insert?(sql)
8
+ pk = primary_key(table_name)
9
+ end
10
+
11
+ sql = if pk && use_output_inserted? && !database_prefix_remote_server?
12
+ # CPK
13
+ #quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
14
+ quoted_pk = Array(pk).map {|subkey| SQLServer::Utils.extract_identifiers(subkey).quoted}
15
+
16
+ table_name ||= get_table_name(sql)
17
+ exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
18
+ if exclude_output_inserted
19
+ id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
20
+ # CPK
21
+ # <<~SQL.squish
22
+ # DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
23
+ # #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
24
+ # SELECT CAST(#{quoted_pk.join(',')} AS #{id_sql_type}) FROM @ssaIdInsertTable
25
+ # SQL
26
+ <<~SQL.squish
27
+ DECLARE @ssaIdInsertTable table (#{quoted_pk.map {|subkey| "#{subkey} #{id_sql_type}"}.join(", ")});
28
+ #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk.join(', INSERTED.')} INTO @ssaIdInsertTable"}
29
+ SELECT #{quoted_pk.map {|subkey| "CAST(#{subkey} AS #{id_sql_type}) #{subkey}"}.join(", ")} FROM @ssaIdInsertTable
30
+ SQL
31
+ else
32
+ # CPK
33
+ # sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
34
+ sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk.join(', INSERTED.')}"
35
+ end
36
+ else
37
+ "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
38
+ end
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,49 +1,49 @@
1
- module ActiveRecord
2
- module Core
3
- def initialize_dup(other) # :nodoc:
4
- @attributes = @attributes.deep_dup
5
- # CPK
6
- #@attributes.reset(@primary_key)
7
- Array(self.class.primary_key).each {|key| @attributes.reset(key)}
8
-
9
- _run_initialize_callbacks
10
-
11
- @new_record = true
12
- @destroyed = false
13
- @_start_transaction_state = nil
14
- @transaction_state = nil
15
-
16
- super
17
- end
18
-
19
- module ClassMethods
20
- def find(*ids) # :nodoc:
21
- # We don't have cache keys for this stuff yet
22
- return super unless ids.length == 1
23
- return super if block_given? ||
24
- primary_key.nil? ||
25
- scope_attributes? ||
26
- columns_hash.key?(inheritance_column) && !base_class?
27
-
28
- # CPK
29
- return super if self.composite?
30
-
31
- id = ids.first
32
-
33
- return super if StatementCache.unsupported_value?(id)
34
-
35
- key = primary_key
36
-
37
- statement = cached_find_by_statement(key) { |params|
38
- where(key => params.bind).limit(1)
39
- }
40
-
41
- record = statement.execute([id], connection)&.first
42
- unless record
43
- raise ::ActiveRecord::RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
44
- end
45
- record
46
- end
47
- end
48
- end
1
+ module ActiveRecord
2
+ module Core
3
+ def initialize_dup(other) # :nodoc:
4
+ @attributes = @attributes.deep_dup
5
+ # CPK
6
+ #@attributes.reset(@primary_key)
7
+ Array(self.class.primary_key).each {|key| @attributes.reset(key)}
8
+
9
+ _run_initialize_callbacks
10
+
11
+ @new_record = true
12
+ @destroyed = false
13
+ @_start_transaction_state = nil
14
+ @transaction_state = nil
15
+
16
+ super
17
+ end
18
+
19
+ module ClassMethods
20
+ def find(*ids) # :nodoc:
21
+ # We don't have cache keys for this stuff yet
22
+ return super unless ids.length == 1
23
+ return super if block_given? ||
24
+ primary_key.nil? ||
25
+ scope_attributes? ||
26
+ columns_hash.key?(inheritance_column) && !base_class?
27
+
28
+ # CPK
29
+ return super if self.composite?
30
+
31
+ id = ids.first
32
+
33
+ return super if StatementCache.unsupported_value?(id)
34
+
35
+ key = primary_key
36
+
37
+ statement = cached_find_by_statement(key) { |params|
38
+ where(key => params.bind).limit(1)
39
+ }
40
+
41
+ record = statement.execute([id], connection)&.first
42
+ unless record
43
+ raise ::ActiveRecord::RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
44
+ end
45
+ record
46
+ end
47
+ end
48
+ end
49
49
  end
@@ -70,7 +70,7 @@ module ActiveRecord
70
70
  if target_record
71
71
  existing_record = target_record
72
72
  else
73
- association.add_to_target(existing_record, :skip_callbacks)
73
+ association.add_to_target(existing_record, skip_callbacks: true)
74
74
  end
75
75
 
76
76
  assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])