activerecord 3.1.12 → 3.2.22.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +804 -338
  3. data/README.rdoc +3 -3
  4. data/examples/performance.rb +20 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +3 -6
  7. data/lib/active_record/associations/association.rb +13 -45
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
  11. data/lib/active_record/associations/builder/association.rb +6 -4
  12. data/lib/active_record/associations/builder/belongs_to.rb +7 -4
  13. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +4 -4
  15. data/lib/active_record/associations/builder/has_one.rb +5 -6
  16. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  17. data/lib/active_record/associations/collection_association.rb +65 -32
  18. data/lib/active_record/associations/collection_proxy.rb +8 -41
  19. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  20. data/lib/active_record/associations/has_many_association.rb +11 -7
  21. data/lib/active_record/associations/has_many_through_association.rb +19 -9
  22. data/lib/active_record/associations/has_one_association.rb +23 -13
  23. data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
  24. data/lib/active_record/associations/join_dependency.rb +3 -3
  25. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  26. data/lib/active_record/associations/preloader.rb +14 -10
  27. data/lib/active_record/associations/through_association.rb +8 -4
  28. data/lib/active_record/associations.rb +92 -76
  29. data/lib/active_record/attribute_assignment.rb +221 -0
  30. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  32. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  33. data/lib/active_record/attribute_methods/read.rb +73 -83
  34. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  36. data/lib/active_record/attribute_methods/write.rb +32 -6
  37. data/lib/active_record/attribute_methods.rb +231 -30
  38. data/lib/active_record/autosave_association.rb +44 -26
  39. data/lib/active_record/base.rb +227 -1708
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  41. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  45. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  49. data/lib/active_record/connection_adapters/column.rb +37 -11
  50. data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
  51. data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
  52. data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
  53. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  54. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  55. data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
  56. data/lib/active_record/counter_cache.rb +9 -4
  57. data/lib/active_record/dynamic_finder_match.rb +12 -0
  58. data/lib/active_record/dynamic_matchers.rb +84 -0
  59. data/lib/active_record/errors.rb +11 -1
  60. data/lib/active_record/explain.rb +86 -0
  61. data/lib/active_record/explain_subscriber.rb +25 -0
  62. data/lib/active_record/fixtures/file.rb +65 -0
  63. data/lib/active_record/fixtures.rb +57 -86
  64. data/lib/active_record/identity_map.rb +3 -4
  65. data/lib/active_record/inheritance.rb +174 -0
  66. data/lib/active_record/integration.rb +60 -0
  67. data/lib/active_record/locking/optimistic.rb +33 -26
  68. data/lib/active_record/locking/pessimistic.rb +23 -1
  69. data/lib/active_record/log_subscriber.rb +8 -4
  70. data/lib/active_record/migration/command_recorder.rb +8 -8
  71. data/lib/active_record/migration.rb +68 -35
  72. data/lib/active_record/model_schema.rb +368 -0
  73. data/lib/active_record/nested_attributes.rb +60 -24
  74. data/lib/active_record/persistence.rb +57 -11
  75. data/lib/active_record/query_cache.rb +6 -6
  76. data/lib/active_record/querying.rb +58 -0
  77. data/lib/active_record/railtie.rb +37 -29
  78. data/lib/active_record/railties/controller_runtime.rb +3 -1
  79. data/lib/active_record/railties/databases.rake +213 -117
  80. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  81. data/lib/active_record/readonly_attributes.rb +26 -0
  82. data/lib/active_record/reflection.rb +7 -15
  83. data/lib/active_record/relation/batches.rb +7 -4
  84. data/lib/active_record/relation/calculations.rb +55 -16
  85. data/lib/active_record/relation/delegation.rb +49 -0
  86. data/lib/active_record/relation/finder_methods.rb +16 -11
  87. data/lib/active_record/relation/predicate_builder.rb +8 -6
  88. data/lib/active_record/relation/query_methods.rb +75 -9
  89. data/lib/active_record/relation/spawn_methods.rb +48 -7
  90. data/lib/active_record/relation.rb +78 -32
  91. data/lib/active_record/result.rb +10 -4
  92. data/lib/active_record/sanitization.rb +194 -0
  93. data/lib/active_record/schema_dumper.rb +12 -5
  94. data/lib/active_record/scoping/default.rb +142 -0
  95. data/lib/active_record/scoping/named.rb +200 -0
  96. data/lib/active_record/scoping.rb +152 -0
  97. data/lib/active_record/serialization.rb +1 -43
  98. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  99. data/lib/active_record/session_store.rb +18 -16
  100. data/lib/active_record/store.rb +52 -0
  101. data/lib/active_record/test_case.rb +11 -7
  102. data/lib/active_record/timestamp.rb +17 -3
  103. data/lib/active_record/transactions.rb +27 -6
  104. data/lib/active_record/translation.rb +22 -0
  105. data/lib/active_record/validations/associated.rb +5 -4
  106. data/lib/active_record/validations/uniqueness.rb +8 -8
  107. data/lib/active_record/validations.rb +1 -1
  108. data/lib/active_record/version.rb +3 -3
  109. data/lib/active_record.rb +38 -3
  110. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  111. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  112. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  113. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  114. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  115. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  116. metadata +49 -28
  117. data/lib/active_record/named_scope.rb +0 -200
