activerecord 3.2.22.5 → 4.0.0.beta1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,133 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require "set"
3
+
4
+ module ActiveRecord
5
+ class Relation
6
+ class HashMerger # :nodoc:
7
+ attr_reader :relation, :hash
8
+
9
+ def initialize(relation, hash)
10
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
11
+
12
+ @relation = relation
13
+ @hash = hash
14
+ end
15
+
16
+ def merge
17
+ Merger.new(relation, other).merge
18
+ end
19
+
20
+ # Applying values to a relation has some side effects. E.g.
21
+ # interpolation might take place for where values. So we should
22
+ # build a relation to merge in rather than directly merging
23
+ # the values.
24
+ def other
25
+ other = Relation.new(relation.klass, relation.table)
26
+ hash.each { |k, v|
27
+ if k == :joins
28
+ if Hash === v
29
+ other.joins!(v)
30
+ else
31
+ other.joins!(*v)
32
+ end
33
+ else
34
+ other.send("#{k}!", v)
35
+ end
36
+ }
37
+ other
38
+ end
39
+ end
40
+
41
+ class Merger # :nodoc:
42
+ attr_reader :relation, :values
43
+
44
+ def initialize(relation, other)
45
+ if other.default_scoped? && other.klass != relation.klass
46
+ other = other.with_default_scope
47
+ end
48
+
49
+ @relation = relation
50
+ @values = other.values
51
+ end
52
+
53
+ NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
54
+ Relation::MULTI_VALUE_METHODS -
55
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
56
+
57
+ def normal_values
58
+ NORMAL_VALUES
59
+ end
60
+
61
+ def merge
62
+ normal_values.each do |name|
63
+ value = values[name]
64
+ relation.send("#{name}!", *value) unless value.blank?
65
+ end
66
+
67
+ merge_multi_values
68
+ merge_single_values
69
+
70
+ relation
71
+ end
72
+
73
+ private
74
+
75
+ def merge_multi_values
76
+ relation.where_values = merged_wheres
77
+ relation.bind_values = merged_binds
78
+
79
+ if values[:reordering]
80
+ # override any order specified in the original relation
81
+ relation.reorder! values[:order]
82
+ elsif values[:order]
83
+ # merge in order_values from r
84
+ relation.order! values[:order]
85
+ end
86
+
87
+ relation.extend(*values[:extending]) unless values[:extending].blank?
88
+ end
89
+
90
+ def merge_single_values
91
+ relation.from_value = values[:from] unless relation.from_value
92
+ relation.lock_value = values[:lock] unless relation.lock_value
93
+ relation.reverse_order_value = values[:reverse_order]
94
+
95
+ unless values[:create_with].blank?
96
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
97
+ end
98
+ end
99
+
100
+ def merged_binds
101
+ if values[:bind]
102
+ (relation.bind_values + values[:bind]).uniq(&:first)
103
+ else
104
+ relation.bind_values
105
+ end
106
+ end
107
+
108
+ def merged_wheres
109
+ values[:where] ||= []
110
+
111
+ if values[:where].empty? || relation.where_values.empty?
112
+ relation.where_values + values[:where]
113
+ else
114
+ # Remove equalities from the existing relation with a LHS which is
115
+ # present in the relation being merged in.
116
+
117
+ seen = Set.new
118
+ values[:where].each { |w|
119
+ if w.respond_to?(:operator) && w.operator == :==
120
+ seen << w.left
121
+ end
122
+ }
123
+
124
+ relation.where_values.reject { |w|
125
+ w.respond_to?(:operator) &&
126
+ w.operator == :== &&
127
+ seen.include?(w.left)
128
+ } + values[:where]
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -1,63 +1,111 @@
1
1
  module ActiveRecord
2
2
  class PredicateBuilder # :nodoc:
3
- def self.build_from_hash(engine, attributes, default_table, allow_table_name = true)
4
- predicates = attributes.map do |column, value|
5
- table = default_table
3
+ def self.build_from_hash(klass, attributes, default_table)
4
+ queries = []
6
5
 
7
- if allow_table_name && value.is_a?(Hash)
8
- table = Arel::Table.new(column, engine)
6
+ attributes.each do |column, value|
7
+ table = default_table
9
8
 
9
+ if value.is_a?(Hash)
10
10
  if value.empty?
11
- '1 = 2'
11
+ queries << '1=0'
12
12
  else
13
- build_from_hash(engine, value, table, false)
13
+ table = Arel::Table.new(column, default_table.engine)
14
+ association = klass.reflect_on_association(column.to_sym)
15
+
16
+ value.each do |k, v|
17
+ queries.concat expand(association && association.klass, table, k, v)
18
+ end
14
19
  end
15
20
  else
16
21
  column = column.to_s
17
22
 
18
- if allow_table_name && column.include?('.')
23
+ if column.include?('.')
19
24
  table_name, column = column.split('.', 2)
20
- table = Arel::Table.new(table_name, engine)
25
+ table = Arel::Table.new(table_name, default_table.engine)
21
26
  end
22
27
 
