squeel 1.0.18 → 1.1.0

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 (50) hide show
  1. data/.ruby-gemset +1 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +2 -7
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +2 -2
  6. data/README.md +223 -147
  7. data/lib/generators/templates/squeel.rb +1 -1
  8. data/lib/squeel/adapters/active_record.rb +1 -1
  9. data/lib/squeel/adapters/active_record/4.0/compat.rb +17 -0
  10. data/lib/squeel/adapters/active_record/4.0/context.rb +1 -0
  11. data/lib/squeel/adapters/active_record/4.0/preloader_extensions.rb +1 -0
  12. data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +126 -0
  13. data/lib/squeel/adapters/active_record/base_extensions.rb +1 -1
  14. data/lib/squeel/adapters/active_record/context.rb +10 -10
  15. data/lib/squeel/adapters/active_record/relation_extensions.rb +24 -16
  16. data/lib/squeel/configuration.rb +1 -0
  17. data/lib/squeel/constants.rb +2 -2
  18. data/lib/squeel/context.rb +2 -2
  19. data/lib/squeel/dsl.rb +1 -1
  20. data/lib/squeel/nodes.rb +2 -0
  21. data/lib/squeel/nodes/binary.rb +1 -1
  22. data/lib/squeel/nodes/function.rb +5 -5
  23. data/lib/squeel/nodes/join.rb +2 -2
  24. data/lib/squeel/nodes/key_path.rb +10 -5
  25. data/lib/squeel/nodes/literal.rb +1 -1
  26. data/lib/squeel/nodes/nary.rb +5 -7
  27. data/lib/squeel/nodes/node.rb +6 -0
  28. data/lib/squeel/nodes/operation.rb +1 -1
  29. data/lib/squeel/nodes/order.rb +1 -1
  30. data/lib/squeel/nodes/predicate.rb +5 -5
  31. data/lib/squeel/nodes/predicate_methods.rb +11 -2
  32. data/lib/squeel/nodes/sifter.rb +2 -2
  33. data/lib/squeel/nodes/stub.rb +2 -2
  34. data/lib/squeel/nodes/unary.rb +1 -1
  35. data/lib/squeel/version.rb +1 -1
  36. data/lib/squeel/visitors/predicate_visitation.rb +6 -6
  37. data/lib/squeel/visitors/predicate_visitor.rb +1 -1
  38. data/lib/squeel/visitors/visitor.rb +20 -20
  39. data/spec/spec_helper.rb +6 -4
  40. data/spec/squeel/adapters/active_record/base_extensions_spec.rb +6 -6
  41. data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +55 -24
  42. data/spec/squeel/core_ext/symbol_spec.rb +2 -2
  43. data/spec/squeel/nodes/key_path_spec.rb +3 -3
  44. data/spec/squeel/nodes/predicate_operators_spec.rb +4 -4
  45. data/spec/squeel/visitors/predicate_visitor_spec.rb +11 -11
  46. data/spec/squeel/visitors/visitor_spec.rb +9 -9
  47. data/spec/support/models.rb +25 -7
  48. data/spec/support/schema.rb +1 -1
  49. data/squeel.gemspec +4 -4
  50. metadata +19 -12
@@ -5,7 +5,7 @@ Squeel.configure do |config|
5
5
  # config.load_core_extensions :hash
6
6
 
7
7
  # To load symbol extensions (for a subset of the old MetaWhere functionality,
8
- # via ARel predicate methods on Symbols: :name.matches, etc):
8
+ # via Arel predicate methods on Symbols: :name.matches, etc):
9
9
  #
10
10
  # NOTE: Not recommended. Learn the new DSL. Use it. Love it.
11
11
  #
@@ -1,5 +1,5 @@
1
1
  case ActiveRecord::VERSION::MAJOR
2
- when 3
2
+ when 3, 4
3
3
  ActiveRecord::Relation.send :include, Squeel::Nodes::Aliasing
4
4
  require 'squeel/adapters/active_record/join_dependency_extensions'