@@ -1,5 +1,5 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/module/delegation'
3
3
 
4
4
  module ActiveRecord
5
5
  # = Active Record Relation
@@ -7,13 +7,9 @@ module ActiveRecord
7
7
  JoinOperation = Struct.new(:relation, :join_class, :on)
8
8
  ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
9
9
  MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
10
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order]
10
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
11
11
 
12
- include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
13
-
14
- # These are explicitly delegated to improve performance (avoids method_missing)
15
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
16
- delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
12
+ include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
17
13
 
18
14
  attr_reader :table, :klass, :loaded
19
15
  attr_accessor :extensions, :default_scoped
@@ -95,14 +91,77 @@ module ActiveRecord
95
91
  scoping { @klass.create!(*args, &block) }
96
92
  end
97
93
 
98
- def respond_to?(method, include_private = false)
99
- arel.respond_to?(method, include_private) ||
100
- Array.method_defined?(method) ||
101
- @klass.respond_to?(method, include_private) ||
102
- super
94
+ # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
95
+ #
96
+ # Expects arguments in the same format as <tt>Base.create</tt>.
97
+ #
98
+ # ==== Examples
99
+ # # Find the first user named Penélope or create a new one.
100
+ # User.where(:first_name => 'Penélope').first_or_create
101
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
102
+ #
103
+ # # Find the first user named Penélope or create a new one.
104
+ # # We already have one so the existing record will be returned.
105
+ # User.where(:first_name => 'Penélope').first_or_create
106
+ # # => <User id: 1, first_name: 'Penélope', last_name: nil>
107
+ #
108
+ # # Find the first user named Scarlett or create a new one with a particular last name.
109
+ # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
110
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
111
+ #
112
+ # # Find the first user named Scarlett or create a new one with a different last name.
113
+ # # We already have one so the existing record will be returned.
114
+ # User.where(:first_name => 'Scarlett').first_or_create do |user|
115
+ # user.last_name = "O'Hara"
116
+ # end
117
+ # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
118
+ def first_or_create(attributes = nil, options = {}, &block)
119
+ first || create(attributes, options, &block)
120
+ end
121
+
122
+ # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
123
+ #
124
+ # Expects arguments in the same format as <tt>Base.create!</tt>.
125
+ def first_or_create!(attributes = nil, options = {}, &block)
126
+ first || create!(attributes, options, &block)
127
+ end
128
+
129
+ # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
130
+ #
131
+ # Expects arguments in the same format as <tt>Base.new</tt>.
132
+ def first_or_initialize(attributes = nil, options = {}, &block)
133
+ first || new(attributes, options, &block)
134
+ end
135
+
136
+ # Runs EXPLAIN on the query or queries triggered by this relation and
137
+ # returns the result as a string. The string is formatted imitating the
138
+ # ones printed by the database shell.
139
+ #
140
+ # Note that this method actually runs the queries, since the results of some
141
+ # are needed by the next ones when eager loading is going on.
142
+ #
143
+ # Please see further details in the
144
+ # {Active Record Query Interface guide}[http://edgeguides.rubyonrails.org/active_record_querying.html#running-explain].
145
+ def explain
146
+ _, queries = collecting_queries_for_explain { exec_queries }
147
+ exec_explain(queries)
103
148
  end
104
149
 
105
150
  def to_a
