Polymorphic address in Rails 4

Something tells me I am mostly a moron for having such difficulty getting polymorphic associations to work in a Rails 4.0 RC1 app I'm working on. Being a moron aside, I DID ask Stack Overflow to help with the problems I was seeing, and reposted the question Ruby on Rails' Google Group. No answers were forthcoming, so, this issue becomes the first I effectively figured out on my own.

So that's good, right?

Anyway, my issue was simple. I want street/mailing/billing addresses to be shared across businesses, customers, owners, etc. I don't want to code three different tables doing the same. A polymorphic association seems the way to go from what I gather from such associations. I poke around for guidance on this and do have some things to model after.

This site was useful, though dated. I am using Rails 4. That was written at the tail end of Rails 2 I believe. It did help me shrink my time because I'd never done a has_one association before and my initial problem was in the Rails console, I couldn't do something like:

b = Owner.first.businesses.first
b.address.create!(params n' stuff)

I kept getting a no_method error for create. That's when I really focused on the article above and realized he wasn't using create either. He was using:

b = Owner.first.businesses.first
b.build_address(params n' stuff) -- he actually wrote @customer.build_address

The build_association is a has_one thing. I know this now very well. Of course, I could have read the docs ahead of time, but, I read them during this. You can read up on this effectively at http://guides.rubyonrails.org/association_basics.html#has_one-association-reference if you're interested.

I follow that document pretty closely but it's not quite right. The "build_address" item in the new method of my Business controller DID allow the fields_for in my form to display. But, nothing was being written to the database. An empty record was being put in for the address with all nil values and not attached at all to the business it was added for. And, obviously, if a business were to actually be "edited" rather than created new, you'd see no address form at all, so I figured we would need that build_address method in the edit field at least.

I was correct on that, but, still nothing saved.

As always I go to Railscasts to see what Ryan has. And, he has some polymorphic stuff up which will be great when I add comments for different models. But it wasn't directly beneficial to what I wanted to do here. I read a lot on Stack Overflow. Checked the issues list in the Rails repository at Github. Read the Rails Google group. Nothing really laid out what I was seeing.

Largely because I was seeing a Rails 4 issue.

Strong parameters are built in to Rails 4. So I added the following to the address controller to account for this:

# Never trust parameters from the scary internet, only allow the white list through.
def address_params
  params.require(:address).permit(:line1, :line2, :city, :state, :zip)
end

This is what the scaffold would make for you naturally in Rails 4 if you used rails g scaffold for this. Strong parameters replaces the attr_accessible in the model from previous versions of Rails. So the model is just a reference to the polymorphic association for address and addressable and business added the has_one :address, as: addressable as well as the accepts_nested_attributes_for :address. That's all good, but still nothing is being saved. I even added the address_params hash to the appliction_controller.rb.

I realize now I'm being stupid. The addition of the address is taking place in the business controller and will NEVER see this params hash. Even in the application_controller.rb nothing changed. So it couldn't be that.

I see the params being passed in the log, and an insert attempted, but an unpermitted params reference to "address" keeps showing.

I'm asking for help at this point and trying to tweak it.

I see this section at github in the strong parameters repository. I don't understand why accepts_nested_attributes_for wouldn't do this for me, but I go into the Business controller and adjust the params hash there to look like this:

# Never trust parameters from the scary internet, only allow the white list through.
def business_params
  params.require(:business).permit(:name, :description, :address_attributes => [:line1, :line2, :city, :state, :zip])
end

One of the articles I read said the has_one association passes an :address_attributes through, so I put that there. Now things are different. Data is still not being saved, but I'm getting no unpermitted errors. And still no answers at help sites.

I decide to tweak my new, edit, create and update methods.

In new and edit I had:

@business.build_address

I changed that in new and edit and added to create and update the following:

@business.build_address(params[:address]) -- NOTE, :address_attributes also appears to work there.

Now data was saving. All the fields were entering the DB. The problem is TWo were. One was empty and one was complete on every new and every update. So, I remove the code above from the create and update methods in the business controller. Now I have ONE address created. CORRECTLY. Feeling good about myself, I open the business to edit the address and I get a blank address form. Nothing is being passed back to the form view.

So, I change my edit method in the business controller to look like this:

@business.address ||= @business.build_address(params[:address])

This basically says if there is a @business.address, show it, otherwise @business.address will be built.

Now I'm rolling. I now see the saved address when I edit the form. I edit the address. The edit is saved and shows again. But the OLD address associated with this remains. The addressable_id is simply changed to nil and a new address record is written. THAT is not what I want. Still no help coming from help sites. Nothing in the log at all.

I think to myself the update method should be used here. So I add the following line to the update method of the business controller:

@business.address.update(params[:address])

No behavior change, but now the log now shows a new unpermitted parameter.

It shows "ID" as unpermitted. So, I change my business controller params hash to read:

# Never trust parameters from the scary internet, only allow the white list through.
def address_params
  params.require(:address).permit(:id, :line1, :line2, :city, :state, :zip)
end

And on that, things work. I can create a business with an address. In fact, even if I don't fill in anything for the address, the address is created (given no validations at this point). I can update that address, complete or empty, and it updates the existing record. It works. I'm not sure this is the best or cleanest way, but I'll keep looking.

One issue exists in that if I manually delete a created address associated with a business and THEN try to edit that business, the update method I added above throws an error given there is no record to update. I can probably live without that. I THINK I am ok with creating a record for every business (and eventually user, owner, customer, etc.) even if it is empty and then only allowing the delete through the association rather than directly.

But I may tweak the code to find a way ONLY to create a complete address and not to write it empty.

In any case, things kind of work at present.

I'd love to hear if there's anything I can improve or should change.

For now, I move to the next thing.

Back
paperclip paperclip