composite_primary_keys 6.0.8 → 7.0.0

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +2 -8
  3. data/lib/composite_primary_keys.rb +9 -11
  4. data/lib/composite_primary_keys/active_model/dirty.rb +14 -0
  5. data/lib/composite_primary_keys/associations/association_scope.rb +30 -32
  6. data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +1 -2
  7. data/lib/composite_primary_keys/associations/has_many_association.rb +13 -14
  8. data/lib/composite_primary_keys/associations/has_many_through_association.rb +88 -0
  9. data/lib/composite_primary_keys/associations/join_dependency.rb +23 -15
  10. data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +3 -3
  11. data/lib/composite_primary_keys/associations/preloader/association.rb +37 -21
  12. data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +9 -3
  13. data/lib/composite_primary_keys/base.rb +0 -1
  14. data/lib/composite_primary_keys/composite_predicates.rb +11 -34
  15. data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +6 -5
  16. data/lib/composite_primary_keys/core.rb +28 -16
  17. data/lib/composite_primary_keys/model_schema.rb +15 -0
  18. data/lib/composite_primary_keys/nested_attributes.rb +8 -10
  19. data/lib/composite_primary_keys/persistence.rb +28 -29
  20. data/lib/composite_primary_keys/relation.rb +86 -16
  21. data/lib/composite_primary_keys/relation/finder_methods.rb +113 -59
  22. data/lib/composite_primary_keys/version.rb +2 -2
  23. data/test/abstract_unit.rb +7 -8
  24. data/test/fixtures/db_definitions/db2-create-tables.sql +13 -20
  25. data/test/fixtures/db_definitions/db2-drop-tables.sql +13 -14
  26. data/test/fixtures/db_definitions/mysql.sql +0 -7
  27. data/test/fixtures/db_definitions/oracle.drop.sql +0 -1
  28. data/test/fixtures/db_definitions/oracle.sql +0 -6
  29. data/test/fixtures/db_definitions/postgresql.sql +0 -7
  30. data/test/fixtures/db_definitions/sqlite.sql +24 -30
  31. data/test/fixtures/db_definitions/sqlserver.drop.sql +1 -4
  32. data/test/fixtures/db_definitions/sqlserver.sql +7 -16
  33. data/test/fixtures/dorm.rb +2 -2
  34. data/test/fixtures/suburb.rb +2 -2
  35. data/test/test_associations.rb +1 -2
  36. data/test/test_attribute_methods.rb +3 -3
  37. data/test/test_delete.rb +3 -5
  38. data/test/test_equal.rb +5 -5
  39. data/test/test_find.rb +2 -14
  40. data/test/test_predicates.rb +2 -2
  41. data/test/test_santiago.rb +1 -1
  42. data/test/test_serialize.rb +1 -1
  43. data/test/test_suite.rb +0 -1
  44. data/test/test_tutorial_example.rb +3 -3
  45. metadata +9 -32
  46. data/lib/composite_primary_keys/attribute_methods/primary_key.rb +0 -17
  47. data/lib/composite_primary_keys/composite_relation.rb +0 -44
  48. data/lib/composite_primary_keys/locking/optimistic.rb +0 -51
  49. data/test/fixtures/model_with_callback.rb +0 -39
  50. data/test/fixtures/model_with_callbacks.yml +0 -3
  51. data/test/test_callbacks.rb +0 -36
  52. data/test/test_dumpable.rb +0 -15
  53. data/test/test_optimistic.rb +0 -18
@@ -1,94 +1,132 @@
1
1
  module CompositePrimaryKeys
2
2
  module ActiveRecord
3
3
  module FinderMethods
4
- def construct_limited_ids_condition(relation)
5
- orders = relation.order_values
6
- # CPK
7
- # values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
8
- keys = @klass.primary_keys.map do |key|
9
- "#{@klass.connection.quote_table_name @klass.table_name}.#{key}"
4
+ def apply_join_dependency(relation, join_dependency)
5
+ relation = relation.except(:includes, :eager_load, :preload)
6
+ relation = relation.joins join_dependency
7
+
8
+ if using_limitable_reflections?(join_dependency.reflections)
9
+ relation
10
+ else
11
+ if relation.limit_value
12
+ limited_ids = limited_ids_for(relation)
13
+ # CPK
14
+ #limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
15
+ limited_ids.empty? ? relation.none! : relation.where!(cpk_in_predicate(table, self.primary_keys, limited_ids))
16
+ end
17
+ relation.except(:limit, :offset)
10
18
  end
