Status of Project

6 views
Skip to first unread message

trans

unread,
Oct 12, 2008, 10:49:28 AM10/12/08
to ambition.rb
Wondering what the current status of the project is?

I'm looking for something along these lines, but I also will need to
add SQL update and insert capabilities. Is that within the scope of
this library?

T.

Matthew King

unread,
Oct 13, 2008, 3:30:05 PM10/13/08
to ambit...@googlegroups.com

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?

trans

unread,
Oct 14, 2008, 12:15:19 AM10/14/08
to ambition.rb


On Oct 13, 3:30 pm, "Matthew King" <automatt...@gmail.com> wrote:
I haven't full worked it out. So lets see, how would an insert/update
query look in Ruby-ese? Well, at the simplest level we do something
like:

user = User.new
user.name = "Tom"

But with queries, we need to deal multiple records at once. so lets
say we have:

users = User.select { |m| m.name =~ /chris/ }

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

And more generally

users.update do |u|
u.age = 30
...
end

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

What do you think?

T.

Matthew King

unread,
Oct 14, 2008, 1:32:12 PM10/14/08
to ambit...@googlegroups.com
On Mon, Oct 13, 2008 at 11:15 PM, trans <tran...@gmail.com> wrote:
>
> I haven't full worked it out. So lets see, how would an insert/update
> query look in Ruby-ese? Well, at the simplest level we do something
> like:
>
> user = User.new
> user.name = "Tom"

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

trans

unread,
Oct 14, 2008, 9:44:09 PM10/14/08
to ambition.rb


On Oct 14, 1:32 pm, "Matthew King" <automatt...@gmail.com> wrote:

> To be clear, we're assuming User < ActiveRecord::Base, right?

Of course that's likely, but I'm not trying to assume anything about
the particular back-end.

> > 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.

I don't fully get the #kick thing. Do you need to manually call #kick
to work with the results? Why not use a lazy collection instead?

> 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.

Good point. This fluent notation is not necessary, so let not consider
it. (At least for now. It could be nice sugar in the future. Ruby 1.9
supports enumerators and it's not a far cry from that.)

> > 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

Ah, good point.

Also, another Enumerable notation that is standard Ruby is for Hash:

users.update(:age => 30)

So maybe #update is not that far off from being acceptable, even if it
has some addition capabilities (via a block).

> 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.

I'm not sure that's a good idea. When we use each, we expect access to
each object in turn. I wouldn't want to have to use #entries first.
Maybe there's a way to do things lazily though, so that each object
isn't actually loaded until called upon. (Of course one could force
load all the objects as you suggest, if need be.) Likewise for changes
to an object, they could be lazily collected and all applied at the
end of the loop.

Not sure. Coding such a thing could prove very tricky.

> > 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

I don't think the block notation is too much of a stretch -- we could
easily extend Array itself to handle it too. The semantics is really
just a shorthand for:

User.insert(-1, User.new(:name=>'...',:age=>30))

> 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:

I'm not quite sure what you mean. We could use any adapter. How would
ArrayFields help?

If I use ambition for my project I won't be using ActiveRecord, btw. I
will probably create a DBI adapter.

T.
Reply all
Reply to author
Forward
0 new messages