composite_primary_keys 8.1.0 → 8.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +642 -625
  3. data/README.rdoc +5 -2
  4. data/lib/composite_primary_keys.rb +115 -115
  5. data/lib/composite_primary_keys/associations/association.rb +23 -23
  6. data/lib/composite_primary_keys/associations/association_scope.rb +73 -73
  7. data/lib/composite_primary_keys/associations/collection_association.rb +14 -14
  8. data/lib/composite_primary_keys/associations/has_many_association.rb +69 -69
  9. data/lib/composite_primary_keys/associations/join_dependency.rb +87 -87
  10. data/lib/composite_primary_keys/associations/preloader/association.rb +90 -90
  11. data/lib/composite_primary_keys/associations/singular_association.rb +18 -18
  12. data/lib/composite_primary_keys/attribute_methods.rb +9 -9
  13. data/lib/composite_primary_keys/attribute_methods/dirty.rb +29 -29
  14. data/lib/composite_primary_keys/attribute_methods/read.rb +24 -24
  15. data/lib/composite_primary_keys/attribute_methods/write.rb +30 -30
  16. data/lib/composite_primary_keys/attribute_set/builder.rb +19 -19
  17. data/lib/composite_primary_keys/base.rb +129 -135
  18. data/lib/composite_primary_keys/composite_arrays.rb +43 -43
  19. data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +2 -3
  20. data/lib/composite_primary_keys/core.rb +60 -60
  21. data/lib/composite_primary_keys/persistence.rb +56 -56
  22. data/lib/composite_primary_keys/relation.rb +68 -68
  23. data/lib/composite_primary_keys/relation/calculations.rb +78 -78
  24. data/lib/composite_primary_keys/relation/finder_methods.rb +179 -179
  25. data/lib/composite_primary_keys/sanitization.rb +52 -52
  26. data/lib/composite_primary_keys/validations/uniqueness.rb +36 -36
  27. data/lib/composite_primary_keys/version.rb +8 -8
  28. data/tasks/databases/sqlserver.rake +27 -27
  29. data/test/abstract_unit.rb +114 -113
  30. data/test/connections/databases.example.yml +25 -25
  31. data/test/connections/native_sqlserver/connection.rb +11 -11
  32. data/test/fixtures/db_definitions/mysql.sql +218 -218
  33. data/test/fixtures/db_definitions/postgresql.sql +220 -220
  34. data/test/fixtures/db_definitions/sqlite.sql +206 -206
  35. data/test/fixtures/db_definitions/sqlserver.drop.sql +91 -91
  36. data/test/fixtures/db_definitions/sqlserver.sql +226 -226
  37. data/test/fixtures/employee.rb +11 -11
  38. data/test/fixtures/salary.rb +5 -5
  39. data/test/test_associations.rb +341 -340
  40. data/test/test_attributes.rb +60 -60
  41. data/test/test_create.rb +157 -157
  42. data/test/test_delete.rb +158 -158
  43. data/test/test_delete_all.rb +33 -28
  44. data/test/test_enum.rb +21 -21
  45. data/test/test_equal.rb +26 -26
  46. data/test/test_find.rb +119 -118
  47. data/test/test_habtm.rb +117 -113
  48. data/test/test_polymorphic.rb +27 -26
  49. data/test/test_tutorial_example.rb +25 -25
  50. metadata +44 -2
