composite_primary_keys 5.0.9 → 5.0.10
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.
- data/History.rdoc +8 -0
- data/lib/composite_primary_keys.rb +3 -1
- data/lib/composite_primary_keys/attribute_methods/dirty.rb +2 -2
- data/lib/composite_primary_keys/base.rb +2 -2
- data/lib/composite_primary_keys/dirty.rb +2 -2
- data/lib/composite_primary_keys/persistence.rb +36 -13
- data/lib/composite_primary_keys/relation.rb +1 -0
- data/lib/composite_primary_keys/relation/batches.rb +63 -0
- data/lib/composite_primary_keys/version.rb +1 -1
- data/test/fixtures/db_definitions/postgresql.sql +6 -2
- data/test/fixtures/db_definitions/sqlite.sql +5 -1
- data/test/test_find.rb +7 -1
- data/test/test_ids.rb +2 -2
- data/test/test_touch.rb +23 -0
- data/test/test_update.rb +3 -3
- metadata +8 -5
data/History.rdoc
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
== 5.0.10 2012-11-21
|
2
|
+
* ActiveRecord 3.2.9 compatability (Tom Hughes, Chris Heald, Jack Tang)
|
3
|
+
* Add support for find_in_batches (Charlie Savage)
|
4
|
+
* Add support for touch method (Macario Ortega)
|
5
|
+
* Remove symbolized representation of primary keys to be consistent with ActiveRecord (Tiago Cardoso)
|
6
|
+
* Update destroy method to use hash of primary keys over an array. This allows the proper SQL
|
7
|
+
(using IS NULL) to be generated when one of the primary key attributes is nil (Scott Hunter).
|
8
|
+
|
1
9
|
== 5.0.9 2012-09-29
|
2
10
|
* Enable tests for MS SQL Servder (Enderson Maia)
|
3
11
|
* Update ActiveRecord::Base#initialize_dup override in line with ActiveRecord::Base 3.2.5+ (Lucas Maxwell)
|
@@ -26,7 +26,7 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
26
26
|
|
27
27
|
unless defined?(ActiveRecord)
|
28
28
|
require 'rubygems'
|
29
|
-
gem 'activerecord', '>= 3.2.
|
29
|
+
gem 'activerecord', '>= 3.2.9', '~> 3.2.0'
|
30
30
|
require 'active_record'
|
31
31
|
end
|
32
32
|
|
@@ -56,6 +56,7 @@ require 'active_record/attribute_methods/write'
|
|
56
56
|
|
57
57
|
require 'active_record/connection_adapters/abstract_adapter'
|
58
58
|
|
59
|
+
require 'active_record/relation/batches'
|
59
60
|
require 'active_record/relation/calculations'
|
60
61
|
require 'active_record/relation/finder_methods'
|
61
62
|
require 'active_record/relation/query_methods'
|
@@ -94,6 +95,7 @@ require 'composite_primary_keys/attribute_methods/write'
|
|
94
95
|
require 'composite_primary_keys/connection_adapters/abstract_adapter'
|
95
96
|
require 'composite_primary_keys/connection_adapters/abstract/connection_specification_changes'
|
96
97
|
|
98
|
+
require 'composite_primary_keys/relation/batches'
|
97
99
|
require 'composite_primary_keys/relation/calculations'
|
98
100
|
require 'composite_primary_keys/relation/finder_methods'
|
99
101
|
require 'composite_primary_keys/relation/query_methods'
|
@@ -13,12 +13,12 @@ module ActiveRecord
|
|
13
13
|
# The attribute already has an unsaved change.
|
14
14
|
if attribute_changed?(attr)
|
15
15
|
old = @changed_attributes[attr]
|
16
|
-
@changed_attributes.delete(attr) unless
|
16
|
+
@changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
|
17
17
|
else
|
18
18
|
old = clone_attribute_value(:read_attribute, attr)
|
19
19
|
# Save Time objects as TimeWithZone if time_zone_aware_attributes == true
|
20
20
|
old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
|
21
|
-
@changed_attributes[attr] = old if
|
21
|
+
@changed_attributes[attr] = old if _field_changed?(attr, old, value)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
return
|
28
28
|
end
|
29
29
|
|
30
|
-
@primary_keys = keys.map { |k| k.
|
30
|
+
@primary_keys = keys.map { |k| k.to_s }.to_composite_keys
|
31
31
|
|
32
32
|
class_eval <<-EOV
|
33
33
|
extend CompositeClassMethods
|
@@ -70,7 +70,7 @@ module ActiveRecord
|
|
70
70
|
|
71
71
|
@changed_attributes = {}
|
72
72
|
self.class.column_defaults.each do |attr, orig_value|
|
73
|
-
@changed_attributes[attr] = orig_value if
|
73
|
+
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
|
74
74
|
end
|
75
75
|
|
76
76
|
@aggregation_cache = {}
|
@@ -5,12 +5,12 @@ module ActiveModel
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def primary_key_changed?
|
8
|
-
!!changed.detect { |key| ids_hash.keys.include?(key.
|
8
|
+
!!changed.detect { |key| ids_hash.keys.include?(key.to_s) }
|
9
9
|
end
|
10
10
|
|
11
11
|
def primary_key_was
|
12
12
|
ids_hash.keys.inject(Hash.new) do |result, attribute_name|
|
13
|
-
result[attribute_name
|
13
|
+
result[attribute_name] = attribute_was(attribute_name.to_s)
|
14
14
|
result
|
15
15
|
end
|
16
16
|
end
|
@@ -10,24 +10,20 @@ module ActiveRecord
|
|
10
10
|
#column = self.class.columns_hash[pk]
|
11
11
|
#substitute = connection.substitute_at(column, 0)
|
12
12
|
|
13
|
+
where_hash = {}
|
13
14
|
primary_keys = Array(self.class.primary_key)
|
14
|
-
bind_values = Array.new
|
15
|
-
eq_predicates = Array.new
|
16
|
-
primary_keys.each_with_index do |key, i|
|
17
|
-
column = self.class.columns_hash[key.to_s]
|
18
|
-
bind_values << [column, self[key]]
|
19
|
-
substitute = connection.substitute_at(column, i)
|
20
|
-
eq_predicates << self.class.arel_table[key].eq(substitute)
|
21
|
-
end
|
22
|
-
predicate = Arel::Nodes::And.new(eq_predicates)
|
23
|
-
relation = self.class.unscoped.where(predicate)
|
24
15
|
|
25
16
|
#relation = self.class.unscoped.where(
|
26
17
|
# self.class.arel_table[pk].eq(substitute))
|
27
18
|
|
28
|
-
|
19
|
+
primary_keys.each do |key|
|
20
|
+
where_hash[key.to_s] = self[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
# CPK
|
29
24
|
#relation.bind_values = [[column, id]]
|
30
|
-
|
25
|
+
|
26
|
+
relation = self.class.unscoped.where(where_hash)
|
31
27
|
relation.delete_all
|
32
28
|
end
|
33
29
|
|
@@ -35,6 +31,33 @@ module ActiveRecord
|
|
35
31
|
freeze
|
36
32
|
end
|
37
33
|
|
34
|
+
def touch(name = nil)
|
35
|
+
attributes = timestamp_attributes_for_update_in_model
|
36
|
+
attributes << name if name
|
37
|
+
|
38
|
+
unless attributes.empty?
|
39
|
+
current_time = current_time_from_proper_timezone
|
40
|
+
changes = {}
|
41
|
+
|
42
|
+
attributes.each do |column|
|
43
|
+
column = column.to_s
|
44
|
+
changes[column] = write_attribute(column, current_time)
|
45
|
+
end
|
46
|
+
|
47
|
+
changes[self.class.locking_column] = increment_lock if locking_enabled?
|
48
|
+
|
49
|
+
@changed_attributes.except!(*changes.keys)
|
50
|
+
|
51
|
+
relation = self.class.send(:relation)
|
52
|
+
arel_table = self.class.arel_table
|
53
|
+
primary_key = self.class.primary_key
|
54
|
+
|
55
|
+
primary_key_predicate = relation.cpk_id_predicate(arel_table, Array(primary_key), Array(id))
|
56
|
+
|
57
|
+
self.class.unscoped.where(primary_key_predicate).update_all(changes) == 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
38
61
|
def update(attribute_names = @attributes.keys)
|
39
62
|
klass = self.class
|
40
63
|
if !self.composite?
|
@@ -56,4 +79,4 @@ module ActiveRecord
|
|
56
79
|
klass.connection.update stmt.to_sql
|
57
80
|
end
|
58
81
|
end
|
59
|
-
end
|
82
|
+
end
|
@@ -2,6 +2,7 @@ module ActiveRecord
|
|
2
2
|
class Relation
|
3
3
|
def add_cpk_support
|
4
4
|
class << self
|
5
|
+
include CompositePrimaryKeys::ActiveRecord::Batches
|
5
6
|
include CompositePrimaryKeys::ActiveRecord::Calculations
|
6
7
|
include CompositePrimaryKeys::ActiveRecord::FinderMethods
|
7
8
|
include CompositePrimaryKeys::ActiveRecord::QueryMethods
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module ActiveRecord
|
3
|
+
module Batches
|
4
|
+
def find_in_batches(options = {})
|
5
|
+
relation = self
|
6
|
+
|
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")
|
9
|
+
end
|
10
|
+
|
11
|
+
if (finder_options = options.except(:start, :batch_size)).present?
|
12
|
+
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
|
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)
|
16
|
+
end
|
17
|
+
|
18
|
+
start = options.delete(:start).to_i
|
19
|
+
batch_size = options.delete(:batch_size) || 1000
|
20
|
+
|
21
|
+
relation = relation.reorder(batch_order).limit(batch_size)
|
22
|
+
|
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
|
29
|
+
|
30
|
+
while records.any?
|
31
|
+
records_size = records.size
|
32
|
+
primary_key_offset = records.last.id
|
33
|
+
|
34
|
+
yield records
|
35
|
+
|
36
|
+
break if records_size < batch_size
|
37
|
+
|
38
|
+
if primary_key_offset
|
39
|
+
# 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
|
45
|
+
|
46
|
+
else
|
47
|
+
raise "Primary key not included in the custom select clause"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def batch_order
|
55
|
+
# CPK
|
56
|
+
#"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
57
|
+
self.primary_key.map do |key|
|
58
|
+
"#{quoted_table_name}.#{key} ASC"
|
59
|
+
end.join(",")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -18,14 +18,18 @@ create table reference_codes (
|
|
18
18
|
create table products (
|
19
19
|
id serial not null,
|
20
20
|
name varchar(50) default null,
|
21
|
-
primary key (id)
|
21
|
+
primary key (id),
|
22
|
+
created_at timestamp without time zone NOT NULL,
|
23
|
+
updated_at timestamp without time zone NOT NULL
|
22
24
|
);
|
23
25
|
|
24
26
|
create table tariffs (
|
25
27
|
tariff_id int not null,
|
26
28
|
start_date date not null,
|
27
29
|
amount int default null,
|
28
|
-
primary key (tariff_id, start_date)
|
30
|
+
primary key (tariff_id, start_date),
|
31
|
+
created_at timestamp without time zone NOT NULL,
|
32
|
+
updated_at timestamp without time zone NOT NULL
|
29
33
|
);
|
30
34
|
|
31
35
|
create table product_tariffs (
|
@@ -16,13 +16,17 @@ create table reference_codes (
|
|
16
16
|
|
17
17
|
create table products (
|
18
18
|
id int(11) not null primary key,
|
19
|
-
name varchar(50) default null
|
19
|
+
name varchar(50) default null,
|
20
|
+
created_at TIMESTAMP,
|
21
|
+
updated_at TIMESTAMP
|
20
22
|
);
|
21
23
|
|
22
24
|
create table tariffs (
|
23
25
|
tariff_id int(11) not null,
|
24
26
|
start_date date not null,
|
25
27
|
amount integer(11) default null,
|
28
|
+
created_at TIMESTAMP,
|
29
|
+
updated_at TIMESTAMP,
|
26
30
|
primary key (tariff_id, start_date)
|
27
31
|
);
|
28
32
|
|
data/test/test_find.rb
CHANGED
@@ -2,7 +2,7 @@ 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 TestFind < ActiveSupport::TestCase
|
5
|
-
fixtures :capitols, :reference_types, :reference_codes, :suburbs
|
5
|
+
fixtures :capitols, :departments, :reference_types, :reference_codes, :suburbs
|
6
6
|
|
7
7
|
def test_find_first
|
8
8
|
ref_code = ReferenceCode.find(:first, :order => 'reference_type_id, reference_code')
|
@@ -75,4 +75,10 @@ class TestFind < ActiveSupport::TestCase
|
|
75
75
|
suburb = Suburb.find(:last, :order => 'suburbs.city_id DESC')
|
76
76
|
assert_equal([1,1], suburb.id)
|
77
77
|
end
|
78
|
+
|
79
|
+
def test_find_in_batchs
|
80
|
+
departments = Department.find_in_batches do |batch|
|
81
|
+
assert_equal(2, batch.size)
|
82
|
+
end
|
83
|
+
end
|
78
84
|
end
|
data/test/test_ids.rb
CHANGED
@@ -78,7 +78,7 @@ class TestIds < ActiveSupport::TestCase
|
|
78
78
|
testing_with do
|
79
79
|
if composite?
|
80
80
|
assert_not_nil @klass.primary_keys
|
81
|
-
assert_equal @primary_keys.map {|key| key.
|
81
|
+
assert_equal @primary_keys.map {|key| key.to_s}, @klass.primary_keys
|
82
82
|
assert_equal @klass.primary_keys, @klass.primary_key
|
83
83
|
assert_kind_of(CompositePrimaryKeys::CompositeKeys, @klass.primary_keys)
|
84
84
|
assert_equal @primary_keys.map {|key| key.to_sym}.join(','), @klass.primary_key.to_s
|
@@ -91,7 +91,7 @@ class TestIds < ActiveSupport::TestCase
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def test_inherited_primary_keys
|
94
|
-
assert_equal([
|
94
|
+
assert_equal(["reference_type_id", "reference_code"], ChildCpkTest.primary_keys)
|
95
95
|
end
|
96
96
|
|
97
97
|
def test_inherited_ids
|
data/test/test_touch.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Test cases devised by Santiago that broke the Composite Primary Keys
|
2
|
+
# code at one point in time. But no more!!!
|
3
|
+
require File.expand_path('../abstract_unit', __FILE__)
|
4
|
+
|
5
|
+
class TestTouch < ActiveSupport::TestCase
|
6
|
+
fixtures :products, :tariffs
|
7
|
+
|
8
|
+
def test_touching_a_record_updates_its_timestamp
|
9
|
+
tariff = tariffs(:flat)
|
10
|
+
previous_amount = tariff.amount
|
11
|
+
previously_updated_at = tariff.updated_at
|
12
|
+
|
13
|
+
tariff.amount = previous_amount + 1
|
14
|
+
tariff.touch
|
15
|
+
|
16
|
+
assert_not_equal previously_updated_at, tariff.updated_at
|
17
|
+
assert_equal previous_amount + 1, tariff.amount
|
18
|
+
assert tariff.amount_changed?, 'tarif amount should have changed'
|
19
|
+
assert tariff.changed?, 'tarif should be marked as changed'
|
20
|
+
tariff.reload
|
21
|
+
assert_not_equal previously_updated_at, tariff.updated_at
|
22
|
+
end
|
23
|
+
end
|
data/test/test_update.rb
CHANGED
@@ -41,13 +41,13 @@ class TestUpdate < ActiveSupport::TestCase
|
|
41
41
|
obj.reference_type_id = 2
|
42
42
|
obj.reference_code = 3
|
43
43
|
assert(obj.primary_key_changed?)
|
44
|
-
assert_equal({
|
45
|
-
assert_equal({
|
44
|
+
assert_equal({"reference_type_id" => 1, "reference_code" => 1}, obj.primary_key_was)
|
45
|
+
assert_equal({"reference_type_id" => 2, "reference_code" => 3}, obj.ids_hash)
|
46
46
|
assert(obj.save)
|
47
47
|
assert(obj.reload)
|
48
48
|
assert_equal(2, obj.reference_type_id)
|
49
49
|
assert_equal(3, obj.reference_code)
|
50
|
-
assert_equal({
|
50
|
+
assert_equal({"reference_type_id" => 2, "reference_code" => 3}, obj.ids_hash)
|
51
51
|
assert_equal([2, 3], obj.id)
|
52
52
|
end
|
53
53
|
|
metadata
CHANGED
@@ -1,7 +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.
|
4
|
+
version: 5.0.10
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-11-21 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 3.2.
|
22
|
+
version: 3.2.9
|
23
23
|
- - ~>
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: 3.2.0
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.2.
|
33
|
+
version: 3.2.9
|
34
34
|
- - ~>
|
35
35
|
- !ruby/object:Gem::Version
|
36
36
|
version: 3.2.0
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- lib/composite_primary_keys/dirty.rb
|
71
71
|
- lib/composite_primary_keys/fixtures.rb
|
72
72
|
- lib/composite_primary_keys/persistence.rb
|
73
|
+
- lib/composite_primary_keys/relation/batches.rb
|
73
74
|
- lib/composite_primary_keys/relation/calculations.rb
|
74
75
|
- lib/composite_primary_keys/relation/finder_methods.rb
|
75
76
|
- lib/composite_primary_keys/relation/query_methods.rb
|
@@ -187,6 +188,7 @@ files:
|
|
187
188
|
- test/test_predicates.rb
|
188
189
|
- test/test_santiago.rb
|
189
190
|
- test/test_suite.rb
|
191
|
+
- test/test_touch.rb
|
190
192
|
- test/test_tutorial_example.rb
|
191
193
|
- test/test_update.rb
|
192
194
|
- test/test_validations.rb
|
@@ -210,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
212
|
version: '0'
|
211
213
|
requirements: []
|
212
214
|
rubyforge_project: compositekeys
|
213
|
-
rubygems_version: 1.8.
|
215
|
+
rubygems_version: 1.8.24
|
214
216
|
signing_key:
|
215
217
|
specification_version: 3
|
216
218
|
summary: Composite key support for ActiveRecord
|
@@ -240,6 +242,7 @@ test_files:
|
|
240
242
|
- test/test_predicates.rb
|
241
243
|
- test/test_santiago.rb
|
242
244
|
- test/test_suite.rb
|
245
|
+
- test/test_touch.rb
|
243
246
|
- test/test_tutorial_example.rb
|
244
247
|
- test/test_update.rb
|
245
248
|
- test/test_validations.rb
|