The original developer, Chris Wanstrath, has moved on to other
projects. I have a passing familiarity with the code base and am
willing to do some maintenance work as I have free time. Major
enhancements likely won't fall into that category, but something might
pique my interest.
Regarding update and insert capability:
Ambition's original goal was to extend the search functionality of
existing database interface libraries. It relies on the client
library to define and return collections of objects. All Ambition
does is allow these adapters to act like Enumerable collections. I've
wondered whether and how we might add writability into Ambition, but
my imagination fails me in providing reasonable use cases.
What are yours? Could you sketch out the syntax you would like to be
able to use?
To be clear, we're assuming User < ActiveRecord::Base, right?
>
> But with queries, we need to deal multiple records at once. so lets
> say we have:
>
> users = User.select { |m| m.name =~ /chris/ }
This returns an instance of Ambition::Context, which can be turned
into an SQL string or a hash suitable for consumption by
AR::Base#find. It can also be "kicked" into directly returning
results, usually a collection of User instances.
SQL would look like this:
"SELECT * FROM users WHERE (users.name = 'Chris')"
The #kick method in the ActiveRecord adapter currently uses #find with
a hash instead of #find_by_sql with an SQL string.
>
> So if we said
>
> users.age = 30
>
> Then every user with 'chris' in their name would be made 30 year old.
> Now, I realize this this isn't perfectly feasible, because of
> potential method name clashes between the recordset object (users) and
> the elements (users[n]). So we would need a fluent interface:
>
> users.update.age = 30
Implementation-wise, this could craft an SQL statement using the WHERE
clause constructed by the Context.
"UPDATE users SET users.age = 30 WHERE (users.name REGEXP 'chris')"
The object returned by update would use method_missing on names ending
in "=". It could fire off the UPDATE during the method_missing
processing, avoiding the need for a separate kicker method. Then,
perhaps, it could kick off the original Context's query, thus
returning the set of affected objects. Unless the update changed the
fields used to find the records. Humph. Note also that this bypasses
ActiveRecord validations (which sometimes might be the goal).
However:
Ambition's original conceit was the idea that you could treat external
data sources as Enumerable collections. I.e., "Let's make database
queries look like Enumerable#select". So the result of Users.select {
... } should be treated as if it were an array. A developer browsing
code using Ambitionized collections ideally wouldn't be able to tell
that the collection was actually ORM-based without looking up the
class definition.
users.update.something=(value) isn't transparently an action on a
collection; isn't array semantics.
>
> And more generally
>
> users.update do |u|
> u.age = 30
> ...
> end
Better; closer to what Ambition is already doing. Let's make believe
that User.select { ... } gives us back an array of objects, and we
want to call :age= on each object. We'd normally do:
users.each do |u|
u.age = 30
end
As currently implemented, Context#each(&block) kicks off the query
using #entries, then calls #each on the resulting array. We could
instead make Context#each produce a new context, initializing it with
the WHERE construct from the first context. We would probably want
the methods that create UPDATEing contexts to kick themselves off,
instead of using a separate kicker method.
This change would mean that developers, to use a real #each call,
would need to kick off the original context with #entries, then use
#each on the resulting array.
>
> In the case of updates we may not want underlying data source to be
> updated immediately. Maybe transactions would be good here:
>
> users.transaction
> users.update.age = 30
> users.commit
>
> Insert would be basically the same but against the Class.
>
> User.insert do |u|
> u.name = "chris cringle"
> u.age = "30"
> end
Array#insert has conflicting semantics:
http://www.ruby-doc.org/core/classes/Array.html#M002195
More importantly for my slavish devotion to the original metaphor,
#insert seems like a leak from SQL into Ruby.
Sadly, however, the metaphor is already falling apart here: Ambition
pretends that User is a collection, but calling collection#new to add
a new member to the collection is unusual Ruby. If User were an
array, we'd use #<< or #insert to append an already constructed
object.
Perhaps this:
User << { :name => 'Chris', :age => 30 }
I've been wondering lately if an adapter that dispensed with
ActiveRecord might make things clearer and cleaner. Queries could
return instances of ArrayFields:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/269121
Matthew