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.
- checksums.yaml +5 -5
- data/History.rdoc +59 -7
- data/README.rdoc +62 -1
- data/Rakefile +4 -1
- data/lib/composite_primary_keys/arel/in.rb +6 -0
- data/lib/composite_primary_keys/arel/sqlserver.rb +36 -0
- data/lib/composite_primary_keys/arel/to_sql.rb +26 -0
- data/lib/composite_primary_keys/associations/association.rb +14 -12
- data/lib/composite_primary_keys/associations/association_scope.rb +22 -27
- data/lib/composite_primary_keys/associations/collection_association.rb +39 -8
- data/lib/composite_primary_keys/associations/has_many_association.rb +17 -40
- data/lib/composite_primary_keys/associations/has_many_through_association.rb +58 -58
- data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +13 -11
- data/lib/composite_primary_keys/associations/join_dependency.rb +73 -56
- data/lib/composite_primary_keys/associations/preloader/association.rb +75 -73
- data/lib/composite_primary_keys/attribute_methods/primary_key.rb +13 -11
- data/lib/composite_primary_keys/attribute_methods/read.rb +18 -15
- data/lib/composite_primary_keys/attribute_methods/write.rb +21 -19
- data/lib/composite_primary_keys/attribute_methods.rb +6 -4
- data/lib/composite_primary_keys/autosave_association.rb +19 -54
- data/lib/composite_primary_keys/base.rb +18 -82
- data/lib/composite_primary_keys/composite_arrays.rb +9 -1
- data/lib/composite_primary_keys/composite_predicates.rb +1 -0
- data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +10 -10
- data/lib/composite_primary_keys/connection_adapters/abstract_mysql_adapter.rb +5 -6
- data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +23 -0
- data/lib/composite_primary_keys/core.rb +46 -45
- data/lib/composite_primary_keys/fixtures.rb +19 -17
- data/lib/composite_primary_keys/locking/optimistic.rb +53 -49
- data/lib/composite_primary_keys/nested_attributes.rb +60 -53
- data/lib/composite_primary_keys/persistence.rb +49 -41
- data/lib/composite_primary_keys/relation/batches.rb +35 -32
- data/lib/composite_primary_keys/relation/calculations.rb +3 -7
- data/lib/composite_primary_keys/relation/finder_methods.rb +122 -55
- data/lib/composite_primary_keys/relation/predicate_builder.rb +18 -29
- data/lib/composite_primary_keys/relation/query_methods.rb +25 -36
- data/lib/composite_primary_keys/relation/where_clause.rb +33 -0
- data/lib/composite_primary_keys/relation.rb +96 -43
- data/lib/composite_primary_keys/sanitization.rb +6 -15
- data/lib/composite_primary_keys/version.rb +3 -3
- data/lib/composite_primary_keys.rb +10 -19
- data/scripts/console.rb +48 -48
- data/scripts/txt2html +76 -76
- data/scripts/txt2js +65 -65
- data/tasks/databases/mysql.rake +17 -19
- data/tasks/databases/oracle.rake +29 -15
- data/tasks/databases/postgresql.rake +20 -29
- data/tasks/databases/sqlite.rake +25 -0
- data/tasks/databases/sqlserver.rake +32 -16
- data/tasks/website.rake +18 -18
- data/test/README_tests.rdoc +56 -56
- data/test/abstract_unit.rb +24 -18
- data/test/connections/connection_spec.rb +11 -2
- data/test/connections/databases.ci.yml +5 -4
- data/test/connections/databases.example.yml +19 -4
- data/test/connections/databases.yml +40 -30
- data/test/db_test.rb +52 -52
- data/test/fixtures/article.rb +1 -0
- data/test/fixtures/articles.yml +6 -6
- data/test/fixtures/capitol.rb +3 -3
- data/test/fixtures/capitols.yml +16 -16
- data/test/fixtures/comments.yml +15 -15
- data/test/fixtures/db_definitions/mysql.sql +16 -17
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +26 -17
- data/test/fixtures/db_definitions/postgresql.sql +2 -3
- data/test/fixtures/db_definitions/sqlite.sql +0 -1
- data/test/fixtures/db_definitions/sqlserver.sql +20 -35
- data/test/fixtures/department.rb +5 -5
- data/test/fixtures/departments.yml +15 -15
- data/test/fixtures/dorms.yml +4 -4
- data/test/fixtures/employee.rb +5 -7
- data/test/fixtures/employees.yml +1 -5
- data/test/fixtures/group.rb +2 -2
- data/test/fixtures/groups.yml +6 -6
- data/test/fixtures/hack.rb +4 -4
- data/test/fixtures/hacks.yml +2 -2
- data/test/fixtures/membership_status.rb +2 -2
- data/test/fixtures/product.rb +9 -9
- data/test/fixtures/product_tariff.rb +5 -5
- data/test/fixtures/products.yml +11 -11
- data/test/fixtures/reading.rb +4 -4
- data/test/fixtures/readings.yml +10 -10
- data/test/fixtures/reference_code_using_composite_key_alias.rb +8 -8
- data/test/fixtures/reference_code_using_simple_key_alias.rb +8 -8
- data/test/fixtures/reference_codes.yml +28 -28
- data/test/fixtures/reference_type.rb +1 -1
- data/test/fixtures/reference_types.yml +9 -9
- data/test/fixtures/restaurant.rb +9 -9
- data/test/fixtures/restaurants.yml +14 -14
- data/test/fixtures/restaurants_suburbs.yml +10 -10
- data/test/fixtures/room.rb +11 -11
- data/test/fixtures/room_assignment.rb +13 -13
- data/test/fixtures/room_assignments.yml +24 -24
- data/test/fixtures/room_attribute.rb +2 -2
- data/test/fixtures/room_attribute_assignment.rb +4 -4
- data/test/fixtures/room_attribute_assignments.yml +4 -4
- data/test/fixtures/room_attributes.yml +2 -2
- data/test/fixtures/rooms.yml +12 -12
- data/test/fixtures/seat.rb +5 -5
- data/test/fixtures/seats.yml +8 -8
- data/test/fixtures/street.rb +2 -2
- data/test/fixtures/streets.yml +16 -16
- data/test/fixtures/student.rb +3 -3
- data/test/fixtures/students.yml +15 -15
- data/test/fixtures/suburbs.yml +14 -14
- data/test/fixtures/tariff.rb +1 -1
- data/test/fixtures/tariffs.yml +14 -14
- data/test/fixtures/user.rb +0 -1
- data/test/plugins/pagination.rb +405 -405
- data/test/plugins/pagination_helper.rb +135 -135
- data/test/setup.rb +50 -50
- data/test/test_aliases.rb +18 -18
- data/test/test_associations.rb +18 -17
- data/test/test_composite_arrays.rb +24 -24
- data/test/test_counter_cache.rb +30 -30
- data/test/test_create.rb +14 -6
- data/test/test_delete.rb +36 -34
- data/test/test_dup.rb +37 -37
- data/test/test_exists.rb +39 -39
- data/test/test_find.rb +16 -4
- data/test/test_habtm.rb +27 -3
- data/test/test_miscellaneous.rb +32 -32
- data/test/test_nested_attributes.rb +6 -6
- data/test/test_pagination.rb +35 -35
- data/test/test_polymorphic.rb +0 -7
- data/test/test_predicates.rb +20 -20
- data/test/test_preload.rb +94 -0
- data/test/test_suite.rb +1 -1
- data/test/test_update.rb +10 -7
- data/test/test_validations.rb +13 -13
- metadata +30 -39
- data/README_DB2.rdoc +0 -33
- data/lib/composite_primary_keys/arel/visitors/to_sql.rb +0 -36
- data/lib/composite_primary_keys/associations/singular_association.rb +0 -18
- data/lib/composite_primary_keys/attribute_methods/dirty.rb +0 -29
- data/lib/composite_primary_keys/attribute_set/builder.rb +0 -20
- data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +0 -70
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +0 -19
- data/lib/composite_primary_keys/dirty.rb +0 -19
- data/lib/composite_primary_keys/validations/uniqueness.rb +0 -41
- data/tasks/databases/oracle_enhanced.rake +0 -27
- data/tasks/databases/sqlite3.rake +0 -27
- data/test/connections/native_ibm_db/connection.rb +0 -19
- data/test/connections/native_mysql/connection.rb +0 -17
- data/test/connections/native_oracle/connection.rb +0 -11
- data/test/connections/native_oracle_enhanced/connection.rb +0 -16
- data/test/connections/native_postgresql/connection.rb +0 -13
- data/test/connections/native_sqlite3/connection.rb +0 -9
- data/test/connections/native_sqlserver/connection.rb +0 -11
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -92
- data/test/test_delete_all.rb +0 -35
- data/test/test_find_in_batches.rb +0 -30
@@ -7,21 +7,8 @@ module ActiveRecord
|
|
7
7
|
NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
|
8
8
|
|
9
9
|
class << self
|
10
|
-
|
11
|
-
|
12
|
-
reset_primary_keys
|
13
|
-
end
|
14
|
-
@primary_keys
|
15
|
-
end
|
16
|
-
|
17
|
-
# Don't like this method name, but its modeled after how AR does it
|
18
|
-
def reset_primary_keys
|
19
|
-
if self != base_class
|
20
|
-
self.primary_keys = base_class.primary_keys
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def primary_key_with_composite_key_support=(keys)
|
10
|
+
alias_method :primary_key_without_composite_key_support=, :primary_key=
|
11
|
+
def primary_key=(keys)
|
25
12
|
unless keys.kind_of?(Array)
|
26
13
|
self.primary_key_without_composite_key_support = keys
|
27
14
|
return
|
@@ -34,7 +21,6 @@ module ActiveRecord
|
|
34
21
|
include CompositeInstanceMethods
|
35
22
|
EOV
|
36
23
|
end
|
37
|
-
alias_method_chain :primary_key=, :composite_key_support
|
38
24
|
alias_method :primary_keys=, :primary_key=
|
39
25
|
|
40
26
|
def set_primary_keys(*keys)
|
@@ -48,75 +34,11 @@ module ActiveRecord
|
|
48
34
|
else
|
49
35
|
self.primary_keys = keys
|
50
36
|
end
|
51
|
-
|
37
|
+
end
|
52
38
|
|
53
39
|
def composite?
|
54
40
|
false
|
55
41
|
end
|
56
|
-
|
57
|
-
def find_in_batches(options = {})
|
58
|
-
return super unless primary_key.is_a?(Array)
|
59
|
-
|
60
|
-
# Unfortunately .count uses a subquery temp table, which is a big problem when your table is large
|
61
|
-
number_of_rows = count(primary_key.first)
|
62
|
-
batch_size = options[:batch_size] || 100000
|
63
|
-
row_number = 0
|
64
|
-
|
65
|
-
while row_number < number_of_rows
|
66
|
-
end_row_number = row_number + batch_size - 1
|
67
|
-
end_row_number = number_of_rows - 1 if end_row_number > number_of_rows - 1
|
68
|
-
|
69
|
-
# Force the necessary sorting; AR as is will sort a PK table incorrectly
|
70
|
-
start_key = order(*primary_key).
|
71
|
-
offset(row_number).
|
72
|
-
first.
|
73
|
-
attributes.
|
74
|
-
slice(*primary_key)
|
75
|
-
|
76
|
-
end_key = order(*(primary_key.map { |k| "#{k} ASC" })).
|
77
|
-
offset(end_row_number).
|
78
|
-
first.
|
79
|
-
attributes.
|
80
|
-
slice(*primary_key)
|
81
|
-
|
82
|
-
relation = self
|
83
|
-
lower_bounds = []
|
84
|
-
upper_bounds = []
|
85
|
-
|
86
|
-
# Iterate through the PKs positionally; when we have found a discrepancy between start and end
|
87
|
-
# then we know that's where the boundaries are
|
88
|
-
primary_key.each do |col|
|
89
|
-
if start_key[col] == end_key[col]
|
90
|
-
relation = relation.where("#{col} = '#{start_key[col]}'")
|
91
|
-
else
|
92
|
-
lower_bounds << [col, start_key[col]]
|
93
|
-
upper_bounds << [col, end_key[col]]
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
relation = relation.where(build_batch_case(lower_bounds, '>')) unless lower_bounds.empty?
|
98
|
-
relation = relation.where(build_batch_case(upper_bounds, '<')) unless upper_bounds.empty?
|
99
|
-
|
100
|
-
yield(relation.order(*primary_key))
|
101
|
-
|
102
|
-
row_number = end_row_number + 1
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
|
108
|
-
def build_batch_case(bounds, operator)
|
109
|
-
bounds = bounds.dup
|
110
|
-
bound = bounds.shift
|
111
|
-
if bounds.empty?
|
112
|
-
"#{bound[0]} #{operator}= '#{bound[1]}' "
|
113
|
-
else
|
114
|
-
sql_case = "CASE WHEN #{bound[0]} = '#{bound[1]}' THEN "
|
115
|
-
sql_case += build_batch_case(bounds, operator)
|
116
|
-
sql_case += "ELSE #{bound[0]} #{operator} '#{bound[1]}' END "
|
117
|
-
sql_case
|
118
|
-
end
|
119
|
-
end
|
120
42
|
end
|
121
43
|
|
122
44
|
def composite?
|
@@ -124,12 +46,26 @@ module ActiveRecord
|
|
124
46
|
end
|
125
47
|
|
126
48
|
module CompositeClassMethods
|
49
|
+
def primary_keys
|
50
|
+
unless defined?(@primary_keys)
|
51
|
+
reset_primary_keys
|
52
|
+
end
|
53
|
+
@primary_keys
|
54
|
+
end
|
55
|
+
|
56
|
+
# Don't like this method name, but its modeled after how AR does it
|
57
|
+
def reset_primary_keys
|
58
|
+
if self != base_class
|
59
|
+
self.primary_keys = base_class.primary_keys
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
127
63
|
def primary_key
|
128
64
|
primary_keys
|
129
65
|
end
|
130
66
|
|
131
67
|
def primary_key=(keys)
|
132
|
-
primary_keys = keys
|
68
|
+
self.primary_keys = keys
|
133
69
|
end
|
134
70
|
|
135
71
|
def composite?
|
@@ -22,7 +22,7 @@ module CompositePrimaryKeys
|
|
22
22
|
|
23
23
|
class CompositeKeys < Array
|
24
24
|
|
25
|
-
|
25
|
+
def self.parse(value)
|
26
26
|
case value
|
27
27
|
when Array
|
28
28
|
value.to_composite_keys
|
@@ -33,6 +33,14 @@ module CompositePrimaryKeys
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
def in(other)
|
37
|
+
case other
|
38
|
+
when Arel::SelectManager
|
39
|
+
CompositePrimaryKeys::Nodes::In.new(self, other.ast)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
36
44
|
def to_s
|
37
45
|
# Doing this makes it easier to parse Base#[](attr_name)
|
38
46
|
join(ID_SEP)
|
@@ -39,6 +39,7 @@ module CompositePrimaryKeys
|
|
39
39
|
key2_fields = Array(key2).map {|key| table2[key]}
|
40
40
|
|
41
41
|
eq_predicates = key1_fields.zip(key2_fields).map do |key_field1, key_field2|
|
42
|
+
key_field2 = Arel::Nodes::Quoted.new(key_field2) unless Arel::Attributes::Attribute === key_field2
|
42
43
|
key_field1.eq(key_field2)
|
43
44
|
end
|
44
45
|
cpk_and_predicate(eq_predicates)
|
@@ -1,11 +1,11 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module ConnectionAdapters
|
3
|
-
class AbstractAdapter
|
4
|
-
def quote_column_names(name)
|
5
|
-
Array(name).map do |col|
|
6
|
-
quote_column_name(col.to_s)
|
7
|
-
end.join(CompositePrimaryKeys::ID_SEP)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class AbstractAdapter
|
4
|
+
def quote_column_names(name)
|
5
|
+
Array(name).map do |col|
|
6
|
+
quote_column_name(col.to_s)
|
7
|
+
end.join(CompositePrimaryKeys::ID_SEP)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
11
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
|
-
class AbstractMysqlAdapter
|
4
|
-
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
5
|
-
# to give it some prompting in the form of a subsubquery. Ugh!
|
3
|
+
class AbstractMysqlAdapter
|
6
4
|
def subquery_for(key, select)
|
7
5
|
subsubselect = select.clone
|
8
6
|
subsubselect.projections = [key]
|
@@ -13,9 +11,10 @@ module ActiveRecord
|
|
13
11
|
|
14
12
|
subselect = Arel::SelectManager.new(select.engine)
|
15
13
|
|
16
|
-
#
|
17
|
-
|
18
|
-
subselect.project
|
14
|
+
# CPK
|
15
|
+
#subselect.project Arel.sql(key.name)
|
16
|
+
subselect.project Arel.sql(Array(key).map(&:name).join(', '))
|
17
|
+
|
19
18
|
subselect.from subsubselect.as('__active_record_temp')
|
20
19
|
end
|
21
20
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class SQLite3Adapter
|
4
|
+
def join_to_update(update, select, key)
|
5
|
+
if key.is_a?(::CompositePrimaryKeys::CompositeKeys)
|
6
|
+
subselect = subquery_for(key, select)
|
7
|
+
subselect_aliased = Arel::Nodes::TableAlias.new(subselect, 'cpk_inner')
|
8
|
+
cpk_subselect = Arel::SelectManager.new(subselect_aliased)
|
9
|
+
cpk_subselect.project('*')
|
10
|
+
key.each do |a_key|
|
11
|
+
where_expr = subselect_aliased[a_key.name].eq(update.ast.relation[a_key.name])
|
12
|
+
cpk_subselect.where(where_expr)
|
13
|
+
end
|
14
|
+
where_clause = Arel::Nodes::SqlLiteral.new("EXISTS (#{cpk_subselect.to_sql})")
|
15
|
+
update.where(where_clause)
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
alias join_to_delete join_to_update
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,60 +1,61 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Core
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
silence_warnings do
|
4
|
+
def initialize_dup(other) # :nodoc:
|
5
|
+
@attributes = @attributes.dup
|
6
|
+
# CPK
|
7
|
+
# @attributes.reset(self.class.primary_key)
|
8
|
+
Array(self.class.primary_key).each {|key| @attributes.reset(key)}
|
8
9
|
|
9
|
-
|
10
|
+
run_callbacks(:initialize) unless _initialize_callbacks.empty?
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
@aggregation_cache = {}
|
13
|
+
@association_cache = {}
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
@new_record = true
|
16
|
+
@destroyed = false
|
16
17
|
|
17
|
-
|
18
|
+
super
|
19
|
+
end
|
18
20
|
end
|
19
21
|
|
20
22
|
module ClassMethods
|
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
|
-
s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
|
47
|
-
find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
|
23
|
+
silence_warnings do
|
24
|
+
def find(*ids) # :nodoc:
|
25
|
+
# We don't have cache keys for this stuff yet
|
26
|
+
return super unless ids.length == 1
|
27
|
+
return super if block_given? ||
|
28
|
+
primary_key.nil? ||
|
29
|
+
scope_attributes? ||
|
30
|
+
columns_hash.include?(inheritance_column) ||
|
31
|
+
ids.first.kind_of?(Array)
|
32
|
+
# CPK
|
33
|
+
return super if self.composite?
|
34
|
+
|
35
|
+
id = ids.first
|
36
|
+
if ActiveRecord::Base === id
|
37
|
+
id = id.id
|
38
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
39
|
+
You are passing an instance of ActiveRecord::Base to `find`.
|
40
|
+
Please pass the id of the object by calling `.id`
|
41
|
+
MSG
|
42
|
+
end
|
43
|
+
|
44
|
+
key = primary_key
|
45
|
+
|
46
|
+
statement = cached_find_by_statement(key) { |params|
|
48
47
|
where(key => params.bind).limit(1)
|
49
48
|
}
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
record = statement.execute([id], self, connection).first
|
50
|
+
unless record
|
51
|
+
raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
|
52
|
+
name, primary_key, id)
|
53
|
+
end
|
54
|
+
record
|
55
|
+
rescue RangeError
|
56
|
+
raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
|
57
|
+
name, primary_key)
|
54
58
|
end
|
55
|
-
record
|
56
|
-
rescue RangeError
|
57
|
-
raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -1,25 +1,27 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Fixture
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
silence_warnings do
|
4
|
+
def find
|
5
|
+
if model_class
|
6
|
+
# CPK
|
7
|
+
# model_class.unscoped do
|
8
|
+
# model_class.find(fixture[model_class.primary_key])
|
9
|
+
# end
|
10
|
+
model_class.unscoped do
|
11
|
+
ids = self.ids(model_class.primary_key)
|
12
|
+
model_class.find(ids)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
raise FixtureClassNotFound, "No class attached to find."
|
12
16
|
end
|
13
|
-
else
|
14
|
-
raise FixtureClassNotFound, "No class attached to find."
|
15
17
|
end
|
16
|
-
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def ids(key)
|
20
|
+
if key.is_a? Array
|
21
|
+
key.map {|a_key| fixture[a_key.to_s] }
|
22
|
+
else
|
23
|
+
fixture[key]
|
24
|
+
end
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -1,54 +1,58 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
1
|
+
module ActiveRecord
|
2
|
+
module Locking
|
3
|
+
module Optimistic
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
silence_warnings do
|
8
|
+
def _update_record(attribute_names = self.attribute_names) #:nodoc:
|
9
|
+
return super unless locking_enabled?
|
10
|
+
return 0 if attribute_names.empty?
|
11
|
+
|
12
|
+
lock_col = self.class.locking_column
|
13
|
+
previous_lock_value = send(lock_col).to_i
|
14
|
+
increment_lock
|
15
|
+
|
16
|
+
attribute_names += [lock_col]
|
17
|
+
attribute_names.uniq!
|
18
|
+
|
19
|
+
begin
|
20
|
+
relation = self.class.unscoped
|
21
|
+
|
22
|
+
if self.composite?
|
23
|
+
affected_rows = relation.where(
|
24
|
+
relation.cpk_id_predicate(relation.table, self.class.primary_key, id_was)
|
25
|
+
).where(
|
26
|
+
lock_col => previous_lock_value
|
27
|
+
).update_all(
|
28
|
+
attributes_for_update(attribute_names).map do |name|
|
29
|
+
[name, _read_attribute(name)]
|
30
|
+
end.to_h
|
31
|
+
)
|
32
|
+
else
|
33
|
+
affected_rows = relation.where(
|
34
|
+
self.class.primary_key => id,
|
35
|
+
lock_col => previous_lock_value,
|
36
|
+
).update_all(
|
37
|
+
attributes_for_update(attribute_names).map do |name|
|
38
|
+
[name, _read_attribute(name)]
|
39
|
+
end.to_h
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
unless affected_rows == 1
|
44
|
+
raise ActiveRecord::StaleObjectError.new(self, "update")
|
45
|
+
end
|
46
|
+
|
47
|
+
affected_rows
|
48
|
+
|
49
|
+
# If something went wrong, revert the version.
|
50
|
+
rescue Exception
|
51
|
+
send(lock_col + '=', previous_lock_value)
|
52
|
+
raise
|
50
53
|
end
|
51
54
|
end
|
55
|
+
end
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
@@ -8,70 +8,77 @@ module ActiveRecord
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
silence_warnings do
|
12
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
13
|
+
options = self.nested_attributes_options[association_name]
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
if attributes_collection.respond_to?(:permitted?)
|
16
|
+
attributes_collection = attributes_collection.to_h
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
+
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
20
|
+
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
21
|
+
end
|
19
22
|
|
20
|
-
|
21
|
-
keys = attributes_collection.keys
|
22
|
-
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
23
|
-
[attributes_collection]
|
24
|
-
else
|
25
|
-
attributes_collection.values
|
26
|
-
end
|
27
|
-
end
|
23
|
+
check_record_limit!(options[:limit], attributes_collection)
|
28
24
|
|
29
|
-
|
25
|
+
if attributes_collection.is_a? Hash
|
26
|
+
keys = attributes_collection.keys
|
27
|
+
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
28
|
+
[attributes_collection]
|
29
|
+
else
|
30
|
+
attributes_collection.values
|
31
|
+
end
|
32
|
+
end
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
association = association(association_name)
|
35
|
+
|
36
|
+
existing_records = if association.loaded?
|
37
|
+
association.target
|
38
|
+
# CPK
|
39
|
+
elsif association.reflection.klass.composite?
|
40
|
+
attributes_collection.map do |attribute_collection|
|
41
|
+
attribute_ids = attribute_collection['id'] || attribute_collection[:id]
|
42
|
+
if attribute_ids
|
43
|
+
ids = CompositePrimaryKeys::CompositeKeys.parse(attribute_ids)
|
44
|
+
eq_predicates = Class.new.extend(CompositePrimaryKeys::Predicates).cpk_id_predicate(association.klass.arel_table, association.klass.primary_key, ids)
|
45
|
+
association.scope.where(eq_predicates).to_a
|
46
|
+
else
|
47
|
+
[]
|
41
48
|
end
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
else
|
48
|
-
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
49
|
-
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
50
|
-
end
|
49
|
+
end.flatten.compact
|
50
|
+
else
|
51
|
+
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
52
|
+
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
53
|
+
end
|
51
54
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
unless reject_new_record?(association_name, attributes)
|
56
|
-
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
55
|
+
attributes_collection.each do |attributes|
|
56
|
+
if attributes.respond_to?(:permitted?)
|
57
|
+
attributes = attributes.to_h
|
57
58
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# Take into account that the proxy_target may have changed due to callbacks
|
63
|
-
target_record = cpk_detect_record(attributes['id'], association.target)
|
64
|
-
|
65
|
-
if target_record
|
66
|
-
existing_record = target_record
|
67
|
-
else
|
68
|
-
association.add_to_target(existing_record, :skip_callbacks)
|
59
|
+
attributes = attributes.with_indifferent_access
|
60
|
+
if attributes['id'].blank?
|
61
|
+
unless reject_new_record?(association_name, attributes)
|
62
|
+
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
69
63
|
end
|
64
|
+
elsif existing_record = cpk_detect_record(attributes['id'], existing_records)
|
65
|
+
unless call_reject_if(association_name, attributes)
|
66
|
+
# Make sure we are operating on the actual object which is in the association's
|
67
|
+
# proxy_target array (either by finding it, or adding it if not found)
|
68
|
+
# Take into account that the proxy_target may have changed due to callbacks
|
69
|
+
target_record = cpk_detect_record(attributes['id'], association.target)
|
70
70
|
|
71
|
-
|
71
|
+
if target_record
|
72
|
+
existing_record = target_record
|
73
|
+
else
|
74
|
+
association.add_to_target(existing_record, :skip_callbacks)
|
75
|
+
end
|
76
|
+
|
77
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
78
|
+
end
|
79
|
+
else
|
80
|
+
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
|
72
81
|
end
|
73
|
-
else
|
74
|
-
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
|
75
82
|
end
|
76
83
|
end
|
77
84
|
end
|