composite_primary_keys 7.0.13 → 7.0.14

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +615 -608
  3. data/lib/composite_primary_keys.rb +110 -110
  4. data/lib/composite_primary_keys/associations/association.rb +23 -23
  5. data/lib/composite_primary_keys/associations/association_scope.rb +77 -77
  6. data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +59 -59
  7. data/lib/composite_primary_keys/associations/has_many_association.rb +56 -56
  8. data/lib/composite_primary_keys/associations/join_dependency.rb +89 -89
  9. data/lib/composite_primary_keys/associations/join_dependency/join_part.rb +38 -38
  10. data/lib/composite_primary_keys/associations/preloader/association.rb +78 -78
  11. data/lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb +46 -46
  12. data/lib/composite_primary_keys/attribute_methods/dirty.rb +26 -26
  13. data/lib/composite_primary_keys/attribute_methods/read.rb +34 -34
  14. data/lib/composite_primary_keys/attribute_methods/write.rb +36 -36
  15. data/lib/composite_primary_keys/base.rb +0 -6
  16. data/lib/composite_primary_keys/composite_arrays.rb +30 -30
  17. data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +4 -2
  18. data/lib/composite_primary_keys/connection_adapters/sqlserver_adapter.rb +17 -0
  19. data/lib/composite_primary_keys/core.rb +47 -47
  20. data/lib/composite_primary_keys/persistence.rb +60 -60
  21. data/lib/composite_primary_keys/relation.rb +56 -56
  22. data/lib/composite_primary_keys/relation/calculations.rb +75 -65
  23. data/lib/composite_primary_keys/relation/finder_methods.rb +196 -196
  24. data/lib/composite_primary_keys/sanitization.rb +52 -52
  25. data/lib/composite_primary_keys/validations/uniqueness.rb +37 -39
  26. data/lib/composite_primary_keys/version.rb +8 -8
  27. data/tasks/databases/sqlserver.rake +40 -27
  28. data/test/connections/databases.example.yml +18 -18
  29. data/test/connections/native_sqlserver/connection.rb +14 -11
  30. data/test/fixtures/db_definitions/mysql.sql +208 -208
  31. data/test/fixtures/db_definitions/postgresql.sql +210 -210
  32. data/test/fixtures/db_definitions/sqlite.sql +197 -197
  33. data/test/fixtures/db_definitions/sqlserver.drop.sql +94 -91
  34. data/test/fixtures/db_definitions/sqlserver.sql +232 -226
  35. data/test/fixtures/employee.rb +5 -5
  36. data/test/test_associations.rb +275 -275
  37. data/test/test_attributes.rb +60 -60
  38. data/test/test_create.rb +112 -112
  39. data/test/test_delete.rb +152 -148
  40. data/test/test_delete_all.rb +21 -21
  41. data/test/test_enum.rb +20 -20
  42. data/test/test_equal.rb +1 -1
  43. data/test/test_tutorial_example.rb +21 -21
  44. metadata +3 -2
