squeel 1.1.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +36 -4
  5. data/CHANGELOG.md +15 -0
  6. data/Gemfile +1 -1
  7. data/README.md +47 -6
  8. data/Rakefile +14 -2
  9. data/lib/squeel.rb +9 -1
  10. data/lib/squeel/adapters/active_record.rb +0 -1
  11. data/lib/squeel/adapters/active_record/3.0/join_dependency_extensions.rb +1 -0
  12. data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +12 -1
  13. data/lib/squeel/adapters/active_record/3.1/join_dependency_extensions.rb +1 -0
  14. data/lib/squeel/adapters/active_record/3.2/join_dependency_extensions.rb +1 -0
  15. data/lib/squeel/adapters/active_record/4.0/join_dependency_extensions.rb +1 -0
  16. data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +92 -0
  17. data/lib/squeel/adapters/active_record/4.1/compat.rb +15 -0
  18. data/lib/squeel/adapters/active_record/4.1/context.rb +88 -0
  19. data/lib/squeel/adapters/active_record/4.1/preloader_extensions.rb +31 -0
  20. data/lib/squeel/adapters/active_record/4.1/reflection_extensions.rb +37 -0
  21. data/lib/squeel/adapters/active_record/4.1/relation_extensions.rb +307 -0
  22. data/lib/squeel/adapters/active_record/4.2/compat.rb +1 -0
  23. data/lib/squeel/adapters/active_record/4.2/context.rb +1 -0
  24. data/lib/squeel/adapters/active_record/4.2/preloader_extensions.rb +1 -0
  25. data/lib/squeel/adapters/active_record/4.2/relation_extensions.rb +108 -0
  26. data/lib/squeel/adapters/active_record/context.rb +7 -7
  27. data/lib/squeel/adapters/active_record/join_dependency_extensions.rb +9 -13
  28. data/lib/squeel/adapters/active_record/relation_extensions.rb +38 -8
  29. data/lib/squeel/core_ext/symbol.rb +3 -3
  30. data/lib/squeel/dsl.rb +1 -1
  31. data/lib/squeel/nodes.rb +1 -0
  32. data/lib/squeel/nodes/as.rb +12 -0
  33. data/lib/squeel/nodes/join.rb +8 -4
  34. data/lib/squeel/nodes/key_path.rb +10 -1
  35. data/lib/squeel/nodes/node.rb +21 -0
  36. data/lib/squeel/nodes/stub.rb +8 -4
  37. data/lib/squeel/nodes/subquery_join.rb +44 -0
  38. data/lib/squeel/version.rb +1 -1
  39. data/lib/squeel/visitors.rb +2 -0
  40. data/lib/squeel/visitors/enumeration_visitor.rb +101 -0
  41. data/lib/squeel/visitors/order_visitor.rb +9 -2
  42. data/lib/squeel/visitors/predicate_visitor.rb +11 -0
  43. data/lib/squeel/visitors/preload_visitor.rb +12 -0
  44. data/lib/squeel/visitors/visitor.rb +89 -13
  45. data/spec/config.travis.yml +13 -0
  46. data/spec/config.yml +12 -0
  47. data/spec/console.rb +3 -12
  48. data/spec/core_ext/symbol_spec.rb +3 -3
  49. data/spec/helpers/squeel_helper.rb +8 -5
  50. data/spec/spec_helper.rb +4 -16
  51. data/spec/squeel/adapters/active_record/context_spec.rb +8 -4
  52. data/spec/squeel/adapters/active_record/join_dependency_extensions_spec.rb +123 -38
  53. data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +350 -124
  54. data/spec/squeel/core_ext/symbol_spec.rb +3 -3
  55. data/spec/squeel/nodes/join_spec.rb +4 -4
  56. data/spec/squeel/nodes/stub_spec.rb +3 -3
  57. data/spec/squeel/nodes/subquery_join_spec.rb +46 -0
  58. data/spec/squeel/visitors/order_visitor_spec.rb +3 -3
  59. data/spec/squeel/visitors/predicate_visitor_spec.rb +69 -36
  60. data/spec/squeel/visitors/select_visitor_spec.rb +1 -1
  61. data/spec/squeel/visitors/visitor_spec.rb +7 -6
  62. data/spec/support/models.rb +99 -15
  63. data/spec/support/schema.rb +109 -4
  64. data/squeel.gemspec +8 -6
  65. metadata +89 -107
  66. data/.ruby-gemset +0 -1
  67. data/.ruby-version +0 -1
  68. data/spec/blueprints/articles.rb +0 -5
  69. data/spec/blueprints/comments.rb +0 -5
  70. data/spec/blueprints/notes.rb +0 -3
  71. data/spec/blueprints/people.rb +0 -4
  72. data/spec/blueprints/tags.rb +0 -3
