activerecord 3.0.0.beta2 → 3.0.0.beta3

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 (28) hide show
  1. data/CHANGELOG +11 -1
  2. data/lib/active_record/associations.rb +26 -54
  3. data/lib/active_record/associations/association_collection.rb +13 -2
  4. data/lib/active_record/associations/association_proxy.rb +3 -1
  5. data/lib/active_record/associations/through_association_scope.rb +1 -1
  6. data/lib/active_record/attribute_methods/dirty.rb +6 -0
  7. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  8. data/lib/active_record/base.rb +66 -62
  9. data/lib/active_record/callbacks.rb +4 -2
  10. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -3
  11. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -3
  12. data/lib/active_record/connection_adapters/abstract_adapter.rb +1 -0
  13. data/lib/active_record/connection_adapters/postgresql_adapter.rb +45 -45
  14. data/lib/active_record/migration.rb +1 -1
  15. data/lib/active_record/named_scope.rb +26 -109
  16. data/lib/active_record/railties/databases.rake +3 -7
  17. data/lib/active_record/reflection.rb +11 -0
  18. data/lib/active_record/relation.rb +20 -1
  19. data/lib/active_record/relation/finder_methods.rb +18 -2
  20. data/lib/active_record/relation/predicate_builder.rb +4 -2
  21. data/lib/active_record/relation/query_methods.rb +26 -8
  22. data/lib/active_record/relation/spawn_methods.rb +9 -6
  23. data/lib/active_record/serializers/xml_serializer.rb +2 -1
  24. data/lib/active_record/validations/uniqueness.rb +3 -1
  25. data/lib/active_record/version.rb +1 -1
  26. data/lib/rails/generators/active_record/model/model_generator.rb +5 -0
  27. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  28. metadata +8 -7
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
1
3
  module ActiveRecord
2
4
  # Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
3
5
  # before or after an alteration of the object state. This can be used to make sure that associated and
@@ -250,7 +252,7 @@ module ActiveRecord
250
252
  def before_validation(*args, &block)
251
253
  options = args.last
252
254
  if options.is_a?(Hash) && options[:on]
253
- options[:if] = Array(options[:if])
255
+ options[:if] = Array.wrap(options[:if])
254
256
  options[:if] << "@_on_validate == :#{options[:on]}"
255
257
  end
256
258
  set_callback(:validation, :before, *args, &block)
@@ -259,7 +261,7 @@ module ActiveRecord
259
261
  def after_validation(*args, &block)
260
262
  options = args.extract_options!
261
263
  options[:prepend] = true
262
- options[:if] = Array(options[:if])
264
+ options[:if] = Array.wrap(options[:if])
263
265
  options[:if] << "!halted && value != false"
264
266
  options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
265
267
  set_callback(:validation, :after, *(args << options), &block)
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
 
16
16
  def dirties_query_cache(base, *method_names)
17
17
  method_names.each do |method_name|
18
- base.class_eval <<-end_code, __FILE__, __LINE__
18
+ base.class_eval <<-end_code, __FILE__, __LINE__ + 1
19
19
  def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
20
20
  clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
21
21
  #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
@@ -32,7 +32,6 @@ module ActiveRecord
32
32
  # Enable the query cache within the block.
33
33
  def cache
34
34
  old, @query_cache_enabled = @query_cache_enabled, true
35
- @query_cache ||= {}
36
35
  yield
37
36
  ensure
38
37
  clear_query_cache
@@ -54,7 +53,7 @@ module ActiveRecord
54
53
  # the same SQL query and repeatedly return the same result each time, silently
55
54
  # undermining the randomness you were expecting.
56
55
  def clear_query_cache
57
- @query_cache.clear if @query_cache
56
+ @query_cache.clear
58
57
  end
59
58
 
60
59
  def select_all_with_query_cache(*args)
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters # :nodoc:
3
5
  module SchemaStatements
@@ -267,7 +269,7 @@ module ActiveRecord
267
269
  # generates
268
270
  # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
269
271
  def add_index(table_name, column_name, options = {})
270
- column_names = Array(column_name)
272
+ column_names = Array.wrap(column_name)
271
273
  index_name = index_name(table_name, :column => column_names)
272
274
 
273
275
  if Hash === options # legacy support, since this param was a string
@@ -291,13 +293,13 @@ module ActiveRecord
291
293
  # Remove the index named by_branch_party in the accounts table.
292
294
  # remove_index :accounts, :name => :by_branch_party
293
295
  def remove_index(table_name, options = {})
294
- execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{table_name}"
296
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{quote_table_name(table_name)}"
295
297
  end