11
- values = @klass.connection.distinct(keys.join(', '), orders)
12
-
13
- relation = relation.dup
19
+ end
14
20
 
15
- ids_array = relation.select(values).collect {|row| row[primary_key]}
21
+ def limited_ids_for(relation)
16
22
  # CPK
17
- # ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
18
-
19
- # OR together each and expression (key=value and key=value) that matches an id set
20
- # since we only need to match 0 or more records
21
- or_expressions = ids_array.map do |id_set|
22
- # AND together "key=value" exprssios to match each id set
23
- and_expressions = [self.primary_keys, id_set].transpose.map do |key, id|
24
- table[key].eq(id)
25
- end
26
- Arel::Nodes::And.new(and_expressions)
23
+ #values = @klass.connection.columns_for_distinct(
24
+ # "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
25
+ columns = @klass.primary_keys.map do |key|
26
+ "#{quoted_table_name}.#{connection.quote_column_name(key)}"
27
27
  end
28
+ values = @klass.connection.columns_for_distinct(columns, relation.order_values)
29
+
30
+ relation = relation.except(:select).select(values).distinct!
28
31
 
29
- first = or_expressions.shift
30
- Arel::Nodes::Grouping.new(or_expressions.inject(first) do |memo, expr|
31
- Arel::Nodes::Or.new(memo, expr)
32
- end)
32
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
33
+
34
+ # CPK
35
+ #id_rows.map {|row| row[primary_key]}
36
+ id_rows.map {|row| row.values}
33
37
  end
34
38
 
35
- def exists?(id = nil)
36
- # ID can be:
39
+ def exists?(conditions = :none)
40
+ # conditions can be:
37
41
  # Array - ['department_id = ? and location_id = ?', 1, 1]
38
42
  # Array -> [1,2]
39
43
  # CompositeKeys -> [1,2]
40
44
 
41
- id = id.id if ::ActiveRecord::Base === id
45
+ conditions = conditions.id if ::ActiveRecord::Base === conditions
46
+ return false if !conditions
47
+
48
+ relation = apply_join_dependency(self, construct_join_dependency)
49
+ return false if ::ActiveRecord::NullRelation === relation
42
50
 
43
- join_dependency = construct_join_dependency_for_association_find
44
- relation = construct_relation_for_association_find(join_dependency)
45
- relation = relation.except(:select).select("1").limit(1)
51
+ relation = relation.except(:select, :order).select(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit(1)
46
52
 
47
53
  # CPK
48
- #case id
54
+ #case conditions
49
55
  #when Array, Hash
50
- # relation = relation.where(id)
56
+ # relation = relation.where(conditions)
51
57
  #else
52
- # relation = relation.where(table[primary_key].eq(id)) if id
58
+ # relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
53
59
  #end
54
60
 
55
- case id
61
+ case conditions
56
62
  when CompositePrimaryKeys::CompositeKeys
57
- relation = relation.where(cpk_id_predicate(table, primary_key, id))
63
+ relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
58
64
  when Array
59
65
  pk_length = @klass.primary_keys.length
60
66
 
61
- if id.length == pk_length # E.g. id = ['France', 'Paris']
62
- return self.exists?(id.to_composite_keys)
63
- else # Assume that id contains where relation
64
- relation = relation.where(id)
67
+ if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
68
+ return self.exists?(conditions.to_composite_keys)
69
+ else # Assume that conditions contains where relation
70
+ relation = relation.where(conditions)
65
71
  end
66
72
  when Hash
67
- relation = relation.where(id)
73
+ relation = relation.where(conditions)
74
+ end
75
+
76
+ connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
77
+ end
78
+
79
+ def find_with_ids(*ids)
80
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
81
+
82
+ expects_array = ids.first.kind_of?(Array)
83
+ return ids.first if expects_array && ids.first.empty?
84
+
85
+ # CPK - don't do this, we want an array of arrays
86
+ #ids = ids.flatten.compact.uniq
87
+
88
+ case ids.size
89
+ when 0
90
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
91
+ when 1
92
+ result = find_one(ids.first)
93
+ # CPK
94
+ # expects_array ? [ result ] : result
95
+ result
96
+ else
97
+ find_some(ids)
68
98
  end
69
- connection.select_value(relation.to_sql) ? true : false
70
99
  end
71
100
 
72
- def find_with_ids(*ids, &block)
73
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
101
+ def find_one(id)
102
+ # CPK
103
+ #id = id.id if ActiveRecord::Base === id
104
+ id = id.id if ::ActiveRecord::Base === id
74
105
 
75
- # Supports:
76
- # find('1,2') -> ['1,2']
77
- # find(1,2) -> [1,2]
78
- # find([1,2]) -> [['1,2']]
79
- # find([1,2], [3,4]) -> [[1,2],[3,4]]
80
- #
81
- # Does *not* support:
82
- # find('1,2', '3,4') -> ['1,2','3,4']
106
+ # CPK
107
+ #column = columns_hash[primary_key]
108
+ #substitute = connection.substitute_at(column, bind_values.length)
109
+ #relation = where(table[primary_key].eq(substitute))
110
+ #relation.bind_values += [[column, id]]
111
+ #record = relation.take
112
+ relation = self
113
+ values = primary_keys.each_with_index.map do |primary_key, i|
114
+ column = columns_hash[primary_key]
115
+ relation.bind_values += [[column, id[i]]]
116
+ connection.substitute_at(column, bind_values.length - 1)
117
+ end
118
+ relation = relation.where(cpk_id_predicate(table, primary_keys, values))
83
119
 
84
- # Normalize incoming data. Note the last arg can be nil. Happens
85
- # when find is called with nil options which is then passed on
86
- # to find_with_ids.
87
- ids.compact!
120
+ record = relation.take
121
+ raise_record_not_found_exception!(id, 0, 1) unless record
122
+ record
123
+ end
88
124
 
89
- ids = [ids] unless ids.first.kind_of?(Array)
125
+ def find_some(ids)
126
+ # CPK
127
+ # result = where(table[primary_key].in(ids)).to_a
90
128
 
91
- results = ids.map do |cpk_ids|
129
+ result = ids.map do |cpk_ids|
92
130
  cpk_ids = if cpk_ids.length == 1
93
131
  cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
94
132
  else
@@ -114,8 +152,24 @@ module CompositePrimaryKeys
114
152
  records
115
153
  end.flatten
116
154
 
117
- ids.length == 1 ? results.first : results
155
+ expected_size =
156
+ if limit_value && ids.size > limit_value
157
+ limit_value
158
+ else
159
+ ids.size
160
+ end
161
+
162
+ # 11 ids with limit 3, offset 9 should give 2 results.
163
+ if offset_value && (ids.size - offset_value < expected_size)
164
+ expected_size = ids.size - offset_value
165
+ end
166
+
167
+ if result.size == expected_size
168
+ result
169
+ else
170
+ raise_record_not_found_exception!(ids, result.size, expected_size)
171
+ end
118
172
  end
119
173
  end
120
174
  end
121
- end
175
+ end
@@ -1,8 +1,8 @@
1
1
  module CompositePrimaryKeys
2
2
  module VERSION
3
- MAJOR = 6
3
+ MAJOR = 7
4
4
  MINOR = 0
5
- TINY = 8
5
+ TINY = 0
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -6,7 +6,6 @@ require 'composite_primary_keys'
6
6
 
7
7
  require 'active_support/test_case'
8
8
  require 'minitest/autorun'
9
- require 'mocha/mini_test'
10
9
 
11
10
  # Now load the connection spec
12
11
  require File.join(PROJECT_ROOT, "test", "connections", "connection_spec")
@@ -28,13 +27,13 @@ I18n.config.enforce_available_locales = true
28
27
 
29
28
  class ActiveSupport::TestCase
30
29
  include ActiveRecord::TestFixtures
31
-
30
+
32
31
  self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
33
32
  self.use_instantiated_fixtures = false
34
33
  self.use_transactional_fixtures = true
35
34
 
36
35
  def assert_date_from_db(expected, actual, message = nil)
37
- # SQL Server doesn't have a separate column type just for dates,
36
+ # SQL Server doesn't have a separate column type just for dates,
38
37
  # so the time is in the string and incorrectly formatted
39
38
  if current_adapter?(:SQLServerAdapter)
40
39
  assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
@@ -61,11 +60,11 @@ class ActiveSupport::TestCase
61
60
  def assert_no_queries(&block)
62
61
  assert_queries(0, &block)
63
62
  end
64
-
63
+
65
64
  cattr_accessor :classes
66
65
 
67
66
  protected
68
-
67
+
69
68
  def testing_with(&block)
70
69
  classes.keys.each do |key_test|
71
70
  @key_test = key_test
@@ -76,15 +75,15 @@ class ActiveSupport::TestCase
76
75
  yield
77
76
  end
78
77
  end
79
-
78
+
80
79
  def first_id
81
80
  ids = (1..@primary_keys.length).map {|num| 1}
82
81
  composite? ? ids.to_composite_ids : ids.first
83
82
  end
84
-
83
+
85
84
  def composite?
86
85
  @key_test != :single
87
- end
86
+ end
88
87
 
89
88
  # Oracle metadata is in all caps.
90
89
  def with_quoted_identifiers(s)
@@ -1,8 +1,8 @@
1
1
  CREATE TABLE reference_types (
2
- reference_type_id integer NOT NULL generated by default as identity (start with 100, increment by 1, no cache),
3
- type_label varchar(50) default NULL,
4
- abbreviation varchar(50) default NULL,
5
- description varchar(50) default NULL,
2
+ reference_type_id integer NOT NULL generated by default as identity (start with 100, increment by 1, no cache),
3
+ type_label varchar(50) default NULL,
4
+ abbreviation varchar(50) default NULL,
5
+ description varchar(50) default NULL,
6
6
  PRIMARY KEY (reference_type_id)
7
7
  );
8
8
 
@@ -74,7 +74,7 @@ CREATE TABLE groups (
74
74
  id integer NOT NULL ,
75
75
  name varchar(50) NOT NULL,
76
76
  PRIMARY KEY (id)
77
- );
77
+ );
78
78
 
