Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ambition site

  • Loading branch information...
commit add607f9695115cddab87f8639f1401fa704d5af 1 parent 0016ea5
@defunkt authored
Showing with 919 additions and 1,737 deletions.
  1. +0 −2  .gitignore
  2. +0 −18 LICENSE
  3. +0 −42 Manifest
  4. +0 −24 README
  5. +0 −84 Rakefile
  6. +29 −1 site/src/layout.textile → _adapters.html
  7. +276 −0 adapters.html
  8. +303 −0 adapters/activerecord.html
  9. +0 −120 ambition.gemspec
  10. +187 −0 api.html
  11. +0 −1  app_generators/ambition_adapter/USAGE
  12. +0 −66 app_generators/ambition_adapter/ambition_adapter_generator.rb
  13. +0 −18 app_generators/ambition_adapter/templates/LICENSE
  14. +0 −6 app_generators/ambition_adapter/templates/README
  15. +0 −31 app_generators/ambition_adapter/templates/Rakefile
  16. +0 −12 app_generators/ambition_adapter/templates/lib/adapter/base.rb.erb
  17. +0 −52 app_generators/ambition_adapter/templates/lib/adapter/query.rb.erb
  18. +0 −100 app_generators/ambition_adapter/templates/lib/adapter/select.rb.erb
  19. +0 −19 app_generators/ambition_adapter/templates/lib/adapter/slice.rb.erb
  20. +0 −43 app_generators/ambition_adapter/templates/lib/adapter/sort.rb.erb
  21. +0 −22 app_generators/ambition_adapter/templates/lib/init.rb.erb
  22. +0 −9 app_generators/ambition_adapter/templates/test/helper.rb.erb
  23. +0 −157 app_generators/ambition_adapter/templates/test/select_test.rb.erb
  24. +0 −36 app_generators/ambition_adapter/templates/test/slice_test.rb.erb
  25. +0 −53 app_generators/ambition_adapter/templates/test/sort_test.rb.erb
  26. +0 −13 bin/ambition_adapter
  27. +124 −0 index.html
  28. +0 −11 lib/ambition.rb
  29. +0 −98 lib/ambition/api.rb
  30. +0 −62 lib/ambition/context.rb
  31. +0 −7 lib/ambition/core_ext.rb
  32. +0 −6 lib/ambition/enumerable.rb
  33. +0 −134 lib/ambition/processors/base.rb
  34. +0 −26 lib/ambition/processors/ruby.rb
  35. +0 −105 lib/ambition/processors/select.rb
  36. +0 −15 lib/ambition/processors/slice.rb
  37. +0 −51 lib/ambition/processors/sort.rb
  38. +0 −16 lib/ambition/sexp_translator.rb
  39. +0 −61 site/Rakefile
  40. 0  {site/src → }/static/bg.png
  41. 0  {site/src → }/static/code_highlighter.js
  42. 0  {site/src → }/static/css.js
  43. 0  {site/src → }/static/html.js
  44. 0  {site/src → }/static/hubris.css
  45. 0  {site/src → }/static/javascript.js
  46. 0  {site/src → }/static/ruby.js
  47. +0 −34 test/adapters/exemplar/association_test.rb
  48. 0  test/adapters/exemplar/count_test.rb
  49. +0 −9 test/adapters/exemplar/detect_test.rb
  50. 0  test/adapters/exemplar/enumerable_test.rb
  51. +0 −3  test/adapters/exemplar/helper.rb
  52. +0 −6 test/adapters/exemplar/index_operator.rb
  53. 0  test/adapters/exemplar/reject_test.rb
  54. +0 −151 test/adapters/exemplar/select_test.rb
  55. 0  test/adapters/exemplar/slice_test.rb
  56. 0  test/adapters/exemplar/sort_test.rb
  57. +0 −9 test/debug
  58. +0 −4 test/helper.rb
  59. 0  site/src/_adapters.textile → textile/_adapters.txt
  60. 0  site/src/adapters/activerecord.textile → textile/activerecord.txt
  61. 0  site/src/adapters.textile → textile/adapters.txt
  62. 0  site/src/api.textile → textile/api.txt
  63. 0  site/src/index.textile → textile/index.txt
