views:

34

answers:

5

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(:address) 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!

A: 

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

enokd
I don't want to allow many... but your link helped me out!
phil
A: 

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.

Lukas
Your comments are fair about the form structure and so on, but that doesn't actually fix the issue of why the address object is not saving (as far as I can see). I am using formtastic so unfortunately your solution doesn't apply directly to me. With your solution you can still end up with no address saved.
phil
Oh, and the field on the user object is current_shipping_address_id, so there is not a problem with the foreign key,
phil
A: 

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!
krunal shah
This doesn't work because current_shipping_address is not an array
phil
A: 

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!

phil
A: 

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
Lichtamberg