composite_primary_keys 8.1.6 → 9.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +5 -5
  2. data/History.rdoc +59 -7
  3. data/README.rdoc +62 -1
  4. data/Rakefile +4 -1
  5. data/lib/composite_primary_keys/arel/in.rb +6 -0
  6. data/lib/composite_primary_keys/arel/sqlserver.rb +36 -0
  7. data/lib/composite_primary_keys/arel/to_sql.rb +26 -0
  8. data/lib/composite_primary_keys/associations/association.rb +14 -12
  9. data/lib/composite_primary_keys/associations/association_scope.rb +22 -27
  10. data/lib/composite_primary_keys/associations/collection_association.rb +39 -8
  11. data/lib/composite_primary_keys/associations/has_many_association.rb +17 -40
  12. data/lib/composite_primary_keys/associations/has_many_through_association.rb +58 -58
  13. data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +13 -11
  14. data/lib/composite_primary_keys/associations/join_dependency.rb +73 -56
  15. data/lib/composite_primary_keys/associations/preloader/association.rb +75 -73
  16. data/lib/composite_primary_keys/attribute_methods/primary_key.rb +13 -11
  17. data/lib/composite_primary_keys/attribute_methods/read.rb +18 -15
  18. data/lib/composite_primary_keys/attribute_methods/write.rb +21 -19
  19. data/lib/composite_primary_keys/attribute_methods.rb +6 -4
  20. data/lib/composite_primary_keys/autosave_association.rb +19 -54
  21. data/lib/composite_primary_keys/base.rb +18 -82
  22. data/lib/composite_primary_keys/composite_arrays.rb +9 -1
  23. data/lib/composite_primary_keys/composite_predicates.rb +1 -0
  24. data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +10 -10
  25. data/lib/composite_primary_keys/connection_adapters/abstract_mysql_adapter.rb +5 -6
  26. data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +23 -0
  27. data/lib/composite_primary_keys/core.rb +46 -45
  28. data/lib/composite_primary_keys/fixtures.rb +19 -17
  29. data/lib/composite_primary_keys/locking/optimistic.rb +53 -49
  30. data/lib/composite_primary_keys/nested_attributes.rb +60 -53
  31. data/lib/composite_primary_keys/persistence.rb +49 -41
  32. data/lib/composite_primary_keys/relation/batches.rb +35 -32
  33. data/lib/composite_primary_keys/relation/calculations.rb +3 -7
  34. data/lib/composite_primary_keys/relation/finder_methods.rb +122 -55
  35. data/lib/composite_primary_keys/relation/predicate_builder.rb +18 -29
  36. data/lib/composite_primary_keys/relation/query_methods.rb +25 -36
  37. data/lib/composite_primary_keys/relation/where_clause.rb +33 -0
  38. data/lib/composite_primary_keys/relation.rb +96 -43
  39. data/lib/composite_primary_keys/sanitization.rb +6 -15
  40. data/lib/composite_primary_keys/version.rb +3 -3
  41. data/lib/composite_primary_keys.rb +10 -19
  42. data/scripts/console.rb +48 -48
  43. data/scripts/txt2html +76 -76
  44. data/scripts/txt2js +65 -65
  45. data/tasks/databases/mysql.rake +17 -19
  46. data/tasks/databases/oracle.rake +29 -15
  47. data/tasks/databases/postgresql.rake +20 -29
  48. data/tasks/databases/sqlite.rake +25 -0
  49. data/tasks/databases/sqlserver.rake +32 -16
  50. data/tasks/website.rake +18 -18
  51. data/test/README_tests.rdoc +56 -56
  52. data/test/abstract_unit.rb +24 -18
  53. data/test/connections/connection_spec.rb +11 -2
  54. data/test/connections/databases.ci.yml +5 -4
  55. data/test/connections/databases.example.yml +19 -4
  56. data/test/connections/databases.yml +40 -30
  57. data/test/db_test.rb +52 -52
  58. data/test/fixtures/article.rb +1 -0
  59. data/test/fixtures/articles.yml +6 -6
  60. data/test/fixtures/capitol.rb +3 -3
  61. data/test/fixtures/capitols.yml +16 -16
  62. data/test/fixtures/comments.yml +15 -15
  63. data/test/fixtures/db_definitions/mysql.sql +16 -17
  64. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  65. data/test/fixtures/db_definitions/oracle.sql +26 -17
  66. data/test/fixtures/db_definitions/postgresql.sql +2 -3
  67. data/test/fixtures/db_definitions/sqlite.sql +0 -1
  68. data/test/fixtures/db_definitions/sqlserver.sql +20 -35
  69. data/test/fixtures/department.rb +5 -5
  70. data/test/fixtures/departments.yml +15 -15
  71. data/test/fixtures/dorms.yml +4 -4
  72. data/test/fixtures/employee.rb +5 -7
  73. data/test/fixtures/employees.yml +1 -5
  74. data/test/fixtures/group.rb +2 -2
  75. data/test/fixtures/groups.yml +6 -6
  76. data/test/fixtures/hack.rb +4 -4
  77. data/test/fixtures/hacks.yml +2 -2
  78. data/test/fixtures/membership_status.rb +2 -2
  79. data/test/fixtures/product.rb +9 -9
  80. data/test/fixtures/product_tariff.rb +5 -5
  81. data/test/fixtures/products.yml +11 -11
  82. data/test/fixtures/reading.rb +4 -4
  83. data/test/fixtures/readings.yml +10 -10
  84. data/test/fixtures/reference_code_using_composite_key_alias.rb +8 -8
  85. data/test/fixtures/reference_code_using_simple_key_alias.rb +8 -8
  86. data/test/fixtures/reference_codes.yml +28 -28
  87. data/test/fixtures/reference_type.rb +1 -1
  88. data/test/fixtures/reference_types.yml +9 -9
  89. data/test/fixtures/restaurant.rb +9 -9
  90. data/test/fixtures/restaurants.yml +14 -14
  91. data/test/fixtures/restaurants_suburbs.yml +10 -10
  92. data/test/fixtures/room.rb +11 -11
  93. data/test/fixtures/room_assignment.rb +13 -13
  94. data/test/fixtures/room_assignments.yml +24 -24
  95. data/test/fixtures/room_attribute.rb +2 -2
  96. data/test/fixtures/room_attribute_assignment.rb +4 -4
  97. data/test/fixtures/room_attribute_assignments.yml +4 -4
  98. data/test/fixtures/room_attributes.yml +2 -2
  99. data/test/fixtures/rooms.yml +12 -12
  100. data/test/fixtures/seat.rb +5 -5
  101. data/test/fixtures/seats.yml +8 -8
  102. data/test/fixtures/street.rb +2 -2
  103. data/test/fixtures/streets.yml +16 -16
  104. data/test/fixtures/student.rb +3 -3
  105. data/test/fixtures/students.yml +15 -15
  106. data/test/fixtures/suburbs.yml +14 -14
  107. data/test/fixtures/tariff.rb +1 -1
  108. data/test/fixtures/tariffs.yml +14 -14
  109. data/test/fixtures/user.rb +0 -1
  110. data/test/plugins/pagination.rb +405 -405
  111. data/test/plugins/pagination_helper.rb +135 -135
  112. data/test/setup.rb +50 -50
  113. data/test/test_aliases.rb +18 -18
  114. data/test/test_associations.rb +18 -17
  115. data/test/test_composite_arrays.rb +24 -24
  116. data/test/test_counter_cache.rb +30 -30
  117. data/test/test_create.rb +14 -6
  118. data/test/test_delete.rb +36 -34
  119. data/test/test_dup.rb +37 -37
  120. data/test/test_exists.rb +39 -39
  121. data/test/test_find.rb +16 -4
  122. data/test/test_habtm.rb +27 -3
  123. data/test/test_miscellaneous.rb +32 -32
  124. data/test/test_nested_attributes.rb +6 -6
  125. data/test/test_pagination.rb +35 -35
  126. data/test/test_polymorphic.rb +0 -7
  127. data/test/test_predicates.rb +20 -20
  128. data/test/test_preload.rb +94 -0
  129. data/test/test_suite.rb +1 -1
  130. data/test/test_update.rb +10 -7
  131. data/test/test_validations.rb +13 -13
  132. metadata +30 -39
  133. data/README_DB2.rdoc +0 -33
  134. data/lib/composite_primary_keys/arel/visitors/to_sql.rb +0 -36
  135. data/lib/composite_primary_keys/associations/singular_association.rb +0 -18
  136. data/lib/composite_primary_keys/attribute_methods/dirty.rb +0 -29
  137. data/lib/composite_primary_keys/attribute_set/builder.rb +0 -20
  138. data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +0 -70
  139. data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +0 -19
  140. data/lib/composite_primary_keys/dirty.rb +0 -19
  141. data/lib/composite_primary_keys/validations/uniqueness.rb +0 -41
  142. data/tasks/databases/oracle_enhanced.rake +0 -27
  143. data/tasks/databases/sqlite3.rake +0 -27
  144. data/test/connections/native_ibm_db/connection.rb +0 -19
  145. data/test/connections/native_mysql/connection.rb +0 -17
  146. data/test/connections/native_oracle/connection.rb +0 -11
  147. data/test/connections/native_oracle_enhanced/connection.rb +0 -16
  148. data/test/connections/native_postgresql/connection.rb +0 -13
  149. data/test/connections/native_sqlite3/connection.rb +0 -9
  150. data/test/connections/native_sqlserver/connection.rb +0 -11
  151. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -92
  152. data/test/test_delete_all.rb +0 -35
  153. data/test/test_find_in_batches.rb +0 -30
