globe-composite_primary_keys 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,68 @@
|
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module ActiveRecord
|
3
|
+
module FinderMethods
|
4
|
+
module InstanceMethods
|
5
|
+
def exists?(id = nil)
|
6
|
+
case id
|
7
|
+
when Array
|
8
|
+
# CPK
|
9
|
+
if id.first.is_a?(String) and id.first.match(/\?/)
|
10
|
+
where(id).exists?
|
11
|
+
else
|
12
|
+
where(ids_predicate(id)).exists?
|
13
|
+
end
|
14
|
+
when Hash
|
15
|
+
where(id).exists?
|
16
|
+
else
|
17
|
+
relation = select(primary_key).limit(1)
|
18
|
+
# CPK
|
19
|
+
#relation = relation.where(primary_key.eq(id)) if id
|
20
|
+
relation = relation.where(ids_predicate(id)) if id
|
21
|
+
relation.first ? true : false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_with_ids(*ids, &block)
|
26
|
+
return to_a.find(&block) if block_given?
|
27
|
+
|
28
|
+
ids.pop if ids.last.nil?
|
29
|
+
|
30
|
+
# if ids is just a flat list, then its size must = primary_key.length (one id per primary key, in order)
|
31
|
+
# if ids is list of lists, then each inner list must follow rule above
|
32
|
+
if ids.first.is_a? String
|
33
|
+
# find '2,1' -> ids = ['2,1']
|
34
|
+
# find '2,1;7,3' -> ids = ['2,1;7,3']
|
35
|
+
match = ids.first.match(/^\[(.*)\]$/)
|
36
|
+
ids = (match ? match[1] : ids.first).split(ID_SET_SEP).map {|id_set| id_set.split(CompositePrimaryKeys::ID_SEP).to_composite_ids}
|
37
|
+
# find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds
|
38
|
+
end
|
39
|
+
|
40
|
+
ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array)
|
41
|
+
ids.each do |id_set|
|
42
|
+
unless id_set.is_a?(Array)
|
43
|
+
raise "Ids must be in an Array, instead received: #{id_set.inspect}"
|
44
|
+
end
|
45
|
+
unless id_set.length == @klass.primary_keys.length
|
46
|
+
raise "#{id_set.inspect}: Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
new_relation = clone
|
51
|
+
ids.each do |id_set|
|
52
|
+
[@klass.primary_keys, id_set].transpose.map do |key, id|
|
53
|
+
new_relation = new_relation.where(key => id)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
result = new_relation.to_a
|
58
|
+
|
59
|
+
if result.size == ids.size
|
60
|
+
ids.size == 1 ? result[0] : result
|
61
|
+
else
|
62
|
+
raise ::ActiveRecord::RecordNotFound, "Couldn't find all #{@klass.name} with IDs (#{ids.inspect})"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module AttributeMethods
|
3
|
+
module Read
|
4
|
+
def read_attribute(attr_name)
|
5
|
+
attr_name = attr_name.to_s
|
6
|
+
# CPK
|
7
|
+
# attr_name = self.class.primary_key if attr_name == 'id'
|
8
|
+
attr_name = self.class.primary_key if (attr_name == 'id' and !self.composite?)
|
9
|
+
if !(value = @attributes[attr_name]).nil?
|
10
|
+
if column = column_for_attribute(attr_name)
|
11
|
+
if unserializable_attribute?(attr_name, column)
|
12
|
+
unserialize_attribute(attr_name)
|
13
|
+
else
|
14
|
+
column.type_cast(value)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
value
|
18
|
+
end
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Reflection
|
3
|
+
class AssociationReflection
|
4
|
+
def derive_primary_key
|
5
|
+
result = if options[:foreign_key]
|
6
|
+
options[:foreign_key]
|
7
|
+
elsif belongs_to?
|
8
|
+
#CPK
|
9
|
+
#"#{name}_id"
|
10
|
+
class_name.foreign_key
|
11
|
+
elsif options[:as]
|
12
|
+
options[:as]
|
13
|
+
else
|
14
|
+
active_record.name.foreign_key
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def cpk_primary_key
|
19
|
+
# Make sure the returned key(s) are an array
|
20
|
+
@cpk_primary_key ||= [derive_primary_key].flatten
|
21
|
+
end
|
22
|
+
|
23
|
+
def primary_key_name
|
24
|
+
@primary_key_name ||= derive_primary_key_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def derive_primary_key_name
|
28
|
+
result = derive_primary_key
|
29
|
+
|
30
|
+
# CPK
|
31
|
+
if result.is_a?(Array)
|
32
|
+
result.to_composite_keys.to_s
|
33
|
+
else
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module ActiveRecord
|
3
|
+
module Relation
|
4
|
+
module InstanceMethods
|
5
|
+
def ids_predicate(id)
|
6
|
+
predicate = nil
|
7
|
+
self.primary_key.zip(id).each do |key, value|
|
8
|
+
eq = key.eq(value)
|
9
|
+
predicate = predicate ? predicate.and(eq) : eq
|
10
|
+
end
|
11
|
+
predicate
|
12
|
+
end
|
13
|
+
|
14
|
+
def delete(id_or_array)
|
15
|
+
# CPK
|
16
|
+
# where(@klass.primary_key => id_or_array).delete_all
|
17
|
+
where(ids_predicate(id_or_array)).delete_all
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy(id)
|
21
|
+
# CPK
|
22
|
+
#if id.is_a?(Array)
|
23
|
+
# id.map { |one_id| destroy(one_id) }
|
24
|
+
#else
|
25
|
+
find(id).destroy
|
26
|
+
#end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
module ThroughAssociationScope
|
4
|
+
def composite_join_clause(table1, keys1, table2, keys2)
|
5
|
+
predicates = composite_join_predicates(table1, keys1, table2, keys2)
|
6
|
+
|
7
|
+
join_clause = predicates.map do |predicate|
|
8
|
+
predicate.to_sql
|
9
|
+
end.join(" AND ")
|
10
|
+
|
11
|
+
"(#{join_clause})"
|
12
|
+
end
|
13
|
+
|
14
|
+
def composite_join_predicates(table1, keys1, table2, keys2)
|
15
|
+
attributes1 = [keys1].flatten.map do |key|
|
16
|
+
table1[key]
|
17
|
+
end
|
18
|
+
|
19
|
+
attributes2 = [keys2].flatten.map do |key|
|
20
|
+
table2[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
[attributes1, attributes2].transpose.map do |attribute1, attribute2|
|
24
|
+
attribute1.eq(attribute2)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def composite_ids_hash(keys, ids)
|
29
|
+
[keys].flatten.zip([ids].flatten).inject(Hash.new) do |hash, (key, value)|
|
30
|
+
hash[key] = value
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def construct_quoted_owner_attributes(reflection)
|
36
|
+
if as = reflection.options[:as]
|
37
|
+
{ "#{as}_id" => owner_quoted_id,
|
38
|
+
"#{as}_type" => reflection.klass.quote_value(
|
39
|
+
@owner.class.base_class.name.to_s,
|
40
|
+
reflection.klass.columns_hash["#{as}_type"]) }
|
41
|
+
elsif reflection.macro == :belongs_to
|
42
|
+
# CPK
|
43
|
+
# { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
|
44
|
+
composite_ids_hash(reflection.klass.primary_key, @owner.quoted_id)
|
45
|
+
else
|
46
|
+
# CPK
|
47
|
+
#{ reflection.primary_key_name => owner_quoted_id }
|
48
|
+
composite_ids_hash(reflection.cpk_primary_key, @owner.quoted_id)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Construct attributes for associate pointing to owner.
|
53
|
+
def construct_owner_attributes(reflection)
|
54
|
+
if as = reflection.options[:as]
|
55
|
+
{ "#{as}_id" => @owner.id,
|
56
|
+
"#{as}_type" => @owner.class.base_class.name.to_s }
|
57
|
+
else
|
58
|
+
# CPK
|
59
|
+
# { reflection.primary_key_name => @owner.id }
|
60
|
+
composite_ids_hash(reflection.cpk_primary_key, @owner.id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def construct_joins(custom_joins = nil)
|
65
|
+
polymorphic_join = nil
|
66
|
+
if @reflection.source_reflection.macro == :belongs_to
|
67
|
+
reflection_primary_key = @reflection.klass.primary_key
|
68
|
+
source_primary_key = @reflection.source_reflection.cpk_primary_key
|
69
|
+
if @reflection.options[:source_type]
|
70
|
+
polymorphic_join = "AND %s.%s = %s" % [
|
71
|
+
@reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
|
72
|
+
@owner.class.quote_value(@reflection.options[:source_type])
|
73
|
+
]
|
74
|
+
end
|
75
|
+
else
|
76
|
+
reflection_primary_key = @reflection.source_reflection.cpk_primary_key
|
77
|
+
source_primary_key = @reflection.through_reflection.klass.primary_key
|
78
|
+
if @reflection.source_reflection.options[:as]
|
79
|
+
polymorphic_join = "AND %s.%s = %s" % [
|
80
|
+
@reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
|
81
|
+
@owner.class.quote_value(@reflection.through_reflection.klass.name)
|
82
|
+
]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# CPK
|
87
|
+
# "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
|
88
|
+
# @reflection.through_reflection.quoted_table_name,
|
89
|
+
# @reflection.quoted_table_name, reflection_primary_key,
|
90
|
+
# @reflection.through_reflection.quoted_table_name, source_primary_key,
|
91
|
+
# polymorphic_join
|
92
|
+
# ]
|
93
|
+
|
94
|
+
"INNER JOIN %s ON %s %s #{@reflection.options[:joins]} #{custom_joins}" % [
|
95
|
+
@reflection.through_reflection.quoted_table_name,
|
96
|
+
composite_join_clause(@reflection.klass.arel_table, reflection_primary_key,
|
97
|
+
@reflection.through_reflection.klass.arel_table, source_primary_key),
|
98
|
+
polymorphic_join
|
99
|
+
]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module ClassMethods
|
104
|
+
class JoinAssociation
|
105
|
+
# Ugly to include this twice, but I couldn't figure out how to make this
|
106
|
+
# work via a module
|
107
|
+
def composite_join_predicates(table1, keys1, table2, keys2)
|
108
|
+
attributes1 = [keys1].flatten.map do |key|
|
109
|
+
table1[key]
|
110
|
+
end
|
111
|
+
|
112
|
+
attributes2 = [keys2].flatten.map do |key|
|
113
|
+
table2[key]
|
114
|
+
end
|
115
|
+
|
116
|
+
[attributes1, attributes2].transpose.map do |attribute1, attribute2|
|
117
|
+
attribute1.eq(attribute2)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def association_join
|
122
|
+
return @join if @join
|
123
|
+
|
124
|
+
aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
|
125
|
+
parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
|
126
|
+
|
127
|
+
@join = case reflection.macro
|
128
|
+
when :has_and_belongs_to_many
|
129
|
+
join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
|
130
|
+
fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
|
131
|
+
klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
|
132
|
+
|
133
|
+
[
|
134
|
+
join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
|
135
|
+
aliased_table[klass.primary_key].eq(join_table[klass_fk])
|
136
|
+
]
|
137
|
+
when :has_many, :has_one
|
138
|
+
if reflection.options[:through]
|
139
|
+
join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
|
140
|
+
jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
|
141
|
+
first_key = second_key = as_extra = nil
|
142
|
+
|
143
|
+
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
144
|
+
jt_foreign_key = through_reflection.options[:as].to_s + '_id'
|
145
|
+
jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
|
146
|
+
else
|
147
|
+
jt_foreign_key = through_reflection.primary_key_name
|
148
|
+
end
|
149
|
+
|
150
|
+
case source_reflection.macro
|
151
|
+
when :has_many
|
152
|
+
if source_reflection.options[:as]
|
153
|
+
first_key = "#{source_reflection.options[:as]}_id"
|
154
|
+
second_key = options[:foreign_key] || primary_key
|
155
|
+
as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
|
156
|
+
else
|
157
|
+
first_key = through_reflection.klass.base_class.to_s.foreign_key
|
158
|
+
second_key = options[:foreign_key] || primary_key
|
159
|
+
end
|
160
|
+
|
161
|
+
unless through_reflection.klass.descends_from_active_record?
|
162
|
+
jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
|
163
|
+
end
|
164
|
+
when :belongs_to
|
165
|
+
first_key = primary_key
|
166
|
+
if reflection.options[:source_type]
|
167
|
+
second_key = source_reflection.association_foreign_key
|
168
|
+
jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
|
169
|
+
else
|
170
|
+
second_key = source_reflection.primary_key_name
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
[
|
175
|
+
[parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
|
176
|
+
aliased_table[first_key].eq(join_table[second_key])
|
177
|
+
]
|
178
|
+
elsif reflection.options[:as]
|
179
|
+
id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
|
180
|
+
type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
|
181
|
+
[id_rel, type_rel]
|
182
|
+
else
|
183
|
+
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
|
184
|
+
# CPK
|
185
|
+
# [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
|
186
|
+
composite_join_predicates(aliased_table, foreign_key,
|
187
|
+
parent_table, reflection.options[:primary_key] || parent.primary_key)
|
188
|
+
end
|
189
|
+
when :belongs_to
|
190
|
+
[aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.cpk_primary_key])]
|
191
|
+
end
|
192
|
+
|
193
|
+
unless klass.descends_from_active_record?
|
194
|
+
sti_column = aliased_table[klass.inheritance_column]
|
195
|
+
sti_condition = sti_column.eq(klass.sti_name)
|
196
|
+
klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
|
197
|
+
|
198
|
+
@join << sti_condition
|
199
|
+
end
|
200
|
+
|
201
|
+
[through_reflection, reflection].each do |ref|
|
202
|
+
if ref && ref.options[:conditions]
|
203
|
+
@join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
@join
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Validations
|
3
|
+
class UniquenessValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
finder_class = find_finder_class_for(record)
|
6
|
+
table = finder_class.unscoped
|
7
|
+
|
8
|
+
table_name = record.class.quoted_table_name
|
9
|
+
sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
|
10
|
+
|
11
|
+
relation = table.where(sql, *params)
|
12
|
+
|
13
|
+
Array.wrap(options[:scope]).each do |scope_item|
|
14
|
+
scope_value = record.send(scope_item)
|
15
|
+
relation = relation.where(scope_item => scope_value)
|
16
|
+
end
|
17
|
+
|
18
|
+
unless record.new_record?
|
19
|
+
# CPK
|
20
|
+
# TODO : This should be in Arel
|
21
|
+
#relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
|
22
|
+
if record.composite?
|
23
|
+
predicate = nil
|
24
|
+
record.ids_hash.each do |key, value|
|
25
|
+
neq = relation.table[key].not_eq(value)
|
26
|
+
predicate = predicate ? predicate.and(neq) : neq
|
27
|
+
end
|
28
|
+
relation = relation.where(predicate)
|
29
|
+
else
|
30
|
+
relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if relation.first
|
35
|
+
record.errors.add(attribute, :taken, :default => options[:message], :value => value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#module CompositePrimaryKeys
|
43
|
+
# module ActiveRecord
|
44
|
+
# module Validations
|
45
|
+
# module Uniqueness
|
46
|
+
# module ClassMethods
|
47
|
+
# def validates_uniqueness_of(*attr_names)
|
48
|
+
# configuration = { :case_sensitive => true }
|
49
|
+
# configuration.update(attr_names.extract_options!)
|
50
|
+
#
|
51
|
+
# validates_each(attr_names,configuration) do |record, attr_name, value|
|
52
|
+
# # The check for an existing value should be run from a class that
|
53
|
+
# # isn't abstract. This means working down from the current class
|
54
|
+
# # (self), to the first non-abstract class. Since classes don't know
|
55
|
+
# # their subclasses, we have to build the hierarchy between self and
|
56
|
+
# # the record's class.
|
57
|
+
# class_hierarchy = [record.class]
|
58
|
+
# while class_hierarchy.first != self
|
59
|
+
# class_hierarchy.insert(0, class_hierarchy.first.superclass)
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # Now we can work our way down the tree to the first non-abstract
|
63
|
+
# # class (which has a database table to query from).
|
64
|
+
# finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
|
65
|
+
#
|
66
|
+
# column = finder_class.columns_hash[attr_name.to_s]
|
67
|
+
#
|
68
|
+
# if value.nil?
|
69
|
+
# comparison_operator = "IS ?"
|
70
|
+
# elsif column.text?
|
71
|
+
# comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
72
|
+
# value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
|
73
|
+
# else
|
74
|
+
# comparison_operator = "= ?"
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
|
78
|
+
#
|
79
|
+
# if value.nil? || (configuration[:case_sensitive] || !column.text?)
|
80
|
+
# condition_sql = "#{sql_attribute} #{comparison_operator}"
|
81
|
+
# condition_params = [value]
|
82
|
+
# else
|
83
|
+
# condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
|
84
|
+
# condition_params = [value.mb_chars.downcase]
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# if scope = configuration[:scope]
|
88
|
+
# Array(scope).map do |scope_item|
|
89
|
+
# scope_value = record.send(scope_item)
|
90
|
+
# condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
|
91
|
+
# condition_params << scope_value
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# unless record.new_record?
|
96
|
+
# if record.class.composite?
|
97
|
+
# record.class.primary_keys.each do |key|
|
98
|
+
# condition_sql << " AND #{record.class.quoted_table_name}.#{key} <> ?"
|
99
|
+
# condition_params << record.send(key)
|
100
|
+
# end
|
101
|
+
# else
|
102
|
+
# condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
|
103
|
+
# condition_params << record.send(:id)
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# finder_class.with_exclusive_scope do
|
108
|
+
# if finder_class.exists?([condition_sql, *condition_params])
|
109
|
+
# record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
#end
|