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
@@ -1,56 +1,64 @@
1
1
  module ActiveRecord
2
2
  module Persistence
3
- def relation_for_destroy
4
- # CPK
5
- if self.composite?
6
- relation = self.class.unscoped
7
-
8
- Array(self.class.primary_key).each_with_index do |key, index|
9
- column = self.class.columns_hash[key]
10
- substitute = self.class.connection.substitute_at(column, index)
11
- relation = relation.where(self.class.arel_table[key].eq(substitute))
12
- relation.bind_values += [[column, self[key]]]
3
+ silence_warnings do
4
+ def relation_for_destroy
5
+ # CPK
6
+ if self.composite?
7
+ relation = self.class.unscoped
8
+
9
+ Array(self.class.primary_key).each do |key|
10
+ column = self.class.arel_table[key]
11
+ value = self[key]
12
+ relation = relation.where(column.eq(value))
13
+ end
14
+
15
+ relation
16
+ else
17
+ self.class.unscoped.where(self.class.primary_key => id)
13
18
  end
19
+ end
14
20
 
15
- relation
16
- else
17
- pk = self.class.primary_key
18
- column = self.class.columns_hash[pk]
19
- substitute = self.class.connection.substitute_at(column, 0)
21
+ def touch(*names, time: nil)
22
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
20
23
 
21
- relation = self.class.unscoped.where(
22
- self.class.arel_table[pk].eq(substitute))
24
+ time ||= current_time_from_proper_timezone
25
+ attributes = timestamp_attributes_for_update_in_model
26
+ attributes.concat(names)
23
27
 
24
- relation.bind_values = [[column, id]]
25
- relation
26
- end
27
- end
28
+ unless attributes.empty?
29
+ changes = {}
28
30
 
29
- def touch(*names)
30
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
31
+ attributes.each do |column|
32
+ column = column.to_s
33
+ changes[column] = write_attribute(column, time)
34
+ end
31
35
 
32
- attributes = timestamp_attributes_for_update_in_model
33
- attributes.concat(names)
36
+ clear_attribute_changes(changes.keys)
37
+ primary_key = self.class.primary_key
38
+ scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
34
39
 
35
- unless attributes.empty?
36
- current_time = current_time_from_proper_timezone
37
- changes = {}
40
+ if locking_enabled?
41
+ locking_column = self.class.locking_column
42
+ scope = scope.where(locking_column => _read_attribute(locking_column))
43
+ changes[locking_column] = increment_lock
44
+ end
38
45
 
39
- attributes.each do |column|
40
- column = column.to_s
41
- changes[column] = write_attribute(column, current_time)
42
- end
46
+ # CPK
47
+ if composite?
48
+ primary_key_predicate = self.class.unscoped.cpk_id_predicate(self.class.arel_table, Array(primary_key), Array(id))
49
+ scope = self.class.unscoped.where(primary_key_predicate)
50
+ end
43
51
 
44
- changes[self.class.locking_column] = increment_lock if locking_enabled?
52
+ result = scope.update_all(changes) == 1
45
53
 
46
- clear_attribute_changes(changes.keys)
47
- primary_key = self.class.primary_key
48
- # CPK
49
- #self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
50
- primary_key_predicate = self.class.unscoped.cpk_id_predicate(self.class.arel_table, Array(primary_key), Array(id))
51
- self.class.unscoped.where(primary_key_predicate).update_all(changes) == 1
52
- else
53
- true
54
+ if !result && locking_enabled?
55
+ raise ActiveRecord::StaleObjectError.new(self, "touch")
56
+ end
57
+
58
+ result
59
+ else
60
+ true
61
+ end
54
62
  end
55
63
  end
56
64
  end
@@ -1,46 +1,48 @@
1
1
  module CompositePrimaryKeys
2
2
  module ActiveRecord
3
3
  module Batches
4
- def find_in_batches(options = {})
5
- options.assert_valid_keys(:start, :batch_size)
6
-
4
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
7
5
  relation = self
8
- start = options[:start]
9
- batch_size = options[:batch_size] || 1000
10
-
11
6
  unless block_given?
12
- return to_enum(:find_in_batches, options) do
13
- total = start ? where(table[primary_key].gteq(start)).size : size
14
- (total - 1).div(batch_size) + 1
15
- end
7
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
16
8
  end
17
9
 
