squeel_rbg 0.8.2

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 (80) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +20 -0
  5. data/README.md +398 -0
  6. data/Rakefile +19 -0
  7. data/lib/core_ext/hash.rb +13 -0
  8. data/lib/core_ext/symbol.rb +39 -0
  9. data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
  10. data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
  11. data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
  12. data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
  13. data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
  14. data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
  15. data/lib/squeel/adapters/active_record/context.rb +66 -0
  16. data/lib/squeel/adapters/active_record/join_association.rb +44 -0
  17. data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
  18. data/lib/squeel/adapters/active_record/preloader.rb +21 -0
  19. data/lib/squeel/adapters/active_record/relation.rb +351 -0
  20. data/lib/squeel/adapters/active_record.rb +28 -0
  21. data/lib/squeel/configuration.rb +54 -0
  22. data/lib/squeel/constants.rb +24 -0
  23. data/lib/squeel/context.rb +67 -0
  24. data/lib/squeel/dsl.rb +86 -0
  25. data/lib/squeel/nodes/aliasing.rb +13 -0
  26. data/lib/squeel/nodes/and.rb +9 -0
  27. data/lib/squeel/nodes/as.rb +14 -0
  28. data/lib/squeel/nodes/binary.rb +32 -0
  29. data/lib/squeel/nodes/function.rb +66 -0
  30. data/lib/squeel/nodes/join.rb +113 -0
  31. data/lib/squeel/nodes/key_path.rb +192 -0
  32. data/lib/squeel/nodes/nary.rb +45 -0
  33. data/lib/squeel/nodes/not.rb +9 -0
  34. data/lib/squeel/nodes/operation.rb +32 -0
  35. data/lib/squeel/nodes/operators.rb +43 -0
  36. data/lib/squeel/nodes/or.rb +9 -0
  37. data/lib/squeel/nodes/order.rb +53 -0
  38. data/lib/squeel/nodes/predicate.rb +71 -0
  39. data/lib/squeel/nodes/predicate_operators.rb +29 -0
  40. data/lib/squeel/nodes/stub.rb +125 -0
  41. data/lib/squeel/nodes/unary.rb +28 -0
  42. data/lib/squeel/nodes.rb +17 -0
  43. data/lib/squeel/predicate_methods.rb +14 -0
  44. data/lib/squeel/version.rb +3 -0
  45. data/lib/squeel/visitors/attribute_visitor.rb +191 -0
  46. data/lib/squeel/visitors/base.rb +112 -0
  47. data/lib/squeel/visitors/predicate_visitor.rb +319 -0
  48. data/lib/squeel/visitors/symbol_visitor.rb +48 -0
  49. data/lib/squeel/visitors.rb +3 -0
  50. data/lib/squeel.rb +28 -0
  51. data/lib/squeel_rbg.rb +5 -0
  52. data/spec/blueprints/articles.rb +5 -0
  53. data/spec/blueprints/comments.rb +5 -0
  54. data/spec/blueprints/notes.rb +3 -0
  55. data/spec/blueprints/people.rb +4 -0
  56. data/spec/blueprints/tags.rb +3 -0
  57. data/spec/console.rb +22 -0
  58. data/spec/core_ext/symbol_spec.rb +75 -0
  59. data/spec/helpers/squeel_helper.rb +21 -0
  60. data/spec/spec_helper.rb +66 -0
  61. data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
  62. data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
  63. data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
  64. data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
  65. data/spec/squeel/dsl_spec.rb +92 -0
  66. data/spec/squeel/nodes/function_spec.rb +149 -0
  67. data/spec/squeel/nodes/join_spec.rb +47 -0
  68. data/spec/squeel/nodes/key_path_spec.rb +100 -0
  69. data/spec/squeel/nodes/operation_spec.rb +149 -0
  70. data/spec/squeel/nodes/operators_spec.rb +87 -0
  71. data/spec/squeel/nodes/order_spec.rb +30 -0
  72. data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
  73. data/spec/squeel/nodes/predicate_spec.rb +50 -0
  74. data/spec/squeel/nodes/stub_spec.rb +198 -0
  75. data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
  76. data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
  77. data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
  78. data/spec/support/schema.rb +104 -0
  79. data/squeel.gemspec +43 -0
  80. metadata +246 -0