151
+ # We monitor here the entire execution rather than individual SELECTs
152
+ # because from the point of view of the user fetching the records of a
153
+ # relation is a single unit of work. You want to know if this call takes
154
+ # too long, not if the individual queries take too long.
155
+ #
156
+ # It could be the case that none of the queries involved surpass the
157
+ # threshold, and at the same time the sum of them all does. The user
158
+ # should get a query plan logged in that case.
159
+ logging_query_plan do
160
+ exec_queries
161
+ end
162
+ end
163
+
164
+ def exec_queries
106
165
  return @records if loaded?
107
166
 
108
167
  default_scoped = with_default_scope
@@ -133,6 +192,7 @@ module ActiveRecord
133
192
  @loaded = true
134
193
  @records
135
194
  end
195
+ private :exec_queries
136
196
 
137
197
  def as_json(options = nil) #:nodoc:
138
198
  to_a.as_json(options)
@@ -178,7 +238,7 @@ module ActiveRecord
178
238
  # Please check unscoped if you want to remove all previous scopes (including
179
239
  # the default_scope) during the execution of a block.
180
240
  def scoping
181
- @klass.send(:with_scope, self, :overwrite) { yield }
241
+ @klass.with_scope(self, :overwrite) { yield }
182
242
  end
183
243
 
184
244
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -253,8 +313,7 @@ module ActiveRecord
253
313
  # Person.update(people.keys, people.values)
254
314
  def update(id, attributes)
255
315
  if id.is_a?(Array)
256
- idx = -1
257
- id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
316
+ id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
258
317
  else
259
318
  object = find(id)
260
319
  object.update_attributes(attributes)
@@ -298,7 +357,7 @@ module ActiveRecord
298
357
  end
299
358
 
300
359
  # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
301
- # therefore all callbacks and filters are fired off before the object is deleted. This method is
360
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
302
361
  # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
303
362
  #
304
363
  # This essentially finds the object (or multiple objects) with the given id, creates a new object
@@ -327,7 +386,7 @@ module ActiveRecord
327
386
  # Deletes the records matching +conditions+ without instantiating the records first, and hence not
328
387
  # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
329
388
  # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
330
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
389
+ # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
331
390
  # the number of rows affected.
332
391
  #
333
392
  # ==== Parameters
@@ -344,6 +403,8 @@ module ActiveRecord
344
403
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
345
404
  # +after_destroy+ callbacks, use the +destroy_all+ method instead.
346
405
  def delete_all(conditions = nil)
406
+ raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
407
+
347
408
  IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
348
409
  if conditions
349
410
  where(conditions).delete_all
@@ -447,20 +508,6 @@ module ActiveRecord
447
508
  end
448
509
  end
449
510
 
450
- protected
451
-
452
- def method_missing(method, *args, &block)
453
- if Array.method_defined?(method)
454
- to_a.send(method, *args, &block)
455
- elsif @klass.respond_to?(method)
456
- scoping { @klass.send(method, *args, &block) }
457
- elsif arel.respond_to?(method)
458
- arel.send(method, *args, &block)
459
- else
460
- super
461
- end
462
- end
463
-
464
511
  private
465
512
 
466
513
  def references_eager_loaded_tables?
@@ -486,6 +533,5 @@ module ActiveRecord
486
533
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
487
534
  string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
488
535
  end
489
-
490
536
  end
491
537
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveRecord
2
2
  ###
3
3
  # This class encapsulates a Result returned from calling +exec_query+ on any
4
- # database connection adapter. For example:
4
+ # database connection adapter. For example:
5
5
  #
6
6
  # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
7
7
  # x # => #<ActiveRecord::Result:0xdeadbeef>
@@ -26,9 +26,15 @@ module ActiveRecord
26
26
 
27
27
  private
28
28
  def hash_rows
29
- @hash_rows ||= @rows.map { |row|
30
- Hash[@columns.zip(row)]
31
- }
29
+ @hash_rows ||=
30
+ begin
31
+ # We freeze the strings to prevent them getting duped when
32
+ # used as keys in ActiveRecord::Model's @attributes hash
33
+ columns = @columns.map { |c| c.dup.freeze }
34
+ @rows.map { |row|
35
+ Hash[columns.zip(row)]
36
+ }
37
+ end
32
38
  end
33
39
  end
34
40
  end