18
- if logger && (arel.orders.present? || arel.taken.present?)
19
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
10
+ if arel.orders.present? || arel.taken.present?
11
+ act_on_order_or_limit_ignored(error_on_ignore)
20
12
  end
21
13
 
22
- relation = relation.reorder(batch_order).limit(batch_size)
14
+ relation = relation.reorder(batch_order).limit(of)
15
+ relation = apply_limits(relation, start, finish)
16
+ batch_relation = relation
23
17
 
24
- # CPK
25
- # records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
26
- records = if start
27
- self.primary_key.reduce(relation) do |rel, key|
28
- rel.where(table[key].gteq(start))
29
- end
30
- else
31
- relation.to_a
32
- end
18
+ loop do
19
+ if load
20
+ records = batch_relation.records
21
+ ids = records.map(&:id)
22
+ # CPK
23
+ # yielded_relation = self.where(primary_key => ids)
24
+ yielded_relation = self.where(cpk_in_predicate(table, primary_keys, ids))
25
+ yielded_relation.load_records(records)
26
+ else
27
+ # CPK
28
+ # ids = batch_relation.pluck(primary_key)
29
+ ids = batch_relation.pluck(*Array(primary_keys))
30
+ # CPK
31
+ # yielded_relation = self.where(primary_key => ids)
32
+ yielded_relation = self.where(cpk_in_predicate(table, primary_keys, ids))
33
+ end
33
34
 
34
- while records.any?
35
- records_size = records.size
36
- primary_key_offset = records.last.id
37
- raise "Primary key not included in the custom select clause" unless primary_key_offset
35
+ break if ids.empty?
38
36
 
39
- yield records
37
+ primary_key_offset = ids.last
38
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
40
39
 
41
- break if records_size < batch_size
40
+ yield yielded_relation
42
41
 
43
- if primary_key_offset
42
+ break if ids.length < of
43
+ # CPK
44
+ # batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
45
+ batch_relation = if composite?
44
46
  # CPK
45
47
  # Lexicographically select records
46
48
  #
@@ -57,15 +59,16 @@ module CompositePrimaryKeys
57
59
 
58
60
  Arel::Nodes::Grouping.new(and_clause)
59
61
  end.reduce(:or)
62
+ relation.where(query)
63
+ else
64
+ relation.where(arel_attribute(primary_key).gt(primary_key_offset))
60
65
  end
61
-
62
- records = relation.where(query)
63
66
  end
64
67
  end
65
68
 
66
69
  private
67
70
 
68
- # Helper method to collect prefixes of an array:
71
+ # CPK Helper method to collect prefixes of an array:
69
72
  # prefixes([:a, :b, :c]) => [[:a], [:a, :b], [:a, :b, :c]]
70
73
  #
71
74
  def prefixes(ary)
@@ -14,14 +14,12 @@ module CompositePrimaryKeys
14
14
  end
15
15
  end
16
16
 
17
- def execute_simple_calculation(operation, column_name, distinct)
18
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
17
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
18
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
19
19
  relation = unscope(:order)
20
20
 
21
21
  column_alias = column_name
22
22
 
23
- bind_values = nil
24
-
25
23
  # CPK
26
24
  # if operation == "count" && (relation.limit_value || relation.offset_value)
27
25
  if operation == "count"
@@ -29,7 +27,6 @@ module CompositePrimaryKeys
29
27
  return 0 if relation.limit_value == 0
30
28
 
31
29
  query_builder = build_count_subquery(relation, column_name, distinct)
32
- bind_values = query_builder.bind_values + relation.bind_values
33
30
  else
34
31
  column = aggregate_column(column_name)
35
32
 
@@ -40,10 +37,9 @@ module CompositePrimaryKeys
40
37
  relation.select_values = [select_value]
41
38
 
42
39
  query_builder = relation.arel
43
- bind_values = query_builder.bind_values + relation.bind_values
44
40
  end
45
41
 
46
- result = @klass.connection.select_all(query_builder, nil, bind_values)
42
+ result = @klass.connection.select_all(query_builder, nil, bound_attributes)
47
43
  row = result.first
48
44
  value = row && row.values.first
49
45
  column = result.column_types.fetch(column_alias) do
@@ -30,17 +30,17 @@ module CompositePrimaryKeys
30
30
  relation = relation.except(:select).select(values).distinct!
31
31
  arel = relation.arel
