Search code examples
ruby-on-railsruby-on-rails-4nested-formsmodel-associations

rails 4 nested form, 1 model dont associate id


I have an order form and when an order is created, a new customer is created as well. For this I have the following models:

class Customer < ActiveRecord::Base
    has_many :orders 
    has_many :subscriptions, through: orders
end

class Order < ActiveRecord::Base
    belongs_to :customer
    has_many :subscriptions

    accepts_nested_attributes_for :customer
    accepts_nested_attributes_for :subscriptions
end

class Subscription< ActiveRecord::Base
    belongs_to :order
    belongs_to :customer
end

On my order page I have this form:

= simple_form_for(@order) do |f|
    = render 'order_fields', f: f 
    = f.simple_fields_for :subscriptions do |subscription| 
        = render 'subscription_fields', subscription: subscription
    = f.simple_fields_for :customer do |customer| 
        = render 'customer_fields', customer: customer 
    = f.button :submit

In my OrderController I have:

def new
    @order = Order.new
    @order.build_customer
    @order.subscriptions.build
end

def create
    @order = Order.new(order_params)

    if @order.save       
        (.... etc ...)
end

private
    def order_params
        params.require(:order).permit(
            :amount,
            customer_attributes: [ :id, :email, :password, :password_confirmation], 
            subscriptions_attributes: [ :id, :product_id, :customer_id])
    end

Almost everything goes well:
- User is created
- Order is created and has customer_id = User.id
- Subscription is created and has order_id = Order.id

But somehow it wont associate the subscription to the customer :(
I keep having Subscription.customer_id = nil

Can someone please point me in the right direction? Is there something wrong in the models? or in the controllers? I have no idea anymore where to look.


Solution

  • Your relationships are set up a little different. Instead of creating a customer_id field on Subscription, I'd expect you'd just have a has_one :customer, through: :order.

    If you do this you won't have need for a customer_id attribute on your Subscription model anymore. And if you want the id of the customer from the world-view of a subscription you'd call subscription.customer.id.

    You may also want to add inverse_of designations for your relationships in your models (always a good practice to minimize reloading of models from the database).

    So, in total, I'd recommend:

    class Customer < ActiveRecord::Base
      has_many :orders, inverse_of: :customer
      has_many :subscriptions, through: orders
    end
    
    class Order < ActiveRecord::Base
      belongs_to :customer, inverse_of: :orders
      has_many :subscriptions, inverse_of: :order
    
      accepts_nested_attributes_for :customer
      accepts_nested_attributes_for :subscriptions
    end
    
    class Subscription< ActiveRecord::Base
      belongs_to :order, inverse_of: :subscriptions
      has_one :customer, through: :order # THIS IS THE KEY CHANGE
    end
    

    Oh, and then you can remove the customer_id from the permitted attributes for subscriptions_attributes.

    UPDATE

    Given that Subscription#customer_id is meant to be disjointed from the Customer -> Order -> Subscription relationship... Ignore the above (except for perhaps the inverse_of stuff) and... You should be able to do:

    class Customer < ActiveRecord::Base
      has_many :subscriptions, through: :orders, after_add: :cache_customer_id
    
      private
    
      def cache_customer_id(subscription)
        subscription.update_column(:customer_id, self.id)
      end
    end