@@ -7,21 +7,8 @@ module ActiveRecord
7
7
  NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
8
8
 
9
9
  class << self
10
- def primary_keys
11
- unless defined?(@primary_keys)
12
- reset_primary_keys
13
- end
14
- @primary_keys
15
- end
16
-
17
- # Don't like this method name, but its modeled after how AR does it
18
- def reset_primary_keys
19
- if self != base_class
20
- self.primary_keys = base_class.primary_keys
21
- end
22
- end
23
-
24
- def primary_key_with_composite_key_support=(keys)
10
+ alias_method :primary_key_without_composite_key_support=, :primary_key=
11
+ def primary_key=(keys)
25
12
  unless keys.kind_of?(Array)
26
13
  self.primary_key_without_composite_key_support = keys
27
14
  return
@@ -34,7 +21,6 @@ module ActiveRecord
34
21
  include CompositeInstanceMethods
35
22
  EOV
36
23
  end
37
- alias_method_chain :primary_key=, :composite_key_support
38
24
  alias_method :primary_keys=, :primary_key=
39
25
 
40
26
  def set_primary_keys(*keys)
@@ -48,75 +34,11 @@ module ActiveRecord
48
34
  else
49
35
  self.primary_keys = keys
50
36
  end
51
- end
37
+ end
52
38
 
