activerecord 1.13.2 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (144) hide show
  1. data/CHANGELOG +452 -10
  2. data/RUNNING_UNIT_TESTS +1 -1
  3. data/lib/active_record.rb +5 -2
  4. data/lib/active_record/acts/list.rb +1 -1
  5. data/lib/active_record/acts/tree.rb +29 -25
  6. data/lib/active_record/aggregations.rb +3 -2
  7. data/lib/active_record/associations.rb +783 -337
  8. data/lib/active_record/associations/association_collection.rb +7 -12
  9. data/lib/active_record/associations/association_proxy.rb +62 -24
  10. data/lib/active_record/associations/belongs_to_association.rb +27 -46
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
  13. data/lib/active_record/associations/has_many_association.rb +61 -56
  14. data/lib/active_record/associations/has_many_through_association.rb +144 -0
  15. data/lib/active_record/associations/has_one_association.rb +22 -16
  16. data/lib/active_record/base.rb +482 -182
  17. data/lib/active_record/calculations.rb +225 -0
  18. data/lib/active_record/callbacks.rb +7 -7
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
  20. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
  24. data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
  25. data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
  26. data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
  27. data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
  31. data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
  32. data/lib/active_record/fixtures.rb +42 -17
  33. data/lib/active_record/locking.rb +36 -15
  34. data/lib/active_record/migration.rb +111 -8
  35. data/lib/active_record/observer.rb +25 -1
  36. data/lib/active_record/reflection.rb +103 -41
  37. data/lib/active_record/schema.rb +2 -2
  38. data/lib/active_record/schema_dumper.rb +55 -18
  39. data/lib/active_record/timestamp.rb +6 -6
  40. data/lib/active_record/validations.rb +65 -40
  41. data/lib/active_record/vendor/db2.rb +10 -5
  42. data/lib/active_record/vendor/simple.rb +693 -702
  43. data/lib/active_record/version.rb +2 -2
  44. data/rakefile +4 -4
  45. data/test/aaa_create_tables_test.rb +25 -6
  46. data/test/abstract_unit.rb +39 -1
  47. data/test/adapter_test.rb +31 -4
  48. data/test/associations_cascaded_eager_loading_test.rb +106 -0
  49. data/test/associations_go_eager_test.rb +85 -16
  50. data/test/associations_join_model_test.rb +338 -0
  51. data/test/associations_test.rb +129 -50
  52. data/test/base_test.rb +204 -49
  53. data/test/binary_test.rb +1 -1
  54. data/test/calculations_test.rb +169 -0
  55. data/test/callbacks_test.rb +5 -23
  56. data/test/class_inheritable_attributes_test.rb +1 -1
  57. data/test/column_alias_test.rb +1 -1
  58. data/test/connections/native_mysql/connection.rb +1 -0
  59. data/test/connections/native_openbase/connection.rb +22 -0
  60. data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
  61. data/test/connections/native_sqlite/connection.rb +1 -1
  62. data/test/connections/native_sqlite3/connection.rb +1 -0
  63. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
  64. data/test/connections/native_sybase/connection.rb +24 -0
  65. data/test/defaults_test.rb +18 -0
  66. data/test/deprecated_associations_test.rb +2 -2
  67. data/test/deprecated_finder_test.rb +0 -6
  68. data/test/finder_test.rb +26 -23
  69. data/test/fixtures/accounts.yml +10 -0
  70. data/test/fixtures/author.rb +31 -6
  71. data/test/fixtures/author_favorites.yml +4 -0
  72. data/test/fixtures/categories/special_categories.yml +9 -0
  73. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  74. data/test/fixtures/categories_posts.yml +4 -0
  75. data/test/fixtures/categorization.rb +5 -0
  76. data/test/fixtures/categorizations.yml +11 -0
  77. data/test/fixtures/category.rb +6 -0
  78. data/test/fixtures/company.rb +17 -5
  79. data/test/fixtures/company_in_module.rb +19 -5
  80. data/test/fixtures/db_definitions/db2.drop.sql +3 -0
  81. data/test/fixtures/db_definitions/db2.sql +121 -100
  82. data/test/fixtures/db_definitions/db22.sql +2 -2
  83. data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
  84. data/test/fixtures/db_definitions/firebird.sql +26 -0
  85. data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
  86. data/test/fixtures/db_definitions/mysql.sql +21 -1
  87. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  88. data/test/fixtures/db_definitions/openbase.sql +282 -0
  89. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  90. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  91. data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
  92. data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
  93. data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
  94. data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
  95. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  96. data/test/fixtures/db_definitions/postgresql.sql +22 -1
  97. data/test/fixtures/db_definitions/schema.rb +32 -0
  98. data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
  99. data/test/fixtures/db_definitions/sqlite.sql +18 -0
  100. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  101. data/test/fixtures/db_definitions/sqlserver.sql +23 -3
  102. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  103. data/test/fixtures/db_definitions/sybase.sql +204 -0
  104. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  106. data/test/fixtures/developers.yml +6 -1
  107. data/test/fixtures/developers_projects.yml +4 -0
  108. data/test/fixtures/funny_jokes.yml +14 -0
  109. data/test/fixtures/joke.rb +6 -0
  110. data/test/fixtures/legacy_thing.rb +3 -0
  111. data/test/fixtures/legacy_things.yml +3 -0
  112. data/test/fixtures/mixin.rb +1 -1
  113. data/test/fixtures/person.rb +4 -1
  114. data/test/fixtures/post.rb +26 -1
  115. data/test/fixtures/project.rb +1 -0
  116. data/test/fixtures/reader.rb +4 -0
  117. data/test/fixtures/readers.yml +4 -0
  118. data/test/fixtures/reply.rb +2 -1
  119. data/test/fixtures/tag.rb +5 -0
  120. data/test/fixtures/tagging.rb +6 -0
  121. data/test/fixtures/taggings.yml +18 -0
  122. data/test/fixtures/tags.yml +7 -0
  123. data/test/fixtures/tasks.yml +2 -2
  124. data/test/fixtures/topic.rb +2 -2
  125. data/test/fixtures/topics.yml +1 -0
  126. data/test/fixtures_test.rb +47 -13
  127. data/test/inheritance_test.rb +2 -2
  128. data/test/locking_test.rb +15 -1
  129. data/test/method_scoping_test.rb +248 -13
  130. data/test/migration_test.rb +68 -11
  131. data/test/mixin_nested_set_test.rb +1 -1
  132. data/test/modules_test.rb +6 -1
  133. data/test/readonly_test.rb +1 -1
  134. data/test/reflection_test.rb +63 -9
  135. data/test/schema_dumper_test.rb +41 -0
  136. data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
  137. data/test/threaded_connections_test.rb +10 -0
  138. data/test/unconnected_test.rb +12 -5
  139. data/test/validations_test.rb +197 -10
  140. metadata +295 -260
  141. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
  142. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
  143. data/test/fixtures/fixture_database.sqlite +0 -0
  144. data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -1,20 +1,18 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class HasManyAssociation < AssociationCollection #:nodoc:
