squeel 1.0.18 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)