Ruby语法解析疑问及Authorize.net代码赋值逻辑问题
Great question—let’s unpack this piece by piece, starting with how Ruby parses that chain of assignments, then addressing your two core concerns about nil errors and seemingly redundant overwrites.
Ruby treats method-based assignments (like obj.attr = value) as method calls (obj.attr=(value)), and follows right-to-left associativity for nested expressions. So your example:
request.transactionRequest.payment = PaymentType.new( request.transactionRequest.payment.creditCard = CreditCardType.new('4242424242424242','0220','123') )
Gets parsed in this order:
- Inner assignment first: Execute
request.transactionRequest.payment.creditCard = CreditCardType.new(...). This calls thepaymentgetter ontransactionRequest, then callscreditCard=on that returned object with the newCreditCardTypeinstance. Ruby assignment expressions return the value being assigned, so this step returns theCreditCardTypeinstance. - Create new PaymentType: Pass that
CreditCardTypeinstance as an argument toPaymentType.new, creating a newPaymentTypeobject. - Outer assignment: Assign this new
PaymentTypeinstance torequest.transactionRequest.paymentvia itspayment=setter method.
payment Isn’t Set? This is all down to how the Authorize.Net SDK implements its getter methods. Most Ruby SDKs for APIs (especially those dealing with structured data like SOAP/REST requests) use lazy initialization for getters. Here’s a simplified example of what the SDK might be doing:
class TransactionRequest def payment # If @payment doesn't exist yet, create a new PaymentType instance @payment ||= PaymentType.new end def payment=(new_payment) @payment = new_payment end end
When you call request.transactionRequest.payment for the first time (before setting it), the getter automatically creates and returns a fresh PaymentType instance instead of nil. That’s why payment.creditCard= doesn’t throw a NoMethodError (Ruby’s version of a null pointer error)—you’re calling the method on a valid object, not nil.
payment Right After Setting creditCard? This part is the weird one—because in this code, the initial payment.creditCard= assignment is redundant and effectively useless. Here’s why:
- When you call
request.transactionRequest.paymentto setcreditCard, the getter creates a temporaryPaymentTypeinstance, sets itscreditCard, then discards it. - You immediately create a brand new
PaymentTypeinstance usingPaymentType.new(credit_card_instance)and assign it torequest.transactionRequest.payment, overwriting the temporary one you just modified.
This is likely either:
- A poorly written example in the SDK docs (maybe trying to demonstrate chaining but missing the mark), or
- A misunderstanding of how the SDK’s constructor works. The
PaymentType.newmight accept aCreditCardTypedirectly to set itscreditCardattribute, making the intermediate assignment unnecessary.
A cleaner, more efficient version of this code would be:
# Option 1: Explicit steps (most readable) credit_card = CreditCardType.new('4242424242424242','0220','123') payment = PaymentType.new payment.creditCard = credit_card request.transactionRequest.payment = payment # Option 2: Use a constructor that accepts attributes (if supported) request.transactionRequest.payment = PaymentType.new( creditCard: CreditCardType.new('4242424242424242','0220','123') ) # Option 3: Block-based initialization (common in Ruby SDKs) request.transactionRequest.payment = PaymentType.new do |payment| payment.creditCard = CreditCardType.new('4242424242424242','0220','123') end
内容的提问来源于stack exchange,提问作者Chloe

