15

I have the following models

class Order < AR::Base
  has_many :products

  accepts_nested_attributes_for :products
end

class Product < AR::Base
  belongs_to :order
  has_and_belongs_to_many :stores

  accepts_nested_attributes_for :stores
end

class Store < AR::Base
  has_and_belongs_to_many :products
end

Now I have a order view where I want to update the shops for the product. The thing is that I only want to connect the products to the existing shops in my db, not create new ones.

My form in the order view looks like this (using Formtastic):

= semantic_form_for @order do |f|
  = f.inputs :for => :live_products do |live_products_form|
    = live_products_form.inputs :for => :stores do |stores_form|
      = stores_form.input :name, :as => :select, :collection => Store.all.map(&:name)

Although its nested it works fine. The problem is that, when I select a store and try to update the order (and the products and stores with it), Rails tries to create a new store with that name. I want it to just use the existing store and connect the product to that.

Any help appreciated!

EDIT 1:

In the end I solved this problem in a very crude way:

# ProductsController

def update
  [...]

  # Filter out stores
  stores_attributes = params[:product].delete(:stores_attributes)

  @product.attributes = params[:product]

  if stores_attributes.present?
    # Set stores
    @product.stores = stores_attributes.map do |store_attributes|
      # This will raise RecordNotFound exception if a store with that name doesn't exist
      Store.find_by_name!(store_attributes[:name])
    end
  end

  @order.save

  [...]
end

EDIT 2:

Pablo's solution is much more elegant and should be preferred over mine.

Manuel Meurer
  • 3,170
  • 6
  • 31
  • 50
  • After reviewing the docs for a_n_a_f (http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html) I first got excited when I saw the update_only option but quickly realized there is no way to do what I want to do (update_only updates the existing objects before creating new ones). – Manuel Meurer Nov 23 '10 at 20:13

2 Answers2

24

Try to implement a :reject_if that check if the Store already exists and then use it:

class Product < AR::Base
  belongs_to :order
  has_and_belongs_to_many :stores

  accepts_nested_attributes_for :stores, :reject_if => :check_store

  protected

    def check_store(store_attr)
      if _store = Store.find(store_attr['id'])
        self.store = _store
        return true
      end
      return false
    end
end

I have this code working fine in a current project.

Please, let me know if you found a better solution.

Pablo
  • 368
  • 4
  • 7
  • Very clever! I solved the problem in a much less elegant way (I'll edit my question to show it) but your solution should work better. – Manuel Meurer May 06 '11 at 10:04
  • 10
    This doesn't make sense to me. `self` in `check_store` is the Product... and a Product doesn't have a `store` relationship (it HABTM :stores). So what does this code actually do? Plus, it doesn't appear to update the found store. – davemyron Jun 15 '11 at 20:57
  • 1
    I tweaked it to work for my own use by finding the existing related record and updating its attributes *in the reject_if*. Seems hacky, of course, but it worked. – davemyron Jun 15 '11 at 21:20
  • Does someone find a solution since? – gaetanm Feb 21 '16 at 12:00
1

I had the same problem and solved it by adding :id to the nested parameter list.

def family_params
  params.require(:family).permit(:user_id, :address, people_attributes: [:id, :relation, :first_name, :last_name)
end
timeon
  • 1,984
  • 4
  • 17
  • 18