296
298
 
297
299
  def index_name(table_name, options) #:nodoc:
298
300
  if Hash === options # legacy support
299
301
  if options[:column]
300
- "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
302
+ "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
301
303
  elsif options[:name]
302
304
  options[:name]
303
305
  else
@@ -41,6 +41,7 @@ module ActiveRecord
41
41
  @connection, @logger = connection, logger
42
42
  @runtime = 0
43
43
  @query_cache_enabled = false
44
+ @query_cache = {}
44
45
  end
45
46
 
46
47
  # Returns the human-readable name of the adapter. Use mixed case - one
@@ -54,6 +54,12 @@ module ActiveRecord
54
54
  super(name, self.class.extract_value_from_default(default), sql_type, null)
55
55
  end
56
56
 
57
+ # :stopdoc:
58
+ class << self
59
+ attr_accessor :money_precision
60
+ end
61
+ # :startdoc:
62
+
57
63
  private
58
64
  def extract_limit(sql_type)
59
65
  case sql_type
@@ -71,9 +77,11 @@ module ActiveRecord
71
77
 
72
78
  # Extracts the precision from PostgreSQL-specific data types.
73
79
  def extract_precision(sql_type)
74
- # Actual code is defined dynamically in PostgreSQLAdapter.connect
75
- # depending on the server specifics
76
- super
80
+ if sql_type == 'money'
81
+ self.class.money_precision
82
+ else
83
+ super
84
+ end
77
85
  end
78
86
 
79
87
  # Maps PostgreSQL-specific data types to logical Rails types.
@@ -83,18 +91,18 @@ module ActiveRecord
83
91
  when /^(?:real|double precision)$/
84
92
  :float
85
93
  # Monetary types
86
- when /^money$/
94
+ when 'money'
87
95
  :decimal
88
96
  # Character types
89
97
  when /^(?:character varying|bpchar)(?:\(\d+\))?$/
90
98
  :string
91
99
  # Binary data types
92
- when /^bytea$/
100
+ when 'bytea'
93
101
  :binary
94
102
  # Date/time types
95
103
  when /^timestamp with(?:out)? time zone$/
96
104
  :datetime
97
- when /^interval$/
105
+ when 'interval'
98
106
  :string
99
107
  # Geometric types
100
108
  when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
@@ -106,16 +114,16 @@ module ActiveRecord
106
114
  when /^bit(?: varying)?(?:\(\d+\))?$/
107
115
  :string
108
116
  # XML type
109
- when /^xml$/
117
+ when 'xml'
110
118
  :xml
111
119
  # Arrays
112
120
  when /^\D+\[\]$/
113
121
  :string
114
122
  # Object identifier types
115
- when /^oid$/
123
+ when 'oid'
116
124
  :integer
117
125
  # UUID type
118
- when /^uuid$/
126
+ when 'uuid'
119
127
  :string
120
128
  # Small and big integer types
121
129
  when /^(?:small|big)int$/
@@ -383,9 +391,9 @@ module ActiveRecord
383
391
  def quote(value, column = nil) #:nodoc:
384
392
  if value.kind_of?(String) && column && column.type == :binary
385
393
  "#{quoted_string_prefix}'#{escape_bytea(value)}'"
386
- elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
394
+ elsif value.kind_of?(String) && column && column.sql_type == 'xml'
387
395
  "xml E'#{quote_string(value)}'"
388
- elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
396
+ elsif value.kind_of?(Numeric) && column && column.sql_type == 'money'
389
397
  # Not truly string input, so doesn't require (or allow) escape string syntax.
390
398
  "'#{value.to_s}'"
391
399
  elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
@@ -658,33 +666,34 @@ module ActiveRecord
658
666
  end
659
667
  end
660
668
 
661
- # Creates a schema for the given user
662
- #
663
- # Example:
664
- # create_schema('products', 'postgres')
665
- def create_schema(schema_name, pg_username)
666
- execute("CREATE SCHEMA \"#{schema_name}\" AUTHORIZATION \"#{pg_username}\"")
667
- end
668
-
669
- # Drops a schema
670
- #
671
- # Example:
672
- # drop_schema('products')
673
- def drop_schema(schema_name)
674
- execute("DROP SCHEMA \"#{schema_name}\"")
675
- end
676
-
677
- # Returns an array of all schemas in the database
678
- def all_schemas
679
- query('SELECT schema_name FROM information_schema.schemata').flatten
680
- end
681
-
682
669
  # Returns the list of all tables in the schema search path or a specified schema.
683
670
  def tables(name = nil)
684
671
  query(<<-SQL, name).map { |row| row[0] }
