activerecord 3.1.1 → 3.1.2.rc1

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.

@@ -0,0 +1,20 @@
1
+ Copyright (c) 2004-2011 David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -150,7 +150,7 @@ A short rundown of some of the major features:
150
150
  * Database agnostic schema management with Migrations.
151
151
 
152
152
  class AddSystemSettings < ActiveRecord::Migration
153
- def self.up
153
+ def up
154
154
  create_table :system_settings do |t|
155
155
  t.string :name
156
156
  t.string :label
@@ -162,7 +162,7 @@ A short rundown of some of the major features:
162
162
  SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
163
163
  end
164
164
 
165
- def self.down
165
+ def down
166
166
  drop_table :system_settings
167
167
  end
168
168
  end
@@ -172,8 +172,8 @@ module ActiveRecord
172
172
  # with this option.
173
173
  # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
174
174
  # object. Each mapping is represented as an array where the first item is the name of the
175
- # entity attribute and the second item is the name the attribute in the value object. The
176
- # order in which mappings are defined determine the order in which attributes are sent to the
175
+ # entity attribute and the second item is the name of the attribute in the value object. The
176
+ # order in which mappings are defined determines the order in which attributes are sent to the
177
177
  # value class constructor.
178
178
  # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
179
179
  # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
@@ -191,7 +191,8 @@ module ActiveRecord
191
191
  #
192
192
  # Option examples:
193
193
  # composed_of :temperature, :mapping => %w(reading celsius)
194
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
194
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
195
+ # :converter => Proc.new { |balance| balance.to_money }
195
196
  # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
196
197
  # composed_of :gps_location
197
198
  # composed_of :gps_location, :allow_nil => true
@@ -1420,18 +1420,18 @@ module ActiveRecord
1420
1420
  # join table with a migration such as this:
1421
1421
  #
1422
1422
  # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
1423
- # def self.up
1423
+ # def change
1424
1424
  # create_table :developers_projects, :id => false do |t|
1425
1425
  # t.integer :developer_id
1426
1426
  # t.integer :project_id
1427
1427
  # end
1428
1428
  # end
1429
- #
1430
- # def self.down
1431
- # drop_table :developers_projects
1432
- # end
1433
1429
  # end
1434
1430
  #
1431
+ # It's also a good idea to add indexes to each of those columns to speed up the joins process.
1432
+ # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
1433
+ # uses one index per table during the lookup.
1434
+ #
1435
1435
  # Adds the following methods for retrieval and query:
1436
1436
  #
1437
1437
  # [collection(force_reload = false)]
@@ -354,8 +354,12 @@ module ActiveRecord
354
354
  if options[:counter_sql]
355
355
  interpolate(options[:counter_sql])
356
356
  else
357
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
358
- interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
357
+ # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
358
+ interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
359
+ count_with = $2.to_s
360
+ count_with = '*' if count_with.blank? || count_with =~ /,/
361
+ "SELECT #{$1}COUNT(#{count_with}) FROM"
362
+ end
359
363
  end
360
364
  end
361
365
 
@@ -6,6 +6,11 @@ module ActiveRecord
6
6
  class HasManyThroughAssociation < HasManyAssociation #:nodoc:
7
7
  include ThroughAssociation
8
8
 
9
+ def initialize(owner, reflection)
10
+ super
11
+ @through_records = {}
12
+ end
13
+
9
14
  # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
10
15
  # loaded and calling collection.size if it has. If it's more likely than not that the collection does
11
16
  # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
@@ -42,27 +47,36 @@ module ActiveRecord
42
47
  end
43
48
  end
44
49
 
45
- through_record(record).save!
50
+ save_through_record(record)
46
51
  update_counter(1)
47
52
  record
48
53
  end
49
54
 
50
55
  private
51
56
 
52
- def through_record(record)
53
- through_association = owner.association(through_reflection.name)
54
- attributes = construct_join_attributes(record)
55
-
56
- through_record = Array.wrap(through_association.target).find { |candidate|
57
- candidate.attributes.slice(*attributes.keys) == attributes
58
- }
57
+ def through_association
58
+ owner.association(through_reflection.name)
59
+ end
59
60
 