5
5
  require 'squeel/adapters/active_record/base_extensions'
@@ -0,0 +1,17 @@
1
+ require 'squeel/adapters/active_record/compat'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class AssociationScope
6
+ # 4.0's eval_scope doesn't play nicely with different bases. Need to do
7
+ # a similar workaround to the one for AR::Relation#merge, visiting it
8
+ def eval_scope(klass, scope)
9
+ if scope.is_a?(Relation)
10
+ scope
11
+ else
12
+ klass.unscoped.instance_exec(owner, &scope).visited
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/context'
@@ -0,0 +1 @@
1
+ require 'squeel/adapters/active_record/preloader_extensions'
@@ -0,0 +1,126 @@
1
+ require 'squeel/adapters/active_record/relation_extensions'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ module RelationExtensions
7
+
8
+ # where.not is a pain. It calls the private `build_where` method on its
9
+ # scope, and since ActiveRecord::Relation already includes the original
10
+ # ActiveRecord::QueryMethods module, we have to find a way to trick the
11
+ # scope passed to the WhereChain initializer into having the original
12
+ # behavior. This is a way to do it that avoids using alias_method_chain.
13
+ module WhereChainCompatibility
14
+ include ::ActiveRecord::QueryMethods
15
+ define_method :build_where,
16
+ ::ActiveRecord::QueryMethods.instance_method(:build_where)
17
+ end
18
+
19
+ def where(opts = :chain, *rest)
20
+ if block_given?
21
+ super(DSL.eval &Proc.new)
22
+ else
23
+ if opts == :chain
24
+ scope = spawn
25
+ scope.extend(WhereChainCompatibility)
26
+ ::ActiveRecord::QueryMethods::WhereChain.new(scope)
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+
33
+ def build_arel
34
+ arel = Arel::SelectManager.new(table.engine, table)
35
+
36
+ build_joins(arel, joins_values) unless joins_values.empty?
37
+
38
+ collapse_wheres(arel, where_visit((where_values - ['']).uniq))
39
+
40
+ arel.having(*having_visit(having_values.uniq.reject{|h| h.blank?})) unless having_values.empty?
41
+
42
+ arel.take(connection.sanitize_limit(limit_value)) if limit_value
43
+ arel.skip(offset_value.to_i) if offset_value
44
+
45
+ arel.group(*group_visit(group_values.uniq.reject{|g| g.blank?})) unless group_values.empty?
46
+
47
+ build_order(arel)
48
+
49
+ build_select(arel, select_visit(select_values.uniq))
50
+
51
+ arel.distinct(distinct_value)
52
+ arel.from(from_visit(from_value)) if from_value
53
+ arel.lock(lock_value) if lock_value
54
+
55
+ arel
56
+ end
57
+
58
+ def build_order(arel)
59
+ orders = order_visit(dehashified_order_values)
60
+ orders = reverse_sql_order(attrs_to_orderings(orders)) if reverse_order_value
61
+
62
+ orders = orders.uniq.reject(&:blank?).flat_map do |order|
63
+ case order
64
+ when Symbol
65
+ table[order].asc
66
+ when Hash
67
+ order.map { |field, dir| table[field].send(dir) }
68
+ else
69
+ order
70
+ end
71
+ end
72
+
73
+ arel.order(*orders) unless orders.empty?
74
+ end
75
+
76
+ def build_from
77
+ opts, name = from_value
78
+ case opts
79
+ when Relation
80
+ name ||= 'subquery'
81
+ opts.arel.as(name.to_s)
82
+ else
83
+ opts
84
+ end
85
+ end
86
+
87
+ # This is copied directly from 4.0.0's implementation, but adds an extra
88
+ # exclusion for Squeel::Nodes::Node to fix #248. Can be removed if/when
89
+ # rails/rails#11439 is merged.
90
+ def order!(*args)
91
+ args.flatten!
92
+ validate_order_args args
93
+
94
+ references = args.reject { |arg|
95
+ Arel::Node === arg || Squeel::Nodes::Node === arg
96
+ }
97
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
98
+ references!(references) if references.any?
99
+
100
+ # if a symbol is given we prepend the quoted table name
101
+ args = args.map { |arg|
102
+ arg.is_a?(Symbol) ? "#{quoted_table_name}.#{arg} ASC" : arg
103
+ }
104
+
105
+ self.order_values = args + self.order_values
106
+ self
107
+ end
108
+
109
+ private
110
+
111
+ def dehashified_order_values
112
+ order_values.map { |o|
113
+ if Hash === o && o.values.all? { |v| [:asc, :desc].include?(v) }
114
+ o.map { |field, dir| table[field].send(dir) }
115
+ else
116
+ o
117
+ end
118
+ }
119
+ end
120
+
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
@@ -9,7 +9,7 @@ module Squeel
9
9
 