53
39
  def composite?
54
40
  false
55
41
  end
56
-
57
- def find_in_batches(options = {})
58
- return super unless primary_key.is_a?(Array)
59
-
60
- # Unfortunately .count uses a subquery temp table, which is a big problem when your table is large
61
- number_of_rows = count(primary_key.first)
62
- batch_size = options[:batch_size] || 100000
63
- row_number = 0
64
-
65
- while row_number < number_of_rows
66
- end_row_number = row_number + batch_size - 1
67
- end_row_number = number_of_rows - 1 if end_row_number > number_of_rows - 1
68
-
69
- # Force the necessary sorting; AR as is will sort a PK table incorrectly
70
- start_key = order(*primary_key).
71
- offset(row_number).
72
- first.
73
- attributes.
74
- slice(*primary_key)
75
-
76
- end_key = order(*(primary_key.map { |k| "#{k} ASC" })).
77
- offset(end_row_number).
78
- first.
79
- attributes.
80
- slice(*primary_key)
81
-
82
- relation = self
83
- lower_bounds = []
84
- upper_bounds = []
85
-
86
- # Iterate through the PKs positionally; when we have found a discrepancy between start and end
87
- # then we know that's where the boundaries are
88
- primary_key.each do |col|
89
- if start_key[col] == end_key[col]
90
- relation = relation.where("#{col} = '#{start_key[col]}'")
91
- else
92
- lower_bounds << [col, start_key[col]]
93
- upper_bounds << [col, end_key[col]]
94
- end
95
- end
96
-
97
- relation = relation.where(build_batch_case(lower_bounds, '>')) unless lower_bounds.empty?
98
- relation = relation.where(build_batch_case(upper_bounds, '<')) unless upper_bounds.empty?
99
-
100
- yield(relation.order(*primary_key))
101
-
102
- row_number = end_row_number + 1
103
- end
104
- end
105
-
106
- private
107
-
108
- def build_batch_case(bounds, operator)
109
- bounds = bounds.dup
110
- bound = bounds.shift
111
- if bounds.empty?
112
- "#{bound[0]} #{operator}= '#{bound[1]}' "
113
- else
114
- sql_case = "CASE WHEN #{bound[0]} = '#{bound[1]}' THEN "
115
- sql_case += build_batch_case(bounds, operator)
116
- sql_case += "ELSE #{bound[0]} #{operator} '#{bound[1]}' END "
117
- sql_case
118
- end
119
- end
120
42
  end
