composite_primary_keys 2.3.5.1 → 3.0.0.b2
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 +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
|