activerecord 1.11.1 → 1.12.1

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 (102) hide show
  1. data/CHANGELOG +198 -0
  2. data/lib/active_record.rb +19 -14
  3. data/lib/active_record/acts/list.rb +8 -6
  4. data/lib/active_record/acts/tree.rb +33 -10
  5. data/lib/active_record/aggregations.rb +1 -7
  6. data/lib/active_record/associations.rb +151 -82
  7. data/lib/active_record/associations/association_collection.rb +25 -0
  8. data/lib/active_record/associations/association_proxy.rb +9 -8
  9. data/lib/active_record/associations/belongs_to_association.rb +19 -5
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
  11. data/lib/active_record/associations/has_many_association.rb +6 -14
  12. data/lib/active_record/associations/has_one_association.rb +5 -3
  13. data/lib/active_record/base.rb +344 -130
  14. data/lib/active_record/callbacks.rb +2 -2
  15. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
  16. data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
  17. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
  18. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
  20. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
  21. data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
  23. data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
  24. data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
  25. data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
  27. data/lib/active_record/fixtures.rb +47 -24
  28. data/lib/active_record/migration.rb +34 -5
  29. data/lib/active_record/observer.rb +32 -2
  30. data/lib/active_record/query_cache.rb +12 -11
  31. data/lib/active_record/schema.rb +58 -0
  32. data/lib/active_record/schema_dumper.rb +84 -0
  33. data/lib/active_record/transactions.rb +1 -3
  34. data/lib/active_record/validations.rb +40 -26
  35. data/lib/active_record/vendor/mysql.rb +6 -0
  36. data/lib/active_record/version.rb +9 -0
  37. data/rakefile +5 -16
  38. data/test/abstract_unit.rb +6 -11
  39. data/test/adapter_test.rb +58 -0
  40. data/test/ar_schema_test.rb +33 -0
  41. data/test/association_callbacks_test.rb +14 -0
  42. data/test/associations_go_eager_test.rb +56 -14
  43. data/test/associations_test.rb +245 -25
  44. data/test/base_test.rb +205 -34
  45. data/test/binary_test.rb +25 -42
  46. data/test/callbacks_test.rb +75 -0
  47. data/test/conditions_scoping_test.rb +136 -0
  48. data/test/connections/native_mysql/connection.rb +0 -4
  49. data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
  50. data/test/copy_table_sqlite.rb +64 -0
  51. data/test/deprecated_associations_test.rb +7 -6
  52. data/test/deprecated_finder_test.rb +3 -3
  53. data/test/finder_test.rb +33 -3
  54. data/test/fixtures/accounts.yml +5 -0
  55. data/test/fixtures/categories_ordered.yml +7 -0
  56. data/test/fixtures/category.rb +11 -1
  57. data/test/fixtures/comment.rb +22 -2
  58. data/test/fixtures/comments.yml +6 -0
  59. data/test/fixtures/companies.yml +15 -0
  60. data/test/fixtures/company.rb +24 -1
  61. data/test/fixtures/db_definitions/db2.drop.sql +5 -1
  62. data/test/fixtures/db_definitions/db2.sql +15 -1
  63. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  64. data/test/fixtures/db_definitions/mysql.sql +17 -2
  65. data/test/fixtures/db_definitions/oci.drop.sql +37 -5
  66. data/test/fixtures/db_definitions/oci.sql +47 -4
  67. data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
  68. data/test/fixtures/db_definitions/oci2.sql +2 -2
  69. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  70. data/test/fixtures/db_definitions/postgresql.sql +33 -4
  71. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  72. data/test/fixtures/db_definitions/sqlite.sql +16 -2
  73. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  74. data/test/fixtures/db_definitions/sqlserver.sql +16 -2
  75. data/test/fixtures/developer.rb +1 -1
  76. data/test/fixtures/flowers.jpg +0 -0
  77. data/test/fixtures/keyboard.rb +3 -0
  78. data/test/fixtures/mixins.yml +11 -1
  79. data/test/fixtures/order.rb +4 -0
  80. data/test/fixtures/post.rb +4 -0
  81. data/test/fixtures/posts.yml +7 -0
  82. data/test/fixtures/project.rb +1 -0
  83. data/test/fixtures/subject.rb +4 -0
  84. data/test/fixtures/subscriber.rb +2 -4
  85. data/test/fixtures/topics.yml +2 -2
  86. data/test/fixtures_test.rb +79 -7
  87. data/test/inheritance_test.rb +2 -2
  88. data/test/lifecycle_test.rb +14 -6
  89. data/test/migration_test.rb +164 -6
  90. data/test/mixin_test.rb +78 -2
  91. data/test/pk_test.rb +25 -1
  92. data/test/readonly_test.rb +31 -0
  93. data/test/reflection_test.rb +4 -1
  94. data/test/schema_dumper_test.rb +19 -0
  95. data/test/schema_test_postgresql.rb +3 -2
  96. data/test/synonym_test_oci.rb +17 -0
  97. data/test/threaded_connections_test.rb +2 -1
  98. data/test/transactions_test.rb +109 -10
  99. data/test/validations_test.rb +70 -42
  100. metadata +25 -5
  101. data/test/fixtures/associations.png +0 -0
  102. data/test/thread_safety_test.rb +0 -36
@@ -33,6 +33,12 @@ module ActiveRecord
33
33
 
34
34
  alias_method :push, :<<
35
35
  alias_method :concat, :<<
36
+
37
+ # Remove all records from this association
38
+ def delete_all
39
+ delete(@target)
40
+ @target = []
41
+ end
36
42
 
37
43
  # Remove +records+ from this association. Does not destroy +records+.
38
44
  def delete(*records)
@@ -50,6 +56,17 @@ module ActiveRecord
50
56
  end
51
57
  end
52
58
  end
59
+
60
+ # Removes all records from this association. Returns +self+ so method calls may be chained.
61
+ def clear
62
+ return self if empty? # forces load_target if hasn't happened already
63
+ if @options[:exclusively_dependent]
64
+ destroy_all
65
+ else
66
+ delete_all
67
+ end
68
+ self
69
+ end
53
70
 
54
71
  def destroy_all
55
72
  @owner.transaction do
@@ -107,6 +124,14 @@ module ActiveRecord
107
124
  end
108
125
 
109
126
  private
127
+ def method_missing(method, *args, &block)
128
+ if @target.respond_to?(method) or (not @association_class.respond_to?(method) and Class.respond_to?(method))
129
+ super
130
+ else
131
+ @association_class.constrain(:conditions => @finder_sql, :joins => @join_sql) { @association_class.send(method, *args, &block) }
132
+ end
133
+ end
134
+
110
135
  def raise_on_type_mismatch(record)
111
136
  raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
112
137
  end
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  @owner = owner
9
9
  @options = options
10
10
  @association_name = association_name
11
- @association_class = eval(association_class_name)
11
+ @association_class = eval(association_class_name, nil, __FILE__, __LINE__)
12
12
  @association_class_primary_key_name = association_class_primary_key_name
13
13
 
14
14
  reset
@@ -19,11 +19,6 @@ module ActiveRecord
19
19
  load_target
20
20
  end
21
21
 
22
- def method_missing(symbol, *args, &block)
23
- load_target
24
- @target.send(symbol, *args, &block)
25
- end
26
-
27
22
  def respond_to?(symbol, include_priv = false)
28
23
  proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
29
24
  end
@@ -44,7 +39,7 @@ module ActiveRecord
44
39
  @target = t
45
40
  @loaded = true
