ambition 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. data/Manifest +41 -48
  2. data/README +6 -203
  3. data/ambition.gemspec +111 -0
  4. data/app_generators/ambition_adapter/USAGE +1 -0
  5. data/app_generators/ambition_adapter/ambition_adapter_generator.rb +66 -0
  6. data/app_generators/ambition_adapter/templates/LICENSE +18 -0
  7. data/app_generators/ambition_adapter/templates/README +6 -0
  8. data/app_generators/ambition_adapter/templates/Rakefile +31 -0
  9. data/app_generators/ambition_adapter/templates/lib/base.rb.erb +12 -0
  10. data/app_generators/ambition_adapter/templates/lib/init.rb.erb +22 -0
  11. data/app_generators/ambition_adapter/templates/lib/query.rb.erb +52 -0
  12. data/app_generators/ambition_adapter/templates/lib/select.rb.erb +100 -0
  13. data/app_generators/ambition_adapter/templates/lib/slice.rb.erb +19 -0
  14. data/app_generators/ambition_adapter/templates/lib/sort.rb.erb +43 -0
  15. data/app_generators/ambition_adapter/templates/test/helper.rb.erb +9 -0
  16. data/app_generators/ambition_adapter/templates/test/select_test.rb.erb +157 -0
  17. data/app_generators/ambition_adapter/templates/test/slice_test.rb.erb +36 -0
  18. data/app_generators/ambition_adapter/templates/test/sort_test.rb.erb +53 -0
  19. data/bin/ambition_adapter +13 -0
  20. data/lib/ambition.rb +8 -13
  21. data/lib/ambition/api.rb +42 -35
  22. data/lib/ambition/context.rb +62 -0
  23. data/lib/ambition/{proc_to_ruby.rb → core_ext.rb} +13 -0
  24. data/lib/ambition/enumerable.rb +6 -0
  25. data/lib/ambition/processors/base.rb +126 -0
  26. data/lib/ambition/processors/ruby.rb +24 -0
  27. data/lib/ambition/processors/select.rb +105 -0
  28. data/lib/ambition/processors/slice.rb +15 -0
  29. data/lib/ambition/processors/sort.rb +51 -0
  30. data/test/adapters/exemplar/association_test.rb +34 -0
  31. data/test/adapters/exemplar/count_test.rb +0 -0
  32. data/test/adapters/exemplar/detect_test.rb +9 -0
  33. data/test/adapters/exemplar/enumerable_test.rb +0 -0
  34. data/test/adapters/exemplar/helper.rb +3 -0
  35. data/test/adapters/exemplar/index_operator.rb +6 -0
  36. data/test/adapters/exemplar/reject_test.rb +0 -0
  37. data/test/adapters/exemplar/select_test.rb +151 -0
  38. data/test/adapters/exemplar/slice_test.rb +0 -0
  39. data/test/adapters/exemplar/sort_test.rb +0 -0
  40. data/test/debug +9 -0
  41. data/test/helper.rb +2 -52
  42. metadata +56 -71
  43. data/Rakefile +0 -64
  44. data/init.rb +0 -1
  45. data/lib/ambition/database_statements.rb +0 -31
  46. data/lib/ambition/processor.rb +0 -123
  47. data/lib/ambition/query.rb +0 -91
  48. data/lib/ambition/ruby_processor.rb +0 -22
  49. data/lib/ambition/select_processor.rb +0 -149
  50. data/lib/ambition/simple_processor.rb +0 -10
  51. data/lib/ambition/sort_processor.rb +0 -47
  52. data/lib/ambition/source.rb +0 -53
  53. data/test/benchmark.rb +0 -68
  54. data/test/chaining_test.rb +0 -34
  55. data/test/console +0 -9
  56. data/test/count_test.rb +0 -17
  57. data/test/databases/boot.rb +0 -3
  58. data/test/databases/database.yml +0 -17
  59. data/test/databases/fixtures/admin.rb +0 -3
  60. data/test/databases/fixtures/companies.yml +0 -24
  61. data/test/databases/fixtures/company.rb +0 -23
  62. data/test/databases/fixtures/developer.rb +0 -11
  63. data/test/databases/fixtures/developers_projects.yml +0 -13
  64. data/test/databases/fixtures/project.rb +0 -4
  65. data/test/databases/fixtures/projects.yml +0 -7
  66. data/test/databases/fixtures/replies.yml +0 -20
  67. data/test/databases/fixtures/reply.rb +0 -5
  68. data/test/databases/fixtures/topic.rb +0 -19
  69. data/test/databases/fixtures/topics.yml +0 -32
  70. data/test/databases/fixtures/user.rb +0 -2
  71. data/test/databases/fixtures/users.yml +0 -35
  72. data/test/databases/lib/activerecord_test_connector.rb +0 -65
  73. data/test/databases/lib/load_fixtures.rb +0 -13
  74. data/test/databases/lib/schema.rb +0 -41
  75. data/test/enumerable_test.rb +0 -95
  76. data/test/join_test.rb +0 -61
  77. data/test/limit_test.rb +0 -41
  78. data/test/order_test.rb +0 -52
  79. data/test/profiler.rb +0 -34
  80. data/test/ruby_test.rb +0 -9
  81. data/test/source_test.rb +0 -43
  82. data/test/types_test.rb +0 -59
  83. data/test/where_test.rb +0 -245
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ context "<%= adapter_module %> Adapter :: Slice" do
4
+ setup do
5
+ @klass = User
6
+ @block = @klass.select { |m| m.name == 'jon' }
7
+ end
8
+
9
+ xspecify "first" do
10
+ @klass.expects(:find).with(:limit => 1, :name => 'jon')
11
+ @block.first
12
+ end
13
+
14
+ xspecify "first with argument" do
15
+ @klass.expects(:find).with(:limit => 5, :name => 'jon')
16
+ @block.first(5).entries
17
+ end
18
+
19
+ xspecify "[] with two elements" do
20
+ @klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
21
+ @block[10, 20].entries
22
+
23
+ @klass.expects(:find).with(:limit => 20, :offset => 20, :name => 'jon')
24
+ @block[20, 20].entries
25
+ end
26
+
27
+ xspecify "slice is an alias of []" do
28
+ @klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
29
+ @block.slice(10, 20).entries
30
+ end
31
+
32
+ xspecify "[] with range" do
33
+ @klass.expects(:find).with(:limit => 10, :offset => 10, :name => 'jon')
34
+ @block[11..20].entries
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ context "<%= adapter_module %> Adapter :: Sort" do
4
+ setup do
5
+ @klass = User
6
+ @block = @klass.select { |m| m.name == 'jon' }
7
+ end
8
+
9
+ xspecify "order" do
10
+ string = @block.sort_by { |m| m.name }.to_s
11
+ string.should == "foo"
12
+ end
13
+
14
+ xspecify "combined order" do
15
+ string = @block.sort_by { |m| [ m.name, m.age ] }.to_s
16
+ string.should == "foo"
17
+ end
18
+
19
+ xspecify "combined order with single reverse" do
20
+ string = @block.sort_by { |m| [ m.name, -m.age ] }.to_s
21
+ string.should == "foo"
22
+ end
23
+
24
+ xspecify "combined order with two reverses" do
25
+ string = @block.sort_by { |m| [ -m.name, -m.age ] }.to_s
26
+ string.should == "foo"
27
+ end
28
+
29
+ xspecify "reverse order with -" do
30
+ string = @block.sort_by { |m| -m.age }.to_s
31
+ string.should == "foo"
32
+ end
33
+
34
+ xspecify "reverse order with #reverse" do
35
+ # TODO: not implemented
36
+ string = @block.sort_by { |m| m.age }.reverse.to_s
37
+ string.should == "foo"
38
+ end
39
+
40
+ xspecify "random order" do
41
+ string = @block.sort_by { rand }.to_s
42
+ string.should == "foo"
43
+ end
44
+
45
+ xspecify "non-existent method to sort by" do
46
+ should.raise(NoMethodError) { @block.sort_by { foo }.to_s }
47
+ end
48
+
49
+ xspecify "Symbol#to_proc" do
50
+ string = @klass.sort_by(&:name).to_s
51
+ string.should == "foo"
52
+ end
53
+ end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rubigen'
4
+
5
+ if %w(-v --version).include? ARGV.first
6
+ version = File.read('./Rakefile').scan(/Version = '(.+)'/).first.first
7
+ puts "#{File.basename($0)} #{version}"
8
+ exit(0)
9
+ end
10
+
11
+ require 'rubigen/scripts/generate'
12
+ RubiGen::Base.use_application_sources! :ambition_adapter
13
+ RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'ambition_adapter')
data/lib/ambition.rb CHANGED
@@ -1,15 +1,10 @@
1
- unless defined? ActiveRecord
2
- require 'rubygems'
3
- require 'active_record'
4
- end
1
+ require 'ambition/enumerable'
5
2
  require 'ambition/api'