121
43
 
122
44
  def composite?
@@ -124,12 +46,26 @@ module ActiveRecord
124
46
  end
125
47
 
126
48
  module CompositeClassMethods
49
+ def primary_keys
50
+ unless defined?(@primary_keys)
51
+ reset_primary_keys
52
+ end
53
+ @primary_keys
54
+ end
55
+
56
+ # Don't like this method name, but its modeled after how AR does it
57
+ def reset_primary_keys
58
+ if self != base_class
59
+ self.primary_keys = base_class.primary_keys
60
+ end
61
+ end
62
+
127
63
  def primary_key
128
64
  primary_keys
129
65
  end
130
66
 
131
67
  def primary_key=(keys)
132
- primary_keys = keys
68
+ self.primary_keys = keys
133
69
  end
134
70
 
135
71
  def composite?
@@ -22,7 +22,7 @@ module CompositePrimaryKeys
22
22
 
23
23
  class CompositeKeys < Array
24
24
 
25
- def self.parse(value)
25
+ def self.parse(value)
26
26
  case value
27
27
  when Array
28
28
  value.to_composite_keys
@@ -33,6 +33,14 @@ module CompositePrimaryKeys
33
33
  end
34
34
  end
35
35
 
36
+ def in(other)
37
+ case other
38
+ when Arel::SelectManager
39
+ CompositePrimaryKeys::Nodes::In.new(self, other.ast)
40
+ end
41
+ end
42
+
43
+
36
44
  def to_s
37
45
  # Doing this makes it easier to parse Base#[](attr_name)
38
46
  join(ID_SEP)
@@ -39,6 +39,7 @@ module CompositePrimaryKeys
39
39
  key2_fields = Array(key2).map {|key| table2[key]}
40
40
 
41
41
  eq_predicates = key1_fields.zip(key2_fields).map do |key_field1, key_field2|
42
+ key_field2 = Arel::Nodes::Quoted.new(key_field2) unless Arel::Attributes::Attribute === key_field2
42
43
  key_field1.eq(key_field2)
43
44
  end
44
45
  cpk_and_predicate(eq_predicates)
