Search code examples
ruby-on-railstwitter-bootstrapruby-on-rails-4postgresql-9.3

Rails 4 form that saves parent id values in a children record in a join table


i'm developing in Rails 4 with bootstrap rails forms and i'm trying to make a form that saves a product to a table. The product has many inputs and the input could belong to many products. The DB is postgres.

My main issue here is to save the product_id and the input_id to a join table I've created, named "productinput". this could be an example of what i'm trying to accomplish:

productinput table values example to generate each time i generate a new product with many inputs selected from a checkbox select:

product_id: 1 input_id: 1 product_id: 1 input_id: 2 product_id: 1 input_id: 3 product_id: 1 input_id: 4

I've done most of the configuration in my app. please take a look:

**productinput model**

class Productinput < ActiveRecord::Base
belongs_to :input, inverse_of: :productinputs
belongs_to :product, inverse_of: :productinputs

validates_presence_of :user_id
validates_presence_of :input_id
validates_presence_of :product_id
end

**product model**

class Product < ActiveRecord::Base
has_many :productinputs, inverse_of: :product, :dependent => :destroy
has_many :inputs, :through => :productinputs
accepts_nested_attributes_for :inputs
accepts_nested_attributes_for :productinputs, :allow_destroy => true

has_many :clientproducts, inverse_of: :product
has_many :worktasks, inverse_of: :product

validates_presence_of :user_id

accepts_nested_attributes_for :clientproducts, :allow_destroy => true
accepts_nested_attributes_for :worktasks
end

**input model**

class Input < ActiveRecord::Base
has_many :productinputs, inverse_of: :input, :dependent => :destroy
has_many :products, :through => :productinputs
accepts_nested_attributes_for :productinputs, :allow_destroy => true

has_many :inputproviders, inverse_of: :input

validates_presence_of :user_id

accepts_nested_attributes_for :inputproviders
end

Then the product form i'd like to have updated with the Input field to add inputs dinamically with a multiple select:

<%= bootstrap_form_for( @product, layout: :horizontal, label_col: "col-sm-2 hide", control_col: "col-sm-12", label_errors: true) do |f| %>
<div class="container-fluid">
  <div class="row">
    <%= f.alert_message "Please fix the errors below." %>
  </div>
  <div class="row">
    <div class="col-xs-12">
      <%= f.text_field :name, hide_label: true, placeholder: "Name", icon: "tag" %>
      <%= f.text_field :description, hide_label: true, placeholder: "Description", icon: "align-justify" %>
      <%= f.text_field :stock, hide_label: true, placeholder: "Stock", icon: "book" %>
      <%= f.text_field :price, hide_label: true, placeholder: "Price", icon: "usd" %>
    </div>
  </div>
  <div class="row text-center">
    <%= f.submit "Submit Product", :class => 'btn btn-primary' %>
    <%= link_to t('.cancel', :default => t("helpers.links.cancel")),
                products_path, :class => 'btn btn-default' %>
  </div>
</div>

i've tried cocoon, nested_form, formtastic, simple_form gems, many tutorials and more that 2 weeks investigating this and i still don't understand how can i manage to do this, would you give me a hand please ?

Thanks,


Solution

  • This is how i manage to create children records in the join table from the parent form view:

    Models:

    class Product < ActiveRecord::Base
        has_many :productinputs, :dependent => :destroy
        has_many :inputs, through: :productinputs
        accepts_nested_attributes_for :productinputs, :allow_destroy => true
    
        has_many :clientproducts, inverse_of: :product
        has_many :worktasks, inverse_of: :product
    
        validates_presence_of :user_id
    end
    
    class Productinput < ActiveRecord::Base
    
        belongs_to :product
        belongs_to :input
    
        #validates_presence_of :user_id
        #validates_presence_of :input_id
        #validates_presence_of :product_id
    end
    
    class Input < ActiveRecord::Base
        has_many :productinputs
        has_many :products, through: :productinputs
        accepts_nested_attributes_for :productinputs, :allow_destroy => true
    
        has_many :inputproviders, inverse_of: :input
    
        validates_presence_of :user_id
    
        accepts_nested_attributes_for :inputproviders
    end
    

    Controller:

    Key here, the :inputs_ids[] fills up with an array of input elements from the "check_box_tag "product[input_ids][]"" in the form:

    def product_params
      parameters = params.require(:product).permit(:name, :description, :stock, :price, :input_ids => [], productinputs_attributes: [:user_id, :quantity])
      parameters[:user_id] = current_user.id
      parameters
    end
    

    Form:

    <%= simple_form_for @product, defaults: { input_html: { class: 'form-horizontal' } } do |f| %>
        <div class="row"> <%= f.error_notification id: 'user_error_message', class: 'form_error' %>
        </div>
        <div class="row">
          <div class="col-xs-12 col-md-6">
            <%= f.input :name, placeholder: 'Product Name', error: 'Username is mandatory, please specify one', label: false %>
            <%= f.input :description, placeholder: 'Enter Description', label: false %>
            <%= f.input :stock, placeholder: 'Stock', label: false %>
            <%= f.input :price, placeholder: 'Price', label: false %>
          </div>
          <div class="col-xs-12 col-md-6">
            <%= hidden_field_tag "product[input_ids][]", nil %>
            <% Input.where("user_id = ?", current_user.id).each do |input| %>
                <%= check_box_tag "product[input_ids][]", input.id,
                                  @product.input_ids.include?(input.id), id: dom_id(input) %>
                <%= label_tag dom_id(input), input.name %><br>
            <% end %>
          </div>
        </div>
        <div class="row text-center">
          <%= f.button :submit, :class => 'btn btn-primary' %>
          <%= link_to t('.cancel', :default => t("helpers.links.cancel")),
                      products_path, :class => 'btn btn-default' %>
        </div>
    
    <% end %>
    

    Hope this helps anyone who has the same issue that i did.

    Sorry if some people didn't understand what i was looking for.

    P.S: just to let you know, i could simplify the multiple checkbox with simple form from this:

    <%= hidden_field_tag "product[input_ids][]", nil %>
    <% Input.where("user_id = ?", current_user.id).each do |input| %>
        <%= check_box_tag "product[input_ids][]", input.id, @product.input_ids.include?(input.id), id: dom_id(input) %>
        <%= label_tag dom_id(input), input.name %><br>
    <% end %>
    

    to this:

    <%= f.association :inputs, :as => :check_boxes %>