composite_primary_keys 10.0.5 → 11.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/History.rdoc +2 -15
  3. data/README.rdoc +2 -1
  4. data/lib/composite_primary_keys.rb +7 -7
  5. data/lib/composite_primary_keys/arel/to_sql.rb +12 -6
  6. data/lib/composite_primary_keys/associations/association.rb +12 -14
  7. data/lib/composite_primary_keys/associations/association_scope.rb +7 -2
  8. data/lib/composite_primary_keys/associations/collection_association.rb +14 -5
  9. data/lib/composite_primary_keys/associations/foreign_association.rb +15 -0
  10. data/lib/composite_primary_keys/associations/has_many_association.rb +3 -15
  11. data/lib/composite_primary_keys/associations/has_many_through_association.rb +56 -58
  12. data/lib/composite_primary_keys/associations/join_dependency.rb +69 -71
  13. data/lib/composite_primary_keys/associations/preloader/association.rb +92 -75
  14. data/lib/composite_primary_keys/attribute_methods.rb +4 -6
  15. data/lib/composite_primary_keys/attribute_methods/primary_key.rb +20 -23
  16. data/lib/composite_primary_keys/attribute_methods/read.rb +16 -18
  17. data/lib/composite_primary_keys/attribute_methods/write.rb +31 -33
  18. data/lib/composite_primary_keys/composite_arrays.rb +1 -1
  19. data/lib/composite_primary_keys/composite_predicates.rb +3 -0
  20. data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +10 -10
  21. data/lib/composite_primary_keys/core.rb +40 -45
  22. data/lib/composite_primary_keys/fixtures.rb +17 -19
  23. data/lib/composite_primary_keys/locking/optimistic.rb +46 -38
  24. data/lib/composite_primary_keys/nested_attributes.rb +58 -60
  25. data/lib/composite_primary_keys/persistence.rb +72 -44
  26. data/lib/composite_primary_keys/reflection.rb +20 -0
  27. data/lib/composite_primary_keys/relation.rb +33 -79
  28. data/lib/composite_primary_keys/relation/batches.rb +7 -28
  29. data/lib/composite_primary_keys/relation/calculations.rb +28 -28
  30. data/lib/composite_primary_keys/relation/finder_methods.rb +34 -56
  31. data/lib/composite_primary_keys/relation/predicate_builder/association_query_value.rb +18 -0
  32. data/lib/composite_primary_keys/relation/query_methods.rb +23 -11
  33. data/lib/composite_primary_keys/relation/where_clause.rb +13 -22
  34. data/lib/composite_primary_keys/sanitization.rb +0 -2
  35. data/lib/composite_primary_keys/version.rb +3 -3
  36. data/scripts/console.rb +48 -48
  37. data/scripts/txt2html +76 -76
  38. data/scripts/txt2js +65 -65
  39. data/tasks/website.rake +18 -18
  40. data/test/README_tests.rdoc +56 -56
  41. data/test/connections/databases.yml +30 -40
  42. data/test/db_test.rb +52 -52
  43. data/test/fixtures/articles.yml +6 -6
  44. data/test/fixtures/capitol.rb +3 -3
  45. data/test/fixtures/capitols.yml +16 -16
  46. data/test/fixtures/comments.yml +15 -15
  47. data/test/fixtures/department.rb +5 -5
  48. data/test/fixtures/departments.yml +15 -15
  49. data/test/fixtures/dorms.yml +4 -4
  50. data/test/fixtures/group.rb +2 -2
  51. data/test/fixtures/groups.yml +6 -6
  52. data/test/fixtures/hack.rb +4 -4
  53. data/test/fixtures/hacks.yml +2 -2
  54. data/test/fixtures/membership_status.rb +2 -2
  55. data/test/fixtures/product.rb +9 -9
  56. data/test/fixtures/product_tariff.rb +5 -5
  57. data/test/fixtures/products.yml +11 -11
  58. data/test/fixtures/reading.rb +4 -4
  59. data/test/fixtures/readings.yml +10 -10
  60. data/test/fixtures/reference_code_using_composite_key_alias.rb +8 -8
  61. data/test/fixtures/reference_code_using_simple_key_alias.rb +8 -8
  62. data/test/fixtures/reference_codes.yml +28 -28
  63. data/test/fixtures/reference_types.yml +9 -9
  64. data/test/fixtures/restaurant.rb +9 -9
  65. data/test/fixtures/restaurants.yml +14 -14
  66. data/test/fixtures/restaurants_suburbs.yml +10 -10
  67. data/test/fixtures/room.rb +11 -11
  68. data/test/fixtures/room_assignment.rb +13 -13
  69. data/test/fixtures/room_assignments.yml +24 -24
  70. data/test/fixtures/room_attribute.rb +2 -2
  71. data/test/fixtures/room_attribute_assignment.rb +4 -4
  72. data/test/fixtures/room_attribute_assignments.yml +4 -4
  73. data/test/fixtures/room_attributes.yml +2 -2
  74. data/test/fixtures/rooms.yml +12 -12
  75. data/test/fixtures/seat.rb +5 -5
  76. data/test/fixtures/seats.yml +8 -8
  77. data/test/fixtures/street.rb +2 -2
  78. data/test/fixtures/streets.yml +16 -16
  79. data/test/fixtures/student.rb +3 -3
  80. data/test/fixtures/students.yml +15 -15
  81. data/test/fixtures/suburbs.yml +14 -14
  82. data/test/fixtures/tariffs.yml +1 -1
  83. data/test/plugins/pagination.rb +405 -405
  84. data/test/plugins/pagination_helper.rb +135 -135
  85. data/test/setup.rb +50 -50
  86. data/test/test_aliases.rb +18 -18
  87. data/test/test_associations.rb +6 -20
  88. data/test/test_composite_arrays.rb +24 -24
  89. data/test/test_counter_cache.rb +30 -30
  90. data/test/test_delete.rb +146 -146
  91. data/test/test_dup.rb +37 -37
  92. data/test/test_exists.rb +39 -39
  93. data/test/test_find.rb +1 -1
  94. data/test/test_miscellaneous.rb +32 -32
  95. data/test/test_pagination.rb +35 -35
  96. data/test/test_preload.rb +0 -7
  97. data/test/test_validations.rb +13 -13
  98. metadata +10 -17
  99. data/lib/composite_primary_keys/arel/in.rb +0 -6
  100. data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +0 -24
  101. data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +0 -19
  102. data/lib/composite_primary_keys/relation/predicate_builder/association_query_handler.rb +0 -33