@@ -1,11 +1,11 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- class AbstractAdapter
4
- def quote_column_names(name)
5
- Array(name).map do |col|
6
- quote_column_name(col.to_s)
7
- end.join(CompositePrimaryKeys::ID_SEP)
8
- end
9
- end
10
- end
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class AbstractAdapter
4
+ def quote_column_names(name)
5
+ Array(name).map do |col|
6
+ quote_column_name(col.to_s)
7
+ end.join(CompositePrimaryKeys::ID_SEP)
8
+ end
9
+ end
10
+ end
11
11
  end
@@ -1,8 +1,6 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class AbstractMysqlAdapter < AbstractAdapter
4
- # MySQL is too stupid to create a temporary table for use subquery, so we have
5
- # to give it some prompting in the form of a subsubquery. Ugh!
3
+ class AbstractMysqlAdapter
6
4
  def subquery_for(key, select)
7
5
  subsubselect = select.clone
8
6
  subsubselect.projections = [key]
@@ -13,9 +11,10 @@ module ActiveRecord
13
11
 
14
12
  subselect = Arel::SelectManager.new(select.engine)
15
13
 
16
- # subselect.project Arel.sql(key.name)
17
- arel_table = select.engine.arel_table
18
- subselect.project *key.name.map{|x| arel_table[x]}
14
+ # CPK
15
+ #subselect.project Arel.sql(key.name)
16
+ subselect.project Arel.sql(Array(key).map(&:name).join(', '))
17
+
19
18
  subselect.from subsubselect.as('__active_record_temp')
20
19
  end
21
20
  end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class SQLite3Adapter
4
+ def join_to_update(update, select, key)
5
+ if key.is_a?(::CompositePrimaryKeys::CompositeKeys)
6
+ subselect = subquery_for(key, select)
7
+ subselect_aliased = Arel::Nodes::TableAlias.new(subselect, 'cpk_inner')
8
+ cpk_subselect = Arel::SelectManager.new(subselect_aliased)
9
+ cpk_subselect.project('*')
10
+ key.each do |a_key|
11
+ where_expr = subselect_aliased[a_key.name].eq(update.ast.relation[a_key.name])
12
+ cpk_subselect.where(where_expr)
13
+ end
14
+ where_clause = Arel::Nodes::SqlLiteral.new("EXISTS (#{cpk_subselect.to_sql})")
15
+ update.where(where_clause)
16
+ else
17
+ super
18
+ end
19
+ end
20
+ alias join_to_delete join_to_update
21
+ end
22
+ end
23
+ end
@@ -1,60 +1,61 @@
1
1
  module ActiveRecord
2
2
  module Core
3
- def initialize_dup(other) # :nodoc:
4
- @attributes = @attributes.dup
5
- # CPK
6
- # @attributes.reset(self.class.primary_key)
7
- Array(self.class.primary_key).each {|key| @attributes.reset(key)}
3
+ silence_warnings do
4
+ def initialize_dup(other) # :nodoc:
5
+ @attributes = @attributes.dup
6
+ # CPK
7
+ # @attributes.reset(self.class.primary_key)
8
+ Array(self.class.primary_key).each {|key| @attributes.reset(key)}
8
9
 
9
- run_callbacks(:initialize) unless _initialize_callbacks.empty?
10
+ run_callbacks(:initialize) unless _initialize_callbacks.empty?
10
11
 
11
- @aggregation_cache = {}
12
- @association_cache = {}
12
+ @aggregation_cache = {}
13
+ @association_cache = {}
13
14
 
14
- @new_record = true
15
- @destroyed = false
15
+ @new_record = true
16
+ @destroyed = false
16
17
 
17
- super
18
+ super
19
+ end
18
20
  end
19
21
 
20
22
  module ClassMethods
