composite_primary_keys 8.1.0 → 8.1.1

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 (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