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,43 +1,43 @@
1
- module CompositePrimaryKeys
2
- ID_SEP = ','
3
- ID_SET_SEP = ';'
4
-
5
- module ArrayExtension
6
- def to_composite_keys
7
- CompositeKeys.new(self)
8
- end
9
- end
10
-
11
- def self.normalize(ids)
12
- ids.map do |id|
13
- if id.is_a?(Array)
14
- normalize(id)
15
- elsif id.is_a?(String) && id.index(ID_SEP)
16
- id.split(ID_SEP)
17
- else
18
- id
19
- end
20
- end
21
- end
22
-
23
- class CompositeKeys < Array
24
-
25
- def self.parse(value)
26
- case value
27
- when Array
28
- value.to_composite_keys
29
- when String
30
- self.new(value.split(ID_SEP))
31
- else
32
- raise(ArgumentError, "Unsupported type: #{value}")
33
- end
34
- end
35
-
36
- def to_s
37
- # Doing this makes it easier to parse Base#[](attr_name)
38
- join(ID_SEP)
39
- end
40
- end
41
- end
42
-
43
- Array.send(:include, CompositePrimaryKeys::ArrayExtension)
1
+ module CompositePrimaryKeys
2
+ ID_SEP = ','
3
+ ID_SET_SEP = ';'
4
+
5
+ module ArrayExtension
6
+ def to_composite_keys
7
+ CompositeKeys.new(self)
8
+ end
9
+ end
10
+
11
+ def self.normalize(ids)
12
+ ids.map do |id|
13
+ if id.is_a?(Array)
14
+ normalize(id)
15
+ elsif id.is_a?(String) && id.index(ID_SEP)
16
+ id.split(ID_SEP)
17
+ else
18
+ id
19
+ end
20
+ end
21
+ end
22
+
23
+ class CompositeKeys < Array
24
+
25
+ def self.parse(value)
26
+ case value
27
+ when Array
28
+ value.to_composite_keys
29
+ when String
30
+ self.new(value.split(ID_SEP))
31
+ else
32
+ raise(ArgumentError, "Unsupported type: #{value}")
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ # Doing this makes it easier to parse Base#[](attr_name)
38
+ join(ID_SEP)
39
+ end
40
+ end
41
+ end
42
+
43
+ Array.send(:include, CompositePrimaryKeys::ArrayExtension)
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  end
10
10
  end
11
11
 
12
- def self.establish_connection(spec = ENV["DATABASE_URL"])
12
+ def self.establish_connection(spec = nil)
13
13
  spec ||= ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_sym
14
14
  resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
15
15
  spec = resolver.spec(spec)
@@ -64,8 +64,7 @@ module ActiveRecord
64
64
  connection_handler.clear_active_connections!
65
65
  end
66
66
 
67
- delegate :clear_reloadable_connections!,
68
- :clear_all_connections!,:verify_active_connections!, :to => :connection_handler
67
+ delegate :clear_reloadable_connections!, :clear_all_connections!, :to => :connection_handler
69
68
  end
70
69
  end
71
70
  end
@@ -1,61 +1,61 @@
1
- module ActiveRecord
2
- module Core
3
- def initialize_dup(other) # :nodoc:
4
- @attributes = @attributes.dup
5
- # CPK
6
- # @attributes.reset(self.class.primary_key)
7
- Array(self.class.primary_key).each {|key| @attributes.reset(key)}
8
-
9
- run_callbacks(:initialize) unless _initialize_callbacks.empty?
10
-
11
- @aggregation_cache = {}
12
- @association_cache = {}
13
-
14
- @new_record = true
15
- @destroyed = false
16
-
17
- super
18
- end
19
-
20
- module ClassMethods
21
- def find(*ids) # :nodoc:
22
- # We don't have cache keys for this stuff yet
23
- return super unless ids.length == 1
24
- # Allow symbols to super to maintain compatibility for deprecated finders until Rails 5
25
- return super if ids.first.kind_of?(Symbol)
26
- return super if block_given? ||
27
- primary_key.nil? ||
28
- default_scopes.any? ||
29
- current_scope ||
30
- columns_hash.include?(inheritance_column) ||
31
- ids.first.kind_of?(Array)
32
-
33
- # CPK
34
- return super if self.composite?
35
-
36
- id = ids.first
37
- if ActiveRecord::Base === id
38
- id = id.id
39
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
40
- You are passing an instance of ActiveRecord::Base to `find`.
41
- Please pass the id of the object by calling `.id`
42
- MSG
43
- end
44
- key = primary_key
45
-
46
- s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
47
- find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
48
- where(key => params.bind).limit(1)
49
- }
50
- }
51
- record = s.execute([id], self, connection).first
52
- unless record
53
- raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
54
- end
55
- record
56
- rescue RangeError
57
- raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
58
- end
59
- end
60
- end
1
+ module ActiveRecord
2
+ module Core
3
+ def initialize_dup(other) # :nodoc:
4
+ @attributes = @attributes.dup
5
+ # CPK
6
+ # @attributes.reset(self.class.primary_key)
7
+ Array(self.class.primary_key).each {|key| @attributes.reset(key)}
8
+
9
+ run_callbacks(:initialize) unless _initialize_callbacks.empty?
10
+
11
+ @aggregation_cache = {}
12
+ @association_cache = {}
13
+
14
+ @new_record = true
15
+ @destroyed = false
16
+
17
+ super
18
+ end
19
+
20
+ module ClassMethods
21
+ def find(*ids) # :nodoc:
22
+ # We don't have cache keys for this stuff yet
23
+ return super unless ids.length == 1
24
+ # Allow symbols to super to maintain compatibility for deprecated finders until Rails 5
25
+ return super if ids.first.kind_of?(Symbol)
26
+ return super if block_given? ||
27
+ primary_key.nil? ||
28
+ default_scopes.any? ||
29
+ current_scope ||
30
+ columns_hash.include?(inheritance_column) ||
31
+ ids.first.kind_of?(Array)
32
+
33
+ # CPK
34
+ return super if self.composite?
35
+
36
+ id = ids.first
37
+ if ActiveRecord::Base === id
38
+ id = id.id
39
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
40
+ You are passing an instance of ActiveRecord::Base to `find`.
41
+ Please pass the id of the object by calling `.id`
42
+ MSG
43
+ end
44
+ key = primary_key
45
+
46
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
47
+ find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
48
+ where(key => params.bind).limit(1)
49
+ }
50
+ }
51
+ record = s.execute([id], self, connection).first
52
+ unless record
53
+ raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
54
+ end
55
+ record
56
+ rescue RangeError
57
+ raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
58
+ end
59
+ end
60
+ end
61
61
  end
@@ -1,57 +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(*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
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
57
57
  end
@@ -1,68 +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) {
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
+ 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