32
32
 
33
- id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
33
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
34
34
 
35
35
  # CPK
36
36
  #id_rows.map {|row| row[primary_key]}
37
37
  id_rows.map {|row| row.values}
38
38
  end
39
-
39
+
40
40
  def exists?(conditions = :none)
41
41
  if ::ActiveRecord::Base === conditions
42
42
  conditions = conditions.id
43
- ::ActiveSupport::Deprecation.warn(<<-MSG.squish)
43
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
44
44
  You are passing an instance of ActiveRecord::Base to `exists?`.
45
45
  Please pass the id of the object by calling `.id`
46
46
  MSG
@@ -53,30 +53,28 @@ module CompositePrimaryKeys
53
53
 
54
54
  relation = relation.except(:select, :order).select(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit(1)
55
55
 
56
- # case conditions
57
- # when Array, Hash
58
- # relation = relation.where(conditions)
59
- # else
60
- # unless conditions == :none
61
- # relation = relation.where(primary_key => conditions)
62
- # end
63
- # end
64
56
  case conditions
65
- when CompositePrimaryKeys::CompositeKeys
66
- relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
67
- when Array
68
- pk_length = @klass.primary_keys.length
69
-
70
- if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
71
- return self.exists?(conditions.to_composite_keys)
72
- else # Assume that conditions contains where relation
57
+ # CPK
58
+ when CompositePrimaryKeys::CompositeKeys
59
+ relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
60
+ # CPK
61
+ when Array
62
+ pk_length = @klass.primary_keys.length
63
+
64
+ if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
65
+ return self.exists?(conditions.to_composite_keys)
66
+ else # Assume that conditions contains where relation
67
+ relation = relation.where(conditions)
68
+ end
69
+ when Array, Hash
73
70
  relation = relation.where(conditions)
74
- end
75
- when Hash
76
- relation = relation.where(conditions)
71
+ else
72
+ unless conditions == :none
73
+ relation = relation.where(primary_key => conditions)
74
+ end
77
75
  end
78
76
 
79
- connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
77
+ connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
80
78
  end
81
79
 
82
80
  def find_with_ids(*ids)
@@ -86,7 +84,6 @@ module CompositePrimaryKeys
86
84
  # expects_array = ids.first.kind_of?(Array)
87
85
  ids = CompositePrimaryKeys.normalize(ids)
88
86
  expects_array = ids.flatten != ids.flatten(1)
89
-
90
87
  return ids.first if expects_array && ids.first.empty?
91
88
 
92
89
  # CPK
@@ -106,6 +103,59 @@ module CompositePrimaryKeys
106
103
  raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
107
104
  end
108
105
 
106
+ def last(limit = nil)
107
+ return find_last(limit) if loaded? || limit_value
108
+
109
+ result = limit(limit || 1)
110
+ # CPK
111
+ # result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
112
+ if order_values.empty? && primary_key
113
+ if composite?
114
+ result.order!(primary_keys.map { |pk| arel_attribute(pk).asc })
115
+ elsif
116
+ result.order!(arel_attribute(primary_key))
117
+ end
118
+ end
119
+
120
+ result = result.reverse_order!
121
+
122
+ limit ? result.reverse : result.first
123
+ rescue ::ActiveRecord::IrreversibleOrderError
124
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
125
+ Finding a last element by loading the relation when SQL ORDER
126
+ can not be reversed is deprecated.
127
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
128
+ Please call `to_a.last` if you still want to load the relation.
129
+ WARNING
130
+ find_last(limit)
131
+ end
132
+
133
+
134
+ def find_nth_with_limit(index, limit)
135
+ # TODO: once the offset argument is removed from find_nth,
136
+ # find_nth_with_limit_and_offset can be merged into this method
137
+ #
138
+ # CPK
139
+ # relation = if order_values.empty? && primary_key
140
+ # order(arel_attribute(primary_key).asc)
141
+ # else
142
+ # self
143
+ # end
144
+
145
+ relation = self
146
+
147
+ if order_values.empty? && primary_key
148
+ if composite?
149
+ relation = relation.order(primary_keys.map { |pk| arel_attribute(pk).asc })
150
+ elsif
151
+ relation = relation.order(arel_attribute(primary_key).asc)
152
+ end
153
+ end
154
+
155
+ relation = relation.offset(index) unless index.zero?
156
+ relation.limit(limit).to_a
157
+ end
158
+
109
159
  def find_one(id)
110
160
  # CPK
111
161
  # if ActiveRecord::Base === id
@@ -117,14 +167,9 @@ module CompositePrimaryKeys
117
167
  MSG
118
168
  end
119
169
 
120
- relation = self
121
- values = primary_keys.each_with_index.map do |primary_key, i|
122
- column = columns_hash[primary_key]
123
- relation.bind_values += [[column, id[i]]]
124
- connection.substitute_at(column, bind_values.length - 1)
125
- end
126
-
127
- relation = relation.where(cpk_id_predicate(table, primary_keys, values))
170
+ # CPK
171
+ #relation = where(primary_key => id)
172
+ relation = where(cpk_id_predicate(table, primary_keys, id))
128
173
  record = relation.take
129
174
 
130
175
  raise_record_not_found_exception!(id, 0, 1) unless record
@@ -134,33 +179,23 @@ module CompositePrimaryKeys
134
179
 
135
180
  def find_some(ids)
136
181
  # CPK
137
- # result = where(table[primary_key].in(ids)).to_a
138
-
139
- result = ids.map do |cpk_ids|
140
- cpk_ids = if cpk_ids.length == 1
141
- cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
182
+ if composite?
183
+ ids = if ids.length == 1
184
+ ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
142
185
  else
143
- cpk_ids.to_composite_keys
144
- end
145
-
146
- unless cpk_ids.length == @klass.primary_keys.length
147
- raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
148
- end
149
-
150
- new_relation = clone
151
- [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
152
- new_relation = new_relation.where(key => id)
186
+ ids.to_composite_keys
153
187
  end
188
+ end
154
189
 
155
- records = new_relation.to_a
190
+ return find_some_ordered(ids) unless order_values.present?
156
191
 
157
- if records.empty?
158
- conditions = new_relation.arel.where_sql
159
- raise(::ActiveRecord::RecordNotFound,
160
- "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
161
- end
162
- records
163
- end.flatten
192
+ # CPK
193
+ # result = where(primary_key => ids).to_a
194
+ result = if composite?
195
+ result = where(cpk_in_predicate(table, primary_keys, ids)).to_a
196
+ else
197
+ result = where(primary_key => ids).to_a
198
+ end
164
199
 
165
200
  expected_size =
166
201
  if limit_value && ids.size > limit_value
@@ -180,6 +215,38 @@ module CompositePrimaryKeys
180
215
  raise_record_not_found_exception!(ids, result.size, expected_size)
181
216
  end
182
217
  end
218
+
219
+ def find_some_ordered(ids)
220
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
221
+
222
+ # CPK
223
+ # result = except(:limit, :offset).where(primary_key => ids).records
224
+ result = if composite?
225
+ except(:limit, :offset).where(cpk_in_predicate(table, primary_keys, ids)).records
226
+ else
227
+ except(:limit, :offset).where(primary_key => ids).records
228
+ end
229
+
230
+ if result.size == ids.size
231
+ pk_type = @klass.type_for_attribute(primary_key)
232
+
233
+ records_by_id = result.index_by(&:id)
234
+ # CPK
235
+ # ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
236
+ if composite?
237
+ ids.map do |id|
238
+ typecasted_id = primary_keys.zip(id).map do |col, val|
239
+ @klass.type_for_attribute(col).cast(val)
240
+ end
241
+ records_by_id.fetch(typecasted_id)
242
+ end
243
+ else
244
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
245
+ end
246
+ else
247
+ raise_record_not_found_exception!(ids, result.size, ids.size)
248
+ end
249
+ end
183
250
  end
184
251
  end
185
252
  end
@@ -1,37 +1,26 @@
1
1
  module ActiveRecord
2
2
  class PredicateBuilder
3
- def self.expand(klass, table, column, value)
4
- queries = []
5
-
6
- # Find the foreign key when using queries such as:
7
- # Post.where(author: author)
8
- #
9
- # For polymorphic relationships, find the foreign key and type:
10
- # PriceEstimate.where(estimate_of: treasure)
11
- if klass && reflection = klass._reflect_on_association(column)
12
- base_class = polymorphic_base_class_from_value(value)
13
-
14
- if reflection.polymorphic? && base_class
15
- queries << build(table[reflection.foreign_type], base_class)
16
- end
17
-
18
- column = reflection.foreign_key
3
+ silence_warnings do
4
+ def expand(column, value)
5
+ # Find the foreign key when using queries such as:
6
+ # Post.where(author: author)
7
+ #
8
+ # For polymorphic relationships, find the foreign key and type:
9
+ # PriceEstimate.where(estimate_of: treasure)
19
10
 
20
11
  # CPK
21
- # if base_class
22
- if base_class && !(Base === value && value.composite?)
23
- primary_key = reflection.association_primary_key(base_class)
24
- value = convert_value_to_association_ids(value, primary_key)
12
+ if Base === Array(value).first && Array(value).first.composite? && reflection = table.associated_with?(column)
13
+ columns = reflection.foreign_key
14
+ values = Array(value).map{|v| columns.map{|c| v.public_send(c) }}
15
+ cpk_predicate_builder = Class.new.extend(CompositePrimaryKeys::Predicates)
16
+ predicate = cpk_predicate_builder.cpk_in_predicate(table.send(:arel_table), columns, values)
17
+ return predicate
18
+ else
19
+ # Original code
20
+ value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
21
+ build(table.arel_attribute(column), value)
25
22
  end
26
23
  end
27
-
28
- #CPK
29
- if Base === value && value.composite?
30
- queries << cpk_id_predicate(table, column, value.id)
31
- else
32
- queries << build(table[column], value)
33
- end
34
- queries
35
24
  end
36
25
  end
37
- end
26
+ end
@@ -1,41 +1,30 @@
1
- module CompositePrimaryKeys::ActiveRecord::QueryMethods
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module QueryMethods
4
+ def reverse_sql_order(order_query)
5
+ # CPK
6
+ # order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
2
7
 
3
- def reverse_sql_order(order_query)
4
- # CPK
5
- # order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
8
+ # break apart CPKs
9
+ order_query = primary_key.map do |key|
10
+ "#{quoted_table_name}.#{connection.quote_column_name(key)} ASC"
11
+ end if order_query.empty?
6
12
 
7
- # break apart CPKs
8
- order_query = primary_key.map do |key|
9
- "#{quoted_table_name}.#{connection.quote_column_name(key)} ASC"
10
- end if order_query.empty?
11
-
12
- order_query.map do |o|
13
- case o
14
- when Arel::Nodes::Ordering
15
- o.reverse
16
- when String, Symbol
17
- o.to_s.split(',').collect do |s|
18
- s.strip!
19
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
20
- end
21
- else
22
- o
23
- end
24
- end.flatten
25
- end
26
-
27
-
28
- def order(*args)
29
- args.map! do |arg|
30
- if arg.is_a?(Arel::Nodes::Ordering) && arg.expr.name.is_a?(Array)
31
- arg = arg.expr.name.map do |key|
32
- cloned_node = arg.clone
33
- cloned_node.expr.name = key
34
- cloned_node
35
- end
13
+ order_query.map do |o|
14
+ case o
15
+ when Arel::Nodes::Ordering
16
+ o.reverse
17
+ when String, Symbol
18
+ o.to_s.split(',').collect do |s|
19
+ s.strip!
20
+ s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
21
+ end
22
+ else
23
+ o
24
+ end
25
+ end.flatten
36
26
  end
37
- arg
38
- end if composite?
39
- super(*args)
27
+ end
40
28
  end
41
29
  end
30
+
@@ -0,0 +1,33 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class WhereClause
4
+ silence_warnings do
5
+ def to_h(table_name = nil)
6
+ equalities = predicates.grep(Arel::Nodes::Equality)
7
+
8
+ # CPK Adds this line, because ours are coming in with AND->{EQUALITY, EQUALITY}
9
+ equalities = predicates.grep(Arel::Nodes::And).map(&:children).flatten.grep(Arel::Nodes::Equality) if equalities.empty?
10
+
11
+ if table_name
12
+ equalities = equalities.select do |node|
13
+ node.left.relation.name == table_name
14
+ end
15
+ end
16
+
17
+ binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
18
+
19
+ equalities.map do |node|
20
+ name = node.left.name
21
+ [name, binds.fetch(name.to_s) {
22
+ case node.right
23
+ when Array then node.right.map(&:val)
24
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
25
+ node.right.val
26
+ end
27
+ }]
28
+ end.to_h
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end