60
- unless through_record
61
- through_record = through_association.build(attributes)
61
+ # We temporarily cache through record that has been build, because if we build a
62
+ # through record in build_record and then subsequently call insert_record, then we
63
+ # want to use the exact same object.
64
+ #
65
+ # However, after insert_record has been called, we clear the cache entry because
66
+ # we want it to be possible to have multiple instances of the same record in an
67
+ # association
68
+ def build_through_record(record)
69
+ @through_records[record.object_id] ||= begin
70
+ through_record = through_association.build(construct_join_attributes(record))
62
71
  through_record.send("#{source_reflection.name}=", record)
72
+ through_record
63
73
  end
74
+ end
64
75
 
65
- through_record
76
+ def save_through_record(record)
77
+ build_through_record(record).save!
78
+ ensure
79
+ @through_records.delete(record.object_id)
66
80
  end
67
81
 
68
82
  def build_record(attributes, options = {})
@@ -73,9 +87,9 @@ module ActiveRecord
73
87
  inverse = source_reflection.inverse_of
74
88
  if inverse
75
89
  if inverse.macro == :has_many
76
- record.send(inverse.name) << through_record(record)
90
+ record.send(inverse.name) << build_through_record(record)
77
91
  elsif inverse.macro == :has_one
78
- record.send("#{inverse.name}=", through_record(record))
92
+ record.send("#{inverse.name}=", build_through_record(record))
79
93
  end
80
94
  end
81
95
 
@@ -104,7 +118,7 @@ module ActiveRecord
104
118
  def delete_records(records, method)
105
119
  ensure_not_nested
106
120
 
107
- through = owner.association(through_reflection.name)
121
+ through = through_association
108
122
  scope = through.scoped.where(construct_join_attributes(*records))
109
123
 
110
124
  case method
@@ -125,15 +139,23 @@ module ActiveRecord
125
139
  update_counter(-count)
126
140
  end
127
141
 
142
+ def through_records_for(record)
143
+ attributes = construct_join_attributes(record)
144
+ candidates = Array.wrap(through_association.target)
145
+ candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
146
+ end
147
+
128
148
  def delete_through_records(through, records)
129
- if through_reflection.macro == :has_many
130
- records.each do |record|
131
- through.target.delete(through_record(record))
132
- end
133
- else
134
- records.each do |record|
135
- through.target = nil if through.target == through_record(record)
149
+ records.each do |record|
150
+ through_records = through_records_for(record)
151
+
152
+ if through_reflection.macro == :has_many
153
+ through_records.each { |r| through.target.delete(r) }
154
+ else
155
+ through.target = nil if through_records.include?(through.target)
136
156
  end
157
+
158
+ @through_records.delete(record.object_id)
137
159
  end
138
160
  end
139
161
 
@@ -44,7 +44,7 @@ module ActiveRecord
44
44
  join_attributes = {
45
45
  source_reflection.foreign_key =>
46
46
  records.map { |record|
47
- record.send(source_reflection.association_primary_key)
47
+ record.send(source_reflection.association_primary_key(reflection.klass))
48
48
  }
49
49
  }
50
50
 
@@ -116,8 +116,8 @@ module ActiveRecord #:nodoc:
116
116
  # When joining tables, nested hashes or keys written in the form 'table_name.column_name'
117
117
  # can be used to qualify the table name of a particular condition. For instance:
118
118
  #
119
- # Student.joins(:schools).where(:schools => { :type => 'public' })
120
- # Student.joins(:schools).where('schools.type' => 'public' )
119
+ # Student.joins(:schools).where(:schools => { :category => 'public' })
120
+ # Student.joins(:schools).where('schools.category' => 'public' )
121
121
  #
122
122
  # == Overwriting default accessors
123
123
  #
@@ -756,7 +756,7 @@ module ActiveRecord #:nodoc:
756
756
  # values, eg:
757
757
  #
758
758
  # class CreateJobLevels < ActiveRecord::Migration
759
- # def self.up
759
+ # def up
760
760
  # create_table :job_levels do |t|
761
761
  # t.integer :id
762
762
  # t.string :name
@@ -770,7 +770,7 @@ module ActiveRecord #:nodoc:
770
770
  # end
771
771
  # end
772
772
  #
773
- # def self.down
773
+ # def down
774
774
  # drop_table :job_levels
775
775
  # end
776
776
  # end
@@ -314,7 +314,7 @@ module ActiveRecord
314
314
  end
315
315
 
316
316
  def current_connection_id #:nodoc:
317
- Thread.current.object_id
317
+ ActiveRecord::Base.connection_id ||= Thread.current.object_id
318
318
  end
