composite_primary_keys 12.0.9 → 14.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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +894 -877
  3. data/README.rdoc +182 -180
  4. data/Rakefile +37 -37
  5. data/lib/composite_primary_keys/active_model/attribute_assignment.rb +19 -19
  6. data/lib/composite_primary_keys/arel/sqlserver.rb +37 -37
  7. data/lib/composite_primary_keys/arel/to_sql.rb +18 -18
  8. data/lib/composite_primary_keys/associations/association.rb +23 -23
  9. data/lib/composite_primary_keys/associations/association_scope.rb +66 -68
  10. data/lib/composite_primary_keys/associations/collection_association.rb +31 -31
  11. data/lib/composite_primary_keys/associations/foreign_association.rb +15 -15
  12. data/lib/composite_primary_keys/associations/has_many_association.rb +35 -35
  13. data/lib/composite_primary_keys/associations/join_dependency.rb +137 -103
  14. data/lib/composite_primary_keys/associations/preloader/association.rb +68 -53
  15. data/lib/composite_primary_keys/associations/through_association.rb +25 -25
  16. data/lib/composite_primary_keys/attribute_methods/primary_key.rb +0 -2
  17. data/lib/composite_primary_keys/attribute_methods/read.rb +30 -30
  18. data/lib/composite_primary_keys/attribute_methods/write.rb +35 -35
  19. data/lib/composite_primary_keys/attribute_methods.rb +21 -9
  20. data/lib/composite_primary_keys/autosave_association.rb +60 -60
  21. data/lib/composite_primary_keys/base.rb +141 -141
  22. data/lib/composite_primary_keys/composite_arrays.rb +86 -86
  23. data/lib/composite_primary_keys/composite_predicates.rb +71 -69
  24. data/lib/composite_primary_keys/composite_relation.rb +29 -29
  25. data/lib/composite_primary_keys/connection_adapters/abstract/database_statements.rb +37 -37
  26. data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +10 -10
  27. data/lib/composite_primary_keys/connection_adapters/postgresql/database_statements.rb +26 -26
  28. data/lib/composite_primary_keys/connection_adapters/sqlserver/database_statements.rb +44 -44
  29. data/lib/composite_primary_keys/core.rb +48 -48
  30. data/lib/composite_primary_keys/counter_cache.rb +15 -15
  31. data/lib/composite_primary_keys/fixtures.rb +21 -21
  32. data/lib/composite_primary_keys/nested_attributes.rb +1 -1
  33. data/lib/composite_primary_keys/persistence.rb +96 -81
  34. data/lib/composite_primary_keys/reflection.rb +91 -29
  35. data/lib/composite_primary_keys/relation/batches.rb +15 -7
  36. data/lib/composite_primary_keys/relation/calculations.rb +110 -81
  37. data/lib/composite_primary_keys/relation/finder_methods.rb +235 -235
  38. data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +39 -20
  39. data/lib/composite_primary_keys/relation/query_methods.rb +42 -42
  40. data/lib/composite_primary_keys/relation/where_clause.rb +18 -23
  41. data/lib/composite_primary_keys/relation.rb +197 -193
  42. data/lib/composite_primary_keys/sanitization.rb +42 -42
  43. data/lib/composite_primary_keys/table_metadata.rb +11 -0
  44. data/lib/composite_primary_keys/transactions.rb +34 -34
  45. data/lib/composite_primary_keys/validations/uniqueness.rb +31 -31
  46. data/lib/composite_primary_keys/version.rb +8 -8
  47. data/lib/composite_primary_keys.rb +119 -117
  48. data/scripts/console.rb +48 -48
  49. data/scripts/txt2html +76 -76
  50. data/scripts/txt2js +65 -65
  51. data/tasks/databases/mysql.rake +40 -40
  52. data/tasks/databases/oracle.rake +41 -41
  53. data/tasks/databases/postgresql.rake +38 -38
  54. data/tasks/databases/sqlite.rake +25 -25
  55. data/tasks/databases/sqlserver.rake +43 -43
  56. data/tasks/website.rake +18 -18
  57. data/test/README_tests.rdoc +56 -56
  58. data/test/abstract_unit.rb +118 -114
  59. data/test/connections/connection_spec.rb +27 -27
  60. data/test/connections/databases.ci.yml +22 -22
  61. data/test/connections/databases.example.yml +40 -40
  62. data/test/connections/databases.yml +40 -39
  63. data/test/fixtures/article.rb +10 -10
  64. data/test/fixtures/articles.yml +7 -7
  65. data/test/fixtures/capitol.rb +3 -3
  66. data/test/fixtures/capitols.yml +16 -16
  67. data/test/fixtures/comment.rb +5 -5
  68. data/test/fixtures/comments.yml +17 -17
  69. data/test/fixtures/db_definitions/db2-create-tables.sql +112 -112
  70. data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -16
  71. data/test/fixtures/db_definitions/mysql.sql +180 -180
  72. data/test/fixtures/db_definitions/oracle.drop.sql +41 -41
  73. data/test/fixtures/db_definitions/oracle.sql +199 -199
  74. data/test/fixtures/db_definitions/postgresql.sql +182 -182
  75. data/test/fixtures/db_definitions/sqlite.sql +169 -169
  76. data/test/fixtures/db_definitions/sqlserver.sql +176 -176
  77. data/test/fixtures/department.rb +16 -16
  78. data/test/fixtures/departments.yml +19 -15
  79. data/test/fixtures/dorm.rb +2 -2
  80. data/test/fixtures/dorms.yml +4 -4
  81. data/test/fixtures/employee.rb +5 -5
  82. data/test/fixtures/employees.yml +33 -28
  83. data/test/fixtures/group.rb +2 -2
  84. data/test/fixtures/groups.yml +6 -6
  85. data/test/fixtures/membership.rb +8 -6
  86. data/test/fixtures/membership_status.rb +2 -2
  87. data/test/fixtures/membership_statuses.yml +16 -16
  88. data/test/fixtures/memberships.yml +10 -10
  89. data/test/fixtures/product.rb +9 -9
  90. data/test/fixtures/product_tariff.rb +5 -5
  91. data/test/fixtures/product_tariffs.yml +14 -14
  92. data/test/fixtures/products.yml +11 -11
  93. data/test/fixtures/reading.rb +4 -4
  94. data/test/fixtures/readings.yml +10 -10
  95. data/test/fixtures/reference_code.rb +7 -7
  96. data/test/fixtures/reference_codes.yml +28 -28
  97. data/test/fixtures/reference_type.rb +12 -12
  98. data/test/fixtures/reference_types.yml +9 -9
  99. data/test/fixtures/restaurant.rb +9 -9
  100. data/test/fixtures/restaurants.yml +14 -14
  101. data/test/fixtures/restaurants_suburb.rb +2 -2
  102. data/test/fixtures/restaurants_suburbs.yml +10 -10
  103. data/test/fixtures/room.rb +11 -11
  104. data/test/fixtures/room_assignment.rb +13 -13
  105. data/test/fixtures/room_assignments.yml +24 -24
  106. data/test/fixtures/room_attribute.rb +2 -2
  107. data/test/fixtures/room_attribute_assignment.rb +4 -4
  108. data/test/fixtures/room_attribute_assignments.yml +4 -4
  109. data/test/fixtures/room_attributes.yml +2 -2
  110. data/test/fixtures/rooms.yml +12 -12
  111. data/test/fixtures/street.rb +2 -2
  112. data/test/fixtures/streets.yml +16 -16
  113. data/test/fixtures/student.rb +3 -3
  114. data/test/fixtures/students.yml +15 -15
  115. data/test/fixtures/suburb.rb +5 -5
  116. data/test/fixtures/suburbs.yml +14 -14
  117. data/test/fixtures/tariff.rb +5 -5
  118. data/test/fixtures/tariffs.yml +14 -14
  119. data/test/fixtures/topic_sources.yml +3 -3
  120. data/test/fixtures/topics.yml +8 -8
  121. data/test/fixtures/user.rb +11 -11
  122. data/test/fixtures/users.yml +10 -10
  123. data/test/plugins/pagination.rb +405 -405
  124. data/test/plugins/pagination_helper.rb +135 -135
  125. data/test/test_associations.rb +372 -358
  126. data/test/test_attribute_methods.rb +63 -63
  127. data/test/test_attributes.rb +75 -60
  128. data/test/test_calculations.rb +49 -42
  129. data/test/test_callbacks.rb +99 -99
  130. data/test/test_composite_arrays.rb +38 -38
  131. data/test/test_counter_cache.rb +30 -30
  132. data/test/test_create.rb +218 -206
  133. data/test/test_delete.rb +188 -179
  134. data/test/test_dumpable.rb +15 -15
  135. data/test/test_dup.rb +37 -37
  136. data/test/test_equal.rb +26 -26
  137. data/test/test_exists.rb +39 -39
  138. data/test/test_find.rb +170 -164
  139. data/test/test_habtm.rb +141 -141
  140. data/test/test_ids.rb +112 -112
  141. data/test/test_miscellaneous.rb +32 -32
  142. data/test/test_nested_attributes.rb +67 -67
  143. data/test/test_optimistic.rb +18 -18
  144. data/test/test_pagination.rb +35 -35
  145. data/test/test_polymorphic.rb +43 -43
  146. data/test/test_predicates.rb +59 -59
  147. data/test/test_preload.rb +102 -102
  148. data/test/test_santiago.rb +23 -23
  149. data/test/test_touch.rb +23 -23
  150. data/test/test_tutorial_example.rb +25 -25
  151. data/test/test_update.rb +102 -96
  152. data/test/test_validations.rb +13 -13
  153. metadata +7 -6
