I have a form that displays information about a piece of equipment.
Among it's attributes is its site and building. The trick is that a
site can have many buildings. Instead of storing both the site and the
building in the model, I just store the building because I can always
get the site from the building. If I store both of them in the model,
I would have to validate on each save if the combination is valid. And
I don't want to go there.
So, a piece of equipment belongs to a building (through building_id)
and a building belongs to a site (through site_id).
Now, on the form I have two dependent selects. One for the building,
which depends on the selected value of the site, and one for the site
itself.
When the form is submitted, I need to remove site_id from the params
hash because otherwise I would get an error from ActiveRecord that
equipment.site_id is not a valid method (which would be correct) So,
that's easy.
Here's the problem. When I select the site and don't select a building
my model validation sees that building_id is missing and reloads the
form with an error. Great! BUT, I lose the previous selection for the
site because it's not in the model.
What do I do to maintain the "view state" of something that is not in
the model?
Thanks for the reply! Hmm, I don't see how that would help. See, I'm
losing site_id after the equipment model has determined that it can't
save the data from the form. It returns the equipment object and by
that time the site_id is gone. It seems like I need to store it in a
session variable or something. Or did I misunderstand your solution?
I like the cookie idea. The reason why I'm removing site_id from the
params hash is because, you're right, I'm generating the select using
the equipment model like so: <%= select(:equipment, :site_id,
Site.list) %>
But since site_id is not part of the equipment model, I defined a
method in that model that returns site_id through the relationship of
"equipment" with "building". And the side effect is of course that
site_id is now part of the params. I went with this approach because
it was so easy. But if I go with the cookie solution, I'm going to
have to use select_tag and then, for sure, there's no need for site_id
to be part of the params for the equipment model.
The way I understand the view state is maintained is as follows. When
a form representing a model is submitted and the validation fails, the
model object is returned to the view. It would be nice to be able to
modify that object before it's passed back to the view.
Ringo, thanks for your help. I think the solution is to use either a
session variable or a cookie. Because site_id is not part of the
model, I don't think there's a way around sessions or cookies.
I just tried it. Unfortunately, It didn't work. If site_id is not part
of the equipment model, how would "attr_accessor :site_id" help? I am
very curious about what you had in mind. Maybe I can use it after all.
Let me know when you have a minute.
All right, I've done more testing with attr_accessor :site_id. My
latest finding is that site_id IS returned upon an error in the
validation, BUT the site select doesn't restore the selection. I have
my site select defined as follows:
I have no idea how the selection is not maintained if
equipment.site_id does contain the value that I submitted.
Also, I forgot to mention that I WOULD like to have site_id as a model
method for equipment. I have it defined as:
def site_id
if self.building
return self.building.site.id
else
nil
end
end
So, instead of attr_acessor :site_id, I decided to go with
attr_writer :site_id. But here's the interesting part, if I do it that
way, I don't get the site_id back upon an error. But I do get it back
when I use attr_accessor. I don't understand it at all.
Any ideas? This is not such a big deal, but I'm very curious how to do
it elegantly.
I so hoped it would! Unfortunately, it didn't. Also, I don't see
":selected" as a valid option that can be passed to "select()". At
least not in the API docs.
Probably because your site_id method will only return a value if the
equipment has a building_id. Try this instead:
def site_id
if self.building
return self.building.site.id
else
return @site_id
end
end
You're absolutely right and I like that change a lot
But the select control still doesn't restore the selection after a
reload. I probably should look inside the code for select -- unless
you can think of something really obvious.
By default, post.person_id is the selected option. Specify :selected =>
value to use a different selection or :selected => nil to leave all
options unselected.
The only thing I can think of is that the function you are using to
produce the list values, Site.list(session[:customer_id]), is returning
the site.id as a string or something, so when the select list is
rendered, it does not match with the equipment.id?
Well, Dave, you did it after all. That's exactly what it was, only in
the opposite direction. I needed to convert site_id to an integer. My
Site.list looks as follows:
So, I think it's OK. But! When I submit the form, site_id comes in as
a string and attr_writer stores it as a string. So, all that needed to
be done was:
def site_id
if self.building
self.building.site.id
else
@site_id.to_i
end
end
Coming from the PHP world where such conversations are automatic, I'll
need to remind myself about such issues in Ruby. Which is not a
complaint, just something to watch out for. By the way, is that the
best place to convert @site_id to an integer? Or could it be done
somewhere else? I probably could define the "site_id=()" function
and convert it there.
I'd like to thank everybody and especially you, Dave for helping me
out.