问题
I'm using Devise and am trying to build nested models in my RegistrationsController.
Though this is not working. I can build nested models via
resource.build_nested_model
in the view, but not in the controller itself.
This is my registrationscontrollers' new-action
def new
super
resource.build_user_info
resource.user_info.languageskills.build
if params[:is_driver].to_i == 1
resource.build_driver
end
Rails.logger.debug(resource.build_user_info.inspect)
end
This is the output it generates:
Started GET "/en/sign_up?is_driver=1" for 127.0.0.1 at 2014-02-13 13:20:01 +0100
Processing by RegistrationsController#new as JS
Parameters: {"is_driver"=>"1", "locale"=>"en"}
Rendered registrations/_new_user_fields.html.erb (12.1ms)
Rendered registrations/new.html.erb within layouts/application (27.8ms)
Rendered layouts/_header.html.erb (2.8ms)
Rendered layouts/_messages.html.erb (0.2ms)
Rendered layouts/_footer.html.erb (0.6ms)
(0.2ms) begin transaction
(0.1ms) commit transaction
#<UserInfo id: nil, user_id: nil, first_name: nil, last_name: nil, year_of_birth: nil, city: nil, created_at: nil, updated_at: nil, gender_id: nil, interests: nil, about: nil, country_alpha2: nil>
Completed 200 OK in 117ms (Views: 96.5ms | ActiveRecord: 0.4ms)
Why is it not possible? I guess devise isn't saving my changes to the resource, when I build the associated models. The only way out I see is defining my own variable holding a whole copy of the updated resource. This is not a good practice, though.
What would you do?
回答1:
I think the problem is that the default RegistrationsController.new is just:
def new
build_resource({})
respond_with self.resource
end
(It depends on your version of devise, but it seems to be this or equivalent for quite a long way back.)
This means that the view is rendered (by respond_with) before you add your nested models in your overridden new because you do this after calling super. I think you have two options:
- Don't call
super. Just put the first line from the defaultnewat the start of yournew, and the last line from the defaultnewat the end of yours. - Override
build_resourceinstead ofnew. At the beginning of your overriddenbuild_resource, callsuperand then add the code that was in yournewafter thesuperin yourbuild_resource. The only extra thing you'll need to do is check whetherbuild_resourcewas called withnilor an empty hash, in which case you build your blank user_info etc., or if it was called with a non-empty hash then don't add your blank user_info etc., becausebuild_resourcemust have been called fromcreate, so the hash will contain whatever your user entered on the form for user_info, so you don't want to overwrite with a blank version! (you could perhaps check the current url or something like that instead of checking the hash parameter, but I personally like that a little less.)
I have used option 2 in the past. I like it because it still calls super, so we're still using devise's implementation of build_resource, whereas option 1 completely ignores devise's implementation of new -- and what if they make some important change to their new in future which you are then missing out on? (For example, there used to be a resource variable local to new, but it's now on self as you can see in the code above.) Option 2 is just a bit more fiddly because you need to check whether you should add your blank user_info etc. So it's up to your taste!
来源:https://stackoverflow.com/questions/21765482/rails-devise-build-nested-model-in-controller