21
- def find(*ids) # :nodoc:
22
- # We don't have cache keys for this stuff yet
23
- return super unless ids.length == 1
24
- # Allow symbols to super to maintain compatibility for deprecated finders until Rails 5
25
- return super if ids.first.kind_of?(Symbol)
26
- return super if block_given? ||
27
- primary_key.nil? ||
28
- default_scopes.any? ||
29
- current_scope ||
30
- columns_hash.include?(inheritance_column) ||
31
- ids.first.kind_of?(Array)
32
-
33
- # CPK
34
- return super if self.composite?
35
-
36
- id = ids.first
37
- if ActiveRecord::Base === id
38
- id = id.id
39
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
40
- You are passing an instance of ActiveRecord::Base to `find`.
41
- Please pass the id of the object by calling `.id`
42
- MSG
43
- end
44
- key = primary_key
45
-
46
- s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
47
- find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
23
+ silence_warnings do
24
+ def find(*ids) # :nodoc:
25
+ # We don't have cache keys for this stuff yet
26
+ return super unless ids.length == 1
27
+ return super if block_given? ||
28
+ primary_key.nil? ||
29
+ scope_attributes? ||
30
+ columns_hash.include?(inheritance_column) ||
31
+ ids.first.kind_of?(Array)
32
+ # CPK
33
+ return super if self.composite?
34
+
35
+ id = ids.first
36
+ if ActiveRecord::Base === id
37
+ id = id.id
38
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
39
+ You are passing an instance of ActiveRecord::Base to `find`.
40
+ Please pass the id of the object by calling `.id`
41
+ MSG
42
+ end
43
+
44
+ key = primary_key
45
+
46
+ statement = cached_find_by_statement(key) { |params|
48
47
  where(key => params.bind).limit(1)
49
48
  }
50
- }
51
- record = s.execute([id], self, connection).first
52
- unless record
53
- raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
49
+ record = statement.execute([id], self, connection).first
50
+ unless record
51
+ raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
52
+ name, primary_key, id)
53
+ end
54
+ record
55
+ rescue RangeError
56
+ raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
57
+ name, primary_key)
54
58
  end
55
- record
56
- rescue RangeError
57
- raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
58
59
  end
59
60
  end
60
61
  end
@@ -1,25 +1,27 @@
1
1
  module ActiveRecord
2
2
  class Fixture
3
- def find
4
- if model_class
5
- # CPK
6
- # model_class.unscoped do
7
- # model_class.find(fixture[model_class.primary_key])
8
- # end
9
- model_class.unscoped do
10
- ids = self.ids(model_class.primary_key)
11
- model_class.find(ids)
3
+ silence_warnings do
4
+ def find
5
+ if model_class
6
+ # CPK
7
+ # model_class.unscoped do
8
+ # model_class.find(fixture[model_class.primary_key])
9
+ # end
10
+ model_class.unscoped do
11
+ ids = self.ids(model_class.primary_key)
12
+ model_class.find(ids)
13
+ end
14
+ else
15
+ raise FixtureClassNotFound, "No class attached to find."
12
16
  end
13
- else
14
- raise FixtureClassNotFound, "No class attached to find."
15
17
  end
16
- end
17
18
 
18
- def ids(key)
19
- if key.is_a? Array
20
- key.map {|a_key| fixture[a_key.to_s] }
21
- else
22
- fixture[key]
19
+ def ids(key)
20
+ if key.is_a? Array
21
+ key.map {|a_key| fixture[a_key.to_s] }
22
+ else
23
+ fixture[key]
24
+ end
23
25
  end
24
26
  end
25
27
  end
