squeel 0.5.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 (72) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +41 -0
  5. data/Rakefile +19 -0
  6. data/lib/core_ext/hash.rb +13 -0
  7. data/lib/core_ext/symbol.rb +36 -0
  8. data/lib/squeel.rb +26 -0
  9. data/lib/squeel/adapters/active_record.rb +6 -0
  10. data/lib/squeel/adapters/active_record/join_association.rb +90 -0
  11. data/lib/squeel/adapters/active_record/join_dependency.rb +68 -0
  12. data/lib/squeel/adapters/active_record/relation.rb +292 -0
  13. data/lib/squeel/configuration.rb +25 -0
  14. data/lib/squeel/constants.rb +23 -0
  15. data/lib/squeel/contexts/join_dependency_context.rb +74 -0
  16. data/lib/squeel/dsl.rb +31 -0
  17. data/lib/squeel/nodes.rb +10 -0
  18. data/lib/squeel/nodes/and.rb +8 -0
  19. data/lib/squeel/nodes/binary.rb +23 -0
  20. data/lib/squeel/nodes/function.rb +84 -0
  21. data/lib/squeel/nodes/join.rb +51 -0
  22. data/lib/squeel/nodes/key_path.rb +127 -0
  23. data/lib/squeel/nodes/nary.rb +35 -0
  24. data/lib/squeel/nodes/not.rb +8 -0
  25. data/lib/squeel/nodes/operation.rb +23 -0
  26. data/lib/squeel/nodes/operators.rb +27 -0
  27. data/lib/squeel/nodes/or.rb +8 -0
  28. data/lib/squeel/nodes/order.rb +35 -0
  29. data/lib/squeel/nodes/predicate.rb +49 -0
  30. data/lib/squeel/nodes/predicate_operators.rb +17 -0
  31. data/lib/squeel/nodes/stub.rb +113 -0
  32. data/lib/squeel/nodes/unary.rb +22 -0
  33. data/lib/squeel/predicate_methods.rb +22 -0
  34. data/lib/squeel/predicate_methods/function.rb +9 -0
  35. data/lib/squeel/predicate_methods/predicate.rb +11 -0
  36. data/lib/squeel/predicate_methods/stub.rb +9 -0
  37. data/lib/squeel/predicate_methods/symbol.rb +9 -0
  38. data/lib/squeel/version.rb +3 -0
  39. data/lib/squeel/visitors.rb +3 -0
  40. data/lib/squeel/visitors/base.rb +46 -0
  41. data/lib/squeel/visitors/order_visitor.rb +107 -0
  42. data/lib/squeel/visitors/predicate_visitor.rb +179 -0
  43. data/lib/squeel/visitors/select_visitor.rb +103 -0
  44. data/spec/blueprints/articles.rb +5 -0
  45. data/spec/blueprints/comments.rb +5 -0
  46. data/spec/blueprints/notes.rb +3 -0
  47. data/spec/blueprints/people.rb +4 -0
  48. data/spec/blueprints/tags.rb +3 -0
  49. data/spec/console.rb +22 -0
  50. data/spec/core_ext/symbol_spec.rb +68 -0
  51. data/spec/helpers/squeel_helper.rb +5 -0
  52. data/spec/spec_helper.rb +30 -0
  53. data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
  54. data/spec/squeel/adapters/active_record/join_depdendency_spec.rb +60 -0
  55. data/spec/squeel/adapters/active_record/relation_spec.rb +437 -0
  56. data/spec/squeel/contexts/join_dependency_context_spec.rb +43 -0
  57. data/spec/squeel/dsl_spec.rb +73 -0
  58. data/spec/squeel/nodes/function_spec.rb +149 -0
  59. data/spec/squeel/nodes/join_spec.rb +27 -0
  60. data/spec/squeel/nodes/key_path_spec.rb +92 -0
  61. data/spec/squeel/nodes/operation_spec.rb +149 -0
  62. data/spec/squeel/nodes/operators_spec.rb +87 -0
  63. data/spec/squeel/nodes/order_spec.rb +30 -0
  64. data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
  65. data/spec/squeel/nodes/predicate_spec.rb +92 -0
  66. data/spec/squeel/nodes/stub_spec.rb +178 -0
  67. data/spec/squeel/visitors/order_visitor_spec.rb +128 -0
  68. data/spec/squeel/visitors/predicate_visitor_spec.rb +267 -0
  69. data/spec/squeel/visitors/select_visitor_spec.rb +115 -0
  70. data/spec/support/schema.rb +101 -0
  71. data/squeel.gemspec +44 -0
  72. metadata +221 -0
