the thing that is different is that the raw sql facilities are made explicitly available not through the common methods that expect them, but through something else.
The orm supports building the sql piecemeal, e.g
find(select: "name, foo(bar) as baz",
conditions: 'x=y',
limit: 3)
this is a small step above a raw execute, and obviously ugly and low level. Also a somewhat obsolete practice, since for a few years you could write it as a composition of calls
select("name, foo(bar) as baz").
where('x=y').
limit(3)
Anyway the functionality is there to compose SQL via bits using an hash of parameters, moving on.
Now remember that ruby <2.0 does not support keyword arguments, so the common practice is to use one normal argument with an hash value wich contains the keyword args.
Rails has this (antipattern imo) of accepting arguments in a dozen way for some methods e.g.
find(1)
find(:first)
find([1])
find(1,limit: 1)
let us not argue whether this is good, it's there.
And AR has dynamically generated finders (which Django does not have AFAIR).
One would expect the dynamically generated finder to be doing
def find_by_foo arg
where(foo, arg).limit(1)
end
but in reality it does
def find_by_foo *args
many_options = args.extract_options!
opts = combine_with_foo_handling(many_options)
find(opts)
end
and here you get the problem that you may be unknowingly passing an hash object wich builds sql piecemeal.
Notice that, as others already pointed out, usually as a user you shouldn't be able to create custom objects of the kind that exploits this issue (an hash with symbols as keys) unless your have other vulnerabilities already.