@@ -1,54 +1,58 @@
1
- module ActiveRecord
2
- module Locking
3
- module Optimistic
4
- private
5
- def _update_record(attribute_names = @attributes.keys) #:nodoc:
6
- return super unless locking_enabled?
7
- return 0 if attribute_names.empty?
8
-
9
- lock_col = self.class.locking_column
10
- previous_lock_value = send(lock_col).to_i
11
- increment_lock
12
-
13
- attribute_names += [lock_col]
14
- attribute_names.uniq!
15
-
16
- begin
17
- relation = self.class.unscoped
18
-
19
- if self.composite?
20
- affected_rows = relation.where(
21
- relation.cpk_id_predicate(relation.table, self.class.primary_key, id_was)
22
- ).where(
23
- lock_col => previous_lock_value
24
- ).update_all(
25
- Hash[attributes_for_update(attribute_names).map do |name|
26
- [name, _read_attribute(name)]
27
- end]
28
- )
29
- else
30
- affected_rows = relation.where(
31
- self.class.primary_key => id,
32
- lock_col => previous_lock_value,
33
- ).update_all(
34
- Hash[attributes_for_update(attribute_names).map do |name|
35
- [name, _read_attribute(name)]
36
- end]
37
- )
38
- end
39
-
40
- unless affected_rows == 1
41
- raise ActiveRecord::StaleObjectError.new(self, "update")
42
- end
43
-
44
- affected_rows
45
-
46
- # If something went wrong, revert the version.
47
- rescue Exception
48
- send(lock_col + '=', previous_lock_value)
49
- raise
1
+ module ActiveRecord
2
+ module Locking
3
+ module Optimistic
4
+
5
+ private
6
+
7
+ silence_warnings do
8
+ def _update_record(attribute_names = self.attribute_names) #:nodoc:
9
+ return super unless locking_enabled?
10
+ return 0 if attribute_names.empty?
11
+
12
+ lock_col = self.class.locking_column
13
+ previous_lock_value = send(lock_col).to_i
14
+ increment_lock
15
+
16
+ attribute_names += [lock_col]
17
+ attribute_names.uniq!
18
+
19
+ begin
20
+ relation = self.class.unscoped
21
+
22
+ if self.composite?
23
+ affected_rows = relation.where(
24
+ relation.cpk_id_predicate(relation.table, self.class.primary_key, id_was)
25
+ ).where(
26
+ lock_col => previous_lock_value
27
+ ).update_all(
28
+ attributes_for_update(attribute_names).map do |name|
29
+ [name, _read_attribute(name)]
30
+ end.to_h
31
+ )
32
+ else
33
+ affected_rows = relation.where(
34
+ self.class.primary_key => id,
35
+ lock_col => previous_lock_value,
36
+ ).update_all(
37
+ attributes_for_update(attribute_names).map do |name|
38
+ [name, _read_attribute(name)]
39
+ end.to_h
40
+ )
41
+ end
42
+
43
+ unless affected_rows == 1
44
+ raise ActiveRecord::StaleObjectError.new(self, "update")
45
+ end
46
+
47
+ affected_rows
48
+
49
+ # If something went wrong, revert the version.
50
+ rescue Exception
51
+ send(lock_col + '=', previous_lock_value)
52
+ raise
50
53
  end
51
54
  end
55
+ end
52
56
  end
53
57
  end
54
58
  end
@@ -8,70 +8,77 @@ module ActiveRecord
8
8
  end
9
9
  end
10
10
 
11
- def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
12
- options = self.nested_attributes_options[association_name]
11
+ silence_warnings do
12
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
13
+ options = self.nested_attributes_options[association_name]
13
14
 
14
- unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
15
- raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
16
- end
15
+ if attributes_collection.respond_to?(:permitted?)
16
+ attributes_collection = attributes_collection.to_h
17
+ end
17
18
 
18
- check_record_limit!(options[:limit], attributes_collection)
19
+ unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
20
+ raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
21
+ end
19
22
 
20
- if attributes_collection.is_a? Hash
21
- keys = attributes_collection.keys
22
- attributes_collection = if keys.include?('id') || keys.include?(:id)
23
- [attributes_collection]
24
- else
25
- attributes_collection.values
26
- end
27
- end
23
+ check_record_limit!(options[:limit], attributes_collection)
28
24
 
