ambition 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -2,7 +2,7 @@ require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
4
 
5
- Version = '0.1.5'
5
+ Version = '0.1.6'
6
6
 
7
7
  module Rake::TaskManager
8
8
  def redefine_task(task_class, args, &block)
@@ -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
@@ -3,7 +3,7 @@ module Ambition
3
3
  include ::Enumerable
4
4
 
5
5
  def each(&block)
6
- find(:all, query_context.to_hash).each(&block)
6
+ entries.each(&block)
7
7
  end
8
8
 
9
9
  def any?(&block)
@@ -1,27 +1,35 @@
1
1
  module Ambition
2
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)
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 [](offset, limit = nil)
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
- first(offset.first, limit - offset.first)
15
- else
16
- first(offset, 1)
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 :slice, :[]
27
+ alias_method :[], :slice
20
28
  end
21
29
 
22
30
  class LimitProcessor
23
- def initialize(*args)
24
- @args = args
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
- @args.compact * ', '
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
@@ -45,15 +45,41 @@ module Ambition
45
45
  end
46
46
 
47
47
  def sanitize(value)
48
- case value.to_s
49
- when 'true' then '1'
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) rescue value.to_s
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)
@@ -45,7 +45,11 @@ module Ambition
45
45
  end
46
46
 
47
47
  if limit = keyed[:limit]
48
- hash[:limit] = limit.join(', ')
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].join(', ')}" unless hash[:limit].blank?
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
@@ -30,9 +30,16 @@ module Ambition
30
30
  end
31
31
 
32
32
  def process_not(exp)
33
- _, receiver, method, other = *exp.first
33
+ type, receiver, method, other = *exp.first
34
34
  exp.clear
35
- return translation(receiver, negate(method), other)
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.inspect.gsub(/\/([^\/]+)\/\S*/, '\1'), process(exp.shift)
67
- "#{target} REGEXP '#{regexp}'"
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)} is #{other_value}"
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
- require 'rubygems'
2
- require 'active_record' unless defined? ActiveRecord
3
- require 'proc_to_ruby'
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,3 @@
1
+ require File.dirname(__FILE__) + '/lib/load_fixtures'
2
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
3
+ require 'ambition'
@@ -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,3 @@
1
+ class Admin < User
2
+ has_many :companies, :finder_sql => 'SELECT * FROM companies'
3
+ end
@@ -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,13 @@
1
+ david_action_controller:
2
+ developer_id: 1
3
+ project_id: 2
4
+ joined_on: 2004-10-10
5
+
6
+ david_active_record:
7
+ developer_id: 1
8
+ project_id: 1
9
+ joined_on: 2004-10-10
10
+
11
+ jamis_active_record:
12
+ developer_id: 2
13
+ project_id: 1
@@ -0,0 +1,4 @@
1
+ class Project < ActiveRecord::Base
2
+ has_and_belongs_to_many :developers, :uniq => true
3
+ has_many :topics
4
+ end
@@ -0,0 +1,7 @@
1
+ action_controller:
2
+ id: 2
3
+ name: Active Controller
4
+
5
+ active_record:
6
+ id: 1
7
+ name: Active Record
@@ -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,5 @@
1
+ class Reply < ActiveRecord::Base
2
+ belongs_to :topic, :include => [:replies]
3
+
4
+ validates_presence_of :content
5
+ end
@@ -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,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -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
@@ -10,7 +10,7 @@ context "Each" do
10
10
  end
11
11
 
12
12
  specify "limit and conditions" do
13
- hash = { :limit => '5', :conditions => "users.age = 21" }
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 => '5', :conditions => "users.age = 21", :order => 'users.name' }
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 => '5', :order => 'users.name' }
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, mocha, and activesupport gems to run these tests."
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 => '1' }
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 => '5' }
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 => '10, 20' }
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[10, 20]
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 => '10, 20' }
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 => '10, 10' }
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 is NULL"
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
- conditions = { :conditions => "users.name = 'chris'", :limit => '1' }
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
- conditions = { :conditions => "users.name = 'chris'", :limit => '1' }
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.5
7
- date: 2007-08-30 00:00:00 -07:00
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