composite_primary_keys 7.0.16 → 8.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/History.rdoc +600 -623
  3. data/lib/composite_primary_keys.rb +113 -115
  4. data/lib/composite_primary_keys/associations/association.rb +23 -23
  5. data/lib/composite_primary_keys/associations/association_scope.rb +73 -77
  6. data/lib/composite_primary_keys/associations/collection_association.rb +15 -0
  7. data/lib/composite_primary_keys/associations/has_many_association.rb +69 -56
  8. data/lib/composite_primary_keys/associations/has_many_through_association.rb +30 -28
  9. data/lib/composite_primary_keys/associations/join_dependency.rb +87 -89
  10. data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +22 -22
  11. data/lib/composite_primary_keys/associations/preloader/association.rb +90 -78
  12. data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +19 -19
  13. data/lib/composite_primary_keys/associations/singular_association.rb +15 -0
  14. data/lib/composite_primary_keys/attribute_methods.rb +9 -0
  15. data/lib/composite_primary_keys/attribute_methods/dirty.rb +29 -26
  16. data/lib/composite_primary_keys/attribute_methods/read.rb +19 -34
  17. data/lib/composite_primary_keys/attribute_methods/write.rb +30 -36
  18. data/lib/composite_primary_keys/attribute_set/builder.rb +20 -0
  19. data/lib/composite_primary_keys/base.rb +135 -129
  20. data/lib/composite_primary_keys/composite_arrays.rb +30 -30
  21. data/lib/composite_primary_keys/composite_predicates.rb +50 -50
  22. data/lib/composite_primary_keys/composite_relation.rb +48 -48
  23. data/lib/composite_primary_keys/connection_adapters/abstract/connection_specification_changes.rb +2 -4
  24. data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +46 -60
  25. data/lib/composite_primary_keys/core.rb +69 -47
  26. data/lib/composite_primary_keys/fixtures.rb +22 -22
  27. data/lib/composite_primary_keys/persistence.rb +56 -60
  28. data/lib/composite_primary_keys/relation.rb +68 -56
  29. data/lib/composite_primary_keys/relation/calculations.rb +79 -75
  30. data/lib/composite_primary_keys/relation/finder_methods.rb +175 -196
  31. data/lib/composite_primary_keys/relation/query_methods.rb +40 -40
  32. data/lib/composite_primary_keys/sanitization.rb +52 -52
  33. data/lib/composite_primary_keys/validations/uniqueness.rb +36 -37
  34. data/lib/composite_primary_keys/version.rb +8 -8
  35. data/tasks/databases/oracle.rake +25 -25
  36. data/tasks/databases/sqlserver.rake +27 -40
  37. data/test/abstract_unit.rb +113 -113
  38. data/test/connections/databases.ci.yml +15 -15
  39. data/test/connections/databases.example.yml +18 -18
  40. data/test/connections/native_oracle/connection.rb +11 -11
  41. data/test/connections/native_oracle_enhanced/connection.rb +16 -16
  42. data/test/connections/native_sqlserver/connection.rb +11 -14
  43. data/test/fixtures/comment.rb +7 -7
  44. data/test/fixtures/db_definitions/db2-create-tables.sql +125 -126
  45. data/test/fixtures/db_definitions/db2-drop-tables.sql +18 -18
  46. data/test/fixtures/db_definitions/mysql.sql +207 -208
  47. data/test/fixtures/db_definitions/oracle.drop.sql +45 -45
  48. data/test/fixtures/db_definitions/oracle.sql +222 -223
  49. data/test/fixtures/db_definitions/postgresql.sql +209 -210
  50. data/test/fixtures/db_definitions/sqlite.sql +196 -197
  51. data/test/fixtures/db_definitions/sqlserver.drop.sql +91 -94
  52. data/test/fixtures/db_definitions/sqlserver.sql +225 -232
  53. data/test/fixtures/dorm.rb +2 -2
  54. data/test/fixtures/employee.rb +5 -5
  55. data/test/fixtures/membership.rb +6 -6
  56. data/test/fixtures/membership_statuses.yml +16 -16
  57. data/test/fixtures/memberships.yml +10 -10
  58. data/test/fixtures/product_tariffs.yml +14 -14
  59. data/test/fixtures/reference_code.rb +7 -7
  60. data/test/fixtures/restaurants_suburb.rb +2 -2
  61. data/test/fixtures/suburb.rb +5 -5
  62. data/test/fixtures/topic.rb +5 -5
  63. data/test/fixtures/topic_source.rb +6 -6
  64. data/test/fixtures/topic_sources.yml +3 -3
  65. data/test/fixtures/topics.yml +8 -8
  66. data/test/fixtures/users.yml +10 -10
  67. data/test/test_associations.rb +295 -275
  68. data/test/test_attribute_methods.rb +63 -63
  69. data/test/test_attributes.rb +60 -60
  70. data/test/test_calculations.rb +37 -42
  71. data/test/test_callbacks.rb +99 -99
  72. data/test/test_create.rb +112 -112
  73. data/test/test_delete.rb +148 -152
  74. data/test/test_delete_all.rb +28 -26
  75. data/test/test_dumpable.rb +15 -15
  76. data/test/test_enum.rb +21 -20
  77. data/test/test_equal.rb +26 -26
  78. data/test/test_find.rb +118 -118
  79. data/test/test_habtm.rb +113 -113
  80. data/test/test_nested_attributes.rb +124 -124
  81. data/test/test_polymorphic.rb +26 -26
  82. data/test/test_predicates.rb +40 -40
  83. data/test/test_santiago.rb +23 -23
  84. data/test/test_suite.rb +33 -34
  85. data/test/test_touch.rb +23 -23
  86. data/test/test_tutorial_example.rb +21 -21
  87. data/test/test_update.rb +71 -71
  88. metadata +9 -13
  89. data/lib/composite_primary_keys/arel/visitors/to_sql.rb +0 -20
  90. data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +0 -59
  91. data/lib/composite_primary_keys/associations/join_dependency/join_part.rb +0 -39
  92. data/lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb +0 -46
  93. data/lib/composite_primary_keys/connection_adapters/sqlserver_adapter.rb +0 -17
  94. data/lib/composite_primary_keys/locking/optimistic.rb +0 -55
  95. data/test/test_optimistic.rb +0 -18
