composite_primary_keys 14.0.1 → 14.0.2

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.
data/README.rdoc CHANGED
@@ -1,182 +1,182 @@
1
- = Composite Primary Keys for ActiveRecords
2
-
3
- == Summary
4
-
5
- ActiveRecords infamously doesn't support composite primary keys.
6
- This gem, composite_primary_keys, or CPK for short, extends ActiveRecord
7
- to support composite keys.
8
-
9
- == Installation
10
-
11
- gem install composite_primary_keys
12
-
13
- If you are using Rails add the following to your Gemfile:
14
-
15
- gem 'composite_primary_keys', '=x.x.x' (see next section about what version to use)
16
-
17
- == Versions
18
-
19
- Every major version of ActiveRecord has included numerous internal changes. As a result,
20
- CPK has to be rewritten for each version of ActiveRecord. To help keep
21
- things straight, here is the mapping:
22
-
23
- Version 14.x is designed to work with ActiveRecord 7.0.x
24
- Version 13.x is designed to work with ActiveRecord 6.1.x
25
- Version 12.x is designed to work with ActiveRecord 6.0.x
26
- Version 11.x is designed to work with ActiveRecord 5.2.x
27
- Version 10.x is designed to work with ActiveRecord 5.1.x
28
- Version 9.x is designed to work with ActiveRecord 5.0.x
29
- Version 8.x is designed to work with ActiveRecord 4.2.x
30
- Version 7.x is designed to work with ActiveRecord 4.1.x
31
- Version 6.x is designed to work with ActiveRecord 4.0.x
32
- Version 5.x is designed to work with ActiveRecord 3.2.x
33
- Version 4.x is designed to work with ActiveRecord 3.1.x
34
-
35
- Run the following command to list available versions:
36
-
37
- gem list composite_primary_keys -ra
38
-
39
- == The basics
40
-
41
- A model with composite primary keys is defined like this:
42
-
43
- class Membership < ActiveRecord::Base
44
- self.primary_keys = :user_id, :group_id
45
- belongs_to :user
46
- belongs_to :group
47
- has_many :statuses, :class_name => 'MembershipStatus', :foreign_key => [:user_id, :group_id]
48
- end
49
-
50
- Note the addition of the line:
51
-
52
- self.primary_keys = :user_id, :group_id
53
-
54
-
55
- A model associated with a composite key model is defined like this:
56
-
57
- class MembershipStatus < ActiveRecord::Base
58
- belongs_to :membership, :foreign_key => [:user_id, :group_id]
59
- end
60
-
61
- That is, associations can include composite keys too. All Rails association types are supported. Nice.
62
-
63
- == Usage
64
-
65
- Once you’ve created your models to specify composite primary keys (such as the Membership class)
66
- and associations (such as MembershipStatus#membership), you can use them like any normal model
67
- with associations.
68
-
69
- But first, lets check out our primary keys.
70
-
71
- MembershipStatus.primary_key # => "id" # normal single key
72
- Membership.primary_key # => [:user_id, :group_id] # composite keys
73
- Membership.primary_key.to_s # => "user_id,group_id"
74
-
75
- Now we want to be able to find instances using the same syntax we always use for ActiveRecords…
76
-
77
- MembershipStatus.find(1) # single id returns single instance
78
- => <MembershipStatus:0x392a8c8 @attributes={"id"=>"1", "status"=>"Active"}>
79
-
80
- Membership.find([1,1]) # composite ids returns single instance
81
- => <Membership:0x39218b0 @attributes={"user_id"=>"1", "group_id"=>"1"}>
82
-
83
- Notice the use of an array to specify the composite key values.
84
-
85
- NOTE - API CHANGE. CPK Version 6.x and earlier used to allow composite keys to be listed out
86
- like this:
87
-
88
- Membership.find(1,1)
89
-
90
- This usage is no longer supported.
91
-
92
- == Databases
93
-
94
- CPK supports the following databases:
95
-
96
- * PostgreSQL
97
- * MySQL
98
- * MariaDB
99
- * Oracle
100
- * DB2
101
- * SQLite
102
- * SQLServer
103
-
104
- == Tests
105
-
106
- To run tests you first need to install the appropriate gems for the database you want to test. Database gems are
107
- divided into the following bundler groups:
108
-
109
- * mysql
110
- * oracle
111
- * postgresql
112
- * sqlite
113
- * sqlserver
114
-
115
- Since it is likely you do not have all the above databases installed on your computer, you want to install just the
116
- gems for your database. For example, to test postgresql you would install the appropriate gems like this:
117
-
118
- bundler config set --local without "mysql oracle sqlite sqlserver"
119
- bundler install
120
-
121
- Once you have installed the appropriate gems, the next step is to create the test database. There is a rake
122
- command for each database. Using our example:
123
-
124
- rake postgresql:build_database
125
-
126
- You can also rebuild the database if it already exists using this command:
127
-
128
- rake postgresql:rebuild_database
129
-
130
- To get a list of commands for your database use:
131
-
132
- Rake -T
133
-
134
- Finally, to run tests:
135
-
136
- rake postgresql:test
137
-
138
- Travis build status: {<img src="https://travis-ci.com/composite-primary-keys/composite_primary_keys.svg" alt="Build Status" />}[https://travis-ci.com/composite-primary-keys/composite_primary_keys]
139
-
140
- === DB2
141
-
142
- DB2 is no longer supported due to difficulties in getting the ibm_db2 gem to build. Thus tests
143
- have not been run against db2.
144
-
145
- === MariaDb (mysql)
146
-
147
- MariaDb is fully supported with all tests passing.
148
-
149
- === Oracle
150
-
151
- Oracle is fully supported with all tests passing.
152
-
153
- === Postgresql
154
-
155
- Postgresql is fully supported with all tests passing.
156
-
157
- === Sqlite 3
158
-
159
- The sqlite database is created at the path composite_primary_keys/db. Note you must *first* create the database using the
160
- built-in rake task before running tests:
161
-
162
- rake sqlite:build_database
163
-
164
- For sqlite3 to work correctly, you must manually require 'composite_primary_keys/connection_adapters/sqlite3_adapter' after
165
- loading the CPK gem.
166
-
167
- === SqlServer
168
-
169
- SqlServer is partially supported. There are a number of failing tests - patches welcomed.
170
-
171
- == Questions, Discussion and Contributions
172
-
173
- For help please visit https://github.com/composite-primary-keys/composite_primary_keys.
174
-
175
- == Author
176
-
177
- First version was written by Dr Nic Williams.
178
-
179
- Maintained by Charlie Savage
180
-
181
- Contributions by many!
182
-
1
+ = Composite Primary Keys for ActiveRecords
2
+
3
+ == Summary
4
+
5
+ ActiveRecords infamously doesn't support composite primary keys.
6
+ This gem, composite_primary_keys, or CPK for short, extends ActiveRecord
7
+ to support composite keys.
8
+
9
+ == Installation
10
+
11
+ gem install composite_primary_keys
12
+
13
+ If you are using Rails add the following to your Gemfile:
14
+
15
+ gem 'composite_primary_keys', '=x.x.x' (see next section about what version to use)
16
+
17
+ == Versions
18
+
19
+ Every major version of ActiveRecord has included numerous internal changes. As a result,
20
+ CPK has to be rewritten for each version of ActiveRecord. To help keep
21
+ things straight, here is the mapping:
22
+
23
+ Version 14.x is designed to work with ActiveRecord 7.0.x
24
+ Version 13.x is designed to work with ActiveRecord 6.1.x
25
+ Version 12.x is designed to work with ActiveRecord 6.0.x
26
+ Version 11.x is designed to work with ActiveRecord 5.2.x
27
+ Version 10.x is designed to work with ActiveRecord 5.1.x
28
+ Version 9.x is designed to work with ActiveRecord 5.0.x
29
+ Version 8.x is designed to work with ActiveRecord 4.2.x
30
+ Version 7.x is designed to work with ActiveRecord 4.1.x
31
+ Version 6.x is designed to work with ActiveRecord 4.0.x
32
+ Version 5.x is designed to work with ActiveRecord 3.2.x
33
+ Version 4.x is designed to work with ActiveRecord 3.1.x
34
+
35
+ Run the following command to list available versions:
36
+
37
+ gem list composite_primary_keys -ra
38
+
39
+ == The basics
40
+
41
+ A model with composite primary keys is defined like this:
42
+
43
+ class Membership < ActiveRecord::Base
44
+ self.primary_keys = :user_id, :group_id
45
+ belongs_to :user
46
+ belongs_to :group
47
+ has_many :statuses, :class_name => 'MembershipStatus', :foreign_key => [:user_id, :group_id]
48
+ end
49
+
50
+ Note the addition of the line:
51
+
52
+ self.primary_keys = :user_id, :group_id
53
+
54
+
55
+ A model associated with a composite key model is defined like this:
56
+
57
+ class MembershipStatus < ActiveRecord::Base
58
+ belongs_to :membership, :foreign_key => [:user_id, :group_id]
59
+ end
60
+
61
+ That is, associations can include composite keys too. All Rails association types are supported. Nice.
62
+
63
+ == Usage
64
+
65
+ Once you’ve created your models to specify composite primary keys (such as the Membership class)
66
+ and associations (such as MembershipStatus#membership), you can use them like any normal model
67
+ with associations.
68
+
69
+ But first, lets check out our primary keys.
70
+
71
+ MembershipStatus.primary_key # => "id" # normal single key
72
+ Membership.primary_key # => [:user_id, :group_id] # composite keys
73
+ Membership.primary_key.to_s # => "user_id,group_id"
74
+
75
+ Now we want to be able to find instances using the same syntax we always use for ActiveRecords…
76
+
77
+ MembershipStatus.find(1) # single id returns single instance
78
+ => <MembershipStatus:0x392a8c8 @attributes={"id"=>"1", "status"=>"Active"}>
79
+
80
+ Membership.find([1,1]) # composite ids returns single instance
81
+ => <Membership:0x39218b0 @attributes={"user_id"=>"1", "group_id"=>"1"}>
82
+
83
+ Notice the use of an array to specify the composite key values.
84
+
85
+ NOTE - API CHANGE. CPK Version 6.x and earlier used to allow composite keys to be listed out
86
+ like this:
87
+
88
+ Membership.find(1,1)
89
+
90
+ This usage is no longer supported.
91
+
92
+ == Databases
93
+
94
+ CPK supports the following databases:
95
+
96
+ * PostgreSQL
97
+ * MySQL
98
+ * MariaDB
99
+ * Oracle
100
+ * DB2
101
+ * SQLite
102
+ * SQLServer
103
+
104
+ == Tests
105
+
106
+ To run tests you first need to install the appropriate gems for the database you want to test. Database gems are
107
+ divided into the following bundler groups:
108
+
109
+ * mysql
110
+ * oracle
111
+ * postgresql
112
+ * sqlite
113
+ * sqlserver
114
+
115
+ Since it is likely you do not have all the above databases installed on your computer, you want to install just the
116
+ gems for your database. For example, to test postgresql you would install the appropriate gems like this:
117
+
118
+ bundler config set --local without "mysql oracle sqlite sqlserver"
119
+ bundler install
120
+
121
+ Once you have installed the appropriate gems, the next step is to create the test database. There is a rake
122
+ command for each database. Using our example:
123
+
124
+ rake postgresql:build_database
125
+
126
+ You can also rebuild the database if it already exists using this command:
127
+
128
+ rake postgresql:rebuild_database
129
+
130
+ To get a list of commands for your database use:
131
+
132
+ Rake -T
133
+
134
+ Finally, to run tests:
135
+
136
+ rake postgresql:test
137
+
138
+ Travis build status: {<img src="https://travis-ci.com/composite-primary-keys/composite_primary_keys.svg" alt="Build Status" />}[https://travis-ci.com/composite-primary-keys/composite_primary_keys]
139
+
140
+ === DB2
141
+
142
+ DB2 is no longer supported due to difficulties in getting the ibm_db2 gem to build. Thus tests
143
+ have not been run against db2.
144
+
145
+ === MariaDb (mysql)
146
+
147
+ MariaDb is fully supported with all tests passing.
148
+
149
+ === Oracle
150
+
151
+ Oracle is fully supported with all tests passing.
152
+
153
+ === Postgresql
154
+
155
+ Postgresql is fully supported with all tests passing.
156
+
157
+ === Sqlite 3
158
+
159
+ The sqlite database is created at the path composite_primary_keys/db. Note you must *first* create the database using the
160
+ built-in rake task before running tests:
161
+
162
+ rake sqlite:build_database
163
+
164
+ For sqlite3 to work correctly, you must manually require 'composite_primary_keys/connection_adapters/sqlite3_adapter' after
165
+ loading the CPK gem.
166
+
167
+ === SqlServer
168
+
169
+ SqlServer is partially supported. There are a number of failing tests - patches welcomed.
170
+
171
+ == Questions, Discussion and Contributions
172
+
173
+ For help please visit https://github.com/composite-primary-keys/composite_primary_keys.
174
+
175
+ == Author
176
+
177
+ First version was written by Dr Nic Williams.
178
+
179
+ Maintained by Charlie Savage
180
+
181
+ Contributions by many!
182
+
@@ -1,137 +1,137 @@
1
- module ActiveRecord
2
- module Associations
3
- class JoinDependency
4
-
5
- class JoinAssociation < JoinPart # :nodoc:
6
- private
7
- def append_constraints(join, constraints)
8
- if join.is_a?(Arel::Nodes::StringJoin)
9
- join_string = Arel::Nodes::And.new(constraints.unshift join.left)
10
- join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
11
- else
12
- right = join.right
13
- # CPK
14
- if right.expr.children.empty?
15
- right.expr = Arel::Nodes::And.new(constraints)
16
- else
17
- right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
18
- end
19
- end
20
- end
21
- end
22
-
23
- class Aliases # :nodoc:
24
- def column_alias(node, column)
25
- # CPK
26
- #@alias_cache[node][column]
27
- if column.kind_of?(Array)
28
- column.map do |a_column|
29
- @alias_cache[node][a_column]
30
- end
31
- else
32
- @alias_cache[node][column]
33
- end
34
- end
35
- end
36
-
37
- def instantiate(result_set, strict_loading_value, &block)
38
- primary_key = aliases.column_alias(join_root, join_root.primary_key)
39
-
40
- seen = Hash.new { |i, parent|
41
- i[parent] = Hash.new { |j, child_class|
42
- j[child_class] = {}
43
- }
44
- }.compare_by_identity
45
-
46
- model_cache = Hash.new { |h, klass| h[klass] = {} }
47
- parents = model_cache[join_root]
48
-
49
- column_aliases = aliases.column_aliases(join_root)
50
- column_names = []
51
-
52
- result_set.columns.each do |name|
53
- column_names << name unless /\At\d+_r\d+\z/.match?(name)
54
- end
55
-
56
- if column_names.empty?
57
- column_types = {}
58
- else
59
- column_types = result_set.column_types
60
- unless column_types.empty?
61
- attribute_types = join_root.attribute_types
62
- column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
63
- end
64
- column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
65
- end
66
-
67
- message_bus = ActiveSupport::Notifications.instrumenter
68
-
69
- payload = {
70
- record_count: result_set.length,
71
- class_name: join_root.base_klass.name
72
- }
73
-
74
- message_bus.instrument("instantiation.active_record", payload) do
75
- result_set.each { |row_hash|
76
- # CPK
77
- # parent_key = primary_key ? row_hash[primary_key] : row_hash
78
- parent_key = if primary_key.kind_of?(Array)
79
- primary_key.map {|key| row_hash[key]}
80
- else
81
- primary_key ? row_hash[primary_key] : row_hash
82
- end
83
-
84
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
85
- construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
86
- }
87
- end
88
-
89
- parents.values
90
- end
91
-
92
- def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
93
- return if ar_parent.nil?
94
-
95
- parent.children.each do |node|
96
- if node.reflection.collection?
97
- other = ar_parent.association(node.reflection.name)
98
- other.loaded!
99
- elsif ar_parent.association_cached?(node.reflection.name)
100
- model = ar_parent.association(node.reflection.name).target
101
- construct(model, node, row, seen, model_cache, strict_loading_value)
102
- next
103
- end
104
-
105
- key = aliases.column_alias(node, node.primary_key)
106
- # CPK
107
- if key.is_a?(Array)
108
- id = Array(key).map do |column_alias|
109
- row[column_alias]
110
- end
111
- # At least the first value in the key has to be set. Should we require all values to be set?
112
- id = nil if id.first.nil?
113
- else # original
114
- id = row[key]
115
- end
116
-
117
- if id.nil?
118
- nil_association = ar_parent.association(node.reflection.name)
119
- nil_association.loaded!
120
- next
121
- end
122
-
123
- model = seen[ar_parent][node][id]
124
-
125
- if model
126
- construct(model, node, row, seen, model_cache, strict_loading_value)
127
- else
128
- model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
129
-
130
- seen[ar_parent][node][id] = model
131
- construct(model, node, row, seen, model_cache, strict_loading_value)
132
- end
133
- end
134
- end
135
- end
136
- end
137
- end
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency
4
+
5
+ class JoinAssociation < JoinPart # :nodoc:
6
+ private
7
+ def append_constraints(join, constraints)
8
+ if join.is_a?(Arel::Nodes::StringJoin)
9
+ join_string = Arel::Nodes::And.new(constraints.unshift join.left)
10
+ join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
11
+ else
12
+ right = join.right
13
+ # CPK
14
+ if right.expr.is_a?(Arel::Nodes::And) && right.expr.children.empty?
15
+ right.expr = Arel::Nodes::And.new(constraints)
16
+ else
17
+ right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ class Aliases # :nodoc:
24
+ def column_alias(node, column)
25
+ # CPK
26
+ #@alias_cache[node][column]
27
+ if column.kind_of?(Array)
28
+ column.map do |a_column|
29
+ @alias_cache[node][a_column]
30
+ end
31
+ else
32
+ @alias_cache[node][column]
33
+ end
34
+ end
35
+ end
36
+
37
+ def instantiate(result_set, strict_loading_value, &block)
38
+ primary_key = aliases.column_alias(join_root, join_root.primary_key)
39
+
40
+ seen = Hash.new { |i, parent|
41
+ i[parent] = Hash.new { |j, child_class|
42
+ j[child_class] = {}
43
+ }
44
+ }.compare_by_identity
45
+
46
+ model_cache = Hash.new { |h, klass| h[klass] = {} }
47
+ parents = model_cache[join_root]
48
+
49
+ column_aliases = aliases.column_aliases(join_root)
50
+ column_names = []
51
+
52
+ result_set.columns.each do |name|
53
+ column_names << name unless /\At\d+_r\d+\z/.match?(name)
54
+ end
55
+
56
+ if column_names.empty?
57
+ column_types = {}
58
+ else
59
+ column_types = result_set.column_types
60
+ unless column_types.empty?
61
+ attribute_types = join_root.attribute_types
62
+ column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
63
+ end
64
+ column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
65
+ end
66
+
67
+ message_bus = ActiveSupport::Notifications.instrumenter
68
+
69
+ payload = {
70
+ record_count: result_set.length,
71
+ class_name: join_root.base_klass.name
72
+ }
73
+
74
+ message_bus.instrument("instantiation.active_record", payload) do
75
+ result_set.each { |row_hash|
76
+ # CPK
77
+ # parent_key = primary_key ? row_hash[primary_key] : row_hash
78
+ parent_key = if primary_key.kind_of?(Array)
79
+ primary_key.map {|key| row_hash[key]}
80
+ else
81
+ primary_key ? row_hash[primary_key] : row_hash
82
+ end
83
+
84
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
85
+ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
86
+ }
87
+ end
88
+
89
+ parents.values
90
+ end
91
+
92
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
93
+ return if ar_parent.nil?
94
+
95
+ parent.children.each do |node|
96
+ if node.reflection.collection?
97
+ other = ar_parent.association(node.reflection.name)
98
+ other.loaded!
99
+ elsif ar_parent.association_cached?(node.reflection.name)
100
+ model = ar_parent.association(node.reflection.name).target
101
+ construct(model, node, row, seen, model_cache, strict_loading_value)
102
+ next
103
+ end
104
+
105
+ key = aliases.column_alias(node, node.primary_key)
106
+ # CPK
107
+ if key.is_a?(Array)
108
+ id = Array(key).map do |column_alias|
109
+ row[column_alias]
110
+ end
111
+ # At least the first value in the key has to be set. Should we require all values to be set?
112
+ id = nil if id.first.nil?
113
+ else # original
114
+ id = row[key]
115
+ end
116
+
117
+ if id.nil?
118
+ nil_association = ar_parent.association(node.reflection.name)
119
+ nil_association.loaded!
120
+ next
121
+ end
122
+
123
+ model = seen[ar_parent][node][id]
124
+
125
+ if model
126
+ construct(model, node, row, seen, model_cache, strict_loading_value)
127
+ else
128
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
129
+
130
+ seen[ar_parent][node][id] = model
131
+ construct(model, node, row, seen, model_cache, strict_loading_value)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end