squeel 0.5.0

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