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.
- checksums.yaml +4 -4
- data/History.rdoc +615 -608
- data/lib/composite_primary_keys.rb +110 -110
- data/lib/composite_primary_keys/associations/association.rb +23 -23
- data/lib/composite_primary_keys/associations/association_scope.rb +77 -77
- data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +59 -59
- data/lib/composite_primary_keys/associations/has_many_association.rb +56 -56
- data/lib/composite_primary_keys/associations/join_dependency.rb +89 -89
- data/lib/composite_primary_keys/associations/join_dependency/join_part.rb +38 -38
- data/lib/composite_primary_keys/associations/preloader/association.rb +78 -78
- data/lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb +46 -46
- data/lib/composite_primary_keys/attribute_methods/dirty.rb +26 -26
- data/lib/composite_primary_keys/attribute_methods/read.rb +34 -34
- data/lib/composite_primary_keys/attribute_methods/write.rb +36 -36
- data/lib/composite_primary_keys/base.rb +0 -6
- data/lib/composite_primary_keys/composite_arrays.rb +30 -30
- data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +4 -2
- data/lib/composite_primary_keys/connection_adapters/sqlserver_adapter.rb +17 -0
- data/lib/composite_primary_keys/core.rb +47 -47
- data/lib/composite_primary_keys/persistence.rb +60 -60
- data/lib/composite_primary_keys/relation.rb +56 -56
- data/lib/composite_primary_keys/relation/calculations.rb +75 -65
- data/lib/composite_primary_keys/relation/finder_methods.rb +196 -196
- data/lib/composite_primary_keys/sanitization.rb +52 -52
- data/lib/composite_primary_keys/validations/uniqueness.rb +37 -39
- data/lib/composite_primary_keys/version.rb +8 -8
- data/tasks/databases/sqlserver.rake +40 -27
- data/test/connections/databases.example.yml +18 -18
- data/test/connections/native_sqlserver/connection.rb +14 -11
- data/test/fixtures/db_definitions/mysql.sql +208 -208
- data/test/fixtures/db_definitions/postgresql.sql +210 -210
- data/test/fixtures/db_definitions/sqlite.sql +197 -197
- data/test/fixtures/db_definitions/sqlserver.drop.sql +94 -91
- data/test/fixtures/db_definitions/sqlserver.sql +232 -226
- data/test/fixtures/employee.rb +5 -5
- data/test/test_associations.rb +275 -275
- data/test/test_attributes.rb +60 -60
- data/test/test_create.rb +112 -112
- data/test/test_delete.rb +152 -148
- data/test/test_delete_all.rb +21 -21
- data/test/test_enum.rb +20 -20
- data/test/test_equal.rb +1 -1
- data/test/test_tutorial_example.rb +21 -21
- 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
|
-
|
22
|
-
#
|
23
|
-
if operation == "count"
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|