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,107 @@
1
+ require 'squeel/visitors/base'
2
+ require 'squeel/contexts/join_dependency_context'
3
+
4
+ module Squeel
5
+ module Visitors
6
+ class OrderVisitor < Base
7
+
8
+ def visit_Hash(o, parent)
9
+ o.map do |k, v|
10
+ if implies_context_change?(v)
11
+ visit_with_context_change(k, v, parent)
12
+ else
13
+ visit_without_context_change(k, v, parent)
14
+ end
15
+ end.flatten
16
+ end
17
+
18
+ def implies_context_change?(v)
19
+ Hash === v || can_accept?(v) ||
20
+ (Array === v && !v.empty? && v.all? {|val| can_accept?(val)})
21
+ end
22
+
23
+ def visit_with_context_change(k, v, parent)
24
+ parent = case k
25
+ when Nodes::KeyPath
26
+ traverse(k, parent, true)
27
+ else
28
+ find(k, parent)
29
+ end
30
+
31
+ if Array === v
32
+ v.map {|val| accept(val, parent || k)}
33
+ else
34
+ can_accept?(v) ? accept(v, parent || k) : v
35
+ end
36
+ end
37
+
38
+ def visit_without_context_change(k, v, parent)
39
+ v
40
+ end
41
+
42
+ def visit_Array(o, parent)
43
+ o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
44
+ end
45
+
46
+ def visit_Symbol(o, parent)
47
+ contextualize(parent)[o]
48
+ end
49
+
50
+ def visit_Squeel_Nodes_Stub(o, parent)
51
+ contextualize(parent)[o.symbol]
52
+ end
53
+
54
+ def visit_Squeel_Nodes_KeyPath(o, parent)
55
+ parent = traverse(o, parent)
56
+
57
+ accept(o.endpoint, parent)
58
+ end
59
+
60
+ def visit_Squeel_Nodes_Order(o, parent)
61
+ accept(o.expr, parent).send(o.descending? ? :desc : :asc)
62
+ end
63
+
64
+ def visit_Squeel_Nodes_Function(o, parent)
65
+ args = o.args.map do |arg|
66
+ case arg
67
+ when Nodes::Function, Nodes::KeyPath
68
+ accept(arg, parent)
69
+ when Symbol, Nodes::Stub
70
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
71
+ else
72
+ quoted?(arg) ? Arel.sql(arel_visitor.accept arg) : arg
73
+ end
74
+ end
75
+ Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
76
+ end
77
+
78
+ def visit_Squeel_Nodes_Operation(o, parent)
79
+ args = o.args.map do |arg|
80
+ case arg
81
+ when Nodes::Function
82
+ accept(arg, parent)
83
+ when Symbol, Nodes::Stub
84
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
85
+ else
86
+ quoted?(arg) ? Arel.sql(arel_visitor.accept arg) : arg
87
+ end
88
+ end
89
+
90
+ op = case o.operator
91
+ when :+
92
+ Arel::Nodes::Addition.new(args[0], args[1])
93
+ when :-
94
+ Arel::Nodes::Subtraction.new(args[0], args[1])
95
+ when :*
96
+ Arel::Nodes::Multiplication.new(args[0], args[1])
97
+ when :/
98
+ Arel::Nodes::Division.new(args[0], args[1])
99
+ else
100
+ Arel.sql("#{arel_visitor.accept(args[0])} #{o.operator} #{arel_visitor.accept(args[1])}")
101
+ end
102
+ o.alias ? op.as(o.alias) : op
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,179 @@
1
+ require 'squeel/visitors/base'
2
+ require 'squeel/contexts/join_dependency_context'
3
+
4
+ module Squeel
5
+ module Visitors
6
+ class PredicateVisitor < Base
7
+
8
+ def visit_Hash(o, parent)
9
+ predicates = o.map do |k, v|
10
+ if implies_context_change?(v)
11
+ visit_with_context_change(k, v, parent)
12
+ else
13
+ visit_without_context_change(k, v, parent)
14
+ end
15
+ end
16
+
17
+ predicates.flatten!
18
+
19
+ if predicates.size > 1
20
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new predicates)
21
+ else
22
+ predicates.first
23
+ end
24
+ end
25
+
26
+ def visit_Array(o, parent)
27
+ if o.first.is_a? String
28
+ sanitize_sql(o, parent)
29
+ else
30
+ o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
31
+ end
32
+ end
33
+
34
+ def visit_Squeel_Nodes_KeyPath(o, parent)
35
+ parent = traverse(o, parent)
36
+
37
+ accept(o.endpoint, parent)
38
+ end
39
+
40
+ def visit_Squeel_Nodes_Predicate(o, parent)
41
+ value = o.value
42
+ case value
43
+ when Nodes::Function
44
+ value = accept(value, parent)
45
+ when Nodes::KeyPath
46
+ value = can_accept?(value.endpoint) ? accept(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_sym]
47
+ end
48
+ if Nodes::Function === o.expr
49
+ accept(o.expr, parent).send(o.method_name, value)
50
+ else
51
+ contextualize(parent)[o.expr].send(o.method_name, value)
52
+ end
53
+ end
54
+
55
+ def visit_Squeel_Nodes_Function(o, parent)
56
+ args = o.args.map do |arg|
57
+ case arg
58
+ when Nodes::Function
59
+ accept(arg, parent)
60
+ when Nodes::KeyPath
61
+ can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
62
+ when Symbol, Nodes::Stub
63
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
64
+ else
65
+ quoted?(arg) ? Arel.sql(arel_visitor.accept arg) : arg
66
+ end
67
+ end
68
+ Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
69
+ end
70
+
71
+ def visit_Squeel_Nodes_Operation(o, parent)
72
+ args = o.args.map do |arg|
73
+ case arg
74
+ when Nodes::Function
75
+ accept(arg, parent)
76
+ when Nodes::KeyPath
77
+ can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
78
+ when Symbol, Nodes::Stub
79
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
80
+ else
81
+ quoted?(arg) ? Arel.sql(arel_visitor.accept arg) : arg
82
+ end
83
+ end
84
+
85
+ op = case o.operator
86
+ when :+
87
+ Arel::Nodes::Addition.new(args[0], args[1])
88
+ when :-
89
+ Arel::Nodes::Subtraction.new(args[0], args[1])
90
+ when :*
91
+ Arel::Nodes::Multiplication.new(args[0], args[1])
92
+ when :/
93
+ Arel::Nodes::Division.new(args[0], args[1])
94
+ else
95
+ Arel::Nodes::InfixOperation(o.operator, args[0], args[1])
96
+ end
97
+ o.alias ? op.as(o.alias) : op
98
+ end
99
+
100
+ def visit_Squeel_Nodes_And(o, parent)
101
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(accept(o.children, parent)))
102
+ end
103
+
104
+ def visit_Squeel_Nodes_Or(o, parent)
105
+ accept(o.left, parent).or(accept(o.right, parent))
106
+ end
107
+
108
+ def visit_Squeel_Nodes_Not(o, parent)
109
+ accept(o.expr, parent).not
110
+ end
111
+
112
+ def implies_context_change?(v)
113
+ case v
114
+ when Hash, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
115
+ true
116
+ when Nodes::KeyPath
117
+ can_accept?(v.endpoint)
118
+ when Array
119
+ (!v.empty? && v.all? {|val| can_accept?(val)})
120
+ else
121
+ false
122
+ end
123
+ end
124
+
125
+ def visit_with_context_change(k, v, parent)
126
+ parent = case k
127
+ when Nodes::KeyPath
128
+ traverse(k, parent, true)
129
+ else
130
+ find(k, parent)
131
+ end
132
+
133
+ case v
134
+ when Hash, Nodes::KeyPath, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
135
+ accept(v, parent || k)
136
+ when Array
137
+ v.map {|val| accept(val, parent || k)}
138
+ else
139
+ raise ArgumentError, <<-END
140
+ Hashes, Predicates, and arrays of visitables as values imply that their
141
+ corresponding keys are a parent. This didn't work out so well in the case
142
+ of key = #{k} and value = #{v}"
143
+ END
144
+ end
145
+ end
146
+
147
+ def visit_without_context_change(k, v, parent)
148
+ case v
149
+ when Nodes::Stub, Symbol
150
+ v = contextualize(parent)[v.to_sym]
151
+ when Nodes::KeyPath # If we could accept the endpoint, we wouldn't be here
152
+ v = contextualize(traverse(v, parent))[v.endpoint.to_sym]
153
+ end
154
+
155
+ case k
156
+ when Nodes::Predicate
157
+ accept(k % v, parent)
158
+ when Nodes::Function
159
+ arel_predicate_for(accept(k, parent), v, parent)
160
+ when Nodes::KeyPath
161
+ accept(k % v, parent)
162
+ else
163
+ attribute = contextualize(parent)[k.to_sym]
164
+ arel_predicate_for(attribute, v, parent)
165
+ end
166
+ end
167
+
168
+ def arel_predicate_for(attribute, value, parent)
169
+ if [Array, Range, Arel::SelectManager].include?(value.class)
170
+ attribute.in(value)
171
+ else
172
+ value = can_accept?(value) ? accept(value, parent) : value
173
+ attribute.eq(value)
174
+ end
175
+ end
176
+
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,103 @@
1
+ require 'squeel/visitors/base'
2
+ require 'squeel/contexts/join_dependency_context'
3
+
4
+ module Squeel
5
+ module Visitors
6
+ class SelectVisitor < Base
7
+
8
+ def visit_Hash(o, parent)
9
+ o.map do |k, v|
10
+ if implies_context_change?(v)
11
+ visit_with_context_change(k, v, parent)
12
+ else
13
+ visit_without_context_change(k, v, parent)
14
+ end
15
+ end.flatten
16
+ end
17
+
18
+ def implies_context_change?(v)
19
+ Hash === v || can_accept?(v) ||
20
+ (Array === v && !v.empty? && v.all? {|val| can_accept?(val)})
21
+ end
22
+
23
+ def visit_with_context_change(k, v, parent)
24
+ parent = case k
25
+ when Nodes::KeyPath
26
+ traverse(k, parent, true)
27
+ else
28
+ find(k, parent)
29
+ end
30
+
31
+ if Array === v
32
+ v.map {|val| accept(val, parent || k)}
33
+ else
34
+ can_accept?(v) ? accept(v, parent || k) : v
35
+ end
36
+ end
37
+
38
+ def visit_without_context_change(k, v, parent)
39
+ v
40
+ end
41
+
42
+ def visit_Array(o, parent)
43
+ o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
44
+ end
45
+
46
+ def visit_Symbol(o, parent)
47
+ contextualize(parent)[o]
48
+ end
49
+
50
+ def visit_Squeel_Nodes_Stub(o, parent)
51
+ contextualize(parent)[o.symbol]
52
+ end
53
+
54
+ def visit_Squeel_Nodes_KeyPath(o, parent)
55
+ parent = traverse(o, parent)
56
+
57
+ accept(o.endpoint, parent)
58
+ end
59
+
60
+ def visit_Squeel_Nodes_Function(o, parent)
61
+ args = o.args.map do |arg|
62
+ case arg
63
+ when Nodes::Function, Nodes::KeyPath
64
+ accept(arg, parent)
65
+ when Symbol, Nodes::Stub
66
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
67
+ else
68
+ quoted?(arg) ? Arel.sql(arel_visitor.accept arg) : arg
69
+ end
70
+ end
71
+ Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
72
+ end
73
+
74
+ def visit_Squeel_Nodes_Operation(o, parent)
75
+ args = o.args.map do |arg|
76
+ case arg
77
+ when Nodes::Function
78
+ accept(arg, parent)
79
+ when Symbol, Nodes::Stub
80
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
81
+ else
82
+ quoted?(arg) ? Arel.sql(arel_visitor.accept arg) : arg
83
+ end
84
+ end
85
+
86
+ op = case o.operator
87
+ when :+
88
+ Arel::Nodes::Addition.new(args[0], args[1])
89
+ when :-
90
+ Arel::Nodes::Subtraction.new(args[0], args[1])
91
+ when :*
92
+ Arel::Nodes::Multiplication.new(args[0], args[1])
93
+ when :/
94
+ Arel::Nodes::Division.new(args[0], args[1])
95
+ else
96
+ Arel.sql("#{arel_visitor.accept(args[0])} #{o.operator} #{arel_visitor.accept(args[1])}")
97
+ end
98
+ o.alias ? op.as(o.alias) : op
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,5 @@
1
+ Article.blueprint do
2
+ person
3
+ title
4
+ body
5
+ end
@@ -0,0 +1,5 @@
1
+ Comment.blueprint do
2
+ article
3
+ person
4
+ body
5
+ end
@@ -0,0 +1,3 @@
1
+ Note.blueprint do
2
+ note
3
+ end
@@ -0,0 +1,4 @@
1
+ Person.blueprint do
2
+ name
3
+ salary
4
+ end
@@ -0,0 +1,3 @@
1
+ Tag.blueprint do
2
+ name { Sham.tag_name }
3
+ end
data/spec/console.rb ADDED
@@ -0,0 +1,22 @@
1
+ Bundler.setup
2
+ require 'machinist/active_record'
3
+ require 'sham'
4
+ require 'faker'
5
+
6
+ Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
7
+ require f
8
+ end
9
+
10
+ Sham.define do
11
+ name { Faker::Name.name }
12
+ title { Faker::Lorem.sentence }
13
+ body { Faker::Lorem.paragraph }
14
+ salary {|index| 30000 + (index * 1000)}
15
+ tag_name { Faker::Lorem.words(3).join(' ') }
16
+ note { Faker::Lorem.words(7).join(' ') }
17
+ end
18
+
19
+ Schema.create
20
+
21
+ require 'squeel'
22
+
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe Symbol do
4
+
5
+ Squeel::Constants::PREDICATES.each do |method_name|
6
+ it "creates #{method_name} predicates with no value" do
7
+ predicate = :attribute.send(method_name)
8
+ predicate.expr.should eq :attribute
9
+ predicate.method_name.should eq method_name
10
+ predicate.value?.should be_false
11
+ end
12
+
13
+ it "creates #{method_name} predicates with a value" do
14
+ predicate = :attribute.send(method_name, 'value')
15
+ predicate.expr.should eq :attribute
16
+ predicate.method_name.should eq method_name
17
+ predicate.value.should eq 'value'
18
+ end
19
+ end
20
+
21
+ Squeel::Constants::PREDICATE_ALIASES.each do |method_name, aliases|
22
+ aliases.each do |aliaz|
23
+ ['', '_any', '_all'].each do |suffix|
24
+ it "creates #{method_name.to_s + suffix} predicates with no value using the alias #{aliaz.to_s + suffix}" do
25
+ predicate = :attribute.send(aliaz.to_s + suffix)
26
+ predicate.expr.should eq :attribute
27
+ predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
28
+ predicate.value?.should be_false
29
+ end
30
+
31
+ it "creates #{method_name.to_s + suffix} predicates with a value using the alias #{aliaz.to_s + suffix}" do
32
+ predicate = :attribute.send((aliaz.to_s + suffix), 'value')
33
+ predicate.expr.should eq :attribute
34
+ predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
35
+ predicate.value.should eq 'value'
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ it 'creates ascending orders' do
42
+ order = :attribute.asc
43
+ order.should be_ascending
44
+ end
45
+
46
+ it 'creates descending orders' do
47
+ order = :attribute.desc
48
+ order.should be_descending
49
+ end
50
+
51
+ it 'creates functions' do
52
+ function = :function.func
53
+ function.should be_a Squeel::Nodes::Function
54
+ end
55
+
56
+ it 'creates inner joins' do
57
+ join = :join.inner
58
+ join.should be_a Squeel::Nodes::Join
59
+ join.type.should eq Arel::InnerJoin
60
+ end
61
+
62
+ it 'creates outer joins' do
63
+ join = :join.outer
64
+ join.should be_a Squeel::Nodes::Join
65
+ join.type.should eq Arel::OuterJoin
66
+ end
67
+
68
+ end