activerecord-hackery / squeel

Active Record, improved. Live again :)

Home Page:http://erniemiller.org/2013/11/17/anyone-interested-in-activerecord-hackery/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AR dynamic finders fail when using symbols

hoverlover opened this issue · comments

AR finders like find_by_name :john fail when the squeel gem is installed, but when removed they work just fine. I'm not sure I agree with using a symbol for the value, but Rails allows it. This can cause confusion if a project already has symbols in it's code, and then introduces squeel, only to start getting errors.

I've created a sample project with specs that show the behavior that fails. Let me know if you need anything else.

I understand how you feel, but I think that if it causes people to fix already-questionable code, it's a worthwhile tradeoff. Sorry :(

Good point :)

On Oct 1, 2011, at 9:54 AM, Ernie Miller
reply@reply.github.com
wrote:

I understand how you feel, but I think that if it causes people to fix already-questionable code, it's a worthwhile tradeoff. Sorry :(

Reply to this email directly or view it on GitHub:
#67 (comment)

Rails queries eventually get passed to this method:

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/predicate_builder.rb

That method enumerates the legal classes for a query value: Relation, Array, Range, AR::Base…. The big case statement ends with an "else" clause, which explicitly allows any object to be used as a query value. Note that String does not appear in that list; it's handled by the else clause. Values passed through that else clause eventually have their to_s method called in the bowel of Arel.

In short, Rails queries are built to allow any value that responds to the to_s method (which include symbols). Call it questionable or not, but squeel is explicitly narrowing the set of valid inputs on existing ActiveRecord methods. That is a Bad Thing™.

Another good point!

I think you are misunderstanding the point of that "else". It's to
allow other kinds of objects to be passed to the RHS of an ARel
Equality node. While its true that a symbol gets eventually converted
to a string by the visitor, that's ARel behavior, and it's not being
specified here by ActiveRecord.

It's also possible to pass in other kinds of ARel nodes directly as a
value. These will not be converted to strings.

In short, the point of the else in this code is to indicate that other
types of values should not be artificially restricted, and that
they'll be handled in some kind of appropriate manner by another
layer. The code itself says nothing about what that handling might
look like, nor should it. In AR documentation, symbols are never shown
as a value in examples.

Sent from my iPhone

On Oct 3, 2011, at 12:17 AM, pcantrell
reply@reply.github.com
wrote:

Rails queries eventually get passed to this method:

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/predicate_builder.rb

That method enumerates the legal classes for a query value: Relation, Array, Range, AR::Base…. The big case statement ends with an "else" clause, which explicitly allows any object to be used as a query value. Note that String does not appear in that list; it's handled by the else clause. Values passed through that else clause eventually have their to_s method called in the bowel of Arel.

In short, Rails queries are built to allow any value that responds to the to_s method (which include symbols). Call it questionable or not, but squeel is explicitly narrowing the set of valid inputs on existing ActiveRecord methods. That is a Bad Thing™.

Reply to this email directly or view it on GitHub:
#67 (comment)

I think you are misunderstanding the point of that "else".

Wrong. Disagreeing about it.

In short, the point of the else in this code is to indicate that other
types of values should not be artificially restricted

…which is what you're doing, in effect.

In AR documentation, symbols are never shown
as a value in examples.

Indeed, the weakness of the specification is the root problem. The docs neither explicitly allow nor explicitly disallow symbols.

It seems to me that in the absence of a clear answer, the right behavior is to leave AR alone. Your site: "Squeel enhances the normal ActiveRecord query methods." Not "Squeel changes the ActiveRecord query methods." The word "enhances" suggests that existing behavior is preserved. It suggests that I can safely add Squeel to a project and not have existing AR queries break. That is currently not true.

I've made my case that it's not Squeel's job to change behavior it doesn't add. I don't think there's much more I can say. It sounds like you're not open to changing your mind, and our immediate problem is easily fixed.

On Oct 3, 2011, at 12:15 PM, pcantrell wrote:

I think you are misunderstanding the point of that "else".

Wrong. Disagreeing about it.

In short, the point of the else in this code is to indicate that other
types of values should not be artificially restricted

…which is what you're doing, in effect.

I'm not restricting a value, I'm specifying a contextually-relevant behavior where no behavior was previously specified (note that I say "specified" here, not "provided"). I don't think it's a huge stretch to say that the most sensible mapping for a Ruby identifier is a database identifier, at AR's abstraction layer.

In AR documentation, symbols are never shown
as a value in examples.

Indeed, the weakness of the specification is the root problem. The docs neither explicitly allow nor explicitly disallow symbols.

It seems to me that in the absence of a clear answer, the right behavior is to leave AR alone. Your site: "Squeel enhances the normal ActiveRecord query methods." Not "Squeel changes the ActiveRecord query methods." The word "enhances" suggests that existing behavior is preserved. It suggests that I can safely add Squeel to a project and not have existing AR queries break. That is currently not true.

I don't really feel that's a fair assertion, in this case. If the behavior you previously observed were actually specified somewhere, that would be one thing. I'd probably still opt to change it, because AR already has a way of saying "string value", and that's "use a String", but I'd at least flag the caveat. I don't think it's reasonable to cry foul when a library breaks undocumented and (I would contend) unintentional use cases.

