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,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