6
- require 'ambition/source'
7
- require 'ambition/processor'
8
- require 'ambition/ruby_processor'
9
- require 'ambition/select_processor'
10
- require 'ambition/sort_processor'
11
- require 'ambition/simple_processor'
12
- require 'ambition/query'
13
- require 'ambition/database_statements'
3
+ require 'ambition/context'
4
+ require 'ambition/core_ext'
14
5
 
15
- ActiveRecord::Base.extend Ambition::API
6
+ require 'ambition/processors/base'
7
+ require 'ambition/processors/select'
8
+ require 'ambition/processors/sort'
9
+ require 'ambition/processors/slice'
10
+ require 'ambition/processors/ruby'
data/lib/ambition/api.rb CHANGED
@@ -2,57 +2,43 @@ module Ambition
2
2
  module API
3
3
  include Enumerable
4
4
 
5
- attr_accessor :query_context
6
- def query_context
7
- @query_context || Query.new(self)
5
+ ##
6
+ # Entry methods
7
+ def select(&block)
8
+ context = ambition_context
9
+ context << Processors::Select.new(context, block)
8
10
  end
9
11
 
10
- def entries
11
- find(:all, query_context.to_hash)
12
+ def sort_by(&block)
13
+ context = ambition_context
14
+ context << Processors::Sort.new(context, block)
12
15
  end