@@ -0,0 +1,194 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveRecord
4
+ module Sanitization
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def quote_value(value, column = nil) #:nodoc:
9
+ connection.quote(value,column)
10
+ end
11
+
12
+ # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
13
+ def sanitize(object) #:nodoc:
14
+ connection.quote(object)
15
+ end
16
+
17
+ protected
18
+
19
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
20
+ # them into a valid SQL fragment for a WHERE clause.
21
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
22
+ # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
23
+ # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
24
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
25
+ return nil if condition.blank?
26
+
27
+ case condition
28
+ when Array; sanitize_sql_array(condition)
29
+ when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
30
+ else condition
31
+ end
32
+ end
33
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
34
+
35
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
36
+ # them into a valid SQL fragment for a SET clause.
37
+ # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
38
+ def sanitize_sql_for_assignment(assignments)
39
+ case assignments
40
+ when Array; sanitize_sql_array(assignments)
41
+ when Hash; sanitize_sql_hash_for_assignment(assignments)
42
+ else assignments
43
+ end
44
+ end
45
+
46
+ # Accepts a hash of SQL conditions and replaces those attributes
47
+ # that correspond to a +composed_of+ relationship with their expanded
48
+ # aggregate attribute values.
49
+ # Given:
50
+ # class Person < ActiveRecord::Base
51
+ # composed_of :address, :class_name => "Address",
52
+ # :mapping => [%w(address_street street), %w(address_city city)]
53
+ # end
54
+ # Then:
55
+ # { :address => Address.new("813 abc st.", "chicago") }
56
+ # # => { :address_street => "813 abc st.", :address_city => "chicago" }
57
+ def expand_hash_conditions_for_aggregates(attrs)
58
+ expanded_attrs = {}
59
+ attrs.each do |attr, value|
60
+ unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
61
+ mapping = aggregate_mapping(aggregation)
62
+ mapping.each do |field_attr, aggregate_attr|
63
+ if mapping.size == 1 && !value.respond_to?(aggregate_attr)
64
+ expanded_attrs[field_attr] = value
65
+ else
66
+ expanded_attrs[field_attr] = value.send(aggregate_attr)
67
+ end
68
+ end
69
+ else
70
+ expanded_attrs[attr] = value
71
+ end
72
+ end
73
+ expanded_attrs
74
+ end
75
+
76
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
77
+ # { :name => "foo'bar", :group_id => 4 }
78
+ # # => "name='foo''bar' and group_id= 4"
79
+ # { :status => nil, :group_id => [1,2,3] }
80
+ # # => "status IS NULL and group_id IN (1,2,3)"
81
+ # { :age => 13..18 }
82
+ # # => "age BETWEEN 13 AND 18"
83
+ # { 'other_records.id' => 7 }
84
+ # # => "`other_records`.`id` = 7"
85
+ # { :other_records => { :id => 7 } }
86
+ # # => "`other_records`.`id` = 7"
87
+ # And for value objects on a composed_of relationship:
88
+ # { :address => Address.new("123 abc st.", "chicago") }
89
+ # # => "address_street='123 abc st.' and address_city='chicago'"
90
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
91
+ attrs = expand_hash_conditions_for_aggregates(attrs)
92
+
93
+ table = Arel::Table.new(table_name).alias(default_table_name)
94
+ PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
95
+ connection.visitor.accept b
96
+ }.join(' AND ')
97
+ end
98
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
99
+
100
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
101
+ # { :status => nil, :group_id => 1 }
102
+ # # => "status = NULL , group_id = 1"
103
+ def sanitize_sql_hash_for_assignment(attrs)
104
+ attrs.map do |attr, value|
105
+ "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
106
+ end.join(', ')
107
+ end
108
+
109
+ # Accepts an array of conditions. The array has each value
110
+ # sanitized and interpolated into the SQL statement.
111
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
112
+ def sanitize_sql_array(ary)
113
+ statement, *values = ary
114
+ if values.first.is_a?(Hash) && statement =~ /:\w+/
115
+ replace_named_bind_variables(statement, values.first)
116
+ elsif statement.include?('?')
117
+ replace_bind_variables(statement, values)
118
+ elsif statement.blank?
119
+ statement
120
+ else
121
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
122
+ end
123
+ end
124
+
125
+ alias_method :sanitize_conditions, :sanitize_sql
126
+
127
+ def replace_bind_variables(statement, values) #:nodoc:
128
+ raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
129
+ bound = values.dup
130
+ c = connection
131
+ statement.gsub('?') { quote_bound_value(bound.shift, c) }
132
+ end
133
+
134
+ def replace_named_bind_variables(statement, bind_vars) #:nodoc:
135
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do
136
+ if $1 == ':' # skip postgresql casts
137
+ $& # return the whole match
138
+ elsif bind_vars.include?(match = $2.to_sym)
139
+ quote_bound_value(bind_vars[match])
140
+ else
141
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
142
+ end
143
+ end
144
+ end
145
+
146
+ def expand_range_bind_variables(bind_vars) #:nodoc:
147
+ expanded = []
148
+
149
+ bind_vars.each do |var|
150
+ next if var.is_a?(Hash)
151
+
152
+ if var.is_a?(Range)
153
+ expanded << var.first
154
+ expanded << var.last
155
+ else
156
+ expanded << var
157
+ end
158
+ end
159
+
160
+ expanded
161
+ end
162
+
163
+ def quote_bound_value(value, c = connection) #:nodoc:
164
+ if value.respond_to?(:map) && !value.acts_like?(:string)
165
+ if value.respond_to?(:empty?) && value.empty?
166
+ c.quote(nil)
167
+ else
168
+ value.map { |v| c.quote(v) }.join(',')
169
+ end
170
+ else
171
+ c.quote(value)
172
+ end
173
+ end
174
+
175
+ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
176
+ unless expected == provided
177
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
178
+ end
179
+ end
180
+ end
181
+
182
+ # TODO: Deprecate this
183
+ def quoted_id #:nodoc:
184
+ quote_value(id, column_for_attribute(self.class.primary_key))
185
+ end
186
+
187
+ private
188
+
189
+ # Quote strings appropriately for SQL statements.
190
+ def quote_value(value, column = nil)
191
+ self.class.connection.quote(value, column)
192
+ end
193
+ end
194
+ end
@@ -70,8 +70,8 @@ HEADER
70
70
  @connection.tables.sort.each do |tbl|