46
41
  end
47
-
42
+
48
43
  protected
49
44
  def dependent?
50
45
  @options[:dependent] || false
@@ -69,8 +64,14 @@ module ActiveRecord
69
64
  def extract_options_from_args!(args)
70
65
  @owner.send(:extract_options_from_args!, args)
71
66
  end
72
-
67
+
73
68
  private
69
+
70
+ def method_missing(method, *args, &block)
71
+ load_target
72
+ @target.send(method, *args, &block)
73
+ end
74
+
74
75
  def load_target
75
76
  if !@owner.new_record? || foreign_key_present
76
77
  begin
@@ -1,14 +1,20 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class BelongsToAssociation < AssociationProxy #:nodoc:
4
+
5
+ def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
6
+ super
7
+ construct_sql
8
+ end
9
+
4
10
  def reset
5
11
  @target = nil
6
12
  @loaded = false
7
13
  end
8
14
 
9
15
  def create(attributes = {})
10
- record = build(attributes)
11
- record.save
16
+ record = @association_class.create(attributes)
17
+ replace(record, true)
12
18
  record
13
19
  end
14
20
 
@@ -24,13 +30,21 @@ module ActiveRecord
24
30
  else
25
31
  raise_on_type_mismatch(obj) unless obj.nil?
26
32
 
27
- @target = obj
33
+ @target = (AssociationProxy === obj ? obj.target : obj)
28
34
  @owner[@association_class_primary_key_name] = obj.id unless obj.new_record?
35
+ @updated = true
29
36
  end
30
37
  @loaded = true
31
38
 
32
39
  return (@target.nil? ? nil : self)
33
40
  end
41
+
42
+ def updated?
43
+ @updated
44
+ end
45
+
46
+ protected
47
+
34
48
 
35
49
  private
36
50
  def find_target
@@ -48,9 +62,9 @@ module ActiveRecord
48
62
  def target_obsolete?
49
63
  @owner[@association_class_primary_key_name] != @target.id
50
64
  end
51
-
65
+
52
66
  def construct_sql
53
- # no sql to construct
67
+ @finder_sql = "#{@association_class.table_name}.#{@association_class.primary_key} = #{@owner.id}"
54
68
  end
55
69
  end
56
70
  end
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
5
5
  super
6
6
 
7
- @association_foreign_key = options[:association_foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name)) + "_id"
7
+ @association_foreign_key = options[:association_foreign_key] || association_class_name.foreign_key
8
8
  @association_table_name = options[:table_name] || @association_class.table_name
9
9
  @join_table = options[:join_table]
10
10
  @order = options[:order]
@@ -19,65 +19,44 @@ module ActiveRecord
19
19
  record
20
20
  end
21
21
 
22
- # Removes all records from this association. Returns +self+ so method calls may be chained.
23
- def clear
24
- return self if size == 0 # forces load_target if hasn't happened already
25
-
26
- if sql = @options[:delete_sql]
27
- each { |record| @owner.connection.execute(sql) }
28
- elsif @options[:conditions]
29
- sql =
30
- "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id} " +
31
- "AND #{@association_foreign_key} IN (#{collect { |record| record.id }.join(", ")})"
32
- @owner.connection.execute(sql)
33
- else
34
- sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id}"
35
- @owner.connection.execute(sql)
36
- end
37
-
38
- @target = []
39
- self
40
- end
41
-
42
22
  def find_first
43
23
  load_target.first
44
24
  end
45
-
25
+
46
26
  def find(*args)
47
- # Return an Array if multiple ids are given.
48
- expects_array = args.first.kind_of?(Array)
49
-
50
- ids = args.flatten.compact.uniq
51
-
52
- # If no block is given, raise RecordNotFound.
53
- if ids.empty?
54
- raise RecordNotFound, "Couldn't find #{@association_class.name} without an ID"
27
+ options = Base.send(:extract_options_from_args!, args)
55
28
 
56
29
  # If using a custom finder_sql, scan the entire collection.
57
- elsif @options[:finder_sql]
30
+ if @options[:finder_sql]
31
+ expects_array = args.first.kind_of?(Array)
32
+ ids = args.flatten.compact.uniq
33
+
58
34
  if ids.size == 1
59
- id = ids.first
35
+ id = ids.first.to_i
60
36
  record = load_target.detect { |record| id == record.id }
61
- expects_array? ? [record] : record
37
+ expects_array ? [record] : record
62
38
  else
63
39
  load_target.select { |record| ids.include?(record.id) }
64
40
  end
65
-
66
- # Otherwise, construct a query.
67
41
  else
68
- ids_list = ids.map { |id| @owner.send(:quote, id) }.join(',')
69
- records = find_target(@finder_sql.sub(/(ORDER BY|$)/, " AND j.#{@association_foreign_key} IN (#{ids_list}) \\1"))
70
- if records.size == ids.size
71
- if ids.size == 1 and !expects_array
72
- records.first
73
- else
74
- records
75
- end
76
- else
77
- raise RecordNotFound, "Couldn't find #{@association_class.name} with ID in (#{ids_list})"
42
+ conditions = "#{@finder_sql}"
43
+ if sanitized_conditions = sanitize_sql(options[:conditions])
44
+ conditions << " AND (#{sanitized_conditions})"
45
+ end
46
+ options[:conditions] = conditions
47
+ options[:joins] = @join_sql
48
+
49
+ if options[:order] && @options[:order]
50
+ options[:order] = "#{options[:order]}, #{@options[:order]}"
51
+ elsif @options[:order]
52
+ options[:order] = @options[:order]
78
53
  end
54
+
55
+ # Pass through args exactly as we received them.
56
+ args << options
57
+ @association_class.find(*args)
79
58
  end
80
- end
59
+ end
81
60
 
82
61
  def push_with_attributes(record, join_attributes = {})
83
62
  raise_on_type_mismatch(record)
@@ -96,11 +75,16 @@ module ActiveRecord
96
75
  end
97
76
 
98
77
  protected
99
- def find_target(sql = @finder_sql)
100
- records = @association_class.find_by_sql(sql)
78
+ def find_target
79
+ if @options[:finder_sql]
80
+ records = @association_class.find_by_sql(@finder_sql)
81
+ else
82
+ records = find(:all)
83
+ end
84
+
101
85
  @options[:uniq] ? uniq(records) : records
102
86
  end
103
-
87
+
104
88
  def count_records
105
89
  load_target.size
106
90
  end
@@ -122,15 +106,17 @@ module ActiveRecord
122
106
  when @association_foreign_key
123
107
  attributes[column.name] = record.quoted_id
124
108
  else
125
- value = record[column.name]
126
- attributes[column.name] = value unless value.nil?
109
+ if record.attributes.has_key?(column.name)
110
+ value = @owner.send(:quote, record[column.name], column)
111
+ attributes[column.name] = value unless value.nil?
112
+ end
127
113
  end
128
114
  attributes
129
115
  end
130
116
 
131
117
  sql =
132
118
  "INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
133
- "VALUES (#{attributes.values.collect { |value| @owner.send(:quote, value) }.join(', ')})"
119
+ "VALUES (#{attributes.values.join(', ')})"
134
120
 
135
121
  @owner.connection.execute(sql)
136
122
  end
@@ -150,28 +136,17 @@ module ActiveRecord
150
136
 
151
137
  def construct_sql
152
138
  interpolate_sql_options!(@options, :finder_sql)
153
-
139
+
154
140
  if @options[:finder_sql]
155
141
  @finder_sql = @options[:finder_sql]