@@ -1,79 +1,79 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module Calculations
4
- def aggregate_column(column_name)
5
- # CPK
6
- if column_name.kind_of?(Array)
7
- column_name.map do |column|
8
- Arel::Attribute.new(@klass.unscoped.table, column)
9
- end
10
- elsif @klass.column_names.include?(column_name.to_s)
11
- Arel::Attribute.new(@klass.unscoped.table, column_name)
12
- else
13
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
14
- end
15
- end
16
-
17
- def execute_simple_calculation(operation, column_name, distinct)
18
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
19
- relation = unscope(:order)
20
-
21
- column_alias = column_name
22
-
23
- bind_values = nil
24
-
25
- # CPK
26
- # if operation == "count" && (relation.limit_value || relation.offset_value)
27
- if operation == "count"
28
- # Shortcut when limit is zero.
29
- return 0 if relation.limit_value == 0
30
-
31
- query_builder = build_count_subquery(relation, column_name, distinct)
32
- bind_values = query_builder.bind_values + relation.bind_values
33
- else
34
- column = aggregate_column(column_name)
35
-
36
- select_value = operation_over_aggregate_column(column, operation, distinct)
37
-
38
- column_alias = select_value.alias
39
- relation.select_values = [select_value]
40
-
41
- query_builder = relation.arel
42
- bind_values = query_builder.bind_values + relation.bind_values
43
- end
44
-
45
- result = @klass.connection.select_all(query_builder, nil, bind_values)
46
- row = result.first
47
- value = row && row.values.first
48
- column = result.column_types.fetch(column_alias) do
49
- type_for(column_name)
50
- end
51
-
52
- type_cast_calculated_value(value, column, operation)
53
- end
54
-
55
- def build_count_subquery(relation, column_name, distinct)
56
- return super(relation, column_name, distinct) unless column_name.kind_of?(Array)
57
- # CPK
58
- # column_alias = Arel.sql('count_column')
59
- subquery_alias = Arel.sql('subquery_for_count')
60
-
61
- # CPK
62
- # aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
63
- # relation.select_values = [aliased_column]
64
- relation.select_values = column_name.map do |column|
65
- Arel::Attribute.new(@klass.unscoped.table, column)
66
- end
67
-
68
- relation = relation.distinct(true)
69
- subquery = relation.arel.as(subquery_alias)
70
-
71
- sm = Arel::SelectManager.new relation.engine
72
- # CPK
73
- # select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
74
- select_value = operation_over_aggregate_column(Arel.sql("*"), 'count', false)
75
- sm.project(select_value).from(subquery)
76
- end
77
- end
78
- end
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module Calculations
4
+ def aggregate_column(column_name)
5
+ # CPK
6
+ if column_name.kind_of?(Array)
7
+ column_name.map do |column|
8
+ Arel::Attribute.new(@klass.unscoped.table, column)
9
+ end
10
+ elsif @klass.column_names.include?(column_name.to_s)
11
+ Arel::Attribute.new(@klass.unscoped.table, column_name)
12
+ else
13
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
14
+ end
15
+ end
16
+
17
+ def execute_simple_calculation(operation, column_name, distinct)
18
+ # Postgresql doesn't like ORDER BY when there are no GROUP BY
19
+ relation = unscope(:order)
20
+
21
+ column_alias = column_name
22
+
23
+ bind_values = nil
24
+
25
+ # CPK
26
+ # if operation == "count" && (relation.limit_value || relation.offset_value)
27
+ if operation == "count"
28
+ # Shortcut when limit is zero.
29
+ return 0 if relation.limit_value == 0
30
+
31
+ query_builder = build_count_subquery(relation, column_name, distinct)
32
+ bind_values = query_builder.bind_values + relation.bind_values
33
+ else
34
+ column = aggregate_column(column_name)
35
+
36
+ select_value = operation_over_aggregate_column(column, operation, distinct)
37
+
38
+ column_alias = select_value.alias
39
+ relation.select_values = [select_value]
40
+
41
+ query_builder = relation.arel
42
+ bind_values = query_builder.bind_values + relation.bind_values
43
+ end
44
+
45
+ result = @klass.connection.select_all(query_builder, nil, bind_values)
46
+ row = result.first
47
+ value = row && row.values.first
48
+ column = result.column_types.fetch(column_alias) do
49
+ type_for(column_name)
50
+ end
51
+
52
+ type_cast_calculated_value(value, column, operation)
53
+ end
54
+
55
+ def build_count_subquery(relation, column_name, distinct)
56
+ return super(relation, column_name, distinct) unless column_name.kind_of?(Array)
57
+ # CPK
58
+ # column_alias = Arel.sql('count_column')
59
+ subquery_alias = Arel.sql('subquery_for_count')
60
+
61
+ # CPK
62
+ # aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
63
+ # relation.select_values = [aliased_column]
64
+ relation.select_values = column_name.map do |column|
65
+ Arel::Attribute.new(@klass.unscoped.table, column)
66
+ end
67
+
68
+ relation = relation.distinct(true)
69
+ subquery = relation.arel.as(subquery_alias)
70
+
71
+ sm = Arel::SelectManager.new relation.engine
72
+ # CPK
73
+ # select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
74
+ select_value = operation_over_aggregate_column(Arel.sql("*"), 'count', false)
75
+ sm.project(select_value).from(subquery)
76
+ end
77
+ end
78
+ end
79
79
  end