13
- alias_method :to_a, :entries
14
16
 
15
- def select(*args, &block)
16
- query_context.add SelectProcessor.new(self, block)
17
+ def entries
18
+ ambition_context.kick
17
19
  end
20
+ alias_method :to_a, :entries
18
21
 
19
- def first(limit = 1)
20
- query_context.add SimpleProcessor.new(:limit => limit)
21
-
22
- if limit == 1
23
- find(:first, query_context.to_hash)
24
- else
25
- query_context
26
- end
22
+ def size
23
+ ambition_context.size
27
24
  end
28
25
 
29
- def slice(offset, limit = nil)
30
- if offset.is_a? Range
31
- limit = offset.end
32
- limit -= 1 if offset.exclude_end?
33
- offset = offset.first
34
- limit -= offset
35
- elsif limit.nil?
36
- return find(offset)
37
- end
38
-
39
- query_context.add SimpleProcessor.new(:offset => offset)
40
- query_context.add SimpleProcessor.new(:limit => limit)
26
+ def slice(start, length = nil)
27
+ context = ambition_context
28
+ context << Processors::Slice.new(context, start, length)
41
29
  end
42
30
  alias_method :[], :slice
43
31
 
44
- def sort_by(&block)
45
- query_context.add SortProcessor.new(self, block)
46
- end
47
-
32
+ ##
33
+ # Convenience methods
48
34
  def detect(&block)
49
35
  select(&block).first
50
36
  end
51
37
 
52
- def size
53
- count(query_context.to_hash)
38
+ def first(count = 1)
39
+ sliced = slice(0, count)
40
+ count == 1 ? sliced.kick : sliced
54
41
  end
55
- alias_method :length, :size
56
42
 
57
43
  def each(&block)
58
44
  entries.each(&block)
@@ -69,5 +55,26 @@ module Ambition
69
55
  def empty?
70
56
  size.zero?
71
57
  end
58
+
59
+ ##
60
+ # Plumbing
61
+ def ambition_context
62
+ Context.new(self)
63
+ end
64
+
65
+ def ambition_adapter
66
+ name = respond_to?(:name) ? name : self.class.name
67
+ parent = respond_to?(:superclass) ? superclass : self.class.superclass
68
+ @@ambition_adapter[name] || @@ambition_adapter[parent.name]
69
+ end
70
+
71
+ def ambition_adapter=(klass)
72
+ @@ambition_adapter ||= {}
73
+ @@ambition_adapter[name] = klass
74
+ end
75
+
76
+ def ambition_owner
77
+ @owner || self
78
+ end
72
79
  end
73
80
  end
@@ -0,0 +1,62 @@
1
+ module Ambition
2
+ class Context
3
+ include API
4
+
5
+ ##
6
+ # These are the methods your Query and Translator classes will
7
+ # want to access.
8
+ #
9
+ # +owner+ The class everything was called on. Like `User'
10
+ #
11
+ # +clauses+ A hash of arrays, one key per processor.
12
+ # So, if someone called User.select, your
13
+ # +clauses+ hash would have a :select key with
14
+ # an array of translated strings via your Select
15
+ # class.
16
+ #
17
+ # +stash+ A place for you to stick stuff. Available to
18
+ # all Translators and your Query class.
19
+ attr_reader :clauses, :owner, :stash
20
+
21
+ def initialize(owner)
22
+ @owner = owner
23
+ @clauses = {}
24
+ @stash = {}
25
+ end
26
+
27
+ def ambition_context
28
+ self
29
+ end
30
+
31
+ def <<(clause)
32
+ @clauses[clause.key] ||= []
33
+ @clauses[clause.key] << clause.to_s
34
+ self
35
+ end
36
+
37
+ def adapter_query
38
+ Processors::Base.translator(self, :Query)
39
+ end
40
+
41
+ def to_hash
42
+ adapter_query.to_hash
43
+ end
44
+
45
+ def to_s
46
+ adapter_query.to_s
47
+ end
48
+
49
+ def kick
50
+ adapter_query.kick
51
+ end
52
+
53
+ def size
54
+ adapter_query.size
55
+ end
56
+ alias_method :length, :size
57
+
58
+ def inspect
59
+ "(Query object: call #to_s or #to_hash to inspect, call an Enumerable (such as #each or #first) to request data)"
60
+ end
61
+ end
62
+ end
@@ -3,6 +3,19 @@
3
3
  require 'parse_tree'
