activerecord 3.0.0.beta → 3.0.0.beta2

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 (53) hide show
  1. data/CHANGELOG +8 -1
  2. data/lib/active_record.rb +9 -6
  3. data/lib/active_record/aggregations.rb +5 -0
  4. data/lib/active_record/association_preload.rb +7 -2
  5. data/lib/active_record/associations.rb +74 -54
  6. data/lib/active_record/associations/association_collection.rb +1 -0
  7. data/lib/active_record/associations/association_proxy.rb +2 -1
  8. data/lib/active_record/associations/has_many_association.rb +4 -0
  9. data/lib/active_record/associations/has_many_through_association.rb +1 -0
  10. data/lib/active_record/attribute_methods/dirty.rb +11 -9
  11. data/lib/active_record/attribute_methods/primary_key.rb +6 -0
  12. data/lib/active_record/attribute_methods/query.rb +2 -0
  13. data/lib/active_record/base.rb +57 -212
  14. data/lib/active_record/callbacks.rb +10 -0
  15. data/lib/active_record/connection_adapters/abstract/database_statements.rb +24 -1
  16. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -0
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -5
  18. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -0
  19. data/lib/active_record/connection_adapters/mysql_adapter.rb +22 -5
  20. data/lib/active_record/connection_adapters/postgresql_adapter.rb +34 -8
  21. data/lib/active_record/dynamic_finder_match.rb +3 -0
  22. data/lib/active_record/errors.rb +165 -0
  23. data/lib/active_record/fixtures.rb +1 -0
  24. data/lib/active_record/migration.rb +8 -6
  25. data/lib/active_record/named_scope.rb +14 -5
  26. data/lib/active_record/nested_attributes.rb +6 -2
  27. data/lib/active_record/query_cache.rb +2 -0
  28. data/lib/active_record/railtie.rb +30 -19
  29. data/lib/active_record/railties/databases.rake +13 -7
  30. data/lib/active_record/railties/{subscriber.rb → log_subscriber.rb} +7 -2
  31. data/lib/active_record/reflection.rb +5 -3
  32. data/lib/active_record/relation.rb +13 -2
  33. data/lib/active_record/relation/batches.rb +84 -0
  34. data/lib/active_record/relation/calculations.rb +2 -0
  35. data/lib/active_record/relation/finder_methods.rb +13 -2
  36. data/lib/active_record/relation/predicate_builder.rb +2 -7
  37. data/lib/active_record/relation/query_methods.rb +20 -27
  38. data/lib/active_record/relation/spawn_methods.rb +18 -28
  39. data/lib/active_record/schema.rb +2 -0
  40. data/lib/active_record/validations/uniqueness.rb +2 -4
  41. data/lib/active_record/version.rb +3 -2
  42. data/lib/{generators → rails/generators}/active_record.rb +0 -0
  43. data/lib/{generators → rails/generators}/active_record/migration/migration_generator.rb +1 -1
  44. data/lib/{generators → rails/generators}/active_record/migration/templates/migration.rb +0 -0
  45. data/lib/{generators → rails/generators}/active_record/model/model_generator.rb +1 -1
  46. data/lib/{generators → rails/generators}/active_record/model/templates/migration.rb +0 -0
  47. data/lib/{generators → rails/generators}/active_record/model/templates/model.rb +0 -0
  48. data/lib/{generators → rails/generators}/active_record/observer/observer_generator.rb +1 -1
  49. data/lib/{generators → rails/generators}/active_record/observer/templates/observer.rb +0 -0
  50. data/lib/{generators → rails/generators}/active_record/session_migration/session_migration_generator.rb +1 -1
  51. data/lib/{generators → rails/generators}/active_record/session_migration/templates/migration.rb +0 -0
  52. metadata +61 -34
  53. data/lib/active_record/batches.rb +0 -79
@@ -4,6 +4,7 @@ require 'csv'
4
4
  require 'zlib'
5
5
  require 'active_support/dependencies'
6
6
  require 'active_support/core_ext/logger'
7
+ require 'active_support/core_ext/object/blank'
7
8
 
8
9
  if RUBY_VERSION < '1.9'
9
10
  module YAML #:nodoc:
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/metaclass'
1
+ require 'active_support/core_ext/object/singleton_class'
2
2
 