@@ -1,179 +1,179 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module FinderMethods
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)
18
- end
19
- end
20
-
21
- def limited_ids_for(relation)
22
- # CPK
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
- end
28
- values = @klass.connection.columns_for_distinct(columns, relation.order_values)
29
-
30
- relation = relation.except(:select).select(values).distinct!
31
-
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}
37
- end
38
-
39
- def exists?(conditions = :none)
40
- # conditions can be:
41
- # Array - ['department_id = ? and location_id = ?', 1, 1]
42
- # Array -> [1,2]
43
- # CompositeKeys -> [1,2]
44
-
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
50
-
51
- relation = relation.except(:select, :order).select(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit(1)
52
-
53
- # CPK
54
- #case conditions
55
- #when Array, Hash
56
- # relation = relation.where(conditions)
57
- #else
58
- # relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
59
- #end
60
-
61
- case conditions
62
- when CompositePrimaryKeys::CompositeKeys
63
- relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
64
- when Array
65
- pk_length = @klass.primary_keys.length
66
-
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)
71
- end
72
- when Hash
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
- # CPK
83
- #expects_array = ids.first.kind_of?(Array)
84
- ids = CompositePrimaryKeys.normalize(ids)
85
- expects_array = ids.flatten != ids.flatten(1)
86
-
87
- return ids.first if expects_array && ids.first.empty?
88
-
89
- # CPK
90
- #ids = ids.flatten.compact.uniq
91
- ids = expects_array ? ids.first : ids
92
-
93
- case ids.size
94
- when 0
95
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
96
- when 1
97
- result = find_one(ids.first)
98
- expects_array ? [ result ] : result
99
- else
100
- find_some(ids)
101
- end
102
- rescue RangeError
103
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
104
- end
105
-
106
- def find_one(id)
107
- # CPK
108
- #id = id.id if ActiveRecord::Base === id
109
- id = id.id if ::ActiveRecord::Base === id
110
-
111
- # CPK
112
- #column = columns_hash[primary_key]
113
- #substitute = connection.substitute_at(column, bind_values.length)
114
- #relation = where(table[primary_key].eq(substitute))
115
- #relation.bind_values += [[column, id]]
116
- #record = relation.take
117
- relation = self
118
- values = primary_keys.each_with_index.map do |primary_key, i|
119
- column = columns_hash[primary_key]
120
- relation.bind_values += [[column, id[i]]]
121
- connection.substitute_at(column, bind_values.length - 1)
122
- end
123
- relation = relation.where(cpk_id_predicate(table, primary_keys, values))
124
- record = relation.take
125
- raise_record_not_found_exception!(id, 0, 1) unless record
126
- record
127
- end
128
-
129
- def find_some(ids)
130
- # CPK
131
- # result = where(table[primary_key].in(ids)).to_a
132
-
133
- result = ids.map do |cpk_ids|
134
- cpk_ids = if cpk_ids.length == 1
135
- cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
136
- else
137
- cpk_ids.to_composite_keys
138
- end
139
-
140
- unless cpk_ids.length == @klass.primary_keys.length
141
- raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
142
- end
143
-
144
- new_relation = clone
145
- [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
146
- new_relation = new_relation.where(key => id)
147
- end
148
-
149
- records = new_relation.to_a
150
-
151
- if records.empty?
152
- conditions = new_relation.arel.where_sql
153
- raise(::ActiveRecord::RecordNotFound,
154
- "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
155
- end
156
- records
157
- end.flatten
158
-
159
- expected_size =
160
- if limit_value && ids.size > limit_value
161
- limit_value
162
- else
163
- ids.size
164
- end
165
-
166
- # 11 ids with limit 3, offset 9 should give 2 results.
167
- if offset_value && (ids.size - offset_value < expected_size)
168
- expected_size = ids.size - offset_value
169
- end
170
-
171
- if result.size == expected_size
172
- result
173
- else
174
- raise_record_not_found_exception!(ids, result.size, expected_size)
175
- end
176
- end
177
- end
178
- end
179
- end
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module FinderMethods
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)
18
+ end
19
+ end
20
+
21
+ def limited_ids_for(relation)
22
+ # CPK
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
+ end
28
+ values = @klass.connection.columns_for_distinct(columns, relation.order_values)
29
+
30
+ relation = relation.except(:select).select(values).distinct!
31
+
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}
37
+ end
38
+
39
+ def exists?(conditions = :none)
40
+ # conditions can be:
41
+ # Array - ['department_id = ? and location_id = ?', 1, 1]
42
+ # Array -> [1,2]
43
+ # CompositeKeys -> [1,2]
44
+
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
50
+
51
+ relation = relation.except(:select, :order).select(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit(1)
52
+
53
+ # CPK
54
+ #case conditions
55
+ #when Array, Hash
56
+ # relation = relation.where(conditions)
57
+ #else
58
+ # relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
59
+ #end
60
+
61
+ case conditions
62
+ when CompositePrimaryKeys::CompositeKeys
63
+ relation = relation.where(cpk_id_predicate(table, primary_key, conditions))
64
+ when Array
65
+ pk_length = @klass.primary_keys.length
66
+
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)
71
+ end
72
+ when Hash
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
+ # CPK
83
+ #expects_array = ids.first.kind_of?(Array)
84
+ ids = CompositePrimaryKeys.normalize(ids)
85
+ expects_array = ids.flatten != ids.flatten(1)
86
+
87
+ return ids.first if expects_array && ids.first.empty?
88
+
89
+ # CPK
90
+ #ids = ids.flatten.compact.uniq
91
+ ids = expects_array ? ids.first : ids
92
+
93
+ case ids.size
94
+ when 0
95
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
96
+ when 1
97
+ result = find_one(ids.first)
98
+ expects_array ? [ result ] : result
99
+ else
100
+ find_some(ids)
101
+ end
102
+ rescue RangeError
103
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
104
+ end
105
+
106
+ def find_one(id)
107
+ # CPK
108
+ #id = id.id if ActiveRecord::Base === id
109
+ id = id.id if ::ActiveRecord::Base === id
110
+
111
+ # CPK
112
+ #column = columns_hash[primary_key]
113
+ #substitute = connection.substitute_at(column, bind_values.length)
114
+ #relation = where(table[primary_key].eq(substitute))
115
+ #relation.bind_values += [[column, id]]
116
+ #record = relation.take
117
+ relation = self
118
+ values = primary_keys.each_with_index.map do |primary_key, i|
119
+ column = columns_hash[primary_key]
120
+ relation.bind_values += [[column, id[i]]]
121
+ connection.substitute_at(column, bind_values.length - 1)
122
+ end
123
+ relation = relation.where(cpk_id_predicate(table, primary_keys, values))
124
+ record = relation.take
125
+ raise_record_not_found_exception!(id, 0, 1) unless record
126
+ record
127
+ end
128
+
129
+ def find_some(ids)
130
+ # CPK
131
+ # result = where(table[primary_key].in(ids)).to_a
132
+
133
+ result = ids.map do |cpk_ids|
134
+ cpk_ids = if cpk_ids.length == 1
135
+ cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
136
+ else
137
+ cpk_ids.to_composite_keys
138
+ end
139
+
140
+ unless cpk_ids.length == @klass.primary_keys.length
141
+ raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
142
+ end
143
+
144
+ new_relation = clone
145
+ [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
146
+ new_relation = new_relation.where(key => id)
147
+ end
148
+
149
+ records = new_relation.to_a
150
+
151
+ if records.empty?
152
+ conditions = new_relation.arel.where_sql
153
+ raise(::ActiveRecord::RecordNotFound,
154
+ "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
155
+ end
156
+ records
157
+ end.flatten
158
+
159
+ expected_size =
160
+ if limit_value && ids.size > limit_value
161
+ limit_value
162
+ else
163
+ ids.size
164
+ end
165
+
166
+ # 11 ids with limit 3, offset 9 should give 2 results.
167
+ if offset_value && (ids.size - offset_value < expected_size)
168
+ expected_size = ids.size - offset_value
169
+ end
170
+
171
+ if result.size == expected_size
172
+ result
173
+ else
174
+ raise_record_not_found_exception!(ids, result.size, expected_size)
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end