156
142
  else
157
- @finder_sql =
158
- "SELECT t.*, j.* FROM #{@join_table} j, #{@association_table_name} t " +
159
- "WHERE t.#{@association_class.primary_key} = j.#{@association_foreign_key} AND " +
160
- "j.#{@association_class_primary_key_name} = #{@owner.quoted_id} "
161
-
162
- @finder_sql << " AND #{interpolate_sql(@options[:conditions])}" if @options[:conditions]
163
-
164
- unless @association_class.descends_from_active_record?
165
- type_condition = @association_class.send(:subclasses).inject("t.#{@association_class.inheritance_column} = '#{@association_class.name.demodulize}' ") do |condition, subclass|
166
- condition << "OR t.#{@association_class.inheritance_column} = '#{subclass.name.demodulize}' "
167
- end
168
-
169
- @finder_sql << " AND (#{type_condition})"
170
- end
171
-
172
- @finder_sql << " ORDER BY #{@order}" if @order
143
+ @finder_sql = "#{@join_table}.#{@association_class_primary_key_name} = #{@owner.quoted_id} "
144
+ @finder_sql << " AND (#{interpolate_sql(@options[:conditions])})" if @options[:conditions]
173
145
  end
146
+
147
+ @join_sql = "LEFT JOIN #{@join_table} ON #{@association_class.table_name}.#{@association_class.primary_key} = #{@join_table}.#{@association_foreign_key}"
174
148
  end
149
+
175
150
  end
176
151
  end
177
152
  end
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  records = @association_class.find_by_sql(@finder_sql)
27
27
  else
28
28
  sql = @finder_sql
29
- sql += " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
29
+ sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
30
30
  orderings ||= @options[:order]
31
31
  records = @association_class.find_all(sql, orderings, limit, joins)
32
32
  end
@@ -45,7 +45,7 @@ module ActiveRecord
45
45
  @association_class.count_by_sql(@finder_sql)
46
46
  else
47
47
  sql = @finder_sql
48
- sql += " AND #{sanitize_sql(runtime_conditions)}" if runtime_conditions
48
+ sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
49
49
  @association_class.count(sql)
50
50
  end
51
51
  end
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
  else
69
69
  conditions = "#{@finder_sql}"
70
70
  if sanitized_conditions = sanitize_sql(options[:conditions])
71
- conditions << " AND #{sanitized_conditions}"
71
+ conditions << " AND (#{sanitized_conditions})"
72
72
  end
73
73
  options[:conditions] = conditions
74
74
 
@@ -83,15 +83,7 @@ module ActiveRecord
83
83
  @association_class.find(*args)
84
84
  end
85
85
  end
86
-
87
- # Removes all records from this association. Returns +self+ so
88
- # method calls may be chained.
89
- def clear
90
- @association_class.update_all("#{@association_class_primary_key_name} = NULL", "#{@association_class_primary_key_name} = #{@owner.quoted_id}")
91
- @target = []
92
- self
93
- end
94
-
86
+
95
87
  protected
96
88
  def find_target
97
89
  find_all
@@ -145,7 +137,7 @@ module ActiveRecord
145
137
  @finder_sql = interpolate_sql(@options[:finder_sql])
146
138
  else
147
139
  @finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
148
- @finder_sql << " AND #{interpolate_sql(@conditions)}" if @conditions
140
+ @finder_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
149
141
  end
150
142
 
151
143
  if @options[:counter_sql]
@@ -155,7 +147,7 @@ module ActiveRecord
155
147
  @counter_sql = interpolate_sql(@options[:counter_sql])
156
148
  else
157
149
  @counter_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
158
- @counter_sql << " AND #{interpolate_sql(@conditions)}" if @conditions
150
+ @counter_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
159
151
  end
160
152
  end
161
153
  end
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  def replace(obj, dont_save = false)
30
30
  load_target
31
31
  unless @target.nil?
32
- if dependent? && !dont_save
32
+ if dependent? && !dont_save && @target != obj
33
33
  @target.destroy unless @target.new_record?
34
34
  @owner.clear_association_cache
35
35
  else
@@ -44,7 +44,7 @@ module ActiveRecord
44
44
  raise_on_type_mismatch(obj)
45
45
 
46
46
  obj[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
47
- @target = obj
47
+ @target = (AssociationProxy === obj ? obj.target : obj)
48
48
  end
49
49
 
50
50
  @loaded = true
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  return (obj.nil? ? nil : self)
55
55
  end
56
56
  end
57
-
57
+
58
58
  private
59
59
  def find_target
60
60
  @association_class.find(:first, :conditions => @finder_sql, :order => @options[:order])
@@ -66,6 +66,8 @@ module ActiveRecord
66
66
 
67
67
  def construct_sql
68
68
  @finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}#{@options[:conditions] ? " AND " + @options[:conditions] : ""}"
69
+ @finder_sql << " AND (#{sanitize_sql(@options[:conditions])})" if @options[:conditions]
70
+ @finder_sql
69
71
  end
70
72
  end
71
73
  end
@@ -28,7 +28,9 @@ module ActiveRecord #:nodoc:
28
28
  end
29
29
  class ConfigurationError < StandardError #:nodoc:
30
30
  end
31
-
31
+ class ReadOnlyRecord < StandardError #:nodoc:
32
+ end
33
+
32
34
  class AttributeAssignmentError < ActiveRecordError #:nodoc:
33
35
  attr_reader :exception, :attribute
34
36
  def initialize(message, exception, attribute)
@@ -45,7 +47,7 @@ module ActiveRecord #:nodoc:
45
47
  end
46
48
  end
47
49
 
48
- # Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
50
+ # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
49
51
  # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
50
52
  # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
51
53
  # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
@@ -54,7 +56,7 @@ module ActiveRecord #:nodoc:
54
56
  #
55
57
  # == Creation
56
58
  #
57
- # Active Records accepts constructor parameters either in a hash or as a block. The hash method is especially useful when
59
+ # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
58
60
  # you're receiving the data from somewhere else, like a HTTP request. It works like this:
59
61
  #
60
62
  # user = User.new(:name => "David", :occupation => "Code Artist")
@@ -77,7 +79,7 @@ module ActiveRecord #:nodoc:
77
79
  #
78
80
  # Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
79
81
  # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
80
- # be used for statements that doesn't involve tainted data. Examples:
82
+ # be used for statements that don't involve tainted data. Examples:
81
83
  #
82
84
  # User < ActiveRecord::Base
83
85
  # def self.authenticate_unsafely(user_name, password)
@@ -125,14 +127,14 @@ module ActiveRecord #:nodoc:
125
127
  # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
126
128
  # read_attribute(:attribute) as a shorter form.
127
129
  #
128
- # == Accessing attributes before they have been type casted
130
+ # == Accessing attributes before they have been typecasted
129
131
  #
130
- # Some times you want to be able to read the raw attribute data without having the column-determined type cast run its course first.
132
+ # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
131
133
  # That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
132
134
  # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
133
135
  #
134
136
  # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
135
- # the original string back in an error message. Accessing the attribute normally would type cast the string to 0, which isn't what you
137
+ # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
136
138
  # want.
137
139
  #
138
140
  # == Dynamic attribute-based finders
@@ -155,7 +157,7 @@ module ActiveRecord #:nodoc:
155
157
  # == Saving arrays, hashes, and other non-mappable objects in text columns
156
158
  #
157
159
  # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
158
- # This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
160
+ # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
159
161
  #
160
162
  # class User < ActiveRecord::Base
