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.
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