@@ -1,193 +1,197 @@
1
- module ActiveRecord
2
- class Relation
3
- alias :initialize_without_cpk :initialize
4
- def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
5
- initialize_without_cpk(klass, table: table, predicate_builder: predicate_builder, values: values)
6
- add_cpk_support if klass && klass.composite?
7
- end
8
-
9
- alias :initialize_copy_without_cpk :initialize_copy
10
- def initialize_copy(other)
11
- initialize_copy_without_cpk(other)
12
- add_cpk_support if klass.composite?
13
- end
14
-
15
- def add_cpk_support
16
- extend CompositePrimaryKeys::CompositeRelation
17
- end
18
-
19
- def update_all(updates)
20
- raise ArgumentError, "Empty list of attributes to change" if updates.blank?
21
-
22
- if eager_loading?
23
- relation = apply_join_dependency
24
- return relation.update_all(updates)
25
- end
26
-
27
- stmt = Arel::UpdateManager.new
28
- # CPK
29
- if @klass.composite?
30
- stmt.table(arel_table)
31
- cpk_subquery(stmt)
32
- else
33
- stmt.table(arel.join_sources.empty? ? table : arel.source)
34
- stmt.key = arel_attribute(primary_key)
35
- stmt.wheres = arel.constraints
36
- end
37
- stmt.take(arel.limit)
38
- stmt.offset(arel.offset)
39
- stmt.order(*arel.orders)
40
-
41
- if updates.is_a?(Hash)
42
- if klass.locking_enabled? &&
43
- !updates.key?(klass.locking_column) &&
44
- !updates.key?(klass.locking_column.to_sym)
45
- attr = arel_attribute(klass.locking_column)
46
- updates[attr.name] = _increment_attribute(attr)
47
- end
48
- stmt.set _substitute_values(updates)
49
- else
50
- stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
51
- end
52
-
53
- @klass.connection.update stmt, "#{@klass} Update All"
54
- end
55
-
56
- def delete_all
57
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
58
- value = @values[method]
59
- method == :distinct ? value : value&.any?
60
- end
61
- if invalid_methods.any?
62
- raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
63
- end
64
-
65
- if eager_loading?
66
- relation = apply_join_dependency
67
- return relation.delete_all
68
- end
69
-
70
- stmt = Arel::DeleteManager.new
71
-
72
- if @klass.composite?
73
- stmt.from(arel_table)
74
- cpk_subquery(stmt)
75
- else
76
- stmt.from(arel.join_sources.empty? ? table : arel.source)
77
- stmt.key = arel_attribute(primary_key)
78
- stmt.wheres = arel.constraints
79
- end
80
-
81
- stmt.take(arel.limit)
82
- stmt.offset(arel.offset)
83
- stmt.order(*arel.orders)
84
-
85
- affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
86
-
87
- reset
88
- affected
89
- end
90
-
91
- # CPK
92
- def cpk_subquery(stmt)
93
- # For update and delete statements we need a way to specify which records should
94
- # get updated. By default, Rails creates a nested IN subquery that uses the primary
95
- # key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
96
- # columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
97
- # and MySQL supports obfuscated IN queries. Thus we need to check the type of
98
- # database adapter to decide how to proceed.
99
- if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
100
- cpk_mysql_subquery(stmt)
101
- elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
102
- cpk_exists_subquery(stmt)
103
- else
104
- cpk_in_subquery(stmt)
105
- end
106
- end
107
-
108
- # Used by postgresql, sqlite, mariadb and oracle. Example query:
109
- #
110
- # UPDATE reference_codes
111
- # SET ...
112
- # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
113
- # (SELECT reference_codes.reference_type_id, reference_codes.reference_code
114
- # FROM reference_codes)
115
- def cpk_in_subquery(stmt)
116
- # Setup the subquery
117
- subquery = arel.clone
118
- subquery.projections = primary_keys.map do |key|
119
- arel_table[key]
120
- end
121
-
122
- where_fields = primary_keys.map do |key|
123
- arel_table[key]
124
- end
125
- where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
126
- stmt.wheres = [where]
127
- end
128
-
129
- # CPK. This is an alternative to IN subqueries. It is used by sqlserver.
130
- # Example query:
131
- #
132
- # UPDATE reference_codes
133
- # SET ...
134
- # WHERE EXISTS
135
- # (SELECT 1
136
- # FROM reference_codes cpk_child
137
- # WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
138
- # reference_codes.reference_code = cpk_child.reference_code)
139
- def cpk_exists_subquery(stmt)
140
- arel_attributes = primary_keys.map do |key|
141
- arel_attribute(key)
142
- end.to_composite_keys
143
-
144
- # Clone the query
145
- subselect = arel.clone
146
-
147
- # Alias the table - we assume just one table
148
- aliased_table = subselect.froms.first
149
- aliased_table.table_alias = "cpk_child"
150
-
151
- # Project - really we could just set this to "1"
152
- subselect.projections = arel_attributes
153
-
154
- # Setup correlation to the outer query via where clauses
155
- primary_keys.map do |key|
156
- outer_attribute = arel_table[key]
157
- inner_attribute = aliased_table[key]
158
- where = outer_attribute.eq(inner_attribute)
159
- subselect.where(where)
160
- end
161
- stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
162
- end
163
-
164
- # CPK. This is the old way CPK created subqueries and is used by MySql.
165
- # MySQL does not support referencing the same table that is being UPDATEd or
166
- # DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
167
- #
168
- # UPDATE `reference_codes`
169
- # SET ...
170
- # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
171
- # (SELECT reference_type_id,reference_code
172
- # FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
173
- # FROM `reference_codes`) __active_record_temp)
174
- def cpk_mysql_subquery(stmt)
175
- arel_attributes = primary_keys.map do |key|
176
- arel_attribute(key)
177
- end.to_composite_keys
178
-
179
- subselect = arel.clone
180
- subselect.projections = arel_attributes
181
-
182
- # Materialize subquery by adding distinct
183
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
184
- subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
185
-
186
- key_name = arel_attributes.map(&:name).join(',')
187
-
188
- manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
189
-
190
- stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
191
- end
192
- end
193
- end
1
+ module ActiveRecord
2
+ class Relation
3
+ alias :initialize_without_cpk :initialize
4
+ def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
5
+ initialize_without_cpk(klass, table: table, predicate_builder: predicate_builder, values: values)
6
+ add_cpk_support if klass && klass.composite?
7
+ end
8
+
9
+ alias :initialize_copy_without_cpk :initialize_copy
10
+ def initialize_copy(other)
11
+ initialize_copy_without_cpk(other)
12
+ add_cpk_support if klass.composite?
13
+ end
14
+
15
+ def add_cpk_support
16
+ extend CompositePrimaryKeys::CompositeRelation
17
+ end
18
+
19
+ def update_all(updates)
20
+ raise ArgumentError, "Empty list of attributes to change" if updates.blank?
21
+
22
+ if eager_loading?
23
+ relation = apply_join_dependency
24
+ return relation.update_all(updates)
25
+ end
26
+
27
+ stmt = Arel::UpdateManager.new
28
+ stmt.table(arel.join_sources.empty? ? table : arel.source)
29
+ stmt.key = table[primary_key]
30
+
31
+ # CPK
32
+ if @klass.composite? && @klass.connection.visitor.compile(stmt.ast) =~ /['"]#{primary_key.to_s}['"]/
33
+ stmt = Arel::UpdateManager.new
34
+ stmt.table(arel_table)
35
+ cpk_subquery(stmt)
36
+ else
37
+ stmt.wheres = arel.constraints
38
+ end
39
+ stmt.take(arel.limit)
40
+ stmt.offset(arel.offset)
41
+ stmt.order(*arel.orders)
42
+
43
+ if updates.is_a?(Hash)
44
+ if klass.locking_enabled? &&
45
+ !updates.key?(klass.locking_column) &&
46
+ !updates.key?(klass.locking_column.to_sym)
47
+ attr = table[klass.locking_column]
48
+ updates[attr.name] = _increment_attribute(attr)
49
+ end
50
+ stmt.set _substitute_values(updates)
51
+ else
52
+ stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
53
+ end
54
+
55
+ @klass.connection.update stmt, "#{@klass} Update All"
56
+ end
57
+
58
+ def delete_all
59
+ invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
60
+ value = @values[method]
61
+ method == :distinct ? value : value&.any?
62
+ end
63
+ if invalid_methods.any?
64
+ raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
65
+ end
66
+
67
+ if eager_loading?
68
+ relation = apply_join_dependency
69
+ return relation.delete_all
70
+ end
71
+
72
+ stmt = Arel::DeleteManager.new
73
+ stmt.from(arel.join_sources.empty? ? table : arel.source)
74
+ stmt.key = table[primary_key]
75
+
76
+ # CPK
77
+ if @klass.composite? && @klass.connection.visitor.compile(stmt.ast) =~ /['"]#{primary_key.to_s}['"]/
78
+ stmt = Arel::DeleteManager.new
79
+ stmt.from(arel_table)
80
+ cpk_subquery(stmt)
81
+ else
82
+ stmt.wheres = arel.constraints
83
+ end
84
+
85
+ stmt.take(arel.limit)
86
+ stmt.offset(arel.offset)
87
+ stmt.order(*arel.orders)
88
+
89
+ affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
90
+
91
+ reset
92
+ affected
93
+ end
94
+
95
+ # CPK
96
+ def cpk_subquery(stmt)
97
+ # For update and delete statements we need a way to specify which records should
98
+ # get updated. By default, Rails creates a nested IN subquery that uses the primary
99
+ # key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple
100
+ # columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries
101
+ # and MySQL supports obfuscated IN queries. Thus we need to check the type of
102
+ # database adapter to decide how to proceed.
103
+ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
104
+ cpk_mysql_subquery(stmt)
105
+ elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
106
+ cpk_exists_subquery(stmt)
107
+ else
108
+ cpk_in_subquery(stmt)
109
+ end
110
+ end
111
+
112
+ # Used by postgresql, sqlite, mariadb and oracle. Example query:
113
+ #
114
+ # UPDATE reference_codes
115
+ # SET ...
116
+ # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
117
+ # (SELECT reference_codes.reference_type_id, reference_codes.reference_code
118
+ # FROM reference_codes)
119
+ def cpk_in_subquery(stmt)
120
+ # Setup the subquery
121
+ subquery = arel.clone
122
+ subquery.projections = primary_keys.map do |key|
123
+ arel_table[key]
124
+ end
125
+
126
+ where_fields = primary_keys.map do |key|
127
+ arel_table[key]
128
+ end
129
+ where = Arel::Nodes::Grouping.new(where_fields).in(subquery)
130
+ stmt.wheres = [where]
131
+ end
132
+
133
+ # CPK. This is an alternative to IN subqueries. It is used by sqlserver.
134
+ # Example query:
135
+ #
136
+ # UPDATE reference_codes
137
+ # SET ...
138
+ # WHERE EXISTS
139
+ # (SELECT 1
140
+ # FROM reference_codes cpk_child
141
+ # WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND
142
+ # reference_codes.reference_code = cpk_child.reference_code)
143
+ def cpk_exists_subquery(stmt)
144
+ arel_attributes = primary_keys.map do |key|
145
+ table[key]
146
+ end.to_composite_keys
147
+
148
+ # Clone the query
149
+ subselect = arel.clone
150
+
151
+ # Alias the table - we assume just one table
152
+ aliased_table = subselect.froms.first
153
+ aliased_table.table_alias = "cpk_child"
154
+
155
+ # Project - really we could just set this to "1"
156
+ subselect.projections = arel_attributes
157
+
158
+ # Setup correlation to the outer query via where clauses
159
+ primary_keys.map do |key|
160
+ outer_attribute = arel_table[key]
161
+ inner_attribute = aliased_table[key]
162
+ where = outer_attribute.eq(inner_attribute)
163
+ subselect.where(where)
164
+ end
165
+ stmt.wheres = [Arel::Nodes::Exists.new(subselect)]
166
+ end
167
+
168
+ # CPK. This is the old way CPK created subqueries and is used by MySql.
169
+ # MySQL does not support referencing the same table that is being UPDATEd or
170
+ # DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
171
+ #
172
+ # UPDATE `reference_codes`
173
+ # SET ...
174
+ # WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
175
+ # (SELECT reference_type_id,reference_code
176
+ # FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code`
177
+ # FROM `reference_codes`) __active_record_temp)
178
+ def cpk_mysql_subquery(stmt)
179
+ arel_attributes = primary_keys.map do |key|
180
+ table[key]
181
+ end.to_composite_keys
182
+
183
+ subselect = arel.clone
184
+ subselect.projections = arel_attributes
185
+
186
+ # Materialize subquery by adding distinct
187
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
188
+ subselect.distinct unless arel.limit || arel.offset || arel.orders.any?
189
+
190
+ key_name = arel_attributes.map(&:name).join(',')
191
+
192
+ manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
193
+
194
+ stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)]
195
+ end
196
+ end
197
+ end
@@ -1,43 +1,43 @@
1
- module ActiveRecord
2
- module Sanitization
3
- module ClassMethods
4
- # def expand_hash_conditions_for_aggregates(attrs)
5
- # expanded_attrs = {}
6
- # attrs.each do |attr, value|
7
- # # CPK
8
- # # if aggregation = reflect_on_aggregation(attr.to_sym)
9
- # if attr.is_a?(CompositePrimaryKeys::CompositeKeys)
10
- # value = value.split('/') if value.is_a?(String)
11
- # attr.each_with_index do |key,i|
12
- # expanded_attrs[key] = value.respond_to?(:flatten) ? value.flatten[i] : value
13
- # end
14
- # elsif aggregation = reflect_on_aggregation(attr.to_sym)
15
- # mapping = aggregation.mapping
16
- # mapping.each do |field_attr, aggregate_attr|
17
- # if mapping.size == 1 && !value.respond_to?(aggregate_attr)
18
- # expanded_attrs[field_attr] = value
19
- # else
20
- # expanded_attrs[field_attr] = value.send(aggregate_attr)
21
- # end
22
- # end
23
- # else
24
- # expanded_attrs[attr] = value
25
- # end
26
- # end
27
- # expanded_attrs
28
- # end
29
- #
30
- # def quoted_id
31
- # # CPK
32
- # # self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
33
- # if self.composite?
34
- # [self.class.primary_keys, ids].transpose.map { |attr_name,id|
35
- # self.class.quote_value(@attributes[attr_name].value_for_database)
36
- # }
37
- # else
38
- # self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
39
- # end
40
- # end
41
- end
42
- end
1
+ module ActiveRecord
2
+ module Sanitization
3
+ module ClassMethods
4
+ # def expand_hash_conditions_for_aggregates(attrs)
5
+ # expanded_attrs = {}
6
+ # attrs.each do |attr, value|
7
+ # # CPK
8
+ # # if aggregation = reflect_on_aggregation(attr.to_sym)
9
+ # if attr.is_a?(CompositePrimaryKeys::CompositeKeys)
10
+ # value = value.split('/') if value.is_a?(String)
11
+ # attr.each_with_index do |key,i|
12
+ # expanded_attrs[key] = value.respond_to?(:flatten) ? value.flatten[i] : value
13
+ # end
14
+ # elsif aggregation = reflect_on_aggregation(attr.to_sym)
15
+ # mapping = aggregation.mapping
16
+ # mapping.each do |field_attr, aggregate_attr|
17
+ # if mapping.size == 1 && !value.respond_to?(aggregate_attr)
18
+ # expanded_attrs[field_attr] = value
19
+ # else
20
+ # expanded_attrs[field_attr] = value.send(aggregate_attr)
21
+ # end
22
+ # end
23
+ # else
24
+ # expanded_attrs[attr] = value
25
+ # end
26
+ # end
27
+ # expanded_attrs
28
+ # end
29
+ #
30
+ # def quoted_id
31
+ # # CPK
32
+ # # self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
33
+ # if self.composite?
34
+ # [self.class.primary_keys, ids].transpose.map { |attr_name,id|
35
+ # self.class.quote_value(@attributes[attr_name].value_for_database)
36
+ # }
37
+ # else
38
+ # self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
39
+ # end
40
+ # end
41
+ end
42
+ end
43
43
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class TableMetadata # :nodoc:
5
+ def associated_with?(table_name)
6
+ # CPK
7
+ # klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.singularize)
8
+ klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.to_s.singularize)
9
+ end
10
+ end
11
+ end
@@ -1,34 +1,34 @@
1
- module ActiveRecord
2
- module Transactions
3
- # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
4
- def restore_transaction_record_state(force_restore_state = false)
5
- if restore_state = @_start_transaction_state
6
- if force_restore_state || restore_state[:level] <= 1
7
- @new_record = restore_state[:new_record]
8
- @destroyed = restore_state[:destroyed]
9
- @attributes = restore_state[:attributes].map do |attr|
10
- value = @attributes.fetch_value(attr.name)
11
- attr = attr.with_value_from_user(value) if attr.value != value
12
- attr
13
- end
14
- @mutations_from_database = nil
15
- @mutations_before_last_save = nil
16
-
17
- # CPK
18
- if self.composite?
19
- values = @primary_key.map {|attribute| @attributes.fetch_value(attribute)}
20
- restore_id = restore_state[:id]
21
- if values != restore_id
22
- @primary_key.each_with_index do |attribute, i|
23
- @attributes.write_from_user(attribute, restore_id[i])
24
- end
25
- end
26
- elsif @attributes.fetch_value(@primary_key) != restore_state[:id]
27
- @attributes.write_from_user(@primary_key, restore_state[:id])
28
- end
29
- freeze if restore_state[:frozen?]
30
- end
31
- end
32
- end
33
- end
34
- end
1
+ module ActiveRecord
2
+ module Transactions
3
+ # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
4
+ def restore_transaction_record_state(force_restore_state = false)
5
+ if restore_state = @_start_transaction_state
6
+ if force_restore_state || restore_state[:level] <= 1
7
+ @new_record = restore_state[:new_record]
8
+ @destroyed = restore_state[:destroyed]
9
+ @attributes = restore_state[:attributes].map do |attr|
10
+ value = @attributes.fetch_value(attr.name)
11
+ attr = attr.with_value_from_user(value) if attr.value != value
12
+ attr
13
+ end
14
+ @mutations_from_database = nil
15
+ @mutations_before_last_save = nil
16
+
17
+ # CPK
18
+ if self.composite?
19
+ values = @primary_key.map {|attribute| @attributes.fetch_value(attribute)}
20
+ restore_id = restore_state[:id]
21
+ if values != restore_id
22
+ @primary_key.each_with_index do |attribute, i|
23
+ @attributes.write_from_user(attribute, restore_id[i])
24
+ end
25
+ end
26
+ elsif @attributes.fetch_value(@primary_key) != restore_state[:id]
27
+ @attributes.write_from_user(@primary_key, restore_state[:id])
28
+ end
29
+ freeze if restore_state[:frozen?]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,32 +1,32 @@
1
- module ActiveRecord
2
- module Validations
3
- class UniquenessValidator
4
- def validate_each(record, attribute, value)
5
- finder_class = find_finder_class_for(record)
6
- value = map_enum_attribute(finder_class, attribute, value)
7
-
8
- relation = build_relation(finder_class, attribute, value)
9
- if record.persisted?
10
- # CPK
11
- if finder_class.primary_key.is_a?(Array)
12
- predicate = finder_class.cpk_id_predicate(finder_class.arel_table, finder_class.primary_key, record.id_in_database || record.id)
13
- relation = relation.where.not(predicate)
14
- elsif finder_class.primary_key
15
- relation = relation.where.not(finder_class.primary_key => record.id_in_database)
16
- else
17
- raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
18
- end
19
- end
20
- relation = scope_relation(record, relation)
21
- relation = relation.merge(options[:conditions]) if options[:conditions]
22
-
23
- if relation.exists?
24
- error_options = options.except(:case_sensitive, :scope, :conditions)
25
- error_options[:value] = value
26
-
27
- record.errors.add(attribute, :taken, **error_options)
28
- end
29
- end
30
- end
31
- end
1
+ module ActiveRecord
2
+ module Validations
3
+ class UniquenessValidator
4
+ def validate_each(record, attribute, value)
5
+ finder_class = find_finder_class_for(record)
6
+ value = map_enum_attribute(finder_class, attribute, value)
7
+
8
+ relation = build_relation(finder_class, attribute, value)
9
+ if record.persisted?
10
+ # CPK
11
+ if finder_class.primary_key.is_a?(Array)
12
+ predicate = finder_class.cpk_id_predicate(finder_class.arel_table, finder_class.primary_key, record.id_in_database || record.id)
13
+ relation = relation.where.not(predicate)
14
+ elsif finder_class.primary_key
15
+ relation = relation.where.not(finder_class.primary_key => record.id_in_database)
16
+ else
17
+ raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
18
+ end
19
+ end
20
+ relation = scope_relation(record, relation)
21
+ relation = relation.merge(options[:conditions]) if options[:conditions]
22
+
23
+ if relation.exists?
24
+ error_options = options.except(:case_sensitive, :scope, :conditions)
25
+ error_options[:value] = value
26
+
27
+ record.errors.add(attribute, :taken, **error_options)
28
+ end
29
+ end
30
+ end
31
+ end
32
32
  end
@@ -1,8 +1,8 @@
1
- module CompositePrimaryKeys
2
- module VERSION
3
- MAJOR = 12
4
- MINOR = 0
5
- TINY = 9
6
- STRING = [MAJOR, MINOR, TINY].join('.')
7
- end
8
- end
1
+ module CompositePrimaryKeys
2
+ module VERSION
3
+ MAJOR = 14
4
+ MINOR = 0
5
+ TINY = 1
6
+ STRING = [MAJOR, MINOR, TINY].join('.')
7
+ end
8
+ end