4
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
4
+ def initialize(owner, reflection)
5
5
  super
6
- @conditions = sanitize_sql(options[:conditions])
7
-
8
6
  construct_sql
9
7
  end
10
8
 
11
9
  def build(attributes = {})
12
10
  if attributes.is_a?(Array)
13
- attributes.collect { |attr| create(attr) }
11
+ attributes.collect { |attr| build(attr) }
14
12
  else
15
13
  load_target
16
- record = @association_class.new(attributes)
17
- record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
14
+ record = @reflection.klass.new(attributes)
15
+ set_belongs_to_association_for(record)
18
16
  @target << record
19
17
  record
20
18
  end
@@ -22,13 +20,13 @@ module ActiveRecord
22
20
 
23
21
  # DEPRECATED.
24
22
  def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
25
- if @options[:finder_sql]
26
- @association_class.find_by_sql(@finder_sql)
23
+ if @reflection.options[:finder_sql]
24
+ @reflection.klass.find_by_sql(@finder_sql)
27
25
  else
28
26
  conditions = @finder_sql
29
27
  conditions += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
30
- orderings ||= @options[:order]
31
- @association_class.find_all(conditions, orderings, limit, joins)
28
+ orderings ||= @reflection.options[:order]
29
+ @reflection.klass.find_all(conditions, orderings, limit, joins)
32
30
  end
33
31
  end
34
32
 
@@ -39,14 +37,14 @@ module ActiveRecord
39
37
 
40
38
  # Count the number of associated records. All arguments are optional.
41
39
  def count(runtime_conditions = nil)