10
10
  def sifter(name = nil)
11
11
  if Symbol === name && block_given?
12
- singleton_class.send :define_method, name,
12
+ singleton_class.send :define_method, "sifter_#{name}",
13
13
  lambda {|*args| DSL.exec(*args, &Proc.new)}
14
14
  else
15
15
  raise ArgumentError, "A name and block are required"
@@ -44,6 +44,16 @@ module Squeel
44
44
  parent
45
45
  end
46
46
 
47
+ def classify(object)
48
+ if Class === object
49
+ object
50
+ elsif object.respond_to? :base_klass
51
+ object.base_klass
52
+ else
53
+ raise ArgumentError, "#{object} can't be converted to a class"
54
+ end
55
+ end
56
+
47
57
  private
48
58
 
49
59
  def get_table(object)
@@ -58,16 +68,6 @@ module Squeel
58
68
  end
59
69
  end
60
70
 
61
- def classify(object)
62
- if Class === object
63
- object
64
- elsif object.respond_to? :active_record
65
- object.active_record
66
- else
67
- raise ArgumentError, "#{object} can't be converted to a class"
68
- end
69
- end
70
-
71
71
  def get_arel_visitor
72
72
  @engine.connection.visitor
73
73
  end
@@ -14,7 +14,12 @@ module Squeel
14
14
  # because the default #reset already does this, despite never setting
15
15
  # it anywhere that I can find. Serendipity, I say!
16
16
  def join_dependency
17
- @join_dependency ||= (build_join_dependency(table.from(table), @joins_values) && @join_dependency)
17
+ @join_dependency ||= (
18
+ build_join_dependency(
19
+ Arel::SelectManager.new(table.engine, table),
20
+ joins_values
21
+ ) && @join_dependency
22
+ )
18
23
  end
19
24
 
20
25
  %w(where having group order select from).each do |visitor|
@@ -40,6 +45,7 @@ module Squeel
40
45
  merge_resolving_duplicate_squeel_equalities(r)
41
46
  end
42
47
  else
48
+ puts r.inspect if r.is_a?(Proc)
43
49
  super(r)
44
50
  end
45
51
  end
@@ -49,12 +55,12 @@ module Squeel
49
55
  end
50
56
 
51
57
  def visit!
52
- @where_values = where_visit((@where_values - ['']).uniq)
53
- @having_values = having_visit(@having_values.uniq.reject{|h| h.blank?})
54
- # FIXME: AR barfs on ARel attributes in group_values. Workaround?
55
- # @group_values = group_visit(@group_values.uniq.reject{|g| g.blank?})
56
- @order_values = order_visit(@order_values.uniq.reject{|o| o.blank?})
57
- @select_values = select_visit(@select_values.uniq)
58
+ self.where_values = where_visit((where_values - ['']).uniq)
59
+ self.having_values = having_visit(having_values.uniq.reject{|h| h.blank?})
60
+ # FIXME: AR barfs on Arel attributes in group_values. Workaround?
61
+ # self.group_values = group_visit(group_values.uniq.reject{|g| g.blank?})
62
+ self.order_values = order_visit(order_values.uniq.reject{|o| o.blank?})
63
+ self.select_values = select_visit(select_values.uniq)
58
64
 