71
71
  next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
72
72
  case ignored
73
- when String; tbl == ignored
74
- when Regexp; tbl =~ ignored
73
+ when String; remove_prefix_and_suffix(tbl) == ignored
74
+ when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
75
75
  else
76
76
  raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
77
77
  end
@@ -92,7 +92,7 @@ HEADER
92
92
  pk = @connection.primary_key(table)
93
93
  end
94
94
 
95
- tbl.print " create_table #{table.inspect}"
95
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
96
96
  if columns.detect { |c| c.name == pk }
97
97
  if pk != 'id'
98
98
  tbl.print %Q(, :primary_key => "#{pk}")
@@ -110,7 +110,7 @@ HEADER
110
110
  spec = {}
111
111
  spec[:name] = column.name.inspect
112
112
 
113
- # AR has an optimisation which handles zero-scale decimals as integers. This
113
+ # AR has an optimization which handles zero-scale decimals as integers. This
114
114
  # code ensures that the dumper still dumps the column as a decimal.
115
115
  spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
116
116
  'decimal'
@@ -181,7 +181,7 @@ HEADER
181
181
  if (indexes = @connection.indexes(table)).any?
182
182
  add_index_statements = indexes.map do |index|
183
183
  statement_parts = [
184
- ('add_index ' + index.table.inspect),
184
+ ('add_index ' + remove_prefix_and_suffix(index.table).inspect),
185
185
  index.columns.inspect,
186
186
  (':name => ' + index.name.inspect),
187
187
  ]
@@ -190,6 +190,9 @@ HEADER
190
190
  index_lengths = (index.lengths || []).compact
191
191
  statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
192
192
 
193
+ index_orders = (index.orders || {})
194
+ statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty?
195
+
193
196
  ' ' + statement_parts.join(', ')
194
197
  end
195
198
 
@@ -197,5 +200,9 @@ HEADER
197
200
  stream.puts
198
201
  end
199
202
  end
203
+
204
+ def remove_prefix_and_suffix(table)
205
+ table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
206
+ end
200
207
  end
201
208
  end
