Apart from the priority of the operands, this behaviour is the result of the short-circuit evaluation of logic operators.
When evauating and, if the first operand is found to be false, then there is no reason to evaluate the second. The value of a and b is therefore calculated as a ? b : a.
Likewise, the or operator contracts to a ? a : b.
These optimisations work fine logically, when we don't care about the specific value returned but only whether it is true or false. But it also allows for useful coding idioms, such as
( list = list || [] ) << item
which works because list = list || [] is evaluated as list = list (a no-op) if list is true (non-nil), or list = [] (creating a new empty array) if list has already been initialised, and so is "true".
So an expression like 2 and (c = 3) evaluates to 2 and 3 or 2 ? 3 : 2, so 3.