composite_primary_keys 5.0.12 → 5.0.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/History.rdoc +5 -0
- data/lib/composite_primary_keys.rb +2 -0
- data/lib/composite_primary_keys/base.rb +2 -3
- data/lib/composite_primary_keys/composite_arrays.rb +11 -0
- data/lib/composite_primary_keys/nested_attributes.rb +78 -0
- data/lib/composite_primary_keys/relation/batches.rb +29 -12
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/connections/databases.yml +0 -1
- data/test/fixtures/departments.yml +8 -0
- data/test/fixtures/room_assignments.yml +16 -1
- data/test/fixtures/rooms.yml +6 -1
- data/test/fixtures/students.yml +11 -1
- data/test/test_associations.rb +4 -3
- data/test/test_find.rb +20 -4
- data/test/test_nested_attributes.rb +36 -9
- metadata +6 -9
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTVjMmVhMjcyMDljMDU3NWRjZTNhMzEyOTlhMDZhYTg1MTk0NDUxYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDIxYzNmNjc2ZWZiMDdjZThjYTM2YzQ3MjVjODJiYjBmYzcwYzYxOQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YjI2NjIwOWUzZGQyNDY3NTZhZTBiNjNjOWZlOTA5NDUyZTE2MWY4Y2UwNDAz
|
10
|
+
ZWZlMWEwMmZjNDQ4ODc2OTI2YTk1YmIxMDU2YjY2MTlkOThhMTFlODdmNTE1
|
11
|
+
MjRkNzhjMDA1YjRmZDZhODFkOTQ5MzhmM2U5ZTU2MzlhMWZlYWI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NWZhZDQ3ZjhmNmNiZmFhZTI2YmZiYWJjNTE0OTA0ZWQxZjA3MzAzZTY2NzIz
|
14
|
+
OTU0YjA2NDAxZmY3MWQ4ZTAwMjgwZDdhMWNjZjk0OGViYzdmNjRjZjk4MGZj
|
15
|
+
NDEyMGFhMGNiNmZlZjY2YTJjMmVjMmQ1ZjI0MzgxOTcxYjE5ZDI=
|
data/History.rdoc
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== 5.0.13 2013-04-27
|
2
|
+
* Batch query improvements (Jordan Byron)
|
3
|
+
* Support nested attributes (aiasfina and Charlie Savage)
|
4
|
+
* ActiveRecord 4.0 support in ar_4.0.x branch
|
5
|
+
|
1
6
|
== 5.0.12 2013-01-21
|
2
7
|
* Fix HasManyAssociation foreign_key_present? method to work with non CPK assocations (Charlie Savage)
|
3
8
|
|
@@ -53,6 +53,7 @@ require 'active_model/dirty'
|
|
53
53
|
require 'active_record/attribute_methods/dirty'
|
54
54
|
require 'active_record/attribute_methods/read'
|
55
55
|
require 'active_record/attribute_methods/write'
|
56
|
+
require 'active_record/nested_attributes'
|
56
57
|
|
57
58
|
require 'active_record/connection_adapters/abstract_adapter'
|
58
59
|
|
@@ -91,6 +92,7 @@ require 'composite_primary_keys/dirty'
|
|
91
92
|
require 'composite_primary_keys/attribute_methods/dirty'
|
92
93
|
require 'composite_primary_keys/attribute_methods/read'
|
93
94
|
require 'composite_primary_keys/attribute_methods/write'
|
95
|
+
require 'composite_primary_keys/nested_attributes'
|
94
96
|
|
95
97
|
require 'composite_primary_keys/connection_adapters/abstract_adapter'
|
96
98
|
require 'composite_primary_keys/connection_adapters/abstract/connection_specification_changes'
|
@@ -127,9 +127,8 @@ module ActiveRecord
|
|
127
127
|
|
128
128
|
# Sets the primary ID.
|
129
129
|
def id=(ids)
|
130
|
-
ids =
|
131
|
-
ids.
|
132
|
-
unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length
|
130
|
+
ids = CompositePrimaryKeys::CompositeKeys.parse(ids)
|
131
|
+
unless ids.length == self.class.primary_keys.length
|
133
132
|
raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
|
134
133
|
end
|
135
134
|
[self.class.primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
|
@@ -9,6 +9,17 @@ module CompositePrimaryKeys
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class CompositeKeys < Array
|
12
|
+
def self.parse(value)
|
13
|
+
case value
|
14
|
+
when Array
|
15
|
+
value.to_composite_keys
|
16
|
+
when String
|
17
|
+
self.new(value.split(ID_SEP))
|
18
|
+
else
|
19
|
+
raise(ArgumentError, "Unsupported type: #{value}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
12
23
|
def to_s
|
13
24
|
# Doing this makes it easier to parse Base#[](attr_name)
|
14
25
|
join(ID_SEP)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module NestedAttributes
|
3
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
|
4
|
+
options = self.nested_attributes_options[association_name]
|
5
|
+
|
6
|
+
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
7
|
+
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
8
|
+
end
|
9
|
+
|
10
|
+
if options[:limit] && attributes_collection.size > options[:limit]
|
11
|
+
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
|
12
|
+
end
|
13
|
+
|
14
|
+
if attributes_collection.is_a? Hash
|
15
|
+
keys = attributes_collection.keys
|
16
|
+
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
17
|
+
Array.wrap(attributes_collection)
|
18
|
+
else
|
19
|
+
attributes_collection.values
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
association = association(association_name)
|
24
|
+
|
25
|
+
existing_records = if association.loaded?
|
26
|
+
association.target
|
27
|
+
# CPK
|
28
|
+
elsif association.reflection.klass.composite?
|
29
|
+
attributes_collection.map do |attribute_collection|
|
30
|
+
attribute_ids = attribute_collection['id'] || attribute_collection[:id]
|
31
|
+
if attribute_ids
|
32
|
+
ids = CompositePrimaryKeys::CompositeKeys.parse(attribute_ids)
|
33
|
+
eq_predicates = association.klass.primary_key.zip(ids).map do |primary_key, value|
|
34
|
+
association.klass.arel_table[primary_key].eq(value)
|
35
|
+
end
|
36
|
+
association.scoped.where(*eq_predicates).to_a
|
37
|
+
else
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
end.flatten.compact
|
41
|
+
else
|
42
|
+
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
43
|
+
attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
|
44
|
+
end
|
45
|
+
|
46
|
+
attributes_collection.each do |attributes|
|
47
|
+
attributes = attributes.with_indifferent_access
|
48
|
+
|
49
|
+
if attributes['id'].blank?
|
50
|
+
unless reject_new_record?(association_name, attributes)
|
51
|
+
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
|
52
|
+
end
|
53
|
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
54
|
+
unless association.loaded? || call_reject_if(association_name, attributes)
|
55
|
+
# Make sure we are operating on the actual object which is in the association's
|
56
|
+
# proxy_target array (either by finding it, or adding it if not found)
|
57
|
+
target_record = association.target.detect { |record| record == existing_record }
|
58
|
+
|
59
|
+
if target_record
|
60
|
+
existing_record = target_record
|
61
|
+
else
|
62
|
+
association.add_to_target(existing_record)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
if !call_reject_if(association_name, attributes)
|
68
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
|
69
|
+
end
|
70
|
+
elsif assignment_opts[:without_protection]
|
71
|
+
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
|
72
|
+
else
|
73
|
+
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -5,7 +5,7 @@ module CompositePrimaryKeys
|
|
5
5
|
relation = self
|
6
6
|
|
7
7
|
unless arel.orders.blank? && arel.taken.blank?
|
8
|
-
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
8
|
+
::ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
9
9
|
end
|
10
10
|
|
11
11
|
if (finder_options = options.except(:start, :batch_size)).present?
|
@@ -21,11 +21,10 @@ module CompositePrimaryKeys
|
|
21
21
|
relation = relation.reorder(batch_order).limit(batch_size)
|
22
22
|
|
23
23
|
# CPK
|
24
|
-
#records = relation.where(table[primary_key].gteq(start)).all
|
25
|
-
self.primary_key.
|
26
|
-
|
27
|
-
end
|
28
|
-
records = relation.all
|
24
|
+
# records = relation.where(table[primary_key].gteq(start)).all
|
25
|
+
records = self.primary_key.reduce(relation) do |rel, key|
|
26
|
+
rel.where(table[key].gteq(start))
|
27
|
+
end.all
|
29
28
|
|
30
29
|
while records.any?
|
31
30
|
records_size = records.size
|
@@ -37,12 +36,23 @@ module CompositePrimaryKeys
|
|
37
36
|
|
38
37
|
if primary_key_offset
|
39
38
|
# CPK
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
# Lexicographically select records
|
40
|
+
#
|
41
|
+
query = prefixes(primary_key.zip(primary_key_offset)).map do |kvs|
|
42
|
+
and_clause = kvs.each_with_index.map do |(k, v), i|
|
43
|
+
# Use > for the last key in the and clause
|
44
|
+
# otherwise use =
|
45
|
+
if i == kvs.length - 1
|
46
|
+
table[k].gt(v)
|
47
|
+
else
|
48
|
+
table[k].eq(v)
|
49
|
+
end
|
50
|
+
end.reduce(:and)
|
51
|
+
|
52
|
+
Arel::Nodes::Grouping.new(and_clause)
|
53
|
+
end.reduce(:or)
|
45
54
|
|
55
|
+
records = relation.where(query)
|
46
56
|
else
|
47
57
|
raise "Primary key not included in the custom select clause"
|
48
58
|
end
|
@@ -51,6 +61,13 @@ module CompositePrimaryKeys
|
|
51
61
|
|
52
62
|
private
|
53
63
|
|
64
|
+
# Helper method to collect prefixes of an array:
|
65
|
+
# prefixes([:a, :b, :c]) => [[:a], [:a, :b], [:a, :b, :c]]
|
66
|
+
#
|
67
|
+
def prefixes(ary)
|
68
|
+
ary.length.times.reduce([]) { |results, i| results << ary[0..i] }
|
69
|
+
end
|
70
|
+
|
54
71
|
def batch_order
|
55
72
|
# CPK
|
56
73
|
#"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
@@ -60,4 +77,4 @@ module CompositePrimaryKeys
|
|
60
77
|
end
|
61
78
|
end
|
62
79
|
end
|
63
|
-
end
|
80
|
+
end
|
@@ -6,4 +6,19 @@ jacksons_room:
|
|
6
6
|
bobs_room:
|
7
7
|
student_id: 2
|
8
8
|
dorm_id: 1
|
9
|
-
room_id: 1
|
9
|
+
room_id: 1
|
10
|
+
|
11
|
+
kellys_room:
|
12
|
+
student_id: 3
|
13
|
+
dorm_id: 1
|
14
|
+
room_id: 2
|
15
|
+
|
16
|
+
jordans_room:
|
17
|
+
student_id: 4
|
18
|
+
dorm_id: 1
|
19
|
+
room_id: 2
|
20
|
+
|
21
|
+
brads_room:
|
22
|
+
student_id: 5
|
23
|
+
dorm_id: 1
|
24
|
+
room_id: 3
|
data/test/fixtures/rooms.yml
CHANGED
data/test/fixtures/students.yml
CHANGED
data/test/test_associations.rb
CHANGED
@@ -4,7 +4,7 @@ class TestAssociations < ActiveSupport::TestCase
|
|
4
4
|
fixtures :articles, :products, :tariffs, :product_tariffs, :suburbs, :streets, :restaurants,
|
5
5
|
:dorms, :rooms, :room_attributes, :room_attribute_assignments, :students, :room_assignments, :users, :readings,
|
6
6
|
:departments, :employees, :memberships, :membership_statuses
|
7
|
-
|
7
|
+
|
8
8
|
def test_products
|
9
9
|
assert_not_nil products(:first_product).product_tariffs
|
10
10
|
assert_equal 2, products(:first_product).product_tariffs.length
|
@@ -131,7 +131,7 @@ class TestAssociations < ActiveSupport::TestCase
|
|
131
131
|
|
132
132
|
def test_has_many_through_when_through_association_is_composite
|
133
133
|
dorm = Dorm.find(:first)
|
134
|
-
assert_equal(
|
134
|
+
assert_equal(3, dorm.rooms.length)
|
135
135
|
assert_equal(1, dorm.rooms.first.room_attributes.length)
|
136
136
|
assert_equal('type', dorm.rooms.first.room_attributes.first.name)
|
137
137
|
end
|
@@ -213,9 +213,10 @@ class TestAssociations < ActiveSupport::TestCase
|
|
213
213
|
def test_has_many_with_composite_key
|
214
214
|
# In this case a regular model (Dorm) has_many composite models (Rooms)
|
215
215
|
dorm = dorms(:branner)
|
216
|
-
assert_equal(
|
216
|
+
assert_equal(3, dorm.rooms.length)
|
217
217
|
assert_equal(1, dorm.rooms[0].room_id)
|
218
218
|
assert_equal(2, dorm.rooms[1].room_id)
|
219
|
+
assert_equal(3, dorm.rooms[2].room_id)
|
219
220
|
end
|
220
221
|
|
221
222
|
def test_joins_has_many_with_primary_key
|
data/test/test_find.rb
CHANGED
@@ -46,6 +46,22 @@ class TestFind < ActiveSupport::TestCase
|
|
46
46
|
assert_equal(['The Netherlands', 'Amsterdam'], capitol.id)
|
47
47
|
end
|
48
48
|
|
49
|
+
def test_find_each
|
50
|
+
room_assignments = []
|
51
|
+
RoomAssignment.find_each(:batch_size => 2) do |assignment|
|
52
|
+
room_assignments << assignment
|
53
|
+
end
|
54
|
+
|
55
|
+
assert_equal(RoomAssignment.count, room_assignments.uniq.length)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_find_each_with_scope
|
59
|
+
scoped_departments = Department.where("department_id <> 3")
|
60
|
+
scoped_departments.find_each(:batch_size => 2) do |department|
|
61
|
+
assert department.id != 3
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
49
65
|
def test_not_found
|
50
66
|
error = assert_raise(::ActiveRecord::RecordNotFound) do
|
51
67
|
ReferenceCode.find(['999', '999'])
|
@@ -76,9 +92,9 @@ class TestFind < ActiveSupport::TestCase
|
|
76
92
|
assert_equal([1,1], suburb.id)
|
77
93
|
end
|
78
94
|
|
79
|
-
def
|
80
|
-
|
81
|
-
assert_equal(
|
95
|
+
def test_find_in_batches
|
96
|
+
Department.find_in_batches do |batch|
|
97
|
+
assert_equal(Department.count, batch.size)
|
82
98
|
end
|
83
99
|
end
|
84
|
-
end
|
100
|
+
end
|
@@ -2,15 +2,13 @@ require File.expand_path('../abstract_unit', __FILE__)
|
|
2
2
|
|
3
3
|
# Testing the find action on composite ActiveRecords with two primary keys
|
4
4
|
class TestNestedAttributes < ActiveSupport::TestCase
|
5
|
-
fixtures :reference_types
|
6
|
-
|
7
|
-
def setup
|
8
|
-
@reference_type = ReferenceType.first
|
9
|
-
end
|
5
|
+
fixtures :reference_types, :reference_codes
|
10
6
|
|
11
7
|
def test_nested_atttribute_create
|
12
8
|
code_id = 1001
|
13
|
-
|
9
|
+
|
10
|
+
reference_type = reference_types(:name_prefix)
|
11
|
+
reference_type.update_attribute :reference_codes_attributes, [{
|
14
12
|
:reference_code => code_id,
|
15
13
|
:code_label => 'XX',
|
16
14
|
:abbreviation => 'Xx'
|
@@ -20,14 +18,17 @@ class TestNestedAttributes < ActiveSupport::TestCase
|
|
20
18
|
|
21
19
|
def test_nested_atttribute_update
|
22
20
|
code_id = 1002
|
23
|
-
|
21
|
+
|
22
|
+
reference_type = reference_types(:name_prefix)
|
23
|
+
reference_type.update_attribute :reference_codes_attributes, [{
|
24
24
|
:reference_code => code_id,
|
25
25
|
:code_label => 'XX',
|
26
26
|
:abbreviation => 'Xx'
|
27
27
|
}]
|
28
|
+
|
28
29
|
reference_code = ReferenceCode.find_by_reference_code(code_id)
|
29
|
-
cpk = CompositePrimaryKeys::CompositeKeys[
|
30
|
-
|
30
|
+
cpk = CompositePrimaryKeys::CompositeKeys[reference_type.reference_type_id, code_id]
|
31
|
+
reference_type.update_attribute :reference_codes_attributes, [{
|
31
32
|
:id => cpk,
|
32
33
|
:code_label => 'AAA',
|
33
34
|
:abbreviation => 'Aaa'
|
@@ -36,4 +37,30 @@ class TestNestedAttributes < ActiveSupport::TestCase
|
|
36
37
|
assert_kind_of(ReferenceCode, reference_code)
|
37
38
|
assert_equal(reference_code.code_label, 'AAA')
|
38
39
|
end
|
40
|
+
|
41
|
+
def test_nested_atttribute_update_2
|
42
|
+
reference_type = reference_types(:gender)
|
43
|
+
reference_code = reference_codes(:gender_male)
|
44
|
+
|
45
|
+
reference_type.update_attributes(:reference_codes_attributes => [{:id => reference_code.id,
|
46
|
+
:code_label => 'XX',
|
47
|
+
:abbreviation => 'Xx'}])
|
48
|
+
|
49
|
+
reference_code.reload
|
50
|
+
assert_equal(reference_code.code_label, 'XX')
|
51
|
+
assert_equal(reference_code.abbreviation, 'Xx')
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_nested_atttribute_update_3
|
55
|
+
reference_type = reference_types(:gender)
|
56
|
+
reference_code = reference_codes(:gender_male)
|
57
|
+
|
58
|
+
reference_type.update_attributes(:reference_codes_attributes => [{:id => reference_code.id.to_s,
|
59
|
+
:code_label => 'XX',
|
60
|
+
:abbreviation => 'Xx'}])
|
61
|
+
|
62
|
+
reference_code.reload
|
63
|
+
assert_equal(reference_code.code_label, 'XX')
|
64
|
+
assert_equal(reference_code.abbreviation, 'Xx')
|
65
|
+
end
|
39
66
|
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: composite_primary_keys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.
|
5
|
-
prerelease:
|
4
|
+
version: 5.0.13
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Dr Nic Williams
|
@@ -10,12 +9,11 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2013-
|
12
|
+
date: 2013-04-27 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: activerecord
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
18
|
- - ! '>='
|
21
19
|
- !ruby/object:Gem::Version
|
@@ -26,7 +24,6 @@ dependencies:
|
|
26
24
|
type: :runtime
|
27
25
|
prerelease: false
|
28
26
|
version_requirements: !ruby/object:Gem::Requirement
|
29
|
-
none: false
|
30
27
|
requirements:
|
31
28
|
- - ! '>='
|
32
29
|
- !ruby/object:Gem::Version
|
@@ -69,6 +66,7 @@ files:
|
|
69
66
|
- lib/composite_primary_keys/counter_cache.rb
|
70
67
|
- lib/composite_primary_keys/dirty.rb
|
71
68
|
- lib/composite_primary_keys/fixtures.rb
|
69
|
+
- lib/composite_primary_keys/nested_attributes.rb
|
72
70
|
- lib/composite_primary_keys/persistence.rb
|
73
71
|
- lib/composite_primary_keys/relation/batches.rb
|
74
72
|
- lib/composite_primary_keys/relation/calculations.rb
|
@@ -195,27 +193,26 @@ files:
|
|
195
193
|
- test/test_validations.rb
|
196
194
|
homepage: https://github.com/drnic/composite_primary_keys
|
197
195
|
licenses: []
|
196
|
+
metadata: {}
|
198
197
|
post_install_message:
|
199
198
|
rdoc_options: []
|
200
199
|
require_paths:
|
201
200
|
- lib
|
202
201
|
required_ruby_version: !ruby/object:Gem::Requirement
|
203
|
-
none: false
|
204
202
|
requirements:
|
205
203
|
- - ! '>='
|
206
204
|
- !ruby/object:Gem::Version
|
207
205
|
version: 1.8.7
|
208
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
209
|
-
none: false
|
210
207
|
requirements:
|
211
208
|
- - ! '>='
|
212
209
|
- !ruby/object:Gem::Version
|
213
210
|
version: '0'
|
214
211
|
requirements: []
|
215
212
|
rubyforge_project: compositekeys
|
216
|
-
rubygems_version:
|
213
|
+
rubygems_version: 2.0.3
|
217
214
|
signing_key:
|
218
|
-
specification_version:
|
215
|
+
specification_version: 4
|
219
216
|
summary: Composite key support for ActiveRecord
|
220
217
|
test_files:
|
221
218
|
- test/abstract_unit.rb
|