ambition 0.1.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.
- 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:
|