161
163
  # serialize :preferences
@@ -164,7 +166,7 @@ module ActiveRecord #:nodoc:
164
166
  # user = User.create(:preferences) => { "background" => "black", "display" => large })
165
167
  # User.find(user.id).preferences # => { "background" => "black", "display" => large }
166
168
  #
167
- # You can also specify an class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
169
+ # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
168
170
  # descendent of a class not in the hierarchy. Example:
169
171
  #
170
172
  # class User < ActiveRecord::Base
@@ -208,7 +210,7 @@ module ActiveRecord #:nodoc:
208
210
  # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
209
211
  # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
210
212
  # <tt>:adapter</tt> key.
211
- # * +AdapterNotSpecified+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
213
+ # * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
212
214
  # (or a bad spelling of an existing one).
213
215
  # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
214
216
  # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
@@ -222,12 +224,11 @@ module ActiveRecord #:nodoc:
222
224
  # objects that should be inspected to determine which attributes triggered the errors.
223
225
  # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
224
226
  # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
227
+ #
225
228
  # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
226
229
  # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
227
230
  # instances in the current object space.
228
231
  class Base
229
- include ClassInheritableAttributes
230
-
231
232
  # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
232
233
  # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
233
234
  cattr_accessor :logger
@@ -236,7 +237,11 @@ module ActiveRecord #:nodoc:
236
237
  # also be used to "borrow" the connection to do database work unrelated
237
238
  # to any of the specific Active Records.
238
239
  def self.connection
239
- retrieve_connection
240
+ if allow_concurrency
241
+ retrieve_connection
242
+ else
243
+ @connection ||= retrieve_connection
244
+ end
240
245
  end
241
246
 
242
247
  # Returns the connection currently associated with the class. This can
@@ -251,6 +256,28 @@ module ActiveRecord #:nodoc:
251
256
  @@subclasses[self] << child
252
257
  super
253
258
  end
259
+
260
+ # Allow all subclasses of AR::Base to be reloaded in dev mode, unless they
261
+ # explicitly decline the honor. USE WITH CAUTION. Only AR subclasses kept
262
+ # in the framework should use the flag, so #reset_subclasses and so forth
263
+ # leave it alone.
264
+ def self.reloadable? #:nodoc:
265
+ true
266
+ end
267
+
268
+ def self.reset_subclasses
269
+ nonreloadables = []
270
+ subclasses.each do |klass|
271
+ unless klass.reloadable?
272
+ nonreloadables << klass
273
+ next
274
+ end
275
+ klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
276
+ klass.instance_methods(false).each { |m| klass.send :undef_method, m }
277
+ end
278
+ @@subclasses = {}
279
+ nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
280
+ end
254
281
 
255
282
  @@subclasses = {}
256
283
 
@@ -275,14 +302,14 @@ module ActiveRecord #:nodoc:
275
302
  cattr_accessor :table_name_suffix
276
303
  @@table_name_suffix = ""
277
304
 
278
- # Indicate whether or not table names should be the pluralized versions of the corresponding class names.
279
- # If true, this the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
305
+ # Indicates whether or not table names should be the pluralized versions of the corresponding class names.
306
+ # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
280
307
  # See table_name for the full rules on table/class naming. This is true, by default.
281
308
  cattr_accessor :pluralize_table_names
282
309
  @@pluralize_table_names = true
283
310
 
284
311
  # Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
285
- # makes it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
312
+ # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
286
313
  # may complicate matters if you use software like syslog. This is true, by default.
287
314
  cattr_accessor :colorize_logging
288
315
  @@colorize_logging = true
@@ -294,11 +321,27 @@ module ActiveRecord #:nodoc:
294
321
 
295
322
  # Determines whether or not to use a connection for each thread, or a single shared connection for all threads.
296
323
  # Defaults to true; Railties' WEBrick server sets this to false.
297
- cattr_accessor :threaded_connections
298
- @@threaded_connections = true
324
+ cattr_accessor :allow_concurrency
325
+ @@allow_concurrency = true
326
+
327
+ # Determines whether to speed up access by generating optimized reader
328
+ # methods to avoid expensive calls to method_missing when accessing
329
+ # attributes by name. You might want to set this to false in development
330
+ # mode, because the methods would be regenerated on each request.
331
+ cattr_accessor :generate_read_methods
332
+ @@generate_read_methods = true
333
+
334
+ # Specifies the format to use when dumping the database schema with Rails'
335
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
336
+ # specific) SQL statements. If :ruby, the schema is dumped as an
337
+ # ActiveRecord::Schema file which can be loaded into any database that
338
+ # supports migrations. Use :ruby if you want to have different database
339
+ # adapters for, e.g., your development and test environments.
340
+ cattr_accessor :schema_format
341
+ @@schema_format = :sql
299
342
 
300
343
  class << self # Class methods
301
- # Find operates with three different retreval approaches:
344
+ # Find operates with three different retrieval approaches:
302
345
  #
303
346
  # * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
304
347
  # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
@@ -306,15 +349,20 @@ module ActiveRecord #:nodoc:
306
349
  # conditions or merely an order. If no record can matched, nil is returned.
307
350
  # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
308
351
  #
309
- # All approaches accepts an option hash as their last parameter. The options are:
352
+ # All approaches accept an option hash as their last parameter. The options are:
310
353
  #
311
354
  # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
312
355
  # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
313
356
  # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
314
357
  # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
315
358
  # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
359
+ # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
360
+ # Use <tt>find_by_sql</tt> to circumvent this limitation.
316
361
  # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
317
362
  # to already defined associations. See eager loading under Associations.
363
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
364
+ # include the joined columns.
365
+ # * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
318
366
  #
319
367
  # Examples for find by id:
320
368
  # Person.find(1) # returns the object for ID = 1
@@ -336,15 +384,21 @@ module ActiveRecord #:nodoc:
336
384
  def find(*args)
337
385
  options = extract_options_from_args!(args)
338
386
 
387
+ # :joins implies :readonly => true
388
+ options[:readonly] = true if options[:joins]
389
+
339
390
  case args.first
340
391
  when :first
341
392
  find(:all, options.merge(options[:include] ? { } : { :limit => 1 })).first
342
393
  when :all
343
- options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
394
+ records = options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
395
+ records.each { |record| record.readonly! } if options[:readonly]
396
+ records
344
397
  else
345
398
  return args.first if args.first.kind_of?(Array) && args.first.empty?
346
399
  expects_array = args.first.kind_of?(Array)
347
- conditions = " AND #{sanitize_sql(options[:conditions])}" if options[:conditions]
400
+
401
+ conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
348
402
 
349
403
  ids = args.flatten.compact.uniq
350
404
  case ids.size
@@ -384,7 +438,7 @@ module ActiveRecord #:nodoc:
384
438
  end
385
439
 
386
440
  # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
387
- # fail under validations, the unsaved object is still returned.
441
+ # fails under validations, the unsaved object is still returned.
388
442
  def create(attributes = nil)
389
443
  if attributes.is_a?(Array)
390
444
  attributes.collect { |attr| create(attr) }
@@ -396,7 +450,7 @@ module ActiveRecord #:nodoc:
396
450
  end
397
451
 
398
452
  # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
399
- # and returns it. If the save fail under validations, the unsaved object is still returned.
453
+ # and returns it. If the save fails under validations, the unsaved object is still returned.
400
454
  def update(id, attributes)
401
455
  if id.is_a?(Array)
402
456
  idx = -1