View
2  .gitignore
@@ -1,2 +0,0 @@
-pkg
-live
View
18 LICENSE
@@ -1,18 +0,0 @@
-Copyright (c) 2007 Chris Wanstrath
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
42 Manifest
@@ -1,42 +0,0 @@
-app_generators/ambition_adapter/ambition_adapter_generator.rb
-app_generators/ambition_adapter/templates/lib/adapter/base.rb.erb
-app_generators/ambition_adapter/templates/lib/adapter/query.rb.erb
-app_generators/ambition_adapter/templates/lib/adapter/select.rb.erb
-app_generators/ambition_adapter/templates/lib/adapter/slice.rb.erb
-app_generators/ambition_adapter/templates/lib/adapter/sort.rb.erb
-app_generators/ambition_adapter/templates/lib/init.rb.erb
-app_generators/ambition_adapter/templates/LICENSE
-app_generators/ambition_adapter/templates/Rakefile
-app_generators/ambition_adapter/templates/README
-app_generators/ambition_adapter/templates/test/helper.rb.erb
-app_generators/ambition_adapter/templates/test/select_test.rb.erb
-app_generators/ambition_adapter/templates/test/slice_test.rb.erb
-app_generators/ambition_adapter/templates/test/sort_test.rb.erb
-app_generators/ambition_adapter/USAGE
-bin/ambition_adapter
-lib/ambition/api.rb
-lib/ambition/context.rb
-lib/ambition/core_ext.rb
-lib/ambition/enumerable.rb
-lib/ambition/processors/base.rb
-lib/ambition/processors/ruby.rb
-lib/ambition/processors/select.rb
-lib/ambition/processors/slice.rb
-lib/ambition/processors/sort.rb
-lib/ambition/sexp_translator.rb
-lib/ambition.rb
-LICENSE
-Manifest
-README
-test/adapters/exemplar/association_test.rb
-test/adapters/exemplar/count_test.rb
-test/adapters/exemplar/detect_test.rb
-test/adapters/exemplar/enumerable_test.rb
-test/adapters/exemplar/helper.rb
-test/adapters/exemplar/index_operator.rb
-test/adapters/exemplar/reject_test.rb
-test/adapters/exemplar/select_test.rb
-test/adapters/exemplar/slice_test.rb
-test/adapters/exemplar/sort_test.rb
-test/debug
-test/helper.rb
View
24 README
@@ -1,24 +0,0 @@
-= Ambition
-
-
-== Get it
-
-
-
-$ git clone git://github.com/defunkt/ambition.git
-
-== Resources
-
- * http://ambition.rubyforge.org/
- * http://groups.google.com/group/ambition-rb/
- * http://errtheblog.com/posts/63-full-of-ambition
- * http://errtheblog.com/posts/82-adapting-ambitiously
- * http://errtheblog.com/posts/86-sugary-adapters
- * http://errtheblog.com/posts/64-even-more-ambitious
-
-
-
-== Author
-
-Chris Wanstrath
-chris@ozmm.org
View
84 Rakefile
@@ -1,84 +0,0 @@
-require 'rake'
-require 'rake/testtask'
-require 'rake/rdoctask'
-
-Version = '0.5.4'
-
-module Rake::TaskManager
- def delete_task(task_class, *args, &block)
- task_name, deps = resolve_args(args)
- @tasks.delete(task_class.scope_name(@scope, task_name).to_s)
- end
-end
-class Rake::Task
- def self.delete_task(args, &block) Rake.application.delete_task(self, args, &block) end
-end
-def delete_task(args, &block) Rake::Task.delete_task(args, &block) end
-
-begin
- require 'rubygems'
- gem 'echoe', '>=2.7'
- ENV['RUBY_FLAGS'] = ""
- require 'echoe'
-
- Echoe.new('ambition', Version) do |p|
- p.project = 'err'
- p.summary = "Ambition builds yer API calls from plain jane Ruby."
- p.description = "Ambition builds yer API calls from plain jane Ruby."
- p.url = "http://errtheblog.com/"
- p.author = 'Chris Wanstrath'
- p.email = "chris@ozmm.org"
- p.ruby_version = '>= 1.8.6'
- p.ignore_pattern = /^(\.git|site|adapters).+/
- p.test_pattern = 'test/*_test.rb'
- p.dependencies << 'ParseTree =2.1.1'
- p.dependencies << 'ruby2ruby =1.1.8'
- p.dependencies << 'rubigen =1.1.1'
- end
-
-rescue LoadError
- puts "Not doing any of the Echoe gemmy stuff, because you don't have the specified gem versions"
-end
-
-delete_task :test
-delete_task :install_gem
-
-Rake::TestTask.new('test') do |t|
- t.pattern = 'test/*_test.rb'
-end
-
-Rake::TestTask.new('test:adapters') do |t|
- t.pattern = 'adapters/*/test/*_test.rb'
-end
-
-Dir['adapters/*'].each do |adapter|
- adapter = adapter.split('/').last
- Rake::TestTask.new("test:adapters:#{adapter.sub('ambitious_','')}") do |t|
- t.pattern = "adapters/#{adapter}/test/*_test.rb"
- end
-end
-
-desc 'Default: run unit tests.'
-task :default => :test
-
-desc 'Generate RDoc documentation'
-Rake::RDocTask.new(:rdoc) do |rdoc|
- files = ['README', 'LICENSE', 'lib/**/*.rb']
- rdoc.rdoc_files.add(files)
- rdoc.main = "README"
- rdoc.title = "ambition"
- # rdoc.template = File.exists?(t="/Users/chris/ruby/projects/err/rock/template.rb") ? t : "/var/www/rock/template.rb"
- rdoc.rdoc_dir = 'doc'
- rdoc.options << '--inline-source'
-end
-
-desc 'Generate coverage reports'
-task :rcov do
- `rcov -e gems test/*_test.rb`
- puts 'Generated coverage reports.'
-end
-
-desc 'Install as a gem'
-task :install_gem do
- puts `rake manifest package && gem install pkg/ambition-#{Version}.gem`
-end
View
30 site/src/layout.textile → _adapters.html
@@ -22,7 +22,35 @@
</div>
<div id="body">
- %CONTENT%
+ <p>Adapters are gems named <code>ambitious-something</code>, where <em>something</em> corresponds to the data
+store they are adapting. They can be required in code via <code>ambition/adapters/something</code>.</p>
+
+
+ <p>To install and test the ActiveRecord adapter:</p>
+
+
+<pre>
+$ gem install ambitious-activerecord
+$ irb
+&gt;&gt; require 'rubygems'
+&gt;&gt; require 'ambition/adapters/active_record'
+</pre>
+
+ <p>Adapters typically inject themselves into their target automatically, so that should be
+all you need.</p>
+
+
+ <p>There are a few adapters in development or released currently:</p>
+
+
+ <ul>
+ <li><a href="adapters/activerecord.html">ActiveRecord</a></li>
+ <li>ActiveLDAP</li>
+ <li>Facebook</li>
+ <li>XPath</li>
+ <li>CouchDB</li>
+ <li>DataMapper</li>
+ </ul>
</div>
<div id="footer">
View
276 adapters.html
@@ -0,0 +1,276 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>Ruby's Ambition</title>
+ <link href="/static/hubris.css" media="screen" rel="Stylesheet" type="text/css" />
+ <script src="/static/code_highlighter.js" type="text/javascript"></script>
+ <script src="/static/ruby.js" type="text/javascript"></script>
+</head>
+
+<body>
+ <div id="main">
+ <div id="header">
+ <h1><span class="a">A</span>mbition</h1>
+ <div id="nav">
+ <a href="/"><span class="a">o</span>verview</a>
+ &#183; <a href="/adapters.html"><span class="a">a</span>dapters</a>
+ &#183; <a href="/api.html"><span class="a">a</span>pi</a>
+ </div>
+ </div>
+
+ <div id="body">
+ <h2>The Adapters</h2>
+
+
+ <p>Adapters are gems named <code>ambitious-something</code>, where <em>something</em> corresponds to the data
+store they are adapting. They can be required in code via <code>ambition/adapters/something</code>.</p>
+
+
+ <p>To install and test the ActiveRecord adapter:</p>
+
+
+<pre>
+$ gem install ambitious-activerecord
+$ irb
+&gt;&gt; require 'rubygems'
+&gt;&gt; require 'ambition/adapters/active_record'
+</pre>
+
+ <p>Adapters typically inject themselves into their target automatically, so that should be
+all you need.</p>
+
+
+ <p>There are a few adapters in development or released currently:</p>
+
+
+ <ul>
+ <li><a href="adapters/activerecord.html">ActiveRecord</a></li>
+ <li>ActiveLDAP</li>
+ <li>Facebook</li>
+ <li>XPath</li>
+ <li>CouchDB</li>
+ <li>DataMapper</li>
+ </ul>
+
+
+ <p>If you&#8217;re interested in writing your own adapter, read on.</p>
+
+
+ <h2>The Anatomy of an Adapter</h2>
+
+
+ <p>Ambition adapters consist of two parts: <strong>Translators</strong> and the <strong>Query</strong>. Translators
+are used to translate plane jane Ruby into strings while the Query is used to build
+and execute a query from those strings.</p>
+
+
+ <p>The three translators are <code>Select</code>, <code>Slice</code>, and <code>Sort</code>. Their names correspond to the
+<span class="caps">API</span> method they represent. Each translator consists of methods which convert passed
+arguments into a string.</p>
+
+
+ <p>Here&#8217;s how the ActiveRecord adapter maps translator classes to <span class="caps">SQL</span> clauses:</p>
+
+
+ <ul>
+ <li><code>Select</code> =&gt; <code>WHERE</code></li>
+ <li><code>Slice</code> =&gt; <code>LIMIT</code> and <code>OFFSET</code></li>
+ <li><code>Sort</code> =&gt; <code>ORDER BY</code></li>
+ </ul>
+
+
+ <p>Your translators and the Query have three special methods available at all times:</p>
+
+
+ <ul>
+ <li><code>owner</code></li>
+ <li><code>clauses</code></li>
+ <li><code>stash</code></li>
+ </ul>
+
+
+ <p><code>owner</code> is the class from which the request was generated.</p>
+
+
+<pre class="ruby">
+User.select { |u| u.name == 'Pork' }
+# =&gt; owner == User
+</pre>
+
+ <p><code>clauses</code> is the hash of translated string arrays, keyed by processors.</p>
+
+
+<pre class="ruby">
+User.select { |u| u.name == 'Pork' }
+# =&gt; clauses == { :select =&gt; [ "users.name = 'Pork'" ] }
+</pre>
+
+ <p><code>stash</code> is your personal private stash. A hash you can use for
+keeping stuff around. Translators are free to set things which
+can later be picked up by the Query class.</p>
+
+
+ <p>For instance, the <code>ActiveRecord</code> adapter&#8217;s <code>Select</code> translator adds to the
+<code>stash[:include]</code> array whenever it thinks it needs to do a join. The
+Query class picks this up and adds it to the hash it feeds
+<code>find(:all)</code>.</p>
+
+
+<pre class="ruby">
+User.select { |u| u.profile.name == 'Pork' }
+# =&gt; stash == { :include =&gt; [ :profile ] }
+</pre>
+
+ <p><code>stash</code> is basically a way for your translators to talk to each other and,
+more importantly, to the Query.</p>
+
+
+ <p>The Query is what kicks off the actual data store query, after all the translators have done
+their business. Its <code>clauses</code> and <code>stash</code> hashes are as full as they will ever be.</p>
+
+
+ <p>The kicking is done by one of two methods:</p>
+
+
+ <ul>
+ <li><code>kick</code> </li>
+ <li><code>count</code></li>
+ </ul>
+
+
+ <p>While two other methods are generally expected to turn the <code>clauses</code> hash into something
+semantically valid:</p>
+
+
+ <ul>
+ <li><code>to_s</code></li>
+ <li><code>to_hash</code></li>
+ </ul>
+
+
+ <p>So, for instance, <code>Query#kick</code> may look like this:</p>
+
+
+<pre class="ruby">
+def kick
+ owner.find(:all, to_hash)
+end
+</pre>
+
+ <p>A straightforward translator/query <span class="caps">API</span> reference can be found on the <a href="api.html">api</a> page.</p>
+
+
+ <p>The easiest way to understand translators is to check out the source of the existing adapters
+or by using the adapter generator.</p>
+
+
+ <h2>The Adapter Generator</h2>
+
+
+ <p>Ambition ships with an <code>ambition_adapter</code> script which can generate an adapter skeleton. Built
+using Dr Nic&#8217;s <a href="http://rubigen.rubyforge.org/">Rubigen</a>, it spits out all the files, tests, and
+Rakefiles your adapter needs.</p>
+
+
+ <p>Run it:</p>
+
+
+<pre>
+$ ambition_adapter flickr
+ create
+ create lib/ambition/adapters/flickr
+ create test
+ create lib/ambition/adapters/flickr/base.rb
+ create lib/ambition/adapters/flickr/query.rb
+ create lib/ambition/adapters/flickr/select.rb
+ create lib/ambition/adapters/flickr/slice.rb
+ create lib/ambition/adapters/flickr/sort.rb
+ create lib/ambition/adapters/flickr.rb
+ create test/helper.rb
+ create test/select_test.rb
+ create test/slice_test.rb
+ create test/sort_test.rb
+ create README
+ create Rakefile
+</pre>
+
+ <p>Presto, you&#8217;ve got a ready and willing adapter skeleton in place now. Check out the comments
+and you&#8217;re on your way.</p>
+
+
+ <h2>The Flow: Ambition + Adapters</h2>
+
+
+ <p>Let us examine the flow of a typical call, using the <code>ActiveRecord</code> adapter for reference.</p>
+
+
+ <p>The call:</p>
+
+
+<pre class="ruby">
+User.select { |u| u.name == 'Chris' &#38;&#38; u.age == 22 }.to_s
+</pre>
+
+ <p>The first few steps:</p>
+
+
+ <ul>
+ <li><code>Ambition::API#select</code> is called.</li>
+ <li>An <code>Ambition::Context</code> is created.</li>
+ <li>An <code>Ambition::Processors::Select</code> is created and added to the context.</li>
+ <li>The context calls <code>to_s</code> on the new <code>Select</code> processor</li>
+ <li>The block passed to <code>select</code> is processed.</li>
+ </ul>
+
+
+ <p>This processing is the real meat. Ambition will instantiate your <code>Select</code> translator and
+pass values to it, saving the return value of these method calls.
+returning <code>"(users.name = 'Chris' AND users.age = 22)"</code></p>
+
+
+ <ul>
+ <li><code>Ambition::Adapters::ActiveRecord::Select</code> is instantiated.</li>
+ <li>The translator&#8217;s <code>call</code> method is passed <code>:name</code>, returning <code>"users.name"</code></li>
+ <li>The translator&#8217;s <code>==</code> method is passed <code>"users.name"</code> and <code>"Chris"</code>, returning <code>"users.name = 'Chris'"</code></li>
+ <li><code>call</code> is passed <code>:age</code>, returning <code>"users.age"</code></li>
+ <li><code>==</code> is passed <code>"users.age"</code> and <code>22</code>, returning <code>"users.age = 22"</code></li>
+ <li>The translator&#8217;s <code>both</code> method is passed <code>"users.name = 'Chris'"</code> and <code>"users.age = 22"</code>,</li>
+ </ul>
+
+
+ <p>At this point we leave adapter-land. The final string is stored in the <code>clauses</code> hash
+(available to your <code>Query</code>) by the context. The <code>clauses</code> hash is keyed by the translator
+name&#8212;in this case, <code>:select</code>.</p>
+
+
+ <ul>
+ <li>The context is returned by <code>Ambition::API#select</code>.</li>
+ <li><code>to_s</code> is called on the context</li>
+ <li>The context forwards this <code>to_s</code> call to an instance of the adapter&#8217;s <code>Query</code> class</li>
+ <li>The ActiveRecord adapter&#8217;s <code>to_s</code> calls <code>to_hash</code></li>
+ <li><code>to_hash</code> uses the <code>clauses</code> hash to build an AR hash</li>
+ <li><code>to_s</code> then uses the hash&#8217;s members to build a <span class="caps">SQL</span> string</li>
+ </ul>
+
+
+ <p>The final string is then returned:</p>
+
+
+<pre class="ruby">
+"SELECT * FROM users WHERE (users.name = 'Chris' AND users.age = 22)"
+</pre>
+
+ <p>And that&#8217;s all there is to it. Except, of course, for the <a href="api.html">api</a> page.</p>
+ </div>
+
+ <div id="footer">
+ <a href="" target="_top">back to top</a>
+ | <a href="http://rubyforge.org/projects/ambition/">rubyforge</a>
+ | <a href="http://groups.google.com/group/ambition-rb/">the list</a>
+ </div>
+ </div>
+</body>
+</html>
View
303 adapters/activerecord.html
@@ -0,0 +1,303 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>Ruby's Ambition</title>
+ <link href="/static/hubris.css" media="screen" rel="Stylesheet" type="text/css" />
+ <script src="/static/code_highlighter.js" type="text/javascript"></script>
+ <script src="/static/ruby.js" type="text/javascript"></script>
+</head>
+
+<body>
+ <div id="main">
+ <div id="header">
+ <h1><span class="a">A</span>mbition</h1>
+ <div id="nav">
+ <a href="/"><span class="a">o</span>verview</a>
+ &#183; <a href="/adapters.html"><span class="a">a</span>dapters</a>
+ &#183; <a href="/api.html"><span class="a">a</span>pi</a>
+ </div>
+ </div>
+
+ <div id="body">
+ <h2>An Ambitious ActiveRecord Adapter</h2>
+
+
+ <p>I could tell you all about how awesome the internals are, or
+how fun it was to write, or how it&#8217;ll make you rich and famous,
+but instead I&#8217;m just going to show you some examples.</p>
+
+
+ <h2>Get It</h2>
+
+
+ <p><code>$ sudo gem install ambitious-activerecord</code></p>
+
+
+ <p>This will suck in the adapter and its dependencies (ActiveRecord &#38; Ambition).
+It&#8217;s fully usable outside of Rails (I use it in a Camping app or two), as long
+as you&#8217;re riding ActiveRecord.</p>
+
+
+ <p>Now require it in your app:</p>
+
+
+<pre>
+require 'rubygems'
+require 'ambition/adapters/active_record'
+</pre>
+
+ <h2>Examples</h2>
+
+
+ <p>Basically, you write your <span class="caps">SQL</span> in Ruby. No, not in Ruby. As Ruby.</p>
+
+
+<pre class="ruby">
+User.select { |u| u.city == 'San Francisco' }.each do |user|
+ puts user.name
+end
+</pre>
+
+ <p>And that&#8217;s it.</p>
+
+
+ <p>The key is that queries aren&#8217;t actually run until the data they represent is
+requested. Usually this is done with what I call a kicker method. You can call them
+that, too.</p>
+
+
+ <p>Kicker methods are guys like <code>detect</code>, <code>each</code>, <code>each_with_index</code>, <code>map</code>, <code>entries</code>,
+<code>to_a</code>, and <code>first</code> (with no argument). Methods like <code>select</code>, <code>sort_by</code>, and <code>first</code>
+(with an argument) are not kicker methods and return a <code>Context</code> object without running any <span class="caps">SQL</span>.</p>
+
+
+ <p>Our <code>Context</code> object has two useful methods: <code>to_s</code> and <code>to_hash</code>. With these,
+we can check out what exactly we&#8217;re building. Not everyone has <code>to_s</code>,
+though. Mostly ignore these methods and treat everything like you normally
+would.</p>
+
+
+ <p>See, <code>to_s</code>:</p>
+
+
+<pre class="ruby">
+&gt;&gt; User.select { |m| m.name == 'jon' }.to_s
+=&gt; "SELECT * FROM users WHERE users.name = 'jon'"
+</pre>
+
+ <p>See, <code>to_hash</code>:</p>
+
+
+<pre class="ruby">
+&gt;&gt; User.select { |m| m.name == 'jon' }.to_hash
+=&gt; { :conditions =&gt; "users.name = 'jon'" }
+</pre>
+
+ <h2>Equality &#8211; select { |u| u.field 'bob' }</h2>
+
+
+<pre class="ruby">
+User.select { |m| m.name == 'jon' }
+"SELECT * FROM users WHERE users.name = 'jon'"
+
+User.select { |m| m.created_at &gt; 2.days.ago }
+"SELECT * FROM users WHERE users.created_at &gt; '2007-09-26 20:37:47'"
+
+User.select { |m| m.name == 'jon' }
+"SELECT * FROM users WHERE users.name = 'jon'"
+
+User.select { |m| m.name != 'jon' }
+"SELECT * FROM users WHERE users.name &lt;&gt; 'jon'"
+
+User.select { |m| m.name == 'jon' &#38;&#38; m.age == 21 }
+"SELECT * FROM users WHERE (users.name = 'jon' AND users.age = 21)"
+
+User.select { |m| m.name == 'jon' || m.age == 21 }
+"SELECT * FROM users WHERE (users.name = 'jon' OR users.age = 21)"
+
+User.select { |m| m.name == 'jon' || m.age == 21 &#38;&#38; m.password == 'pass' }
+"SELECT * FROM users WHERE
+ (users.name = 'jon' OR (users.age = 21 AND users.password = 'pass'))"
+
+User.select { |m| (m.name == 'jon' || m.name == 'rick') &#38;&#38; m.age == 21 }
+"SELECT * FROM users WHERE
+ ((users.name = 'jon' OR users.name = 'rick') AND users.age = 21)"
+</pre>
+
+ <h2>Associations &#8211; select { |u| u.field &#8216;bob&#8217; &#38;&#38; u.association.field == &#8216;bob@bob.com&#8217; }</h2>
+
+
+ <p>The <code>to_s</code> method doesn&#8217;t work on associations yet, but that&#8217;s okay: they can
+still query through ActiveRecord just fine.</p>
+
+
+<pre class="ruby">
+User.select do |u|
+ u.email == 'chris@ozmm.org' &#38;&#38; u.profile.name == 'chris wanstrath'
+end.map(&#38;:title)
+
+"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'))"
+</pre>
+
+ <h2>Comparisons &#8211; select { |u| u.age &gt; 21 }</h2>
+
+
+<pre class="ruby">
+User.select { |m| m.age &gt; 21 }
+"SELECT * FROM users WHERE users.age &gt; 21"
+
+User.select { |m| m.age &lt; 21 }.to_s
+"SELECT * FROM users WHERE users.age &lt; 21"
+
+User.select { |m| [1, 2, 3, 4].include? m.id }
+"SELECT * FROM users WHERE users.id IN (1, 2, 3, 4)"
+</pre>
+
+ <h2><span class="caps">LIKE</span> and <span class="caps">REGEXP</span> (RLIKE) &#8211; select { |m| m.name =~ &#8216;chris&#8217; }</h2>
+
+
+<pre class="ruby">
+User.select { |m| m.name =~ 'chris' }
+"SELECT * FROM users WHERE users.name LIKE 'chris'"
+
+User.select { |m| m.name =~ 'chri%' }
+"SELECT * FROM users WHERE users.name LIKE 'chri%'"
+
+User.select { |m| m.name !~ 'chris' }
+"SELECT * FROM users WHERE users.name NOT LIKE 'chris'"
+
+User.select { |m| !(m.name =~ 'chris') }
+"SELECT * FROM users WHERE users.name NOT LIKE 'chris'"
+
+User.select { |m| m.name =~ /chris/ }
+"SELECT * FROM users WHERE users.name REGEXP 'chris'"
+</pre>
+
+ <h2>#detect</h2>
+
+
+<pre class="ruby">
+User.detect { |m| m.name == 'chris' }
+"SELECT * FROM users WHERE users.name = 'chris' LIMIT 1"
+</pre>
+
+ <h2>LIMITs &#8211; first, first(x), [offset, limit], [range], slice</h2>
+
+
+<pre class="ruby">
+User.select { |m| m.name == 'jon' }.first
+"SELECT * FROM users WHERE users.name = 'jon' LIMIT 1"
+
+User.select { |m| m.name == 'jon' }.first(5)
+"SELECT * FROM users WHERE users.name = 'jon' LIMIT 5"
+
+User.select { |m| m.name == 'jon' }[10, 20]
+"SELECT * FROM users WHERE users.name = 'jon' LIMIT 10, 20"
+
+User.select { |m| m.name == 'jon' }[10..20]
+"SELECT * FROM users WHERE users.name = 'jon' LIMIT 10, 10"
+</pre>
+
+ <h2><span class="caps">ORDER</span> &#8211; sort_by { |u| u.field }</h2>
+
+
+<pre class="ruby">
+User.select { |m| m.name == 'jon' }.sort_by { |m| m.name }
+"SELECT * FROM users WHERE users.name = 'jon' ORDER BY users.name"
+
+User.select { |m| m.name == 'jon' }.sort_by { |m| [ m.name, m.age ] }
+"SELECT * FROM users WHERE users.name = 'jon' ORDER BY users.name, users.age"
+
+User.select { |m| m.name == 'jon' }.sort_by { |m| [ m.name, -m.age ] }
+"SELECT * FROM users WHERE users.name = 'jon'
+ ORDER BY users.name, users.age DESC"
+
+User.select { |m| m.name == 'jon' }.sort_by { |m| [ -m.name, -m.age ] }
+"SELECT * FROM users WHERE users.name = 'jon'
+ ORDER BY users.name DESC, users.age DESC"
+
+User.select { |m| m.name == 'jon' }.sort_by { |m| -m.age }
+"SELECT * FROM users WHERE users.name = 'jon' ORDER BY users.age DESC"
+
+User.select { |m| m.name == 'jon' }.sort_by { |m| -m.profiles.title }
+"SELECT users.id AS t0_r0, ... FROM users
+ LEFT OUTER JOIN profiles ON profiles.user_id = users.id
+ WHERE (users.name = 'jon') ORDER BY profiles.title DESC"
+
+User.select { |m| m.name == 'jon' }.sort_by { rand }
+"SELECT * FROM users WHERE users.name = 'jon' ORDER BY RAND()"
+</pre>
+
+ <h2><span class="caps">COUNT</span> &#8211; select { |u| u.name == &#8216;jon&#8217; }.size</h2>
+
+
+<pre class="ruby">
+User.select { |m| m.name == 'jon' }.size
+"SELECT count(*) AS count_all FROM users WHERE (users.name = 'jon')"
+
+&gt;&gt; User.select { |m| m.name == 'jon' }.size
+=&gt; 21
+</pre>
+
+ <h2>Other Enumerables</h2>
+
+
+ <p>These methods perform <acronym title="">COUNT</acronym> operations rather than loading your array into memory. They&#8217;re all
+kickers.</p>
+
+
+<pre class="ruby">
+User.any? { |m| m.name == 'jon' }
+User.all? { |m| m.name == 'jon' }
+User.select { |m| m.name == 'jon' }.empty?
+</pre>
+
+ <h2>More Sugar</h2>
+
+
+ <p>The <code>downcase</code> and <code>upcase</code> methods will map to <acronym title="">LOWER</acronym> and <acronym title="">UPPER</acronym>, respectively.</p>
+
+
+<pre class="ruby">
+&gt;&gt; User.select { |m| m.name.downcase =~ 'jon%' }.to_s
+=&gt; "SELECT * FROM users WHERE LOWER(users.name) LIKE 'jon%'"
+</pre>
+
+ <h2>Quoting</h2>
+
+
+ <p>Columns and values will be quoted using ActiveRecord&#8217;s quote_column_name and quote methods, if
+possible.</p>
+
+
+ <h2><span class="caps">SELECT</span> * <span class="caps">FROM</span> bugs</h2>
+
+
+ <p>Found a bug? Sweet. Add it at <a href="http://err.lighthouseapp.com/projects/466-plugins/tickets/new">the Lighthouse</a>.</p>
+
+
+ <p>More information on Ambition:</p>
+
+
+ <ul>
+ <li><a href="http://ambition.rubyforge.org">http://ambition.rubyforge.org</a></li>
+ <li><a href="http://groups.google.com/group/ambition-rb/">http://groups.google.com/group/ambition-rb/</a></li>
+ </ul>
+
+
+ <p>- Chris Wanstrath [ chris@ozmm.org ]</p>
+ </div>
+
+ <div id="footer">
+ <a href="" target="_top">back to top</a>
+ | <a href="http://rubyforge.org/projects/ambition/">rubyforge</a>
+ | <a href="http://groups.google.com/group/ambition-rb/">the list</a>
+ </div>
+ </div>
+</body>
+</html>
View
120 ambition.gemspec
@@ -1,120 +0,0 @@
-
-# Gem::Specification for Ambition-0.5.4
-# Originally generated by Echoe
-
-Gem::Specification.new do |s|
- s.name = %q{ambition}
- s.version = "0.5.4"
-
- s.specification_version = 2 if s.respond_to? :specification_version=
-
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
- s.authors = ["Chris Wanstrath"]
- s.date = %q{2008-04-26}
- s.default_executable = %q{ambition_adapter}
- s.description = %q{Ambition builds yer API calls from plain jane Ruby.}
- s.email = %q{chris@ozmm.org}
- s.executables = ["ambition_adapter"]
- s.extra_rdoc_files = ["bin/ambition_adapter", "lib/ambition/api.rb", "lib/ambition/context.rb", "lib/ambition/core_ext.rb", "lib/ambition/enumerable.rb", "lib/ambition/processors/base.rb", "lib/ambition/processors/ruby.rb", "lib/ambition/processors/select.rb", "lib/ambition/processors/slice.rb", "lib/ambition/processors/sort.rb", "lib/ambition/sexp_translator.rb", "lib/ambition.rb", "LICENSE", "README"]
- s.files = ["app_generators/ambition_adapter/ambition_adapter_generator.rb", "app_generators/ambition_adapter/templates/lib/adapter/base.rb.erb", "app_generators/ambition_adapter/templates/lib/adapter/query.rb.erb", "app_generators/ambition_adapter/templates/lib/adapter/select.rb.erb", "app_generators/ambition_adapter/templates/lib/adapter/slice.rb.erb", "app_generators/ambition_adapter/templates/lib/adapter/sort.rb.erb", "app_generators/ambition_adapter/templates/lib/init.rb.erb", "app_generators/ambition_adapter/templates/LICENSE", "app_generators/ambition_adapter/templates/Rakefile", "app_generators/ambition_adapter/templates/README", "app_generators/ambition_adapter/templates/test/helper.rb.erb", "app_generators/ambition_adapter/templates/test/select_test.rb.erb", "app_generators/ambition_adapter/templates/test/slice_test.rb.erb", "app_generators/ambition_adapter/templates/test/sort_test.rb.erb", "app_generators/ambition_adapter/USAGE", "bin/ambition_adapter", "lib/ambition/api.rb", "lib/ambition/context.rb", "lib/ambition/core_ext.rb", "lib/ambition/enumerable.rb", "lib/ambition/processors/base.rb", "lib/ambition/processors/ruby.rb", "lib/ambition/processors/select.rb", "lib/ambition/processors/slice.rb", "lib/ambition/processors/sort.rb", "lib/ambition/sexp_translator.rb", "lib/ambition.rb", "LICENSE", "Manifest", "README", "test/adapters/exemplar/association_test.rb", "test/adapters/exemplar/count_test.rb", "test/adapters/exemplar/detect_test.rb", "test/adapters/exemplar/enumerable_test.rb", "test/adapters/exemplar/helper.rb", "test/adapters/exemplar/index_operator.rb", "test/adapters/exemplar/reject_test.rb", "test/adapters/exemplar/select_test.rb", "test/adapters/exemplar/slice_test.rb", "test/adapters/exemplar/sort_test.rb", "test/debug", "test/helper.rb", "ambition.gemspec"]
- s.has_rdoc = true
- s.homepage = %q{http://errtheblog.com/}
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ambition", "--main", "README"]
- s.require_paths = ["lib"]
- s.required_ruby_version = Gem::Requirement.new(">= 1.8.6")
- s.rubyforge_project = %q{err}
- s.rubygems_version = %q{1.0.1}
- s.summary = %q{Ambition builds yer API calls from plain jane Ruby.}
-
- s.add_dependency(%q<ParseTree>, ["= 2.1.1"])
- s.add_dependency(%q<ruby2ruby>, ["= 1.1.8"])
- s.add_dependency(%q<rubigen>, ["= 1.1.1"])
-end
-
-
-# # Original Rakefile source (requires the Echoe gem):
-#
-# require 'rake'
-# require 'rake/testtask'
-# require 'rake/rdoctask'
-#
-# Version = '0.5.4'
-#
-# module Rake::TaskManager
-# def delete_task(task_class, *args, &block)
-# task_name, deps = resolve_args(args)
-# @tasks.delete(task_class.scope_name(@scope, task_name).to_s)
-# end
-# end
-# class Rake::Task
-# def self.delete_task(args, &block) Rake.application.delete_task(self, args, &block) end
-# end
-# def delete_task(args, &block) Rake::Task.delete_task(args, &block) end
-#
-# begin
-# require 'rubygems'
-# gem 'echoe', '>=2.7'
-# ENV['RUBY_FLAGS'] = ""
-# require 'echoe'
-#
-# Echoe.new('ambition', Version) do |p|
-# p.project = 'err'
-# p.summary = "Ambition builds yer API calls from plain jane Ruby."
-# p.description = "Ambition builds yer API calls from plain jane Ruby."
-# p.url = "http://errtheblog.com/"
-# p.author = 'Chris Wanstrath'
-# p.email = "chris@ozmm.org"
-# p.ruby_version = '>= 1.8.6'
-# p.ignore_pattern = /^(\.git|site|adapters).+/
-# p.test_pattern = 'test/*_test.rb'
-# p.dependencies << 'ParseTree =2.1.1'
-# p.dependencies << 'ruby2ruby =1.1.8'
-# p.dependencies << 'rubigen =1.1.1'
-# end
-#
-# rescue LoadError
-# puts "Not doing any of the Echoe gemmy stuff, because you don't have the specified gem versions"
-# end
-#
-# delete_task :test
-# delete_task :install_gem
-#
-# Rake::TestTask.new('test') do |t|
-# t.pattern = 'test/*_test.rb'
-# end
-#
-# Rake::TestTask.new('test:adapters') do |t|
-# t.pattern = 'adapters/*/test/*_test.rb'
-# end
-#
-# Dir['adapters/*'].each do |adapter|
-# adapter = adapter.split('/').last
-# Rake::TestTask.new("test:adapters:#{adapter.sub('ambitious_','')}") do |t|
-# t.pattern = "adapters/#{adapter}/test/*_test.rb"
-# end
-# end
-#
-# desc 'Default: run unit tests.'
-# task :default => :test
-#
-# desc 'Generate RDoc documentation'
-# Rake::RDocTask.new(:rdoc) do |rdoc|
-# files = ['README', 'LICENSE', 'lib/**/*.rb']
-# rdoc.rdoc_files.add(files)
-# rdoc.main = "README"
-# rdoc.title = "ambition"
-# # rdoc.template = File.exists?(t="/Users/chris/ruby/projects/err/rock/template.rb") ? t : "/var/www/rock/template.rb"
-# rdoc.rdoc_dir = 'doc'
-# rdoc.options << '--inline-source'
-# end
-#
-# desc 'Generate coverage reports'
-# task :rcov do
-# `rcov -e gems test/*_test.rb`
-# puts 'Generated coverage reports.'
-# end
-#
-# desc 'Install as a gem'
-# task :install_gem do
-# puts `rake manifest package && gem install pkg/ambition-#{Version}.gem`
-# end
View
187 api.html
@@ -0,0 +1,187 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>Ruby's Ambition</title>
+ <link href="/static/hubris.css" media="screen" rel="Stylesheet" type="text/css" />
+ <script src="/static/code_highlighter.js" type="text/javascript"></script>
+ <script src="/static/ruby.js" type="text/javascript"></script>
+</head>
+
+<body>
+ <div id="main">
+ <div id="header">
+ <h1><span class="a">A</span>mbition</h1>
+ <div id="nav">
+ <a href="/"><span class="a">o</span>verview</a>
+ &#183; <a href="/adapters.html"><span class="a">a</span>dapters</a>
+ &#183; <a href="/api.html"><span class="a">a</span>pi</a>
+ </div>
+ </div>
+
+ <div id="body">
+ <h2>Ambition::API</h2>
+
+
+ <p>Your data store target (e.g. <code>ActiveRecord</code>) is extended or injected with this
+module.</p>
+
+
+ <h3>Processors</h3>
+
+
+ <p>These methods do not fire off a query and can be chained. They return a <code>Context</code>
+object which can be inspected or kicked.</p>
+
+
+ <ul>
+ <li><code>select</code></li>
+ <li><code>sort_by</code></li>
+ <li><code>slice</code></li>
+ <li><code>first</code>(length)</li>
+ </ul>
+
+
+ <h3>Kickers</h3>
+
+
+ <p>Kickers cause a query to execute. All method calls on a context are accumulated
+until kicked, at which point they&#8217;re turned into a query and run.</p>
+
+
+ <ul>
+ <li><code>entries</code></li>
+ <li><code>to_a</code> </li>
+ <li><code>detect</code></li>
+ <li><code>first</code> (no arguments)</li>
+ <li><code>size</code></li>
+ </ul>
+
+
+ <h3>Custom Enumerables</h3>
+
+
+ <p>These <code>Enumerable</code> methods are written special for Ambition. Other <code>Enumerable</code>
+methods, like <code>each_with_index</code>, should work out of the box as they mostly wrap
+<code>each</code>.</p>
+
+
+ <ul>
+ <li><code>each</code></li>
+ <li><code>any?</code></li>
+ <li><code>all?</code></li>
+ <li><code>empty?</code></li>
+ </ul>
+
+
+ <h2>Translators and Query</h2>
+
+
+ <p>Methods available to translator and Query instance methods:</p>
+
+
+ <ul>
+ <li><code>owner</code> &#8211; constant, adapter target</li>
+ <li><code>clauses</code> &#8211; hash, keyed by translator name</li>
+ <li><code>stash</code> &#8211; hash, arbitrary</li>
+ </ul>
+
+
+ <h2>Ambition::Adapters::YourAdapter::Select</h2>
+
+
+ <p>All translators are instantiated and have access to <code>owner</code> and <code>stash</code>. They should not
+touch <code>clauses</code> directly.</p>
+
+
+ <ul>
+ <li><code>call</code> &#8211; passed a symbol. e.g. <code>call(:name)</code></li>
+ <li><code>chained_call</code> &#8211; passed an array of symbols. e.g. <code>call(:name, :downcase)</code></li>
+ <li><code>include?</code> &#8211; passed the array it&#8217;s called on and the argument.</li>
+ </ul>
+
+
+ <p>The following methods are passed the left and right side of the expression they represent.</p>
+
+
+ <ul>
+ <li><code>both</code> &#8211; &#38;&#38;</li>
+ <li><code>either</code> &#8211; ||</li>
+ <li><code>not_equal</code> &#8211; !=</li>
+ <li><code>not_regexp</code> &#8211; !~</li>
+ <li><code>==</code></li>
+ <li><code>=~</code></li>
+ <li><code>&lt;</code></li>
+ <li><code>&lt;=</code> </li>
+ <li><code>&gt;</code></li>
+ <li><code>&gt;=</code></li>
+ </ul>
+
+
+ <h2>Ambition::Adapters::YourAdapter::Slice</h2>
+
+
+ <p>All translators are instantiated and have access to <code>owner</code> and <code>stash</code>. They should not
+touch <code>clauses</code> directly.</p>
+
+
+ <p>The <code>Slice</code> translator has only one method: <code>slice</code>(start, length)</p>
+
+
+ <p>Some examples:</p>
+
+
+ <ul>
+ <li><code>first(5)</code> becomes <code>slice(0, 5)</code></li>
+ <li><code>first</code> becomes <code>slice(0, 1)</code></li>
+ <li><code>User[10, 20]</code> becomes <code>slice(10, 20)</code></li>
+ <li><code>User[10..20]</code> becomes <code>slice(10, 10)</code></li>
+ </ul>
+
+
+ <h2>Ambition::Adapters::YourAdapter::Sort</h2>
+
+
+ <p>All translators are instantiated and have access to <code>owner</code> and <code>stash</code>. They should not
+touch <code>clauses</code> directly.</p>
+
+
+ <ul>
+ <li><code>sort_by</code> &#8211; passed a symbol</li>
+ <li><code>reverse_sort_by</code> &#8211; passed a symbol</li>
+ <li><code>chained_sort_by</code> &#8211; passed an array of symbols</li>
+ <li><code>chained_reverse_sort_by</code> &#8211; passed an array of symbols</li>
+ <li><code>to_proc</code> &#8211; passed a symbol</li>
+ <li><code>rand</code></li>
+ </ul>
+
+
+ <h2>Ambition::Adapters::YourAdapter::Query</h2>
+
+
+ <p>The Query is instantiated and has access to <code>owner</code>, <code>clauses</code>, and <code>stash</code>. It should
+use the information these methods provide to build its domain specific query.</p>
+
+
+ <p>When any of the following methods are called on a <code>Context</code> they are forwarded to the Query
+instance.</p>
+
+
+ <ul>
+ <li>kick &#8211; the <span class="caps">API</span>&#8217;s kickers call this</li>
+ <li>size</li>
+ <li>to_hash</li>
+ <li>to_s</li>
+ </ul>
+ </div>
+
+ <div id="footer">
+ <a href="" target="_top">back to top</a>
+ | <a href="http://rubyforge.org/projects/ambition/">rubyforge</a>
+ | <a href="http://groups.google.com/group/ambition-rb/">the list</a>
+ </div>
+ </div>
+</body>
+</html>
View
1  app_generators/ambition_adapter/USAGE
@@ -1 +0,0 @@
-o.
View
66 app_generators/ambition_adapter/ambition_adapter_generator.rb
@@ -1,66 +0,0 @@
-class AmbitionAdapterGenerator < RubiGen::Base
-# DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
-# default_options :author => nil
-
- attr_reader :adapter_name, :adapter_module
-
- def initialize(runtime_args, runtime_options = {})
- super
- usage if args.empty?
- @destination_root = File.expand_path(args.shift)
- base_name = self.base_name.sub(/ambitious(-|_)/,'')
- @adapter_name = base_name
- @adapter_module = base_name.split('_').map { |part| part[0] = part[0...1].upcase; part }.join
- extract_options
- end
-
- def manifest
- record do |m|
- # Ensure appropriate folder(s) exists
- m.directory ''
- dirs = %W(
- lib/ambition/adapters/#{adapter_name}
- test
- )
- dirs.each { |path| m.directory path }
-
- ##
- # Translator / Query stubs
- adapter_path = "lib/ambition/adapters/#{adapter_name}"
-
- %w( base query select slice sort ).each do |file|
- m.template "lib/adapter/#{file}.rb.erb", "#{adapter_path}/#{file}.rb"
- end
-
- m.template 'lib/init.rb.erb', "#{adapter_path}.rb"
-
- ##
- # Test stubs
- Dir[File.dirname(__FILE__) + '/templates/test/*.rb.erb'].each do |file|
- file = File.basename(file, '.*')
- m.template "test/#{file}.erb", "test/#{file}"
- end
-
- ##
- # Normal files
- files = %w( LICENSE README Rakefile )
- files.each do |file|
- m.template file, file
- end
- end
- end
-
-protected
- def banner
- "Usage: ambition_adapter adapter_name"
- end
-
- def add_options!(opts)
- opts.separator ''
- opts.separator 'Options:'
- opts.on("-v", "--version", "Show the #{File.basename($0)} version number and quit.")
- opts.separator ''
- end
-
- def extract_options; end
-end
View
18 app_generators/ambition_adapter/templates/LICENSE
@@ -1,18 +0,0 @@
-Copyright (c) 2007 Your Name
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
6 app_generators/ambition_adapter/templates/README
@@ -1,6 +0,0 @@
-<%= string = "An Ambitious #{adapter_module} Adapter" %>
-<%= '=' * string.size %>
-
-More information on Ambition:
--> http://ambition.rubyforge.org
--> http://groups.google.com/group/ambition-rb/
View
31 app_generators/ambition_adapter/templates/Rakefile
@@ -1,31 +0,0 @@
-require 'rake'
-
-Version = '0.1.0'
-
-begin
- require 'rubygems'
- gem 'echoe', '>=2.7'
- ENV['RUBY_FLAGS'] = ""
- require 'echoe'
-
- Echoe.new('ambitious-<%= adapter_name %>') do |p|
- p.dependencies << '<%= adapter_name %> >=1.0'
- p.summary = "An ambitious adapter for <%= adapter_module %>"
- p.author = 'Your Name'
- p.email = "your@email.com"
-
- p.project = 'ambition'
- p.url = "http://ambition.rubyforge.org/"
- p.test_pattern = 'test/*_test.rb'
- p.version = Version
- p.dependencies << 'ambition >=0.5.0'
- end
-
-rescue LoadError
- puts "Not doing any of the Echoe gemmy stuff, because you don't have the specified gem versions"
-end
-
-desc 'Install as a gem'
-task :install_gem do
- puts `rake manifest package && gem install pkg/ambitious-<%= adapter_name %>-#{Version}.gem`
-end
View
12 app_generators/ambition_adapter/templates/lib/adapter/base.rb.erb
@@ -1,12 +0,0 @@
-module Ambition
- module Adapters
- module <%= adapter_module %>
- class Base
- ##
- # Extract common functionality into this class.
- # All your classes, by default, inherit from this
- # one -- Query and the Translators.
- end
- end
- end
-end
View
52 app_generators/ambition_adapter/templates/lib/adapter/query.rb.erb
@@ -1,52 +0,0 @@
-=begin
-
-These methods are king:
-
- - owner
- - clauses
- - stash
-
-+owner+ is the class from which the request was generated.
-
-User.select { |u| u.name == 'Pork' }
-# => owner == User
-
-+clauses+ is the hash of translated arrays, keyed by processors
-
-User.select { |u| u.name == 'Pork' }
-# => clauses == { :select => [ "users.name = 'Pork'" ] }
-
-+stash+ is your personal private stash. A hash you can use for
-keeping stuff around.
-
-User.select { |u| u.profile.name == 'Pork' }
-# => stash == { :include => [ :profile ] }
-
-The above is totally arbitrary. It's basically a way for your
-translators to talk to each other and, more importantly, to the Query
-object.
-
-=end
-module Ambition
- module Adapters
- module <%= adapter_module %>
- class Query < Base
- def kick
- raise "Example: owner.find(:all, to_hash)"
- end
-
- def size
- raise "Example: owner.count(to_hash)"
- end
-
- def to_hash
- raise "Not implemented"
- end
-
- def to_s
- raise "Not implemented"
- end
- end
- end
- end
-end
View
100 app_generators/ambition_adapter/templates/lib/adapter/select.rb.erb
@@ -1,100 +0,0 @@
-##
-# The format of the documentation herein is:
-#
-# >> method with block
-# => methods on this class called by Ambition (with arguments)
-#
-module Ambition
- module Adapters
- module <%= adapter_module %>
- class Select < Base
- # >> select { |u| u.name == 'chris' }
- # => #call(:name)
- def call(method)
- raise "Not implemented."
- end
-
- # >> select { |u| u.name.downcase == 'chris' }
- # => #call(:name, :downcase)
- def chained_call(*methods)
- # An idiom here is to call the chained method and pass it
- # the first method.
- #
- # if respond_to? methods[1]
- # send(methods[1], methods[0])
- # end
- #
- # In the above example, this translates to calling:
- #
- # #downcase(:name)
- #
- raise "Not implemented."
- end
-
- # &&
- # >> select { |u| u.name == 'chris' && u.age == 22 }
- # => #both( processed left side, processed right side )
- def both(left, right)
- raise "Not implemented."
- end
-
- # ||
- # >> select { |u| u.name == 'chris' || u.age == 22 }
- # => #either( processed left side, processed right side )
- def either(left, right)
- raise "Not implemented."
- end
-
- # >> select { |u| u.name == 'chris' }
- # => #==( call(:name), 'chris' )
- def ==(left, right)
- raise "Not implemented."
- end
-
- # !=
- # >> select { |u| u.name != 'chris' }
- # => #not_equal( call(:name), 'chris' )
- def not_equal(left, right)
- raise "Not implemented."
- end
-
- # >> select { |u| u.name =~ 'chris' }
- # => #=~( call(:name), 'chris' )
- def =~(left, right)
- raise "Not implemented."
- end
-
- # !~
- # >> select { |u| u.name !~ 'chris' }
- # => #not_regexp( call(:name), 'chris' )
- def not_regexp(left, right)
- raise "Not implemented."
- end
-
- ##
- # Etc.
- def <(left, right)
- raise "Not implemented."
- end
-
- def >(left, right)
- raise "Not implemented."
- end
-
- def >=(left, right)
- raise "Not implemented."
- end
-
- def <=(left, right)
- raise "Not implemented."
- end
-
- # >> select { |u| [1, 2, 3].include? u.id }
- # => #include?( [1, 2, 3], call(:id) )
- def include?(left, right)
- raise "Not implemented."
- end
- end
- end
- end
-end
View
19 app_generators/ambition_adapter/templates/lib/adapter/slice.rb.erb
@@ -1,19 +0,0 @@
-module Ambition
- module Adapters
- module <%= adapter_module %>
- class Slice < Base
- # >> User.first(5)
- # => #slice(0, 5)
- #
- # >> User.first
- # => #slice(0, 1)
- #
- # >> User[10, 20]
- # => #slice(10, 20)
- def slice(start, length)
- raise "Not implemented."
- end
- end
- end
- end
-end
View
43 app_generators/ambition_adapter/templates/lib/adapter/sort.rb.erb
@@ -1,43 +0,0 @@
-module Ambition
- module Adapters
- module <%= adapter_module %>
- class Sort < Base
- # >> sort_by { |u| u.age }
- # => #sort_by(:age)
- def sort_by(method)
- raise "Not implemented."
- end
-
- # >> sort_by { |u| -u.age }
- # => #reverse_sort_by(:age)
- def reverse_sort_by(method)
- raise "Not implemented."
- end
-
- # >> sort_by { |u| u.profile.name }
- # => #chained_sort_by(:profile, :name)
- def chained_sort_by(receiver, method)
- raise "Not implemented."
- end
-
- # >> sort_by { |u| -u.profile.name }
- # => #chained_reverse_sort_by(:profile, :name)
- def chained_reverse_sort_by(receiver, method)
- raise "Not implemented."
- end
-
- # >> sort_by(&:name)
- # => #to_proc(:name)
- def to_proc(symbol)
- raise "Not implemented."
- end
-
- # >> sort_by { rand }
- # => #rand
- def rand
- raise "Not implemented."
- end
- end
- end
- end
-end
View
22 app_generators/ambition_adapter/templates/lib/init.rb.erb
@@ -1,22 +0,0 @@
-require 'ambition'
-require '<%= adapter_name %>'
-require 'ambition/adapters/<%= adapter_name %>/base'
-require 'ambition/adapters/<%= adapter_name %>/query'
-require 'ambition/adapters/<%= adapter_name %>/select'
-require 'ambition/adapters/<%= adapter_name %>/sort'
-require 'ambition/adapters/<%= adapter_name %>/slice'
-
-##
-# This is where you inject Ambition into your target.
-#
-# Use `extend' if you are injecting a class, `include' if you are
-# injecting instances of that class.
-#
-# You must also set the `ambition_adapter' class variable on your target
-# class, regardless of whether you are injecting instances or the class itself.
-#
-# You probably want something like this:
-#
-# <%= adapter_module %>::Base.extend Ambition::API
-# <%= adapter_module %>::Base.ambition_adapter = Ambition::Adapters::<%= adapter_module %>
-#
View
9 app_generators/ambition_adapter/templates/test/helper.rb.erb
@@ -1,9 +0,0 @@
-%w( rubygems test/spec mocha English ).each { |f| require f }
-
-begin
- require 'redgreen'
-rescue LoadError
-end
-
-$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
-require 'ambition/adapters/<%= adapter_name %>'
View
157 app_generators/ambition_adapter/templates/test/select_test.rb.erb
@@ -1,157 +0,0 @@
-require File.dirname(__FILE__) + '/helper'
-
-context "<%= adapter_module %> Adapter :: Select" do
- setup do
- @klass = User
- end
-
- xspecify "==" do
- translator = @klass.select { |m| m.name == 'jon' }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "!=" do
- translator = @klass.select { |m| m.name != 'jon' }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "== && ==" do
- translator = @klass.select { |m| m.name == 'jon' && m.age == 21 }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "== || ==" do
- translator = @klass.select { |m| m.name == 'jon' || m.age == 21 }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "mixed && and ||" do
- translator = @klass.select { |m| m.name == 'jon' || m.age == 21 && m.password == 'pass' }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "grouped && and ||" do
- translator = @klass.select { |m| (m.name == 'jon' || m.name == 'rick') && m.age == 21 }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify ">/<" do
- translator = @klass.select { |m| m.age > 21 }
- translator.to_s.should == %Q(foo)
-
- translator = @klass.select { |m| m.age >= 21 }
- translator.to_s.should == %Q(foo)
-
- translator = @klass.select { |m| m.age < 21 }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "array.include? item" do
- translator = @klass.select { |m| [1, 2, 3, 4].include? m.id }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "variabled array.include? item" do
- array = [1, 2, 3, 4]
- translator = @klass.select { |m| array.include? m.id }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "== with variables" do
- me = 'chris'
- translator = @klass.select { |m| m.name == me }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "== with method arguments" do
- def test_it(name)
- translator = @klass.select { |m| m.name == name }
- translator.to_s.should == %Q(foo)
- end
-
- test_it('chris')
- end
-
- xspecify "== with instance variables" do
- @me = 'chris'
- translator = @klass.select { |m| m.name == @me }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "== with instance variable method call" do
- require 'ostruct'
- @person = OpenStruct.new(:name => 'chris')
-
- translator = @klass.select { |m| m.name == @person.name }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "== with global variables" do
- $my_name = 'boston'
- translator = @klass.select { |m| m.name == $my_name }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "== with method call" do
- def band
- 'skinny puppy'
- end
-
- translator = @klass.select { |m| m.name == band }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "=~ with string" do
- translator = @klass.select { |m| m.name =~ 'chris' }
- translator.to_s.should == %Q(foo)
-
- translator = @klass.select { |m| m.name =~ 'chri%' }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "!~ with string" do
- translator = @klass.select { |m| m.name !~ 'chris' }
- translator.to_s.should == %Q(foo)
-
- translator = @klass.select { |m| !(m.name =~ 'chris') }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "=~ with regexp" do
- translator = @klass.select { |m| m.name =~ /chris/ }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "=~ with regexp flags" do
- translator = @klass.select { |m| m.name =~ /chris/i }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "downcase" do
- translator = @klass.select { |m| m.name.downcase =~ 'chris%' }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "upcase" do
- translator = @klass.select { |m| m.name.upcase =~ 'chris%' }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "undefined equality symbol" do
- should.raise { @klass.select { |m| m.name =* /chris/ } }
- end
-
- xspecify "block variable / assigning variable conflict" do
- m = @klass.select { |m| m.name == 'chris' }
- m.should == %Q(foo)
- end
-
- xspecify "== with inline ruby" do
- translator = @klass.select { |m| m.created_at == Time.now.to_s }
- translator.to_s.should == %Q(foo)
- end
-
- xspecify "inspect" do
- @klass.select { |u| u.name }.inspect.should.match %r(call #to_s or #to_hash)
- end
-end
View
36 app_generators/ambition_adapter/templates/test/slice_test.rb.erb
@@ -1,36 +0,0 @@
-require File.dirname(__FILE__) + '/helper'
-
-context "<%= adapter_module %> Adapter :: Slice" do
- setup do
- @klass = User
- @block = @klass.select { |m| m.name == 'jon' }
- end
-
- xspecify "first" do
- @klass.expects(:find).with(:limit => 1, :name => 'jon')
- @block.first
- end
-
- xspecify "first with argument" do
- @klass.expects(:find).with(:limit => 5, :name => 'jon')
- @block.first(5).entries
- end
-
- xspecify "[] with two elements" do
- @klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
- @block[10, 20].entries
-
- @klass.expects(:find).with(:limit => 20, :offset => 20, :name => 'jon')
- @block[20, 20].entries
- end
-
- xspecify "slice is an alias of []" do
- @klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
- @block.slice(10, 20).entries
- end
-
- xspecify "[] with range" do
- @klass.expects(:find).with(:limit => 10, :offset => 10, :name => 'jon')
- @block[11..20].entries
- end
-end
View
53 app_generators/ambition_adapter/templates/test/sort_test.rb.erb
@@ -1,53 +0,0 @@
-require File.dirname(__FILE__) + '/helper'
-
-context "<%= adapter_module %> Adapter :: Sort" do
- setup do
- @klass = User
- @block = @klass.select { |m| m.name == 'jon' }
- end
-
- xspecify "order" do
- string = @block.sort_by { |m| m.name }.to_s
- string.should == "foo"
- end
-
- xspecify "combined order" do
- string = @block.sort_by { |m| [ m.name, m.age ] }.to_s
- string.should == "foo"
- end
-
- xspecify "combined order with single reverse" do
- string = @block.sort_by { |m| [ m.name, -m.age ] }.to_s
- string.should == "foo"
- end
-
- xspecify "combined order with two reverses" do
- string = @block.sort_by { |m| [ -m.name, -m.age ] }.to_s
- string.should == "foo"
- end
-
- xspecify "reverse order with -" do
- string = @block.sort_by { |m| -m.age }.to_s
- string.should == "foo"
- end
-
- xspecify "reverse order with #reverse" do
- # TODO: not implemented
- string = @block.sort_by { |m| m.age }.reverse.to_s
- string.should == "foo"
- end
-
- xspecify "random order" do
- string = @block.sort_by { rand }.to_s
- string.should == "foo"
- end
-
- xspecify "non-existent method to sort by" do
- should.raise(NoMethodError) { @block.sort_by { foo }.to_s }
- end
-
- xspecify "Symbol#to_proc" do
- string = @klass.sort_by(&:name).to_s
- string.should == "foo"
- end
-end
View
13 bin/ambition_adapter
@@ -1,13 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'rubigen'
-
-if %w(-v --version).include? ARGV.first
- version = File.read('./Rakefile').scan(/Version = '(.+)'/).first.first
- puts "#{File.basename($0)} #{version}"
- exit(0)
-end
-
-require 'rubigen/scripts/generate'
-RubiGen::Base.use_application_sources! :ambition_adapter
-RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'ambition_adapter')
View
124 index.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>Ruby's Ambition</title>
+ <link href="/static/hubris.css" media="screen" rel="Stylesheet" type="text/css" />
+ <script src="/static/code_highlighter.js" type="text/javascript"></script>
+ <script src="/static/ruby.js" type="text/javascript"></script>
+</head>
+
+<body>
+ <div id="main">
+ <div id="header">
+ <h1><span class="a">A</span>mbition</h1>
+ <div id="nav">
+ <a href="/"><span class="a">o</span>verview</a>
+ &#183; <a href="/adapters.html"><span class="a">a</span>dapters</a>
+ &#183; <a href="/api.html"><span class="a">a</span>pi</a>
+ </div>
+ </div>
+
+ <div id="body">
+ <h2>Plain Jane Ruby</h2>
+
+
+ <p>Imagine if instead of writing <span class="caps">SQL</span>, you could write Ruby. Instead of writing <span class="caps">LDAP</span>, you
+could write Ruby. Instead of learning some esoteric <span class="caps">API</span>, you just stick to one you know
+and love.</p>
+
+
+ <p>Imagine <strong>A</strong>mbition.</p>
+
+
+<pre class="ruby">
+&gt;&gt; LDAP::User.select { |m| m.name == 'jon' &#38;&#38; m.age == 21 }.to_s
+=&gt; "(&#38;(name=jon)(age=21))"
+
+&gt;&gt; SQL::User.select { |m| m.name == 'jon' &#38;&#38; m.age == 21 }.to_s
+=&gt; "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 21"
+</pre>
+
+ <p>It <a href="http://errtheblog.com/post/10722">started</a> with <span class="caps">SQL</span>, but it&#8217;s become so much more.</p>
+
+
+ <h2>The Elevator Pitch</h2>
+
+
+ <p>Ambition is a framework for writing adapters. Adapters are RubyGems which depend on the
+<code>ambition</code> gem and are named something along the lines of <code>ambitious-activerecord</code>. They typically
+use Ambition to turn plain jane Ruby into some sort of domain specific query which can be executed.</p>
+
+
+ <p>Anyone can write and release an adapter. This site describes how to write adapters using
+Ambition and also hosts a few.</p>
+
+
+ <h2>The Community</h2>
+
+
+ <p>We&#8217;ve got a hoppin&#8217; community over at the <a href="http://groups.google.com/group/ambition-rb">Google Group</a>. Feel
+free to jump in with any questions, ideas, or top 5 favorite bands discussions.</p>
+
+
+ <p>The initial blog posts were <a href="http://errtheblog.com/post/10722">this</a> and <a href="http://errtheblog.com/post/11998">that</a>.
+Most of what they describe has become the <code>ambitious-activerecord</code> gem.</p>
+
+
+ <h2>Ambitious Adapters</h2>
+
+
+ <p>Adapters are gems named <code>ambitious-something</code>, where <em>something</em> corresponds to the data
+store they are adapting. They can be required in code via <code>ambition/adapters/something</code>.</p>
+
+
+ <p>To install and test the ActiveRecord adapter:</p>
+
+
+<pre>
+$ gem install ambitious-activerecord
+$ irb
+&gt;&gt; require 'rubygems'
+&gt;&gt; require 'ambition/adapters/active_record'
+</pre>
+
+ <p>Adapters typically inject themselves into their target automatically, so that should be
+all you need.</p>
+
+
+ <p>There are a few adapters in development or released currently:</p>
+
+
+ <ul>
+ <li><a href="adapters/activerecord.html">ActiveRecord</a></li>
+ <li>ActiveLDAP</li>
+ <li>Facebook</li>
+ <li>XPath</li>
+ <li>CouchDB</li>
+ <li>DataMapper</li>
+ </ul>
+
+
+ <p>For information on authoring adapters, hit up the <a href="adapters.html">adapters</a> page.</p>
+
+
+ <h2>Development</h2>
+
+
+ <p>Development discussion happens in the <a href="http://groups.google.com/group/ambition-rb">Google Group</a>.
+Stay up to date by following Chris&#8217; git repository:</p>
+
+
+ <p><code>git clone git://github.com/defunkt/ambition</code></p>
+ </div>
+
+ <div id="footer">
+ <a href="" target="_top">back to top</a>
+ | <a href="http://rubyforge.org/projects/ambition/">rubyforge</a>
+ | <a href="http://groups.google.com/group/ambition-rb/">the list</a>
+ </div>
+ </div>
+</body>
+</html>
View
11 lib/ambition.rb
@@ -1,11 +0,0 @@
-require 'ambition/enumerable'
-require 'ambition/api'
-require 'ambition/context'
-require 'ambition/core_ext'
-require 'ambition/sexp_translator'
-
-require 'ambition/processors/base'
-require 'ambition/processors/select'
-require 'ambition/processors/sort'
-require 'ambition/processors/slice'
-require 'ambition/processors/ruby'
View
98 lib/ambition/api.rb
@@ -1,98 +0,0 @@
-module Ambition #:nodoc:
- # Module that you will extend from in your adapters in your toplevel file.
- #
- # For example, for ambitious_sphinx in lib/ambition/adapters/ambitious_sphinx.rb, we have:
- # ActiveRecord::Base.extend Ambition::API
- module API
- include Enumerable
-
- ##
- # Entry methods
- def select(&block)
- context = ambition_context
- context << Processors::Select.new(context, block)
- end
-
- def sort_by(&block)
- context = ambition_context
- context << Processors::Sort.new(context, block)
- end
-
- # Entries that our context is able to find.
- def entries
- ambition_context.kick
- end
- alias_method :to_a, :entries
-
- def size
- ambition_context == self ? super : ambition_context.size
- end
-
- def slice(start, length = nil)
- context = ambition_context
- context << Processors::Slice.new(context, start, length)
- end
- alias_method :[], :slice
-
- ##
- # Convenience methods
-
- # See Enumerable#detect
- def detect(&block)
- select(&block).first
- end
-
- # See Array#first
- def first(count = 1)
- sliced = slice(0, count)
- count == 1 ? Array(sliced.kick).first : sliced
- end
-
- # See Array#each, applied to +entries+
- def each(&block)
- entries.each(&block)
- end
-
- # See Enumerable#any?
- def any?(&block)
- select(&block).size > 0
- end
-
- # See Enumerable#all?
- def all?(&block)
- size == select(&block).size
- end
-
- # See Array#empty?
- def empty?
- size.zero?
- end
-
- # Builds a new +Context+.
- def ambition_context
- Context.new(self)
- end
-
- # Gives you the current ambitious adapter.
- def ambition_adapter
- name = respond_to?(:name) ? name : self.class.name
- parent = respond_to?(:superclass) ? superclass : self.class.superclass
- @@ambition_adapter[name] || @@ambition_adapter[parent.name]
- end
-
- # Assign the ambition adapter. Typically, you use this in the toplevel file of your adapter.
- #
- # For example, for ambitious_sphinx, in our lib/ambition/adapters/ambitious_sphinx.rb:
- #
- # ActiveRecord::Base.ambition_adapter = Ambition::Adapters::AmbitiousSphinx
- def ambition_adapter=(klass)
- @@ambition_adapter ||= {}
- # should this be doing the same check for respond_to?(:name) like above?
- @@ambition_adapter[name] = klass
- end
-
- def ambition_owner
- @owner || self
- end
- end
-end
View
62 lib/ambition/context.rb
@@ -1,62 +0,0 @@
-module Ambition #:nodoc:
- # This class includes several methods you will likely want to be accessing through your
- # Query and Translator classes:
- #
- # * +clauses+
- # * +owner+
- # * +stash+
- class Context
- undef_method :to_s
- include API
-
- # A hash of arrays, one key per processor.
- # So, if someone called User.select, your
- # +clauses+ hash would have a :select key with
- # an array of translated strings via your Select
- # class.
- #
- # This is accessible from your Query and Translator classes.
- attr_reader :clauses
-
- # The class everything was called on. Like `User`
- #
- # This is accessible from your Query and Translator classes.
- attr_reader :owner
-
- # A place for you to stick stuff. Available to all Translators and your Query class.
- #
- # This is accessible from your Query and Translator classes.
- attr_reader :stash
-
- def initialize(owner)
- @owner = owner
- @clauses = {}
- @stash = {}
- end
-
- # Gets the ambition_context. From a Ambition::Context, this is actually +self+.
- def ambition_context
- self
- end
-
- # Adds a clause to this context.
- def <<(clause)
- @clauses[clause.key] ||= []
- @clauses[clause.key] << clause.to_s
- self
- end
-
- def adapter_query
- Processors::Base.translator(self, :Query)
- end
-
- def method_missing(method, *args, &block)
- return super unless adapter_query.respond_to? method
- adapter_query.send(method, *args, &block)
- end
-
- def inspect
- "(Query object: call #to_s or #to_hash to inspect, call an Enumerable (such as #each or #first) to request data)"
- end
- end
-end
View
7 lib/ambition/core_ext.rb
@@ -1,7 +0,0 @@
-# Object extensions to make metaprogramming a little easier.
-class Object
- def metaclass; (class << self; self end) end
- def meta_eval(&blk) metaclass.instance_eval(&blk) end
- def meta_def(name, &blk) meta_eval { define_method name, &blk } end
- def class_def(name, &blk) class_eval { define_method name, &blk } end
-end
View
6 lib/ambition/enumerable.rb
@@ -1,6 +0,0 @@
-module Ambition #:nodoc:
- Enumerable = ::Enumerable.dup
- Enumerable.class_eval do
- remove_method :find, :find_all
- end
-end
View
134 lib/ambition/processors/base.rb
@@ -1,134 +0,0 @@
-module Ambition #:nodoc:
- module Processors #:nodoc:
- class Base
- ##
- # Processing methods
- def process_proc(exp)
- # puts "=> #{exp.inspect}"
- receiver, body = process(exp.shift), exp.shift
- process(body)
- end
-
- def process_dasgn_curr(exp)
- (@receiver = exp.first).to_s
- end
- alias_method :process_dasgn, :process_dasgn_curr
-
- def process_array(exp)
- # Branch on whether this is straight Ruby or a real array
- rubify(exp) || exp.map { |m| process(m) }
- end
-
- def process_str(exp)
- exp.first
- end
-
- def process_lit(exp)
- exp.first
- end
-
- def process_nil(exp)
- nil
- end
-
- def process_true(exp)
- true
- end
-
- def process_false(exp)
- false
- end
-
- def process_dvar(exp)
- target = exp.shift
- value(target.to_s[0..-1])
- end
-
- def process_ivar(exp)
- value(exp.shift.to_s[0..-1])
- end
-
- def process_lvar(exp)
- value(exp.shift.to_s)
- end
-
- def process_vcall(exp)
- value(exp.shift.to_s)
- end
-
- def process_gvar(exp)
- value(exp.shift.to_s)
- end
-
- def process(node)
- node ||= []
-
- if node.is_a? Symbol
- node
- elsif respond_to?(method = "process_#{node.first}")
- send(method, node[1..-1])
- elsif node.blank?
- ''
- else
- raise "Missing process method for sexp: #{node.inspect}"
- end
- end
-
- ##
- # Helper methods
- def to_s
- process SexpTranslator.translate(@block)
- end
-
- def key
- self.class.name.split('::').last.downcase.intern
- end
-
- def value(variable)
- eval variable, @block
- end
-
- # Gives you the current translator. Uses +self.translator+ to look it up,
- # if it isn't known yet.
- def translator
- @translator ||= self.class.translator(@context)
- end
-
- def self.translator(context, name = nil)
- # Grok the adapter name
- name ||= self.name.split('::').last
-
- # Get the module for it
- klass = context.owner.ambition_adapter.const_get(name)
- instance = klass.new
-
- # Make sure that the instance has everything it will need:
- #
- # * context
- # * owner
- # * clauses
- # * stash
- # * negated?
- unless instance.respond_to? :context
- klass.class_eval do
- attr_accessor :context, :negated
- def owner; @context.owner end
- def clauses; @context.clauses end
- def stash; @context.stash end
- def negated?; @negated end
- end
- end
-
- instance.context = context
- instance
- end
-
- def rubify(exp)
- # TODO: encapsulate this check in Ruby.should_process?(exp)
- if exp.first.first == :call && exp.first[1].last != @receiver && Array(exp.first[1][1]).last != @receiver
- value Ruby.process(exp.first)
- end
- end
- end
- end
-end
View
26 lib/ambition/processors/ruby.rb
@@ -1,26 +0,0 @@
-require 'ruby2ruby'
-
-module Ambition #:nodoc:
- module Processors #:nodoc:
- class Ruby < RubyToRuby
- def self.process(node)
- @processor ||= new
- @processor.process node
- end
-
- ##
- # This is not DRY, and I don't care.
- def process(node)
- node ||= []
-
- if respond_to?(method = "process_#{node.first}")
- send(method, node[1..-1])
- elsif node.blank?
- ''
- else
- raise "Missing process method for sexp: #{node.inspect}"
- end
- end
- end
- end
-end
View
105 lib/ambition/processors/select.rb
@@ -1,105 +0,0 @@
-module Ambition #:nodoc:
- module Processors #:nodoc:
- class Select < Base
- def initialize(context, block)
- @context = context
- @block = block
- end
-
- def process_call(args)
- # Operation (m.name == 'chris')
- # [[:call, [:dvar, :m], :name], :==, [:array, [:str, "chris"]]]
- if args.size == 3
- left, operator, right = args
-
- # params are passed as an array, even when only one element:
- # abc(1)
- # => [:fcall, :abc, [:array, [:lit, 1]]
- # abc([1])
- # => [:fcall, :abc, [:array, [:array, [:lit, 1]]]]
- if right.first == :array
- right = process(right)
- right = right.is_a?(Array) ? right.first : right
- else
- right = process(right)
- end
-
- translator.send(process_operator(operator), process(left), right)
-
- # Property of passed arg:
- # [[:dvar, :m], :name]
- elsif args.first.last == @receiver
- translator.call(*args[1..-1])
-
- # Method call:
- # [[:call, [:dvar, :m], :name], :upcase]
- elsif args.first.first == :call && args.first[1].last == @receiver
- receiver, method = args
- translator.chained_call(receiver.last, method)
-
- # Deep, chained call:
- # [[:call, [:call, [:call, [:dvar, :m], :created_at], :something], :else], :perhaps]
- elsif args.flatten.include? @receiver
- calls = []
-
- until args.empty?
- args = args.last if args.last.is_a?(Array)
- break if args.last == @receiver
- calls << args.pop
- end