globe-composite_primary_keys 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/History.txt +203 -0
  2. data/Manifest.txt +121 -0
  3. data/README.txt +41 -0
  4. data/README_DB2.txt +33 -0
  5. data/Rakefile +30 -0
  6. data/composite_primary_keys.gemspec +17 -0
  7. data/lib/adapter_helper/base.rb +63 -0
  8. data/lib/adapter_helper/mysql.rb +13 -0
  9. data/lib/adapter_helper/oracle.rb +12 -0
  10. data/lib/adapter_helper/oracle_enhanced.rb +12 -0
  11. data/lib/adapter_helper/postgresql.rb +13 -0
  12. data/lib/adapter_helper/sqlite3.rb +13 -0
  13. data/lib/composite_primary_keys.rb +63 -0
  14. data/lib/composite_primary_keys/association_preload.rb +162 -0
  15. data/lib/composite_primary_keys/associations.rb +159 -0
  16. data/lib/composite_primary_keys/attribute_methods.rb +84 -0
  17. data/lib/composite_primary_keys/base.rb +200 -0
  18. data/lib/composite_primary_keys/composite_arrays.rb +29 -0
  19. data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +9 -0
  20. data/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb +21 -0
  21. data/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +15 -0
  22. data/lib/composite_primary_keys/connection_adapters/oracle_enhanced_adapter.rb +17 -0
  23. data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +53 -0
  24. data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +15 -0
  25. data/lib/composite_primary_keys/finder_methods.rb +68 -0
  26. data/lib/composite_primary_keys/fixtures.rb +8 -0
  27. data/lib/composite_primary_keys/read.rb +25 -0
  28. data/lib/composite_primary_keys/reflection.rb +39 -0
  29. data/lib/composite_primary_keys/relation.rb +31 -0
  30. data/lib/composite_primary_keys/through_association_scope.rb +212 -0
  31. data/lib/composite_primary_keys/validations/uniqueness.rb +118 -0
  32. data/lib/composite_primary_keys/version.rb +9 -0
  33. data/loader.rb +24 -0
  34. data/local/database_connections.rb.sample +12 -0
  35. data/local/paths.rb.sample +2 -0
  36. data/local/tasks.rb.sample +2 -0
  37. data/scripts/console.rb +48 -0
  38. data/scripts/txt2html +67 -0
  39. data/scripts/txt2js +59 -0
  40. data/tasks/activerecord_selection.rake +43 -0
  41. data/tasks/databases.rake +12 -0
  42. data/tasks/databases/mysql.rake +30 -0
  43. data/tasks/databases/oracle.rake +25 -0
  44. data/tasks/databases/postgresql.rake +25 -0
  45. data/tasks/databases/sqlite3.rake +28 -0
  46. data/tasks/deployment.rake +22 -0
  47. data/tasks/local_setup.rake +13 -0
  48. data/tasks/website.rake +18 -0
  49. data/test/README_tests.txt +67 -0
  50. data/test/abstract_unit.rb +103 -0
  51. data/test/connections/native_ibm_db/connection.rb +23 -0
  52. data/test/connections/native_mysql/connection.rb +13 -0
  53. data/test/connections/native_oracle/connection.rb +14 -0
  54. data/test/connections/native_oracle_enhanced/connection.rb +20 -0
  55. data/test/connections/native_postgresql/connection.rb +8 -0
  56. data/test/connections/native_sqlite/connection.rb +9 -0
  57. data/test/fixtures/article.rb +5 -0
  58. data/test/fixtures/article_group.rb +4 -0
  59. data/test/fixtures/article_groups.yml +7 -0
  60. data/test/fixtures/articles.yml +6 -0
  61. data/test/fixtures/comment.rb +6 -0
  62. data/test/fixtures/comments.yml +16 -0
  63. data/test/fixtures/db_definitions/db2-create-tables.sql +113 -0
  64. data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -0
  65. data/test/fixtures/db_definitions/mysql.sql +181 -0
  66. data/test/fixtures/db_definitions/oracle.drop.sql +39 -0
  67. data/test/fixtures/db_definitions/oracle.sql +188 -0
  68. data/test/fixtures/db_definitions/postgresql.sql +206 -0
  69. data/test/fixtures/db_definitions/sqlite.sql +166 -0
  70. data/test/fixtures/department.rb +5 -0
  71. data/test/fixtures/departments.yml +3 -0
  72. data/test/fixtures/dorm.rb +3 -0
  73. data/test/fixtures/dorms.yml +2 -0
  74. data/test/fixtures/employee.rb +4 -0
  75. data/test/fixtures/employees.yml +9 -0
  76. data/test/fixtures/group.rb +3 -0
  77. data/test/fixtures/groups.yml +3 -0
  78. data/test/fixtures/hack.rb +6 -0
  79. data/test/fixtures/hacks.yml +2 -0
  80. data/test/fixtures/kitchen_sink.rb +3 -0
  81. data/test/fixtures/kitchen_sinks.yml +5 -0
  82. data/test/fixtures/membership.rb +10 -0
  83. data/test/fixtures/membership_status.rb +3 -0
  84. data/test/fixtures/membership_statuses.yml +10 -0
  85. data/test/fixtures/memberships.yml +6 -0
  86. data/test/fixtures/product.rb +7 -0
  87. data/test/fixtures/product_tariff.rb +5 -0
  88. data/test/fixtures/product_tariffs.yml +12 -0
  89. data/test/fixtures/products.yml +6 -0
  90. data/test/fixtures/reading.rb +4 -0
  91. data/test/fixtures/readings.yml +10 -0
  92. data/test/fixtures/reference_code.rb +7 -0
  93. data/test/fixtures/reference_codes.yml +28 -0
  94. data/test/fixtures/reference_type.rb +7 -0
  95. data/test/fixtures/reference_types.yml +9 -0
  96. data/test/fixtures/restaurant.rb +6 -0
  97. data/test/fixtures/restaurants.yml +5 -0
  98. data/test/fixtures/restaurants_suburbs.yml +11 -0
  99. data/test/fixtures/room.rb +10 -0
  100. data/test/fixtures/room_assignment.rb +4 -0
  101. data/test/fixtures/room_assignments.yml +4 -0
  102. data/test/fixtures/room_attribute.rb +3 -0
  103. data/test/fixtures/room_attribute_assignment.rb +5 -0
  104. data/test/fixtures/room_attribute_assignments.yml +4 -0
  105. data/test/fixtures/room_attributes.yml +3 -0
  106. data/test/fixtures/rooms.yml +3 -0
  107. data/test/fixtures/seat.rb +5 -0
  108. data/test/fixtures/seats.yml +4 -0
  109. data/test/fixtures/street.rb +3 -0
  110. data/test/fixtures/streets.yml +15 -0
  111. data/test/fixtures/student.rb +4 -0
  112. data/test/fixtures/students.yml +2 -0
  113. data/test/fixtures/suburb.rb +6 -0
  114. data/test/fixtures/suburbs.yml +9 -0
  115. data/test/fixtures/tariff.rb +6 -0
  116. data/test/fixtures/tariffs.yml +13 -0
  117. data/test/fixtures/user.rb +10 -0
  118. data/test/fixtures/users.yml +6 -0
  119. data/test/hash_tricks.rb +34 -0
  120. data/test/plugins/pagination.rb +405 -0
  121. data/test/plugins/pagination_helper.rb +135 -0
  122. data/test/test_associations.rb +178 -0
  123. data/test/test_attribute_methods.rb +22 -0
  124. data/test/test_attributes.rb +80 -0
  125. data/test/test_clone.rb +34 -0
  126. data/test/test_composite_arrays.rb +32 -0
  127. data/test/test_create.rb +68 -0
  128. data/test/test_delete.rb +83 -0
  129. data/test/test_exists.rb +25 -0
  130. data/test/test_find.rb +73 -0
  131. data/test/test_ids.rb +90 -0
  132. data/test/test_miscellaneous.rb +39 -0
  133. data/test/test_pagination.rb +38 -0
  134. data/test/test_polymorphic.rb +32 -0
  135. data/test/test_santiago.rb +27 -0
  136. data/test/test_suite.rb +19 -0
  137. data/test/test_tutorial_example.rb +26 -0
  138. data/test/test_update.rb +40 -0
  139. data/test/test_validations.rb +11 -0
  140. data/website/index.html +195 -0
  141. data/website/index.txt +159 -0
  142. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  143. data/website/stylesheets/screen.css +126 -0
  144. data/website/template.js +3 -0
  145. data/website/template.rhtml +53 -0
  146. data/website/version-raw.js +3 -0
  147. data/website/version-raw.txt +2 -0
  148. data/website/version.js +4 -0
  149. data/website/version.txt +3 -0
  150. 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,8 @@
1
+ class Fixture #:nodoc:
2
+ def [](key)
3
+ if key.is_a? Array
4
+ return key.map { |a_key| self[a_key.to_s] }.to_composite_ids
5
+ end
6
+ @fixture[key]
7
+ end
8
+ 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