Shawn Shawn - 7 months ago 11
Ruby Question

Can access ActiveRecord object, but not its attributes

I'm rendering a view in Rails using

form_for
and
nested_form_fields
. Here,
@procedure_step
is a record that
has_many :procedure_step_actions
, each of which
belongs_to :error
, which is a
ProcedureError
that has (among some relations to other models) an integer
:code
that I'm trying to access and print out to the page. Here's my template:

<%= form_for @procedure_step do |f| %>
<%= f.nested_fields_for :procedure_step_actions do |act| %>
<%= act.object.error.code %>
<% end %>
<% end %>


When I run this, I get
undefined method 'code' for nil:NilClass
. Okay, so my relations are messed up and I can't access
act.object.error
, right? Changing my template to display that instead yields
#<ProcedureError:0x0000000ece02a8>
, which is what one would expect of a functioning relation. Dumping its contents to the screen using
debug
shows all the attributes of the record, including
code
, but I still can't access it with the original template! Clearly
act.object.error
is not
nil
, so Rails telling me that
act.object.error
is
nil
doesn't make any sense to me.

Frustrated, I tried to work around the problem by using
act.object.error.to_json
. This printed the correct JSON for the record with all its attributes. Using
JSON.load()
on this gave me a correct
Hash
of all the attributes, but using
[:code]
to try to access the code gives me
undefined method '[]' for nil:NilClass
. Again, I know that object isn't
nil
, but Rails still refuses to allow me to access it.

Running out of ideas, I tried to use regular expressions to pull the code out of the raw JSON string.
/"code":([0-9]+)/.match(act.object.error.to_json)
returned
#<MatchData "\"code\":69" 1:"69">
, which is right. I used
[1]
to try to access the code number that was matched, but again I got
undefined method '[]' for nil:NilClass
.

Enough with ActiveRecord, I thought to myself. I decided to turn to raw SQL queries. I got the ID of the error in question using
act.object.error_id
, then printed that to the screen first to make sure I could access it. Luckily, I could. Then I inserted it into my SQL query with
"... WHERE id = #{act.object.error_id}"
. I refreshed the page again and was greeted with a SQL error. It showed the final SQL query string I had generated, but it ended with
WHERE id =
. The ID of the error didn't get added to the string.
ProcedureError.find(action.object.error_id)
gave a similar error.

I'm totally out of ideas. What could possibly be preventing me from accessing one simple integer in so many different ways?

Answer

One thing I can see right off the bat is you are violating Law of Demeter here

act.object.error.code

The form object obviously has to stay but you can delegate access to the subobjects by making a method on the procedure_step which can help with handling nulls, and other error cases.

Try delegating that first as I'm not sure if the scope that is created by nested_forms_for will allow the ActiveRecord::Relation object to perform properly. I'll double check locally.

A delegation might look like the following

class ProcedureStepActions
   belongs_to :error
   def error_code
       @error.code
   end
end

EDIT:

Other things that might be helpful are the version of Ruby and Rails you are using and any other additional gems or libraries.