@@ -0,0 +1,25 @@
1
+ require 'squeel/constants'
2
+ require 'squeel/predicate_methods'
3
+
4
+ module Squeel
5
+ module Configuration
6
+
7
+ def configure
8
+ yield self
9
+ end
10
+
11
+ def load_core_extensions(*exts)
12
+ exts.each do |ext|
13
+ require "core_ext/#{ext}"
14
+ end
15
+ end
16
+
17
+ def alias_predicate(new_name, existing_name)
18
+ raise ArgumentError, 'the existing name should be the base name, not an _any/_all variation' if existing_name.to_s =~ /(_any|_all)$/
19
+ ['', '_any', '_all'].each do |suffix|
20
+ PredicateMethods.class_eval "alias :#{new_name}#{suffix} :#{existing_name}#{suffix} unless defined?(#{new_name}#{suffix})"
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Squeel
2
+ module Constants
3
+ PREDICATES = [
4
+ :eq, :eq_any, :eq_all,
5
+ :not_eq, :not_eq_any, :not_eq_all,
6
+ :matches, :matches_any, :matches_all,
7
+ :does_not_match, :does_not_match_any, :does_not_match_all,
8
+ :lt, :lt_any, :lt_all,
9
+ :lteq, :lteq_any, :lteq_all,
10
+ :gt, :gt_any, :gt_all,
11
+ :gteq, :gteq_any, :gteq_all,
12
+ :in, :in_any, :in_all,
13
+ :not_in, :not_in_any, :not_in_all
14
+ ].freeze
15
+
16
+ PREDICATE_ALIASES = {
17
+ :matches => [:like],
18
+ :does_not_match => [:not_like],
19
+ :lteq => [:lte],
20
+ :gteq => [:gte]
21
+ }.freeze
22
+ end
23
+ end
@@ -0,0 +1,74 @@
1
+ require 'active_record'
2
+
3
+ module Squeel
4
+ # Because the AR::Associations namespace is insane
5
+ JoinPart = ActiveRecord::Associations::JoinDependency::JoinPart
6
+
7
+ module Contexts
8
+ class JoinDependencyContext
9
+ attr_reader :base, :engine, :arel_visitor
10
+
11
+ def initialize(join_dependency)
12
+ @join_dependency = join_dependency
13
+ @base = join_dependency.join_base
14
+ @engine = @base.arel_engine
15
+ @arel_visitor = Arel::Visitors.visitor_for @engine
16
+ @default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
17
+ @tables = Hash.new {|hash, key| hash[key] = get_table(key)}
18
+ end
19
+
20
+ def find(object, parent = @base)
21
+ if JoinPart === parent
22
+ object = object.to_sym if String === object
23
+ case object
24
+ when Symbol, Nodes::Stub
25
+ @join_dependency.join_associations.detect { |j|
26
+ j.reflection.name == object.to_sym && j.parent == parent
27
+ }
28
+ when Nodes::Join
29
+ @join_dependency.join_associations.detect { |j|
30
+ j.reflection.name == object.name && j.parent == parent &&
31
+ (object.polymorphic? ? j.reflection.klass == object.klass : true)
32
+ }
33
+ else
34
+ @join_dependency.join_associations.detect { |j|
35
+ j.reflection == object && j.parent == parent
36
+ }
37
+ end
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ def traverse(keypath, parent = @base, include_endpoint = false)
44
+ parent = @base if keypath.absolute?
45
+ keypath.path.each do |key|
46
+ parent = find(key, parent)
47
+ end
48
+ parent = find(keypath.endpoint, parent) if include_endpoint
49
+
50
+ parent
51
+ end
52
+
53
+ def contextualize(object)
54
+ @tables[object]
55
+ end
56
+
57
+ def sanitize_sql(conditions, parent)
58
+ parent.active_record.send(:sanitize_sql, conditions, parent.aliased_table_name)
59
+ end
60
+
61
+ private
62
+
63
+ def get_table(object)
64
+ if [Symbol, Nodes::Stub].include?(object.class)
65
+ Arel::Table.new(object.to_sym, :engine => @engine)
66
+ elsif object.respond_to?(:aliased_table_name)
67
+ Arel::Table.new(object.table_name, :as => object.aliased_table_name, :engine => @engine)
68
+ else
69
+ @default_table
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
data/lib/squeel/dsl.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Squeel
2
+ class DSL
3
+
4
+ Squeel.evil_things do
5
+ (instance_methods + private_instance_methods).each do |method|
6
+ unless method.to_s =~ /^(__|instance_eval)/
7
+ undef_method method
8
+ end
9
+ end
10
+ end
11
+
12
+ def self.evaluate(&block)
13
+ if block.arity > 0
14
+ yield self.new
15
+ else
16
+ self.new.instance_eval(&block)
17
+ end
18
+ end
19
+
20
+ def method_missing(method_id, *args)
21
+ if args.empty?
22
+ Nodes::Stub.new method_id
23
+ elsif (args.size == 1) && (Class === args[0])
24
+ Nodes::Join.new(method_id, Arel::InnerJoin, args[0])
25
+ else
26
+ Nodes::Function.new method_id, args
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ require 'squeel/nodes/stub'
2
+ require 'squeel/nodes/key_path'
3
+ require 'squeel/nodes/predicate'
4
+ require 'squeel/nodes/function'
5
+ require 'squeel/nodes/operation'
6
+ require 'squeel/nodes/order'
7
+ require 'squeel/nodes/and'
8
+ require 'squeel/nodes/or'
9
+ require 'squeel/nodes/not'
10
+ require 'squeel/nodes/join'
@@ -0,0 +1,8 @@
1
+ require 'squeel/nodes/nary'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ class And < Nary
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ require 'squeel/nodes/predicate_operators'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ class Binary
6
+ include PredicateOperators
7
+
8
+ attr_reader :left, :right
9
+
10
+ def initialize(left, right)
11
+ @left, @right = left, right
12
+ end
13
+
14
+ def eql?(other)
15
+ self.class == other.class &&
16
+ self.left == other.left &&
17
+ self.right == other.right
18
+ end
19
+
20
+ alias :== :eql?
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,84 @@
1
+ require 'squeel/predicate_methods'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ class Function
6
+
7
+ include PredicateMethods
8
+ include Operators
9
+
10
+ attr_reader :name, :args, :alias
11
+
12
+ def initialize(name, args)
13
+ @name, @args = name, args
14
+ end
15
+
16
+ def as(alias_name)
17
+ @alias = alias_name.to_s
18
+ self
19
+ end
20
+
21
+ def ==(value)
22
+ Predicate.new self, :eq, value
23
+ end
24
+
25
+ def asc
26
+ Order.new self, 1
27
+ end
28
+
29
+ def desc
30
+ Order.new self, -1
31
+ end
32
+
33
+ # Won't work on Ruby 1.8.x so need to do this conditionally
34
+ define_method('!=') do |value|
35
+ Predicate.new(self, :not_eq, value)
36
+ end if respond_to?('!=')
37
+
38
+ def ^(value)
39
+ Predicate.new self, :not_eq, value
40
+ end
41
+
42
+ def >>(value)
43
+ Predicate.new self, :in, value
44
+ end
45
+
46
+ def <<(value)
47
+ Predicate.new self, :not_in, value
48
+ end
49
+
50
+ def =~(value)
51
+ Predicate.new self, :matches, value
52
+ end
53
+
54
+ # Won't work on Ruby 1.8.x so need to do this conditionally
55
+ define_method('!~') do |value|
56
+ Predicate.new(self, :does_not_match, value)
57
+ end if respond_to?('!~')
58
+
59
+ def >(value)
60
+ Predicate.new self, :gt, value
61
+ end
62
+
63
+ def >=(value)
64
+ Predicate.new self, :gteq, value
65
+ end
66
+
67
+ def <(value)
68
+ Predicate.new self, :lt, value
69
+ end
70
+
71
+ def <=(value)
72
+ Predicate.new self, :lteq, value
73
+ end
74
+
75
+ # expand_hash_conditions_for_aggregates assumes our hash keys can be
76
+ # converted to symbols, so this has to be implemented, but it doesn't
77
+ # really have to do anything useful.
78
+ def to_sym
79
+ nil
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,51 @@
1
+ module Squeel
2
+ module Nodes
3
+ class Join
4
+ attr_reader :name, :type, :klass
5
+
6
+ def initialize(name, type = Arel::InnerJoin, klass = nil)
7
+ @name, @type = name, type
8
+ @klass = convert_to_class(klass) if klass
9
+ end
10
+
11
+ def inner
12
+ @type = Arel::InnerJoin
13
+ self
14
+ end
15
+
16
+ def outer
17
+ @type = Arel::OuterJoin
18
+ self
19
+ end
20
+
21
+ def klass=(class_or_class_name)
22
+ @klass = convert_to_class(class_or_class_name)
23
+ end
24
+
25
+ def polymorphic?
26
+ @klass
27
+ end
28
+
29
+ # expand_hash_conditions_for_aggregates assumes our hash keys can be
30
+ # converted to symbols, so this has to be implemented, but it doesn't
31
+ # really have to do anything useful.
32
+ def to_sym
33
+ nil
34
+ end
35
+
36
+ private
37
+
38
+ def convert_to_class(value)
39
+ case value
40
+ when String, Symbol
41
+ Kernel.const_get(value)
42
+ when Class
43
+ value
44
+ else
45
+ raise ArgumentError, "#{value} cannot be converted to a Class"
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,127 @@
1
+ require 'squeel/nodes/operators'
2
+ require 'squeel/nodes/predicate_operators'
3
+
4
+ module Squeel
5
+ module Nodes
6
+ class KeyPath
7
+ include PredicateOperators
8
+ include Operators
9
+
10
+ attr_reader :path, :endpoint
11
+
12
+ def initialize(path, endpoint, absolute = false)
13
+ @path, @endpoint = path, endpoint
14
+ @path = [@path] unless Array === @path
15
+ @endpoint = Stub.new(@endpoint) if Symbol === @endpoint
16
+ @absolute = absolute
17
+ end
18
+
19
+ def absolute?
20
+ @absolute
21
+ end
22
+
23
+ def eql?(other)
24
+ self.class == other.class &&
25
+ self.path == other.path &&
26
+ self.endpoint.eql?(other.endpoint) &&
27
+ self.absolute? == other.absolute?
28
+ end
29
+
30
+ def |(other)
31
+ endpoint.respond_to?(:|) ? super : no_method_error(:|)
32
+ end
33
+
34
+ def &(other)
35
+ endpoint.respond_to?(:&) ? super : no_method_error(:&)
36
+ end
37
+
38
+ def -@
39
+ endpoint.respond_to?(:-@) ? super : no_method_error(:-@)
40
+ end
41
+
42
+ def +(other)
43
+ endpoint.respond_to?(:+) ? super : no_method_error(:+)
44
+ end
45
+
46
+ def -(other)
47
+ endpoint.respond_to?(:-) ? super : no_method_error(:-)
48
+ end
49
+
50
+ def *(other)
51
+ endpoint.respond_to?(:*) ? super : no_method_error(:*)
52
+ end
53
+
54
+ def /(other)
55
+ endpoint.respond_to?(:/) ? super : no_method_error(:/)
56
+ end
57
+
58
+ def op(operator, other)
59
+ endpoint.respond_to?(:op) ? super : no_method_error(:/)
60
+ end
61
+
62
+ def ~
63
+ @absolute = true
64
+ self
65
+ end
66
+
67
+ # To let these fall through to the endpoint via method_missing
68
+ instance_methods.grep(/^(==|=~|!~)$/) do |operator|
69
+ undef_method operator
70
+ end
71
+
72
+ def hash
73
+ [self.class, endpoint, *path].hash
74
+ end
75
+
76
+ def to_sym
77
+ nil
78
+ end
79
+
80
+ def %(val)
81
+ case endpoint
82
+ when Stub, Function
83
+ eq(val)
84
+ self
85
+ else
86
+ endpoint % val
87
+ self
88
+ end
89
+ end
90
+
91
+ def path_with_endpoint
92
+ path + [endpoint]
93
+ end
94
+
95
+ def to_s
96
+ path.map(&:to_s).join('.') << ".#{endpoint}"
97
+ end
98
+
99
+ def method_missing(method_id, *args)
100
+ super if method_id == :to_ary
101
+ if endpoint.respond_to? method_id
102
+ @endpoint = @endpoint.send(method_id, *args)
103
+ self
104
+ elsif Stub === endpoint
105
+ @path << endpoint.symbol
106
+ if args.empty?
107
+ @endpoint = Stub.new(method_id)
108
+ elsif (args.size == 1) && (Class === args[0])
109
+ @endpoint = Join.new(method_id, Arel::InnerJoin, args[0])
110
+ else
111
+ @endpoint = Nodes::Function.new method_id, args
112
+ end
113
+ self
114
+ else
115
+ super
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def no_method_error(method_id)
122
+ raise NoMethodError, "undefined method `#{method_id}' for #{self}:#{self.class}"
123
+ end
124
+
125
+ end
126
+ end
127
+ end