685
672
  SELECT tablename
673
+ FROM pg_tables
674
+ WHERE schemaname = ANY (current_schemas(false))
675
+ SQL
676
+ end
677
+
678
+ def table_exists?(name)
679
+ name = name.to_s
680
+ schema, table = name.split('.', 2)
681
+
682
+ unless table # A table was provided without a schema
683
+ table = schema
684
+ schema = nil
685
+ end
686
+
687
+ if name =~ /^"/ # Handle quoted table names
688
+ table = name
689
+ schema = nil
690
+ end
691
+
692
+ query(<<-SQL).first[0].to_i > 0
693
+ SELECT COUNT(*)
686
694
  FROM pg_tables
687
- WHERE schemaname = ANY (current_schemas(false))
695
+ WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}'
696
+ #{schema ? "AND schemaname = '#{schema}'" : ''}
688
697
  SQL
689
698
  end
690
699
 
@@ -946,7 +955,7 @@ module ActiveRecord
946
955
  # Construct a clean list of column names from the ORDER BY clause, removing
947
956
  # any ASC/DESC modifiers
948
957
  order_columns = order_by.split(',').collect { |s| s.split.first }
949
- order_columns.delete_if &:blank?
958
+ order_columns.delete_if(&:blank?)
950
959
  order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
951
960
 
952
961
  # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
@@ -1010,17 +1019,8 @@ module ActiveRecord
1010
1019
  # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
1011
1020
  # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
1012
1021
  # should know about this but can't detect it there, so deal with it here.
1013
- money_precision = (postgresql_version >= 80300) ? 19 : 10
1014
- PostgreSQLColumn.module_eval(<<-end_eval)
1015
- def extract_precision(sql_type) # def extract_precision(sql_type)
1016
- if sql_type =~ /^money$/ # if sql_type =~ /^money$/
1017
- #{money_precision} # 19
1018
- else # else
1019
- super # super
1020
- end # end
1021
- end # end
1022
- end_eval
1023
-
1022
+ PostgreSQLColumn.money_precision =
1023
+ (postgresql_version >= 80300) ? 19 : 10
1024
1024
  configure_connection
1025
1025
  end
1026
1026
 
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/singleton_class'
1
+ require 'active_support/core_ext/kernel/singleton_class'
2
2
 
3
3
  module ActiveRecord
4
4
  # Exception that can be raised to stop migrations from going backwards.
@@ -1,6 +1,6 @@
1
1
  require 'active_support/core_ext/array'
2
2
  require 'active_support/core_ext/hash/except'
3
- require 'active_support/core_ext/object/singleton_class'
3
+ require 'active_support/core_ext/kernel/singleton_class'
4
4
  require 'active_support/core_ext/object/blank'
5
5
 
6
6
  module ActiveRecord
@@ -8,16 +8,15 @@ module ActiveRecord
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  module ClassMethods
11
- # Returns a relation if invoked without any arguments.
11
+ # Returns an anonymous scope.
12
12
  #
13
13
  # posts = Post.scoped
14
14
  # posts.size # Fires "select count(*) from posts" and returns the count
15
15
  # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
16
16
  #
17
- # Returns an anonymous named scope if any options are supplied.
18
- #
19
- # shirts = Shirt.scoped(:conditions => {:color => 'red'})
20
- # shirts = shirts.scoped(:include => :washing_instructions)
17
+ # fruits = Fruit.scoped
18
+ # fruits = fruits.where(:colour => 'red') if options[:red_only]
19
+ # fruits = fruits.limit(10) if limited?
21
20
  #
22
21
  # Anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
23
22
  # intermediate values (scopes) around as first-class objects is convenient.
@@ -25,7 +24,8 @@ module ActiveRecord
25
24
  # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
26
25
  def scoped(options = {}, &block)
27
26
  if options.present?
28
- Scope.init(self, options, &block)
27
+ relation = scoped.apply_finder_options(options)
28
+ block_given? ? relation.extending(Module.new(&block)) : relation
29
29
  else
30
30
  current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
31
31
  end
@@ -36,21 +36,21 @@ module ActiveRecord
36
36
  end
37
37
 
38
38
  # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
39
- # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
39
+ # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
40
40
  #
41
41
  # class Shirt < ActiveRecord::Base
42
- # scope :red, :conditions => {:color => 'red'}
43
- # scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
42
+ # scope :red, where(:color => 'red')
43
+ # scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
44
44
  # end
45
45
  #
46
46
  # The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
47
- # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
47
+ # in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
48
48
  #
49
49
  # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it resembles the association object