@@ -420,7 +474,7 @@ module ActiveRecord #:nodoc:
420
474
  id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
421
475
  end
422
476
 
423
- # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updates.
477
+ # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updated.
424
478
  # A subset of the records can be selected by specifying +conditions+. Example:
425
479
  # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
426
480
  def update_all(updates, conditions = nil)
@@ -429,14 +483,14 @@ module ActiveRecord #:nodoc:
429
483
  connection.update(sql, "#{name} Update")
430
484
  end
431
485
 
432
- # Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling
486
+ # Destroys the objects for all the records that match the +condition+ by instantiating each object and calling
433
487
  # the destroy method. Example:
434
488
  # Person.destroy_all "last_login < '2004-04-04'"
435
489
  def destroy_all(conditions = nil)
436
490
  find(:all, :conditions => conditions).each { |object| object.destroy }
437
491
  end
438
492
 
439
- # Deletes all the records that matches the +condition+ without instantiating the objects first (and hence not
493
+ # Deletes all the records that match the +condition+ without instantiating the objects first (and hence not
440
494
  # calling the destroy method). Example:
441
495
  # Post.destroy_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
442
496
  def delete_all(conditions = nil)
@@ -445,7 +499,7 @@ module ActiveRecord #:nodoc:
445
499
  connection.delete(sql, "#{name} Delete all")
446
500
  end
447
501
 
448
- # Returns the number of records that meets the +conditions+. Zero is returned if no records match. Example:
502
+ # Returns the number of records that meet the +conditions+. Zero is returned if no records match. Example:
449
503
  # Product.count "sales > 1"
450
504
  def count(conditions = nil, joins = nil)
451
505
  sql = "SELECT COUNT(*) FROM #{table_name} "
@@ -455,21 +509,15 @@ module ActiveRecord #:nodoc:
455
509
  end
456
510
 
457
511
  # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
458
- # Product.count "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
512
+ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
459
513
  def count_by_sql(sql)
460
514
  sql = sanitize_conditions(sql)
461
- rows = connection.select_one(sql, "#{name} Count")
462
-
463
- if !rows.nil? and count = rows.values.first
464
- count.to_i
465
- else
466
- 0
467
- end
515
+ connection.select_value(sql, "#{name} Count").to_i
468
516
  end
469
517
 
470
518
  # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
471
519
  # discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
472
- # This is used for caching aggregate values, so that they doesn't need to be computed every time. Especially important
520
+ # This is used for caching aggregate values, so that they don't need to be computed every time. Especially important
473
521
  # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
474
522
  # that needs to list both the number of posts and comments.
475
523
  def increment_counter(counter_name, id)
@@ -483,7 +531,7 @@ module ActiveRecord #:nodoc:
483
531
 
484
532
  # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
485
533
  # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
486
- # methods to do assignment. This is meant to protect sensitive attributes to be overwritten by URL/form hackers. Example:
534
+ # methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
487
535
  #
488
536
  # class Customer < ActiveRecord::Base
489
537
  # attr_protected :credit_rating
@@ -497,7 +545,7 @@ module ActiveRecord #:nodoc:
497
545
  # customer.credit_rating = "Average"
498
546
  # customer.credit_rating # => "Average"
499
547
  def attr_protected(*attributes)
500
- write_inheritable_array("attr_protected", attributes)
548
+ write_inheritable_array("attr_protected", attributes - (protected_attributes || []))
501
549
  end
502
550
 
503
551
  # Returns an array of all the attributes that have been protected from mass-assignment.
@@ -505,12 +553,12 @@ module ActiveRecord #:nodoc:
505
553
  read_inheritable_attribute("attr_protected")
506
554
  end
507
555
 
508
- # If this macro is used, only those attributed named in it will be accessible for mass-assignment, such as
556
+ # If this macro is used, only those attributes named in it will be accessible for mass-assignment, such as
509
557
  # <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
510
558
  # protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
511
559
  # attr_protected.
512
560
  def attr_accessible(*attributes)
513
- write_inheritable_array("attr_accessible", attributes)
561
+ write_inheritable_array("attr_accessible", attributes - (accessible_attributes || []))
514
562
  end
515
563
 
516
564
  # Returns an array of all the attributes that have been made accessible to mass-assignment.
@@ -545,20 +593,31 @@ module ActiveRecord #:nodoc:
545
593
  # set_table_name "mice"
546
594
  # end
547
595
  def table_name
548
- "#{table_name_prefix}#{undecorated_table_name(class_name_of_active_record_descendant(self))}#{table_name_suffix}"
596
+ reset_table_name
597
+ end
598
+
599
+ def reset_table_name
600
+ name = "#{table_name_prefix}#{undecorated_table_name(class_name_of_active_record_descendant(self))}#{table_name_suffix}"
601
+ set_table_name name
602
+ name
549
603
  end
550
604
 
551
605
  # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
552
606
  # primary_key_prefix_type setting, though.
553
607
  def primary_key
608
+ reset_primary_key
609
+ end
610
+
611
+ def reset_primary_key
612
+ key = 'id'
554
613
  case primary_key_prefix_type
555
614
  when :table_name
556
- Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
615
+ key = Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
557
616
  when :table_name_with_underscore
558
- Inflector.foreign_key(class_name_of_active_record_descendant(self))
559
- else
560
- "id"
617
+ key = Inflector.foreign_key(class_name_of_active_record_descendant(self))
561
618
  end
619
+ set_primary_key(key)
620
+ key
562
621
  end
563
622
 
564
623
  # Defines the column name for use with single table inheritance -- can be overridden in subclasses.
@@ -566,6 +625,11 @@ module ActiveRecord #:nodoc:
566
625
  "type"
567
626
  end
568
627
 
628
+ # Default sequence_name. Use set_sequence_name to override.
629
+ def sequence_name
630
+ connection.default_sequence_name(table_name, primary_key)
631
+ end
632
+
569
633
  # Sets the table name to use to the given value, or (if the value
570
634
  # is nil or false) to the value returned by the given block.
571
635
  #
@@ -609,6 +673,25 @@ module ActiveRecord #:nodoc:
609
673
  end
610
674
  alias :inheritance_column= :set_inheritance_column
611
675
 
676
+ # Sets the name of the sequence to use when generating ids to the given
677
+ # value, or (if the value is nil or false) to the value returned by the
678
+ # given block. This is required for Oracle and is useful for any
679
+ # database which relies on sequences for primary key generation.
680
+ #
681
+ # Setting the sequence name when using other dbs will have no effect.
682
+ # If a sequence name is not explicitly set when using Oracle, it will
683
+ # default to the commonly used pattern of: #{table_name}_seq
684
+ #
685
+ # Example:
686
+ #
687
+ # class Project < ActiveRecord::Base
688
+ # set_sequence_name "projectseq" # default would have been "project_seq"
689
+ # end
690
+ def set_sequence_name( value=nil, &block )
691
+ define_attr_method :sequence_name, value, &block
692
+ end
693
+ alias :sequence_name= :set_sequence_name
694
+
612
695
  # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
613
696
  def class_name(table_name = table_name) # :nodoc:
614
697
  # remove any prefix and/or suffix from the table name
@@ -619,7 +702,11 @@ module ActiveRecord #:nodoc:
619
702
 
620
703
  # Returns an array of column objects for the table associated with this class.
621
704
  def columns
622
- @columns ||= connection.columns(table_name, "#{name} Columns")
705
+ unless @columns
706
+ @columns = connection.columns(table_name, "#{name} Columns")
707
+ @columns.each {|column| column.primary = column.name == primary_key}
708
+ end
709
+ @columns
623
710
  end