@@ -0,0 +1,31 @@
1
+ module Squeel
2
+ module Adapters
3
+ module ActiveRecord
4
+ module PreloaderExtensions
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ alias_method_chain :preload, :squeel
9
+ end
10
+ end
11
+
12
+ def preload_with_squeel(records, associations, preload_scope = nil)
13
+ records = Array.wrap(records).compact.uniq
14
+ associations = Array.wrap(associations)
15
+ preload_scope = preload_scope || ::ActiveRecord::Associations::Preloader::NULL_RELATION
16
+
17
+ if records.empty?
18
+ []
19
+ else
20
+ Visitors::PreloadVisitor.new.accept(associations).each do |association|
21
+ preloaders_on(association, records, preload_scope)
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ ActiveRecord::Associations::Preloader.send :include, Squeel::Adapters::ActiveRecord::PreloaderExtensions
@@ -0,0 +1,37 @@
1
+ module Squeel
2
+ module Adapters
3
+ module ActiveRecord
4
+ module ReflectionExtensions
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.class_eval do
8
+ class << self
9
+ alias_method_chain :reflect_on_aggregation, :squeel
10
+ alias_method_chain :reflect_on_association, :squeel
11
+ alias_method_chain :_reflect_on_association, :squeel
12
+ end
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def reflect_on_aggregation_with_squeel(aggregation)
18
+ aggregation ||= ""
19
+ reflect_on_aggregation_without_squeel(aggregation)
20
+ end
21
+
22
+ def reflect_on_association_with_squeel(association)
23
+ association ||= ""
24
+ reflect_on_association_without_squeel(association)
25
+ end
26
+
27
+ def _reflect_on_association_with_squeel(association)
28
+ association ||= ""
29
+ _reflect_on_association_without_squeel(association)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ ActiveRecord::Base.send :include, Squeel::Adapters::ActiveRecord::ReflectionExtensions
@@ -0,0 +1,307 @@
1
+ require 'squeel/adapters/active_record/relation_extensions'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module RelationExtensions
7
+
8
+ attr_accessor :stashed_join_dependencies
9
+ private :stashed_join_dependencies, :stashed_join_dependencies=
10
+
11
+ def reset
12
+ @stashed_join_dependencies = nil
13
+ super
14
+ end
15
+
16
+ # where.not is a pain. It calls the private `build_where` method on its
17
+ # scope, and since ActiveRecord::Relation already includes the original
18
+ # ActiveRecord::QueryMethods module, we have to find a way to trick the
19
+ # scope passed to the WhereChain initializer into having the original
20
+ # behavior. This is a way to do it that avoids using alias_method_chain.
21
+ module WhereChainCompatibility
22
+ include ::ActiveRecord::QueryMethods
23
+ define_method :build_where,
24
+ ::ActiveRecord::QueryMethods.instance_method(:build_where)
25
+ end
26
+
27
+ def where(opts = :chain, *rest)
28
+ if block_given?
29
+ super(DSL.eval &Proc.new)
30
+ else
31
+ if opts == :chain
32
+ scope = spawn
33
+ scope.extend(WhereChainCompatibility)
34
+ ::ActiveRecord::QueryMethods::WhereChain.new(scope)
35
+ else
36
+ super
37
+ end
38
+ end
39
+ end
40
+
41
+ def visited
42
+ visit!
43
+ end
44
+
45
+ def where_unscoping(target_value)
46
+ target_value = target_value.to_s
47
+
48
+ where_values.reject! do |rel|
49
+ case rel
50
+ when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
51
+ subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
52
+ subrelation.name == target_value
53
+ when Hash
54
+ rel.stringify_keys.has_key?(target_value)
55
+ when Squeel::Nodes::Predicate
56
+ rel.expr.symbol.to_s == target_value
57
+ end
58
+ end
59
+
60
+ bind_values.reject! { |col,_| col.name == target_value }
61
+ end
62
+
63
+ def reverse_sql_order(order_query)
64
+ return super if order_query.empty?
65
+
66
+ order_query.flat_map do |o|
67
+ case o
68
+ when Arel::Attributes::Attribute
69
+ Arel::Nodes::Ascending.new(o).reverse
70
+ else
71
+ super
72
+ end
73
+ end
74
+ end
75
+
76
+ def build_arel
77
+ arel = Arel::SelectManager.new(table.engine, table)
78
+
79
+ build_joins(arel, joins_values.flatten) unless joins_values.empty?
80
+
81
+ collapse_wheres(arel, where_visit((where_values - ['']).uniq))
82
+
83
+ arel.having(*having_visit(having_values.uniq.reject{|h| h.blank?})) unless having_values.empty?
84
+
85
+ arel.take(connection.sanitize_limit(limit_value)) if limit_value
86
+ arel.skip(offset_value.to_i) if offset_value
87
+
88
+ arel.group(*group_visit(group_values.uniq.reject{|g| g.blank?})) unless group_values.empty?
89
+
90
+ build_order(arel)
91
+
92
+ build_select(arel, select_visit(select_values.uniq))
93
+
94
+ arel.distinct(distinct_value)
95
+ arel.from(build_from) if from_value
96
+ arel.lock(lock_value) if lock_value
97
+
98
+ # Reorder bind indexes when joins or subqueries include more bindings.
99
+ # Special for PostgreSQL
100
+ if arel.bind_values.any? || bind_values.size > 1
101
+ bvs = arel.bind_values + bind_values
102
+ arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
103
+ column = bvs[i].first
104
+ bp.replace connection.substitute_at(column, i)
105
+ end
106
+ end
107
+
108
+ arel
109
+ end
110
+
111
+ def build_join_dependency(manager, joins)
112
+ buckets = joins.group_by do |join|
113
+ case join
114
+ when String
115
+ :string_join
116
+ when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
117
+ :association_join
118
+ when ::ActiveRecord::Associations::JoinDependency
119
+ :stashed_join
120
+ when Arel::Nodes::Join
121
+ :join_node
122
+ when Nodes::SubqueryJoin
123
+ :subquery_join
124
+ else
125
+ raise 'unknown class: %s' % join.class.name
126
+ end
127
+ end
128
+
129
+ association_joins = buckets[:association_join] || []
130
+ stashed_association_joins = buckets[:stashed_join] || []
131
+ join_nodes = (buckets[:join_node] || []).uniq
132
+ subquery_joins = (buckets[:subquery_join] || []).uniq
133
+ string_joins = (buckets[:string_join] || []).map { |x|
134
+ x.strip
135
+ }.uniq
136
+
137
+ join_list = join_nodes + custom_join_ast(manager, string_joins)
138
+
139
+ # All of that duplication just to do this...
140
+ self.join_dependency = ::ActiveRecord::Associations::JoinDependency.new(
141
+ @klass,
142
+ association_joins,
143
+ join_list
144
+ )
145
+
146
+ self.stashed_join_dependencies = stashed_association_joins
147
+
148
+ joins = join_dependency.join_constraints stashed_association_joins
149
+
150
+ joins.each { |join| manager.from(join) }
151
+
152
+ manager.join_sources.concat(join_list)
153
+ manager.join_sources.concat(build_join_from_subquery(subquery_joins))
154
+
155
+ manager
156
+ end
157
+
158
+ alias :build_joins :build_join_dependency
159
+
160
+ # Redefine all visiting methods that depends on build_join
161
+ # All includes_values and eager_loading_values are pushed into a new
162
+ # JoinDependency class after Rails 4.1 and never are grafted,
163
+ # so that we need to walk through all JoinDependency
164
+ # to find proper alias table names.
165
+ #
166
+ # And use a ! method in Context, so we can throw an error when we can't
167
+ # find proper value in a JoinDependency
168
+ %w(where having group order).each do |visitor|
169
+ define_method "#{visitor}_visit" do |values|
170
+ join_dependencies = [join_dependency] + stashed_join_dependencies
171
+ join_dependencies.each do |jd|
172
+ context = Adapters::ActiveRecord::Context.new(jd)
173
+ begin
174
+ return Visitors.const_get("#{visitor.capitalize}Visitor").new(context).accept!(values)
175
+ rescue Adapters::ActiveRecord::Context::NoParentFoundError => e
176
+ next
177
+ end
178
+ end
179
+
180
+ # Fail Safe, call the normal accept method.
181
+ context = Adapters::ActiveRecord::Context.new(join_dependency)
182
+ Visitors.const_get("#{visitor.capitalize}Visitor").new(context).accept(values)
183
+ end
184
+ end
185
+
186
+ def build_where(opts, other = [])
187
+ case opts
188
+ when String, Array
189
+ super
190
+ else # Let's prevent PredicateBuilder from doing its thing
191
+ [opts, *other].map do |arg|
192
+ case arg
193
+ when Array # Just in case there's an array in there somewhere
194
+ super
195
+ when Hash
196
+ attributes = expand_attrs_from_hash(arg)
197
+
198
+ preprocess_attrs_with_ar(attributes)
199
+ when Squeel::Nodes::Node
200
+ arg.grep(::ActiveRecord::Relation) do |rel|
201
+ self.bind_values += rel.bind_values
202
+ end
203
+ arg
204
+ else
205
+ arg
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ def build_from
212
+ opts, name = from_visit(from_value)
213
+ case opts
214
+ when ::ActiveRecord::Relation
215
+ name ||= 'subquery'
216
+ self.bind_values = opts.bind_values + self.bind_values
217
+ opts.arel.as(name.to_s)
218
+ when ::Arel::SelectManager
219
+ name ||= 'subquery'
220
+ opts.as(name.to_s)
221
+ else
222
+ opts
223
+ end
224
+ end
225
+
226
+ def build_order(arel)
227
+ orders = order_visit(dehashified_order_values)
228
+ orders = orders.uniq.reject(&:blank?)
229
+ orders = reverse_sql_order(orders) if reverse_order_value && !reordering_value
230
+
231
+ arel.order(*orders) unless orders.empty?
232
+ end
233
+
234
+ def where_values_hash_with_squeel(relation_table_name = table_name)
235
+ equalities = find_equality_predicates(where_visit(where_values), relation_table_name)
236
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
237
+
238
+ Hash[equalities.map { |where|
239
+ name = where.left.name
240
+ [name, binds.fetch(name.to_s) { where.right }]
241
+ }]
242
+ end
243
+
244
+ def debug_sql
245
+ eager_loading? ? to_sql : arel.to_sql
246
+ end
247
+
248
+ def to_sql_with_binding_params
249
+ @to_sql ||= begin
250
+ relation = self
251
+ connection = klass.connection
252
+
253
+ if eager_loading?
254
+ find_with_associations { |rel| relation = rel }
255
+ end
256
+
257
+ ast = relation.arel.ast
258
+ binds = relation.bind_values.dup
259
+
260
+ visitor = connection.visitor.clone
261
+ visitor.class_eval do
262
+ include ::Arel::Visitors::BindVisitor
263
+ end
264
+
265
+ visitor.accept(ast) do
266
+ connection.quote(*binds.shift.reverse)
267
+ end
268
+ end
269
+ end
270
+
271
+ private
272
+
273
+ def expand_attrs_from_hash(opts)
274
+ opts = ::ActiveRecord::PredicateBuilder.resolve_column_aliases(klass, opts)
275
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
276
+
277
+ attributes.values.grep(::ActiveRecord::Relation) do |rel|
278
+ self.bind_values += rel.bind_values
279
+ end
280
+
281
+ attributes
282
+ end
283
+
284
+ def dehashified_order_values
285
+ order_values.map { |o|
286
+ if Hash === o && o.values.all? { |v| [:asc, :desc].include?(v) }
287
+ o.map { |field, dir| table[field].send(dir) }
288
+ else
289
+ o
290
+ end
291
+ }
292
+ end
293
+
294
+ def build_join_from_subquery(subquery_joins)
295
+ subquery_joins.map do |join|
296
+ join.type.new(
297
+ Arel::Nodes::TableAlias.new(join.subquery.left.arel, join.subquery.right),
298
+ Arel::Nodes::On.new(where_visit(join.constraints))
299
+ )
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/4.1/compat'
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/4.1/context'
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/4.1/preloader_extensions'
@@ -0,0 +1,108 @@
1
+ require 'squeel/adapters/active_record/4.1/relation_extensions'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module RelationExtensions
7
+
8
+ undef_method 'to_sql_with_binding_params'
9
+
10
+ attr_accessor :reverse_order_value
11
+ private :reverse_order_value, :reverse_order_value=
12
+
13
+ # We are using 4.1 version of reverse_order!
14
+ # Because 4.2 reverse the order immediately before build_order
15
+ def reverse_order!
16
+ self.reverse_order_value = !reverse_order_value
17
+ self
18
+ end
19
+
20
+ def build_join_dependency(manager, joins)
21
+ buckets = joins.group_by do |join|
22
+ case join
23
+ when String
24
+ :string_join
25
+ when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
26
+ :association_join
27
+ when ::ActiveRecord::Associations::JoinDependency
28
+ :stashed_join
29
+ when Arel::Nodes::Join
30
+ :join_node
31
+ when Nodes::SubqueryJoin
32
+ :subquery_join
33
+ else
34
+ raise 'unknown class: %s' % join.class.name
35
+ end
36
+ end
37
+
38
+ association_joins = buckets[:association_join] || []
39
+ stashed_association_joins = buckets[:stashed_join] || []
40
+ join_nodes = (buckets[:join_node] || []).uniq
41
+ subquery_joins = buckets[:subquery_join] || []
42
+ string_joins = (buckets[:string_join] || []).map { |x|
43
+ x.strip
44
+ }.uniq
45
+
46
+ join_list = join_nodes + custom_join_ast(manager, string_joins)
47
+
48
+ # All of that duplication just to do this...
49
+ self.join_dependency = ::ActiveRecord::Associations::JoinDependency.new(
50
+ @klass,
51
+ association_joins,
52
+ join_list
53
+ )
54
+
55
+ self.stashed_join_dependencies = stashed_association_joins
56
+
57
+ join_infos = join_dependency.join_constraints stashed_association_joins
58
+
59
+ join_infos.each do |info|
60
+ info.joins.each { |join| manager.from(join) }
61
+ manager.bind_values.concat info.binds
62
+ end
63
+
64
+ manager.join_sources.concat(join_list)
65
+ manager.join_sources.concat(build_join_from_subquery(subquery_joins))
66
+
67
+ manager
68
+ end
69
+
70
+ alias :build_joins :build_join_dependency
71
+
72
+ def where_values_hash_with_squeel(relation_table_name = table_name)
73
+ equalities = find_equality_predicates(where_visit(where_values), relation_table_name)
74
+
75
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
76
+
77
+ Hash[equalities.map { |where|
78
+ name = where.left.name
79
+ [name, binds.fetch(name.to_s) {
80
+ case where.right
81
+ when Array then where.right.map(&:val)
82
+ else
83
+ where.right.val
84
+ end
85
+ }]
86
+ }]
87
+ end
88
+
89
+ def expand_attrs_from_hash(opts)
90
+ opts = ::ActiveRecord::PredicateBuilder.resolve_column_aliases(klass, opts)
91
+
92
+ bv_len = bind_values.length
93
+ tmp_opts, bind_values = create_binds(opts, bv_len)
94
+ self.bind_values += bind_values
95
+
96
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
97
+ attributes.values.grep(::ActiveRecord::Relation) do |rel|
98
+ self.bind_values += rel.bind_values
99
+ end
100
+
101
+ attributes
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions