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.
Files changed (103) hide show
  1. data/History.txt +26 -0
  2. data/README.txt +1 -1
  3. data/Rakefile +41 -51
  4. data/lib/composite_primary_keys.rb +19 -7
  5. data/lib/composite_primary_keys/association_preload.rb +75 -170
  6. data/lib/composite_primary_keys/associations.rb +98 -400
  7. data/lib/composite_primary_keys/associations/association_proxy.rb +32 -0
  8. data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +30 -0
  9. data/lib/composite_primary_keys/associations/has_many_association.rb +72 -0
  10. data/lib/composite_primary_keys/associations/has_one_association.rb +19 -0
  11. data/lib/composite_primary_keys/associations/through_association_scope.rb +103 -0
  12. data/lib/composite_primary_keys/base.rb +148 -305
  13. data/lib/composite_primary_keys/calculations.rb +22 -64
  14. data/lib/composite_primary_keys/composite_arrays.rb +3 -10
  15. data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +9 -0
  16. data/lib/composite_primary_keys/connection_adapters/oracle_enhanced_adapter.rb +17 -0
  17. data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +1 -1
  18. data/lib/composite_primary_keys/finder_methods.rb +71 -0
  19. data/lib/composite_primary_keys/fixtures.rb +1 -1
  20. data/lib/composite_primary_keys/read.rb +25 -0
  21. data/lib/composite_primary_keys/reflection.rb +30 -10
  22. data/lib/composite_primary_keys/relation.rb +31 -0
  23. data/lib/composite_primary_keys/validations/uniqueness.rb +106 -66
  24. data/lib/composite_primary_keys/version.rb +4 -4
  25. data/scripts/console.rb +1 -1
  26. data/tasks/Rakefile.rb +13 -0
  27. data/tasks/databases/mysql.rake +11 -15
  28. data/tasks/databases/oracle.rake +10 -11
  29. data/tasks/databases/postgresql.rake +10 -13
  30. data/tasks/databases/sqlite3.rake +9 -9
  31. data/test/README_tests.txt +1 -45
  32. data/test/abstract_unit.rb +17 -14
  33. data/test/connections/connection_spec.rb +19 -0
  34. data/test/connections/databases.example.yml +11 -0
  35. data/test/connections/databases.yml +13 -0
  36. data/test/connections/native_mysql/connection.rb +10 -2
  37. data/test/connections/native_oracle/connection.rb +7 -4
  38. data/test/connections/native_oracle_enhanced/connection.rb +23 -0
  39. data/test/connections/native_postgresql/connection.rb +13 -5
  40. data/test/connections/native_sqlite/connection.rb +7 -3
  41. data/test/fixtures/article_group.rb +4 -0
  42. data/test/fixtures/article_groups.yml +7 -0
  43. data/test/fixtures/db_definitions/postgresql.sql +2 -1
  44. data/test/fixtures/debug.log +133 -0
  45. data/test/fixtures/dorm.rb +3 -0
  46. data/test/fixtures/dorms.yml +2 -0
  47. data/test/fixtures/kitchen_sink.rb +3 -0
  48. data/test/fixtures/kitchen_sinks.yml +5 -0
  49. data/test/fixtures/reference_codes.yml +2 -0
  50. data/test/fixtures/reference_type.rb +1 -1
  51. data/test/fixtures/restaurant.rb +6 -0
  52. data/test/fixtures/restaurants.yml +5 -0
  53. data/test/fixtures/restaurants_suburbs.yml +11 -0
  54. data/test/fixtures/room.rb +10 -0
  55. data/test/fixtures/room_assignment.rb +4 -0
  56. data/test/fixtures/room_assignments.yml +4 -0
  57. data/test/fixtures/room_attribute.rb +3 -0
  58. data/test/fixtures/room_attribute_assignment.rb +5 -0
  59. data/test/fixtures/room_attribute_assignments.yml +4 -0
  60. data/test/fixtures/room_attributes.yml +3 -0
  61. data/test/fixtures/rooms.yml +3 -0
  62. data/test/fixtures/seat.rb +5 -0
  63. data/test/fixtures/seats.yml +4 -0
  64. data/test/fixtures/student.rb +4 -0
  65. data/test/fixtures/students.yml +2 -0
  66. data/test/test_associations.rb +27 -50
  67. data/test/test_attributes.rb +15 -19
  68. data/test/test_composite_arrays.rb +2 -21
  69. data/test/test_create.rb +3 -3
  70. data/test/test_delete.rb +7 -20
  71. data/test/test_exists.rb +3 -7
  72. data/test/test_find.rb +0 -8
  73. data/test/test_ids.rb +3 -17
  74. data/test/test_polymorphic.rb +5 -4
  75. data/test/test_suite.rb +19 -0
  76. data/test/{test_tutorial_examle.rb → test_tutorial_example.rb} +0 -0
  77. metadata +110 -72
  78. data/Manifest.txt +0 -123
  79. data/lib/adapter_helper/base.rb +0 -63
  80. data/lib/adapter_helper/mysql.rb +0 -13
  81. data/lib/adapter_helper/oracle.rb +0 -12
  82. data/lib/adapter_helper/postgresql.rb +0 -13
  83. data/lib/adapter_helper/sqlite3.rb +0 -13
  84. data/lib/composite_primary_keys/migration.rb +0 -20
  85. data/local/database_connections.rb.sample +0 -12
  86. data/local/paths.rb.sample +0 -2
  87. data/local/tasks.rb.sample +0 -2
  88. data/tasks/activerecord_selection.rake +0 -43
  89. data/tasks/databases.rake +0 -12
  90. data/tasks/deployment.rake +0 -22
  91. data/tasks/local_setup.rake +0 -13
  92. data/test/test_dummy.rb +0 -28
  93. data/tmp/test.db +0 -0
  94. data/website/index.html +0 -195
  95. data/website/index.txt +0 -159
  96. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  97. data/website/stylesheets/screen.css +0 -126
  98. data/website/template.js +0 -3
  99. data/website/template.rhtml +0 -53
  100. data/website/version-raw.js +0 -3
  101. data/website/version-raw.txt +0 -2
  102. data/website/version.js +0 -4
  103. data/website/version.txt +0 -3