319
319
 
320
320
  def checkout_new_connection
@@ -89,6 +89,14 @@ module ActiveRecord
89
89
  retrieve_connection
90
90
  end
91
91
 
92
+ def connection_id
93
+ Thread.current['ActiveRecord::Base.connection_id']
94
+ end
95
+
96
+ def connection_id=(connection_id)
97
+ Thread.current['ActiveRecord::Base.connection_id'] = connection_id
98
+ end
99
+
92
100
  # Returns the configuration of the associated connection as a hash:
93
101
  #
94
102
  # ActiveRecord::Base.connection_config
@@ -46,13 +46,13 @@ module ActiveRecord
46
46
  # +change_table+ is actually of this type:
47
47
  #
48
48
  # class SomeMigration < ActiveRecord::Migration
49
- # def self.up
49
+ # def up
50
50
  # create_table :foo do |t|
51
51
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
52
52
  # end
53
53
  # end
54
54
  #
55
- # def self.down
55
+ # def down
56
56
  # ...
57
57
  # end
58
58
  # end
@@ -479,4 +479,3 @@ module ActiveRecord
479
479
 
480
480
  end
481
481
  end
482
-
@@ -573,9 +573,18 @@ module ActiveRecord
573
573
  # Returns a table's primary key and belonging sequence.
574
574
  def pk_and_sequence_for(table)
575
575
  keys = []
576
- result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA')
576
+ sql = <<-SQL
577
+ SELECT t.constraint_type, k.column_name
578
+ FROM information_schema.table_constraints t
579
+ JOIN information_schema.key_column_usage k
580
+ USING (constraint_name, table_schema, table_name)
581
+ WHERE t.table_schema = DATABASE()
582
+ AND t.table_name = '#{table}'
583
+ SQL
584
+
585
+ result = execute(sql, 'SCHEMA')
577
586
  result.each(:symbolize_keys => true, :as => :hash) do |row|
578
- keys << row[:Field] if row[:Key] == "PRI"
587
+ keys << row[:column_name] if row[:constraint_type] == "PRIMARY KEY"
579
588
  end
580
589
  keys.length == 1 ? [keys.first, nil] : nil
581
590
  end
@@ -756,9 +756,18 @@ module ActiveRecord
756
756
  # Returns a table's primary key and belonging sequence.
757
757
  def pk_and_sequence_for(table) #:nodoc:
758
758
  keys = []
759
- result = execute("describe #{quote_table_name(table)}", 'SCHEMA')
759
+ sql = <<-SQL
760
+ SELECT t.constraint_type, k.column_name
761
+ FROM information_schema.table_constraints t
762
+ JOIN information_schema.key_column_usage k
763
+ USING (constraint_name, table_schema, table_name)
764
+ WHERE t.table_schema = DATABASE()
765
+ AND t.table_name = '#{table}'
766
+ SQL
767
+
768
+ result = execute(sql, 'SCHEMA')
760
769
  result.each_hash do |h|
761
- keys << h["Field"]if h["Key"] == "PRI"
770
+ keys << h["column_name"] if h["constraint_type"] == "PRIMARY KEY"
762
771
  end
763
772
  result.free
764
773
  keys.length == 1 ? [keys.first, nil] : nil
@@ -279,13 +279,24 @@ module ActiveRecord
279
279
  cache.clear
280
280
  end
281
281
 
282
+ def delete(sql_key)
283
+ dealloc cache[sql_key]
284
+ cache.delete sql_key
285
+ end
286
+
282
287
  private
283
288
  def cache
284
289
  @cache[$$]
285
290
  end
286
291
 
287
292
  def dealloc(key)
288
- @connection.query "DEALLOCATE #{key}"
293
+ @connection.query "DEALLOCATE #{key}" if connection_active?
294
+ end
295
+
296
+ def connection_active?
297
+ @connection.status == PGconn::CONNECTION_OK
298
+ rescue PGError
299
+ false
289
300
  end
290
301
  end
291
302
 
@@ -743,7 +754,6 @@ module ActiveRecord
743
754
 
744
755
  # Returns an array of indexes for the given table.
745
756
  def indexes(table_name, name = nil)
746
- schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
747
757
  result = query(<<-SQL, name)
748
758
  SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
749
759
  FROM pg_class t