624
711
 
625
712
  # Returns an array of column objects for the table associated with this class.
@@ -631,10 +718,10 @@ module ActiveRecord #:nodoc:
631
718
  @column_names ||= columns.map { |column| column.name }
632
719
  end
633
720
 
634
- # Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
635
- # and columns used for single table inheritance has been removed.
721
+ # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
722
+ # and columns used for single table inheritance have been removed.
636
723
  def content_columns
637
- @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
724
+ @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
638
725
  end
639
726
 
640
727
  # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
@@ -650,9 +737,16 @@ module ActiveRecord #:nodoc:
650
737
  end
651
738
  end
652
739
 
653
- # Resets all the cached information about columns, which will cause they to be reloaded on the next request.
740
+
741
+ # Contains the names of the generated reader methods.
742
+ def read_methods
743
+ @read_methods ||= {}
744
+ end
745
+
746
+ # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
654
747
  def reset_column_information
655
- @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = nil
748
+ read_methods.each_key {|name| undef_method(name)}
749
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
656
750
  end
657
751
 
658
752
  def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -679,19 +773,28 @@ module ActiveRecord #:nodoc:
679
773
  connection.quote(object)
680
774
  end
681
775
 
682
- # Log and benchmark multiple statements in a single block.
683
- # Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
776
+ # Log and benchmark multiple statements in a single block. Example:
684
777
  #
685
778
  # Project.benchmark("Creating project") do
686
779
  # project = Project.create("name" => "stuff")
687
780
  # project.create_manager("name" => "David")
688
781
  # project.milestones << Milestone.find(:all)
689
782
  # end
690
- def benchmark(title)
691
- result = nil
692
- seconds = Benchmark.realtime { result = silence { yield } }
693
- logger.info "#{title} (#{sprintf("%f", seconds)})" if logger
694
- return result
783
+ #
784
+ # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
785
+ # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
786
+ # will only be conducted if the log level is low enough.
787
+ #
788
+ # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
789
+ def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
790
+ if logger && logger.level == log_level
791
+ result = nil
792
+ seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
793
+ logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
794
+ result
795
+ else
796
+ yield
797
+ end
695
798
  end
696
799
 
697
800
  # Silences the logger for the duration of the block.
@@ -701,48 +804,77 @@ module ActiveRecord #:nodoc:
701
804
  ensure
702
805
  logger.level = old_logger_level if logger
703
806
  end
807
+
808
+ # Add constraints to all queries to the same model in the given block.
809
+ # Currently supported constraints are <tt>:conditions</tt> and <tt>:joins</tt>
810
+ #
811
+ # Article.constrain(:conditions => "blog_id = 1") do
812
+ # Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
813
+ # end
814
+ def constrain(options = {}, &block)
815
+ begin
816
+ self.scope_constraints = options
817
+ block.call if block_given?
818
+ ensure
819
+ self.scope_constraints = nil
820
+ end
821
+ end
704
822
 
705
823
  # Overwrite the default class equality method to provide support for association proxies.
706
824
  def ===(object)
707
825
  object.is_a?(self)
826
+ end
827
+
828
+ # Deprecated
829
+ def threaded_connections
830
+ allow_concurrency
708
831
  end
709
832
 
833
+ # Deprecated
834
+ def threaded_connections=(value)
835
+ self.allow_concurrency = value
836
+ end
837
+
838
+
710
839
  private
711
840
  # Finder methods must instantiate through this method to work with the single-table inheritance model
712
841
  # that makes it possible to create objects of different types from the same table.
713
842
  def instantiate(record)
714
- subclass_name = record[inheritance_column]
715
- require_association_class(subclass_name)
716
-
717
- object = if subclass_name.blank?
718
- allocate
719
- else
720
- begin
721
- compute_type(subclass_name).allocate
722
- rescue NameError
723
- raise SubclassNotFound,
724
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
725
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
726
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
727
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
843
+ object =
844
+ if subclass_name = record[inheritance_column]
845
+ if subclass_name.empty?
846
+ allocate
847
+ else
848
+ require_association_class(subclass_name)
849
+ begin
850
+ compute_type(subclass_name).allocate
851
+ rescue NameError
852
+ raise SubclassNotFound,
853
+ "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
854
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
855
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
856
+ "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
857
+ end
858
+ end
859
+ else
860
+ allocate
728
861
  end
729
- end
730
862
 
731
863
  object.instance_variable_set("@attributes", record)
732
864
  object
733
865
  end
734
866
 
735
867
  # Returns the name of the type of the record using the current module as a prefix. So descendents of
736
- # MyApp::Business::Account would be appear as "MyApp::Business::AccountSubclass".
868
+ # MyApp::Business::Account would appear as "MyApp::Business::AccountSubclass".
737
869
  def type_name_with_module(type_name)
738
870
  self.name =~ /::/ ? self.name.scan(/(.*)::/).first.first + "::" + type_name : type_name
739
871
  end
740
872
 
741
873
  def construct_finder_sql(options)
742
- sql = "SELECT * FROM #{table_name} "
743
- sql << " #{options[:joins]} " if options[:joins]
874
+ sql = "SELECT #{options[:select] || '*'} FROM #{table_name} "
875
+ add_joins!(sql, options)
744
876
  add_conditions!(sql, options[:conditions])
745
- sql << "ORDER BY #{options[:order]} " if options[:order]
877
+ sql << " ORDER BY #{options[:order]} " if options[:order]
746
878
  add_limit!(sql, options)
747
879
  sql
748
880
  end
@@ -750,11 +882,19 @@ module ActiveRecord #:nodoc:
750
882
  def add_limit!(sql, options)
751
883
  connection.add_limit_offset!(sql, options)
752
884
  end