I'm not restricting a value, I'm specifying a contextually-relevant behavior where no behavior was previously specified (note that I say "specified" here, not "provided"). I don't think it's a huge stretch to say that the most sensible mapping for a Ruby identifier is a database identifier, at AR's abstraction layer.

I don't follow the argument. What's the new contextually relevant behavior you're adding? I thought you were only breaking behavior. Maybe there's a part of this equation I'm missing.

I don't really feel that's a fair assertion, in this case. If the behavior you previously observed were actually specified somewhere, that would be one thing. … I don't think it's reasonable to cry foul when a library breaks undocumented and (I would contend) unintentional use cases.

But Rails isn't really specified, only documented. Most of Ruby itself is not specified. (The JRuby guys, who are friends of mine, run up against this all the time. All they have to go off of when things get really sticky is Matz's C code and a bunch of forum discussions.) Ain't no JCP for Rails, which is not necessarily a bad thing.

Our argument, I think, is about whether this use case really is "unintentional." It's a very common pattern in Ruby to not expect any specific arg type, but simply to call to_s or to_a (or now, in 1.9, more properly to_str or to_ary), or whatever the appropriate converter method is, and see if the object responds. That's what AR does in the absence of Squeel.

I'm not restricting a value, I'm specifying a contextually-relevant behavior where no behavior was previously specified (note that I say "specified" here, not "provided"). I don't think it's a huge stretch to say that the most sensible mapping for a Ruby identifier is a database identifier, at AR's abstraction layer.

I don't follow the argument. What's the new contextually relevant behavior you're adding? I thought you were only breaking behavior. Maybe there's a part of this equation I'm missing.

Ernie can correct me if I'm wrong, but I think the contextually-relavent behavior to which he's referring is a symbol should indicate a database identifier, not a column value.

@hoverlover close -- a column name is an identifier. So, for instance, to find all people with the same first and last name:

Person.where(:first_name => :last_name).to_sql
# => SELECT "people".* FROM "people"  WHERE "people"."first_name" = "people"."last_name"

close -- a column name is an identifier

@ernie, right. I knew you meant database column. I was just using your words:

I don't think it's a huge stretch to say that the most sensible mapping for a Ruby identifier is a database identifier, at AR's abstraction layer.

@hoverlover close -- a column name is an identifier. So, for instance, to find all people with the same first and last name:

Person.where(:first_name => :last_name).to_sql
# => SELECT "people".* FROM "people"  WHERE "people"."first_name" = "people"."last_name"

…and then of course there's no way to distinguish existing AR queries from this newly added behavior, so there's a tradeoff. Your intransigence about this suddenly makes a lot more sense!

@pcantrell I'm glad! I don't like to argue a point for no reason... well, most of the time. I just don't see a reasonable way to add the enhancement without breaking the undocumented "feature".

Yeah, I don't see a good way either.

Argument from principles rarely works in software without understand what problem the programmer is actually solving! Glad I (eventually) got it.

@ernie, regarding your example:

Person.where(:first_name => :last_name).to_sql
# => SELECT "people".* FROM "people"  WHERE "people"."first_name" = "people"."last_name"

It is actually NOT what stock ActiveRecord produces.
It gives this in the where clause: WHERE "people"."first_name" = 'last_name'.

Which definitely means that Squeel does change the meaning of that.

@dnagir You're right -- I was acknowledging that fact when I used it as an example, but giving a basis for my reasoning about it.

Then I get really confused. That was the main argument against symbols.
Unfortunately I still fail to see why symbols should break AR :-(

Especially when guys from rails core team said that it will work in the future rails (even though they don't encourage that).

Gonna make one last attempt to summon @tenderlove and/or @jonleighton to give an idea at to how bad a person I am for the reasoning given in #67 (comment) and #67 (comment) -- looking to release 1.0 in as kosher (per Rails Core team opinion) a condition as possible, but definitely aiming for before Railsconf is over.

Based on the reasoning that @jonleighton gave in https://groups.google.com/forum/?fromgroups#!topic/rubyonrails-core/NQJJzZ7R7S0 I'm gonna guess that I'm not too terribly out of line here.

Thanks for all the feedback, in either case.

@ernie I can't see that we're ever going to have where(:foo => :bar) == where(:foo => 'bar') as an intentional feature, so go for your life I'd say

I agree with Jon. :-)

Aaron Patterson
http://tenderlovemaking.com/

On Apr 22, 2012, at 1:26 PM, Jon Leightonreply@reply.github.com wrote:

@ernie I can't see that we're ever going to have where(:foo => :bar) == where(:foo => 'bar') as an intentional feature, so go for your life I'd say


Reply to this email directly or view it on GitHub:
#67 (comment)

@tenderlove ❤️ ❤️ (You get two of them because without your tireless efforts on ARel (and willingness to accept way too many of my pull requests!) Squeel wouldn't even be possible. THANK YOU! ❤️

Thanks a lot guys. So it's officially settled - symbols should not be used.