Why does associated object not save?
I have a ruby (on rails) class:
class User < ActiveRecord::Base
# relationships
belongs_to :current_shipping_address, :class_name => "Address"
belongs_to :subscription
# Validators
validates_presence_of :subscription
validates_presence_of :current_shipping_address
end
I do this in a controller:
subscription = Subscription.new
address_info = params[:user].delete(:addr开发者_如何学Cess) rescue {}
@address = Address.new(address_info.merge(:country => "US"))
@user = User.new(params[:user].merge(:first_name => @address.first_name, :last_name => @address.last_name))
@user.subscription = subscription
@user.current_shipping_address = @address
@user.save!
At this point, incredibly, I have a @user that has been saved in the database but has no current_shipping_address (despite the validation). The subscription has also been saved in the database.
The address does NOT get saved.
What am I missing here? 1 - how does user get saved without the validation failing? 2 - why is the address not saved?
How can I alter this code so that the address gets saved (as I would expect it to)?
I am running this under ruby on rails 3.
Thanks!
You cannot have subscription and current_shipping_address saved by user in your case because, they are not simple fields in model User. You define them as model associated to User through belongs_to, I'm not sure about what you are willing to do, but if I understand correctly one way to do it is using nested attributes:
class User < ActiveRecord::Base
# relationships
has_many :current_shipping_addresses, :class_name => "Address", :dependant => destroy
has_many :subscriptions, :dependant => destroy
# Nesting
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :current_shipping_addresses
end
After that, when you then create and save a User, a subscription and current_shipping_address are saved whith it .
More on assocations here : http://guides.rubyonrails.org/association_basics.html
You need to tell it what the foreign key is if you're not sticking with the standard table structure. You'll just need to add:
belongs_to :current_shipping_address, :class_name => "Address", :foreign_key => "address_id"
or whatever column you are using to store the address id to the address table.
This is not the recommended way of doing nested attributes though. I would recommend using a fields_for
in your form rather than using the lines:
address_info = params[:user].delete(:address) rescue {}
@address = Address.new(address_info.merge(:country => "US"))
You can just do
<%= f.fields_for :current_shipping_address do |ff| %> # ... your address fields... <% end %>
which will then let you simply save the address when you run @user.save!
You can still add the :country => "US"
beforehand with
params[:user][:current_shipping_address][:country] = "US"
and then run save. Its really up to you though.
Try this way!
subscription = Subscription.new
address_info = params[:user].delete(:address) rescue {}
@user = User.new(params[:user].merge(:first_name => @address.first_name, :last_name => @address.last_name))
@user.subscription = subscription
@user.current_shipping_address << Address.new(address_info.merge(:country => "US"))
@user.save!
Seems the problem was that address was actually failing to save. not because of a validation but because of an error in a 'before_create' method (and yes I know I didn't give you the address object... I didn't think it important at the time!).
class Address < ActiveRecord::Base
# relationships
# Validators
validates_presence_of :city, :state, :country, :first_name, :last_name, :address_1
before_create :check_state
before_create :check_country
def check_state
retval = true
state.upcase!
if country == "US" and !US_STATES.map{|s| s[1]}.include?(state)
errors.add(:state, "Must be valid")
retval = false
end
retval
end
end
Check state was failing. But that meant that address passed the 'valid?' call, which it seems is all active record cares about. (This method really should be a validation)
I have switched to doing this (thanks enokd for the link!):
@user = User.new(params[:user].merge(:first_name => @address.first_name, :last_name => @address.last_name))
@user.build_subscription(:subscription_plan_id => @subscription_plan.id)
@user.build_current_shipping_address(address_info.merge(:country => "US"))
I haven't bothered to investigate fully, but, if address fails to save it stops the whole @user.save! Personally I think this is a little bit of bug perhaps or certainly an unexpected behaviour, but what do I know!
Try:
class User < ActiveRecord::Base
# relationships
has_one :current_shipping_address, :class_name => "Address", :dependant => destroy
has_many :subscriptions, :dependant => destroy
validates :current_shipping_address, :presence => true
end
精彩评论