ambition 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +18 -0
- data/Manifest +23 -0
- data/README +175 -0
- data/Rakefile +52 -0
- data/init.rb +1 -0
- data/lib/ambition.rb +21 -0
- data/lib/ambition/count.rb +8 -0
- data/lib/ambition/enumerable.rb +9 -0
- data/lib/ambition/limit.rb +34 -0
- data/lib/ambition/order.rb +52 -0
- data/lib/ambition/processor.rb +54 -0
- data/lib/ambition/query.rb +81 -0
- data/lib/ambition/where.rb +158 -0
- data/lib/proc_to_ruby.rb +36 -0
- data/test/chaining_test.rb +34 -0
- data/test/count_test.rb +17 -0
- data/test/enumerable_test.rb +51 -0
- data/test/helper.rb +30 -0
- data/test/join_test.rb +32 -0
- data/test/limit_test.rb +37 -0
- data/test/order_test.rb +52 -0
- data/test/types_test.rb +56 -0
- data/test/where_test.rb +139 -0
- metadata +85 -0
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2007 Chris Wanstrath
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
./init.rb
|
2
|
+
./lib/ambition/count.rb
|
3
|
+
./lib/ambition/enumerable.rb
|
4
|
+
./lib/ambition/limit.rb
|
5
|
+
./lib/ambition/order.rb
|
6
|
+
./lib/ambition/processor.rb
|
7
|
+
./lib/ambition/query.rb
|
8
|
+
./lib/ambition/where.rb
|
9
|
+
./lib/ambition.rb
|
10
|
+
./lib/proc_to_ruby.rb
|
11
|
+
./LICENSE
|
12
|
+
./Rakefile
|
13
|
+
./README
|
14
|
+
./test/chaining_test.rb
|
15
|
+
./test/count_test.rb
|
16
|
+
./test/enumerable_test.rb
|
17
|
+
./test/helper.rb
|
18
|
+
./test/join_test.rb
|
19
|
+
./test/limit_test.rb
|
20
|
+
./test/order_test.rb
|
21
|
+
./test/types_test.rb
|
22
|
+
./test/where_test.rb
|
23
|
+
./Manifest
|
data/README
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
== Ambitious SQL
|
2
|
+
|
3
|
+
A simple experiment and even simpler query library.
|
4
|
+
|
5
|
+
I could tell you all about how awesome the internals are, or
|
6
|
+
how fun it was to write, or how it'll make you rich and famous,
|
7
|
+
but instead I'm just going to show you some examples.
|
8
|
+
|
9
|
+
The goal is this: write once, run with ActiveRecord, Sequel, DataMapper, whatever. Kind
|
10
|
+
of like Rack for databases.
|
11
|
+
|
12
|
+
== Git It (Not with Git, though)
|
13
|
+
|
14
|
+
$ sudo gem install ambition -y
|
15
|
+
|
16
|
+
This will suck in Ambition and its dependencies (ParseTree & ActiveRecord). It's fully usable
|
17
|
+
outside of Rails (I use it in a Camping app or two), as long as you're riding ActiveRecord.
|
18
|
+
|
19
|
+
To use with Rails, after installing the gem:
|
20
|
+
|
21
|
+
$ cd vendor/plugins
|
22
|
+
$ gem unpack ambition
|
23
|
+
|
24
|
+
RDoc exists: http://rock.errtheblog.com/ambition
|
25
|
+
|
26
|
+
== Examples
|
27
|
+
|
28
|
+
Basically, you write your SQL in Ruby. No, not in Ruby. As Ruby.
|
29
|
+
|
30
|
+
User.select { |u| u.city == 'San Francisco' }.each do |user|
|
31
|
+
puts user.name
|
32
|
+
end
|
33
|
+
|
34
|
+
And that's it.
|
35
|
+
|
36
|
+
The key is the +each+ method. You build up a +Query+ using +select+, +first+, and +sort_by+,
|
37
|
+
then call +each+ on it. This'll run the query and enumerate through the results. Really, you
|
38
|
+
can use any Enumerable method: +map+, +each_with_index+, etc.
|
39
|
+
|
40
|
+
Our +Query+ object has two useful methods: +to_sql+ and +to_hash+. With these, we can
|
41
|
+
check out what exactly we're building. Not everyone has +to_sql+, though. Mostly ignore
|
42
|
+
these methods and treat everything like you normally would.
|
43
|
+
|
44
|
+
See, +to_sql+:
|
45
|
+
>> User.select { |m| m.name == 'jon' }.to_sql
|
46
|
+
=> "SELECT * FROM users WHERE users.`name` = 'jon'"
|
47
|
+
|
48
|
+
See, +to_hash+:
|
49
|
+
>> User.select { |m| m.name == 'jon' }.to_hash
|
50
|
+
=> {:conditions=>"users.`name` = 'jon'"}
|
51
|
+
|
52
|
+
== Limitations
|
53
|
+
|
54
|
+
You can use variables, but any more complex Ruby (right now) won't work
|
55
|
+
inside your blocks. Just do it outside the block and assign it to a variable, okay?
|
56
|
+
|
57
|
+
Instead of:
|
58
|
+
User.select { |m| m.date == 2.days.ago }
|
59
|
+
|
60
|
+
Just do:
|
61
|
+
date = 2.days.ago
|
62
|
+
User.select { |m| m.date == date }
|
63
|
+
|
64
|
+
Instance variables and globals work, too. Same with method calls.
|
65
|
+
|
66
|
+
== Equality -- select { |u| u.field == 'bob' }
|
67
|
+
|
68
|
+
User.select { |m| m.name == 'jon' }
|
69
|
+
"SELECT * FROM users WHERE users.`name` = 'jon'"
|
70
|
+
|
71
|
+
User.select { |m| m.name != 'jon' }
|
72
|
+
"SELECT * FROM users WHERE users.`name` <> 'jon'"
|
73
|
+
|
74
|
+
User.select { |m| m.name == 'jon' && m.age == 21 }
|
75
|
+
"SELECT * FROM users WHERE (users.`name` = 'jon' AND users.`age` = 21)"
|
76
|
+
|
77
|
+
User.select { |m| m.name == 'jon' || m.age == 21 }
|
78
|
+
"SELECT * FROM users WHERE (users.`name` = 'jon' OR users.`age` = 21)"
|
79
|
+
|
80
|
+
User.select { |m| m.name == 'jon' || m.age == 21 && m.password == 'pass' }
|
81
|
+
"SELECT * FROM users WHERE (users.`name` = 'jon' OR (users.`age` = 21 AND users.`password` = 'pass'))"
|
82
|
+
|
83
|
+
User.select { |m| (m.name == 'jon' || m.name == 'rick') && m.age == 21 }
|
84
|
+
"SELECT * FROM users WHERE ((users.`name` = 'jon' OR users.`name` = 'rick') AND users.`age` = 21)"
|
85
|
+
|
86
|
+
== Associations -- select { |u| u.field == 'bob' && u.association.field == 'bob@bob.com' }
|
87
|
+
|
88
|
+
The +to_sql+ method doesn't work on associations yet, but that's okay: they can still query through
|
89
|
+
ActiveRecord just fine.
|
90
|
+
|
91
|
+
User.select { |u| u.email == 'chris@ozmm.org' && u.profile.name == 'chris wanstrath' }.map(&:title)
|
92
|
+
SELECT users.`id` AS t0_r0, ... FROM users LEFT OUTER JOIN profiles ON profiles.user_id = users.id WHERE ((users.`email` = 'chris@ozmm.org' AND profiles.name = 'chris wanstrath'))
|
93
|
+
|
94
|
+
== Comparisons -- select { |u| u.age > 21 }
|
95
|
+
|
96
|
+
User.select { |m| m.age > 21 }
|
97
|
+
"SELECT * FROM users WHERE users.`age` > 21"
|
98
|
+
|
99
|
+
User.select { |m| m.age < 21 }.to_sql
|
100
|
+
"SELECT * FROM users WHERE users.`age` < 21"
|
101
|
+
|
102
|
+
User.select { |m| [1, 2, 3, 4].include? m.id }
|
103
|
+
"SELECT * FROM users WHERE users.`id` IN (1, 2, 3, 4)"
|
104
|
+
|
105
|
+
== LIKE and REGEXP (RLIKE) -- select { |m| m.name =~ 'chris' }
|
106
|
+
|
107
|
+
User.select { |m| m.name =~ 'chris' }
|
108
|
+
"SELECT * FROM users WHERE users.`name` LIKE 'chris'"
|
109
|
+
|
110
|
+
User.select { |m| m.name =~ 'chri%' }
|
111
|
+
"SELECT * FROM users WHERE users.`name` LIKE 'chri%'"
|
112
|
+
|
113
|
+
User.select { |m| m.name !~ 'chris' }
|
114
|
+
"SELECT * FROM users WHERE users.`name` NOT LIKE 'chris'"
|
115
|
+
|
116
|
+
User.select { |m| !(m.name =~ 'chris') }
|
117
|
+
"SELECT * FROM users WHERE users.`name` NOT LIKE 'chris'"
|
118
|
+
|
119
|
+
User.select { |m| m.name =~ /chris/ }
|
120
|
+
"SELECT * FROM users WHERE users.`name` REGEXP 'chris'"
|
121
|
+
|
122
|
+
== #detect
|
123
|
+
|
124
|
+
User.detect { |m| m.name == 'chris' }
|
125
|
+
"SELECT * FROM users WHERE users.`name` = 'chris' LIMIT 1"
|
126
|
+
|
127
|
+
== LIMITs -- first, first(x), [offset, limit], [range]
|
128
|
+
|
129
|
+
User.select { |m| m.name == 'jon' }.first
|
130
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' LIMIT 1"
|
131
|
+
|
132
|
+
User.select { |m| m.name == 'jon' }.first(5)
|
133
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' LIMIT 5"
|
134
|
+
|
135
|
+
User.select { |m| m.name == 'jon' }[10, 20]
|
136
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' LIMIT 10, 20"
|
137
|
+
|
138
|
+
User.select { |m| m.name == 'jon' }[10..20]
|
139
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' LIMIT 10, 10"
|
140
|
+
|
141
|
+
== ORDER -- sort_by { |u| u.field }
|
142
|
+
|
143
|
+
User.select { |m| m.name == 'jon' }.sort_by { |m| m.name }
|
144
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name"
|
145
|
+
|
146
|
+
User.select { |m| m.name == 'jon' }.sort_by { |m| [ m.name, m.age ] }
|
147
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name, users.age"
|
148
|
+
|
149
|
+
User.select { |m| m.name == 'jon' }.sort_by { |m| [ m.name, -m.age ] }
|
150
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name, users.age DESC"
|
151
|
+
|
152
|
+
User.select { |m| m.name == 'jon' }.sort_by { |m| [ -m.name, -m.age ] }
|
153
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name DESC, users.age DESC"
|
154
|
+
|
155
|
+
User.select { |m| m.name == 'jon' }.sort_by { |m| -m.age }
|
156
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.age DESC"
|
157
|
+
|
158
|
+
User.select { |m| m.name == 'jon' }.sort_by { rand }
|
159
|
+
"SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY RAND()"
|
160
|
+
|
161
|
+
== COUNT -- select { |u| u.name == 'jon' }.size
|
162
|
+
|
163
|
+
User.select { |m| m.name == 'jon' }.size
|
164
|
+
SELECT count(*) AS count_all FROM users WHERE (users.`name` = 'jon')
|
165
|
+
|
166
|
+
>> User.select { |m| m.name == 'jon' }.size
|
167
|
+
=> 21
|
168
|
+
|
169
|
+
== SELECT * FROM bugs
|
170
|
+
|
171
|
+
Found a bug? Sweet. Add it at the Lighthouse: http://err.lighthouseapp.com/projects/466-plugins/tickets/new
|
172
|
+
|
173
|
+
Feature requests are welcome.
|
174
|
+
|
175
|
+
* Chris Wanstrath [ chris@ozmm.org ]
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test it!'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.pattern = 'test/**/*_test.rb'
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Generate RDoc documentation'
|
15
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
16
|
+
files = ['README', 'LICENSE', 'lib/**/*.rb']
|
17
|
+
rdoc.rdoc_files.add(files)
|
18
|
+
rdoc.main = "README" # page to start on
|
19
|
+
rdoc.title = "ambition"
|
20
|
+
rdoc.template = File.exists?(t="/Users/chris/ruby/projects/err/rock/template.rb") ? t : "/var/www/rock/template.rb"
|
21
|
+
rdoc.rdoc_dir = 'doc' # rdoc output folder
|
22
|
+
rdoc.options << '--inline-source'
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Generate coverage reports'
|
26
|
+
task :rcov do
|
27
|
+
`rcov -e gems test/*_test.rb`
|
28
|
+
puts 'Generated coverage reports.'
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'rubygems'
|
34
|
+
gem 'echoe', '=1.3'
|
35
|
+
ENV['RUBY_FLAGS'] = ""
|
36
|
+
require 'echoe'
|
37
|
+
|
38
|
+
Echoe.new('ambition', '0.1.0') do |p|
|
39
|
+
p.rubyforge_name = 'err'
|
40
|
+
p.summary = "Ambition builds SQL from plain jane Ruby."
|
41
|
+
p.description = "Ambition builds SQL from plain jane Ruby."
|
42
|
+
p.url = "http://errtheblog.com/"
|
43
|
+
p.author = 'Chris Wanstrath'
|
44
|
+
p.email = "chris@ozmm.org"
|
45
|
+
p.extra_deps << ['ParseTree', '=2.0.1']
|
46
|
+
p.extra_deps << ['activerecord', '>=1.15.0']
|
47
|
+
end
|
48
|
+
|
49
|
+
rescue LoadError => boom
|
50
|
+
puts "You are missing a dependency required for meta-operations on this gem."
|
51
|
+
puts "#{boom.to_s.capitalize}."
|
52
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ambition'
|
data/lib/ambition.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'proc_to_ruby'
|
3
|
+
require 'ambition/processor'
|
4
|
+
require 'ambition/query'
|
5
|
+
require 'ambition/where'
|
6
|
+
require 'ambition/order'
|
7
|
+
require 'ambition/limit'
|
8
|
+
require 'ambition/count'
|
9
|
+
require 'ambition/enumerable'
|
10
|
+
|
11
|
+
module Ambition
|
12
|
+
include Where, Order, Limit, Enumerable, Count
|
13
|
+
|
14
|
+
attr_accessor :query_context
|
15
|
+
|
16
|
+
def query_context
|
17
|
+
@query_context || Query.new(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveRecord::Base.extend Ambition
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ambition
|
2
|
+
module Limit
|
3
|
+
def first(limit = 1, offset = nil)
|
4
|
+
query_context.add LimitProcessor.new(limit, offset)
|
5
|
+
find(limit == 1 ? :first : :all, query_context.to_hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](offset, limit = nil)
|
9
|
+
return first(offset, limit) if limit
|
10
|
+
|
11
|
+
if offset.is_a? Range
|
12
|
+
limit = offset.end
|
13
|
+
limit -= 1 if offset.exclude_end?
|
14
|
+
first(offset.first, limit - offset.first)
|
15
|
+
else
|
16
|
+
first(offset, 1)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class LimitProcessor
|
22
|
+
def initialize(*args)
|
23
|
+
@args = args
|
24
|
+
end
|
25
|
+
|
26
|
+
def key
|
27
|
+
:limit
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
@args.compact * ', '
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Ambition
|
2
|
+
module Order
|
3
|
+
def sort_by(&block)
|
4
|
+
query_context.add OrderProcessor.new(table_name, block)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class OrderProcessor < Processor
|
9
|
+
def initialize(table_name, block)
|
10
|
+
super()
|
11
|
+
@receiver = nil
|
12
|
+
@table_name = table_name
|
13
|
+
@block = block
|
14
|
+
@key = :order
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Sexp Processing Methods
|
19
|
+
def process_call(exp)
|
20
|
+
receiver, method, other = *exp
|
21
|
+
exp.clear
|
22
|
+
|
23
|
+
translation(receiver, method, other)
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_vcall(exp)
|
27
|
+
if (method = exp.shift) == :rand
|
28
|
+
'RAND()'
|
29
|
+
else
|
30
|
+
raise "Not implemented: :vcall for #{method}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_masgn(exp)
|
35
|
+
exp.clear
|
36
|
+
''
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Helpers!
|
41
|
+
def translation(receiver, method, other)
|
42
|
+
case method
|
43
|
+
when :-@
|
44
|
+
"#{process(receiver)} DESC"
|
45
|
+
when :__send__
|
46
|
+
"#{@table_name}.#{eval('to_s', @block)}"
|
47
|
+
else
|
48
|
+
"#{@table_name}.#{method}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract/quoting'
|
2
|
+
|
3
|
+
module Ambition
|
4
|
+
class Processor < SexpProcessor
|
5
|
+
include ActiveRecord::ConnectionAdapters::Quoting
|
6
|
+
|
7
|
+
attr_reader :key, :join_string, :prefix
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super()
|
11
|
+
@strict = false
|
12
|
+
@expected = String
|
13
|
+
@auto_shift_type = true
|
14
|
+
@warn_on_default = false
|
15
|
+
@default_method = :process_error
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Processing methods
|
20
|
+
def process_error(exp)
|
21
|
+
raise "Missing process method for sexp: #{exp.inspect}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_proc(exp)
|
25
|
+
receiver, body = process(exp.shift), exp.shift
|
26
|
+
return process(body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_dasgn_curr(exp)
|
30
|
+
@receiver = exp.shift
|
31
|
+
return @receiver.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_array(exp)
|
35
|
+
arrayed = exp.map { |m| process(m) }
|
36
|
+
exp.clear
|
37
|
+
return arrayed.join(', ')
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Helper methods
|
42
|
+
def to_s
|
43
|
+
process(@block.to_sexp).squeeze(' ')
|
44
|
+
end
|
45
|
+
|
46
|
+
def sanitize(value)
|
47
|
+
case value.to_s
|
48
|
+
when 'true' then '1'
|
49
|
+
when 'false' then '0'
|
50
|
+
else ActiveRecord::Base.connection.quote(value) rescue quote(value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Ambition
|
2
|
+
class Query
|
3
|
+
@@select = 'SELECT * FROM %s %s'
|
4
|
+
|
5
|
+
def initialize(owner)
|
6
|
+
@table_name = owner.table_name
|
7
|
+
@owner = owner
|
8
|
+
@clauses = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(clause)
|
12
|
+
@clauses << clause
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
with_context do
|
18
|
+
@owner.send(method, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_context
|
23
|
+
@owner.query_context = self
|
24
|
+
ret = yield
|
25
|
+
ensure
|
26
|
+
@owner.query_context = nil
|
27
|
+
ret
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
keyed = keyed_clauses
|
32
|
+
hash = {}
|
33
|
+
|
34
|
+
unless (where = keyed[:conditions]).blank?
|
35
|
+
hash[:conditions] = Array(where)
|
36
|
+
hash[:conditions] *= ' AND '
|
37
|
+
end
|
38
|
+
|
39
|
+
unless (includes = keyed[:includes]).blank?
|
40
|
+
hash[:include] = includes.flatten
|
41
|
+
end
|
42
|
+
|
43
|
+
if order = keyed[:order]
|
44
|
+
hash[:order] = order.join(', ')
|
45
|
+
end
|
46
|
+
|
47
|
+
if limit = keyed[:limit]
|
48
|
+
hash[:limit] = limit.join(', ')
|
49
|
+
end
|
50
|
+
|
51
|
+
hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
hash = keyed_clauses
|
56
|
+
|
57
|
+
sql = []
|
58
|
+
sql << "JOIN #{hash[:includes].join(', ')}" unless hash[:includes].blank?
|
59
|
+
sql << "WHERE #{hash[:conditions].join(' AND ')}" unless hash[:conditions].blank?
|
60
|
+
sql << "ORDER BY #{hash[:order].join(', ')}" unless hash[:order].blank?
|
61
|
+
sql << "LIMIT #{hash[:limit].join(', ')}" unless hash[:limit].blank?
|
62
|
+
|
63
|
+
@@select % [ @table_name, sql.join(' ') ]
|
64
|
+
end
|
65
|
+
alias_method :to_sql, :to_s
|
66
|
+
|
67
|
+
def keyed_clauses
|
68
|
+
@clauses.inject({}) do |hash, clause|
|
69
|
+
hash[clause.key] ||= []
|
70
|
+
hash[clause.key] << clause.to_s
|
71
|
+
|
72
|
+
if clause.respond_to?(:includes) && !clause.includes.blank?
|
73
|
+
hash[:includes] ||= []
|
74
|
+
hash[:includes] << clause.includes
|
75
|
+
end
|
76
|
+
|
77
|
+
hash
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Ambition
|
2
|
+
module Where
|
3
|
+
def select(*args, &block)
|
4
|
+
query_context.add WhereProcessor.new(self, block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def detect(&block)
|
8
|
+
select(&block).first
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class WhereProcessor < Processor
|
13
|
+
attr_reader :includes
|
14
|
+
|
15
|
+
def initialize(owner, block)
|
16
|
+
super()
|
17
|
+
@receiver = nil
|
18
|
+
@owner = owner
|
19
|
+
@table_name = owner.table_name
|
20
|
+
@block = block
|
21
|
+
@key = :conditions
|
22
|
+
@includes = []
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Sexp Processing Methods
|
27
|
+
def process_and(exp)
|
28
|
+
joined_expressions 'AND', exp
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_or(exp)
|
32
|
+
joined_expressions 'OR', exp
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_not(exp)
|
36
|
+
_, receiver, method, other = *exp.first
|
37
|
+
exp.clear
|
38
|
+
return translation(receiver, negate(method), other)
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_call(exp)
|
42
|
+
receiver, method, other = *exp
|
43
|
+
exp.clear
|
44
|
+
|
45
|
+
return translation(receiver, method, other)
|
46
|
+
end
|
47
|
+
|
48
|
+
def process_lit(exp)
|
49
|
+
exp.shift.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_str(exp)
|
53
|
+
sanitize exp.shift
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_nil(exp)
|
57
|
+
'NULL'
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_false(exp)
|
61
|
+
sanitize 'false'
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_true(exp)
|
65
|
+
sanitize 'true'
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_match3(exp)
|
69
|
+
regexp, target = exp.shift.last.inspect.gsub('/',''), process(exp.shift)
|
70
|
+
"#{target} REGEXP '#{regexp}'"
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_dvar(exp)
|
74
|
+
target = exp.shift
|
75
|
+
if target == @receiver
|
76
|
+
return @table_name
|
77
|
+
else
|
78
|
+
return value(target.to_s[0..-1])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_ivar(exp)
|
83
|
+
value(exp.shift.to_s[0..-1])
|
84
|
+
end
|
85
|
+
|
86
|
+
def process_lvar(exp)
|
87
|
+
value(exp.shift.to_s)
|
88
|
+
end
|
89
|
+
|
90
|
+
def process_vcall(exp)
|
91
|
+
value(exp.shift.to_s)
|
92
|
+
end
|
93
|
+
|
94
|
+
def process_gvar(exp)
|
95
|
+
value(exp.shift.to_s)
|
96
|
+
end
|
97
|
+
|
98
|
+
def process_attrasgn(exp)
|
99
|
+
exp.clear
|
100
|
+
raise "Assignment not supported. Maybe you meant ==?"
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Processor helper methods
|
105
|
+
def joined_expressions(with, exp)
|
106
|
+
clauses = []
|
107
|
+
while clause = exp.shift
|
108
|
+
clauses << clause
|
109
|
+
end
|
110
|
+
return "(" + clauses.map { |c| process(c) }.join(" #{with} ") + ")"
|
111
|
+
end
|
112
|
+
|
113
|
+
def value(variable)
|
114
|
+
sanitize eval(variable, @block)
|
115
|
+
end
|
116
|
+
|
117
|
+
def negate(method)
|
118
|
+
case method
|
119
|
+
when :==
|
120
|
+
'<>'
|
121
|
+
when :=~
|
122
|
+
'!~'
|
123
|
+
else
|
124
|
+
raise "Not implemented: #{method}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def translation(receiver, method, other)
|
129
|
+
case method.to_s
|
130
|
+
when '=='
|
131
|
+
"#{process(receiver)} = #{process(other)}"
|
132
|
+
when '<>', '>', '<'
|
133
|
+
"#{process(receiver)} #{method} #{process(other)}"
|
134
|
+
when 'include?'
|
135
|
+
"#{process(other)} IN (#{process(receiver)})"
|
136
|
+
when '=~'
|
137
|
+
"#{process(receiver)} LIKE #{process(other)}"
|
138
|
+
when '!~'
|
139
|
+
"#{process(receiver)} NOT LIKE #{process(other)}"
|
140
|
+
else
|
141
|
+
build_condition(receiver, method, other)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def build_condition(receiver, method, other)
|
146
|
+
if receiver.first == :call && receiver[1].last == @receiver
|
147
|
+
if reflection = @owner.reflections[receiver.last]
|
148
|
+
@includes << reflection.name unless @includes.include? reflection.name
|
149
|
+
"#{reflection.table_name}.#{method}"
|
150
|
+
else
|
151
|
+
raise "No reflection `#{receiver.last}' found on #{@owner}"
|
152
|
+
end
|
153
|
+
else
|
154
|
+
"#{process(receiver)}.`#{method}` #{process(other)}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/lib/proc_to_ruby.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
##
|
2
|
+
# Taken from ruby2ruby, Copyright (c) 2006 Ryan Davis under the MIT License
|
3
|
+
require 'parse_tree'
|
4
|
+
require 'unique'
|
5
|
+
require 'sexp_processor'
|
6
|
+
|
7
|
+
class Method
|
8
|
+
def with_class_and_method_name
|
9
|
+
if self.inspect =~ /<Method: (.*)\#(.*)>/ then
|
10
|
+
klass = eval $1
|
11
|
+
method = $2.intern
|
12
|
+
raise "Couldn't determine class from #{self.inspect}" if klass.nil?
|
13
|
+
return yield(klass, method)
|
14
|
+
else
|
15
|
+
raise "Can't parse signature: #{self.inspect}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_sexp
|
20
|
+
with_class_and_method_name do |klass, method|
|
21
|
+
ParseTree.new(false).parse_tree_for_method(klass, method)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Proc
|
27
|
+
def to_method
|
28
|
+
Unique.send(:define_method, :proc_to_method, self)
|
29
|
+
Unique.new.method(:proc_to_method)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_sexp
|
33
|
+
body = self.to_method.to_sexp[2][1..-1]
|
34
|
+
[:proc, *body]
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Chaining" do
|
4
|
+
specify "should join selects with AND" do
|
5
|
+
sql = User.select { |m| m.name == 'jon' }
|
6
|
+
sql = sql.select { |m| m.age == 22 }
|
7
|
+
sql.to_sql.should == "SELECT * FROM users WHERE users.`name` = 'jon' AND users.`age` = 22"
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "should join sort_bys with a comma" do
|
11
|
+
sql = User.select { |m| m.name == 'jon' }
|
12
|
+
sql = sql.sort_by { |m| m.name }
|
13
|
+
sql = sql.sort_by { |m| m.age }
|
14
|
+
sql.to_sql.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name, users.age"
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "should join selects and sorts intelligently" do
|
18
|
+
sql = User.select { |m| m.name == 'jon' }
|
19
|
+
sql = sql.select { |m| m.age == 22 }
|
20
|
+
sql = sql.sort_by { |m| -m.name }
|
21
|
+
sql = sql.sort_by { |m| m.age }
|
22
|
+
sql.to_sql.should == "SELECT * FROM users WHERE users.`name` = 'jon' AND users.`age` = 22 ORDER BY users.name DESC, users.age"
|
23
|
+
end
|
24
|
+
|
25
|
+
specify "should join lots of selects and sorts intelligently" do
|
26
|
+
sql = User.select { |m| m.name == 'jon' }
|
27
|
+
sql = sql.select { |m| m.age == 22 }
|
28
|
+
sql = sql.sort_by { |m| m.name }
|
29
|
+
sql = sql.select { |m| m.power == true }
|
30
|
+
sql = sql.sort_by { |m| m.email }
|
31
|
+
sql = sql.select { |m| m.admin == true && m.email == 'chris@ozmm.org' }
|
32
|
+
sql.to_sql.should == "SELECT * FROM users WHERE users.`name` = 'jon' AND users.`age` = 22 AND users.`power` = 1 AND (users.`admin` = 1 AND users.`email` = 'chris@ozmm.org') ORDER BY users.name, users.email"
|
33
|
+
end
|
34
|
+
end
|
data/test/count_test.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Count" do
|
4
|
+
setup do
|
5
|
+
hash = { :conditions => "users.`name` = 'jon'" }
|
6
|
+
User.expects(:count).with(hash)
|
7
|
+
@sql = User.select { |m| m.name == 'jon' }
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "size" do
|
11
|
+
@sql.size
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "length" do
|
15
|
+
@sql.length
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Each" do
|
4
|
+
specify "simple ==" do
|
5
|
+
hash = { :conditions => "users.`age` = 21" }
|
6
|
+
User.expects(:find).with(:all, hash).returns([])
|
7
|
+
User.select { |m| m.age == 21 }.each do |user|
|
8
|
+
puts user.name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "limit and conditions" do
|
13
|
+
hash = { :limit => '5', :conditions => "users.`age` = 21" }
|
14
|
+
User.expects(:find).with(:all, hash).returns([])
|
15
|
+
User.select { |m| m.age == 21 }.first(5).each do |user|
|
16
|
+
puts user.name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "limit and conditions and order" do
|
21
|
+
hash = { :limit => '5', :conditions => "users.`age` = 21", :order => 'users.name' }
|
22
|
+
User.expects(:find).with(:all, hash).returns([])
|
23
|
+
User.select { |m| m.age == 21 }.sort_by { |m| m.name }.first(5).each do |user|
|
24
|
+
puts user.name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "limit and order" do
|
29
|
+
hash = { :limit => '5', :order => 'users.name' }
|
30
|
+
User.expects(:find).with(:all, hash).returns([])
|
31
|
+
User.sort_by { |m| m.name }.first(5).each do |user|
|
32
|
+
puts user.name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "Enumerable Methods" do
|
38
|
+
specify "map" do
|
39
|
+
hash = { :conditions => "users.`age` = 21" }
|
40
|
+
User.expects(:find).with(:all, hash).returns([])
|
41
|
+
User.select { |m| m.age == 21 }.map { |u| u.name }
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "each_with_index" do
|
45
|
+
hash = { :conditions => "users.`age` = 21" }
|
46
|
+
User.expects(:find).with(:all, hash).returns([])
|
47
|
+
User.select { |m| m.age == 21 }.each_with_index do |user, i|
|
48
|
+
puts "#{i}: #{user.name}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/spec'
|
3
|
+
require 'mocha'
|
4
|
+
require 'redgreen'
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
9
|
+
require 'ambition'
|
10
|
+
|
11
|
+
class User
|
12
|
+
extend Ambition
|
13
|
+
|
14
|
+
def self.reflections
|
15
|
+
return @reflections if @reflections
|
16
|
+
@reflections = {}
|
17
|
+
@reflections[:ideas] = Reflection.new(:has_many, 'user_id', :ideas, 'ideas')
|
18
|
+
@reflections[:invites] = Reflection.new(:has_many, 'referrer_id', :invites, 'invites')
|
19
|
+
@reflections[:profile] = Reflection.new(:has_one, 'user_id', :profile, 'profiles')
|
20
|
+
@reflections[:account] = Reflection.new(:belongs_to, 'account_id', :account, 'accounts')
|
21
|
+
@reflections
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.table_name
|
25
|
+
'users'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Reflection < Struct.new(:macro, :primary_key_name, :name, :table_name)
|
30
|
+
end
|
data/test/join_test.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Joins" do
|
4
|
+
specify "simple == on an association" do
|
5
|
+
sql = User.select { |m| m.account.email == 'chris@ozmm.org' }
|
6
|
+
sql.to_hash.should == {
|
7
|
+
:conditions => "accounts.email = 'chris@ozmm.org'",
|
8
|
+
:include => [:account]
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "simple mixed == on an association" do
|
13
|
+
sql = User.select { |m| m.name == 'chris' && m.account.email == 'chris@ozmm.org' }
|
14
|
+
sql.to_hash.should == {
|
15
|
+
:conditions => "(users.`name` = 'chris' AND accounts.email = 'chris@ozmm.org')",
|
16
|
+
:include => [:account]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "multiple associations" do
|
21
|
+
sql = User.select { |m| m.ideas.title == 'New Freezer' || m.invites.email == 'pj@hyett.com' }
|
22
|
+
sql.to_hash.should == {
|
23
|
+
:conditions => "(ideas.title = 'New Freezer' OR invites.email = 'pj@hyett.com')",
|
24
|
+
:include => [:ideas, :invites]
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "non-existant associations" do
|
29
|
+
sql = User.select { |m| m.liquor.brand == 'Jack' }
|
30
|
+
should.raise { sql.to_hash }
|
31
|
+
end
|
32
|
+
end
|
data/test/limit_test.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Limit" do
|
4
|
+
setup do
|
5
|
+
@sql = User.select { |m| m.name == 'jon' }
|
6
|
+
end
|
7
|
+
|
8
|
+
specify "first" do
|
9
|
+
conditions = { :conditions => "users.`name` = 'jon'", :limit => '1' }
|
10
|
+
User.expects(:find).with(:first, conditions)
|
11
|
+
@sql.first
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "first with argument" do
|
15
|
+
conditions = { :conditions => "users.`name` = 'jon'", :limit => '5' }
|
16
|
+
User.expects(:find).with(:all, conditions)
|
17
|
+
@sql.first(5)
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "[] with one element" do
|
21
|
+
conditions = { :conditions => "users.`name` = 'jon'", :limit => '10, 1' }
|
22
|
+
User.expects(:find).with(:all, conditions)
|
23
|
+
@sql[10]
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "[] with two elements" do
|
27
|
+
conditions = { :conditions => "users.`name` = 'jon'", :limit => '10, 20' }
|
28
|
+
User.expects(:find).with(:all, conditions)
|
29
|
+
@sql[10, 20]
|
30
|
+
end
|
31
|
+
|
32
|
+
specify "[] with range" do
|
33
|
+
conditions = { :conditions => "users.`name` = 'jon'", :limit => '10, 10' }
|
34
|
+
User.expects(:find).with(:all, conditions)
|
35
|
+
@sql[10..20]
|
36
|
+
end
|
37
|
+
end
|
data/test/order_test.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Order" do
|
4
|
+
setup do
|
5
|
+
@sql = User.select { |m| m.name == 'jon' }
|
6
|
+
end
|
7
|
+
|
8
|
+
specify "simple order" do
|
9
|
+
string = @sql.sort_by { |m| m.name }.to_sql
|
10
|
+
string.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name"
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "simple combined order" do
|
14
|
+
string = @sql.sort_by { |m| [ m.name, m.age ] }.to_sql
|
15
|
+
string.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name, users.age"
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "simple combined order with single reverse" do
|
19
|
+
string = @sql.sort_by { |m| [ m.name, -m.age ] }.to_sql
|
20
|
+
string.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name, users.age DESC"
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "simple combined order with two reverses" do
|
24
|
+
string = @sql.sort_by { |m| [ -m.name, -m.age ] }.to_sql
|
25
|
+
string.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.name DESC, users.age DESC"
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "reverse order with -" do
|
29
|
+
string = @sql.sort_by { |m| -m.age }.to_sql
|
30
|
+
string.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.age DESC"
|
31
|
+
end
|
32
|
+
|
33
|
+
xspecify "reverse order with #reverse" do
|
34
|
+
# TODO: not implemented
|
35
|
+
string = @sql.sort_by { |m| m.age }.reverse.to_sql
|
36
|
+
string.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY users.age DESC"
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "random order" do
|
40
|
+
string = @sql.sort_by { rand }.to_sql
|
41
|
+
string.should == "SELECT * FROM users WHERE users.`name` = 'jon' ORDER BY RAND()"
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "non-existent method to sort by" do
|
45
|
+
should.raise { @sql.sort_by { foo }.to_sql }
|
46
|
+
end
|
47
|
+
|
48
|
+
specify "Symbol#to_proc" do
|
49
|
+
string = User.sort_by(&:name).to_sql
|
50
|
+
string.should == "SELECT * FROM users ORDER BY users.name"
|
51
|
+
end
|
52
|
+
end
|
data/test/types_test.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Once dynamically, once hardcoded
|
5
|
+
context "Different types" do
|
6
|
+
types_hash = {
|
7
|
+
'string' => "'string'",
|
8
|
+
:symbol => "'--- :symbol\n'",
|
9
|
+
1 => '1',
|
10
|
+
1.2 => '1.2',
|
11
|
+
nil => 'NULL',
|
12
|
+
true => '1',
|
13
|
+
false => '0',
|
14
|
+
Time.now => "'#{Time.now.to_s(:db)}'",
|
15
|
+
DateTime.now => "'#{DateTime.now.to_s(:db)}'",
|
16
|
+
Date.today => "'#{Date.today.to_s(:db)}'"
|
17
|
+
}
|
18
|
+
|
19
|
+
types_hash.each do |type, translation|
|
20
|
+
specify "simple using #{type}" do
|
21
|
+
sql = User.select { |m| m.name == type }.to_sql
|
22
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = #{translation}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "float" do
|
27
|
+
sql = User.select { |m| m.name == 1.2 }.to_sql
|
28
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = 1.2"
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "integer" do
|
32
|
+
sql = User.select { |m| m.name == 1 }.to_sql
|
33
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = 1"
|
34
|
+
end
|
35
|
+
|
36
|
+
specify "true" do
|
37
|
+
sql = User.select { |m| m.name == true }.to_sql
|
38
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = 1"
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "false" do
|
42
|
+
sql = User.select { |m| m.name == false }.to_sql
|
43
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = 0"
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "nil" do
|
47
|
+
sql = User.select { |m| m.name == nil }.to_sql
|
48
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = NULL"
|
49
|
+
end
|
50
|
+
|
51
|
+
xspecify "Time" do
|
52
|
+
# TODO: nothing but variables inside blocks for now
|
53
|
+
sql = User.select { |m| m.name == Time.now }.to_sql
|
54
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = NULL"
|
55
|
+
end
|
56
|
+
end
|
data/test/where_test.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
context "Where (using select)" do
|
4
|
+
specify "simple ==" do
|
5
|
+
sql = User.select { |m| m.name == 'jon' }.to_sql
|
6
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = 'jon'"
|
7
|
+
end
|
8
|
+
|
9
|
+
specify "simple !=" do
|
10
|
+
sql = User.select { |m| m.name != 'jon' }.to_sql
|
11
|
+
sql.should == "SELECT * FROM users WHERE users.`name` <> 'jon'"
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "simple == && ==" do
|
15
|
+
sql = User.select { |m| m.name == 'jon' && m.age == 21 }.to_sql
|
16
|
+
sql.should == "SELECT * FROM users WHERE (users.`name` = 'jon' AND users.`age` = 21)"
|
17
|
+
end
|
18
|
+
|
19
|
+
specify "simple == || ==" do
|
20
|
+
sql = User.select { |m| m.name == 'jon' || m.age == 21 }.to_sql
|
21
|
+
sql.should == "SELECT * FROM users WHERE (users.`name` = 'jon' OR users.`age` = 21)"
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "mixed && and ||" do
|
25
|
+
sql = User.select { |m| m.name == 'jon' || m.age == 21 && m.password == 'pass' }.to_sql
|
26
|
+
sql.should == "SELECT * FROM users WHERE (users.`name` = 'jon' OR (users.`age` = 21 AND users.`password` = 'pass'))"
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "grouped && and ||" do
|
30
|
+
sql = User.select { |m| (m.name == 'jon' || m.name == 'rick') && m.age == 21 }.to_sql
|
31
|
+
sql.should == "SELECT * FROM users WHERE ((users.`name` = 'jon' OR users.`name` = 'rick') AND users.`age` = 21)"
|
32
|
+
end
|
33
|
+
|
34
|
+
specify "simple >/<" do
|
35
|
+
sql = User.select { |m| m.age > 21 }.to_sql
|
36
|
+
sql.should == "SELECT * FROM users WHERE users.`age` > 21"
|
37
|
+
|
38
|
+
sql = User.select { |m| m.age < 21 }.to_sql
|
39
|
+
sql.should == "SELECT * FROM users WHERE users.`age` < 21"
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "array.include? item" do
|
43
|
+
sql = User.select { |m| [1, 2, 3, 4].include? m.id }.to_sql
|
44
|
+
sql.should == "SELECT * FROM users WHERE users.`id` IN (1, 2, 3, 4)"
|
45
|
+
end
|
46
|
+
|
47
|
+
specify "simple == with variables" do
|
48
|
+
me = 'chris'
|
49
|
+
sql = User.select { |m| m.name == me }.to_sql
|
50
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{me}'"
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "simple == with method arguments" do
|
54
|
+
def test_it(name)
|
55
|
+
sql = User.select { |m| m.name == name }.to_sql
|
56
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{name}'"
|
57
|
+
end
|
58
|
+
|
59
|
+
test_it('chris')
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "simple == with instance variables" do
|
63
|
+
@me = 'chris'
|
64
|
+
sql = User.select { |m| m.name == @me }.to_sql
|
65
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{@me}'"
|
66
|
+
end
|
67
|
+
|
68
|
+
xspecify "simple == with instance variable method call" do
|
69
|
+
require 'ostruct'
|
70
|
+
@person = OpenStruct.new(:name => 'chris')
|
71
|
+
|
72
|
+
sql = User.select { |m| m.name == @person.name }.to_sql
|
73
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{@person.name}'"
|
74
|
+
end
|
75
|
+
|
76
|
+
specify "simple == with global variables" do
|
77
|
+
$my_name = 'boston'
|
78
|
+
sql = User.select { |m| m.name == $my_name }.to_sql
|
79
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{$my_name}'"
|
80
|
+
end
|
81
|
+
|
82
|
+
specify "simple == with method call" do
|
83
|
+
def band
|
84
|
+
'megadeth'
|
85
|
+
end
|
86
|
+
|
87
|
+
sql = User.select { |m| m.name == band }.to_sql
|
88
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{band}'"
|
89
|
+
end
|
90
|
+
|
91
|
+
specify "simple =~ with string" do
|
92
|
+
sql = User.select { |m| m.name =~ 'chris' }.to_sql
|
93
|
+
sql.should == "SELECT * FROM users WHERE users.`name` LIKE 'chris'"
|
94
|
+
|
95
|
+
sql = User.select { |m| m.name =~ 'chri%' }.to_sql
|
96
|
+
sql.should == "SELECT * FROM users WHERE users.`name` LIKE 'chri%'"
|
97
|
+
end
|
98
|
+
|
99
|
+
specify "simple !~ with string" do
|
100
|
+
sql = User.select { |m| m.name !~ 'chris' }.to_sql
|
101
|
+
sql.should == "SELECT * FROM users WHERE users.`name` NOT LIKE 'chris'"
|
102
|
+
|
103
|
+
sql = User.select { |m| !(m.name =~ 'chris') }.to_sql
|
104
|
+
sql.should == "SELECT * FROM users WHERE users.`name` NOT LIKE 'chris'"
|
105
|
+
end
|
106
|
+
|
107
|
+
specify "simple =~ with regexp" do
|
108
|
+
sql = User.select { |m| m.name =~ /chris/ }.to_sql
|
109
|
+
sql.should == "SELECT * FROM users WHERE users.`name` REGEXP 'chris'"
|
110
|
+
end
|
111
|
+
|
112
|
+
specify "undefined equality symbol" do
|
113
|
+
should.raise { User.select { |m| m.name =* /chris/ }.to_sql }
|
114
|
+
end
|
115
|
+
|
116
|
+
specify "undefined inequality symbol" do
|
117
|
+
should.raise { User.select { |m| m.name !+ 'chris' }.to_sql }
|
118
|
+
end
|
119
|
+
|
120
|
+
xspecify "simple == with inline ruby" do
|
121
|
+
# TODO: implement this
|
122
|
+
sql = User.select { |m| m.created_at == 2.days.ago.to_s(:db) }.to_sql
|
123
|
+
sql.should == "SELECT * FROM users WHERE users.`created_at` = #{2.days.ago.to_s(:db)}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "Where (using detect)" do
|
128
|
+
specify "simple ==" do
|
129
|
+
conditions = { :conditions => "users.`name` = 'chris'", :limit => '1' }
|
130
|
+
User.expects(:find).with(:first, conditions)
|
131
|
+
User.detect { |m| m.name == 'chris' }
|
132
|
+
end
|
133
|
+
|
134
|
+
specify "nothing found" do
|
135
|
+
conditions = { :conditions => "users.`name` = 'chris'", :limit => '1' }
|
136
|
+
User.expects(:find).with(:first, conditions).returns(nil)
|
137
|
+
User.detect { |m| m.name == 'chris' }.should.be.nil
|
138
|
+
end
|
139
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: ambition
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-08-29 00:00:00 -07:00
|
8
|
+
summary: Ambition builds SQL from plain jane Ruby.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: chris@ozmm.org
|
12
|
+
homepage: http://errtheblog.com/
|
13
|
+
rubyforge_project: err
|
14
|
+
description: Ambition builds SQL from plain jane Ruby.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Chris Wanstrath
|
31
|
+
files:
|
32
|
+
- ./init.rb
|
33
|
+
- ./lib/ambition/count.rb
|
34
|
+
- ./lib/ambition/enumerable.rb
|
35
|
+
- ./lib/ambition/limit.rb
|
36
|
+
- ./lib/ambition/order.rb
|
37
|
+
- ./lib/ambition/processor.rb
|
38
|
+
- ./lib/ambition/query.rb
|
39
|
+
- ./lib/ambition/where.rb
|
40
|
+
- ./lib/ambition.rb
|
41
|
+
- ./lib/proc_to_ruby.rb
|
42
|
+
- ./LICENSE
|
43
|
+
- ./Rakefile
|
44
|
+
- ./README
|
45
|
+
- ./test/chaining_test.rb
|
46
|
+
- ./test/count_test.rb
|
47
|
+
- ./test/enumerable_test.rb
|
48
|
+
- ./test/helper.rb
|
49
|
+
- ./test/join_test.rb
|
50
|
+
- ./test/limit_test.rb
|
51
|
+
- ./test/order_test.rb
|
52
|
+
- ./test/types_test.rb
|
53
|
+
- ./test/where_test.rb
|
54
|
+
- ./Manifest
|
55
|
+
test_files: []
|
56
|
+
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
executables: []
|
62
|
+
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
dependencies:
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: ParseTree
|
70
|
+
version_requirement:
|
71
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.0.1
|
76
|
+
version:
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: activerecord
|
79
|
+
version_requirement:
|
80
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 1.15.0
|
85
|
+
version:
|