@@ -1,68 +1,26 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module Calculations
4
- def self.append_features(base)
5
- super
6
- base.send(:extend, ClassMethods)
7
- end
8
-
9
- module ClassMethods
10
- def construct_calculation_sql(operation, column_name, options) #:nodoc:
11
- operation = operation.to_s.downcase
12
- options = options.symbolize_keys
13
-
14
- scope = scope(:find)
15
- merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
16
- aggregate_alias = column_alias_for(operation, column_name)
17
- use_workaround = !connection.supports_count_distinct? && options[:distinct] && operation.to_s.downcase == 'count'
18
- join_dependency = nil
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
- CompositeIds.new(self)
11
+ Array.new(self)
12
12
  end
13
13
  end
14
14
 
15
- class CompositeArray < Array
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,9 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters # :nodoc:
3
+ class AbstractAdapter
4
+ def concat(*columns)
5
+ "CONCAT(#{columns.join(',')})"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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
@@ -1,7 +1,7 @@
1
1
  class Fixture #:nodoc:
2
2
  def [](key)
3
3
  if key.is_a? Array
4
- return key.map { |a_key| self[a_key.to_s] }.to_composite_ids.to_s
4
+ return key.map { |a_key| self[a_key.to_s] }.to_composite_ids
5
5
  end
6
6
  @fixture[key]
7
7
  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
- return @primary_key_name if @primary_key_name
6
- case
7
- when macro == :belongs_to
8
- @primary_key_name = options[:foreign_key] || class_name.foreign_key
9
- when options[:as]
10
- @primary_key_name = options[:foreign_key] || "#{options[:as]}_id"
11
- else
12
- @primary_key_name = options[:foreign_key] || active_record.name.foreign_key
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 CompositePrimaryKeys
2
- module ActiveRecord
3
- module Validations
4
- module Uniqueness
5
- module ClassMethods
6
- def validates_uniqueness_of(*attr_names)
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
- validates_each(attr_names,configuration) do |record, attr_name, value|
11
- # The check for an existing value should be run from a class that
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
- # Now we can work our way down the tree to the first non-abstract
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
- column = finder_class.columns_hash[attr_name.to_s]
26
-
27
- if value.nil?
28
- comparison_operator = "IS ?"
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
- finder_class.with_exclusive_scope do
67
- if finder_class.exists?([condition_sql, *condition_params])
68
- record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
69
- end
70
- end
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