50
- # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
51
- # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
52
- # as with the association objects, named \scopes act like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
53
- # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really was an Array.
50
+ # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
51
+ # <tt>Shirt.red.where(:size => 'small')</tt>. Also, just as with the association objects, named \scopes act like an Array,
52
+ # implementing Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
53
+ # all behave as if Shirt.red really was an Array.
54
54
  #
55
55
  # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
56
56
  # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
@@ -69,9 +69,7 @@ module ActiveRecord
69
69
  # Named \scopes can also be procedural:
70
70
  #
71
71
  # class Shirt < ActiveRecord::Base
72
- # scope :colored, lambda { |color|
73
- # { :conditions => { :color => color } }
74
- # }
72
+ # scope :colored, lambda {|color| where(:color => color) }
75
73
  # end
76
74
  #
77
75
  # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
@@ -79,26 +77,13 @@ module ActiveRecord
79
77
  # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
80
78
  #
81
79
  # class Shirt < ActiveRecord::Base
82
- # scope :red, :conditions => {:color => 'red'} do
80
+ # scope :red, where(:color => 'red') do
83
81
  # def dom_id
84
82
  # 'red_shirts'
85
83
  # end
86
84
  # end
87
85
  # end
88
- #
89
- #
90
- # For testing complex named \scopes, you can examine the scoping options using the
91
- # <tt>proxy_options</tt> method on the proxy itself.
92
- #
93
- # class Shirt < ActiveRecord::Base
94
- # scope :colored, lambda { |color|
95
- # { :conditions => { :color => color } }
96
- # }
97
- # end
98
- #
99
- # expected_options = { :conditions => { :colored => 'red' } }
100
- # assert_equal expected_options, Shirt.colored('red').proxy_options
101
- def scope(name, options = {}, &block)
86
+ def scope(name, scope_options = {}, &block)
102
87
  name = name.to_sym
103
88
 
104
89
  if !scopes[name] && respond_to?(name, true)
@@ -106,17 +91,17 @@ module ActiveRecord
106
91
  "Overwriting existing method #{self.name}.#{name}."
107
92
  end
108
93
 
109
- scopes[name] = lambda do |parent_scope, *args|
110
- Scope.init(parent_scope, case options
111
- when Hash, Relation
112
- options
113
- when Proc
114
- options.call(*args)
115
- end, &block)
94
+ scopes[name] = lambda do |*args|
95
+ options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
96
+
97
+ relation = scoped
98
+ relation = options.is_a?(Hash) ? relation.apply_finder_options(options) : scoped.merge(options) if options
99
+ block_given? ? relation.extending(Module.new(&block)) : relation
116
100
  end
101
+
117
102
  singleton_class.instance_eval do
118
103
  define_method name do |*args|
119
- scopes[name].call(self, *args)
104
+ scopes[name].call(*args)
120
105
  end
121
106
  end
122
107
  end
@@ -127,73 +112,5 @@ module ActiveRecord
127
112
  end
128
113
  end
129
114
 
130
- class Scope < Relation
131
- attr_accessor :current_scoped_methods_when_defined
132
-
133
- delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass
134
-
135
- def self.init(klass, options, &block)
136
- relation = new(klass, klass.arel_table)
137
-
138
- scope = if options.is_a?(Hash)
139
- klass.scoped.apply_finder_options(options.except(:extend))
140
- else
141
- options ? klass.scoped.merge(options) : klass.scoped
142
- end
143
-
144
- relation = relation.merge(scope)
145
-
146
- Array.wrap(options[:extend]).each {|extension| relation.send(:extend, extension) } if options.is_a?(Hash)
147
- relation.send(:extend, Module.new(&block)) if block_given?
148
-
149
- relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods)
150
- relation
151
- end
152
-
153
- def first(*args)
154
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
155
- to_a.first(*args)
156
- else
157
- args.first.present? ? apply_finder_options(args.first).first : super
158
- end
159
- end
160
-
161
- def last(*args)
162
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
163
- to_a.last(*args)
164
- else
165
- args.first.present? ? apply_finder_options(args.first).last : super
166
- end
167
- end
168
-
169
- def ==(other)
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
178
- end
179
-
180
- private
181
-
182
- def method_missing(method, *args, &block)
183
- if klass.respond_to?(method)
184
- with_scope(self) do
185
- if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method)
186
- with_scope(current_scoped_methods_when_defined) { klass.send(method, *args, &block) }
187
- else
188
- klass.send(method, *args, &block)
189
- end
190
- end
191
- else
192
- super
193
- end
194
- end
195
-
196
- end
197
-
198
115
  end
199
116
  end