squeel 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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