79
79
  CREATE TABLE memberships (
80
80
  user_id integer NOT NULL,
@@ -91,18 +91,17 @@ CREATE TABLE membership_statuses (
91
91
  );
92
92
 
93
93
  create table restaurants (
94
- franchise_id integer not null,
95
- store_id integer not null,
96
- name varchar(100),
97
- lock_version integer default 0,
98
- primary key (franchise_id, store_id)
94
+ franchise_id integer not null,
95
+ store_id integer not null,
96
+ name varchar(100),
97
+ primary key (franchise_id, store_id)
99
98
  );
100
99
 
101
100
  create table restaurants_suburbs (
102
- franchise_id integer not null,
103
- store_id integer not null,
104
- city_id integer not null,
105
- suburb_id integer not null
101
+ franchise_id integer not null,
102
+ store_id integer not null,
103
+ city_id integer not null,
104
+ suburb_id integer not null
106
105
  );
107
106
 
108
107
  create table products_restaurants (
@@ -110,9 +109,3 @@ create table products_restaurants (
110
109
  franchise_id integer not null,
111
110
  store_id integer not null
112
111
  );
113
-
114
- create table model_with_callbacks (
115
- reference_type_id integer,
116
- reference_code integer NOT NULL,
117
- PRIMARY KEY (reference_type_id,reference_code)
118
- );
@@ -1,18 +1,17 @@
1
- drop table MEMBERSHIPS;
2
- drop table REFERENCE_CODES;
3
- drop table TARIFFS;
4
- drop table ARTICLES;
5
- drop table GROUPS;
6
- drop table MEMBERSHIP_STATUSES;
7
- drop table READINGS;
8
- drop table REFERENCE_TYPES;
9
- drop table STREETS;
10
- drop table PRODUCTS;
11
- drop table USERS;
12
- drop table SUBURBS;
13
- drop table PRODUCT_TARIFFS;
1
+ drop table MEMBERSHIPS;
2
+ drop table REFERENCE_CODES;
3
+ drop table TARIFFS;
4
+ drop table ARTICLES;
5
+ drop table GROUPS;
6
+ drop table MEMBERSHIP_STATUSES;
7
+ drop table READINGS;
8
+ drop table REFERENCE_TYPES;
9
+ drop table STREETS;
10
+ drop table PRODUCTS;
11
+ drop table USERS;
12
+ drop table SUBURBS;
13
+ drop table PRODUCT_TARIFFS;
14
14
  drop table KITCHEN_SINK;
15
15
  drop table RESTAURANTS;
16
16
  drop table RESTAURANTS_SUBURBS;
17
17
  drop table PRODUCTS_RESTAURANTS;
18
- drop table MODEL_WITH_CALLBACKS;
@@ -123,7 +123,6 @@ create table restaurants (
123
123
  franchise_id int not null,
124
124
  store_id int not null,
125
125
  name varchar(100),
126
- lock_version int default 0,
127
126
  primary key (franchise_id, store_id)
128
127
  );
129
128
 
@@ -191,9 +190,3 @@ create table employees_groups (
191
190
  employee_id int not null,
192
191
  group_id int not null
193
192
  );
194
-
195
- create table model_with_callbacks (
196
- reference_type_id int not null,
197
- reference_code int not null,
198
- primary key (reference_type_id, reference_code)
199
- );
@@ -40,4 +40,3 @@ drop sequence students_seq;
40
40
  drop table seats;
41
41
  drop table capitols;
42
42
  drop table products_restaurants;
43
- drop table model_with_callbacks;
@@ -131,7 +131,6 @@ create table restaurants (
131
131
  franchise_id number(11) not null,
132
132
  store_id number(11) not null,
133
133
  name varchar(100),
134
- lock_version number(11) default 0,
135
134
  constraint restaurants_pk primary key (franchise_id, store_id)
136
135
  );
137
136
 
@@ -206,8 +205,3 @@ create table employees_groups (
206
205
  group_id int not null
207
206
  );
208
207
 
209
- create table model_with_callbacks(
210
- reference_type_id number(11),
211
- reference_code number(11),
212
- constraint model_with_callbacks_pk primary key (reference_type_id, reference_code)
213
- );
@@ -125,7 +125,6 @@ create table restaurants (
125
125
  franchise_id int not null,
126
126
  store_id int not null,
127
127
  name varchar(100),
128
- lock_version int default 0,
129
128
  primary key (franchise_id, store_id)
130
129
  );
131
130
 
@@ -193,9 +192,3 @@ create table employees_groups (
193
192
  employee_id int not null,
194
193
  group_id int not null
195
194
  );
196
-
197
- create table model_with_callbacks(
198
- reference_type_id int,
199
- reference_code int not null,
200
- primary key (reference_type_id, reference_code)
201
- );
@@ -83,7 +83,7 @@ create table membership_statuses (
83
83
  id integer not null primary key autoincrement,
84
84
  user_id int not null,
85
85
  group_id int not null,
86
- status varchar(50) not null
86
+ status varchar(50) not null
87
87
  );
88
88
 
89
89
  create table departments (
@@ -111,49 +111,48 @@ create table hacks (
111
111
  );
112
112
 
113
113
  create table restaurants (
114
- franchise_id integer not null,
115
- store_id integer not null,
116
- name varchar(100),
117
- lock_version integer default 0,
118
- primary key (franchise_id, store_id)
114
+ franchise_id integer not null,
115
+ store_id integer not null,
116
+ name varchar(100),
117
+ primary key (franchise_id, store_id)
119
118
  );
120
119
 
121
120
  create table restaurants_suburbs (
122
- franchise_id integer not null,
123
- store_id integer not null,
124
- city_id integer not null,
125
- suburb_id integer not null
121
+ franchise_id integer not null,
122
+ store_id integer not null,
123
+ city_id integer not null,
124
+ suburb_id integer not null
126
125
  );
127
126
 
128
127
  create table dorms (
129
- id integer not null primary key autoincrement
128
+ id integer not null primary key autoincrement
130
129
  );
131
130
 
132
131
  create table rooms (
133
- dorm_id integer not null,
134
- room_id integer not null,
135
- primary key (dorm_id, room_id)
132
+ dorm_id integer not null,
133
+ room_id integer not null,
134
+ primary key (dorm_id, room_id)
136
135
  );
137
136
 
138
137
  create table room_attributes (
139
- id integer not null primary key autoincrement,
140
- name varchar(50)
138
+ id integer not null primary key autoincrement,
139
+ name varchar(50)
141
140
  );
142
141
 
143
142
  create table room_attribute_assignments (
144
- dorm_id integer not null,
145
- room_id integer not null,
146
- room_attribute_id integer not null
143
+ dorm_id integer not null,
144
+ room_id integer not null,
145
+ room_attribute_id integer not null
147
146
  );
148
147
 
149
148
  create table students (
150
- id integer not null primary key autoincrement
149
+ id integer not null primary key autoincrement
151
150
  );
152
151
 
153
152
  create table room_assignments (
154
- student_id integer not null,
155
- dorm_id integer not null,
156
- room_id integer not null
153
+ student_id integer not null,
154
+ dorm_id integer not null,
155
+ room_id integer not null
157
156
  );
158
157
 
159
158
  create table seats (
@@ -171,8 +170,8 @@ create table capitols (
171
170
 
172
171
  create table products_restaurants (
173
172
  product_id integer not null,
174
- franchise_id integer not null,
175
- store_id integer not null
173
+ franchise_id integer not null,
174
+ store_id integer not null
176
175
  );
177
176
 
178
177
  create table employees_groups (
@@ -180,8 +179,3 @@ create table employees_groups (
180
179
  group_id integer not null
181
180
  );
182
181
 
183
- create table model_with_callbacks(
184
- reference_type_id int(11),
185
- reference_code int(11) not null,
186
- primary key (reference_type_id, reference_code)
187
- );