29
- association = association(association_name)
25
+ if attributes_collection.is_a? Hash
26
+ keys = attributes_collection.keys
27
+ attributes_collection = if keys.include?('id') || keys.include?(:id)
28
+ [attributes_collection]
29
+ else
30
+ attributes_collection.values
31
+ end
32
+ end
30
33
 
31
- existing_records = if association.loaded?
32
- association.target
33
- # CPK
34
- elsif association.reflection.klass.composite?
35
- attributes_collection.map do |attribute_collection|
36
- attribute_ids = attribute_collection['id'] || attribute_collection[:id]
37
- if attribute_ids
38
- ids = CompositePrimaryKeys::CompositeKeys.parse(attribute_ids)
39
- eq_predicates = association.klass.primary_key.zip(ids).map do |primary_key, value|
40
- association.klass.arel_table[primary_key].eq(value)
34
+ association = association(association_name)
35
+
36
+ existing_records = if association.loaded?
37
+ association.target
38
+ # CPK
39
+ elsif association.reflection.klass.composite?
40
+ attributes_collection.map do |attribute_collection|
41
+ attribute_ids = attribute_collection['id'] || attribute_collection[:id]
42
+ if attribute_ids
43
+ ids = CompositePrimaryKeys::CompositeKeys.parse(attribute_ids)
44
+ eq_predicates = Class.new.extend(CompositePrimaryKeys::Predicates).cpk_id_predicate(association.klass.arel_table, association.klass.primary_key, ids)
45
+ association.scope.where(eq_predicates).to_a
46
+ else
47
+ []
41
48
  end
42
- association.scope.where(*eq_predicates).to_a
43
- else
44
- []
45
- end
46
- end.flatten.compact
47
- else
48
- attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
49
- attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
50
- end
49
+ end.flatten.compact
50
+ else
51
+ attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
52
+ attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
53
+ end
51
54
 
52
- attributes_collection.each do |attributes|
53
- attributes = attributes.with_indifferent_access
54
- if attributes['id'].blank?
55
- unless reject_new_record?(association_name, attributes)
56
- association.build(attributes.except(*UNASSIGNABLE_KEYS))
55
+ attributes_collection.each do |attributes|
56
+ if attributes.respond_to?(:permitted?)
57
+ attributes = attributes.to_h
57
58
  end
58
- elsif existing_record = cpk_detect_record(attributes['id'], existing_records)
59
- unless call_reject_if(association_name, attributes)
60
- # Make sure we are operating on the actual object which is in the association's
61
- # proxy_target array (either by finding it, or adding it if not found)
62
- # Take into account that the proxy_target may have changed due to callbacks
63
- target_record = cpk_detect_record(attributes['id'], association.target)
64
-
65
- if target_record
66
- existing_record = target_record
67
- else
68
- association.add_to_target(existing_record, :skip_callbacks)
59
+ attributes = attributes.with_indifferent_access
60
+ if attributes['id'].blank?
61
+ unless reject_new_record?(association_name, attributes)
62
+ association.build(attributes.except(*UNASSIGNABLE_KEYS))
69
63
  end
64
+ elsif existing_record = cpk_detect_record(attributes['id'], existing_records)
65
+ unless call_reject_if(association_name, attributes)
66
+ # Make sure we are operating on the actual object which is in the association's
67
+ # proxy_target array (either by finding it, or adding it if not found)
68
+ # Take into account that the proxy_target may have changed due to callbacks
69
+ target_record = cpk_detect_record(attributes['id'], association.target)
70
70
 
71
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
71
+ if target_record
72
+ existing_record = target_record
73
+ else
74
+ association.add_to_target(existing_record, :skip_callbacks)
75
+ end
76
+
77
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
78
+ end
79
+ else
80
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
72
81
  end
73
- else
74
- raise_nested_attributes_record_not_found!(association_name, attributes['id'])
75
82
  end
76
83
  end
77
84
  end