42
- if @options[:counter_sql]
43
- @association_class.count_by_sql(@counter_sql)
44
- elsif @options[:finder_sql]
45
- @association_class.count_by_sql(@finder_sql)
40
+ if @reflection.options[:counter_sql]
41
+ @reflection.klass.count_by_sql(@counter_sql)
42
+ elsif @reflection.options[:finder_sql]
43
+ @reflection.klass.count_by_sql(@finder_sql)
46
44
  else
47
45
  sql = @finder_sql
48
46
  sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
49
- @association_class.count(sql)
47
+ @reflection.klass.count(sql)
50
48
  end
51
49
  end
52
50
 
@@ -54,7 +52,7 @@ module ActiveRecord
54
52
  options = Base.send(:extract_options_from_args!, args)
55
53
 
56
54
  # If using a custom finder_sql, scan the entire collection.
57
- if @options[:finder_sql]
55
+ if @reflection.options[:finder_sql]
58
56
  expects_array = args.first.kind_of?(Array)
59
57
  ids = args.flatten.compact.uniq
60
58
 
@@ -72,64 +70,63 @@ module ActiveRecord
72
70
  end
73
71
  options[:conditions] = conditions
74
72
 
75
- if options[:order] && @options[:order]
76
- options[:order] = "#{options[:order]}, #{@options[:order]}"
77
- elsif @options[:order]
78
- options[:order] = @options[:order]
73
+ if options[:order] && @reflection.options[:order]
74
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
75
+ elsif @reflection.options[:order]
76
+ options[:order] = @reflection.options[:order]
79
77
  end
80
78
 
79
+ merge_options_from_reflection!(options)
80
+
81
81
  # Pass through args exactly as we received them.
82
82
  args << options
83
- @association_class.find(*args)
83
+ @reflection.klass.find(*args)
84
84
  end
85
85
  end
86
86
 
87
87
  protected
88
88
  def method_missing(method, *args, &block)
89
- if @target.respond_to?(method) || (!@association_class.respond_to?(method) && Class.respond_to?(method))
89
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
90
90
  super
91
91
  else
