-
-
Save tenderlove/915852 to your computer and use it in GitHub Desktop.
# totally contrived example | |
User.select(Account[:name]).join(:accounts).where(User[:id].in(Group.where(:name => 'foo'))) |
OK, I get it now -- you were referring to using the result while still in Relation form. Makes more sense now.
I also like removing as much context as possible, and in the simpler cases, that would work great. It seems as though once joins enter the picture, a certain amount of context is necessary. That's why I piggy-(SQUEEL!!!)-back on JoinAssociation#table and JoinBase#table so heavily -- they take care of letting me know what the alias will be.
Consider this simple case, from the Squeel specs:
it 'maps wheres inside a hash to their appropriate association table' do
relation = Person.joins({
:children => {
:children => {
:parent => :parent
}
}
}).where({
:children => {
:children => {
:parent => {
:parent => { :name => 'bob' }
}
}
}
})
arel = relation.build_arel
arel.to_sql.should match /"parents_people_2"."name" = 'bob'/
end
It's not in block form, ignore that fact for now. I don't see how the model syntax would work. Lazy interpretation of conditions, however, gets us part of the way there:
ruby-1.9.2-p180 :004 > id_constraint = Squeel::DSL.evaluate {id.eq 100}
=> #<Squeel::Nodes::Predicate:0x00000101558280 @expr=:id, @method_name=:eq, @value=100>
ruby-1.9.2-p180 :005 > Article.joins(:person).where(id_constraint).to_sql
=> "SELECT "articles".* FROM "articles" INNER JOIN "people" ON "people"."id" = "articles"."person_id" WHERE "articles"."id" = 100"
ruby-1.9.2-p180 :006 > Article.joins(:person).where(:person => id_constraint).to_sql
=> "SELECT "articles".* FROM "articles" INNER JOIN "people" ON "people"."id" = "articles"."person_id" WHERE "people"."id" = 100"
I should note that long, long ago, before Rails 3 and ARel 1.x were in the wild, I once played with something similar, only throwing ARel predicates directly into the where_values, etc, then trying to swap their relations out from under them at the last minute. In the end, I found it too brittle (or more likely, my skill at making it work well too weak), and ended up with something not unlike what you see above.
WRT join context, I see your point. But at the same time we can't be sure that the aliases we're picking are correct. For example, selecting from a self join:
Person.joins(:people).select(Person[:name]) # => yikes!
Do you select from the original table name, or the aliased table name? Even with context, that case (I think) would be ambiguous. Either we can guess (and possibly go wrong), or give the user exactly what they asked for with no guesses. Of course that means they would have to do extra work in the case of aliases.
WRT lazy evaluation in Squeel: I like it. I think it's good enough to get the job done (though I think you should rename the method to eval
;-) ).
I think I'm more and more convinced of a couple things:
- There is no DSL for SQL better than SQL (we're putting lipstick on a pig)
- I think of people want to use
Model[:id]
, they should just use Squeel
Your example (self-referencing associations) is precisely the reason I need to maintaining the context, and can't stomach the Person[:name] solution. :( We need to "mount" the constraints, for lack of a better term, on the proper table alias. Squeel does that right now via mapping the hash keys (or keypaths in the case of children.children.children) against the corresponding joins in the JoinDependency, then grabbing the JoinAssociation's table. Hope that makes sense.
See my post in the other thread for the pull request for sample queries and actual output.
Also, I think I'm stealing your "putting lipstick on a pig" line for Squeel. It just fits.
lol! Please take it! :-D
@tenderlove: see activerecord-hackery/squeel@f33784217e3bbb030c21
FWIW: evaluate was a typical case of overthinking -- I didn't like stomping on Kernel#eval, even though there's no reason to care in this case. :)
WRT the user object, imagine we had this:
User.select
doesn't necessarily result in a User object. It depends on the context.I really like the block syntax of Squeel, but it seems to depend on the context in which it was executed. With the
Model[:attribute]
syntax, we can refer to attributes without context. That means we could have methods like:This frees us from context. We're allowed to compose query arguments without knowing the context in which they will be used. Not only that, we can write regular, testable, ruby objects (taking advantage of modules, inheritance, etc) to compose complex queries.
I think having sweet block syntax like Squeel syntax would complement regular method composition swimmingly. Maybe I'm overthinking though. WDYT?