@@ -1,22 +1,22 @@
1
- module ActiveRecord
2
- class Fixture
3
- def find
4
- if model_class
5
- # CPK
6
- # model_class.find(fixture[model_class.primary_key])
7
- ids = self.ids(model_class.primary_key)
8
- model_class.find(ids)
9
- else
10
- raise FixtureClassNotFound, "No class attached to find."
11
- end
12
- end
13
-
14
- def ids(key)
15
- if key.is_a? Array
16
- key.map {|a_key| fixture[a_key.to_s] }
17
- else
18
- fixture[key]
19
- end
20
- end
21
- end
22
- end
1
+ module ActiveRecord
2
+ class Fixture
3
+ def find
4
+ if model_class
5
+ # CPK
6
+ # model_class.find(fixture[model_class.primary_key])
7
+ ids = self.ids(model_class.primary_key)
8
+ model_class.find(ids)
9
+ else
10
+ raise FixtureClassNotFound, "No class attached to find."
11
+ end
12
+ end
13
+
14
+ def ids(key)
15
+ if key.is_a? Array
16
+ key.map {|a_key| fixture[a_key.to_s] }
17
+ else
18
+ fixture[key]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,61 +1,57 @@
1
- module ActiveRecord
2
- module Persistence
3
- def relation_for_destroy
4
- # CPK
5
- if self.composite?
6
- relation = self.class.unscoped
7
-
8
- Array(self.class.primary_key).each_with_index do |key, index|
9
- column = self.class.columns_hash[key]
10
- substitute = self.class.connection.substitute_at(column, index)
11
- relation = relation.where(self.class.arel_table[key].eq(substitute))
12
- relation.bind_values += [[column, self[key]]]
13
- end
14
-
15
- relation
16
- else
17
- pk = self.class.primary_key
18
- column = self.class.columns_hash[pk]
19
- substitute = self.class.connection.substitute_at(column, 0)
20
-
21
- relation = self.class.unscoped.where(
22
- self.class.arel_table[pk].eq(substitute))
23
-
24
- relation.bind_values = [[column, id]]
25
- relation
26
- end
27
- end
28
-
29
- def touch(name = nil)
30
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
31
-
32
- attributes = timestamp_attributes_for_update_in_model
33
- attributes << name if name
34
-
35
- unless attributes.empty?
36
- current_time = current_time_from_proper_timezone
37
- changes = {}
38
-
39
- attributes.each do |column|
40
- column = column.to_s
41
- changes[column] = write_attribute(column, current_time)
42
- end
43
-
44
- changes[self.class.locking_column] = increment_lock if locking_enabled?
45
-
46
- changed_attributes.except!(*changes.keys)
47
-
48
- relation = self.class.send(:relation)
49
- arel_table = self.class.arel_table
50
- primary_key = self.class.primary_key
51
-
52
- # CPK
53
- #self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
54
- primary_key_predicate = relation.cpk_id_predicate(arel_table, Array(primary_key), Array(id))
55
- self.class.unscoped.where(primary_key_predicate).update_all(changes) == 1
56
- else
57
- true
58
- end
59
- end
60
- end
1
+ module ActiveRecord
2
+ module Persistence
3
+ def relation_for_destroy
4
+ # CPK
5
+ if self.composite?
6
+ relation = self.class.unscoped
7
+
8
+ Array(self.class.primary_key).each_with_index do |key, index|
9
+ column = self.class.columns_hash[key]
10
+ substitute = self.class.connection.substitute_at(column, index)
11
+ relation = relation.where(self.class.arel_table[key].eq(substitute))
12
+ relation.bind_values += [[column, self[key]]]
13
+ end
14
+
15
+ relation
16
+ else
17
+ pk = self.class.primary_key
18
+ column = self.class.columns_hash[pk]
19
+ substitute = self.class.connection.substitute_at(column, 0)
20
+
21
+ relation = self.class.unscoped.where(
22
+ self.class.arel_table[pk].eq(substitute))
23
+
24
+ relation.bind_values = [[column, id]]
25
+ relation
26
+ end
27
+ end
28
+
29
+ def touch(*names)
30
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
31
+
32
+ attributes = timestamp_attributes_for_update_in_model
33
+ attributes.concat(names)
34
+
35
+ unless attributes.empty?
36
+ current_time = current_time_from_proper_timezone
37
+ changes = {}
38
+
39
+ attributes.each do |column|
40
+ column = column.to_s
41
+ changes[column] = write_attribute(column, current_time)
42
+ end
43
+
44
+ changes[self.class.locking_column] = increment_lock if locking_enabled?
45
+
46
+ clear_attribute_changes(changes.keys)
47
+ primary_key = self.class.primary_key
48
+ # CPK
49
+ #self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
50
+ primary_key_predicate = self.class.unscoped.cpk_id_predicate(self.class.arel_table, Array(primary_key), Array(id))
51
+ self.class.unscoped.where(primary_key_predicate).update_all(changes) == 1
52
+ else
53
+ true
54
+ end
55
+ end
56
+ end
61
57
  end