@@ -1,61 +1,61 @@
1
- module ActiveRecord
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]]]
13
- end
14
-
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)
20
-
21
- relation = self.class.unscoped.where(
22
- self.class.arel_table[pk].eq(substitute))
23
-
24
- relation.bind_values = [[column, id]]
25
- relation
26
- end
27
- end
28
-
29
- def touch(name = nil)
30
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
31
-
32
- attributes = timestamp_attributes_for_update_in_model
33
- attributes << name if name
34
-
35
- unless attributes.empty?
36
- current_time = current_time_from_proper_timezone
37
- changes = {}
38
-
39
- attributes.each do |column|
40
- column = column.to_s
41
- changes[column] = write_attribute(column, current_time)
42
- end
43
-
44
- changes[self.class.locking_column] = increment_lock if locking_enabled?
45
-
46
- changed_attributes.except!(*changes.keys)
47
-
48
- relation = self.class.send(:relation)
49
- arel_table = self.class.arel_table
50
- primary_key = self.class.primary_key
51
-
52
- # CPK
53
- #self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
54
- primary_key_predicate = relation.cpk_id_predicate(arel_table, Array(primary_key), Array(id))
55
- self.class.unscoped.where(primary_key_predicate).update_all(changes) == 1
56
- else
57
- true
58
- end
59
- end
60
- end
1
+ module ActiveRecord
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]]]
13
+ end
14
+
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)
20
+
21
+ relation = self.class.unscoped.where(
22
+ self.class.arel_table[pk].eq(substitute))
23
+
24
+ relation.bind_values = [[column, id]]
25
+ relation
26
+ end
27
+ end
28
+
29
+ def touch(name = nil)
30
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
31
+
32
+ attributes = timestamp_attributes_for_update_in_model
33
+ attributes << name if name
34
+
35
+ unless attributes.empty?
36
+ current_time = current_time_from_proper_timezone
37
+ changes = {}
38
+
39
+ attributes.each do |column|
40
+ column = column.to_s
41
+ changes[column] = write_attribute(column, current_time)
42
+ end
43
+
44
+ changes[self.class.locking_column] = increment_lock if locking_enabled?
45
+
46
+ changed_attributes.except!(*changes.keys)
47
+
48
+ relation = self.class.send(:relation)
49
+ arel_table = self.class.arel_table
50
+ primary_key = self.class.primary_key
51
+
52
+ # CPK
53
+ #self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
54
+ primary_key_predicate = relation.cpk_id_predicate(arel_table, Array(primary_key), Array(id))
55
+ self.class.unscoped.where(primary_key_predicate).update_all(changes) == 1
56
+ else
57
+ true
58
+ end
59
+ end
60
+ end
61
61
  end