3
3
  module ActiveRecord
4
4
  # Exception that can be raised to stop migrations from going backwards.
@@ -107,7 +107,7 @@ module ActiveRecord
107
107
  # The Rails package has several tools to help create and apply migrations.
108
108
  #
109
109
  # To generate a new migration, you can use
110
- # script/generate migration MyNewMigration
110
+ # rails generate migration MyNewMigration
111
111
  #
112
112
  # where MyNewMigration is the name of your migration. The generator will
113
113
  # create an empty migration file <tt>timestamp_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
@@ -117,7 +117,7 @@ module ActiveRecord
117
117
  # MyNewMigration.
118
118
  #
119
119
  # There is a special syntactic shortcut to generate migrations that add fields to a table.
120
- # script/generate migration add_fieldname_to_tablename fieldname:string
120
+ # rails generate migration add_fieldname_to_tablename fieldname:string
121
121
  #
122
122
  # This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
123
123
  # class AddFieldnameToTablename < ActiveRecord::Migration
@@ -303,7 +303,7 @@ module ActiveRecord
303
303
 
304
304
  case sym
305
305
  when :up, :down
306
- metaclass.send(:alias_method_chain, sym, "benchmarks")
306
+ singleton_class.send(:alias_method_chain, sym, "benchmarks")
307
307
  end
308
308
  ensure
309
309
  @ignore_new_methods = false
@@ -315,7 +315,9 @@ module ActiveRecord
315
315
  end
316
316
 
317
317
  def announce(message)
318
- text = "#{@version} #{name}: #{message}"
318
+ version = defined?(@version) ? @version : nil
319
+
320
+ text = "#{version} #{name}: #{message}"
319
321
  length = [0, 75 - text.length].max
320
322
  write "== %s %s" % [text, "=" * length]
321
323
  end
@@ -372,7 +374,7 @@ module ActiveRecord
372
374
  end
373
375
 
374
376
  def load_migration
375
- load(filename)
377
+ require(File.expand_path(filename))
376
378
  name.constantize
377
379
  end
378
380
 
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/array'
2
2
  require 'active_support/core_ext/hash/except'
3
- require 'active_support/core_ext/object/metaclass'
3
+ require 'active_support/core_ext/object/singleton_class'
4
+ require 'active_support/core_ext/object/blank'
4
5
 
5
6
  module ActiveRecord
6
7
  module NamedScope
@@ -26,7 +27,7 @@ module ActiveRecord
26
27
  if options.present?
27
28
  Scope.init(self, options, &block)
28
29
  else
29
- current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn
30
+ current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
30
31
  end
31
32
  end
32
33
 
@@ -101,7 +102,8 @@ module ActiveRecord
101
102
  name = name.to_sym
102
103
 
103
104
  if !scopes[name] && respond_to?(name, true)
104
- raise ArgumentError, "Cannot define scope :#{name} because #{self.name}.#{name} method already exists."
105
+ logger.warn "Creating scope :#{name}. " \
106
+ "Overwriting existing method #{self.name}.#{name}."
105
107
  end
106
108
 
107
109
  scopes[name] = lambda do |parent_scope, *args|
@@ -112,7 +114,7 @@ module ActiveRecord
112
114
  options.call(*args)
113
115
  end, &block)
114
116
  end
115
- metaclass.instance_eval do
117
+ singleton_class.instance_eval do
116
118
  define_method name do |*args|
117
119
  scopes[name].call(self, *args)
118
120
  end
@@ -165,7 +167,14 @@ module ActiveRecord
165
167
  end
166
168
 
167
169
  def ==(other)
168
- other.respond_to?(:to_ary) ? to_a == other.to_a : false
170
+ case other
171
+ when Scope
172
+ to_sql == other.to_sql
173
+ when Relation
174
+ other == self
175
+ when Array
176
+ to_a == other.to_a
177
+ end
169
178
  end
170
179
 
171
180
  private
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/hash/except'
2
2
  require 'active_support/core_ext/object/try'
3
+ require 'active_support/core_ext/object/blank'
3
4
 
4
5
  module ActiveRecord
5
6
  module NestedAttributes #:nodoc:
