开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