@@ -7,17 +7,11 @@ module CompositePrimaryKeys
7
7
  return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
8
8
  end
9
9
 
10
- if arel.orders.present?
11
- act_on_ignored_order(error_on_ignore)
10
+ if arel.orders.present? || arel.taken.present?
11
+ act_on_order_or_limit_ignored(error_on_ignore)
12
12
  end
13
13
 
14
- batch_limit = of
15
- if limit_value
16
- remaining = limit_value
17
- batch_limit = remaining if remaining < batch_limit
18
- end
19
-
20
- relation = relation.reorder(batch_order).limit(batch_limit)
14
+ relation = relation.reorder(batch_order).limit(of)
21
15
  relation = apply_limits(relation, start, finish)
22
16
  batch_relation = relation
23
17
 
@@ -45,22 +39,9 @@ module CompositePrimaryKeys
45
39
 
46
40
  yield yielded_relation
47
41
 
48
- break if ids.length < batch_limit
49
-
50
- if limit_value
51
- remaining -= ids.length
52
-
53
- if remaining == 0
54
- # Saves a useless iteration when the limit is a multiple of the
55
- # batch size.
56
- break
57
- elsif remaining < batch_limit
58
- relation = relation.limit(remaining)
59
- end
60
- end
61
-
42
+ break if ids.length < of
62
43
  # CPK
63
- batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
44
+ # batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
64
45
  batch_relation = if composite?
65
46
  # CPK
66
47
  # Lexicographically select records
@@ -95,11 +76,9 @@ module CompositePrimaryKeys
95
76
  end
96
77
 
97
78
  def batch_order
98
- # CPK
99
- # "#{quoted_table_name}.#{quoted_primary_key} ASC"
100
79
  self.primary_key.map do |key|
101
- "#{quoted_table_name}.#{key} ASC"
102
- end.join(",")
80
+ arel_attribute(key).asc
81
+ end
103
82
  end
104
83
  end
105
84
  end
@@ -15,22 +15,25 @@ module CompositePrimaryKeys
15
15
  end
16
16
 
17
17
  def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
18
- # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
19
- relation = unscope(:order)
20
-
21
18
  column_alias = column_name
22
19
 
23
20
  # CPK
24
- # if operation == "count" && (relation.limit_value || relation.offset_value)
21
+ #if operation == "count" && has_limit_or_offset?
25
22
  if operation == "count"
26
23
  # Shortcut when limit is zero.