4
4
  require 'ruby2ruby'
5
5
 
6
+ class Object
7
+ def to_sexp
8
+ instance_eval <<-end_eval
9
+ return proc { #{inspect} }.to_sexp.last
10
+ end_eval
11
+ end
12
+
13
+ def metaclass; (class << self; self end) end
14
+ def meta_eval(&blk) metaclass.instance_eval(&blk) end
15
+ def meta_def(name, &blk) meta_eval { define_method name, &blk } end
16
+ def class_def(name, &blk) class_eval { define_method name, &blk } end
17
+ end
18
+
6
19
  class ProcHolder
7
20
  end
8
21
 
@@ -0,0 +1,6 @@
1
+ module Ambition
2
+ Enumerable = ::Enumerable.dup
3
+ Enumerable.class_eval do
4
+ remove_method :find, :find_all
5
+ end
6
+ end
@@ -0,0 +1,126 @@
1
+ module Ambition
2
+ module Processors
3
+ class Base
4
+ ##
5
+ # Processing methods
6
+ def process_proc(exp)
7
+ # puts "=> #{exp.inspect}"
8
+ receiver, body = process(exp.shift), exp.shift
9
+ process(body)
10
+ end
11
+
12
+ def process_dasgn_curr(exp)
13
+ @receiver = exp.first
14
+ @receiver.to_s
15
+ end
16
+ alias_method :process_dasgn, :process_dasgn_curr
17
+
18
+ def process_array(exp)
19
+ # Branch on whether this is straight Ruby or a real array
20
+ if ruby = rubify(exp)
21
+ ruby
22
+ else
23
+ exp.map { |m| process(m) }
24
+ end
25
+ end
26
+
27
+ def process_str(exp)
28
+ exp.first
29
+ end
30
+
31
+ def process_lit(exp)
32
+ exp.first
33
+ end
34
+
35
+ def process_nil(exp)
36
+ nil
37
+ end
38
+
39
+ def process_true(exp)
40
+ true
41
+ end
42
+
43
+ def process_false(exp)
44
+ false
45
+ end
46
+
47
+ def process_dvar(exp)
48
+ target = exp.shift
49
+ value(target.to_s[0..-1])
50
+ end
51
+
52
+ def process_ivar(exp)
53
+ value(exp.shift.to_s[0..-1])
54
+ end
55
+
56
+ def process_lvar(exp)
57
+ value(exp.shift.to_s)
58
+ end
59
+
60
+ def process_vcall(exp)
61
+ value(exp.shift.to_s)
62
+ end
63
+
64
+ def process_gvar(exp)
65
+ value(exp.shift.to_s)
66
+ end
67
+
68
+ def process(node)
69
+ node ||= []
70
+
71
+ if node.is_a? Symbol
72
+ node
73
+ elsif respond_to?(method = "process_#{node.first}")
74
+ send(method, node[1..-1])
75
+ elsif node.blank?
76
+ ''
77
+ else
78
+ raise "Missing process method for sexp: #{node.inspect}"
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Helper methods
84
+ def to_s
85
+ process @block.to_sexp
86
+ end
87
+
88
+ def key
89
+ self.class.name.split('::').last.downcase.intern
90
+ end
91
+
92
+ def value(variable)
93
+ eval variable, @block
94
+ end
95
+
96
+ def translator
97
+ @translator ||= self.class.translator(@context)
98
+ end
99
+
100
+ def self.translator(context, name = nil)
101
+ name ||= self.name.split('::').last
102
+ klass = context.owner.ambition_adapter.const_get(name)
103
+ instance = klass.new
104
+
105
+ unless instance.respond_to? :context
106
+ klass.class_eval do
107
+ attr_accessor :context
108
+ def owner; @context.owner end
109
+ def clauses; @context.clauses end
110
+ def stash; @context.stash end
111
+ end
112
+ end
113
+
114
+ instance.context = context
115
+ instance
116
+ end
117
+
118
+ def rubify(exp)
119
+ # TODO: encapsulate this check in Ruby.should_process?(exp)
120
+ if exp.first.first == :call && exp.first[1].last != @receiver && Array(exp.first[1][1]).last != @receiver
121
+ value Ruby.process(exp.first)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end