@@ -243,11 +244,14 @@ module ActiveRecord
243
244
  # def pirate_attributes=(attributes)
244
245
  # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
245
246
  # end
246
- class_eval %{
247
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
248
+ if method_defined?(:#{association_name}_attributes=)
249
+ remove_method(:#{association_name}_attributes=)
250
+ end
247
251
  def #{association_name}_attributes=(attributes)
248
252
  assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
249
253
  end
250
- }, __FILE__, __LINE__
254
+ eoruby
251
255
  else
252
256
  raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
253
257
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
1
3
  module ActiveRecord
2
4
  class QueryCache
3
5
  module ClassMethods
@@ -10,7 +10,7 @@ require "action_controller/railtie"
10
10
 
11
11
  module ActiveRecord
12
12
  class Railtie < Rails::Railtie
13
- railtie_name :active_record
13
+ config.active_record = ActiveSupport::OrderedOptions.new
14
14
 
15
15
  config.generators.orm :active_record, :migration => true,
16
16
  :timestamps => true
@@ -19,36 +19,43 @@ module ActiveRecord
19
19
  load "active_record/railties/databases.rake"
20
20
  end
21
21
 
22
- # TODO If we require the wrong file, the error never comes up.
23
- require "active_record/railties/subscriber"
24
- subscriber ActiveRecord::Railties::Subscriber.new
22
+ require "active_record/railties/log_subscriber"
23
+ log_subscriber :active_record, ActiveRecord::Railties::LogSubscriber.new
25
24
 
26
25
  initializer "active_record.initialize_timezone" do
27
- ActiveRecord::Base.time_zone_aware_attributes = true
28
- ActiveRecord::Base.default_timezone = :utc
26
+ ActiveSupport.on_load(:active_record) do
27
+ self.time_zone_aware_attributes = true
28
+ self.default_timezone = :utc
29
+ end
29
30
  end
30
31
 
31
32
  initializer "active_record.logger" do
32
- ActiveRecord::Base.logger ||= ::Rails.logger
33
+ ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
33
34
  end
34
35
 
35
36
  initializer "active_record.set_configs" do |app|
36
- app.config.active_record.each do |k,v|
37
- ActiveRecord::Base.send "#{k}=", v
37
+ ActiveSupport.on_load(:active_record) do
38
+ app.config.active_record.each do |k,v|
39
+ send "#{k}=", v
40
+ end
38
41
  end
39
42
  end
40
43
 
41
44
  # This sets the database configuration from Configuration#database_configuration
42
45
  # and then establishes the connection.
43
46
  initializer "active_record.initialize_database" do |app|
44
- ActiveRecord::Base.configurations = app.config.database_configuration
45
- ActiveRecord::Base.establish_connection
47
+ ActiveSupport.on_load(:active_record) do
48
+ self.configurations = app.config.database_configuration
49
+ establish_connection
50
+ end
46
51
  end
47
52
 
48
53
  # Expose database runtime to controller for logging.
49
54
  initializer "active_record.log_runtime" do |app|
50
55
  require "active_record/railties/controller_runtime"
51
- ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
56
+ ActiveSupport.on_load(:action_controller) do
57
+ include ActiveRecord::Railties::ControllerRuntime
58
+ end
52
59
  end
53
60
 
54
61
  # Setup database middleware after initializers have run
@@ -64,18 +71,22 @@ module ActiveRecord
64
71
  end
65
72
 
66
73
  initializer "active_record.load_observers" do
67
- ActiveRecord::Base.instantiate_observers
74
+ ActiveSupport.on_load(:active_record) { instantiate_observers }
68
75
 
69
- ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
70
- ActiveRecord::Base.instantiate_observers
76
+ ActiveSupport.on_load(:active_record) do
77
+ ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
78
+ ActiveRecord::Base.instantiate_observers
79
+ end
71
80
  end
72
81
  end
73
82
 
74
83
  initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
75
- unless app.config.cache_classes
76
- ActionDispatch::Callbacks.after do
77
- ActiveRecord::Base.reset_subclasses
78
- ActiveRecord::Base.clear_reloadable_connections!
84
+ ActiveSupport.on_load(:active_record) do
85
+ unless app.config.cache_classes
86
+ ActionDispatch::Callbacks.after do
87
+ ActiveRecord::Base.reset_subclasses
88
+ ActiveRecord::Base.clear_reloadable_connections!
89
+ end
79
90
  end
80
91
  end
81
92
  end
@@ -84,9 +84,15 @@ namespace :db do
84
84
  end
85
85
  end
86
86
  when 'postgresql'
87
- @encoding = config[:encoding] || ENV['CHARSET'] || 'utf8'
87
+ @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
88
+ schema_search_path = config['schema_search_path'] || 'public'
89
+ first_in_schema_search_path = schema_search_path.split(',').first.strip
88
90
  begin
89
91
  ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
92
+ unless ActiveRecord::Base.connection.all_schemas.include?(first_in_schema_search_path)
93
+ ActiveRecord::Base.connection.create_schema(first_in_schema_search_path, config['username'])
94
+ $stderr.puts "Schema #{first_in_schema_search_path} has been created."
95
+ end
90
96
  ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
91
97
  ActiveRecord::Base.establish_connection(config)
92
98
  rescue
@@ -109,7 +115,7 @@ namespace :db do
109
115
  # Only connect to local databases
110
116
  local_database?(config) { drop_database(config) }
111
117
  rescue Exception => e
112
- puts "Couldn't drop #{config['database']} : #{e.inspect}"
118
+ $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
113
119
  end
114
120
  end
115
121
  end
@@ -121,7 +127,7 @@ namespace :db do
121
127
  begin
122
128
  drop_database(config)
123
129
  rescue Exception => e
124
- puts "Couldn't drop #{config['database']} : #{e.inspect}"
130
+ $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
125
131
  end
126
132
  end
127
133
 
@@ -129,7 +135,7 @@ namespace :db do
129
135
  if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank?
130
136
  yield
131
137
  else
132
- puts "This task only modifies local databases. #{config['database']} is on a remote host."
138
+ $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
133
139
  end
134
140
  end
135
141
 
@@ -204,7 +210,7 @@ namespace :db do
204
210
  ActiveRecord::Base.establish_connection(config)
205
211
  puts ActiveRecord::Base.connection.encoding
206
212
  else
207
- puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
213
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
208
214
  end
209
215
  end
210
216
 
@@ -216,7 +222,7 @@ namespace :db do
216
222
  ActiveRecord::Base.establish_connection(config)
217
223
  puts ActiveRecord::Base.connection.collation
218
224
  else
219
- puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
225
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
220
226
  end
221
227
  end
222
228
 
@@ -435,7 +441,7 @@ namespace :db do
435
441
  task :create => :environment do
436
442
  raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations?
437
443
  require 'rails/generators'
438
- require 'generators/rails/session_migration/session_migration_generator'
444
+ require 'rails/generators/rails/session_migration/session_migration_generator'
439
445
  Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ]
440
446
  end
441
447
 
@@ -1,6 +1,11 @@
1
1
  module ActiveRecord
2
2
  module Railties
3
- class Subscriber < Rails::Subscriber
3
+ class LogSubscriber < Rails::LogSubscriber
4
+ def initialize
5
+ super
6
+ @odd_or_even = false
7
+ end
8
+
4
9
  def sql(event)
5
10
  name = '%s (%.1fms)' % [event.payload[:name], event.duration]
6
11
  sql = event.payload[:sql].squeeze(' ')
@@ -24,4 +29,4 @@ module ActiveRecord
24
29
  end
25
30
  end
26
31
  end
27
- end
32
+ end
@@ -154,6 +154,11 @@ module ActiveRecord
154
154
  @klass ||= active_record.send(:compute_type, class_name)
155
155
  end
156
156
 
157
+ def initialize(macro, name, options, active_record)
158
+ super
159
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
160
+ end
161
+
157
162
  # Returns a new, unsaved instance of the associated class. +options+ will
158
163
  # be passed to the class's constructor.
159
164
  def build_association(*options)
@@ -256,9 +261,6 @@ module ActiveRecord
256
261
  # association. Returns +true+ if the +macro+ is one of +has_many+ or
257
262
  # +has_and_belongs_to_many+, +false+ otherwise.
258
263
  def collection?
259
- if @collection.nil?
260
- @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
261
- end
262
264
  @collection
263
265
  end
264
266
 
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
1
3
  module ActiveRecord
2
4
  class Relation
3
5
  JoinOperation = Struct.new(:relation, :join_class, :on)
@@ -5,7 +7,7 @@ module ActiveRecord
5
7
  MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
6
8
  SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
7
9
 
8
- include FinderMethods, Calculations, SpawnMethods, QueryMethods
10
+ include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
9
11
 
10
12
  delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
11
13
  delegate :insert, :to => :arel
@@ -14,6 +16,11 @@ module ActiveRecord
14
16
 
15
17
  def initialize(klass, table)
16
18
  @klass, @table = klass, table
19
+
20
+ @implicit_readonly = nil
21
+ @loaded = nil
22
+
23
+ SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
17
24
  (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
18
25
  end
19
26
 
@@ -21,6 +28,10 @@ module ActiveRecord
21
28
  with_create_scope { @klass.new(*args, &block) }
22
29
  end
23
30
 
31
+ def initialize_copy(other)
32
+ reset
33
+ end
34
+
24
35
  alias build new
25
36
 
26
37
  def create(*args, &block)
@@ -50,7 +61,7 @@ module ActiveRecord
50
61
 
51
62
  preload = @preload_values
52
63
  preload += @includes_values unless eager_loading?
53
- preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
64
+ preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
54
65
 
55
66
  # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
56
67
  readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
@@ -0,0 +1,84 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ module Batches # :nodoc:
5
+ # Yields each record that was found by the find +options+. The find is
6
+ # performed by find_in_batches with a batch size of 1000 (or as
7
+ # specified by the <tt>:batch_size</tt> option).
8
+ #
9
+ # Example:
10
+ #
11
+ # Person.where("age > 21").find_each do |person|
12
+ # person.party_all_night!
13
+ # end
14
+ #
15
+ # Note: This method is only intended to use for batch processing of
16
+ # large amounts of records that wouldn't fit in memory all at once. If
17
+ # you just need to loop over less than 1000 records, it's probably
18
+ # better just to use the regular find methods.
19
+ def find_each(options = {})
20
+ find_in_batches(options) do |records|
21
+ records.each { |record| yield record }
22
+ end
23
+
24
+ self
25
+ end
26
+
27
+ # Yields each batch of records that was found by the find +options+ as
28
+ # an array. The size of each batch is set by the <tt>:batch_size</tt>
29
+ # option; the default is 1000.
30
+ #
31
+ # You can control the starting point for the batch processing by
32
+ # supplying the <tt>:start</tt> option. This is especially useful if you
33
+ # want multiple workers dealing with the same processing queue. You can
34
+ # make worker 1 handle all the records between id 0 and 10,000 and
35
+ # worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
36
+ # option on that worker).
37
+ #
38
+ # It's not possible to set the order. That is automatically set to
39
+ # ascending on the primary key ("id ASC") to make the batch ordering
40
+ # work. This also mean that this method only works with integer-based
41
+ # primary keys. You can't set the limit either, that's used to control
42
+ # the the batch sizes.
43
+ #
44
+ # Example:
45
+ #
46
+ # Person.where("age > 21").find_in_batches do |group|
47
+ # sleep(50) # Make sure it doesn't get too crowded in there!
48
+ # group.each { |person| person.party_all_night! }
49
+ # end
50
+ def find_in_batches(options = {})
51
+ relation = self
52
+
53
+ if orders.present? || taken.present?
54
+ ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
55
+ end
56
+
57
+ if (finder_options = options.except(:start, :batch_size)).present?
58
+ raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
59
+ raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
60
+
61
+ relation = apply_finder_options(finder_options)
62
+ end
63
+
64
+ start = options.delete(:start).to_i
65
+ batch_size = options.delete(:batch_size) || 1000
66
+
67
+ relation = relation.except(:order).order(batch_order).limit(batch_size)
68
+ records = relation.where(primary_key.gteq(start)).all
69
+
70
+ while records.any?
71
+ yield records
72
+
73
+ break if records.size < batch_size
74
+ records = relation.where(primary_key.gt(records.last.id)).all
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def batch_order
81
+ "#{@klass.table_name}.#{@klass.primary_key} ASC"
82
+ end
83
+ end
84
+ end