27
- return 0 if relation.limit_value == 0
24
+ return 0 if limit_value == 0
28
25
 
29
- query_builder = build_count_subquery(relation, column_name, distinct)
26
+ query_builder = build_count_subquery(spawn, column_name, distinct)
30
27
  else
28
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
29
+ relation = unscope(:order).distinct!(false)
30
+
31
31
  column = aggregate_column(column_name)
32
32
 
33
33
  select_value = operation_over_aggregate_column(column, operation, distinct)
34
+ if operation == "sum" && distinct
35
+ select_value.distinct = true
36
+ end
34
37
 
35
38
  column_alias = select_value.alias
36
39
  column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
@@ -39,38 +42,35 @@ module CompositePrimaryKeys
39
42
  query_builder = relation.arel
40
43
  end
41
44
 
42
- result = @klass.connection.select_all(query_builder, nil, bound_attributes)
45
+ result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
43
46
  row = result.first
44
47
  value = row && row.values.first
45
- column = result.column_types.fetch(column_alias) do
48
+ type = result.column_types.fetch(column_alias) do
46
49
  type_for(column_name)
47
50
  end
48
51
 
49
- type_cast_calculated_value(value, column, operation)
52
+ type_cast_calculated_value(value, type, operation)
50
53
  end
51
54
 
52
55
  def build_count_subquery(relation, column_name, distinct)
53
- return super(relation, column_name, distinct) unless column_name.kind_of?(Array)
54
- # CPK
55
- # column_alias = Arel.sql('count_column')
56
- subquery_alias = Arel.sql('subquery_for_count')
56
+ relation.select_values = [
57
+ if column_name == :all
58
+ distinct ? table[Arel.star] : Arel.sql(::ActiveRecord::FinderMethods::ONE_AS_ONE)
59
+ # CPK
60
+ elsif column_name.is_a?(Array)
61
+ relation.select_values = column_name.map do |column|
62
+ Arel::Attribute.new(@klass.unscoped.table, column)
63
+ end
64
+ else
65
+ column_alias = Arel.sql("count_column")
66
+ aggregate_column(column_name).as(column_alias)
67
+ end
68
+ ]
57
69
 
58
- # CPK
59
- # aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
60
- # relation.select_values = [aliased_column]
61
- relation.select_values = column_name.map do |column|
62
- Arel::Attribute.new(@klass.unscoped.table, column)
63
- end
70
+ subquery = relation.arel.as(Arel.sql("subquery_for_count"))
71
+ select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
64
72
 
65
- relation = relation.distinct(true)
66
- subquery = relation.arel.as(subquery_alias)
67
-
68
- sm = Arel::SelectManager.new relation.engine
69
- sm.bind_values = relation.arel.bind_values
70
- # CPK
71
- # select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
72
- select_value = operation_over_aggregate_column(Arel.sql("*"), 'count', false)
73
- sm.project(select_value).from(subquery)
73
+ Arel::SelectManager.new(subquery).project(select_value)
74
74
  end
75
75
  end
76
76
  end
@@ -1,9 +1,8 @@
1
1
  module CompositePrimaryKeys
2
2
  module ActiveRecord
3
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
4
+ def apply_join_dependency(join_dependency = construct_join_dependency)
5
+ relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
7
6
 
8
7
  if using_limitable_reflections?(join_dependency.reflections)
9
8
  relation
@@ -11,7 +10,7 @@ module CompositePrimaryKeys
11
10
  if relation.limit_value
12
11
  limited_ids = limited_ids_for(relation)
13
12
  # CPK
14
- # limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
13
+ # limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
15
14
  limited_ids.empty? ? relation.none! : relation.where!(cpk_in_predicate(table, self.primary_keys, limited_ids))
16
15
  end
17
16
  relation.except(:limit, :offset)
@@ -21,37 +20,28 @@ module CompositePrimaryKeys
21
20
  def limited_ids_for(relation)
22
21
  # CPK
23
22
  # values = @klass.connection.columns_for_distinct(
24
- # "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
23
+ # connection.column_name_from_arel_node(arel_attribute(primary_key)),
24
+ # relation.order_values
25
+ # )
25
26
  columns = @klass.primary_keys.map do |key|
26
27
  "#{quoted_table_name}.#{connection.quote_column_name(key)}"
27
28
  end
28
29
  values = @klass.connection.columns_for_distinct(columns, relation.order_values)
29
30
 
30
31
  relation = relation.except(:select).select(values).distinct!
31
- arel = relation.arel
32
-
33
- id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
34
32
 
33
+ id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") }
35
34
  # CPK
36
- #id_rows.map {|row| row[primary_key]}
37
- id_rows.map {|row| row.values}
35
+ # id_rows.map { |row| row[primary_key] }
36
+ id_rows.map do |row|
37
+ @klass.primary_keys.map do |key|
38
+ row[key]
39
+ end
40
+ end
38
41
  end
39
42
 
40
- def exists?(conditions = :none)
41
- if ::ActiveRecord::Base === conditions
42
- conditions = conditions.id
43
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
44
- You are passing an instance of ActiveRecord::Base to `exists?`.
45
- Please pass the id of the object by calling `.id`
46
- MSG
47
- end
48
-
49
- return false if !conditions
50
-
51
- relation = apply_join_dependency(self, construct_join_dependency)
52
- return false if ::ActiveRecord::NullRelation === relation
53
-
54
- relation = relation.except(:select, :order).select(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit(1)
43
+ def construct_relation_for_exists(conditions)
44
+ relation = except(:select, :distinct, :order)._select!(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit!(1)
55
45
 
56
46
  case conditions
57
47
  # CPK
@@ -62,7 +52,7 @@ module CompositePrimaryKeys
62
52
  pk_length = @klass.primary_keys.length
63
53
 
64
54
  if conditions.length == pk_length # E.g. conditions = ['France', 'Paris']
65
- return self.exists?(conditions.to_composite_keys)
55
+ return self.construct_relation_for_exists(conditions.to_composite_keys)
66
56
  else # Assume that conditions contains where relation
67
57
  relation = relation.where(conditions)
68
58
  end
@@ -74,7 +64,7 @@ module CompositePrimaryKeys
74
64
  end
75
65
  end
76
66
 
77
- connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
67
+ relation
78
68
  end
79
69
 
80
70
  def find_with_ids(*ids)
@@ -90,17 +80,21 @@ module CompositePrimaryKeys
90
80
  # ids = ids.flatten.compact.uniq
91
81
  ids = expects_array ? ids.first : ids
92
82
 
83
+ model_name = @klass.name
84
+
93
85
  case ids.size
94
86
  when 0
95
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
87
+ error_message = "Couldn't find #{model_name} without an ID"
88
+ raise RecordNotFound.new(error_message, model_name, primary_key)
96
89
  when 1
97
90
  result = find_one(ids.first)
98
91
  expects_array ? [ result ] : result
99
92
  else
100
93
  find_some(ids)
101
94
  end
102
- rescue RangeError
103
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
95
+ rescue ::RangeError
96
+ error_message = "Couldn't find #{model_name} with an out of range ID"
97
+ raise RecordNotFound.new(error_message, model_name, primary_key, ids)
104
98
  end
105
99
 
106
100
  def last(limit = nil)
@@ -130,32 +124,6 @@ module CompositePrimaryKeys
130
124
  find_last(limit)
131
125
  end
132
126
 
133
-
134
- def find_nth_with_limit(index, limit)
135
- # TODO: once the offset argument is removed from find_nth,
136
- # find_nth_with_limit_and_offset can be merged into this method
137
- #
138
- # CPK
139
- # relation = if order_values.empty? && primary_key
140
- # order(arel_attribute(primary_key).asc)
141
- # else
142
- # self
143
- # end
144
-
145
- relation = self
146
-
147
- if order_values.empty? && primary_key
148
- if composite?
149
- relation = relation.order(primary_keys.map { |pk| arel_attribute(pk).asc })
150
- elsif
151
- relation = relation.order(arel_attribute(primary_key).asc)
152
- end
153
- end
154
-
155
- relation = relation.offset(index) unless index.zero?
156
- relation.limit(limit).to_a
157
- end
158
-
159
127
  def find_one(id)
160
128
  # CPK
161
129
  # if ActiveRecord::Base === id
@@ -247,6 +215,16 @@ module CompositePrimaryKeys
247
215
  raise_record_not_found_exception!(ids, result.size, ids.size)
248
216
  end
249
217
  end
218
+
219
+ def ordered_relation
220
+ if order_values.empty? && primary_key
221
+ # CPK
222
+ #order(arel_attribute(primary_key).asc)
223
+ order(Array(primary_key).map {|key| arel_attribute(key).asc})
224
+ else
225
+ self
226
+ end
227
+ end
250
228
  end
251
229
  end
252
230
  end
@@ -0,0 +1,18 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+ class AssociationQueryValue
4
+ def queries
5
+ # CPK
6
+ if associated_table.association_join_foreign_key.is_a?(Array)
7
+ result = associated_table.association_join_foreign_key.zip(ids).reduce(Hash.new) do |hash, pair|
8
+ hash[pair.first.to_s] = pair.last
9
+ hash
10
+ end
11
+ [result]
12
+ else
13
+ [associated_table.association_join_foreign_key.to_s => ids]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -2,27 +2,39 @@ module CompositePrimaryKeys
2
2
  module ActiveRecord
3
3
  module QueryMethods
4
4
  def reverse_sql_order(order_query)
5
- # CPK
6
- # order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
5
+ if order_query.empty?
6
+ # CPK
7
+ # return [arel_attribute(primary_key).desc] if primary_key
7
8
 
8
- # break apart CPKs
9
- order_query = primary_key.map do |key|
10
- "#{quoted_table_name}.#{connection.quote_column_name(key)} ASC"
11
- end if order_query.empty?
9
+ if primary_key
10
+ # break apart CPKs
11
+ return primary_key.map do |key|
12
+ arel_attribute(key).desc
13
+ end
14
+ else
15
+ raise IrreversibleOrderError,
16
+ "Relation has no current order and table has no primary key to be used as default order"
17
+ end
18
+ end
12
19
 
13
- order_query.map do |o|
20
+ order_query.flat_map do |o|
14
21
  case o
22
+ when Arel::Attribute
23
+ o.desc
15
24
  when Arel::Nodes::Ordering
16
25
  o.reverse
17
- when String, Symbol
18
- o.to_s.split(',').collect do |s|
26
+ when String
27
+ if does_not_support_reverse?(o)
28
+ raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
29
+ end
30
+ o.split(",").map! do |s|
19
31
  s.strip!
20
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
32
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
21
33
  end
22
34
  else
23
35
  o
24
36
  end
25
- end.flatten
37
+ end
26
38
  end
27
39
  end
28
40
  end
@@ -1,32 +1,23 @@
1
1
  module ActiveRecord
2
2
  class Relation
3
3
  class WhereClause
4
- silence_warnings do
5
- def to_h(table_name = nil)
6
- equalities = predicates.grep(Arel::Nodes::Equality)
4
+ def to_h(table_name = nil)
5
+ equalities = predicates.grep(Arel::Nodes::Equality)
7
6
 
8
- # CPK Adds this line, because ours are coming in with AND->{EQUALITY, EQUALITY}
9
- equalities = predicates.grep(Arel::Nodes::And).map(&:children).flatten.grep(Arel::Nodes::Equality) if equalities.empty?
7
+ # CPK Adds this line, because ours are coming in with AND->{EQUALITY, EQUALITY}
8
+ equalities = predicates.grep(Arel::Nodes::And).map(&:children).flatten.grep(Arel::Nodes::Equality) if equalities.empty?
10
9
 
11
- if table_name
12
- equalities = equalities.select do |node|
13
- node.left.relation.name == table_name
14
- end
10
+ if table_name
11
+ equalities = equalities.select do |node|
12
+ node.left.relation.name == table_name
15
13
  end
16
-
17
- binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
18
-
19
- equalities.map do |node|
20
- name = node.left.name
21
- [name, binds.fetch(name.to_s) {
22
- case node.right
23
- when Array then node.right.map(&:val)
24
- when Arel::Nodes::Casted, Arel::Nodes::Quoted
25
- node.right.val
26
- end
27
- }]
28
- end.to_h
29
14
  end
15
+
16
+ equalities.map { |node|
17
+ name = node.left.name.to_s
18
+ value = extract_node_value(node.right)
19
+ [name, value]
20
+ }.to_h
30
21
  end
31
22
  end
32
23
  end
@@ -1,8 +1,6 @@
1
1
  module ActiveRecord
2
2
  module Sanitization
3
3
  module ClassMethods
4
- protected
5
-
6
4
  def expand_hash_conditions_for_aggregates(attrs)
7
5
  expanded_attrs = {}
8
6
  attrs.each do |attr, value|
@@ -1,8 +1,8 @@
1
1
  module CompositePrimaryKeys
2
2
  module VERSION
3
- MAJOR = 10
3
+ MAJOR = 11
4
4
  MINOR = 0
5
- TINY = 5
6
- STRING = [MAJOR, MINOR, TINY].join('.')
5
+ TINY = 0
6
+ STRING = [MAJOR, MINOR, TINY, 'beta1'].join('.')
7
7
  end
8
8
  end