23
- attribute = table[column]
24
-
25
- case value
26
- when ActiveRecord::Relation
27
- value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
28
- attribute.in(value.arel.ast)
29
- when Array, ActiveRecord::Associations::CollectionProxy
30
- values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
31
- ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
32
-
33
- array_predicates = ranges.map {|range| attribute.in(range)}
34
-
35
- if values.include?(nil)
36
- values = values.compact
37
- if values.empty?
38
- array_predicates << attribute.eq(nil)
39
- else
40
- array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
41
- end
28
+ queries.concat expand(klass, table, column, value)
29
+ end
30
+ end
31
+
32
+ queries
33
+ end
34
+
35
+ def self.expand(klass, table, column, value)
36
+ queries = []
37
+
38
+ # Find the foreign key when using queries such as:
39
+ # Post.where(author: author)
40
+ #
41
+ # For polymorphic relationships, find the foreign key and type:
42
+ # PriceEstimate.where(estimate_of: treasure)
43
+ if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym)
44
+ if reflection.polymorphic?
45
+ queries << build(table[reflection.foreign_type], value.class.base_class)
46
+ end
47
+
48
+ column = reflection.foreign_key
49
+ end
50
+
51
+ queries << build(table[column.to_sym], value)
52
+ queries
53
+ end
54
+
55
+ def self.references(attributes)
56
+ attributes.map do |key, value|
57
+ if value.is_a?(Hash)
58
+ key
59
+ else
60
+ key = key.to_s
61
+ key.split('.').first if key.include?('.')
62
+ end
63
+ end.compact
64
+ end
65
+
66
+ private
67
+ def self.build(attribute, value)
68
+ case value
69
+ when Array
70
+ values = value.to_a.map {|x| x.is_a?(Base) ? x.id : x}
71
+ ranges, values = values.partition {|v| v.is_a?(Range)}
72
+
73
+ values_predicate = if values.include?(nil)
74
+ values = values.compact
75
+
76
+ case values.length
77
+ when 0
78
+ attribute.eq(nil)
79
+ when 1
80
+ attribute.eq(values.first).or(attribute.eq(nil))
42
81
  else
43
- array_predicates << attribute.in(values)
82
+ attribute.in(values).or(attribute.eq(nil))
44
83
  end
45
-
46
- array_predicates.inject {|composite, predicate| composite.or(predicate)}
47
- when Range, Arel::Relation
48
- attribute.in(value)
49
- when ActiveRecord::Base
50
- attribute.eq(value.id)
51
- when Class
52
- # FIXME: I think we need to deprecate this behavior
53
- attribute.eq(value.name)
54
84
  else
55
- attribute.eq(value)
85
+ attribute.in(values)
56
86
  end
87
+
88
+ array_predicates = ranges.map { |range| attribute.in(range) }
89
+ array_predicates << values_predicate
90
+ array_predicates.inject { |composite, predicate| composite.or(predicate) }
91
+ when ActiveRecord::Relation
92
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
93
+ attribute.in(value.arel.ast)
94
+ when Range
95
+ attribute.in(value)
96
+ when ActiveRecord::Base
97
+ attribute.eq(value.id)
98
+ when Class
99
+ # FIXME: I think we need to deprecate this behavior
100
+ attribute.eq(value.name)
101
+ when Integer, ActiveSupport::Duration
102
+ # Arel treats integers as literals, but they should be quoted when compared with strings
103
+ table = attribute.relation
104
+ column = table.engine.connection.schema_cache.columns_hash(table.name)[attribute.name.to_s]
105
+ attribute.eq(Arel::Nodes::SqlLiteral.new(table.engine.connection.quote(value, column)))
106
+ else
107
+ attribute.eq(value)
57
108
  end
58
109
  end
59
-
60
- predicates.flatten
61
- end
62
110
  end
63
111
  end
@@ -1,48 +1,183 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/object/blank'
3
2
 
4
3
  module ActiveRecord
5
4
  module QueryMethods
6
5
  extend ActiveSupport::Concern
7
6
 
8
- attr_accessor :includes_values, :eager_load_values, :preload_values,
9
- :select_values, :group_values, :order_values, :joins_values,
10
- :where_values, :having_values, :bind_values,
11
- :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
12
- :from_value, :reordering_value, :reverse_order_value,
13
- :uniq_value
7
+ # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
8
+ # In this case, #where must be chained with either #not, #like, or #not_like to return a new relation.
9
+ class WhereChain
10
+ def initialize(scope)
11
+ @scope = scope
12
+ end
14
13
 
