composite_primary_keys 6.0.8 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
- );