@@ -752,7 +762,7 @@ module ActiveRecord
752
762
  WHERE i.relkind = 'i'
753
763
  AND d.indisprimary = 'f'
754
764
  AND t.relname = '#{table_name}'
755
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
765
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
756
766
  ORDER BY i.relname
757
767
  SQL
758
768
 
@@ -899,12 +909,14 @@ module ActiveRecord
899
909
  # Example:
900
910
  # rename_table('octopuses', 'octopi')
901
911
  def rename_table(name, new_name)
912
+ clear_cache!
902
913
  execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
903
914
  end
904
915
 
905
916
  # Adds a new column to the named table.
906
917
  # See TableDefinition#column for details of the options you can use.
907
918
  def add_column(table_name, column_name, type, options = {})
919
+ clear_cache!
908
920
  add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
909
921
  add_column_options!(add_column_sql, options)
910
922
 
@@ -913,6 +925,7 @@ module ActiveRecord
913
925
 
914
926
  # Changes the column of a table.
915
927
  def change_column(table_name, column_name, type, options = {})
928
+ clear_cache!
916
929
  quoted_table_name = quote_table_name(table_name)
917
930
 
918
931
  execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
@@ -923,10 +936,12 @@ module ActiveRecord
923
936
 
924
937
  # Changes the default value of a table column.
925
938
  def change_column_default(table_name, column_name, default)
939
+ clear_cache!
926
940
  execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
927
941
  end
928
942
 
929
943
  def change_column_null(table_name, column_name, null, default = nil)
944
+ clear_cache!
930
945
  unless null || default.nil?
931
946
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
932
947
  end
@@ -935,6 +950,7 @@ module ActiveRecord
935
950
 
936
951
  # Renames a column in a table.
937
952
  def rename_column(table_name, column_name, new_column_name)
953
+ clear_cache!
938
954
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
939
955
  end
940
956
 
@@ -999,27 +1015,55 @@ module ActiveRecord
999
1015
  end
1000
1016
 
1001
1017
  private
1002
- def exec_no_cache(sql, binds)
1003
- @connection.async_exec(sql)
1004
- end
1018
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
1005
1019
 
1006
- def exec_cache(sql, binds)
1007
- unless @statements.key? sql
1008
- nextkey = @statements.next_key
1009
- @connection.prepare nextkey, sql
1010
- @statements[sql] = nextkey
1020
+ def exec_no_cache(sql, binds)
1021
+ @connection.async_exec(sql)
1022
+ end
1023
+
1024
+ def exec_cache(sql, binds)
1025
+ begin
1026
+ stmt_key = prepare_statement sql
1027
+
1028
+ # Clear the queue
1029
+ @connection.get_last_result
1030
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
1031
+ type_cast(val, col)
1032
+ })
1033
+ @connection.block
1034
+ @connection.get_last_result
1035
+ rescue PGError => e
1036
+ # Get the PG code for the failure. Annoyingly, the code for
1037
+ # prepared statements whose return value may have changed is
1038
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
1039
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
1040
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
1041
+ if FEATURE_NOT_SUPPORTED == code
1042
+ @statements.delete sql_key(sql)
1043
+ retry
1044
+ else
1045
+ raise e
1046
+ end
1047
+ end
1011
1048
  end
1012
1049
 
1013
- key = @statements[sql]
1050
+ # Returns the statement identifier for the client side cache
1051
+ # of statements
1052
+ def sql_key(sql)
1053
+ "#{schema_search_path}-#{sql}"
1054
+ end
1014
1055
 
1015
- # Clear the queue
1016
- @connection.get_last_result
1017
- @connection.send_query_prepared(key, binds.map { |col, val|
1018
- type_cast(val, col)
1019
- })
1020
- @connection.block
1021
- @connection.get_last_result
1022
- end
1056
+ # Prepare the statement if it hasn't been prepared, return
1057
+ # the statement key.
1058
+ def prepare_statement(sql)
1059
+ sql_key = sql_key(sql)
1060
+ unless @statements.key? sql_key
1061
+ nextkey = @statements.next_key
1062
+ @connection.prepare nextkey, sql
1063
+ @statements[sql_key] = nextkey
1064
+ end
1065
+ @statements[sql_key]
1066
+ end
1023
1067
 
1024
1068
  # The internal PostgreSQL identifier of the money data type.
1025
1069
  MONEY_COLUMN_TYPE_OID = 790 #:nodoc: