globe-composite_primary_keys 3.0.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.
- data/History.txt +203 -0
- data/Manifest.txt +121 -0
- data/README.txt +41 -0
- data/README_DB2.txt +33 -0
- data/Rakefile +30 -0
- data/composite_primary_keys.gemspec +17 -0
- data/lib/adapter_helper/base.rb +63 -0
- data/lib/adapter_helper/mysql.rb +13 -0
- data/lib/adapter_helper/oracle.rb +12 -0
- data/lib/adapter_helper/oracle_enhanced.rb +12 -0
- data/lib/adapter_helper/postgresql.rb +13 -0
- data/lib/adapter_helper/sqlite3.rb +13 -0
- data/lib/composite_primary_keys.rb +63 -0
- data/lib/composite_primary_keys/association_preload.rb +162 -0
- data/lib/composite_primary_keys/associations.rb +159 -0
- data/lib/composite_primary_keys/attribute_methods.rb +84 -0
- data/lib/composite_primary_keys/base.rb +200 -0
- data/lib/composite_primary_keys/composite_arrays.rb +29 -0
- data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +9 -0
- data/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb +21 -0
- data/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +15 -0
- data/lib/composite_primary_keys/connection_adapters/oracle_enhanced_adapter.rb +17 -0
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +53 -0
- data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +15 -0
- data/lib/composite_primary_keys/finder_methods.rb +68 -0
- data/lib/composite_primary_keys/fixtures.rb +8 -0
- data/lib/composite_primary_keys/read.rb +25 -0
- data/lib/composite_primary_keys/reflection.rb +39 -0
- data/lib/composite_primary_keys/relation.rb +31 -0
- data/lib/composite_primary_keys/through_association_scope.rb +212 -0
- data/lib/composite_primary_keys/validations/uniqueness.rb +118 -0
- data/lib/composite_primary_keys/version.rb +9 -0
- data/loader.rb +24 -0
- data/local/database_connections.rb.sample +12 -0
- data/local/paths.rb.sample +2 -0
- data/local/tasks.rb.sample +2 -0
- data/scripts/console.rb +48 -0
- data/scripts/txt2html +67 -0
- data/scripts/txt2js +59 -0
- data/tasks/activerecord_selection.rake +43 -0
- data/tasks/databases.rake +12 -0
- data/tasks/databases/mysql.rake +30 -0
- data/tasks/databases/oracle.rake +25 -0
- data/tasks/databases/postgresql.rake +25 -0
- data/tasks/databases/sqlite3.rake +28 -0
- data/tasks/deployment.rake +22 -0
- data/tasks/local_setup.rake +13 -0
- data/tasks/website.rake +18 -0
- data/test/README_tests.txt +67 -0
- data/test/abstract_unit.rb +103 -0
- data/test/connections/native_ibm_db/connection.rb +23 -0
- data/test/connections/native_mysql/connection.rb +13 -0
- data/test/connections/native_oracle/connection.rb +14 -0
- data/test/connections/native_oracle_enhanced/connection.rb +20 -0
- data/test/connections/native_postgresql/connection.rb +8 -0
- data/test/connections/native_sqlite/connection.rb +9 -0
- data/test/fixtures/article.rb +5 -0
- data/test/fixtures/article_group.rb +4 -0
- data/test/fixtures/article_groups.yml +7 -0
- data/test/fixtures/articles.yml +6 -0
- data/test/fixtures/comment.rb +6 -0
- data/test/fixtures/comments.yml +16 -0
- data/test/fixtures/db_definitions/db2-create-tables.sql +113 -0
- data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -0
- data/test/fixtures/db_definitions/mysql.sql +181 -0
- data/test/fixtures/db_definitions/oracle.drop.sql +39 -0
- data/test/fixtures/db_definitions/oracle.sql +188 -0
- data/test/fixtures/db_definitions/postgresql.sql +206 -0
- data/test/fixtures/db_definitions/sqlite.sql +166 -0
- data/test/fixtures/department.rb +5 -0
- data/test/fixtures/departments.yml +3 -0
- data/test/fixtures/dorm.rb +3 -0
- data/test/fixtures/dorms.yml +2 -0
- data/test/fixtures/employee.rb +4 -0
- data/test/fixtures/employees.yml +9 -0
- data/test/fixtures/group.rb +3 -0
- data/test/fixtures/groups.yml +3 -0
- data/test/fixtures/hack.rb +6 -0
- data/test/fixtures/hacks.yml +2 -0
- data/test/fixtures/kitchen_sink.rb +3 -0
- data/test/fixtures/kitchen_sinks.yml +5 -0
- data/test/fixtures/membership.rb +10 -0
- data/test/fixtures/membership_status.rb +3 -0
- data/test/fixtures/membership_statuses.yml +10 -0
- data/test/fixtures/memberships.yml +6 -0
- data/test/fixtures/product.rb +7 -0
- data/test/fixtures/product_tariff.rb +5 -0
- data/test/fixtures/product_tariffs.yml +12 -0
- data/test/fixtures/products.yml +6 -0
- data/test/fixtures/reading.rb +4 -0
- data/test/fixtures/readings.yml +10 -0
- data/test/fixtures/reference_code.rb +7 -0
- data/test/fixtures/reference_codes.yml +28 -0
- data/test/fixtures/reference_type.rb +7 -0
- data/test/fixtures/reference_types.yml +9 -0
- data/test/fixtures/restaurant.rb +6 -0
- data/test/fixtures/restaurants.yml +5 -0
- data/test/fixtures/restaurants_suburbs.yml +11 -0
- data/test/fixtures/room.rb +10 -0
- data/test/fixtures/room_assignment.rb +4 -0
- data/test/fixtures/room_assignments.yml +4 -0
- data/test/fixtures/room_attribute.rb +3 -0
- data/test/fixtures/room_attribute_assignment.rb +5 -0
- data/test/fixtures/room_attribute_assignments.yml +4 -0
- data/test/fixtures/room_attributes.yml +3 -0
- data/test/fixtures/rooms.yml +3 -0
- data/test/fixtures/seat.rb +5 -0
- data/test/fixtures/seats.yml +4 -0
- data/test/fixtures/street.rb +3 -0
- data/test/fixtures/streets.yml +15 -0
- data/test/fixtures/student.rb +4 -0
- data/test/fixtures/students.yml +2 -0
- data/test/fixtures/suburb.rb +6 -0
- data/test/fixtures/suburbs.yml +9 -0
- data/test/fixtures/tariff.rb +6 -0
- data/test/fixtures/tariffs.yml +13 -0
- data/test/fixtures/user.rb +10 -0
- data/test/fixtures/users.yml +6 -0
- data/test/hash_tricks.rb +34 -0
- data/test/plugins/pagination.rb +405 -0
- data/test/plugins/pagination_helper.rb +135 -0
- data/test/test_associations.rb +178 -0
- data/test/test_attribute_methods.rb +22 -0
- data/test/test_attributes.rb +80 -0
- data/test/test_clone.rb +34 -0
- data/test/test_composite_arrays.rb +32 -0
- data/test/test_create.rb +68 -0
- data/test/test_delete.rb +83 -0
- data/test/test_exists.rb +25 -0
- data/test/test_find.rb +73 -0
- data/test/test_ids.rb +90 -0
- data/test/test_miscellaneous.rb +39 -0
- data/test/test_pagination.rb +38 -0
- data/test/test_polymorphic.rb +32 -0
- data/test/test_santiago.rb +27 -0
- data/test/test_suite.rb +19 -0
- data/test/test_tutorial_example.rb +26 -0
- data/test/test_update.rb +40 -0
- data/test/test_validations.rb +11 -0
- data/website/index.html +195 -0
- data/website/index.txt +159 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +126 -0
- data/website/template.js +3 -0
- data/website/template.rhtml +53 -0
- data/website/version-raw.js +3 -0
- data/website/version-raw.txt +2 -0
- data/website/version.js +4 -0
- data/website/version.txt +3 -0
- metadata +339 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module AdapterHelper
|
|
2
|
+
class Base
|
|
3
|
+
class << self
|
|
4
|
+
attr_accessor :adapter
|
|
5
|
+
|
|
6
|
+
def load_connection_from_env(adapter)
|
|
7
|
+
self.adapter = adapter
|
|
8
|
+
unless ENV['cpk_adapters']
|
|
9
|
+
puts error_msg_setup_helper
|
|
10
|
+
exit
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Base.configurations = YAML.load(ENV['cpk_adapters'])
|
|
14
|
+
unless spec = ActiveRecord::Base.configurations[adapter]
|
|
15
|
+
puts error_msg_adapter_helper
|
|
16
|
+
exit
|
|
17
|
+
end
|
|
18
|
+
spec[:adapter] = adapter
|
|
19
|
+
spec
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def error_msg_setup_helper
|
|
23
|
+
<<-EOS
|
|
24
|
+
Setup Helper:
|
|
25
|
+
CPK now has a place for your individual testing configuration.
|
|
26
|
+
That is, instead of hardcoding it in the Rakefile and test/connections files,
|
|
27
|
+
there is now a local/database_connections.rb file that is NOT in the
|
|
28
|
+
repository. Your personal DB information (username, password etc) can
|
|
29
|
+
be stored here without making it difficult to submit patches etc.
|
|
30
|
+
|
|
31
|
+
Installation:
|
|
32
|
+
i) cp locals/database_connections.rb.sample locals/database_connections.rb
|
|
33
|
+
ii) For #{adapter} connection details see "Adapter Setup Helper" below.
|
|
34
|
+
iii) Rerun this task
|
|
35
|
+
|
|
36
|
+
#{error_msg_adapter_helper}
|
|
37
|
+
|
|
38
|
+
Current ENV:
|
|
39
|
+
#{ENV.inspect}
|
|
40
|
+
EOS
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def error_msg_adapter_helper
|
|
44
|
+
<<-EOS
|
|
45
|
+
Adapter Setup Helper:
|
|
46
|
+
To run #{adapter} tests, you need to setup your #{adapter} connections.
|
|
47
|
+
In your local/database_connections.rb file, within the ENV['cpk_adapter'] hash, add:
|
|
48
|
+
"#{adapter}" => { adapter settings }
|
|
49
|
+
|
|
50
|
+
That is, it will look like:
|
|
51
|
+
ENV['cpk_adapters'] = {
|
|
52
|
+
"#{adapter}" => {
|
|
53
|
+
:adapter => "#{adapter}",
|
|
54
|
+
:username => "root",
|
|
55
|
+
:password => "root",
|
|
56
|
+
# ...
|
|
57
|
+
}
|
|
58
|
+
}.to_yaml
|
|
59
|
+
EOS
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'base')
|
|
2
|
+
|
|
3
|
+
module AdapterHelper
|
|
4
|
+
class MySQL < Base
|
|
5
|
+
class << self
|
|
6
|
+
def load_connection_from_env
|
|
7
|
+
spec = super('mysql')
|
|
8
|
+
spec[:database] ||= 'composite_primary_keys_unittest'
|
|
9
|
+
spec
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'base')
|
|
2
|
+
|
|
3
|
+
module AdapterHelper
|
|
4
|
+
class Postgresql < Base
|
|
5
|
+
class << self
|
|
6
|
+
def load_connection_from_env
|
|
7
|
+
spec = super('postgresql')
|
|
8
|
+
spec[:database] ||= 'composite_primary_keys_unittest'
|
|
9
|
+
spec
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Copyright (c) 2006 Nic Williams
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
# a copy of this software and associated documentation files (the
|
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
# the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be
|
|
13
|
+
# included in all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
#++
|
|
23
|
+
|
|
24
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
|
25
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
|
26
|
+
|
|
27
|
+
unless defined?(ActiveRecord)
|
|
28
|
+
begin
|
|
29
|
+
require 'active_record'
|
|
30
|
+
rescue LoadError
|
|
31
|
+
require 'rubygems'
|
|
32
|
+
require_gem 'activerecord'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
require 'active_record/associations.rb'
|
|
37
|
+
require 'active_record/associations/association_collection'
|
|
38
|
+
require 'active_record/associations/association_proxy'
|
|
39
|
+
require 'active_record/associations/belongs_to_association'
|
|
40
|
+
require 'active_record/associations/belongs_to_polymorphic_association'
|
|
41
|
+
require 'active_record/associations/has_and_belongs_to_many_association'
|
|
42
|
+
require 'active_record/associations/has_many_association'
|
|
43
|
+
require 'active_record/associations/has_one_association'
|
|
44
|
+
require 'active_record/associations/has_one_through_association'
|
|
45
|
+
|
|
46
|
+
require 'composite_primary_keys/fixtures'
|
|
47
|
+
require 'composite_primary_keys/composite_arrays'
|
|
48
|
+
require 'composite_primary_keys/associations'
|
|
49
|
+
require 'composite_primary_keys/through_association_scope'
|
|
50
|
+
require 'composite_primary_keys/association_preload'
|
|
51
|
+
require 'composite_primary_keys/reflection'
|
|
52
|
+
require 'composite_primary_keys/relation'
|
|
53
|
+
require 'composite_primary_keys/read'
|
|
54
|
+
require 'composite_primary_keys/finder_methods'
|
|
55
|
+
require 'composite_primary_keys/base'
|
|
56
|
+
require 'composite_primary_keys/validations/uniqueness'
|
|
57
|
+
|
|
58
|
+
Dir[File.dirname(__FILE__) + '/composite_primary_keys/connection_adapters/*.rb'].each do |adapter|
|
|
59
|
+
begin
|
|
60
|
+
require adapter.gsub('.rb','')
|
|
61
|
+
rescue MissingSourceFile
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module CompositePrimaryKeys
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module AssociationPreload
|
|
4
|
+
def self.append_features(base)
|
|
5
|
+
super
|
|
6
|
+
base.send(:extend, ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Composite key versions of Association functions
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
|
|
12
|
+
table_name = reflection.klass.quoted_table_name
|
|
13
|
+
id_to_record_map, ids = construct_id_map(records)
|
|
14
|
+
records.each {|record| record.send(reflection.name).loaded}
|
|
15
|
+
options = reflection.options
|
|
16
|
+
|
|
17
|
+
if composite?
|
|
18
|
+
where = (primary_key * ids.size).in_groups_of(primary_key.size).map do |keys|
|
|
19
|
+
"(" + keys.map{|key| "t0.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")"
|
|
20
|
+
end.join(" OR ")
|
|
21
|
+
|
|
22
|
+
conditions = [where, ids].flatten
|
|
23
|
+
joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{full_composite_join_clause(reflection, reflection.klass.table_name, reflection.klass.primary_key, 't0', reflection.association_foreign_key)}"
|
|
24
|
+
parent_primary_keys = reflection.cpk_primary_key.map{|k| "t0.#{connection.quote_column_name(k)}"}
|
|
25
|
+
|
|
26
|
+
concat_arr = ["'['"] +
|
|
27
|
+
parent_primary_keys.zip(["', '"] * (parent_primary_keys.size - 1)).flatten.compact +
|
|
28
|
+
["']'"]
|
|
29
|
+
|
|
30
|
+
parent_record_id = connection.concat(*concat_arr)
|
|
31
|
+
else
|
|
32
|
+
conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
|
|
33
|
+
conditions << append_conditions(reflection, preload_options)
|
|
34
|
+
conditions = [conditions, ids]
|
|
35
|
+
joins = "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}"
|
|
36
|
+
parent_record_id = reflection.primary_key_name
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
associated_records = reflection.klass.unscoped.where(conditions).
|
|
40
|
+
includes(options[:include]).
|
|
41
|
+
joins(joins).
|
|
42
|
+
select("#{options[:select] || table_name+'.*'}, #{parent_record_id} as the_parent_record_id").
|
|
43
|
+
order(options[:order]).to_a
|
|
44
|
+
|
|
45
|
+
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def preload_belongs_to_association(records, reflection, preload_options={})
|
|
49
|
+
return if records.first.send("loaded_#{reflection.name}?")
|
|
50
|
+
options = reflection.options
|
|
51
|
+
|
|
52
|
+
ids = Array.new
|
|
53
|
+
|
|
54
|
+
if options[:polymorphic]
|
|
55
|
+
# CPK
|
|
56
|
+
#polymorph_type = options[:foreign_type]
|
|
57
|
+
#klasses_and_ids = {}
|
|
58
|
+
|
|
59
|
+
# Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
|
|
60
|
+
#records.each do |record|
|
|
61
|
+
# if klass = record.send(polymorph_type)
|
|
62
|
+
# klass_id = record.send(primary_key_name)
|
|
63
|
+
# if klass_id
|
|
64
|
+
# id_map = klasses_and_ids[klass] ||= {}
|
|
65
|
+
# id_list_for_klass_id = (id_map[klass_id.to_s] ||= [])
|
|
66
|
+
# id_list_for_klass_id << record
|
|
67
|
+
# end
|
|
68
|
+
# end
|
|
69
|
+
#end
|
|
70
|
+
#klasses_and_ids = klasses_and_ids.to_a
|
|
71
|
+
raise AssociationNotSupported, "Polymorphic joins not supported for composite keys"
|
|
72
|
+
else
|
|
73
|
+
# I need to keep the original ids for each record (as opposed to the stringified) so
|
|
74
|
+
# that they get properly converted for each db so the id_map ends up looking like:
|
|
75
|
+
#
|
|
76
|
+
# { '1,2' => {:id => [1,2], :records => [...records...]}}
|
|
77
|
+
id_map = {}
|
|
78
|
+
|
|
79
|
+
records.each do |record|
|
|
80
|
+
keys = reflection.cpk_primary_key.map{|k| record.send(k)}
|
|
81
|
+
ids << keys
|
|
82
|
+
|
|
83
|
+
mapped_records = (id_map[keys.to_s] ||= [])
|
|
84
|
+
mapped_records << record
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
klasses_and_ids = [[reflection.klass.name, id_map]]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
klasses_and_ids.each do |klass_and_id|
|
|
91
|
+
klass_name, id_map = *klass_and_id
|
|
92
|
+
next if id_map.empty?
|
|
93
|
+
klass = klass_name.constantize
|
|
94
|
+
|
|
95
|
+
table_name = klass.quoted_table_name
|
|
96
|
+
primary_key = [reflection.options[:primary_key] || klass.primary_key].flatten
|
|
97
|
+
|
|
98
|
+
# CPK
|
|
99
|
+
conditions = id_map.map do |key, value|
|
|
100
|
+
"(" +
|
|
101
|
+
primary_key.map do |key|
|
|
102
|
+
"#{table_name}.#{connection.quote_column_name(key)} = ?"
|
|
103
|
+
end.join(' AND ') + ")"
|
|
104
|
+
end.join(' OR ')
|
|
105
|
+
|
|
106
|
+
conditions << append_conditions(reflection, preload_options)
|
|
107
|
+
|
|
108
|
+
conditions = [conditions] + ids.uniq.flatten
|
|
109
|
+
|
|
110
|
+
associated_records = klass.unscoped.where(conditions).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
|
|
111
|
+
|
|
112
|
+
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def find_associated_records(ids, reflection, preload_options)
|
|
117
|
+
options = reflection.options
|
|
118
|
+
table_name = reflection.klass.quoted_table_name
|
|
119
|
+
|
|
120
|
+
if interface = reflection.options[:as]
|
|
121
|
+
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
|
|
122
|
+
else
|
|
123
|
+
foreign_key = reflection.cpk_primary_key
|
|
124
|
+
|
|
125
|
+
where = (foreign_key * ids.size).in_groups_of(foreign_key.size).map do |keys|
|
|
126
|
+
"(" + keys.map{|key| "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name(key)} = ?"}.join(" AND ") + ")"
|
|
127
|
+
end.join(" OR ")
|
|
128
|
+
|
|
129
|
+
conditions = [where, ids].flatten
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
conditions[0] << append_conditions(reflection, preload_options)
|
|
133
|
+
|
|
134
|
+
find_options = {
|
|
135
|
+
:select => preload_options[:select] || options[:select] || "#{table_name}.*",
|
|
136
|
+
:include => preload_options[:include] || options[:include],
|
|
137
|
+
# CPK
|
|
138
|
+
# :conditions => [conditions, ids],
|
|
139
|
+
:conditions => conditions,
|
|
140
|
+
:joins => options[:joins],
|
|
141
|
+
:group => preload_options[:group] || options[:group],
|
|
142
|
+
:order => preload_options[:order] || options[:order]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
reflection.klass.unscoped.apply_finder_options(find_options).to_a
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def full_composite_join_clause(reflection, table1, full_keys1, table2, full_keys2)
|
|
149
|
+
connection = reflection.active_record.connection
|
|
150
|
+
full_keys1 = full_keys1.split(CompositePrimaryKeys::ID_SEP) if full_keys1.is_a?(String)
|
|
151
|
+
full_keys2 = full_keys2.split(CompositePrimaryKeys::ID_SEP) if full_keys2.is_a?(String)
|
|
152
|
+
where_clause = [full_keys1, full_keys2].transpose.map do |key_pair|
|
|
153
|
+
quoted1 = connection.quote_table_name(table1)
|
|
154
|
+
quoted2 = connection.quote_table_name(table2)
|
|
155
|
+
"#{quoted1}.#{connection.quote_column_name(key_pair.first)}=#{quoted2}.#{connection.quote_column_name(key_pair.last)}"
|
|
156
|
+
end.join(" AND ")
|
|
157
|
+
"(#{where_clause})"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Associations
|
|
3
|
+
class AssociationProxy
|
|
4
|
+
def full_columns_equals(table_name, keys, quoted_ids)
|
|
5
|
+
quoted_table_name = @owner.connection.quote_table_name(table_name)
|
|
6
|
+
|
|
7
|
+
keys = [keys].flatten
|
|
8
|
+
ids = [quoted_ids].flatten
|
|
9
|
+
|
|
10
|
+
[keys,ids].transpose.map do |key, id|
|
|
11
|
+
"(#{quoted_table_name}.#{@owner.connection.quote_column_name(key)} = #{id})"
|
|
12
|
+
end.join(' AND ')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def set_belongs_to_association_for(record)
|
|
16
|
+
if @reflection.options[:as]
|
|
17
|
+
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
|
18
|
+
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
|
19
|
+
else
|
|
20
|
+
unless @owner.new_record?
|
|
21
|
+
primary_key = @reflection.options[:primary_key] || :id
|
|
22
|
+
# CPK
|
|
23
|
+
# record[@reflection.primary_key_name] = @owner.send(primary_key)
|
|
24
|
+
values = [@owner.send(primary_key)].flatten
|
|
25
|
+
key_values = @reflection.cpk_primary_key.zip(values)
|
|
26
|
+
key_values.each {|key, value| record[key] = value}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class HasAndBelongsToManyAssociation
|
|
33
|
+
def construct_sql
|
|
34
|
+
if @reflection.options[:finder_sql]
|
|
35
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
|
36
|
+
else
|
|
37
|
+
# CPK
|
|
38
|
+
# @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
|
|
39
|
+
@finder_sql = full_columns_equals(@reflection.options[:join_table], @reflection.cpk_primary_key, owner_quoted_id)
|
|
40
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
join_condition = if composite?
|
|
44
|
+
conditions = Array.new
|
|
45
|
+
primary_keys.length.times do |i|
|
|
46
|
+
conditions << "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key[i]} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key[i]}"
|
|
47
|
+
end
|
|
48
|
+
conditions.join(' AND ')
|
|
49
|
+
else
|
|
50
|
+
"#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
|
51
|
+
end
|
|
52
|
+
#@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
|
53
|
+
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON (#{join_condition})"
|
|
54
|
+
|
|
55
|
+
construct_counter_sql
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class HasManyAssociation
|
|
60
|
+
def construct_sql
|
|
61
|
+
case
|
|
62
|
+
when @reflection.options[:finder_sql]
|
|
63
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
|
64
|
+
|
|
65
|
+
when @reflection.options[:as]
|
|
66
|
+
@finder_sql =
|
|
67
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
|
68
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
|
69
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
|
70
|
+
|
|
71
|
+
else
|
|
72
|
+
# CPK
|
|
73
|
+
# @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
|
74
|
+
@finder_sql = full_columns_equals(@reflection.quoted_table_name, @reflection.cpk_primary_key, owner_quoted_id)
|
|
75
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
construct_counter_sql
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Deletes the records according to the <tt>:dependent</tt> option.
|
|
82
|
+
def delete_records(records)
|
|
83
|
+
case @reflection.options[:dependent]
|
|
84
|
+
when :destroy
|
|
85
|
+
records.each { |r| r.destroy }
|
|
86
|
+
when :delete_all
|
|
87
|
+
@reflection.klass.delete(records.map { |record| record.id })
|
|
88
|
+
else
|
|
89
|
+
relation = Arel::Table.new(@reflection.table_name)
|
|
90
|
+
# CPK
|
|
91
|
+
# relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
|
92
|
+
# and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id)))
|
|
93
|
+
# ).update(relation[@reflection.primary_key_name] => nil)
|
|
94
|
+
id_predicate = nil
|
|
95
|
+
owner_key_values = @reflection.cpk_primary_key.zip([@owner.id].flatten)
|
|
96
|
+
owner_key_values.each do |key, value|
|
|
97
|
+
eq = relation[key].eq(value)
|
|
98
|
+
id_predicate = id_predicate ? id_predicate.and(eq) : eq
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
record_predicates = nil
|
|
102
|
+
records.each do |record|
|
|
103
|
+
keys = [@reflection.klass.primary_key].flatten
|
|
104
|
+
values = [record.id].flatten
|
|
105
|
+
|
|
106
|
+
record_predicate = nil
|
|
107
|
+
keys.zip(values).each do |key, value|
|
|
108
|
+
eq = relation[key].eq(value)
|
|
109
|
+
record_predicate = record_predicate ? record_predicate.and(eq) : eq
|
|
110
|
+
end
|
|
111
|
+
record_predicates = record_predicates ? record_predicates.or(record_predicate) : record_predicate
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
relation = relation.where(id_predicate.and(record_predicates))
|
|
115
|
+
|
|
116
|
+
nullify = @reflection.cpk_primary_key.inject(Hash.new) do |hash, key|
|
|
117
|
+
hash[relation[key]] = nil
|
|
118
|
+
hash
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
relation.update(nullify)
|
|
122
|
+
|
|
123
|
+
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class HasOneAssociation
|
|
129
|
+
def construct_sql
|
|
130
|
+
case
|
|
131
|
+
when @reflection.options[:as]
|
|
132
|
+
@finder_sql =
|
|
133
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
|
134
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
|
135
|
+
else
|
|
136
|
+
# CPK
|
|
137
|
+
#@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
|
138
|
+
@finder_sql = full_columns_equals(@reflection.quoted_table_name, @reflection.cpk_primary_key, owner_quoted_id)
|
|
139
|
+
end
|
|
140
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class JoinDependency
|
|
145
|
+
class JoinBase
|
|
146
|
+
def column_names_with_alias
|
|
147
|
+
unless defined?(@column_names_with_alias)
|
|
148
|
+
@column_names_with_alias = []
|
|
149
|
+
keys = active_record.composite? ? primary_key.map(&:to_s) : [primary_key]
|
|
150
|
+
(keys + (column_names - keys)).each_with_index do |column_name, i|
|
|
151
|
+
@column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
@column_names_with_alias
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|