composite_primary_keys 2.3.5.1 → 3.0.0.b2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +26 -0
- data/README.txt +1 -1
- data/Rakefile +41 -51
- data/lib/composite_primary_keys.rb +19 -7
- data/lib/composite_primary_keys/association_preload.rb +75 -170
- data/lib/composite_primary_keys/associations.rb +98 -400
- data/lib/composite_primary_keys/associations/association_proxy.rb +32 -0
- data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +30 -0
- data/lib/composite_primary_keys/associations/has_many_association.rb +72 -0
- data/lib/composite_primary_keys/associations/has_one_association.rb +19 -0
- data/lib/composite_primary_keys/associations/through_association_scope.rb +103 -0
- data/lib/composite_primary_keys/base.rb +148 -305
- data/lib/composite_primary_keys/calculations.rb +22 -64
- data/lib/composite_primary_keys/composite_arrays.rb +3 -10
- data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +9 -0
- data/lib/composite_primary_keys/connection_adapters/oracle_enhanced_adapter.rb +17 -0
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +1 -1
- data/lib/composite_primary_keys/finder_methods.rb +71 -0
- data/lib/composite_primary_keys/fixtures.rb +1 -1
- data/lib/composite_primary_keys/read.rb +25 -0
- data/lib/composite_primary_keys/reflection.rb +30 -10
- data/lib/composite_primary_keys/relation.rb +31 -0
- data/lib/composite_primary_keys/validations/uniqueness.rb +106 -66
- data/lib/composite_primary_keys/version.rb +4 -4
- data/scripts/console.rb +1 -1
- data/tasks/Rakefile.rb +13 -0
- data/tasks/databases/mysql.rake +11 -15
- data/tasks/databases/oracle.rake +10 -11
- data/tasks/databases/postgresql.rake +10 -13
- data/tasks/databases/sqlite3.rake +9 -9
- data/test/README_tests.txt +1 -45
- data/test/abstract_unit.rb +17 -14
- data/test/connections/connection_spec.rb +19 -0
- data/test/connections/databases.example.yml +11 -0
- data/test/connections/databases.yml +13 -0
- data/test/connections/native_mysql/connection.rb +10 -2
- data/test/connections/native_oracle/connection.rb +7 -4
- data/test/connections/native_oracle_enhanced/connection.rb +23 -0
- data/test/connections/native_postgresql/connection.rb +13 -5
- data/test/connections/native_sqlite/connection.rb +7 -3
- data/test/fixtures/article_group.rb +4 -0
- data/test/fixtures/article_groups.yml +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +2 -1
- data/test/fixtures/debug.log +133 -0
- data/test/fixtures/dorm.rb +3 -0
- data/test/fixtures/dorms.yml +2 -0
- data/test/fixtures/kitchen_sink.rb +3 -0
- data/test/fixtures/kitchen_sinks.yml +5 -0
- data/test/fixtures/reference_codes.yml +2 -0
- data/test/fixtures/reference_type.rb +1 -1
- 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/student.rb +4 -0
- data/test/fixtures/students.yml +2 -0
- data/test/test_associations.rb +27 -50
- data/test/test_attributes.rb +15 -19
- data/test/test_composite_arrays.rb +2 -21
- data/test/test_create.rb +3 -3
- data/test/test_delete.rb +7 -20
- data/test/test_exists.rb +3 -7
- data/test/test_find.rb +0 -8
- data/test/test_ids.rb +3 -17
- data/test/test_polymorphic.rb +5 -4
- data/test/test_suite.rb +19 -0
- data/test/{test_tutorial_examle.rb → test_tutorial_example.rb} +0 -0
- metadata +110 -72
- data/Manifest.txt +0 -123
- data/lib/adapter_helper/base.rb +0 -63
- data/lib/adapter_helper/mysql.rb +0 -13
- data/lib/adapter_helper/oracle.rb +0 -12
- data/lib/adapter_helper/postgresql.rb +0 -13
- data/lib/adapter_helper/sqlite3.rb +0 -13
- data/lib/composite_primary_keys/migration.rb +0 -20
- data/local/database_connections.rb.sample +0 -12
- data/local/paths.rb.sample +0 -2
- data/local/tasks.rb.sample +0 -2
- data/tasks/activerecord_selection.rake +0 -43
- data/tasks/databases.rake +0 -12
- data/tasks/deployment.rake +0 -22
- data/tasks/local_setup.rake +0 -13
- data/test/test_dummy.rb +0 -28
- data/tmp/test.db +0 -0
- data/website/index.html +0 -195
- data/website/index.txt +0 -159
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -126
- data/website/template.js +0 -3
- data/website/template.rhtml +0 -53
- data/website/version-raw.js +0 -3
- data/website/version-raw.txt +0 -2
- data/website/version.js +0 -4
- data/website/version.txt +0 -3
@@ -1,68 +1,26 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
if merged_includes.any? && operation.to_s.downcase == 'count'
|
21
|
-
options[:distinct] = true
|
22
|
-
use_workaround = !connection.supports_count_distinct?
|
23
|
-
column_name = options[:select] || primary_key.map{ |part| "#{quoted_table_name}.#{connection.quote_column_name(part)}"}.join(',')
|
24
|
-
end
|
25
|
-
|
26
|
-
sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}"
|
27
|
-
|
28
|
-
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
29
|
-
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
|
30
|
-
|
31
|
-
sql << ", #{connection.quote_column_name(options[:group_field])} AS #{options[:group_alias]}" if options[:group]
|
32
|
-
sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround
|
33
|
-
sql << " FROM #{quoted_table_name} "
|
34
|
-
if merged_includes.any?
|
35
|
-
join_dependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
|
36
|
-
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
37
|
-
end
|
38
|
-
|
39
|
-
add_joins!(sql, options[:joins], scope)
|
40
|
-
add_conditions!(sql, options[:conditions], scope)
|
41
|
-
add_limited_ids_condition!(sql, options, join_dependency) if \
|
42
|
-
join_dependency &&
|
43
|
-
!using_limitable_reflections?(join_dependency.reflections) &&
|
44
|
-
((scope && scope[:limit]) || options[:limit])
|
45
|
-
|
46
|
-
if options[:group]
|
47
|
-
group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
|
48
|
-
sql << " GROUP BY #{connection.quote_column_name(options[group_key])} "
|
49
|
-
end
|
50
|
-
|
51
|
-
if options[:group] && options[:having]
|
52
|
-
# FrontBase requires identifiers in the HAVING clause and chokes on function calls
|
53
|
-
if connection.adapter_name == 'FrontBase'
|
54
|
-
options[:having].downcase!
|
55
|
-
options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
|
56
|
-
end
|
57
|
-
|
58
|
-
sql << " HAVING #{options[:having]} "
|
59
|
-
end
|
60
|
-
|
61
|
-
sql << " ORDER BY #{options[:order]} " if options[:order]
|
62
|
-
add_limit!(sql, options, scope)
|
63
|
-
sql << ') w1' if use_workaround # assign a dummy table name as required for postgresql
|
64
|
-
sql
|
1
|
+
module ActiveRecord
|
2
|
+
module Calculations
|
3
|
+
def execute_simple_calculation(operation, column_name, distinct)
|
4
|
+
# CPK changes
|
5
|
+
if column_name.kind_of?(Array)
|
6
|
+
columns = column_name.map do |primary_key_column|
|
7
|
+
table[primary_key_column].to_sql
|
8
|
+
end
|
9
|
+
projection = "DISTINCT #{columns.join(',')}"
|
10
|
+
subquery = "(#{table.project(projection).to_sql}) AS subquery"
|
11
|
+
relation = Arel::Table.new(subquery).project(Arel::SqlLiteral.new('*').count)
|
12
|
+
type_cast_calculated_value(@klass.connection.select_value(relation.to_sql),
|
13
|
+
column_for(column_name.first), operation)
|
14
|
+
else
|
15
|
+
column = if @klass.column_names.include?(column_name.to_s)
|
16
|
+
Arel::Attribute.new(@klass.unscoped, column_name)
|
17
|
+
else
|
18
|
+
Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
|
65
19
|
end
|
20
|
+
|
21
|
+
# Postgresql doesn't like ORDER BY when there are no GROUP BY
|
22
|
+
relation = except(:order).select(operation == 'count' ? column.count(distinct) : column.send(operation))
|
23
|
+
type_cast_calculated_value(@klass.connection.select_value(relation.to_sql), column_for(column_name), operation)
|
66
24
|
end
|
67
25
|
end
|
68
26
|
end
|
@@ -8,23 +8,16 @@ module CompositePrimaryKeys
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def to_composite_ids
|
11
|
-
|
11
|
+
Array.new(self)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
class
|
15
|
+
class CompositeKeys < Array
|
16
16
|
def to_s
|
17
|
+
# Doing this makes it easier to parse Base#[](attr_name)
|
17
18
|
join(ID_SEP)
|
18
19
|
end
|
19
20
|
end
|
20
|
-
|
21
|
-
class CompositeKeys < CompositeArray
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
class CompositeIds < CompositeArray
|
26
|
-
|
27
|
-
end
|
28
21
|
end
|
29
22
|
|
30
23
|
Array.send(:include, CompositePrimaryKeys::ArrayExtension)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Added to OracleEnhancedAdapter version 1.1.4
|
2
|
+
#
|
3
|
+
# module ActiveRecord
|
4
|
+
# module ConnectionAdapters
|
5
|
+
# class OracleEnhancedAdapter < AbstractAdapter
|
6
|
+
#
|
7
|
+
# # This mightn't be in Core, but count(distinct x,y) doesn't work for me
|
8
|
+
# def supports_count_distinct? #:nodoc:
|
9
|
+
# false
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# def concat(*columns)
|
13
|
+
# "(#{columns.join('||')})"
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# end
|
@@ -22,7 +22,7 @@ module ActiveRecord
|
|
22
22
|
pk, sequence_name = *pk_and_sequence_for(table) unless pk
|
23
23
|
if pk
|
24
24
|
quoted_pk = if pk.is_a?(Array)
|
25
|
-
pk.map { |col| quote_column_name(col) }.join(ID_SEP)
|
25
|
+
pk.map { |col| quote_column_name(col) }.join(CompositePrimaryKeys::ID_SEP)
|
26
26
|
else
|
27
27
|
quote_column_name(pk)
|
28
28
|
end
|
@@ -0,0 +1,71 @@
|
|
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
|
+
# CPK
|
18
|
+
#relation = select(primary_key).limit(1)
|
19
|
+
#relation = relation.where(primary_key.eq(id)) if id
|
20
|
+
|
21
|
+
relation = select(primary_keys).limit(1)
|
22
|
+
relation = relation.where(ids_predicate(id)) if id
|
23
|
+
relation.first ? true : false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_with_ids(*ids, &block)
|
28
|
+
return to_a.find(&block) if block_given?
|
29
|
+
|
30
|
+
ids = ids.first if ids.last == nil
|
31
|
+
|
32
|
+
# if ids is just a flat list, then its size must = primary_key.length (one id per primary key, in order)
|
33
|
+
# if ids is list of lists, then each inner list must follow rule above
|
34
|
+
if ids.first.is_a? String
|
35
|
+
# find '2,1' -> ids = ['2,1']
|
36
|
+
# find '2,1;7,3' -> ids = ['2,1;7,3']
|
37
|
+
match = ids.first.match(/^\[(.*)\]$/)
|
38
|
+
ids = (match ? match[1] : ids.first).split(ID_SET_SEP).map {|id_set| id_set.split(CompositePrimaryKeys::ID_SEP).to_composite_ids}
|
39
|
+
# find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds
|
40
|
+
end
|
41
|
+
|
42
|
+
ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array)
|
43
|
+
|
44
|
+
ids.each do |id_set|
|
45
|
+
unless id_set.is_a?(Array)
|
46
|
+
raise "Ids must be in an Array, instead received: #{id_set.inspect}"
|
47
|
+
end
|
48
|
+
unless id_set.length == @klass.primary_keys.length
|
49
|
+
raise "#{id_set.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
new_relation = clone
|
54
|
+
ids.each do |id_set|
|
55
|
+
[@klass.primary_keys, id_set].transpose.map do |key, id|
|
56
|
+
new_relation = new_relation.where(key => id)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
result = new_relation.to_a
|
61
|
+
|
62
|
+
if result.size == ids.size
|
63
|
+
ids.size == 1 ? result[0] : result
|
64
|
+
else
|
65
|
+
raise ::ActiveRecord::RecordNotFound, "Couldn't find all #{@klass.name} with IDs (#{ids.inspect})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
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
|
@@ -1,18 +1,38 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Reflection
|
3
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
|
+
|
4
23
|
def primary_key_name
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
13
35
|
end
|
14
|
-
@primary_key_name = @primary_key_name.to_composite_keys.to_s if @primary_key_name.is_a? Array
|
15
|
-
@primary_key_name
|
16
36
|
end
|
17
37
|
end
|
18
38
|
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_keys.zip(id).each do |key, value|
|
8
|
+
eq = table[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
|
@@ -1,77 +1,117 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
configuration = { :case_sensitive => true }
|
8
|
-
configuration.update(attr_names.extract_options!)
|
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
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
# isn't abstract. This means working down from the current class
|
13
|
-
# (self), to the first non-abstract class. Since classes don't know
|
14
|
-
# their subclasses, we have to build the hierarchy between self and
|
15
|
-
# the record's class.
|
16
|
-
class_hierarchy = [record.class]
|
17
|
-
while class_hierarchy.first != self
|
18
|
-
class_hierarchy.insert(0, class_hierarchy.first.superclass)
|
19
|
-
end
|
8
|
+
table_name = record.class.quoted_table_name
|
9
|
+
sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
|
20
10
|
|
21
|
-
|
22
|
-
# class (which has a database table to query from).
|
23
|
-
finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
|
11
|
+
relation = table.where(sql, *params)
|
24
12
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
elsif column.text?
|
30
|
-
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
31
|
-
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
|
32
|
-
else
|
33
|
-
comparison_operator = "= ?"
|
34
|
-
end
|
35
|
-
|
36
|
-
sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
|
37
|
-
|
38
|
-
if value.nil? || (configuration[:case_sensitive] || !column.text?)
|
39
|
-
condition_sql = "#{sql_attribute} #{comparison_operator}"
|
40
|
-
condition_params = [value]
|
41
|
-
else
|
42
|
-
condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
|
43
|
-
condition_params = [value.mb_chars.downcase]
|
44
|
-
end
|
45
|
-
|
46
|
-
if scope = configuration[:scope]
|
47
|
-
Array(scope).map do |scope_item|
|
48
|
-
scope_value = record.send(scope_item)
|
49
|
-
condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
|
50
|
-
condition_params << scope_value
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
unless record.new_record?
|
55
|
-
if record.class.composite?
|
56
|
-
record.class.primary_keys.each do |key|
|
57
|
-
condition_sql << " AND #{record.class.quoted_table_name}.#{key} <> ?"
|
58
|
-
condition_params << record.send(key)
|
59
|
-
end
|
60
|
-
else
|
61
|
-
condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
|
62
|
-
condition_params << record.send(:id)
|
63
|
-
end
|
64
|
-
end
|
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
|
65
17
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
18
|
+
unless record.new_record?
|
19
|
+
# CPK
|
20
|
+
if record.composite?
|
21
|
+
predicate = nil
|
22
|
+
record.ids_hash.each do |key, value|
|
23
|
+
neq = relation.table[key].not_eq(value)
|
24
|
+
predicate = predicate ? predicate.and(neq) : neq
|
71
25
|
end
|
26
|
+
relation = relation.where(predicate)
|
27
|
+
else
|
28
|
+
# TODO : This should be in Arel
|
29
|
+
relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
|
72
30
|
end
|
73
31
|
end
|
32
|
+
|
33
|
+
if relation.exists?
|
34
|
+
record.errors.add(attribute, :taken, :default => options[:message], :value => value)
|
35
|
+
end
|
74
36
|
end
|
75
37
|
end
|
76
38
|
end
|
77
39
|
end
|
40
|
+
|
41
|
+
#module CompositePrimaryKeys
|
42
|
+
# module ActiveRecord
|
43
|
+
# module Validations
|
44
|
+
# module Uniqueness
|
45
|
+
# module ClassMethods
|
46
|
+
# def validates_uniqueness_of(*attr_names)
|
47
|
+
# configuration = { :case_sensitive => true }
|
48
|
+
# configuration.update(attr_names.extract_options!)
|
49
|
+
#
|
50
|
+
# validates_each(attr_names,configuration) do |record, attr_name, value|
|
51
|
+
# # The check for an existing value should be run from a class that
|
52
|
+
# # isn't abstract. This means working down from the current class
|
53
|
+
# # (self), to the first non-abstract class. Since classes don't know
|
54
|
+
# # their subclasses, we have to build the hierarchy between self and
|
55
|
+
# # the record's class.
|
56
|
+
# class_hierarchy = [record.class]
|
57
|
+
# while class_hierarchy.first != self
|
58
|
+
# class_hierarchy.insert(0, class_hierarchy.first.superclass)
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# # Now we can work our way down the tree to the first non-abstract
|
62
|
+
# # class (which has a database table to query from).
|
63
|
+
# finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
|
64
|
+
#
|
65
|
+
# column = finder_class.columns_hash[attr_name.to_s]
|
66
|
+
#
|
67
|
+
# if value.nil?
|
68
|
+
# comparison_operator = "IS ?"
|
69
|
+
# elsif column.text?
|
70
|
+
# comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
71
|
+
# value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
|
72
|
+
# else
|
73
|
+
# comparison_operator = "= ?"
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
|
77
|
+
#
|
78
|
+
# if value.nil? || (configuration[:case_sensitive] || !column.text?)
|
79
|
+
# condition_sql = "#{sql_attribute} #{comparison_operator}"
|
80
|
+
# condition_params = [value]
|
81
|
+
# else
|
82
|
+
# condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
|
83
|
+
# condition_params = [value.mb_chars.downcase]
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# if scope = configuration[:scope]
|
87
|
+
# Array(scope).map do |scope_item|
|
88
|
+
# scope_value = record.send(scope_item)
|
89
|
+
# condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value)
|
90
|
+
# condition_params << scope_value
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# unless record.new_record?
|
95
|
+
# if record.class.composite?
|
96
|
+
# record.class.primary_keys.each do |key|
|
97
|
+
# condition_sql << " AND #{record.class.quoted_table_name}.#{key} <> ?"
|
98
|
+
# condition_params << record.send(key)
|
99
|
+
# end
|
100
|
+
# else
|
101
|
+
# condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
|
102
|
+
# condition_params << record.send(:id)
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# finder_class.with_exclusive_scope do
|
107
|
+
# if finder_class.exists?([condition_sql, *condition_params])
|
108
|
+
# record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
#end
|