14
+ # Returns a new relation expressing WHERE + NOT condition according to
15
+ # the conditions in the arguments.
16
+ #
17
+ # +not+ accepts conditions as a string, array, or hash. See #where for
18
+ # more details on each format.
19
+ #
20
+ # User.where.not("name = 'Jon'")
21
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
22
+ #
23
+ # User.where.not(["name = ?", "Jon"])
24
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
25
+ #
26
+ # User.where.not(name: "Jon")
27
+ # # SELECT * FROM users WHERE name != 'Jon'
28
+ #
29
+ # User.where.not(name: nil)
30
+ # # SELECT * FROM users WHERE name IS NOT NULL
31
+ #
32
+ # User.where.not(name: %w(Ko1 Nobu))
33
+ # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
34
+ #
35
+ # User.where.not(name: "Jon", role: "admin")
36
+ # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
37
+ #
38
+ def not(opts, *rest)
39
+ where_value = @scope.send(:build_where, opts, rest).map do |rel|
40
+ case rel
41
+ when Arel::Nodes::In
42
+ Arel::Nodes::NotIn.new(rel.left, rel.right)
43
+ when Arel::Nodes::Equality
44
+ Arel::Nodes::NotEqual.new(rel.left, rel.right)
45
+ when String
46
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
47
+ else
48
+ Arel::Nodes::Not.new(rel)
49
+ end
50
+ end
51
+ @scope.where_values += where_value
52
+ @scope
53
+ end
54
+ end
55
+
56
+ Relation::MULTI_VALUE_METHODS.each do |name|
57
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
58
+ def #{name}_values # def select_values
59
+ @values[:#{name}] || [] # @values[:select] || []
60
+ end # end
61
+ #
62
+ def #{name}_values=(values) # def select_values=(values)
63
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
64
+ @values[:#{name}] = values # @values[:select] = values
65
+ end # end
66
+ CODE
67
+ end
68
+
69
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
70
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
71
+ def #{name}_value # def readonly_value
72
+ @values[:#{name}] # @values[:readonly]
73
+ end # end
74
+ CODE
75
+ end
76
+
77
+ Relation::SINGLE_VALUE_METHODS.each do |name|
78
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
79
+ def #{name}_value=(value) # def readonly_value=(value)
80
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
81
+ @values[:#{name}] = value # @values[:readonly] = value
82
+ end # end
83
+ CODE
84
+ end
85
+
86
+ def create_with_value # :nodoc:
87
+ @values[:create_with] || {}
88
+ end
89
+
90
+ alias extensions extending_values
91
+
92
+ # Specify relationships to be included in the result set. For
93
+ # example:
94
+ #
95
+ # users = User.includes(:address)
96
+ # users.each do |user|
97
+ # user.address.city
98
+ # end
99
+ #
100
+ # allows you to access the +address+ attribute of the +User+ model without
101
+ # firing an additional query. This will often result in a
102
+ # performance improvement over a simple +join+.
103
+ #
104
+ # === conditions
105
+ #
106
+ # If you want to add conditions to your included models you'll have
107
+ # to explicitly reference them. For example:
108
+ #
109
+ # User.includes(:posts).where('posts.name = ?', 'example')
110
+ #
111
+ # Will throw an error, but this will work:
112
+ #
113
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
15
114
  def includes(*args)
16
- args.reject! {|a| a.blank? }
115
+ check_if_method_has_arguments!("includes", args)
116
+ spawn.includes!(*args)
117
+ end
17
118
 
18
- return self if args.empty?
119
+ def includes!(*args) # :nodoc:
120
+ args.reject! {|a| a.blank? }
19
121
 
20
- relation = clone
21
- relation.includes_values = (relation.includes_values + args).flatten.uniq
22
- relation
122
+ self.includes_values = (includes_values + args).flatten.uniq
123
+ self
23
124
  end
24
125
 
126
+ # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
127
+ #
128
+ # User.eager_load(:posts)
129
+ # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
130
+ # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
131
+ # "users"."id"
25
132
  def eager_load(*args)
26
- return self if args.blank?
133
+ check_if_method_has_arguments!("eager_load", args)
134
+ spawn.eager_load!(*args)
135
+ end
27
136
 
28
- relation = clone
29
- relation.eager_load_values += args
30
- relation
137
+ def eager_load!(*args) # :nodoc:
138
+ self.eager_load_values += args
139
+ self
31
140
  end
32
141
 
142
+ # Allows preloading of +args+, in the same way that +includes+ does:
143
+ #
144
+ # User.preload(:posts)
145
+ # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
33
146
  def preload(*args)
34
- return self if args.blank?
147
+ check_if_method_has_arguments!("preload", args)
148
+ spawn.preload!(*args)
149
+ end
150
+
151
+ def preload!(*args) # :nodoc:
152
+ self.preload_values += args
153
+ self
154
+ end
155
+
156
+ # Used to indicate that an association is referenced by an SQL string, and should
157
+ # therefore be JOINed in any query rather than loaded separately.
158
+ #
159
+ # User.includes(:posts).where("posts.name = 'foo'")
160
+ # # => Doesn't JOIN the posts table, resulting in an error.
161
+ #
162
+ # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
163
+ # # => Query now knows the string references posts, so adds a JOIN
164
+ def references(*args)
165
+ check_if_method_has_arguments!("references", args)
166
+ spawn.references!(*args)
167
+ end
168
+
169
+ def references!(*args) # :nodoc:
170
+ args.flatten!
35
171
 
36
- relation = clone
37
- relation.preload_values += args
38
- relation
172
+ self.references_values = (references_values + args.map!(&:to_s)).uniq
173
+ self
39
174
  end
40
175
 
41
176
  # Works in two unique ways.
42
177
  #
43
178
  # First: takes a block so it can be used just like Array#select.
44
179
  #
45
- # Model.scoped.select { |m| m.field == value }
180
+ # Model.all.select { |m| m.field == value }
46
181
  #
47
182
  # This will build an array of objects from the database for the scope,
48
183
  # converting them into an array and iterating through them using Array#select.
@@ -50,8 +185,8 @@ module ActiveRecord
50
185
  # Second: Modifies the SELECT statement for the query so that only certain
51
186
  # fields are retrieved:
52
187
  #
53
- # >> Model.select(:field)
54
- # => [#<Model field:value>]
188
+ # Model.select(:field)
189
+ # # => [#<Model field:value>]
55
190
  #
56
191
  # Although in the above example it looks as though this method returns an
57
192
  # array, it actually returns a relation object and can have other query
@@ -59,38 +194,99 @@ module ActiveRecord
59
194
  #
60
195
  # The argument to the method can also be an array of fields.
61
196
  #
62
- # >> Model.select([:field, :other_field, :and_one_more])
63
- # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
197
+ # Model.select(:field, :other_field, :and_one_more)
198
+ # # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
64
199
  #
65
- # Any attributes that do not have fields retrieved by a select
66
- # will raise a ActiveModel::MissingAttributeError when the getter method for that attribute is used:
200
+ # You can also use one or more strings, which will be used unchanged as SELECT fields.
67
201
  #
68
- # >> Model.select(:field).first.other_field
69
- # => ActiveModel::MissingAttributeError: missing attribute: other_field
70
- def select(value = Proc.new)
202
+ # Model.select('field AS field_one', 'other_field AS field_two')
203
+ # # => [#<Model field: "value", other_field: "value">]
204
+ #
205
+ # If an alias was specified, it will be accessible from the resulting objects:
206
+ #
207
+ # Model.select('field AS field_one').first.field_one
208
+ # # => "value"
209
+ #
210
+ # Accessing attributes of an object that do not have fields retrieved by a select
211
+ # will throw <tt>ActiveModel::MissingAttributeError</tt>:
212
+ #
213
+ # Model.select(:field).first.other_field
214
+ # # => ActiveModel::MissingAttributeError: missing attribute: other_field
215
+ def select(*fields)
71
216
  if block_given?
72
- to_a.select {|*block_args| value.call(*block_args) }
217
+ to_a.select { |*block_args| yield(*block_args) }
73
218
  else
74
- relation = clone
75
- relation.select_values += Array.wrap(value)
76
- relation
219
+ raise ArgumentError, 'Call this with at least one field' if fields.empty?
220
+ spawn.select!(*fields)
77
221
  end
78
222
  end
79
223
 
224
+ def select!(*fields) # :nodoc:
225
+ self.select_values += fields.flatten
226
+ self
227
+ end
228
+
229
+ # Allows to specify a group attribute:
230
+ #
231
+ # User.group(:name)
232
+ # => SELECT "users".* FROM "users" GROUP BY name
233
+ #
234
+ # Returns an array with distinct records based on the +group+ attribute:
235
+ #
236
+ # User.select([:id, :name])
237
+ # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
238
+ #
239
+ # User.group(:name)
240
+ # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
241
+ #
242
+ # User.group('name AS grouped_name, age')
243
+ # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
80
244
  def group(*args)
81
- return self if args.blank?
245
+ check_if_method_has_arguments!("group", args)
246
+ spawn.group!(*args)
247
+ end
82
248
 
83
- relation = clone
84
- relation.group_values += args.flatten
85
- relation
249
+ def group!(*args) # :nodoc:
250
+ args.flatten!
251
+
252
+ self.group_values += args
253
+ self
86
254
  end
87
255
 
256
+ # Allows to specify an order attribute:
257
+ #
258
+ # User.order('name')
259
+ # => SELECT "users".* FROM "users" ORDER BY name
260
+ #
261
+ # User.order('name DESC')
262
+ # => SELECT "users".* FROM "users" ORDER BY name DESC
263
+ #
264
+ # User.order('name DESC, email')
265
+ # => SELECT "users".* FROM "users" ORDER BY name DESC, email
266
+ #
267
+ # User.order(:name)
268
+ # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
269
+ #
270
+ # User.order(email: :desc)
271
+ # => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
272
+ #
273
+ # User.order(:name, email: :desc)
274
+ # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
88
275
  def order(*args)
89
- return self if args.blank?
276
+ check_if_method_has_arguments!("order", args)
277
+ spawn.order!(*args)
278
+ end
279
+
280
+ def order!(*args) # :nodoc:
281
+ args.flatten!
282
+ validate_order_args args
283
+
284
+ references = args.reject { |arg| Arel::Node === arg }
285
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
286
+ references!(references) if references.any?
90
287
 
91
- relation = clone
92
- relation.order_values += args.flatten
93
- relation
288
+ self.order_values = args + self.order_values
289
+ self
94
290
  end
95
291
 
96
292
  # Replaces any existing order defined on the relation with the specified order.
@@ -101,91 +297,346 @@ module ActiveRecord
101
297
  #
102
298
  # User.order('email DESC').reorder('id ASC').order('name ASC')
103
299
  #
104
- # generates a query with 'ORDER BY id ASC, name ASC'.
105
- #
300
+ # generates a query with 'ORDER BY name ASC, id ASC'.
106
301
  def reorder(*args)
107
- return self if args.blank?
302
+ check_if_method_has_arguments!("reorder", args)
303
+ spawn.reorder!(*args)
304
+ end
305
+
306
+ def reorder!(*args) # :nodoc:
307
+ args.flatten!
308
+ validate_order_args args
108
309
 
109
- relation = clone
110
- relation.reordering_value = true
111
- relation.order_values = args.flatten
112
- relation
310
+ self.reordering_value = true
311
+ self.order_values = args
312
+ self
113
313
  end
114
314
 
315
+ # Performs a joins on +args+:
316
+ #
317
+ # User.joins(:posts)
318
+ # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
319
+ #
320
+ # You can use strings in order to customize your joins:
321
+ #
322
+ # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
323
+ # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
115
324
  def joins(*args)
116
- return self if args.compact.blank?
325
+ check_if_method_has_arguments!("joins", args)
326
+ spawn.joins!(*args.compact.flatten)
327
+ end
117
328
 
118
- relation = clone
329
+ def joins!(*args) # :nodoc:
330
+ self.joins_values += args
331
+ self
332
+ end
119
333
 
120
- args.flatten!
121
- relation.joins_values += args
334
+ def bind(value)
335
+ spawn.bind!(value)
336
+ end
122
337
 
123
- relation
338
+ def bind!(value) # :nodoc:
339
+ self.bind_values += [value]
340
+ self
124
341
  end
125
342
 
126
- def bind(value)
127
- relation = clone
128
- relation.bind_values += [value]
129
- relation
343
+ # Returns a new relation, which is the result of filtering the current relation
344
+ # according to the conditions in the arguments.
345
+ #
346
+ # #where accepts conditions in one of several formats. In the examples below, the resulting
347
+ # SQL is given as an illustration; the actual query generated may be different depending
348
+ # on the database adapter.
349
+ #
350
+ # === string
351
+ #
352
+ # A single string, without additional arguments, is passed to the query
353
+ # constructor as a SQL fragment, and used in the where clause of the query.
354
+ #
355
+ # Client.where("orders_count = '2'")
356
+ # # SELECT * from clients where orders_count = '2';
357
+ #
358
+ # Note that building your own string from user input may expose your application
359
+ # to injection attacks if not done properly. As an alternative, it is recommended
360
+ # to use one of the following methods.
361
+ #
362
+ # === array
363
+ #
364
+ # If an array is passed, then the first element of the array is treated as a template, and
365
+ # the remaining elements are inserted into the template to generate the condition.
366
+ # Active Record takes care of building the query to avoid injection attacks, and will
367
+ # convert from the ruby type to the database type where needed. Elements are inserted
368
+ # into the string in the order in which they appear.
369
+ #
370
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
371
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
372
+ #
373
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
374
+ # second element of the array. The names in the template are replaced with the corresponding
375
+ # values from the hash.
376
+ #
377
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
378
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
379
+ #
380
+ # This can make for more readable code in complex queries.
381
+ #
382
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
383
+ # than the previous methods; you are responsible for ensuring that the values in the template
384
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
385
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
386
+ # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
387
+ #
388
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
389
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
390
+ #
391
+ # If #where is called with multiple arguments, these are treated as if they were passed as
392
+ # the elements of a single array.
393
+ #
394
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
395
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
396
+ #
397
+ # When using strings to specify conditions, you can use any operator available from
398
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
399
+ # dependencies on the underlying database. If your code is intended for general consumption,
400
+ # test with multiple database backends.
401
+ #
402
+ # === hash
403
+ #
404
+ # #where will also accept a hash condition, in which the keys are fields and the values
405
+ # are values to be searched for.
406
+ #
407
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
408
+ #
409
+ # User.where({ name: "Joe", email: "joe@example.com" })
410
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
411
+ #
412
+ # User.where({ name: ["Alice", "Bob"]})
413
+ # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
414
+ #
415
+ # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
416
+ # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
417
+ #
418
+ # In the case of a belongs_to relationship, an association key can be used
419
+ # to specify the model if an ActiveRecord object is used as the value.
420
+ #
421
+ # author = Author.find(1)
422
+ #
423
+ # # The following queries will be equivalent:
424
+ # Post.where(author: author)
425
+ # Post.where(author_id: author)
426
+ #
427
+ # This also works with polymorphic belongs_to relationships:
428
+ #
429
+ # treasure = Treasure.create(name: 'gold coins')
430
+ # treasure.price_estimates << PriceEstimate.create(price: 125)
431
+ #
432
+ # # The following queries will be equivalent:
433
+ # PriceEstimate.where(estimate_of: treasure)
434
+ # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
435
+ #
436
+ # === Joins
437
+ #
438
+ # If the relation is the result of a join, you may create a condition which uses any of the
439
+ # tables in the join. For string and array conditions, use the table name in the condition.
440
+ #
441
+ # User.joins(:posts).where("posts.created_at < ?", Time.now)
442
+ #
443
+ # For hash conditions, you can either use the table name in the key, or use a sub-hash.
444
+ #
445
+ # User.joins(:posts).where({ "posts.published" => true })
446
+ # User.joins(:posts).where({ posts: { published: true } })
447
+ #
448
+ # === no argument
449
+ #
450
+ # If no argument is passed, #where returns a new instance of WhereChain, that
451
+ # can be chained with #not to return a new relation that negates the where clause.
452
+ #
453
+ # User.where.not(name: "Jon")
454
+ # # SELECT * FROM users WHERE name != 'Jon'
455
+ #
456
+ # See WhereChain for more details on #not.
457
+ #
458
+ # === blank condition
459
+ #
460
+ # If the condition is any blank-ish object, then #where is a no-op and returns
461
+ # the current relation.
462
+ def where(opts = :chain, *rest)
463
+ if opts == :chain
464
+ WhereChain.new(spawn)
465
+ elsif opts.blank?
466
+ self
467
+ else
468
+ spawn.where!(opts, *rest)
469
+ end
130
470
  end
131
471
 
132
- def where(opts, *rest)
133
- return self if opts.blank?
472
+ def where!(opts = :chain, *rest) # :nodoc:
473
+ if opts == :chain
474
+ WhereChain.new(self)
475
+ else
476
+ references!(PredicateBuilder.references(opts)) if Hash === opts
134
477
 
135
- relation = clone
136
- relation.where_values += build_where(opts, rest)
137
- relation
478
+ self.where_values += build_where(opts, rest)
479
+ self
480
+ end
138
481
  end
139
482
 
483
+ # Allows to specify a HAVING clause. Note that you can't use HAVING
484
+ # without also specifying a GROUP clause.
485
+ #
486
+ # Order.having('SUM(price) > 30').group('user_id')
140
487
  def having(opts, *rest)
141
- return self if opts.blank?
488
+ opts.blank? ? self : spawn.having!(opts, *rest)
489
+ spawn.having!(opts, *rest)
490
+ end
491
+
492
+ def having!(opts, *rest) # :nodoc:
493
+ references!(PredicateBuilder.references(opts)) if Hash === opts
142
494
 
143
- relation = clone
144
- relation.having_values += build_where(opts, rest)
145
- relation
495
+ self.having_values += build_where(opts, rest)
496
+ self
146
497
  end
147
498
 
499
+ # Specifies a limit for the number of records to retrieve.
500
+ #
501
+ # User.limit(10) # generated SQL has 'LIMIT 10'
502
+ #
503
+ # User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
148
504
  def limit(value)
149
- relation = clone
150
- relation.limit_value = value
151
- relation
505
+ spawn.limit!(value)
506
+ end
507
+
508
+ def limit!(value) # :nodoc:
509
+ self.limit_value = value
510
+ self
152
511
  end
153
512
 
513
+ # Specifies the number of rows to skip before returning rows.
514
+ #
515
+ # User.offset(10) # generated SQL has "OFFSET 10"
516
+ #
517
+ # Should be used with order.
518
+ #
519
+ # User.offset(10).order("name ASC")
154
520
  def offset(value)
155
- relation = clone
156
- relation.offset_value = value
157
- relation
521
+ spawn.offset!(value)
158
522
  end
159
523
 
524
+ def offset!(value) # :nodoc:
525
+ self.offset_value = value
526
+ self
527
+ end
528
+
529
+ # Specifies locking settings (default to +true+). For more information
530
+ # on locking, please see +ActiveRecord::Locking+.
160
531
  def lock(locks = true)
161
- relation = clone
532
+ spawn.lock!(locks)
533
+ end
162
534
 
535
+ def lock!(locks = true) # :nodoc:
163
536
  case locks
164
537
  when String, TrueClass, NilClass
165
- relation.lock_value = locks || true
538
+ self.lock_value = locks || true
166
539
  else
167
- relation.lock_value = false
540
+ self.lock_value = false
168
541
  end
169
542
 
170
- relation
543
+ self
171
544
  end
172
545
 
546
+ # Returns a chainable relation with zero records, specifically an
547
+ # instance of the <tt>ActiveRecord::NullRelation</tt> class.
548
+ #
549
+ # The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
550
+ # Null Object pattern. It is an object with defined null behavior and always returns an empty
551
+ # array of records without quering the database.
552
+ #
553
+ # Any subsequent condition chained to the returned relation will continue
554
+ # generating an empty relation and will not fire any query to the database.
555
+ #
556
+ # Used in cases where a method or scope could return zero records but the
557
+ # result needs to be chainable.
558
+ #
559
+ # For example:
560
+ #
561
+ # @posts = current_user.visible_posts.where(name: params[:name])
562
+ # # => the visible_posts method is expected to return a chainable Relation
563
+ #
564
+ # def visible_posts
565
+ # case role
566
+ # when 'Country Manager'
567
+ # Post.where(country: country)
568
+ # when 'Reviewer'
569
+ # Post.published
570
+ # when 'Bad User'
571
+ # Post.none # => returning [] instead breaks the previous code
572
+ # end
573
+ # end
574
+ #
575
+ def none
576
+ extending(NullRelation)
577
+ end
578
+
579
+ def none! # :nodoc:
580
+ extending!(NullRelation)
581
+ end
582
+
583
+ # Sets readonly attributes for the returned relation. If value is
584
+ # true (default), attempting to update a record will result in an error.
585
+ #
586
+ # users = User.readonly
587
+ # users.first.save
588
+ # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
173
589
  def readonly(value = true)
174
- relation = clone
175
- relation.readonly_value = value
176
- relation
590
+ spawn.readonly!(value)
591
+ end
592
+
593
+ def readonly!(value = true) # :nodoc:
594
+ self.readonly_value = value
595
+ self
177
596
  end
178
597
 
598
+ # Sets attributes to be used when creating new records from a
599
+ # relation object.
600
+ #
601
+ # users = User.where(name: 'Oscar')
602
+ # users.new.name # => 'Oscar'
603
+ #
604
+ # users = users.create_with(name: 'DHH')
605
+ # users.new.name # => 'DHH'
606
+ #
607
+ # You can pass +nil+ to +create_with+ to reset attributes:
608
+ #
609
+ # users = users.create_with(nil)
610
+ # users.new.name # => 'Oscar'
179
611
  def create_with(value)
180
- relation = clone
181
- relation.create_with_value = value ? create_with_value.merge(value) : {}
182
- relation
612
+ spawn.create_with!(value)
183
613
  end
184
614
 
185
- def from(value)
186
- relation = clone
187
- relation.from_value = value
188
- relation
615
+ def create_with!(value) # :nodoc:
616
+ self.create_with_value = value ? create_with_value.merge(value) : {}
617
+ self
618
+ end
619
+
620
+ # Specifies table from which the records will be fetched. For example:
621
+ #
622
+ # Topic.select('title').from('posts')
623
+ # #=> SELECT title FROM posts
624
+ #
625
+ # Can accept other relation objects. For example:
626
+ #
627
+ # Topic.select('title').from(Topic.approved)
628
+ # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
629
+ #
630
+ # Topic.select('a.title').from(Topic.approved, :a)
631
+ # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
632
+ #
633
+ def from(value, subquery_name = nil)
634
+ spawn.from!(value, subquery_name)
635
+ end
636
+
637
+ def from!(value, subquery_name = nil) # :nodoc:
638
+ self.from_value = [value, subquery_name]
639
+ self
189
640
  end
190
641
 
191
642
  # Specifies whether the records should be unique or not. For example:
@@ -199,9 +650,13 @@ module ActiveRecord
199
650
  # User.select(:name).uniq.uniq(false)
200
651
  # # => You can also remove the uniqueness
201
652
  def uniq(value = true)
202
- relation = clone
203
- relation.uniq_value = value
204
- relation
653
+ spawn.uniq!(value)
654
+ end
655
+
656
+ # Like #uniq, but modifies relation in place.
657
+ def uniq!(value = true) # :nodoc:
658
+ self.uniq_value = value
659
+ self
205
660
  end
206
661
 
207
662
  # Used to extend a scope with additional methods, either through
@@ -217,16 +672,16 @@ module ActiveRecord
217
672
  # end
218
673
  # end
219
674
  #
220
- # scope = Model.scoped.extending(Pagination)
675
+ # scope = Model.all.extending(Pagination)
221
676
  # scope.page(params[:page])
222
677
  #
223
678
  # You can also pass a list of modules:
224
679
  #
225
- # scope = Model.scoped.extending(Pagination, SomethingElse)
680
+ # scope = Model.all.extending(Pagination, SomethingElse)
226
681
  #
227
682
  # === Using a block
228
683
  #
229
- # scope = Model.scoped.extending do
684
+ # scope = Model.all.extending do
230
685
  # def page(number)
231
686
  # # pagination code goes here
232
687
  # end
@@ -235,54 +690,67 @@ module ActiveRecord
235
690
  #
236
691
  # You can also use a block and a module list:
237
692
  #
238
- # scope = Model.scoped.extending(Pagination) do
693
+ # scope = Model.all.extending(Pagination) do
239
694
  # def per_page(number)
240
695
  # # pagination code goes here
241
696
  # end
242
697
  # end
243
- def extending(*modules)
244
- modules << Module.new(&Proc.new) if block_given?
698
+ def extending(*modules, &block)
699
+ if modules.any? || block
700
+ spawn.extending!(*modules, &block)
701
+ else
702
+ self
703
+ end
704
+ end
705
+
706
+ def extending!(*modules, &block) # :nodoc:
707
+ modules << Module.new(&block) if block_given?
245
708
 
246
- return self if modules.empty?
709
+ self.extending_values += modules.flatten
710
+ extend(*extending_values) if extending_values.any?
247
711
 
248
- relation = clone
249
- relation.send(:apply_modules, modules.flatten)
250
- relation
712
+ self
251
713
  end
252
714
 
715
+ # Reverse the existing order clause on the relation.
716
+ #
717
+ # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
253
718
  def reverse_order
254
- relation = clone
255
- relation.reverse_order_value = !relation.reverse_order_value
256
- relation
719
+ spawn.reverse_order!
720
+ end
721
+
722
+ def reverse_order! # :nodoc:
723
+ self.reverse_order_value = !reverse_order_value
724
+ self
257
725
  end
258
726
 
727
+ # Returns the Arel object associated with the relation.
259
728
  def arel
260
729
  @arel ||= with_default_scope.build_arel
261
730
  end
262
731
 
732
+ # Like #arel, but ignores the default scope of the model.
263
733
  def build_arel
264
- arel = table.from table
734
+ arel = Arel::SelectManager.new(table.engine, table)
265
735
 
266
- build_joins(arel, @joins_values) unless @joins_values.empty?
736
+ build_joins(arel, joins_values) unless joins_values.empty?
267
737
 
268
- collapse_wheres(arel, (@where_values - ['']).uniq)
738
+ collapse_wheres(arel, (where_values - ['']).uniq)
269
739
 
270
- arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
740
+ arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
271
741
 
272
- arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
273
- arel.skip(@offset_value.to_i) if @offset_value
742
+ arel.take(connection.sanitize_limit(limit_value)) if limit_value
743
+ arel.skip(offset_value.to_i) if offset_value
274
744
 
275
- arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
745
+ arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
276
746
 
277
- order = @order_values
278
- order = reverse_sql_order(order) if @reverse_order_value
279
- arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
747
+ build_order(arel)
280
748
 
281
- build_select(arel, @select_values.uniq)
749
+ build_select(arel, select_values.uniq)
282
750
 
283
- arel.distinct(@uniq_value)
284
- arel.from(@from_value) if @from_value
285
- arel.lock(@lock_value) if @lock_value
751
+ arel.distinct(uniq_value)
752
+ arel.from(build_from) if from_value
753
+ arel.lock(lock_value) if lock_value
286
754
 
287
755
  arel
288
756
  end
@@ -324,32 +792,48 @@ module ActiveRecord
324
792
  [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
325
793
  when Hash
326
794
  attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
327
- PredicateBuilder.build_from_hash(table.engine, attributes, table)
795
+
796
+ attributes.values.grep(ActiveRecord::Relation) do |rel|
797
+ self.bind_values += rel.bind_values
798
+ end
799
+
800
+ PredicateBuilder.build_from_hash(klass, attributes, table)
328
801
  else
329
802
  [opts]
330
803
  end
331
804
  end
332
805
 
806
+ def build_from
807
+ opts, name = from_value
808
+ case opts
809
+ when Relation
810
+ name ||= 'subquery'
811
+ opts.arel.as(name.to_s)
812
+ else
813
+ opts
814
+ end
815
+ end
816
+
333
817
  def build_joins(manager, joins)
334
818
  buckets = joins.group_by do |join|
335
819
  case join
336
820
  when String
337
- 'string_join'
821
+ :string_join
338
822
  when Hash, Symbol, Array
339
- 'association_join'
823
+ :association_join
340
824
  when ActiveRecord::Associations::JoinDependency::JoinAssociation
341
- 'stashed_join'
825
+ :stashed_join
342
826
  when Arel::Nodes::Join
343
- 'join_node'
827
+ :join_node
344
828
  else
345
829
  raise 'unknown class: %s' % join.class.name
346
830
  end
347
831
  end
348
832
 
349
- association_joins = buckets['association_join'] || []
350
- stashed_association_joins = buckets['stashed_join'] || []
351
- join_nodes = (buckets['join_node'] || []).uniq
352
- string_joins = (buckets['string_join'] || []).map { |x|
833
+ association_joins = buckets[:association_join] || []
834
+ stashed_association_joins = buckets[:stashed_join] || []
835
+ join_nodes = (buckets[:join_node] || []).uniq
836
+ string_joins = (buckets[:string_join] || []).map { |x|
353
837
  x.strip
354
838
  }.uniq
355
839
 
@@ -384,34 +868,80 @@ module ActiveRecord
384
868
  end
385
869
  end
386
870
 
387
- def apply_modules(modules)
388
- unless modules.empty?
389
- @extensions += modules
390
- modules.each {|extension| extend(extension) }
391
- end
392
- end
393
-
394
871
  def reverse_sql_order(order_query)
395
872
  order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
396
873
 
397
- order_query.map do |o|
874
+ order_query.flat_map do |o|
398
875
  case o
399
876
  when Arel::Nodes::Ordering
400
877
  o.reverse
401
- when String, Symbol
878
+ when String
402
879
  o.to_s.split(',').collect do |s|
403
880
  s.strip!
404
881
  s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
405
882
  end
883
+ when Symbol
884
+ { o => :desc }
885
+ when Hash
886
+ o.each_with_object({}) do |(field, dir), memo|
887
+ memo[field] = (dir == :asc ? :desc : :asc )
888
+ end
406
889
  else
407
890
  o
408
891
  end
409
- end.flatten
892
+ end
410
893
  end
411
894
 
412
895
  def array_of_strings?(o)
413
896
  o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
414
897
  end
415
898
 
899
+ def build_order(arel)
900
+ orders = order_values
901
+ orders = reverse_sql_order(orders) if reverse_order_value
902
+
903
+ orders = orders.uniq.reject(&:blank?).flat_map do |order|
904
+ case order
905
+ when Symbol
906
+ table[order].asc
907
+ when Hash
908
+ order.map { |field, dir| table[field].send(dir) }
909
+ else
910
+ order
911
+ end
912
+ end
913
+
914
+ arel.order(*orders) unless orders.empty?
915
+ end
916
+
917
+ def validate_order_args(args)
918
+ args.select { |a| Hash === a }.each do |h|
919
+ unless (h.values - [:asc, :desc]).empty?
920
+ raise ArgumentError, 'Direction should be :asc or :desc'
921
+ end
922
+ end
923
+ end
924
+
925
+ # Checks to make sure that the arguments are not blank. Note that if some
926
+ # blank-like object were initially passed into the query method, then this
927
+ # method will not raise an error.
928
+ #
929
+ # Example:
930
+ #
931
+ # Post.references() # => raises an error
932
+ # Post.references([]) # => does not raise an error
933
+ #
934
+ # This particular method should be called with a method_name and the args
935
+ # passed into that method as an input. For example:
936
+ #
937
+ # def references(*args)
938
+ # check_if_method_has_arguments!("references", args)
939
+ # ...
940
+ # end
941
+ def check_if_method_has_arguments!(method_name, args)
942
+ if args.blank?
943
+ raise ArgumentError, "The method .#{method_name}() must contain arguments."
944
+ end
945
+ end
416
946
  end
417
947
  end