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.
@@ -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