composite_primary_keys 5.0.12 → 5.0.13

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.
@@ -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=
@@ -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 = ids.split(CompositePrimaryKeys::ID_SEP) if ids.is_a?(String)
131
- ids.flatten!
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.each do |key|
26
- relation = relation.where(table[key].gteq(start))
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
- #records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
41
- self.primary_key.each do |key|
42
- relation = relation.where(table[key].gt(primary_key_offset))
43
- end
44
- records = relation.to_a
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
@@ -2,7 +2,7 @@ module CompositePrimaryKeys
2
2
  module VERSION
3
3
  MAJOR = 5
4
4
  MINOR = 0
5
- TINY = 12
5
+ TINY = 13
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -5,7 +5,6 @@
5
5
  mysql:
6
6
  adapter: mysql2
7
7
  username: root
8
- password: root
9
8
  database: composite_primary_keys_unittest
10
9
 
11
10
  sqlite3:
@@ -5,3 +5,11 @@ accounting:
5
5
  engineering:
6
6
  department_id: 2
7
7
  location_id: 1
8
+
9
+ human_resources:
10
+ department_id: 3
11
+ location_id: 2
12
+
13
+ offsite_accounting:
14
+ department_id: 1
15
+ location_id: 2
@@ -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
@@ -4,4 +4,9 @@ branner_room_1:
4
4
 
5
5
  branner_room_2:
6
6
  dorm_id: 1
7
- room_id: 2
7
+ room_id: 2
8
+
9
+ branner_room_3:
10
+ dorm_id: 1
11
+ room_id: 3
12
+
@@ -2,4 +2,14 @@ jackson:
2
2
  id: 1
3
3
 
4
4
  bob:
5
- id: 2
5
+ id: 2
6
+
7
+ kelly:
8
+ id: 3
9
+
10
+ jordan:
11
+ id: 4
12
+
13
+ brad:
14
+ id: 5
15
+
@@ -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(2, dorm.rooms.length)
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(2, dorm.rooms.length)
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
@@ -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 test_find_in_batchs
80
- departments = Department.find_in_batches do |batch|
81
- assert_equal(2, batch.size)
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
- @reference_type.update_attribute :reference_codes_attributes, [{
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
- @reference_type.update_attribute :reference_codes_attributes, [{
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[@reference_type.reference_type_id, code_id]
30
- @reference_type.update_attribute :reference_codes_attributes, [{
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.12
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-01-21 00:00:00.000000000 Z
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: 1.8.24
213
+ rubygems_version: 2.0.3
217
214
  signing_key:
218
- specification_version: 3
215
+ specification_version: 4
219
216
  summary: Composite key support for ActiveRecord
220
217
  test_files:
221
218
  - test/abstract_unit.rb