ambition 0.1.5 → 0.1.6
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/Manifest +24 -2
- data/Rakefile +1 -1
- data/lib/ambition/database_statements.rb +31 -0
- data/lib/ambition/enumerable.rb +1 -1
- data/lib/ambition/limit.rb +34 -12
- data/lib/ambition/proc_to_ruby.rb +25 -0
- data/lib/ambition/processor.rb +31 -5
- data/lib/ambition/query.rb +7 -2
- data/lib/ambition/where.rb +21 -8
- data/lib/ambition.rb +6 -3
- data/test/console +9 -0
- data/test/databases/boot.rb +3 -0
- data/test/databases/database.yml +17 -0
- data/test/databases/fixtures/admin.rb +3 -0
- data/test/databases/fixtures/companies.yml +24 -0
- data/test/databases/fixtures/company.rb +23 -0
- data/test/databases/fixtures/developer.rb +11 -0
- data/test/databases/fixtures/developers_projects.yml +13 -0
- data/test/databases/fixtures/project.rb +4 -0
- data/test/databases/fixtures/projects.yml +7 -0
- data/test/databases/fixtures/replies.yml +20 -0
- data/test/databases/fixtures/reply.rb +5 -0
- data/test/databases/fixtures/topic.rb +19 -0
- data/test/databases/fixtures/topics.yml +32 -0
- data/test/databases/fixtures/user.rb +2 -0
- data/test/databases/fixtures/users.yml +35 -0
- data/test/databases/lib/activerecord_test_connector.rb +65 -0
- data/test/databases/lib/load_fixtures.rb +13 -0
- data/test/databases/lib/schema.rb +41 -0
- data/test/enumerable_test.rb +3 -3
- data/test/helper.rb +25 -7
- data/test/limit_test.rb +13 -15
- data/test/types_test.rb +6 -1
- data/test/where_test.rb +88 -4
- metadata +26 -4
- data/lib/proc_to_ruby.rb +0 -36
data/Manifest
CHANGED
@@ -1,18 +1,41 @@
|
|
1
1
|
./init.rb
|
2
2
|
./lib/ambition/count.rb
|
3
|
+
./lib/ambition/database_statements.rb
|
3
4
|
./lib/ambition/enumerable.rb
|
4
5
|
./lib/ambition/limit.rb
|
5
6
|
./lib/ambition/order.rb
|
7
|
+
./lib/ambition/proc_to_ruby.rb
|
6
8
|
./lib/ambition/processor.rb
|
7
9
|
./lib/ambition/query.rb
|
8
10
|
./lib/ambition/where.rb
|
9
11
|
./lib/ambition.rb
|
10
|
-
./lib/proc_to_ruby.rb
|
11
12
|
./LICENSE
|
13
|
+
./Manifest
|
12
14
|
./Rakefile
|
13
15
|
./README
|
14
16
|
./test/chaining_test.rb
|
17
|
+
./test/console
|
18
|
+
./test/constructive_test.rb
|
15
19
|
./test/count_test.rb
|
20
|
+
./test/databases/boot.rb
|
21
|
+
./test/databases/database.yml
|
22
|
+
./test/databases/fixtures/admin.rb
|
23
|
+
./test/databases/fixtures/companies.yml
|
24
|
+
./test/databases/fixtures/company.rb
|
25
|
+
./test/databases/fixtures/developer.rb
|
26
|
+
./test/databases/fixtures/developers_projects.yml
|
27
|
+
./test/databases/fixtures/project.rb
|
28
|
+
./test/databases/fixtures/projects.yml
|
29
|
+
./test/databases/fixtures/replies.yml
|
30
|
+
./test/databases/fixtures/reply.rb
|
31
|
+
./test/databases/fixtures/topic.rb
|
32
|
+
./test/databases/fixtures/topics.yml
|
33
|
+
./test/databases/fixtures/user.rb
|
34
|
+
./test/databases/fixtures/users.yml
|
35
|
+
./test/databases/lib/activerecord_test_connector.rb
|
36
|
+
./test/databases/lib/load_fixtures.rb
|
37
|
+
./test/databases/lib/schema.rb
|
38
|
+
./test/destructive_test.rb
|
16
39
|
./test/enumerable_test.rb
|
17
40
|
./test/helper.rb
|
18
41
|
./test/join_test.rb
|
@@ -20,4 +43,3 @@
|
|
20
43
|
./test/order_test.rb
|
21
44
|
./test/types_test.rb
|
22
45
|
./test/where_test.rb
|
23
|
-
./Manifest
|
data/Rakefile
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Ambition
|
2
|
+
module DatabaseStatements
|
3
|
+
def self.const_missing(*args)
|
4
|
+
Abstract
|
5
|
+
end
|
6
|
+
|
7
|
+
class Abstract
|
8
|
+
def regexp(options)
|
9
|
+
'REGEXP'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class PostgreSQL < Abstract
|
14
|
+
def regexp(regexp)
|
15
|
+
if regexp.options == 1
|
16
|
+
'~*'
|
17
|
+
else
|
18
|
+
'~'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def negated_regexp(regexp)
|
23
|
+
if regexp.options == 1
|
24
|
+
'!~*'
|
25
|
+
else
|
26
|
+
'!~'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/ambition/enumerable.rb
CHANGED
data/lib/ambition/limit.rb
CHANGED
@@ -1,27 +1,35 @@
|
|
1
1
|
module Ambition
|
2
2
|
module Limit
|
3
|
-
def first(limit = 1
|
4
|
-
query_context.add LimitProcessor.new(limit
|
5
|
-
|
3
|
+
def first(limit = 1)
|
4
|
+
query_context.add LimitProcessor.new(limit)
|
5
|
+
|
6
|
+
if limit == 1
|
7
|
+
find(:first, query_context.to_hash)
|
8
|
+
else
|
9
|
+
query_context
|
10
|
+
end
|
6
11
|
end
|
7
12
|
|
8
|
-
def
|
9
|
-
return first(offset, limit) if limit
|
13
|
+
def slice(offset, limit = nil)
|
10
14
|
|
11
15
|
if offset.is_a? Range
|
12
16
|
limit = offset.end
|
13
17
|
limit -= 1 if offset.exclude_end?
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
offset = offset.first
|
19
|
+
limit -= offset
|
20
|
+
elsif limit.nil?
|
21
|
+
return find(offset)
|
17
22
|
end
|
23
|
+
|
24
|
+
query_context.add OffsetProcessor.new(offset)
|
25
|
+
query_context.add LimitProcessor.new(limit)
|
18
26
|
end
|
19
|
-
alias_method :
|
27
|
+
alias_method :[], :slice
|
20
28
|
end
|
21
29
|
|
22
30
|
class LimitProcessor
|
23
|
-
def initialize(
|
24
|
-
@
|
31
|
+
def initialize(limit)
|
32
|
+
@limit = limit
|
25
33
|
end
|
26
34
|
|
27
35
|
def key
|
@@ -29,7 +37,21 @@ module Ambition
|
|
29
37
|
end
|
30
38
|
|
31
39
|
def to_s
|
32
|
-
@
|
40
|
+
@limit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class OffsetProcessor
|
45
|
+
def initialize(offset)
|
46
|
+
@offset = offset
|
47
|
+
end
|
48
|
+
|
49
|
+
def key
|
50
|
+
:offset
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
@offset
|
33
55
|
end
|
34
56
|
end
|
35
57
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
##
|
2
|
+
# Taken from ruby2ruby, Copyright (c) 2006 Ryan Davis under the MIT License
|
3
|
+
require 'parse_tree'
|
4
|
+
require 'sexp_processor'
|
5
|
+
|
6
|
+
class ProcHolder
|
7
|
+
end
|
8
|
+
|
9
|
+
class Method
|
10
|
+
def to_sexp
|
11
|
+
ParseTree.translate(ProcHolder, :proc_to_method)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Proc
|
16
|
+
def to_method
|
17
|
+
ProcHolder.send(:define_method, :proc_to_method, self)
|
18
|
+
ProcHolder.new.method(:proc_to_method)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_sexp
|
22
|
+
body = to_method.to_sexp[2][1..-1]
|
23
|
+
[:proc, *body]
|
24
|
+
end
|
25
|
+
end
|
data/lib/ambition/processor.rb
CHANGED
@@ -45,15 +45,41 @@ module Ambition
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def sanitize(value)
|
48
|
-
|
49
|
-
|
50
|
-
when 'false' then '0'
|
51
|
-
else ActiveRecord::Base.connection.quote(value) rescue quote(value)
|
48
|
+
if value.is_a? Array
|
49
|
+
return value.map { |v| sanitize(v) }.join(', ')
|
52
50
|
end
|
51
|
+
|
52
|
+
case value
|
53
|
+
when true, 'true'
|
54
|
+
'1'
|
55
|
+
when false, 'false'
|
56
|
+
'0'
|
57
|
+
when Regexp
|
58
|
+
"'#{value.source}'"
|
59
|
+
else
|
60
|
+
ActiveRecord::Base.connection.quote(value)
|
61
|
+
end
|
62
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
63
|
+
quote(value)
|
64
|
+
rescue
|
65
|
+
"'#{value}'"
|
53
66
|
end
|
54
67
|
|
55
68
|
def quote_column_name(value)
|
56
|
-
ActiveRecord::Base.connection.quote_column_name(value)
|
69
|
+
ActiveRecord::Base.connection.quote_column_name(value)
|
70
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
71
|
+
value.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
def statement(*args)
|
75
|
+
@statement_instnace ||= DatabaseStatements.const_get(adapter_name).new
|
76
|
+
@statement_instnace.send(*args)
|
77
|
+
end
|
78
|
+
|
79
|
+
def adapter_name
|
80
|
+
ActiveRecord::Base.connection.adapter_name
|
81
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
82
|
+
'Abstract'
|
57
83
|
end
|
58
84
|
|
59
85
|
def extract_includes(receiver, method)
|
data/lib/ambition/query.rb
CHANGED
@@ -45,7 +45,11 @@ module Ambition
|
|
45
45
|
end
|
46
46
|
|
47
47
|
if limit = keyed[:limit]
|
48
|
-
hash[:limit] = limit.
|
48
|
+
hash[:limit] = limit.last
|
49
|
+
end
|
50
|
+
|
51
|
+
if offset = keyed[:offset]
|
52
|
+
hash[:offset] = offset.last
|
49
53
|
end
|
50
54
|
|
51
55
|
hash
|
@@ -58,7 +62,8 @@ module Ambition
|
|
58
62
|
sql << "JOIN #{hash[:includes].join(', ')}" unless hash[:includes].blank?
|
59
63
|
sql << "WHERE #{hash[:conditions].join(' AND ')}" unless hash[:conditions].blank?
|
60
64
|
sql << "ORDER BY #{hash[:order].join(', ')}" unless hash[:order].blank?
|
61
|
-
sql << "LIMIT #{hash[:limit]
|
65
|
+
sql << "LIMIT #{hash[:limit]}" unless hash[:limit].blank?
|
66
|
+
sql << "OFFSET #{hash[:limit]}" unless hash[:offset].blank?
|
62
67
|
|
63
68
|
@@select % [ @table_name, sql.join(' ') ]
|
64
69
|
end
|
data/lib/ambition/where.rb
CHANGED
@@ -30,9 +30,16 @@ module Ambition
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def process_not(exp)
|
33
|
-
|
33
|
+
type, receiver, method, other = *exp.first
|
34
34
|
exp.clear
|
35
|
-
|
35
|
+
|
36
|
+
case type
|
37
|
+
when :call
|
38
|
+
translation(receiver, negate(method, other), other)
|
39
|
+
when :match3
|
40
|
+
regexp = receiver.last
|
41
|
+
"#{process(method)} #{statement(:negated_regexp, regexp)} #{sanitize(regexp)}"
|
42
|
+
end
|
36
43
|
end
|
37
44
|
|
38
45
|
def process_call(exp)
|
@@ -63,8 +70,8 @@ module Ambition
|
|
63
70
|
end
|
64
71
|
|
65
72
|
def process_match3(exp)
|
66
|
-
regexp, target = exp.shift.last
|
67
|
-
"#{target}
|
73
|
+
regexp, target = exp.shift.last, process(exp.shift)
|
74
|
+
"#{target} #{statement(:regexp, regexp)} #{sanitize(regexp)}"
|
68
75
|
end
|
69
76
|
|
70
77
|
def process_dvar(exp)
|
@@ -111,23 +118,29 @@ module Ambition
|
|
111
118
|
sanitize eval(variable, @block)
|
112
119
|
end
|
113
120
|
|
114
|
-
def negate(method)
|
121
|
+
def negate(method, target = nil)
|
122
|
+
if Array(target).last == [:nil]
|
123
|
+
return 'IS NOT'
|
124
|
+
end
|
125
|
+
|
115
126
|
case method
|
116
127
|
when :==
|
117
128
|
'<>'
|
118
129
|
when :=~
|
119
130
|
'!~'
|
120
131
|
else
|
121
|
-
raise "Not implemented: #{method}"
|
132
|
+
raise "Not implemented: #{method.inspect}"
|
122
133
|
end
|
123
134
|
end
|
124
135
|
|
125
|
-
def translation(receiver, method, other)
|
136
|
+
def translation(receiver, method, other = nil)
|
126
137
|
case method.to_s
|
138
|
+
when 'IS NOT'
|
139
|
+
"#{process(receiver)} IS NOT #{process(other)}"
|
127
140
|
when '=='
|
128
141
|
case other_value = process(other)
|
129
142
|
when "NULL"
|
130
|
-
"#{process(receiver)}
|
143
|
+
"#{process(receiver)} IS #{other_value}"
|
131
144
|
else
|
132
145
|
"#{process(receiver)} = #{other_value}"
|
133
146
|
end
|
data/lib/ambition.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
require '
|
3
|
-
require '
|
1
|
+
unless defined? ActiveRecord
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
end
|
5
|
+
require 'ambition/proc_to_ruby'
|
4
6
|
require 'ambition/processor'
|
5
7
|
require 'ambition/query'
|
6
8
|
require 'ambition/where'
|
@@ -8,6 +10,7 @@ require 'ambition/order'
|
|
8
10
|
require 'ambition/limit'
|
9
11
|
require 'ambition/count'
|
10
12
|
require 'ambition/enumerable'
|
13
|
+
require 'ambition/database_statements'
|
11
14
|
|
12
15
|
module Ambition
|
13
16
|
include Where, Order, Limit, Enumerable, Count
|
data/test/console
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
3
|
+
libs = []
|
4
|
+
dirname = File.dirname(__FILE__)
|
5
|
+
|
6
|
+
libs << 'irb/completion'
|
7
|
+
libs << File.join(dirname, 'databases', 'boot')
|
8
|
+
|
9
|
+
exec "#{irb} #{libs.map{|l| " -r #{l}" }.join} --simple-prompt"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: db/development.sqlite3
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
database: ambition_development
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
host: localhost
|
11
|
+
|
12
|
+
postgres:
|
13
|
+
adapter: postgresql
|
14
|
+
database: ambition_development
|
15
|
+
username: root
|
16
|
+
password:
|
17
|
+
host: localhost
|
@@ -0,0 +1,24 @@
|
|
1
|
+
thirty_seven_signals:
|
2
|
+
id: 1
|
3
|
+
name: 37Signals
|
4
|
+
rating: 4
|
5
|
+
|
6
|
+
TextDrive:
|
7
|
+
id: 2
|
8
|
+
name: TextDrive
|
9
|
+
rating: 3
|
10
|
+
|
11
|
+
PlanetArgon:
|
12
|
+
id: 3
|
13
|
+
name: Planet Argon
|
14
|
+
rating: 3
|
15
|
+
|
16
|
+
Google:
|
17
|
+
id: 4
|
18
|
+
name: Google
|
19
|
+
rating: 5
|
20
|
+
|
21
|
+
Ionist:
|
22
|
+
id: 5
|
23
|
+
name: Ioni.st
|
24
|
+
rating: 4
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Company < ActiveRecord::Base
|
2
|
+
attr_protected :rating
|
3
|
+
set_sequence_name :companies_nonstd_seq
|
4
|
+
|
5
|
+
validates_presence_of :name
|
6
|
+
def validate
|
7
|
+
errors.add('rating', 'rating should not be 2') if rating == 2
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.with_best
|
11
|
+
with_scope :find => { :conditions => ['companies.rating > ?', 3] } do
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find_best(*args)
|
17
|
+
with_best { find(*args) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.calculate_best(*args)
|
21
|
+
with_best { calculate(*args) }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Developer < User
|
2
|
+
has_and_belongs_to_many :projects, :include => :topics, :order => 'projects.name'
|
3
|
+
|
4
|
+
def self.with_poor_ones(&block)
|
5
|
+
with_scope :find => { :conditions => ['salary <= ?', 80000], :order => 'salary' } do
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.per_page() 10 end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
witty_retort:
|
2
|
+
id: 1
|
3
|
+
topic_id: 1
|
4
|
+
content: Birdman is better!
|
5
|
+
created_at: <%= 6.hours.ago.to_s(:db) %>
|
6
|
+
updated_at: nil
|
7
|
+
|
8
|
+
another:
|
9
|
+
id: 2
|
10
|
+
topic_id: 2
|
11
|
+
content: Nuh uh!
|
12
|
+
created_at: <%= 1.hour.ago.to_s(:db) %>
|
13
|
+
updated_at: nil
|
14
|
+
|
15
|
+
spam:
|
16
|
+
id: 3
|
17
|
+
topic_id: 1
|
18
|
+
content: Nice site!
|
19
|
+
created_at: <%= 1.hour.ago.to_s(:db) %>
|
20
|
+
updated_at: nil
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Topic < ActiveRecord::Base
|
2
|
+
has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC'
|
3
|
+
belongs_to :project
|
4
|
+
|
5
|
+
# pretend find and count were extended and accept an extra option
|
6
|
+
# if there is a :foo option, prepend its value to collection
|
7
|
+
def self.find(*args)
|
8
|
+
more = []
|
9
|
+
more << args.last.delete(:foo) if args.last.is_a?(Hash) and args.last[:foo]
|
10
|
+
res = super
|
11
|
+
more.empty?? res : more + res
|
12
|
+
end
|
13
|
+
|
14
|
+
# if there is a :foo option, always return 100
|
15
|
+
def self.count(*args)
|
16
|
+
return 100 if args.last.is_a?(Hash) and args.last[:foo]
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
futurama:
|
2
|
+
id: 1
|
3
|
+
title: Isnt futurama awesome?
|
4
|
+
subtitle: It really is, isnt it.
|
5
|
+
content: I like futurama
|
6
|
+
created_at: <%= 1.day.ago.to_s(:db) %>
|
7
|
+
updated_at: <%= 1.day.ago.to_s(:db) %>
|
8
|
+
|
9
|
+
harvey_birdman:
|
10
|
+
id: 2
|
11
|
+
title: Harvey Birdman is the king of all men
|
12
|
+
subtitle: yup
|
13
|
+
content: He really is
|
14
|
+
created_at: <%= 2.hours.ago.to_s(:db) %>
|
15
|
+
updated_at: <%= 2.hours.ago.to_s(:db) %>
|
16
|
+
|
17
|
+
rails:
|
18
|
+
id: 3
|
19
|
+
project_id: 1
|
20
|
+
title: Rails is nice
|
21
|
+
subtitle: It makes me happy
|
22
|
+
content: except when I have to hack internals to fix pagination. even then really.
|
23
|
+
created_at: <%= 20.minutes.ago.to_s(:db) %>
|
24
|
+
updated_at: <%= 20.minutes.ago.to_s(:db) %>
|
25
|
+
|
26
|
+
ar:
|
27
|
+
id: 4
|
28
|
+
project_id: 1
|
29
|
+
title: ActiveRecord sometimes freaks me out
|
30
|
+
content: "I mean, what's the deal with eager loading?"
|
31
|
+
created_at: <%= 15.minutes.ago.to_s(:db) %>
|
32
|
+
updated_at: <%= 15.minutes.ago.to_s(:db) %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
david:
|
2
|
+
id: 1
|
3
|
+
name: David
|
4
|
+
salary: 80000
|
5
|
+
type: Developer
|
6
|
+
|
7
|
+
jamis:
|
8
|
+
id: 2
|
9
|
+
name: Jamis
|
10
|
+
salary: 150000
|
11
|
+
type: Developer
|
12
|
+
|
13
|
+
<% for digit in 3..10 %>
|
14
|
+
dev_<%= digit %>:
|
15
|
+
id: <%= digit %>
|
16
|
+
name: fixture_<%= digit %>
|
17
|
+
salary: 100000
|
18
|
+
type: Developer
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
poor_jamis:
|
22
|
+
id: 11
|
23
|
+
name: Jamis
|
24
|
+
salary: 9000
|
25
|
+
type: Developer
|
26
|
+
|
27
|
+
admin:
|
28
|
+
id: 12
|
29
|
+
name: admin
|
30
|
+
type: Admin
|
31
|
+
|
32
|
+
goofy:
|
33
|
+
id: 13
|
34
|
+
name: Goofy
|
35
|
+
type: Admin
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class ActiveRecordTestConnector
|
2
|
+
cattr_accessor :able_to_connect
|
3
|
+
cattr_accessor :connected
|
4
|
+
|
5
|
+
# Set our defaults
|
6
|
+
self.connected = false
|
7
|
+
self.able_to_connect = true
|
8
|
+
|
9
|
+
def self.setup
|
10
|
+
unless connected || !able_to_connect
|
11
|
+
setup_connection
|
12
|
+
load_schema
|
13
|
+
require_fixture_models
|
14
|
+
self.connected = true
|
15
|
+
end
|
16
|
+
rescue Exception => e # errors from ActiveRecord setup
|
17
|
+
if e.to_s =~ /unknown database/i
|
18
|
+
puts "\nPlease create an `ambition_development' database to play!"
|
19
|
+
else
|
20
|
+
$stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
|
21
|
+
end
|
22
|
+
self.able_to_connect = false
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.setup_connection
|
28
|
+
if Object.const_defined?(:ActiveRecord)
|
29
|
+
config_file = File.dirname(__FILE__) + '/../database.yml'
|
30
|
+
ActiveRecord::Base.logger = Logger.new STDOUT
|
31
|
+
|
32
|
+
case adapter = ENV['ADAPTER'] || 'mysql'
|
33
|
+
when 'sqlite3'
|
34
|
+
options = { :database => ':memory:', :adapter => 'sqlite3', :timeout => 500 }
|
35
|
+
ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
|
36
|
+
else
|
37
|
+
options = YAML.load_file(config_file)[adapter]
|
38
|
+
end
|
39
|
+
|
40
|
+
puts "Using #{adapter}"
|
41
|
+
|
42
|
+
ActiveRecord::Base.establish_connection(options)
|
43
|
+
ActiveRecord::Base.connection
|
44
|
+
|
45
|
+
unless Object.const_defined?(:QUOTED_TYPE)
|
46
|
+
Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise "Can't setup connection since ActiveRecord isn't loaded."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Load actionpack sqlite tables
|
54
|
+
def self.load_schema
|
55
|
+
ActiveRecord::Base.silence do
|
56
|
+
load File.dirname(__FILE__) + "/schema.rb"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.require_fixture_models
|
61
|
+
models = Dir.glob(File.dirname(__FILE__) + "/../fixtures/*.rb")
|
62
|
+
models = (models.grep(/user.rb/) + models).uniq
|
63
|
+
models.each {|f| require f}
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
dirname = File.dirname(__FILE__)
|
2
|
+
%w(rubygems active_record active_record/version active_record/fixtures).each {|f| require f}
|
3
|
+
puts " (ActiveRecord v#{ActiveRecord::VERSION::STRING})"
|
4
|
+
require File.join(dirname, 'activerecord_test_connector')
|
5
|
+
|
6
|
+
# setup the connection
|
7
|
+
ActiveRecordTestConnector.setup
|
8
|
+
|
9
|
+
# load all fixtures
|
10
|
+
fixture_path = File.join(dirname, '..', 'fixtures')
|
11
|
+
Fixtures.create_fixtures(fixture_path, tables = ActiveRecord::Base.connection.tables - %w(schema_info))
|
12
|
+
|
13
|
+
puts "Available models: #{Dir[fixture_path+'/*.rb'].map{|f|File.basename(f,'.rb')}.map(&:classify).to_sentence}"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 0) do
|
2
|
+
create_table :users, :force => true do |t|
|
3
|
+
t.column :name, :string
|
4
|
+
t.column :type, :string
|
5
|
+
t.column :salary, :integer, :default => 70_000
|
6
|
+
t.column :created_at, :datetime, :null => false
|
7
|
+
t.column :updated_at, :datetime, :null => false
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :replies, :force => true do |t|
|
11
|
+
t.column :content, :string
|
12
|
+
t.column :topic_id, :integer
|
13
|
+
t.column :created_at, :datetime, :null => false
|
14
|
+
t.column :updated_at, :datetime, :null => false
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :topics, :force => true do |t|
|
18
|
+
t.column :project_id, :integer
|
19
|
+
t.column :title, :string
|
20
|
+
t.column :subtitle, :string
|
21
|
+
t.column :content, :text
|
22
|
+
t.column :created_at, :datetime, :null => false
|
23
|
+
t.column :updated_at, :datetime, :null => false
|
24
|
+
end
|
25
|
+
|
26
|
+
create_table :projects, :force => true do |t|
|
27
|
+
t.column :name, :string
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table :developers_projects, :force => true do |t|
|
31
|
+
t.column :developer_id, :integer
|
32
|
+
t.column :project_id, :integer
|
33
|
+
t.column :joined_on, :datetime
|
34
|
+
t.column :access_level, :integer, :default => 1
|
35
|
+
end
|
36
|
+
|
37
|
+
create_table :companies, :force => true do |t|
|
38
|
+
t.column :name, :string
|
39
|
+
t.column :rating, :integer
|
40
|
+
end
|
41
|
+
end
|
data/test/enumerable_test.rb
CHANGED
@@ -10,7 +10,7 @@ context "Each" do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
specify "limit and conditions" do
|
13
|
-
hash = { :limit =>
|
13
|
+
hash = { :limit => 5, :conditions => "users.age = 21" }
|
14
14
|
User.expects(:find).with(:all, hash).returns([])
|
15
15
|
User.select { |m| m.age == 21 }.first(5).each do |user|
|
16
16
|
puts user.name
|
@@ -18,7 +18,7 @@ context "Each" do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
specify "limit and conditions and order" do
|
21
|
-
hash = { :limit =>
|
21
|
+
hash = { :limit => 5, :conditions => "users.age = 21", :order => 'users.name' }
|
22
22
|
User.expects(:find).with(:all, hash).returns([])
|
23
23
|
User.select { |m| m.age == 21 }.sort_by { |m| m.name }.first(5).each do |user|
|
24
24
|
puts user.name
|
@@ -26,7 +26,7 @@ context "Each" do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
specify "limit and order" do
|
29
|
-
hash = { :limit =>
|
29
|
+
hash = { :limit => 5, :order => 'users.name' }
|
30
30
|
User.expects(:find).with(:all, hash).returns([])
|
31
31
|
User.sort_by { |m| m.name }.first(5).each do |user|
|
32
32
|
puts user.name
|
data/test/helper.rb
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
begin
|
3
|
-
require 'test/spec'
|
3
|
+
require 'test/spec' unless $NO_TEST
|
4
4
|
require 'mocha'
|
5
|
-
require 'active_support'
|
6
5
|
rescue LoadError
|
7
|
-
puts "=> You need the test-spec
|
6
|
+
puts "=> You need the test-spec and mocha gems to run these tests."
|
8
7
|
exit
|
9
8
|
end
|
9
|
+
require 'active_support'
|
10
10
|
require 'active_record'
|
11
11
|
|
12
|
-
begin require 'redgreen'; rescue LoadError; end
|
12
|
+
begin require 'redgreen'; rescue LoadError; end unless $NO_TEST
|
13
13
|
|
14
14
|
$:.unshift File.dirname(__FILE__) + '/../lib'
|
15
15
|
require 'ambition'
|
16
16
|
|
17
|
-
class User
|
18
|
-
extend Ambition
|
19
|
-
|
17
|
+
class User < ActiveRecord::Base
|
20
18
|
def self.reflections
|
21
19
|
return @reflections if @reflections
|
22
20
|
@reflections = {}
|
@@ -34,3 +32,23 @@ end
|
|
34
32
|
|
35
33
|
class Reflection < Struct.new(:macro, :primary_key_name, :name, :table_name)
|
36
34
|
end
|
35
|
+
|
36
|
+
module ActiveRecord
|
37
|
+
module ConnectionAdapters
|
38
|
+
class MysqlAdapter
|
39
|
+
def connect(*args)
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class PostgreSQLAdapter
|
45
|
+
def connect(*args)
|
46
|
+
true
|
47
|
+
end
|
48
|
+
class PGError; end
|
49
|
+
end
|
50
|
+
|
51
|
+
class FakeAdapter < AbstractAdapter
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/test/limit_test.rb
CHANGED
@@ -6,38 +6,36 @@ context "Limit" do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
specify "first" do
|
9
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
9
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 1 }
|
10
10
|
User.expects(:find).with(:first, conditions)
|
11
11
|
@sql.first
|
12
12
|
end
|
13
13
|
|
14
14
|
specify "first with argument" do
|
15
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
15
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 5 }
|
16
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]
|
17
|
+
@sql.first(5).entries
|
24
18
|
end
|
25
19
|
|
26
20
|
specify "[] with two elements" do
|
27
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
21
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 20, :offset => 10 }
|
22
|
+
User.expects(:find).with(:all, conditions)
|
23
|
+
@sql[10, 20].entries
|
24
|
+
|
25
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 20, :offset => 20 }
|
28
26
|
User.expects(:find).with(:all, conditions)
|
29
|
-
@sql[
|
27
|
+
@sql[20, 20].entries
|
30
28
|
end
|
31
29
|
|
32
30
|
specify "slice is an alias of []" do
|
33
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
31
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 20, :offset => 10 }
|
34
32
|
User.expects(:find).with(:all, conditions)
|
35
|
-
@sql.slice(10, 20)
|
33
|
+
@sql.slice(10, 20).entries
|
36
34
|
end
|
37
35
|
|
38
36
|
specify "[] with range" do
|
39
|
-
conditions = { :conditions => "users.name = 'jon'", :limit =>
|
37
|
+
conditions = { :conditions => "users.name = 'jon'", :limit => 10, :offset => 10 }
|
40
38
|
User.expects(:find).with(:all, conditions)
|
41
|
-
@sql[10..20]
|
39
|
+
@sql[10..20].entries
|
42
40
|
end
|
43
41
|
end
|
data/test/types_test.rb
CHANGED
@@ -44,7 +44,12 @@ context "Different types" do
|
|
44
44
|
|
45
45
|
specify "nil" do
|
46
46
|
sql = User.select { |m| m.name == nil }.to_sql
|
47
|
-
sql.should == "SELECT * FROM users WHERE users.name
|
47
|
+
sql.should == "SELECT * FROM users WHERE users.name IS NULL"
|
48
|
+
end
|
49
|
+
|
50
|
+
specify "not nil" do
|
51
|
+
sql = User.select { |m| m.name != nil }.to_sql
|
52
|
+
sql.should == "SELECT * FROM users WHERE users.name IS NOT NULL"
|
48
53
|
end
|
49
54
|
|
50
55
|
xspecify "Time" do
|
data/test/where_test.rb
CHANGED
@@ -47,6 +47,12 @@ context "Where (using select)" do
|
|
47
47
|
sql.should == "SELECT * FROM users WHERE users.id IN (1, 2, 3, 4)"
|
48
48
|
end
|
49
49
|
|
50
|
+
specify "variable'd array.include? item" do
|
51
|
+
array = [1, 2, 3, 4]
|
52
|
+
sql = User.select { |m| array.include? m.id }.to_sql
|
53
|
+
sql.should == "SELECT * FROM users WHERE users.id IN (1, 2, 3, 4)"
|
54
|
+
end
|
55
|
+
|
50
56
|
specify "simple == with variables" do
|
51
57
|
me = 'chris'
|
52
58
|
sql = User.select { |m| m.name == me }.to_sql
|
@@ -144,14 +150,92 @@ end
|
|
144
150
|
|
145
151
|
context "Where (using detect)" do
|
146
152
|
specify "simple ==" do
|
147
|
-
|
148
|
-
User.expects(:find).with(:first, conditions)
|
153
|
+
User.expects(:select).returns(mock(:first => true))
|
149
154
|
User.detect { |m| m.name == 'chris' }
|
150
155
|
end
|
151
156
|
|
152
157
|
specify "nothing found" do
|
153
|
-
|
154
|
-
User.expects(:find).with(:first, conditions).returns(nil)
|
158
|
+
User.expects(:select).returns(mock(:first => nil))
|
155
159
|
User.detect { |m| m.name == 'chris' }.should.be.nil
|
156
160
|
end
|
157
161
|
end
|
162
|
+
|
163
|
+
context "Where (using [])" do
|
164
|
+
specify "finds a single row" do
|
165
|
+
User.expects(:find).with(1)
|
166
|
+
User[1]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "PostgreSQL specific" do
|
171
|
+
setup do
|
172
|
+
ActiveRecord::Base.connection = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new 'fake_connection', 'fake_logger'
|
173
|
+
end
|
174
|
+
|
175
|
+
teardown do
|
176
|
+
ActiveRecord::Base.remove_connection
|
177
|
+
end
|
178
|
+
|
179
|
+
specify "quoting of column name" do
|
180
|
+
me = 'chris'
|
181
|
+
sql = User.select { |m| m.name == me }.to_sql
|
182
|
+
sql.should == %(SELECT * FROM users WHERE users."name" = '#{me}')
|
183
|
+
end
|
184
|
+
|
185
|
+
specify "simple =~ with regexp" do
|
186
|
+
sql = User.select { |m| m.name =~ /chris/ }.to_sql
|
187
|
+
sql.should == %(SELECT * FROM users WHERE users."name" ~ 'chris')
|
188
|
+
end
|
189
|
+
|
190
|
+
specify "insensitive =~" do
|
191
|
+
sql = User.select { |m| m.name =~ /chris/i }.to_sql
|
192
|
+
sql.should == %(SELECT * FROM users WHERE users."name" ~* 'chris')
|
193
|
+
end
|
194
|
+
|
195
|
+
specify "negated =~" do
|
196
|
+
sql = User.select { |m| m.name !~ /chris/ }.to_sql
|
197
|
+
sql.should == %(SELECT * FROM users WHERE users."name" !~ 'chris')
|
198
|
+
end
|
199
|
+
|
200
|
+
specify "negated insensitive =~" do
|
201
|
+
sql = User.select { |m| m.name !~ /chris/i }.to_sql
|
202
|
+
sql.should == %(SELECT * FROM users WHERE users."name" !~* 'chris')
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "MySQL specific" do
|
207
|
+
setup do
|
208
|
+
ActiveRecord::Base.connection = ActiveRecord::ConnectionAdapters::MysqlAdapter.new('connection', 'logger', 'options', 'config')
|
209
|
+
end
|
210
|
+
|
211
|
+
teardown do
|
212
|
+
ActiveRecord::Base.remove_connection
|
213
|
+
end
|
214
|
+
|
215
|
+
specify "quoting of column name" do
|
216
|
+
me = 'chris'
|
217
|
+
sql = User.select { |m| m.name == me }.to_sql
|
218
|
+
sql.should == "SELECT * FROM users WHERE users.`name` = '#{me}'"
|
219
|
+
end
|
220
|
+
|
221
|
+
specify "simple =~ with regexp" do
|
222
|
+
sql = User.select { |m| m.name =~ /chris/ }.to_sql
|
223
|
+
sql.should == "SELECT * FROM users WHERE users.`name` REGEXP 'chris'"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context "Adapter without overrides" do
|
228
|
+
setup do
|
229
|
+
ActiveRecord::Base.connection = ActiveRecord::ConnectionAdapters::FakeAdapter.new('connection', 'logger')
|
230
|
+
end
|
231
|
+
|
232
|
+
teardown do
|
233
|
+
ActiveRecord::Base.remove_connection
|
234
|
+
end
|
235
|
+
|
236
|
+
specify "quoting of column name" do
|
237
|
+
me = 'chris'
|
238
|
+
sql = User.select { |m| m.name == me }.to_sql
|
239
|
+
sql.should == "SELECT * FROM users WHERE users.name = '#{me}'"
|
240
|
+
end
|
241
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ambition
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.1.6
|
7
|
+
date: 2007-09-02 00:00:00 -07:00
|
8
8
|
summary: Ambition builds SQL from plain jane Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -31,19 +31,42 @@ authors:
|
|
31
31
|
files:
|
32
32
|
- ./init.rb
|
33
33
|
- ./lib/ambition/count.rb
|
34
|
+
- ./lib/ambition/database_statements.rb
|
34
35
|
- ./lib/ambition/enumerable.rb
|
35
36
|
- ./lib/ambition/limit.rb
|
36
37
|
- ./lib/ambition/order.rb
|
38
|
+
- ./lib/ambition/proc_to_ruby.rb
|
37
39
|
- ./lib/ambition/processor.rb
|
38
40
|
- ./lib/ambition/query.rb
|
39
41
|
- ./lib/ambition/where.rb
|
40
42
|
- ./lib/ambition.rb
|
41
|
-
- ./lib/proc_to_ruby.rb
|
42
43
|
- ./LICENSE
|
44
|
+
- ./Manifest
|
43
45
|
- ./Rakefile
|
44
46
|
- ./README
|
45
47
|
- ./test/chaining_test.rb
|
48
|
+
- ./test/console
|
49
|
+
- ./test/constructive_test.rb
|
46
50
|
- ./test/count_test.rb
|
51
|
+
- ./test/databases/boot.rb
|
52
|
+
- ./test/databases/database.yml
|
53
|
+
- ./test/databases/fixtures/admin.rb
|
54
|
+
- ./test/databases/fixtures/companies.yml
|
55
|
+
- ./test/databases/fixtures/company.rb
|
56
|
+
- ./test/databases/fixtures/developer.rb
|
57
|
+
- ./test/databases/fixtures/developers_projects.yml
|
58
|
+
- ./test/databases/fixtures/project.rb
|
59
|
+
- ./test/databases/fixtures/projects.yml
|
60
|
+
- ./test/databases/fixtures/replies.yml
|
61
|
+
- ./test/databases/fixtures/reply.rb
|
62
|
+
- ./test/databases/fixtures/topic.rb
|
63
|
+
- ./test/databases/fixtures/topics.yml
|
64
|
+
- ./test/databases/fixtures/user.rb
|
65
|
+
- ./test/databases/fixtures/users.yml
|
66
|
+
- ./test/databases/lib/activerecord_test_connector.rb
|
67
|
+
- ./test/databases/lib/load_fixtures.rb
|
68
|
+
- ./test/databases/lib/schema.rb
|
69
|
+
- ./test/destructive_test.rb
|
47
70
|
- ./test/enumerable_test.rb
|
48
71
|
- ./test/helper.rb
|
49
72
|
- ./test/join_test.rb
|
@@ -51,7 +74,6 @@ files:
|
|
51
74
|
- ./test/order_test.rb
|
52
75
|
- ./test/types_test.rb
|
53
76
|
- ./test/where_test.rb
|
54
|
-
- ./Manifest
|
55
77
|
test_files:
|
56
78
|
- test/chaining_test.rb
|
57
79
|
- test/constructive_test.rb
|
data/lib/proc_to_ruby.rb
DELETED
@@ -1,36 +0,0 @@
|
|
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
|