753
-
754
- # Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
755
- def add_conditions!(sql, conditions)
756
- sql << "WHERE #{sanitize_sql(conditions)} " unless conditions.nil?
757
- sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descends_from_active_record?
885
+
886
+ def add_joins!(sql, options)
887
+ join = scope_constraints[:joins] || options[:joins]
888
+ sql << " #{join} " if join
889
+ end
890
+
891
+ # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
892
+ def add_conditions!(sql, conditions)
893
+ condition_segments = [scope_constraints[:conditions]]
894
+ condition_segments << sanitize_sql(conditions) unless conditions.nil?
895
+ condition_segments << type_condition unless descends_from_active_record?
896
+ condition_segments.compact!
897
+ sql << "WHERE (#{condition_segments.join(") AND (")}) " unless condition_segments.empty?
758
898
  end
759
899
 
760
900
  def type_condition
@@ -787,7 +927,7 @@ module ActiveRecord #:nodoc:
787
927
  attributes.each { |attr_name| super unless column_methods_hash.include?(attr_name.to_sym) }
788
928
 
789
929
  attr_index = -1
790
- conditions = attributes.collect { |attr_name| attr_index += 1; "#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ")
930
+ conditions = attributes.collect { |attr_name| attr_index += 1; "#{table_name}.#{attr_name} #{attribute_condition(arguments[attr_index])} " }.join(" AND ")
791
931
 
792
932
  if arguments[attributes.length].is_a?(Hash)
793
933
  find(finder, { :conditions => [conditions, *arguments[0...attributes.length]] }.update(arguments[attributes.length]))
@@ -828,9 +968,14 @@ module ActiveRecord #:nodoc:
828
968
  # end
829
969
  def define_attr_method(name, value=nil, &block)
830
970
  sing = class << self; self; end
831
- block = proc { value.to_s } if value
832
- sing.send( :alias_method, "original_#{name}", name )
833
- sing.send( :define_method, name, &block )
971
+ sing.send :alias_method, "original_#{name}", name
972
+ if value
973
+ # use eval instead of a block to work around a memory leak in dev
974
+ # mode in fcgi
975
+ sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
976
+ else
977
+ sing.send :define_method, name, &block
978
+ end
834
979
  end
835
980
 
836
981
  protected
@@ -838,9 +983,31 @@ module ActiveRecord #:nodoc:
838
983
  @@subclasses[self] ||= []
839
984
  @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
840
985
  end
986
+
987
+ def scope_constraints
988
+ if allow_concurrency
989
+ Thread.current[:constraints] ||= {}
990
+ Thread.current[:constraints][self] ||= {}
991
+ else
992
+ @scope_constraints ||= {}
993
+ end
994
+ end
995
+ # backwards compatibility
996
+ alias_method :scope_constrains, :scope_constraints
841
997
 
998
+ def scope_constraints=(value)
999
+ if allow_concurrency
1000
+ Thread.current[:constraints] ||= {}
1001
+ Thread.current[:constraints][self] = value
1002
+ else
1003
+ @scope_constraints = value
1004
+ end
1005
+ end
1006
+ # backwards compatibility
1007
+ alias_method :scope_constrains=, :scope_constraints=
1008
+
842
1009
  # Returns the class type of the record using the current module as a prefix. So descendents of
843
- # MyApp::Business::Account would be appear as MyApp::Business::AccountSubclass.
1010
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
844
1011
  def compute_type(type_name)
845
1012
  type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
846
1013
  final_type.const_get(part)
@@ -909,7 +1076,13 @@ module ActiveRecord #:nodoc:
909
1076
  end
910
1077
 
911
1078
  def extract_options_from_args!(args)
912
- if args.last.is_a?(Hash) then args.pop else {} end
1079
+ options = args.last.is_a?(Hash) ? args.pop : {}
1080
+ validate_find_options(options)
1081
+ options
1082
+ end
1083
+
1084
+ def validate_find_options(options)
1085
+ options.assert_valid_keys [:conditions, :include, :joins, :limit, :offset, :order, :select, :readonly]
913
1086
  end
914
1087
 
915
1088
  def encode_quoted_value(value)
@@ -935,7 +1108,10 @@ module ActiveRecord #:nodoc:
935
1108
  # Every Active Record class must use "id" as their primary ID. This getter overwrites the native
936
1109
  # id method, which isn't being used in this context.
937
1110
  def id
938
- read_attribute(self.class.primary_key)
1111
+ attr_name = self.class.primary_key
1112
+ column = column_for_attribute(attr_name)
1113
+ define_read_method(:id, attr_name, column) if self.class.generate_read_methods
1114
+ (value = @attributes[attr_name]) && column.type_cast(value)
939
1115
  end
940
1116
 
941
1117
  # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
@@ -962,6 +1138,7 @@ module ActiveRecord #:nodoc:
962
1138
  # * No record exists: Creates a new record with values matching those of the object attributes.
963
1139
  # * A record does exist: Updates the record with values matching those of the object attributes.
964
1140
  def save
1141
+ raise ActiveRecord::ReadOnlyRecord if readonly?
965
1142
  create_or_update
966
1143
  end
967
1144
 
@@ -991,11 +1168,11 @@ module ActiveRecord #:nodoc:
991
1168
  # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
992
1169
  # doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
993
1170
  def update_attribute(name, value)
994
- self[name] = value
1171
+ send(name.to_s + '=', value)
995
1172
  save
996
1173
  end
997
1174
 
998
- # Updates all the attributes in from the passed hash and saves the record. If the object is invalid, the saving will
1175
+ # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
999
1176
  # fail and false will be returned.
1000
1177
  def update_attributes(attributes)
1001
1178
  self.attributes = attributes
@@ -1028,7 +1205,7 @@ module ActiveRecord #:nodoc:
1028
1205
 
1029
1206
  # Turns an +attribute+ that's currently true into false and vice versa. Returns self.
1030
1207
  def toggle(attribute)
1031
- self[attribute] = quote(!send("#{attribute}?", column_for_attribute(attribute)))
1208
+ self[attribute] = !send("#{attribute}?")
1032
1209
  self
1033
1210
  end
1034
1211
 
@@ -1044,17 +1221,17 @@ module ActiveRecord #:nodoc:
1044
1221
  self
1045
1222
  end
1046
1223
 
1047
- # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
1224
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
1048
1225
  # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
1049
1226
  # (Alias for the protected read_attribute method).
1050
1227
  def [](attr_name)
1051
- read_attribute(attr_name.to_s)
1228
+ read_attribute(attr_name)
1052
1229
  end
1053
1230
 
1054
1231
  # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
1055
1232
  # (Alias for the protected write_attribute method).
1056
1233
  def []=(attr_name, value)
1057
- write_attribute(attr_name.to_s, value)
1234
+ write_attribute(attr_name, value)
1058
1235
  end
1059
1236
 
1060
1237
  # Allows you to set all the attributes at once by passing in a hash with keys
@@ -1084,7 +1261,7 @@ module ActiveRecord #:nodoc:
1084
1261
  end
1085
1262
 
1086
1263
  # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
1087
- # nil nor empty? (the latter only applies to objects that responds to empty?, most notably Strings).
1264
+ # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
1088
1265
  def attribute_present?(attribute)
1089
1266
  value = read_attribute(attribute)
1090
1267
  !value.blank? or value == 0
@@ -1102,7 +1279,10 @@ module ActiveRecord #:nodoc:
1102
1279
 
1103
1280
  # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
1104
1281
  def ==(comparison_object)
1105
- comparison_object.equal?(self) or (comparison_object.instance_of?(self.class) and comparison_object.id == id)
1282
+ comparison_object.equal?(self) ||
1283
+ (comparison_object.instance_of?(self.class) &&
1284
+ comparison_object.id == id &&
1285
+ !comparison_object.new_record?)
1106
1286
  end
1107
1287
 
1108
1288
  # Delegates to ==
@@ -1127,20 +1307,27 @@ module ActiveRecord #:nodoc:
1127
1307
 
1128
1308
  # Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
1129
1309
  def freeze
1130
- @attributes.freeze
1310
+ @attributes.freeze; self
1131
1311
  end
1132
1312
 
1133
1313
  def frozen?
1134
1314
  @attributes.frozen?
1135
1315
  end
1136
1316
 
1317
+ def readonly?
1318
+ @readonly == true
1319
+ end
1320
+
1321
+ def readonly!
1322
+ @readonly = true
1323
+ end
1324
+
1137
1325
  private
1138
1326
  def create_or_update
1139
1327
  if new_record? then create else update end
1140
- true
1141
1328
  end
1142
1329
 
1143
- # Updates the associated record with values matching those of the instant attributes.
1330
+ # Updates the associated record with values matching those of the instance attributes.
1144
1331
  def update
1145
1332
  connection.update(
1146
1333
  "UPDATE #{self.class.table_name} " +
@@ -1150,20 +1337,20 @@ module ActiveRecord #:nodoc:
1150
1337
  )
1151
1338
  end
1152
1339
 
1153
- # Creates a new record with values matching those of the instant attributes.
1340
+ # Creates a new record with values matching those of the instance attributes.
1154
1341
  def create
1155
1342
  self.id = connection.insert(
1156
1343
  "INSERT INTO #{self.class.table_name} " +
1157
1344
  "(#{quoted_column_names.join(', ')}) " +
1158
1345
  "VALUES(#{attributes_with_quotes.values.join(', ')})",
1159
1346
  "#{self.class.name} Create",
1160
- self.class.primary_key, self.id
1347
+ self.class.primary_key, self.id, self.class.sequence_name
1161
1348
  )
1162
1349
 
1163
1350
  @new_record = false
1164
1351
  end
1165
1352
 
1166
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendant.
1353
+ # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
1167
1354
  # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
1168
1355
  # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
1169
1356
  # Message class in that example.
@@ -1184,7 +1371,10 @@ module ActiveRecord #:nodoc:
1184
1371
  def method_missing(method_id, *args, &block)
1185
1372
  method_name = method_id.to_s
1186
1373
  if @attributes.include?(method_name)
1374
+ define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
1187
1375
  read_attribute(method_name)
1376
+ elsif self.class.primary_key.to_s == method_name
1377
+ id
1188
1378
  elsif md = /(=|\?|_before_type_cast)$/.match(method_name)
1189
1379
  attribute_name, method_type = md.pre_match, md.to_s
1190
1380
  if @attributes.include?(attribute_name)
@@ -1204,9 +1394,10 @@ module ActiveRecord #:nodoc:
1204
1394
  end
1205
1395
  end
1206
1396
 
1207
- # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
1397
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
1208
1398
  # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
1209
1399
  def read_attribute(attr_name)
1400
+ attr_name = attr_name.to_s
1210
1401
  if !(value = @attributes[attr_name]).nil?
1211
1402
  if column = column_for_attribute(attr_name)
1212
1403
  if unserializable_attribute?(attr_name, column)
@@ -1226,11 +1417,36 @@ module ActiveRecord #:nodoc:
1226
1417
  @attributes[attr_name]
1227
1418
  end
1228
1419
 
1420
+ # Called on first read access to any given column and generates reader
1421
+ # methods for all columns in the columns_hash if
1422
+ # ActiveRecord::Base.generate_read_methods is set to true.
1423
+ def define_read_methods
1424
+ self.class.columns_hash.each do |name, column|
1425
+ unless column.primary || self.class.serialized_attributes[name] || respond_to_without_attributes?(name)
1426
+ define_read_method(name.to_sym, name, column)
1427
+ end
1428
+ end
1429
+ end
1430
+
1431
+ # Define a column type specific reader method.
1432
+ def define_read_method(symbol, attr_name, column)
1433
+ cast_code = column.type_cast_code('v')
1434
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
1435
+ body = access_code
1436
+
1437
+ # The following 3 lines behave exactly like method_missing if the
1438
+ # attribute isn't present.
1439
+ unless symbol == :id
1440
+ body = body.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ")
1441
+ end
1442
+ self.class.class_eval("def #{symbol}; #{body} end")
1443
+
1444
+ self.class.read_methods[attr_name] = true unless symbol == :id
1445
+ end
1446
+
1229
1447
  # Returns true if the attribute is of a text column and marked for serialization.
1230
1448
  def unserializable_attribute?(attr_name, column)
1231
- if value = @attributes[attr_name]
1232
- [:text, :string].include?(column.send(:type)) && value.is_a?(String) && self.class.serialized_attributes[attr_name]
1233
- end
1449
+ column.text? && self.class.serialized_attributes[attr_name]
1234
1450
  end
1235
1451
 
1236
1452
  # Returns the unserialized object of the attribute.
@@ -1248,12 +1464,21 @@ module ActiveRecord #:nodoc:
1248
1464
  # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
1249
1465
  # columns are turned into nil.
1250
1466
  def write_attribute(attr_name, value)
1251
- @attributes[attr_name.to_s] = empty_string_for_number_column?(attr_name.to_s, value) ? nil : value
1467
+ attr_name = attr_name.to_s
1468
+ if (column = column_for_attribute(attr_name)) && column.number?
1469
+ @attributes[attr_name] = convert_number_column_value(value)
1470
+ else
1471
+ @attributes[attr_name] = value
1472
+ end
1252
1473
  end
1253
1474
 
1254
- def empty_string_for_number_column?(attr_name, value)
1255
- column = column_for_attribute(attr_name)
1256
- column && (column.klass == Fixnum || column.klass == Float) && value == ""
1475
+ def convert_number_column_value(value)
1476
+ case value
1477
+ when FalseClass: 0
1478
+ when TrueClass: 1
1479
+ when '': nil
1480
+ else value
1481
+ end
1257
1482
  end
1258
1483
 
1259
1484
  def query_attribute(attr_name)
@@ -1289,7 +1514,9 @@ module ActiveRecord #:nodoc:
1289
1514
 
1290
1515
  # The primary key and inheritance column can never be set by mass-assignment for security reasons.
1291
1516
  def attributes_protected_by_default
1292
- [ self.class.primary_key, self.class.inheritance_column ]
1517
+ default = [ self.class.primary_key, self.class.inheritance_column ]
1518
+ default << 'id' unless self.class.primary_key.eql? 'id'
1519
+ default
1293
1520
  end
1294
1521
 
1295
1522
  # Returns copy of the attributes hash where all the values have been safely quoted for use in
@@ -1297,7 +1524,7 @@ module ActiveRecord #:nodoc:
1297
1524
  def attributes_with_quotes(include_primary_key = true)
1298
1525
  attributes.inject({}) do |quoted, (name, value)|
1299
1526
  if column = column_for_attribute(name)
1300
- quoted[name] = quote(value, column) unless !include_primary_key && name == self.class.primary_key
1527
+ quoted[name] = quote(value, column) unless !include_primary_key && column.primary
1301
1528
  end
1302
1529
  quoted
1303
1530
  end
@@ -1311,7 +1538,7 @@ module ActiveRecord #:nodoc:
1311
1538
  # Interpolate custom sql string in instance context.
1312
1539
  # Optional record argument is meant for custom insert_sql.
1313
1540
  def interpolate_sql(sql, record = nil)
1314
- instance_eval("%(#{sql})")
1541
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
1315
1542
  end
1316
1543
 
1317
1544
  # Initializes the attributes array with keys matching the columns from the linked table and
@@ -1319,7 +1546,7 @@ module ActiveRecord #:nodoc:
1319
1546
  # that a new instance, or one populated from a passed-in Hash, still has all the attributes
1320
1547
  # that instances loaded from the database would.
1321
1548
  def attributes_from_column_definition
1322
- connection.columns(self.class.table_name, "#{self.class.name} Columns").inject({}) do |attributes, column|
1549
+ self.class.columns.inject({}) do |attributes, column|
1323
1550
  attributes[column.name] = column.default unless column.name == self.class.primary_key
1324
1551
  attributes
1325
1552
  end
@@ -1406,20 +1633,7 @@ module ActiveRecord #:nodoc:
1406
1633
 
1407
1634
  def object_from_yaml(string)
1408
1635
  return string unless string.is_a?(String)
1409
- if has_yaml_encoding_header?(string)
1410
- begin
1411
- YAML::load(string)
1412
- rescue Object
1413
- # Apparently wasn't YAML anyway
1414
- string
1415
- end
1416
- else
1417
- string
1418
- end
1419
- end
1420
-
1421
- def has_yaml_encoding_header?(string)
1422
- string[0..3] == "--- "
1636
+ YAML::load(string) rescue string
1423
1637
  end
1424
1638
 
1425
1639
  def clone_attributes(reader_method = :read_attribute, attributes = {})