59
65
  self
60
66
  end
@@ -100,22 +106,22 @@ module Squeel
100
106
  buckets = joins.group_by do |join|
101
107
  case join
102
108
  when String
103
- 'string_join'
109
+ :string_join
104
110
  when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
105
- 'association_join'
111
+ :association_join
106
112
  when JoinAssociation
107
- 'stashed_join'
113
+ :stashed_join
108
114
  when Arel::Nodes::Join
109
- 'join_node'
115
+ :join_node
110
116
  else
111
117
  raise 'unknown class: %s' % join.class.name
112
118
  end
113
119
  end
114
120
 
115
- association_joins = buckets['association_join'] || []
116
- stashed_association_joins = buckets['stashed_join'] || []
117
- join_nodes = (buckets['join_node'] || []).uniq
118
- string_joins = (buckets['string_join'] || []).map { |x|
121
+ association_joins = buckets[:association_join] || []
122
+ stashed_association_joins = buckets[:stashed_join] || []
123
+ join_nodes = (buckets[:join_node] || []).uniq
124
+ string_joins = (buckets[:string_join] || []).map { |x|
119
125
  x.strip
120
126
  }.uniq
121
127
 
@@ -140,6 +146,8 @@ module Squeel
140
146
 
141
147
  manager
142
148
  end
149
+ # For 4.x adapters
150
+ alias :build_joins :build_join_dependency
143
151
 
144
152
  def includes(*args)
145
153
  if block_given? && args.empty?
@@ -335,7 +343,7 @@ module Squeel
335
343
  # DB for debug purposes without actually running the query.
336
344
  def debug_sql
337
345
  if eager_loading?
338
- including = (@eager_load_values + @includes_values).uniq
346
+ including = (eager_load_values + includes_values).uniq
339
347
  join_dependency = JoinDependency.new(@klass, including, [])
340
348
  construct_relation_for_association_find(join_dependency).to_sql
341
349
  else
@@ -33,6 +33,7 @@ module Squeel
33
33
  # @param [Symbol] sym1 :hash or :symbol
34
34
  # @param [Symbol] sym2 :hash or :symbol
35
35
  def load_core_extensions(*exts)
36
+ deprecate 'Core extensions are deprecated and will be removed in Squeel 2.0.'
36
37
  exts.each do |ext|
37
38
  require "squeel/core_ext/#{ext}"
38
39
  end
@@ -1,5 +1,5 @@
1
1
  module Squeel
2
- # Defines the default list of ARel predicates and predicate aliases
2
+ # Defines the default list of Arel predicates and predicate aliases
3
3
  module Constants
4
4
  PREDICATES = [
5
5
  :eq, :eq_any, :eq_all,
@@ -21,4 +21,4 @@ module Squeel
21
21
  :gteq => [:gte]
22
22
  }.freeze
23
23
  end
24
- end
24
+ end
@@ -42,7 +42,7 @@ module Squeel
42
42
  end
43
43
 
44
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.
45
+ # call get_table, where the real work of getting the Arel Table occurs.
46
46
  #
47
47
  # @param object A contextualizable object (this will depend on the subclass's implementation)
48
48
  # @return [Arel::Table] A table corresponding to the object param
@@ -72,4 +72,4 @@ module Squeel
72
72
  end
73
73
 
74
74
  end
75
- end
75
+ end
@@ -63,7 +63,7 @@ module Squeel
63
63
  @caller.instance_eval &block
64
64
  end
65
65
 
66
- # Shorthand for creating ARel SqlLiteral nodes.
66
+ # Shorthand for creating Arel SqlLiteral nodes.
67
67
  #
68
68
  # @param [String] string The string to convert to an SQL literal.
69
69
  # @return [Arel::Nodes::SqlLiteral] The SQL literal.
@@ -5,6 +5,8 @@ module Squeel
5
5
  end
6
6
  end
7
7
 
8
+ require 'squeel/nodes/node'
9
+
8
10
  require 'squeel/nodes/predicate_methods'
9
11
  require 'squeel/nodes/operators'
10
12
  require 'squeel/nodes/predicate_operators'
@@ -1,7 +1,7 @@
1
1
  module Squeel
2
2
  module Nodes
3
3
  # A node that represents an operation with two operands.
4
- class Binary
4
+ class Binary < Node
5
5
 
6
6
  include PredicateOperators
7
7
 
@@ -1,7 +1,7 @@
1
1
  module Squeel
2
2
  module Nodes
3
3
  # A node that represents an SQL function call
4
- class Function
4
+ class Function < Node
5
5
 
6
6
  include PredicateMethods
7
7
  include PredicateOperators
@@ -22,7 +22,7 @@ module Squeel
22
22
  alias :<= :lteq
23
23
 
24
24
  # @return [Symbol] The name of the SQL function to be called
25
- attr_reader :name
25
+ attr_reader :function_name
26
26
 
27
27
  # @return [Array] The arguments to be passed to the SQL function
28
28
  attr_reader :args
@@ -30,8 +30,8 @@ module Squeel
30
30
  # Create a node representing an SQL Function with the given name and arguments
31
31
  # @param [Symbol] name The function name
32
32
  # @param [Array] args The array of arguments to pass to the function.
33
- def initialize(name, args)
34
- @name, @args = name, args
33
+ def initialize(function_name, args)
34
+ @function_name, @args = function_name, args
35
35
  end
36
36
 
37
37
  # expand_hash_conditions_for_aggregates assumes our hash keys can be
@@ -48,7 +48,7 @@ module Squeel
48
48
 
49
49
  def eql?(other)
50
50
  self.class == other.class &&
51
- self.name.eql?(other.name) &&
51
+ self.function_name.eql?(other.function_name) &&
52
52
  self.args.eql?(other.args)
53
53
  end
54
54
 
@@ -3,7 +3,7 @@ require 'active_support/core_ext/module'
3
3
  module Squeel
4
4
  module Nodes
5
5
  # A node representing a joined association
6
- class Join
6
+ class Join < Node
7
7
  undef_method :id if method_defined?(:id)
8
8
 
9
9
  attr_reader :_join
@@ -12,7 +12,7 @@ module Squeel
12
12
 
13
13
  # Create a new Join node
14
14
  # @param [Symbol] name The association name
15
- # @param [Arel::InnerJoin, Arel::OuterJoin] type The ARel join class
15
+ # @param [Arel::InnerJoin, Arel::OuterJoin] type The Arel join class
16
16
  # @param [Class, String, Symbol] klass The polymorphic belongs_to class or class name
17
17
  def initialize(name, type = Arel::InnerJoin, klass = nil)
18
18
  @_join = Polyamorous::Join.new(name, type, klass)
@@ -4,7 +4,7 @@ module Squeel
4
4
  module Nodes
5
5
  # A node that stores a path of keys (of Symbol, Stub, or Join values) and
6
6
  # an endpoint. Used similarly to a nested hash.
7
- class KeyPath
7
+ class KeyPath < Node
8
8
  include PredicateOperators
9
9
  include Operators
10
10
 
@@ -179,12 +179,17 @@ module Squeel
179
179
 
180
180
  # Appends to the KeyPath or delegates to the endpoint, as appropriate
181
181
  # @return [KeyPath] The updated KeyPath
182
- def method_missing(method_id, *args)
182
+ def method_missing(method_id, *args, &block)
183
183
  super if method_id == :to_ary
184
184
 
185
- if endpoint.respond_to? method_id
186
- self.endpoint = endpoint.send(method_id, *args)
187
- self
185
+ if endpoint.respond_to?(method_id)
186
+ if Predicate === endpoint && method_id == :==
187
+ false
188
+ else
189
+ # TODO: We really should not mutate here.
190
+ self.endpoint = endpoint.send(method_id, *args)
191
+ self
192
+ end
188
193
  elsif Stub === endpoint || Join === endpoint
189
194
  if args.empty?
190
195
  @path << Stub.new(method_id)