@@ -1,56 +1,56 @@
1
- module ActiveRecord
2
- class Relation
3
- alias :initialize_without_cpk :initialize
4
- def initialize(klass, table, values = {})
5
- initialize_without_cpk(klass, table, 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
- # CPK adds this so that it finds the Equality nodes beneath the And node:
20
- # equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
21
- # node.left.relation.name == table_name
22
- # }
23
- alias :where_values_hash_without_cpk :where_values_hash
24
- def where_values_hash(relation_table_name = table_name)
25
- nodes_from_and = where_values.grep(Arel::Nodes::And).map {|and_node| and_node.children.grep(Arel::Nodes::Equality) }.flatten
26
-
27
- equalities = (nodes_from_and + where_values.grep(Arel::Nodes::Equality)).find_all { |node|
28
- node.left.relation.name == relation_table_name
29
- }
30
-
31
- binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
32
-
33
- Hash[equalities.map { |where|
34
- name = where.left.name
35
- [name, binds.fetch(name.to_s) { where.right }]
36
- }]
37
- end
38
-
39
- def _update_record(values, id, id_was)
40
- substitutes, binds = substitute_values values
41
-
42
- # CPK
43
- um = if self.composite?
44
- relation = @klass.unscoped.where(cpk_id_predicate(@klass.arel_table, @klass.primary_key, id_was || id))
45
- relation.arel.compile_update(substitutes, @klass.primary_key)
46
- else
47
- @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
48
- end
49
-
50
- @klass.connection.update(
51
- um,
52
- 'SQL',
53
- binds)
54
- end
55
- end
56
- end
1
+ module ActiveRecord
2
+ class Relation
3
+ alias :initialize_without_cpk :initialize
4
+ def initialize(klass, table, values = {})
5
+ initialize_without_cpk(klass, table, 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
+ # CPK adds this so that it finds the Equality nodes beneath the And node:
20
+ # equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
21
+ # node.left.relation.name == table_name
22
+ # }
23
+ alias :where_values_hash_without_cpk :where_values_hash
24
+ def where_values_hash(relation_table_name = table_name)
25
+ nodes_from_and = where_values.grep(Arel::Nodes::And).map {|and_node| and_node.children.grep(Arel::Nodes::Equality) }.flatten
26
+
27
+ equalities = (nodes_from_and + where_values.grep(Arel::Nodes::Equality)).find_all { |node|
28
+ node.left.relation.name == relation_table_name
29
+ }
30
+
31
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
32
+
33
+ Hash[equalities.map { |where|
34
+ name = where.left.name
35
+ [name, binds.fetch(name.to_s) { where.right }]
36
+ }]
37
+ end
38
+
39
+ def _update_record(values, id, id_was)
40
+ substitutes, binds = substitute_values values
41
+
42
+ # CPK
43
+ um = if self.composite?
44
+ relation = @klass.unscoped.where(cpk_id_predicate(@klass.arel_table, @klass.primary_key, id_was || id))
45
+ relation.arel.compile_update(substitutes, @klass.primary_key)
46
+ else
47
+ @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
48
+ end
49
+
50
+ @klass.connection.update(
51
+ um,
52
+ 'SQL',
53
+ binds)
54
+ end
55
+ end
56
+ end
@@ -1,65 +1,75 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module Calculations
4
- def aggregate_column(column_name)
5
- # CPK
6
- if column_name.kind_of?(Array)
7
- column_name.map do |column|
8
- Arel::Attribute.new(@klass.unscoped.table, column)
9
- end
10
- elsif @klass.column_names.include?(column_name.to_s)
11
- Arel::Attribute.new(@klass.unscoped.table, column_name)
12
- else
13
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
14
- end
15
- end
16
-
17
- def execute_simple_calculation(operation, column_name, distinct)
18
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
19
- relation = reorder(nil)
20
-
21
- # CPK
22
- #if operation == "count" && (relation.limit_value || relation.offset_value)
23
- if operation == "count"
24
- # Shortcut when limit is zero.
25
- return 0 if relation.limit_value == 0
26
-
27
- query_builder = build_count_subquery(relation, column_name, distinct)
28
- else
29
- column = aggregate_column(column_name)
30
-
31
- select_value = operation_over_aggregate_column(column, operation, distinct)
32
-
33
- relation.select_values = [select_value]
34
-
35
- query_builder = relation.arel
36
- end
37
-
38
- type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
39
- end
40
-
41
- def build_count_subquery(relation, column_name, distinct)
42
- return super(relation, column_name, distinct) unless column_name.kind_of?(Array)
43
- # CPK
44
- # column_alias = Arel.sql('count_column')
45
- subquery_alias = Arel.sql('subquery_for_count')
46
-
47
- # CPK
48
- # aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
49
- # relation.select_values = [aliased_column]
50
- relation.select_values = column_name.map do |column|
51
- Arel::Attribute.new(@klass.unscoped.table, column)
52
- end
53
-
54
- relation = relation.distinct(true)
55
- subquery = relation.arel.as(subquery_alias)
56
-
57
- sm = Arel::SelectManager.new relation.engine
58
- # CPK
59
- # select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
60
- select_value = operation_over_aggregate_column(Arel.sql("*"), 'count', false)
61
- sm.project(select_value).from(subquery)
62
- end
63
- end
64
- end
65
- end
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module Calculations
4
+ def aggregate_column(column_name)
5
+ # CPK
6
+ if column_name.kind_of?(Array)
7
+ column_name.map do |column|
8
+ Arel::Attribute.new(@klass.unscoped.table, column)
9
+ end
10
+ elsif @klass.column_names.include?(column_name.to_s)
11
+ Arel::Attribute.new(@klass.unscoped.table, column_name)
12
+ else
13
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
14
+ end
15
+ end
16
+
17
+ def execute_simple_calculation(operation, column_name, distinct)
18
+ # Postgresql doesn't like ORDER BY when there are no GROUP BY
19
+ relation = reorder(nil)
20
+
21
+ column_alias = column_name
22
+ # CPK
23
+ #if operation == "count" && (relation.limit_value || relation.offset_value)
24
+ if operation == "count"
25
+ # Shortcut when limit is zero.
26
+ return 0 if relation.limit_value == 0
27
+
28
+ query_builder = build_count_subquery(relation, column_name, distinct)
29
+ else
30
+ column = aggregate_column(column_name)
31
+
32
+ column_alias = select_value.alias
33
+ select_value = operation_over_aggregate_column(column, operation, distinct)
34
+
35
+ relation.select_values = [select_value]
36
+
37
+ query_builder = relation.arel
38
+ end
39
+
40
+ result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
41
+ row = result.first
42
+ value = row && row.values.first
43
+ column = result.column_types.fetch(column_alias) do
44
+ column_for(column_name)
45
+ end
46
+
47
+ type_cast_calculated_value(value, column, operation)
48
+
49
+ end
50
+
51
+ def build_count_subquery(relation, column_name, distinct)
52
+ return super(relation, column_name, distinct) unless column_name.kind_of?(Array)
53
+ # CPK
54
+ # column_alias = Arel.sql('count_column')
55
+ subquery_alias = Arel.sql('subquery_for_count')
56
+
57
+ # CPK
58
+ # aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
59
+ # relation.select_values = [aliased_column]
60
+ relation.select_values = column_name.map do |column|
61
+ Arel::Attribute.new(@klass.unscoped.table, column)
62
+ end
63
+
64
+ relation = relation.distinct(true)
65
+ subquery = relation.arel.as(subquery_alias)
66
+
67
+ sm = Arel::SelectManager.new relation.engine
68
+ # CPK
69
+ # select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
70
+ select_value = operation_over_aggregate_column(Arel.sql("*"), 'count', false)
71
+ sm.project(select_value).from(subquery)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,196 +1,196 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module FinderMethods
4
- def apply_join_dependency(relation, join_dependency)
5
- relation = relation.except(:includes, :eager_load, :preload)
6
- relation = relation.joins join_dependency
7
-
8
- if using_limitable_reflections?(join_dependency.reflections)
9
- relation
10
- else
11
- if relation.limit_value
12
- limited_ids = limited_ids_for(relation)
13
- # CPK
14
- #limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
15
- limited_ids.empty? ? relation.none! : relation.where!(cpk_in_predicate(table, self.primary_keys, limited_ids))
16
- end
17
- relation.except(:limit, :offset)
18
- end
19
- end
20
-
21
- def limited_ids_for(relation)
22
- # CPK
23
- #values = @klass.connection.columns_for_distinct(
24
- # "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
25
- columns = @klass.primary_keys.map do |key|
26
- "#{quoted_table_name}.#{connection.quote_column_name(key)}"
27
- end
28
- values = @klass.connection.columns_for_distinct(columns, relation.order_values)
29
-
30
- relation = relation.except(:select).select(values).distinct!
31
-
32
- id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
33
-
34
- # CPK
35
- #id_rows.map {|row| row[primary_key]}
36
- id_rows.map {|row| row.values}
37
- end
38
-
39
- def exists?(conditions = :none)
40
- # conditions can be:
41
- # Array - ['department_id = ? and location_id = ?', 1, 1]
42
- # Array -> [1,2]
43
- # CompositeKeys -> [1,2]
44
-
45
- conditions = conditions.id if ::ActiveRecord::Base === conditions
46
- return false if !conditions
47
-
48
- relation = apply_join_dependency(self, construct_join_dependency)
49
- return false if ::ActiveRecord::NullRelation === relation
50
-
51
- relation = relation.except(:select, :order).select(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit(1)
52
-
53
- # CPK
54
- #case conditions
55
- #when Array, Hash
56
- # relation = relation.where(conditions)
57
- #else
58
- # relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
59
- #end
60
-
61
- case conditions
62
- when CompositePrimaryKeys::CompositeKeys
63
- relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
64
- when Array
65
- pk_length = @klass.primary_keys.length
66
-
67
- if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
68
- return self.exists?(conditions.to_composite_keys)
69
- else # Assume that conditions contains where relation
70
- relation = relation.where(conditions)
71
- end
72
- when Hash
73
- relation = relation.where(conditions)
74
- end
75
-
76
- connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
77
- end
78
-
79
- def find_with_ids(*ids)
80
- # CPK handle strings that come w/ calling to_param on CPK-enabled models
81
- ids = parse_ids(ids)
82
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
83
-
84
- expects_array = ids.first.kind_of?(Array)
85
- return ids.first if expects_array && ids.first.empty?
86
-
87
- # CPK - don't do this, we want an array of arrays
88
- #ids = ids.flatten.compact.uniq
89
- case ids.size
90
- when 0
91
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
92
- when 1
93
- result = find_one(ids.first)
94
- # CPK
95
- # expects_array ? [ result ] : result
96
- result
97
- else
98
- find_some(ids)
99
- end
100
- end
101
-
102
- def find_one(id)
103
- # CPK
104
- #id = id.id if ActiveRecord::Base === id
105
- id = id.id if ::ActiveRecord::Base === id
106
-
107
- # CPK
108
- #column = columns_hash[primary_key]
109
- #substitute = connection.substitute_at(column, bind_values.length)
110
- #relation = where(table[primary_key].eq(substitute))
111
- #relation.bind_values += [[column, id]]
112
- #record = relation.take
113
- relation = self
114
- values = primary_keys.each_with_index.map do |primary_key, i|
115
- column = columns_hash[primary_key]
116
- relation.bind_values += [[column, id[i]]]
117
- connection.substitute_at(column, bind_values.length - 1)
118
- end
119
- relation = relation.where(cpk_id_predicate(table, primary_keys, values))
120
-
121
- record = relation.take
122
- raise_record_not_found_exception!(id, 0, 1) unless record
123
- record
124
- end
125
-
126
- def find_some(ids)
127
- # CPK
128
- # result = where(table[primary_key].in(ids)).to_a
129
-
130
- result = ids.map do |cpk_ids|
131
- cpk_ids = if cpk_ids.length == 1
132
- cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
133
- else
134
- cpk_ids.to_composite_keys
135
- end
136
-
137
- unless cpk_ids.length == @klass.primary_keys.length
138
- raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
139
- end
140
-
141
- new_relation = clone
142
- [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
143
- new_relation = new_relation.where(key => id)
144
- end
145
-
146
- records = new_relation.to_a
147
-
148
- if records.empty?
149
- conditions = new_relation.arel.where_sql
150
- raise(::ActiveRecord::RecordNotFound,
151
- "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
152
- end
153
- records
154
- end.flatten
155
-
156
- expected_size =
157
- if limit_value && ids.size > limit_value
158
- limit_value
159
- else
160
- ids.size
161
- end
162
-
163
- # 11 ids with limit 3, offset 9 should give 2 results.
164
- if offset_value && (ids.size - offset_value < expected_size)
165
- expected_size = ids.size - offset_value
166
- end
167
-
168
- if result.size == expected_size
169
- result
170
- else
171
- raise_record_not_found_exception!(ids, result.size, expected_size)
172
- end
173
- end
174
-
175
- private
176
- def parse_ids(ids)
177
- result = []
178
- ids.each do |id|
179
- if id.is_a?(String)
180
- if id.index(",")
181
- result << [id.split(",")]
182
- else
183
- result << [id]
184
- end
185
- elsif id.is_a?(Array) && id.count > 1 && id.first.to_s.index(",")
186
- result << id.map{|subid| subid.split(",")}
187
- else
188
- result << [id]
189
- end
190
- end
191
- result = result.flatten(1)
192
- return result
193
- end
194
- end
195
- end
196
- end
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module FinderMethods
4
+ def apply_join_dependency(relation, join_dependency)
5
+ relation = relation.except(:includes, :eager_load, :preload)
6
+ relation = relation.joins join_dependency
7
+
8
+ if using_limitable_reflections?(join_dependency.reflections)
9
+ relation
10
+ else
11
+ if relation.limit_value
12
+ limited_ids = limited_ids_for(relation)
13
+ # CPK
14
+ #limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
15
+ limited_ids.empty? ? relation.none! : relation.where!(cpk_in_predicate(table, self.primary_keys, limited_ids))
16
+ end
17
+ relation.except(:limit, :offset)
18
+ end
19
+ end
20
+
21
+ def limited_ids_for(relation)
22
+ # CPK
23
+ #values = @klass.connection.columns_for_distinct(
24
+ # "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
25
+ columns = @klass.primary_keys.map do |key|
26
+ "#{quoted_table_name}.#{connection.quote_column_name(key)}"
27
+ end
28
+ values = @klass.connection.columns_for_distinct(columns, relation.order_values)
29
+
30
+ relation = relation.except(:select).select(values).distinct!
31
+
32
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
33
+
34
+ # CPK
35
+ #id_rows.map {|row| row[primary_key]}
36
+ id_rows.map {|row| row.values}
37
+ end
38
+
39
+ def exists?(conditions = :none)
40
+ # conditions can be:
41
+ # Array - ['department_id = ? and location_id = ?', 1, 1]
42
+ # Array -> [1,2]
43
+ # CompositeKeys -> [1,2]
44
+
45
+ conditions = conditions.id if ::ActiveRecord::Base === conditions
46
+ return false if !conditions
47
+
48
+ relation = apply_join_dependency(self, construct_join_dependency)
49
+ return false if ::ActiveRecord::NullRelation === relation
50
+
51
+ relation = relation.except(:select, :order).select(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit(1)
52
+
53
+ # CPK
54
+ #case conditions
55
+ #when Array, Hash
56
+ # relation = relation.where(conditions)
57
+ #else
58
+ # relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
59
+ #end
60
+
61
+ case conditions
62
+ when CompositePrimaryKeys::CompositeKeys
63
+ relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
64
+ when Array
65
+ pk_length = @klass.primary_keys.length
66
+
67
+ if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
68
+ return self.exists?(conditions.to_composite_keys)
69
+ else # Assume that conditions contains where relation
70
+ relation = relation.where(conditions)
71
+ end
72
+ when Hash
73
+ relation = relation.where(conditions)
74
+ end
75
+
76
+ connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
77
+ end
78
+
79
+ def find_with_ids(*ids)
80
+ # CPK handle strings that come w/ calling to_param on CPK-enabled models
81
+ ids = parse_ids(ids)
82
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
83
+
84
+ expects_array = ids.first.kind_of?(Array)
85
+ return ids.first if expects_array && ids.first.empty?
86
+
87
+ # CPK - don't do this, we want an array of arrays
88
+ #ids = ids.flatten.compact.uniq
89
+ case ids.size
90
+ when 0
91
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
92
+ when 1
93
+ result = find_one(ids.first)
94
+ # CPK
95
+ # expects_array ? [ result ] : result
96
+ result
97
+ else
98
+ find_some(ids)
99
+ end
100
+ end
101
+
102
+ def find_one(id)
103
+ # CPK
104
+ #id = id.id if ActiveRecord::Base === id
105
+ id = id.id if ::ActiveRecord::Base === id
106
+
107
+ # CPK
108
+ #column = columns_hash[primary_key]
109
+ #substitute = connection.substitute_at(column, bind_values.length)
110
+ #relation = where(table[primary_key].eq(substitute))
111
+ #relation.bind_values += [[column, id]]
112
+ #record = relation.take
113
+ relation = self
114
+ values = primary_keys.each_with_index.map do |primary_key, i|
115
+ column = columns_hash[primary_key]
116
+ relation.bind_values += [[column, id[i]]]
117
+ connection.substitute_at(column, bind_values.length - 1)
118
+ end
119
+ relation = relation.where(cpk_id_predicate(table, primary_keys, values))
120
+
121
+ record = relation.take
122
+ raise_record_not_found_exception!(id, 0, 1) unless record
123
+ record
124
+ end
125
+
126
+ def find_some(ids)
127
+ # CPK
128
+ # result = where(table[primary_key].in(ids)).to_a
129
+
130
+ result = ids.map do |cpk_ids|
131
+ cpk_ids = if cpk_ids.length == 1
132
+ cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
133
+ else
134
+ cpk_ids.to_composite_keys
135
+ end
136
+
137
+ unless cpk_ids.length == @klass.primary_keys.length
138
+ raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
139
+ end
140
+
141
+ new_relation = clone
142
+ [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
143
+ new_relation = new_relation.where(key => id)
144
+ end
145
+
146
+ records = new_relation.to_a
147
+
148
+ if records.empty?
149
+ conditions = new_relation.arel.where_sql
150
+ raise(::ActiveRecord::RecordNotFound,
151
+ "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
152
+ end
153
+ records
154
+ end.flatten
155
+
156
+ expected_size =
157
+ if limit_value && ids.size > limit_value
158
+ limit_value
159
+ else
160
+ ids.size
161
+ end
162
+
163
+ # 11 ids with limit 3, offset 9 should give 2 results.
164
+ if offset_value && (ids.size - offset_value < expected_size)
165
+ expected_size = ids.size - offset_value
166
+ end
167
+
168
+ if result.size == expected_size
169
+ result
170
+ else
171
+ raise_record_not_found_exception!(ids, result.size, expected_size)
172
+ end
173
+ end
174
+
175
+ private
176
+ def parse_ids(ids)
177
+ result = []
178
+ ids.each do |id|
179
+ if id.is_a?(String)
180
+ if id.index(",")
181
+ result << [id.split(",")]
182
+ else
183
+ result << [id]
184
+ end
185
+ elsif id.is_a?(Array) && id.count > 1 && id.first.to_s.index(",")
186
+ result << id.map{|subid| subid.split(",")}
187
+ else
188
+ result << [id]
189
+ end
190
+ end
191
+ result = result.flatten(1)
192
+ return result
193
+ end
194
+ end
195
+ end
196
+ end