@@ -0,0 +1,83 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module JoinDependency
7
+
8
+ # Yes, I'm using alias_method_chain here. No, I don't feel too
9
+ # bad about it. JoinDependency, or, to call it by its full proper
10
+ # name, ::ActiveRecord::Associations::JoinDependency, is one of the
11
+ # most "for internal use only" chunks of ActiveRecord.
12
+ def self.included(base)
13
+ base.class_eval do
14
+ alias_method_chain :build, :squeel
15
+ alias_method_chain :graft, :squeel
16
+ end
17
+ end
18
+
19
+ def graft_with_squeel(*associations)
20
+ associations.each do |association|
21
+ unless join_associations.detect {|a| association == a}
22
+ if association.reflection.options[:polymorphic]
23
+ build(Nodes::Join.new(association.reflection.name, association.join_type, association.reflection.klass),
24
+ association.find_parent_in(self) || join_base, association.join_type)
25
+ else
26
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
27
+ end
28
+ end
29
+ end
30
+ self
31
+ end
32
+
33
+ def build_with_squeel(associations, parent = nil, join_type = Arel::InnerJoin)
34
+ associations = associations.symbol if Nodes::Stub === associations
35
+
36
+ case associations
37
+ when Nodes::Join
38
+ parent ||= join_parts.last
39
+ reflection = parent.reflections[associations._name] or
40
+ raise ::ActiveRecord::ConfigurationError, "Association named '#{ associations._name }' was not found; perhaps you misspelled it?"
41
+
42
+ unless join_association = find_join_association_respecting_polymorphism(reflection, parent, associations._klass)
43
+ @reflections << reflection
44
+ join_association = build_join_association_respecting_polymorphism(reflection, parent, associations._klass)
45
+ join_association.join_type = associations._type
46
+ @join_parts << join_association
47
+ cache_joined_association(join_association)
48
+ end
49
+
50
+ join_association
51
+ when Nodes::KeyPath
52
+ parent ||= join_parts.last
53
+ associations.path_with_endpoint.each do |key|
54
+ parent = build(key, parent, join_type)
55
+ end
56
+ parent
57
+ else
58
+ build_without_squeel(associations, parent, join_type)
59
+ end
60
+ end
61
+
62
+ def find_join_association_respecting_polymorphism(reflection, parent, klass)
63
+ if association = find_join_association(reflection, parent)
64
+ unless reflection.options[:polymorphic]
65
+ association
66
+ else
67
+ association if association.active_record == klass
68
+ end
69
+ end
70
+ end
71
+
72
+ def build_join_association_respecting_polymorphism(reflection, parent, klass)
73
+ if reflection.options[:polymorphic] && klass
74
+ JoinAssociation.new(reflection, self, parent, klass)
75
+ else
76
+ JoinAssociation.new(reflection, self, parent)
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,21 @@
1
+ module Squeel
2
+ module Adapters
3
+ module ActiveRecord
4
+ module Preloader
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ alias_method_chain :run, :squeel
9
+ end
10
+ end
11
+
12
+ def run_with_squeel
13
+ unless records.empty?
14
+ Visitors::SymbolVisitor.new.accept(associations).each { |association| preload(association) }
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,351 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module Relation
7
+
8
+ JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
9
+ JoinDependency = ::ActiveRecord::Associations::JoinDependency
10
+
11
+ attr_writer :join_dependency
12
+ private :join_dependency=
13
+
14
+ # Returns a JoinDependency for the current relation.
15
+ #
16
+ # We don't need to clear out @join_dependency by overriding #reset, because
17
+ # the default #reset already does this, despite never setting it anywhere that
18
+ # I can find. Serendipity, I say!
19
+ def join_dependency
20
+ @join_dependency ||= (build_join_dependency(table.from(table), @joins_values) && @join_dependency)
21
+ end
22
+
23
+ def predicate_visitor
24
+ Visitors::PredicateVisitor.new(
25
+ Context.new(join_dependency)
26
+ )
27
+ end
28
+
29
+ def attribute_visitor
30
+ Visitors::AttributeVisitor.new(
31
+ Context.new(join_dependency)
32
+ )
33
+ end
34
+
35
+ # We need to be able to support merging two relations that have a different
36
+ # base class. Stock ActiveRecord doesn't have to do anything too special, because
37
+ # it's already created predicates out of the where_values by now, and they're
38
+ # already bound to the proper table.
39
+ #
40
+ # Squeel, on the other hand, needs to do its best to ensure the predicates are still
41
+ # winding up against the proper table. Merging relations is a really nifty shortcut
42
+ # but another little corner of ActiveRecord where the magic quickly fades. :(
43
+ def merge(r, association_name = nil)
44
+ if association_name || relation_with_different_base?(r)
45
+ r = r.clone
46
+ association_name ||= infer_association_for_relation_merge(r)
47
+ prepare_relation_for_association_merge!(r, association_name)
48
+ end
49
+
50
+ super(r)
51
+ end
52
+
53
+ def relation_with_different_base?(r)
54
+ ::ActiveRecord::Relation === r &&
55
+ base_class.name != r.klass.base_class.name
56
+ end
57
+
58
+ def infer_association_for_relation_merge(r)
59
+ default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}
60
+ default_association ? default_association.name : r.table_name.to_sym
61
+ end
62
+
63
+ def prepare_relation_for_association_merge!(r, association_name)
64
+ r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_accept?(w) ? {association_name => w} : w}
65
+ r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_accept?(h) ? {association_name => h} : h}
66
+ r.group_values.map! {|g| Squeel::Visitors::AttributeVisitor.can_accept?(g) ? {association_name => g} : g}
67
+ r.order_values.map! {|o| Squeel::Visitors::AttributeVisitor.can_accept?(o) ? {association_name => o} : o}
68
+ r.select_values.map! {|s| Squeel::Visitors::AttributeVisitor.can_accept?(s) ? {association_name => s} : s}
69
+ r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(j.class) ? {association_name => j} : j}
70
+ r.includes_values.map! {|i| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(i.class) ? {association_name => i} : i}
71
+ end
72
+
73
+ def build_arel
74
+ arel = table.from table
75
+
76
+ build_join_dependency(arel, @joins_values) unless @joins_values.empty?
77
+
78
+ predicate_viz = predicate_visitor
79
+ attribute_viz = attribute_visitor
80
+
81
+ collapse_wheres(arel, predicate_viz.accept((@where_values - ['']).uniq))
82
+
83
+ arel.having(*predicate_viz.accept(@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) if @offset_value
87
+
88
+ arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
89
+
90
+ order = @reorder_value ? @reorder_value : @order_values
91
+ order = attribute_viz.accept(order.uniq.reject{|o| o.blank?})
92
+ order = reverse_sql_order(sqlify_order(order)) if @reverse_order_value
93
+ arel.order(*order) unless order.empty?
94
+
95
+ build_select(arel, attribute_viz.accept(@select_values.uniq))
96
+
97
+ arel.from(@from_value) if @from_value
98
+ arel.lock(@lock_value) if @lock_value
99
+
100
+ arel
101
+ end
102
+
103
+ # reverse_sql_order doesn't understand ARel ordering nodes, so we
104
+ # need to convert them to their corresponding SQL
105
+ def sqlify_order(order)
106
+ order.map do |o|
107
+ o.respond_to?(:to_sql) ? o.to_sql : o
108
+ end
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 JoinAssociation
119
+ 'stashed_join'
120
+ when Arel::Nodes::Join
121
+ 'join_node'
122
+ else
123
+ raise 'unknown class: %s' % join.class.name
124
+ end
125
+ end
126
+
127
+ association_joins = buckets['association_join'] || []
128
+ stashed_association_joins = buckets['stashed_join'] || []
129
+ join_nodes = buckets['join_node'] || []
130
+ string_joins = (buckets['string_join'] || []).map { |x|
131
+ x.strip
132
+ }.uniq
133
+
134
+ join_list = custom_join_ast(manager, string_joins)
135
+
136
+ # All of this duplication just to add
137
+ self.join_dependency = JoinDependency.new(
138
+ @klass,
139
+ association_joins,
140
+ join_list
141
+ )
142
+
143
+ join_nodes.each do |join|
144
+ join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
145
+ end
146
+
147
+ join_dependency.graft(*stashed_association_joins)
148
+
149
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
150
+
151
+ join_dependency.join_associations.each do |association|
152
+ association.join_to(manager)
153
+ end
154
+
155
+ manager.join_sources.concat join_nodes.uniq
156
+ manager.join_sources.concat join_list
157
+
158
+ manager
159
+ end
160
+
161
+ def includes(*args)
162
+ if block_given? && args.empty?
163
+ super(DSL.eval &Proc.new)
164
+ else
165
+ super
166
+ end
167
+ end
168
+
169
+ def preload(*args)
170
+ if block_given? && args.empty?
171
+ super(DSL.eval &Proc.new)
172
+ else
173
+ super
174
+ end
175
+ end
176
+
177
+ def eager_load(*args)
178
+ if block_given? && args.empty?
179
+ super(DSL.eval &Proc.new)
180
+ else
181
+ super
182
+ end
183
+ end
184
+
185
+ def select(value = Proc.new)
186
+ if block_given? && Proc === value
187
+ if value.arity > 0
188
+ to_a.select {|*block_args| value.call(*block_args)}
189
+ else
190
+ relation = clone
191
+ relation.select_values += Array.wrap(DSL.eval &value)
192
+ relation
193
+ end
194
+ else
195
+ super
196
+ end
197
+ end
198
+
199
+ def group(*args)
200
+ if block_given? && args.empty?
201
+ super(DSL.eval &Proc.new)
202
+ else
203
+ super
204
+ end
205
+ end
206
+
207
+ def order(*args)
208
+ if block_given? && args.empty?
209
+ super(DSL.eval &Proc.new)
210
+ else
211
+ super
212
+ end
213
+ end
214
+
215
+ def reorder(*args)
216
+ if block_given? && args.empty?
217
+ super(DSL.eval &Proc.new)
218
+ else
219
+ super
220
+ end
221
+ end
222
+
223
+ def joins(*args)
224
+ if block_given? && args.empty?
225
+ super(DSL.eval &Proc.new)
226
+ else
227
+ super
228
+ end
229
+ end
230
+
231
+ def where(opts = Proc.new, *rest)
232
+ if block_given? && Proc === opts
233
+ super(DSL.eval &opts)
234
+ else
235
+ super
236
+ end
237
+ end
238
+
239
+ def having(*args)
240
+ if block_given? && args.empty?
241
+ super(DSL.eval &Proc.new)
242
+ else
243
+ super
244
+ end
245
+ end
246
+
247
+ def build_where(opts, other = [])
248
+ case opts
249
+ when String, Array
250
+ super
251
+ else # Let's prevent PredicateBuilder from doing its thing
252
+ [opts, *other].map do |arg|
253
+ case arg
254
+ when Array # Just in case there's an array in there somewhere
255
+ @klass.send(:sanitize_sql, arg)
256
+ when Hash
257
+ @klass.send(:expand_hash_conditions_for_aggregates, arg)
258
+ else
259
+ arg
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ def collapse_wheres(arel, wheres)
266
+ wheres = [wheres] unless Array === wheres
267
+ binaries = wheres.grep(Arel::Nodes::Binary)
268
+
269
+ groups = binaries.group_by {|b| [b.class, b.left]}
270
+
271
+ groups.each do |_, bins|
272
+ arel.where(Arel::Nodes::And.new(bins))
273
+ end
274
+
275
+ (wheres - binaries).each do |where|
276
+ where = Arel.sql(where) if String === where
277
+ arel.where(Arel::Nodes::Grouping.new(where))
278
+ end
279
+ end
280
+
281
+ def find_equality_predicates(nodes)
282
+ nodes.map { |node|
283
+ case node
284
+ when Arel::Nodes::Equality
285
+ node if node.left.relation.name == table_name
286
+ when Arel::Nodes::Grouping
287
+ find_equality_predicates([node.expr])
288
+ when Arel::Nodes::And
289
+ find_equality_predicates(node.children)
290
+ else
291
+ nil
292
+ end
293
+ }.compact.flatten
294
+ end
295
+
296
+ # Simulate the logic that occurs in #to_a
297
+ #
298
+ # This will let us get a dump of the SQL that will be run against the
299
+ # DB for debug purposes without actually running the query.
300
+ def debug_sql
301
+ if eager_loading?
302
+ including = (@eager_load_values + @includes_values).uniq
303
+ join_dependency = JoinDependency.new(@klass, including, [])
304
+ construct_relation_for_association_find(join_dependency).to_sql
305
+ else
306
+ arel.to_sql
307
+ end
308
+ end
309
+
310
+ ### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
311
+ # ...
312
+ # ...
313
+ # ...
314
+ # Since you're still looking, let me explain this horrible
315
+ # transgression you see before you.
316
+ # You see, Relation#where_values_hash is defined on the
317
+ # ActiveRecord::Relation class. Since it's defined there, but
318
+ # I would very much like to modify its behavior, I have three
319
+ # choices.
320
+ #
321
+ # 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
322
+ # class, and make an attempt to usurp all of the various calls
323
+ # to methods on ActiveRecord::Relation by doing some really
324
+ # evil stuff with constant reassignment, all for the sake of
325
+ # being able to use super().
326
+ #
327
+ # 2. Submit a patch to Rails core, breaking this method off into
328
+ # another module, all for my own selfish desire to use super()
329
+ # while mucking about in Rails internals.
330
+ #
331
+ # 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
332
+ #
333
+ # I opted to go with #3. Except for the hail Hansson thing.
334
+ # Unless you're DHH, in which case, I totally said them.
335
+
336
+ def self.included(base)
337
+ base.class_eval do
338
+ alias_method_chain :where_values_hash, :squeel
339
+ end
340
+ end
341
+
342
+ def where_values_hash_with_squeel
343
+ equalities = find_equality_predicates(predicate_visitor.accept(@where_values))
344
+
345
+ Hash[equalities.map { |where| [where.left.name, where.right] }]
346
+ end
347
+
348
+ end
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,28 @@
1
+ case ActiveRecord::VERSION::MAJOR
2
+ when 3
3
+ case ActiveRecord::VERSION::MINOR
4
+ when 0
5
+ require 'squeel/adapters/active_record/3.0/compat'
6
+ require 'squeel/adapters/active_record/3.0/relation'
7
+ require 'squeel/adapters/active_record/3.0/join_dependency'
8
+ require 'squeel/adapters/active_record/3.0/join_association'
9
+ require 'squeel/adapters/active_record/3.0/association_preload'
10
+ require 'squeel/adapters/active_record/3.0/context'
11
+
12
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::Relation
13
+ ActiveRecord::Associations::ClassMethods::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependency
14
+ ActiveRecord::Base.extend Squeel::Adapters::ActiveRecord::AssociationPreload
15
+ else
16
+ require 'squeel/adapters/active_record/relation'
17
+ require 'squeel/adapters/active_record/join_dependency'
18
+ require 'squeel/adapters/active_record/join_association'
19
+ require 'squeel/adapters/active_record/preloader'
20
+ require 'squeel/adapters/active_record/context'
21
+
22
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::Relation
23
+ ActiveRecord::Associations::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependency
24
+ ActiveRecord::Associations::Preloader.send :include, Squeel::Adapters::ActiveRecord::Preloader
25
+ end
26
+ else
27
+ raise NotImplementedError, "Squeel does not support ActiveRecord version #{ActiveRecord::VERSION::STRING}"
28
+ end
@@ -0,0 +1,54 @@
1
+ require 'squeel/constants'
2
+ require 'squeel/predicate_methods'
3
+
4
+ module Squeel
5
+ # The Squeel configuration module. The Squeel module extends this to provide its
6
+ # configuration capability.
7
+ module Configuration
8
+
9
+ # Start a Squeel configuration block in an initializer.
10
+ #
11
+ # @yield [config] A configuration block
12
+ #
13
+ # @example Load hash and symbol extensions
14
+ # Squeel.configure do |config|
15
+ # config.load_core_extensions :hash, :symbol
16
+ # end
17
+ #
18
+ # @example Alias a predicate
19
+ # Squeel.configure do |config|
20
+ # config.alias_ptedicate :is_less_than, :lt
21
+ # end
22
+ def configure
23
+ yield self
24
+ end
25
+
26
+ # Load core extensions for Hash, Symbol, or both
27
+ #
28
+ # @overload load_core_extensions(sym)
29
+ # Load a single extension
30
+ # @param [Symbol] sym :hash or :symbol
31
+ # @overload load_core_extensions(sym1, sym2)
32
+ # Load both extensions
33
+ # @param [Symbol] sym1 :hash or :symbol
34
+ # @param [Symbol] sym2 :hash or :symbol
35
+ def load_core_extensions(*exts)
36
+ exts.each do |ext|
37
+ require "core_ext/#{ext}"
38
+ end
39
+ end
40
+
41
+ # Create an alias to an existing predication method. The _any/_all variations will
42
+ # be created automatically.
43
+ # @param [Symbol] new_name The alias name
44
+ # @param [Symbol] existing_name The existing predicate name
45
+ # @raise [ArgumentError] The existing name is an _any/_all variation, and not the original predicate name
46
+ def alias_predicate(new_name, existing_name)
47
+ raise ArgumentError, 'the existing name should be the base name, not an _any/_all variation' if existing_name.to_s =~ /(_any|_all)$/
48
+ ['', '_any', '_all'].each do |suffix|
49
+ PredicateMethods.class_eval "alias :#{new_name}#{suffix} :#{existing_name}#{suffix} unless defined?(#{new_name}#{suffix})"
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ module Squeel
2
+ # Defines the default list of ARel predicates and predicate aliases
3
+ module Constants
4
+ PREDICATES = [
5
+ :eq, :eq_any, :eq_all,
6
+ :not_eq, :not_eq_any, :not_eq_all,
7
+ :matches, :matches_any, :matches_all,
8
+ :does_not_match, :does_not_match_any, :does_not_match_all,
9
+ :lt, :lt_any, :lt_all,
10
+ :lteq, :lteq_any, :lteq_all,
11
+ :gt, :gt_any, :gt_all,
12
+ :gteq, :gteq_any, :gteq_all,
13
+ :in, :in_any, :in_all,
14
+ :not_in, :not_in_any, :not_in_all
15
+ ].freeze
16
+
17
+ PREDICATE_ALIASES = {
18
+ :matches => [:like],
19
+ :does_not_match => [:not_like],
20
+ :lteq => [:lte],
21
+ :gteq => [:gte]
22
+ }.freeze
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ require 'arel'
2
+
3
+ module Squeel
4
+ # @abstract Subclass and implement {#traverse}, #{find} and {#get_table}
5
+ # to create a Context that supports a given ORM.
6
+ class Context
7
+ attr_reader :base, :engine, :arel_visitor
8
+
9
+ # The Squeel context expects some kind of context object that is
10
+ # representative of the current joins in a query in order to return
11
+ # appropriate tables. Again, in the case of an ActiveRecord context,
12
+ # this will be a JoinDependency. Subclasses are expected to set the
13
+ # <tt>@base</tt>, <tt>@engine</tt>, and <tt>@arel_visitor</tt>
14
+ # instance variables to appropriate values for use in their implementations
15
+ # of other required methods.
16
+ #
17
+ # @param object The object the context will use for contextualization
18
+ def initialize(object)
19
+ @object = object
20
+ @tables = Hash.new {|hash, key| hash[key] = get_table(key)}
21
+ end
22
+
23
+ # This method should find a given object inside the context.
24
+ #
25
+ # @param object The object to find
26
+ # @param parent The parent object, if applicable
27
+ # @return a valid "parent" or contextualizable object
28
+ def find(object, parent = @base)
29
+ raise NotImplementedError, "Subclasses must implement public method find"
30
+ end
31
+
32
+ # This method should traverse a keypath and return an object for use
33
+ # in future calls to #traverse, #find, or #contextualize.
34
+ #
35
+ # @param [Nodes::KeyPath] keypath The keypath to traverse
36
+ # @param parent The parent object from which traversal should start.
37
+ # @param [Boolean] include_endpoint Whether or not the KeyPath's
38
+ # endpoint should be treated as a traversable key
39
+ # @return a valid "parent" or contextualizable object
40
+ def traverse(keypath, parent = @base, include_endpoint = false)
41
+ raise NotImplementedError, "Subclasses must implement public method traverse"
42
+ end
43
+
44
+ # This method, as implemented, just makes use of the table cache, which will
45
+ # call get_table, where the real work of getting the ARel Table occurs.
46
+ #
47
+ # @param object A contextualizable object (this will depend on the subclass's implementation)
48
+ # @return [Arel::Table] A table corresponding to the object param
49
+ def contextualize(object)
50
+ @tables[object]
51
+ end
52
+
53
+ private
54
+
55
+ # Returns an Arel::Table that's appropriate for the object it's been sent.
56
+ # What's "appropriate"? Well, that's up to the implementation to decide, but
57
+ # it should probably generate a table that is least likely to result in invalid
58
+ # SQL.
59
+ #
60
+ # @param object A contextualizable object (this will depend on the subclass's implementation)
61
+ # @return [Arel::Table] A table corresponding to the object param.
62
+ def get_table(object)
63
+ raise NotImplementedError, "Subclasses must implement private method get_table"
64
+ end
65
+
66
+ end
67
+ end