@@ -1,56 +1,68 @@
1
- module ActiveRecord
2
- class Relation
3
- alias :initialize_without_cpk :initialize
4
- def initialize(klass, table, values = {})
5
- initialize_without_cpk(klass, table, values)
6
- add_cpk_support if klass && klass.composite?
7
- end
8
-
9
- alias :initialize_copy_without_cpk :initialize_copy
10
- def initialize_copy(other)
11
- initialize_copy_without_cpk(other)
12
- add_cpk_support if klass.composite?
13
- end
14
-
15
- def add_cpk_support
16
- extend CompositePrimaryKeys::CompositeRelation
17
- end
18
-
19
- # CPK adds this so that it finds the Equality nodes beneath the And node:
20
- # equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
21
- # node.left.relation.name == table_name
22
- # }
23
- alias :where_values_hash_without_cpk :where_values_hash
24
- def where_values_hash(relation_table_name = table_name)
25
- nodes_from_and = where_values.grep(Arel::Nodes::And).map {|and_node| and_node.children.grep(Arel::Nodes::Equality) }.flatten
26
-
27
- equalities = (nodes_from_and + where_values.grep(Arel::Nodes::Equality)).find_all { |node|
28
- node.left.relation.name == relation_table_name
29
- }
30
-
31
- binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
32
-
33
- Hash[equalities.map { |where|
34
- name = where.left.name
35
- [name, binds.fetch(name.to_s) { where.right }]
36
- }]
37
- end
38
-
39
- def _update_record(values, id, id_was)
40
- substitutes, binds = substitute_values values
41
-
42
- # CPK
43
- um = if self.composite?
44
- relation = @klass.unscoped.where(cpk_id_predicate(@klass.arel_table, @klass.primary_key, id_was || id))
45
- relation.arel.compile_update(substitutes, @klass.primary_key)
46
- else
47
- @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
48
- end
49
-
50
- @klass.connection.update(
51
- um,
52
- 'SQL',
53
- binds)
54
- end
55
- end
56
- end
1
+ module ActiveRecord
2
+ class Relation
3
+ alias :initialize_without_cpk :initialize
4
+ def initialize(klass, table, values = {})
5
+ initialize_without_cpk(klass, table, values)
6
+ add_cpk_support if klass && klass.composite?
7
+ end
8
+
9
+ alias :initialize_copy_without_cpk :initialize_copy
10
+ def initialize_copy(other)
11
+ initialize_copy_without_cpk(other)
12
+ add_cpk_support if klass.composite?
13
+ end
14
+
15
+ def add_cpk_support
16
+ extend CompositePrimaryKeys::CompositeRelation
17
+ end
18
+
19
+ # CPK adds this so that it finds the Equality nodes beneath the And node:
20
+ # equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
21
+ # node.left.relation.name == table_name
22
+ # }
23
+ alias :where_values_hash_without_cpk :where_values_hash
24
+ def where_values_hash(relation_table_name = table_name)
25
+ nodes_from_and = where_values.grep(Arel::Nodes::And).map {|and_node| and_node.children.grep(Arel::Nodes::Equality) }.flatten
26
+
27
+ equalities = (nodes_from_and + where_values.grep(Arel::Nodes::Equality)).find_all { |node|
28
+ node.left.relation.name == relation_table_name
29
+ }
30
+
31
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
32
+
33
+ Hash[equalities.map { |where|
34
+ name = where.left.name
35
+ [name, binds.fetch(name.to_s) {
36
+ case where.right
37
+ when Array then where.right.map(&:val)
38
+ else
39
+ where.right.val
40
+ end
41
+ }]
42
+ }]
43
+ end
44
+
45
+ def _update_record(values, id, id_was)
46
+ substitutes, binds = substitute_values values
47
+
48
+ # CPK
49
+ um = if self.composite?
50
+ relation = @klass.unscoped.where(cpk_id_predicate(@klass.arel_table, @klass.primary_key, id_was || id))
51
+ relation.arel.compile_update(substitutes, @klass.primary_key)
52
+ else
53
+ scope = @klass.unscoped
54
+
55
+ if @klass.finder_needs_type_condition?
56
+ scope.unscope!(where: @klass.inheritance_column)
57
+ end
58
+
59
+ scope.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
60
+ end
61
+
62
+ @klass.connection.update(
63
+ um,
64
+ 'SQL',
65
+ binds)
66
+ end
67
+ end
68
+ end
@@ -1,75 +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 = reorder(nil)
20
-
21
- column_alias = column_name
22
- # CPK
23
- #if operation == "count" && (relation.limit_value || relation.offset_value)
24
- if operation == "count"
25
- # Shortcut when limit is zero.
26
- return 0 if relation.limit_value == 0
27
-
28
- query_builder = build_count_subquery(relation, column_name, distinct)
29
- else
30
- column = aggregate_column(column_name)
31
-
32
- select_value = operation_over_aggregate_column(column, operation, distinct)
33
- column_alias = select_value.alias
34
-
35
- relation.select_values = [select_value]
36
-
37
- query_builder = relation.arel
38
- end
39
-
40
- result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
41
- row = result.first
42
- value = row && row.values.first
43
- column = result.column_types.fetch(column_alias) do
44
- column_for(column_name)
45
- end
46
-
47
- type_cast_calculated_value(value, column, operation)
48
-
49
- end
50
-
51
- def build_count_subquery(relation, column_name, distinct)
52
- return super(relation, column_name, distinct) unless column_name.kind_of?(Array)
53
- # CPK
54
- # column_alias = Arel.sql('count_column')
55
- subquery_alias = Arel.sql('subquery_for_count')
56
-
57
- # CPK
58
- # aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
59
- # relation.select_values = [aliased_column]
60
- relation.select_values = column_name.map do |column|
61
- Arel::Attribute.new(@klass.unscoped.table, column)
62
- end
63
-
64
- relation = relation.distinct(true)
65
- subquery = relation.arel.as(subquery_alias)
66
-
67
- sm = Arel::SelectManager.new relation.engine
68
- # CPK
69
- # select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
70
- select_value = operation_over_aggregate_column(Arel.sql("*"), 'count', false)
71
- sm.project(select_value).from(subquery)
72
- end
73
- end
74
- end
75
- 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
+ end
@@ -1,196 +1,175 @@
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
- # CPK handle strings that come w/ calling to_param on CPK-enabled models
81
- ids = parse_ids(ids)
82
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
83
-
84
- expects_array = ids.first.kind_of?(Array)
85
- return ids.first if expects_array && ids.first.empty?
86
-
87
- # CPK - don't do this, we want an array of arrays
88
- #ids = ids.flatten.compact.uniq
89
- case ids.size
90
- when 0
91
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
92
- when 1
93
- result = find_one(ids.first)
94
- # CPK
95
- # expects_array ? [ result ] : result
96
- result
97
- else
98
- find_some(ids)
99
- end
100
- end
101
-
102
- def find_one(id)
103
- # CPK
104
- #id = id.id if ActiveRecord::Base === id
105
- id = id.id if ::ActiveRecord::Base === id
106
-
107
- # CPK
108
- #column = columns_hash[primary_key]
109
- #substitute = connection.substitute_at(column, bind_values.length)
110
- #relation = where(table[primary_key].eq(substitute))
111
- #relation.bind_values += [[column, id]]
112
- #record = relation.take
113
- relation = self
114
- values = primary_keys.each_with_index.map do |primary_key, i|
115
- column = columns_hash[primary_key]
116
- relation.bind_values += [[column, id[i]]]
117
- connection.substitute_at(column, bind_values.length - 1)
118
- end
119
- relation = relation.where(cpk_id_predicate(table, primary_keys, values))
120
-
121
- record = relation.take
122
- raise_record_not_found_exception!(id, 0, 1) unless record
123
- record
124
- end
125
-
126
- def find_some(ids)
127
- # CPK
128
- # result = where(table[primary_key].in(ids)).to_a
129
-
130
- result = ids.map do |cpk_ids|
131
- cpk_ids = if cpk_ids.length == 1
132
- cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
133
- else
134
- cpk_ids.to_composite_keys
135
- end
136
-
137
- unless cpk_ids.length == @klass.primary_keys.length
138
- raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
139
- end
140
-
141
- new_relation = clone
142
- [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
143
- new_relation = new_relation.where(key => id)
144
- end
145
-
146
- records = new_relation.to_a
147
-
148
- if records.empty?
149
- conditions = new_relation.arel.where_sql
150
- raise(::ActiveRecord::RecordNotFound,
151
- "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
152
- end
153
- records
154
- end.flatten
155
-
156
- expected_size =
157
- if limit_value && ids.size > limit_value
158
- limit_value
159
- else
160
- ids.size
161
- end
162
-
163
- # 11 ids with limit 3, offset 9 should give 2 results.
164
- if offset_value && (ids.size - offset_value < expected_size)
165
- expected_size = ids.size - offset_value
166
- end
167
-
168
- if result.size == expected_size
169
- result
170
- else
171
- raise_record_not_found_exception!(ids, result.size, expected_size)
172
- end
173
- end
174
-
175
- private
176
- def parse_ids(ids)
177
- result = []
178
- ids.each do |id|
179
- if id.is_a?(String)
180
- if id.index(",")
181
- result << [id.split(",")]
182
- else
183
- result << [id]
184
- end
185
- elsif id.is_a?(Array) && id.count > 1 && id.first.to_s.index(",")
186
- result << id.map{|subid| subid.split(",")}
187
- else
188
- result << [id]
189
- end
190
- end
191
- result = result.flatten(1)
192
- return result
193
- end
194
- end
195
- end
196
- 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
+ # CPK handle strings that come w/ calling to_param on CPK-enabled models
81
+ ids = cpk_parse_ids(ids)
82
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
83
+
84
+ expects_array = ids.first.kind_of?(Array)
85
+ return ids.first if expects_array && ids.first.empty?
86
+
87
+ # CPK - don't do this, we want an array of arrays
88
+ #ids = ids.flatten.compact.uniq
89
+ case ids.size
90
+ when 0
91
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
92
+ when 1
93
+ result = find_one(ids.first)
94
+ # CPK
95
+ # expects_array ? [ result ] : result
96
+ result
97
+ else
98
+ find_some(ids)
99
+ end
100
+ end
101
+
102
+ def find_one(id)
103
+ # CPK
104
+ #id = id.id if ActiveRecord::Base === id
105
+ id = id.id if ::ActiveRecord::Base === id
106
+
107
+ # CPK
108
+ #column = columns_hash[primary_key]
109
+ #substitute = connection.substitute_at(column, bind_values.length)
110
+ #relation = where(table[primary_key].eq(substitute))
111
+ #relation.bind_values += [[column, id]]
112
+ #record = relation.take
113
+ relation = self
114
+ values = primary_keys.each_with_index.map do |primary_key, i|
115
+ column = columns_hash[primary_key]
116
+ relation.bind_values += [[column, id[i]]]
117
+ connection.substitute_at(column, bind_values.length - 1)
118
+ end
119
+ relation = relation.where(cpk_id_predicate(table, primary_keys, values))
120
+ record = relation.take
121
+ raise_record_not_found_exception!(id, 0, 1) unless record
122
+ record
123
+ end
124
+
125
+ def find_some(ids)
126
+ # CPK
127
+ # result = where(table[primary_key].in(ids)).to_a
128
+
129
+ result = ids.map do |cpk_ids|
130
+ cpk_ids = if cpk_ids.length == 1
131
+ cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
132
+ else
133
+ cpk_ids.to_composite_keys
134
+ end
135
+
136
+ unless cpk_ids.length == @klass.primary_keys.length
137
+ raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
138
+ end
139
+
140
+ new_relation = clone
141
+ [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
142
+ new_relation = new_relation.where(key => id)
143
+ end
144
+
145
+ records = new_relation.to_a
146
+
147
+ if records.empty?
148
+ conditions = new_relation.arel.where_sql
149
+ raise(::ActiveRecord::RecordNotFound,
150
+ "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
151
+ end
152
+ records
153
+ end.flatten
154
+
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
172
+ end
173
+ end
174
+ end
175
+ end