interesting question (possibly)

Hello,

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.

Hope this makes sense.

Thanks for your suggestion!

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.

Hi David,

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.

Thanks, Sergei

Hi Dave,

I understand how your solution would work now. Let me play with it a little bit longer to see if I forgot something.

Answering your question though, yes, I did remove the line of code that removed site_id from the hash.

Thank you for the follow-up!

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:

<%= select(:equipment, :site_id, Site.list(session[:customer_id]), { :include_blank => 'true' }) %>

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.

Do you mean that when your page is loaded the correct piece of equipment is not selected in your select box?

<%= select(:equipment, :site_id, Site.list(session[:customer_id]),

{ :include_blank => ‘true’, :selected => @equipment.site_id }) %>

Would this help?

Daniel

Do you mean that when your page is loaded the correct piece of equipment is not selected in your select box?

Yep. But it only happens when the page reloads after a validation error.

<%= select(:equipment, :site_id, Site.list(session[:customer_id]), { :include_blank => 'true', :selected => @equipment.site_id }) %>

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.

Thanks for the try anyway!

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 :slight_smile:

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.

Thanks for the post. I do appreciate it.

From the docs

http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#M000506

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.

From the docs

http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelp

Oops... I stand corrected!

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:

def self.list(customer_id)     find(:all, :conditions => "customer_id = #{customer_id}").collect {|p| [p.name, p.id]} end

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.

Thanks!!!

Coming from the PHP world where such conversations are automatic, I'll

I meant to say "conversions"