92
- @association_class.with_scope(
92
+ @reflection.klass.with_scope(
93
93
  :find => {
94
94
  :conditions => @finder_sql,
95
95
  :joins => @join_sql,
96
96
  :readonly => false
97
97
  },
98
98
  :create => {
99
- @association_class_primary_key_name => @owner.id
99
+ @reflection.primary_key_name => @owner.id
100
100
  }
101
101
  ) do
102
- @association_class.send(method, *args, &block)
102
+ @reflection.klass.send(method, *args, &block)
103
103
  end
104
104
  end
105
105
  end
106
106
 
107
107
  def find_target
108
- if @options[:finder_sql]
109
- @association_class.find_by_sql(@finder_sql)
108
+ if @reflection.options[:finder_sql]
109
+ @reflection.klass.find_by_sql(@finder_sql)
110
110
  else
111
- @association_class.find(:all,
112
- :conditions => @finder_sql,
113
- :order => @options[:order],
114
- :limit => @options[:limit],
115
- :joins => @options[:joins],
116
- :include => @options[:include],
117
- :group => @options[:group]
118
- )
111
+ find(:all)
119
112
  end
120
113
  end
121
114
 
122
115
  def count_records
123
116
  count = if has_cached_counter?
124
117
  @owner.send(:read_attribute, cached_counter_attribute_name)
125
- elsif @options[:counter_sql]
126
- @association_class.count_by_sql(@counter_sql)
118
+ elsif @reflection.options[:counter_sql]
119
+ @reflection.klass.count_by_sql(@counter_sql)
127
120
  else
128
- @association_class.count(@counter_sql)
121
+ @reflection.klass.count(@counter_sql)
129
122
  end
130
123
 
131
124
  @target = [] and loaded if count == 0
132
125
 
126
+ if @reflection.options[:limit]
127
+ count = [ @reflection.options[:limit], count ].min
128
+ end
129
+
133
130
  return count
134
131
  end
135
132
 
@@ -138,22 +135,22 @@ module ActiveRecord
138
135
  end
139
136
 
140
137
  def cached_counter_attribute_name
141
- "#{@association_name}_count"
138
+ "#{@reflection.name}_count"
142
139
  end
143
140
 
144
141
  def insert_record(record)
145
- record[@association_class_primary_key_name] = @owner.id
142
+ set_belongs_to_association_for(record)
146
143
  record.save
147
144
  end
148
145
 
149
146
  def delete_records(records)
150
- if @options[:dependent]
147
+ if @reflection.options[:dependent]
151
148
  records.each { |r| r.destroy }
152
149
  else
153
150
  ids = quoted_record_ids(records)
154
- @association_class.update_all(
155
- "#{@association_class_primary_key_name} = NULL",
156
- "#{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_class.primary_key} IN (#{ids})"
151
+ @reflection.klass.update_all(
152
+ "#{@reflection.primary_key_name} = NULL",
153
+ "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
157
154
  )
158
155
  end
159
156
  end
@@ -163,21 +160,29 @@ module ActiveRecord
163
160
  end
164
161
 
165
162
  def construct_sql
166
- if @options[:finder_sql]
167
- @finder_sql = interpolate_sql(@options[:finder_sql])
168
- else
169
- @finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
170
- @finder_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
163
+ case
164
+ when @reflection.options[:finder_sql]
165
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
166
+
167
+ when @reflection.options[:as]
168
+ @finder_sql =
169
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
170
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
171
+ @finder_sql << " AND (#{conditions})" if conditions
172
+
173
+ else
174
+ @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
175
+ @finder_sql << " AND (#{conditions})" if conditions
171
176
  end
172
177
 
173
- if @options[:counter_sql]
174
- @counter_sql = interpolate_sql(@options[:counter_sql])
175
- elsif @options[:finder_sql]
176
- @options[:counter_sql] = @options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
177
- @counter_sql = interpolate_sql(@options[:counter_sql])
178
+ if @reflection.options[:counter_sql]
179
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
180
+ elsif @reflection.options[:finder_sql]
181
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
182
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
183
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
178
184
  else
179
- @counter_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
180
- @counter_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
185
+ @counter_sql = @finder_sql
181
186
  end
182
187
  end
183
188
  end
@@ -0,0 +1,144 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasManyThroughAssociation < AssociationProxy #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ reflection.check_validity!
7
+ @finder_sql = construct_conditions
8
+ construct_sql
9
+ end
10
+
11
+
12
+ def find(*args)
13
+ options = Base.send(:extract_options_from_args!, args)
14
+
15
+ conditions = "#{@finder_sql}"
16
+ if sanitized_conditions = sanitize_sql(options[:conditions])
17
+ conditions << " AND (#{sanitized_conditions})"
18
+ end
19
+ options[:conditions] = conditions
20
+
21
+ if options[:order] && @reflection.options[:order]
22
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
23
+ elsif @reflection.options[:order]
24
+ options[:order] = @reflection.options[:order]
25
+ end
26
+
27
+ options[:select] = construct_select(options[:select])
28
+ options[:from] ||= construct_from
29
+ options[:joins] = construct_joins(options[:joins])
30
+ options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
31
+
32
+ merge_options_from_reflection!(options)
33
+
34
+ # Pass through args exactly as we received them.
35
+ args << options
36
+ @reflection.klass.find(*args)
37
+ end
38
+
39
+ def reset
40
+ @target = []
41
+ @loaded = false
42
+ end
43
+
44
+ protected
45
+ def method_missing(method, *args, &block)
46
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
47
+ super
48
+ else
49
+ @reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
50
+ end
51
+ end
52
+
53
+ def find_target
54
+ @reflection.klass.find(:all,
55
+ :select => construct_select,
56
+ :conditions => construct_conditions,
57
+ :from => construct_from,
58
+ :joins => construct_joins,
59
+ :order => @reflection.options[:order],
60
+ :limit => @reflection.options[:limit],
61
+ :group => @reflection.options[:group],
62
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
63
+ )
64
+ end
65
+
66
+ def construct_conditions
67
+ conditions = if @reflection.through_reflection.options[:as]
68
+ "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_id = #{@owner.quoted_id} " +
69
+ "AND #{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
70
+ else
71
+ case @reflection.source_reflection.macro
72
+ when :belongs_to, :has_many
73
+ "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.primary_key_name} = #{@owner.quoted_id}"
74
+ else
75
+ raise ActiveRecordError, "Invalid source reflection macro :#{@reflection.source_reflection.macro} for has_many #{@reflection.name}, :through => #{@reflection.through_reflection.name}. Use :source to specify the source reflection."
76
+ end
77
+ end
78
+ conditions << " AND (#{sql_conditions})" if sql_conditions
79
+
80
+ return conditions
81
+ end
82
+
83
+ def construct_from
84
+ @reflection.table_name
85
+ end
86
+
87
+ def construct_select(custom_select = nil)
88
+ selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
89
+ end
90
+
91
+ def construct_joins(custom_joins = nil)
92
+ if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
93
+ reflection_primary_key = @reflection.klass.primary_key
94
+ source_primary_key = @reflection.source_reflection.primary_key_name
95
+ else
96
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
97
+ source_primary_key = @reflection.klass.primary_key
98
+ end
99
+
100
+ "INNER JOIN %s ON %s.%s = %s.%s #{@reflection.options[:joins]} #{custom_joins}" % [
101
+ @reflection.through_reflection.table_name,
102
+ @reflection.table_name, reflection_primary_key,
103
+ @reflection.through_reflection.table_name, source_primary_key
104
+ ]
105
+ end
106
+
107
+ def construct_scope
108
+ {
109
+ :find => { :from => construct_from, :conditions => construct_conditions, :joins => construct_joins, :select => construct_select },
110
+ :create => { @reflection.primary_key_name => @owner.id }
111
+ }
112
+ end
113
+
114
+ def construct_sql
115
+ case
116
+ when @reflection.options[:finder_sql]
117
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
118
+
119
+ @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
120
+ @finder_sql << " AND (#{conditions})" if conditions
121
+ end
122
+
123
+ if @reflection.options[:counter_sql]
124
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
125
+ elsif @reflection.options[:finder_sql]
126
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
127
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
128
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
129
+ else
130
+ @counter_sql = @finder_sql
131
+ end
132
+ end
133
+
134
+ def conditions
135
+ @conditions ||= [
136
+ (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
137
+ (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions])
138
+ ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions])
139
+ end
140
+
141
+ alias_method :sql_conditions, :conditions
142
+ end
143
+ end
144
+ end
@@ -1,9 +1,8 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class HasOneAssociation < BelongsToAssociation #:nodoc:
4
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
4
+ def initialize(owner, reflection)
5
5
  super
6
-
7
6
  construct_sql
8
7
  end
9
8
 
@@ -14,12 +13,12 @@ module ActiveRecord
14
13
  end
15
14
 
16
15
  def build(attributes = {}, replace_existing = true)
17
- record = @association_class.new(attributes)
16
+ record = @reflection.klass.new(attributes)
18
17
 
19
18
  if replace_existing
20
19
  replace(record, true)
21
20
  else
22
- record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
21
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
23
22
  self.target = record
24
23
  end
25
24
 
@@ -28,13 +27,14 @@ module ActiveRecord
28
27
 
29
28
  def replace(obj, dont_save = false)
30
29
  load_target
30
+
31
31
  unless @target.nil?
32
32
  if dependent? && !dont_save && @target != obj
33
33
  @target.destroy unless @target.new_record?
34
34
  @owner.clear_association_cache
35
35
  else
36
- @target[@association_class_primary_key_name] = nil
37
- @target.save unless @owner.new_record?
36
+ @target[@reflection.primary_key_name] = nil
37
+ @target.save unless @owner.new_record? || @target.new_record?
38
38
  end
39
39
  end
40
40
 
@@ -42,12 +42,12 @@ module ActiveRecord
42
42
  @target = nil
43
43
  else
44
44
  raise_on_type_mismatch(obj)
45
-
46
- obj[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
45
+ set_belongs_to_association_for(obj)
47
46
  @target = (AssociationProxy === obj ? obj.target : obj)
48
47
  end
49
48
 
50
49
  @loaded = true
50
+
51
51
  unless @owner.new_record? or obj.nil? or dont_save
52
52
  return (obj.save ? self : false)
53
53
  else
@@ -57,17 +57,23 @@ module ActiveRecord
57
57
 
58
58
  private
59
59
  def find_target
60
- @association_class.find(:first, :conditions => @finder_sql, :order => @options[:order], :include => @options[:include])
61
- end
62
-
63
- def target_obsolete?
64
- false
60
+ @reflection.klass.find(:first,
61
+ :conditions => @finder_sql,
62
+ :order => @reflection.options[:order],
63
+ :include => @reflection.options[:include]
64
+ )
65
65
  end
66
66
 
67
67
  def construct_sql
68
- @finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
69
- @finder_sql << " AND (#{sanitize_sql(@options[:conditions])})" if @options[:conditions]
70
- @finder_sql
68
+ case
69
+ when @reflection.options[:as]
70
+ @finder_sql =
71
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
72
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
73
+ else
74
+ @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
75
+ end
76
+ @finder_sql << " AND (#{conditions})" if conditions
71
77
  end
72
78
  end
73
79
  end