composite_primary_keys 8.1.1 → 8.1.2
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 +4 -1
- data/lib/composite_primary_keys.rb +5 -1
- data/lib/composite_primary_keys/arel/visitors/to_sql.rb +24 -0
- data/lib/composite_primary_keys/associations/association_scope.rb +32 -58
- data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +22 -22
- data/lib/composite_primary_keys/associations/preloader/association.rb +12 -6
- data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +19 -19
- data/lib/composite_primary_keys/attribute_methods/primary_key.rb +1 -2
- data/lib/composite_primary_keys/attribute_methods/read.rb +2 -2
- data/lib/composite_primary_keys/attribute_methods/write.rb +1 -1
- data/lib/composite_primary_keys/composite_predicates.rb +55 -50
- data/lib/composite_primary_keys/composite_relation.rb +48 -48
- data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +2 -2
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +19 -46
- data/lib/composite_primary_keys/connection_adapters/sqlserver_adapter.rb +8 -4
- data/lib/composite_primary_keys/fixtures.rb +26 -22
- data/lib/composite_primary_keys/locking/optimistic.rb +54 -55
- data/lib/composite_primary_keys/relation.rb +15 -8
- data/lib/composite_primary_keys/relation/batches.rb +23 -19
- data/lib/composite_primary_keys/relation/calculations.rb +4 -2
- data/lib/composite_primary_keys/relation/finder_methods.rb +33 -27
- data/lib/composite_primary_keys/relation/predicate_builder.rb +18 -3
- data/lib/composite_primary_keys/relation/query_methods.rb +41 -41
- data/lib/composite_primary_keys/sanitization.rb +7 -5
- data/lib/composite_primary_keys/validations/uniqueness.rb +20 -16
- data/lib/composite_primary_keys/version.rb +1 -1
- data/tasks/databases/oracle.rake +27 -25
- data/tasks/databases/oracle_enhanced.rake +27 -0
- data/test/connections/databases.ci.yml +15 -15
- data/test/connections/native_oracle/connection.rb +11 -11
- data/test/connections/native_oracle_enhanced/connection.rb +16 -16
- data/test/fixtures/comment.rb +7 -7
- data/test/fixtures/db_definitions/db2-create-tables.sql +126 -126
- data/test/fixtures/db_definitions/db2-drop-tables.sql +18 -18
- data/test/fixtures/db_definitions/oracle.drop.sql +48 -45
- data/test/fixtures/db_definitions/oracle.sql +236 -223
- data/test/fixtures/dorm.rb +2 -2
- data/test/fixtures/membership.rb +6 -6
- data/test/fixtures/membership_statuses.yml +16 -16
- data/test/fixtures/memberships.yml +10 -10
- data/test/fixtures/product_tariffs.yml +14 -14
- data/test/fixtures/reference_code.rb +7 -7
- data/test/fixtures/restaurants_suburb.rb +2 -2
- data/test/fixtures/suburb.rb +5 -5
- data/test/fixtures/topic.rb +5 -5
- data/test/fixtures/topic_source.rb +6 -6
- data/test/fixtures/topic_sources.yml +3 -3
- data/test/fixtures/topics.yml +8 -8
- data/test/fixtures/users.yml +10 -10
- data/test/test_attribute_methods.rb +63 -63
- data/test/test_calculations.rb +42 -37
- data/test/test_callbacks.rb +99 -99
- data/test/test_delete.rb +28 -21
- data/test/test_delete_all.rb +5 -4
- data/test/test_dumpable.rb +15 -15
- data/test/test_nested_attributes.rb +124 -124
- data/test/test_optimistic.rb +18 -18
- data/test/test_polymorphic.rb +1 -1
- data/test/test_predicates.rb +40 -40
- data/test/test_santiago.rb +23 -23
- data/test/test_suite.rb +34 -34
- data/test/test_touch.rb +23 -23
- data/test/test_update.rb +71 -71
- metadata +9 -8
- data/lib/composite_primary_keys/model_schema.rb +0 -15
@@ -1,48 +1,48 @@
|
|
1
|
-
module CompositePrimaryKeys
|
2
|
-
module CompositeRelation
|
3
|
-
include CompositePrimaryKeys::ActiveRecord::Batches
|
4
|
-
include CompositePrimaryKeys::ActiveRecord::Calculations
|
5
|
-
include CompositePrimaryKeys::ActiveRecord::FinderMethods
|
6
|
-
include CompositePrimaryKeys::ActiveRecord::QueryMethods
|
7
|
-
|
8
|
-
def delete(id_or_array)
|
9
|
-
# CPK
|
10
|
-
if self.composite?
|
11
|
-
id_or_array = if id_or_array.is_a?(CompositePrimaryKeys::CompositeKeys)
|
12
|
-
[id_or_array]
|
13
|
-
else
|
14
|
-
Array(id_or_array)
|
15
|
-
end
|
16
|
-
|
17
|
-
id_or_array.each do |id|
|
18
|
-
# Is the passed in id actually a record?
|
19
|
-
id = id.kind_of?(::ActiveRecord::Base) ? id.id : id
|
20
|
-
where(cpk_id_predicate(table, self.primary_key, id)).delete_all
|
21
|
-
end
|
22
|
-
else
|
23
|
-
where(primary_key => id_or_array).delete_all
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def destroy(id_or_array)
|
28
|
-
# Without CPK:
|
29
|
-
#if id.is_a?(Array)
|
30
|
-
# id.map { |one_id| destroy(one_id) }
|
31
|
-
#else
|
32
|
-
# find(id).destroy
|
33
|
-
#end
|
34
|
-
|
35
|
-
id_or_array = if id_or_array.kind_of?(CompositePrimaryKeys::CompositeKeys)
|
36
|
-
[id_or_array]
|
37
|
-
else
|
38
|
-
Array(id_or_array)
|
39
|
-
end
|
40
|
-
|
41
|
-
id_or_array.each do |id|
|
42
|
-
where(cpk_id_predicate(table, self.primary_key, id)).each do |record|
|
43
|
-
record.destroy
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module CompositeRelation
|
3
|
+
include CompositePrimaryKeys::ActiveRecord::Batches
|
4
|
+
include CompositePrimaryKeys::ActiveRecord::Calculations
|
5
|
+
include CompositePrimaryKeys::ActiveRecord::FinderMethods
|
6
|
+
include CompositePrimaryKeys::ActiveRecord::QueryMethods
|
7
|
+
|
8
|
+
def delete(id_or_array)
|
9
|
+
# CPK
|
10
|
+
if self.composite?
|
11
|
+
id_or_array = if id_or_array.is_a?(CompositePrimaryKeys::CompositeKeys)
|
12
|
+
[id_or_array]
|
13
|
+
else
|
14
|
+
Array(id_or_array)
|
15
|
+
end
|
16
|
+
|
17
|
+
id_or_array.each do |id|
|
18
|
+
# Is the passed in id actually a record?
|
19
|
+
id = id.kind_of?(::ActiveRecord::Base) ? id.id : id
|
20
|
+
where(cpk_id_predicate(table, self.primary_key, id)).delete_all
|
21
|
+
end
|
22
|
+
else
|
23
|
+
where(primary_key => id_or_array).delete_all
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy(id_or_array)
|
28
|
+
# Without CPK:
|
29
|
+
#if id.is_a?(Array)
|
30
|
+
# id.map { |one_id| destroy(one_id) }
|
31
|
+
#else
|
32
|
+
# find(id).destroy
|
33
|
+
#end
|
34
|
+
|
35
|
+
id_or_array = if id_or_array.kind_of?(CompositePrimaryKeys::CompositeKeys)
|
36
|
+
[id_or_array]
|
37
|
+
else
|
38
|
+
Array(id_or_array)
|
39
|
+
end
|
40
|
+
|
41
|
+
id_or_array.each do |id|
|
42
|
+
where(cpk_id_predicate(table, self.primary_key, id)).each do |record|
|
43
|
+
record.destroy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Base
|
3
3
|
def self.load_cpk_adapter(adapter)
|
4
|
-
if
|
4
|
+
if adapter.to_s =~ /postgresql/ || adapter.to_s =~ /postgis/
|
5
5
|
require "composite_primary_keys/connection_adapters/postgresql_adapter.rb"
|
6
6
|
end
|
7
|
-
if
|
7
|
+
if adapter.to_s =~ /sqlserver/
|
8
8
|
require "composite_primary_keys/connection_adapters/sqlserver_adapter.rb"
|
9
9
|
end
|
10
10
|
end
|
@@ -1,46 +1,19 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module ConnectionAdapters
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
22
|
-
unless pk
|
23
|
-
# Extract the table from the insert sql. Yuck.
|
24
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
25
|
-
pk = primary_key(table_ref) if table_ref
|
26
|
-
end
|
27
|
-
|
28
|
-
# CPK
|
29
|
-
# sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
|
30
|
-
sql = "#{sql} RETURNING #{quote_column_names(pk)}" if pk
|
31
|
-
|
32
|
-
[sql, binds]
|
33
|
-
end
|
34
|
-
|
35
|
-
# Returns a single value if query returns a single element
|
36
|
-
# otherwise returns an array coresponding to the composite keys
|
37
|
-
#
|
38
|
-
def last_inserted_id(result)
|
39
|
-
row = result && result.rows.first
|
40
|
-
if Array === row
|
41
|
-
row.size == 1 ? row[0] : row
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module PostgreSQL
|
4
|
+
module Quoting
|
5
|
+
def quote_column_name(name)
|
6
|
+
# CPK
|
7
|
+
# PGconn.quote_ident(name.to_s)
|
8
|
+
if name.is_a?(Array)
|
9
|
+
name.map do |column|
|
10
|
+
PGconn.quote_ident(column.to_s)
|
11
|
+
end.join(', ')
|
12
|
+
else
|
13
|
+
PGconn.quote_ident(name.to_s)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -3,15 +3,19 @@ module ActiveRecord
|
|
3
3
|
class SQLServerAdapter
|
4
4
|
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
5
5
|
sql = if pk && self.class.use_output_inserted
|
6
|
-
#
|
6
|
+
# CPK
|
7
|
+
# quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
|
8
|
+
# sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
|
7
9
|
quoted_pks = [pk].flatten.map {|pk| "INSERTED.#{SQLServer::Utils.extract_identifiers(pk).quoted}"}
|
8
10
|
sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT #{quoted_pks.join(", ")}"
|
9
|
-
# p sql
|
10
11
|
else
|
11
12
|
"#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
|
12
13
|
end
|
14
|
+
|
15
|
+
# CPK
|
16
|
+
# super
|
13
17
|
[sql, binds]
|
14
18
|
end
|
15
|
-
|
16
|
-
|
19
|
+
end
|
20
|
+
end
|
17
21
|
end
|
@@ -1,22 +1,26 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
class Fixture
|
3
|
-
def find
|
4
|
-
if model_class
|
5
|
-
# CPK
|
6
|
-
# model_class.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
1
|
+
module ActiveRecord
|
2
|
+
class Fixture
|
3
|
+
def find
|
4
|
+
if model_class
|
5
|
+
# CPK
|
6
|
+
# model_class.unscoped do
|
7
|
+
# model_class.find(fixture[model_class.primary_key])
|
8
|
+
# end
|
9
|
+
model_class.unscoped do
|
10
|
+
ids = self.ids(model_class.primary_key)
|
11
|
+
model_class.find(ids)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
raise FixtureClassNotFound, "No class attached to find."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def ids(key)
|
19
|
+
if key.is_a? Array
|
20
|
+
key.map {|a_key| fixture[a_key.to_s] }
|
21
|
+
else
|
22
|
+
fixture[key]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,55 +1,54 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Locking
|
3
|
-
module Optimistic
|
4
|
-
private
|
5
|
-
def _update_record(attribute_names = @attributes.keys) #:nodoc:
|
6
|
-
return super unless locking_enabled?
|
7
|
-
return 0 if attribute_names.empty?
|
8
|
-
|
9
|
-
lock_col = self.class.locking_column
|
10
|
-
previous_lock_value = send(lock_col).to_i
|
11
|
-
increment_lock
|
12
|
-
|
13
|
-
attribute_names += [lock_col]
|
14
|
-
attribute_names.uniq!
|
15
|
-
|
16
|
-
begin
|
17
|
-
relation = self.class.unscoped
|
18
|
-
|
19
|
-
if self.composite?
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module Locking
|
3
|
+
module Optimistic
|
4
|
+
private
|
5
|
+
def _update_record(attribute_names = @attributes.keys) #:nodoc:
|
6
|
+
return super unless locking_enabled?
|
7
|
+
return 0 if attribute_names.empty?
|
8
|
+
|
9
|
+
lock_col = self.class.locking_column
|
10
|
+
previous_lock_value = send(lock_col).to_i
|
11
|
+
increment_lock
|
12
|
+
|
13
|
+
attribute_names += [lock_col]
|
14
|
+
attribute_names.uniq!
|
15
|
+
|
16
|
+
begin
|
17
|
+
relation = self.class.unscoped
|
18
|
+
|
19
|
+
if self.composite?
|
20
|
+
affected_rows = relation.where(
|
21
|
+
relation.cpk_id_predicate(relation.table, self.class.primary_key, id_was)
|
22
|
+
).where(
|
23
|
+
lock_col => previous_lock_value
|
24
|
+
).update_all(
|
25
|
+
Hash[attributes_for_update(attribute_names).map do |name|
|
26
|
+
[name, _read_attribute(name)]
|
27
|
+
end]
|
28
|
+
)
|
29
|
+
else
|
30
|
+
affected_rows = relation.where(
|
31
|
+
self.class.primary_key => id,
|
32
|
+
lock_col => previous_lock_value,
|
33
|
+
).update_all(
|
34
|
+
Hash[attributes_for_update(attribute_names).map do |name|
|
35
|
+
[name, _read_attribute(name)]
|
36
|
+
end]
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
unless affected_rows == 1
|
41
|
+
raise ActiveRecord::StaleObjectError.new(self, "update")
|
42
|
+
end
|
43
|
+
|
44
|
+
affected_rows
|
45
|
+
|
46
|
+
# If something went wrong, revert the version.
|
47
|
+
rescue Exception
|
48
|
+
send(lock_col + '=', previous_lock_value)
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -16,20 +16,23 @@ module ActiveRecord
|
|
16
16
|
extend CompositePrimaryKeys::CompositeRelation
|
17
17
|
end
|
18
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
19
|
alias :where_values_hash_without_cpk :where_values_hash
|
24
20
|
def where_values_hash(relation_table_name = table_name)
|
25
|
-
|
21
|
+
# CPK
|
22
|
+
nodes_from_and = where_values.grep(Arel::Nodes::And).map { |and_node|
|
23
|
+
and_node.children.grep(Arel::Nodes::Equality)
|
24
|
+
}.flatten
|
26
25
|
|
26
|
+
# CPK
|
27
|
+
# equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
|
28
|
+
# node.left.relation.name == relation_table_name
|
29
|
+
# }
|
27
30
|
equalities = (nodes_from_and + where_values.grep(Arel::Nodes::Equality)).find_all { |node|
|
28
31
|
node.left.relation.name == relation_table_name
|
29
32
|
}
|
30
33
|
|
31
34
|
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
|
32
|
-
|
35
|
+
|
33
36
|
Hash[equalities.map { |where|
|
34
37
|
name = where.left.name
|
35
38
|
[name, binds.fetch(name.to_s) {
|
@@ -48,6 +51,7 @@ module ActiveRecord
|
|
48
51
|
# CPK
|
49
52
|
um = if self.composite?
|
50
53
|
relation = @klass.unscoped.where(cpk_id_predicate(@klass.arel_table, @klass.primary_key, id_was || id))
|
54
|
+
|
51
55
|
relation.arel.compile_update(substitutes, @klass.primary_key)
|
52
56
|
else
|
53
57
|
scope = @klass.unscoped
|
@@ -55,8 +59,11 @@ module ActiveRecord
|
|
55
59
|
if @klass.finder_needs_type_condition?
|
56
60
|
scope.unscope!(where: @klass.inheritance_column)
|
57
61
|
end
|
58
|
-
|
59
|
-
scope.where(@klass.
|
62
|
+
|
63
|
+
relation = scope.where(@klass.primary_key => (id_was || id))
|
64
|
+
binds += relation.bind_values
|
65
|
+
|
66
|
+
relation.arel.compile_update(substitutes, @klass.primary_key)
|
60
67
|
end
|
61
68
|
|
62
69
|
@klass.connection.update(
|
@@ -2,33 +2,39 @@ module CompositePrimaryKeys
|
|
2
2
|
module ActiveRecord
|
3
3
|
module Batches
|
4
4
|
def find_in_batches(options = {})
|
5
|
+
options.assert_valid_keys(:start, :batch_size)
|
6
|
+
|
5
7
|
relation = self
|
8
|
+
start = options[:start]
|
9
|
+
batch_size = options[:batch_size] || 1000
|
6
10
|
|
7
|
-
unless
|
8
|
-
|
11
|
+
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
|
9
16
|
end
|
10
17
|
|
11
|
-
if
|
12
|
-
|
13
|
-
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
|
14
|
-
|
15
|
-
relation = apply_finder_options(finder_options)
|
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")
|
16
20
|
end
|
17
21
|
|
18
|
-
start = options.delete(:start).to_i
|
19
|
-
batch_size = options.delete(:batch_size) || 1000
|
20
|
-
|
21
22
|
relation = relation.reorder(batch_order).limit(batch_size)
|
22
23
|
|
23
24
|
# CPK
|
24
|
-
# records = relation.where(table[primary_key].gteq(start)).
|
25
|
-
records =
|
26
|
-
|
27
|
-
|
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
|
28
33
|
|
29
34
|
while records.any?
|
30
35
|
records_size = records.size
|
31
36
|
primary_key_offset = records.last.id
|
37
|
+
raise "Primary key not included in the custom select clause" unless primary_key_offset
|
32
38
|
|
33
39
|
yield records
|
34
40
|
|
@@ -51,11 +57,9 @@ module CompositePrimaryKeys
|
|
51
57
|
|
52
58
|
Arel::Nodes::Grouping.new(and_clause)
|
53
59
|
end.reduce(:or)
|
54
|
-
|
55
|
-
records = relation.where(query)
|
56
|
-
else
|
57
|
-
raise "Primary key not included in the custom select clause"
|
58
60
|
end
|
61
|
+
|
62
|
+
records = relation.where(query)
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
@@ -70,7 +74,7 @@ module CompositePrimaryKeys
|
|
70
74
|
|
71
75
|
def batch_order
|
72
76
|
# CPK
|
73
|
-
#"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
77
|
+
# "#{quoted_table_name}.#{quoted_primary_key} ASC"
|
74
78
|
self.primary_key.map do |key|
|
75
79
|
"#{quoted_table_name}.#{key} ASC"
|
76
80
|
end.join(",")
|