@@ -0,0 +1,142 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveRecord
4
+ module Scoping
5
+ module Default
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # Stores the default scope for the class
10
+ class_attribute :default_scopes, :instance_writer => false
11
+ self.default_scopes = []
12
+ end
13
+
14
+ module ClassMethods
15
+ # Returns a scope for the model without the default_scope.
16
+ #
17
+ # class Post < ActiveRecord::Base
18
+ # def self.default_scope
19
+ # where :published => true
20
+ # end
21
+ # end
22
+ #
23
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
24
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
25
+ #
26
+ # This method also accepts a block. All queries inside the block will
27
+ # not use the default_scope:
28
+ #
29
+ # Post.unscoped {
30
+ # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
31
+ # }
32
+ #
33
+ # It is recommended to use the block form of unscoped because chaining
34
+ # unscoped with <tt>scope</tt> does not work. Assuming that
35
+ # <tt>published</tt> is a <tt>scope</tt>, the following two statements
36
+ # are equal: the default_scope is applied on both.
37
+ #
38
+ # Post.unscoped.published
39
+ # Post.published
40
+ def unscoped #:nodoc:
41
+ block_given? ? relation.scoping { yield } : relation
42
+ end
43
+
44
+ def before_remove_const #:nodoc:
45
+ self.current_scope = nil
46
+ end
47
+
48
+ protected
49
+
50
+ # Use this macro in your model to set a default scope for all operations on
51
+ # the model.
52
+ #
53
+ # class Article < ActiveRecord::Base
54
+ # default_scope where(:published => true)
55
+ # end
56
+ #
57
+ # Article.all # => SELECT * FROM articles WHERE published = true
58
+ #
59
+ # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
60
+ # applied while updating a record.
61
+ #
62
+ # Article.new.published # => true
63
+ # Article.create.published # => true
64
+ #
65
+ # You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
66
+ #
67
+ # class Article < ActiveRecord::Base
68
+ # default_scope { where(:published_at => Time.now - 1.week) }
69
+ # end
70
+ #
71
+ # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
72
+ # macro, and it will be called when building the default scope.)
73
+ #
74
+ # If you use multiple <tt>default_scope</tt> declarations in your model then they will
75
+ # be merged together:
76
+ #
77
+ # class Article < ActiveRecord::Base
78
+ # default_scope where(:published => true)
79
+ # default_scope where(:rating => 'G')
80
+ # end
81
+ #
82
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
83
+ #
84
+ # This is also the case with inheritance and module includes where the parent or module
85
+ # defines a <tt>default_scope</tt> and the child or including class defines a second one.
86
+ #
87
+ # If you need to do more complex things with a default scope, you can alternatively
88
+ # define it as a class method:
89
+ #
90
+ # class Article < ActiveRecord::Base
91
+ # def self.default_scope
92
+ # # Should return a scope, you can call 'super' here etc.
93
+ # end
94
+ # end
95
+ def default_scope(scope = {})
96
+ scope = Proc.new if block_given?
97
+ self.default_scopes = default_scopes + [scope]
98
+ end
99
+
100
+ def build_default_scope #:nodoc:
101
+ if method(:default_scope).owner != ActiveRecord::Scoping::Default::ClassMethods
102
+ evaluate_default_scope { default_scope }
103
+ elsif default_scopes.any?
104
+ evaluate_default_scope do
105
+ default_scopes.inject(relation) do |default_scope, scope|
106
+ if scope.is_a?(Hash)
107
+ default_scope.apply_finder_options(scope)
108
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
109
+ default_scope.merge(scope.call)
110
+ else
111
+ default_scope.merge(scope)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def ignore_default_scope? #:nodoc:
119
+ Thread.current["#{self}_ignore_default_scope"]
120
+ end
121
+
122
+ def ignore_default_scope=(ignore) #:nodoc:
123
+ Thread.current["#{self}_ignore_default_scope"] = ignore
124
+ end
125
+
126
+ # The ignore_default_scope flag is used to prevent an infinite recursion situation where
127
+ # a default scope references a scope which has a default scope which references a scope...
128
+ def evaluate_default_scope
129
+ return if ignore_default_scope?
130
+
131
+ begin
132
+ self.ignore_default_scope = true
133
+ yield
134
+ ensure
135
+ self.ignore_default_scope = false
136
+ end
137
+ end
138
+
139
+ end
140
+ end
141
+ end
142
+ end